aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/pprof
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2016-04-12 09:41:11 +0200
committerDmitry Vyukov <dvyukov@google.com>2016-04-12 15:02:28 +0000
commit204b6f48c5107d3132033324fd492ca0253568dc (patch)
tree32f973c80d3abf12a6122f1e148c1ace3a8726b4 /src/cmd/pprof
parent9743e4b0311c37ebacc2c9063a1cd778510eae09 (diff)
downloadgo-204b6f48c5107d3132033324fd492ca0253568dc.tar.gz
go-204b6f48c5107d3132033324fd492ca0253568dc.zip
cmd/pprof/internal: move to cmd/internal/pprof
Make internal pprof packages available to cmd/trace. cmd/trace needs access to them to generate symbolized svg profiles (create and serialize Profile struct). And potentially generate svg programmatically instead of invoking go tool pprof. Change-Id: Iafd0c87ffdd4ddc081093be0b39761f19507907a Reviewed-on: https://go-review.googlesource.com/21870 Run-TryBot: Dmitry Vyukov <dvyukov@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Diffstat (limited to 'src/cmd/pprof')
-rw-r--r--src/cmd/pprof/internal/commands/commands.go243
-rw-r--r--src/cmd/pprof/internal/driver/driver.go1041
-rw-r--r--src/cmd/pprof/internal/driver/interactive.go492
-rw-r--r--src/cmd/pprof/internal/fetch/fetch.go82
-rw-r--r--src/cmd/pprof/internal/plugin/plugin.go213
-rw-r--r--src/cmd/pprof/internal/profile/encode.go470
-rw-r--r--src/cmd/pprof/internal/profile/filter.go158
-rw-r--r--src/cmd/pprof/internal/profile/legacy_profile.go1251
-rw-r--r--src/cmd/pprof/internal/profile/profile.go572
-rw-r--r--src/cmd/pprof/internal/profile/profile_test.go24
-rw-r--r--src/cmd/pprof/internal/profile/proto.go360
-rw-r--r--src/cmd/pprof/internal/profile/proto_test.go67
-rw-r--r--src/cmd/pprof/internal/profile/prune.go97
-rw-r--r--src/cmd/pprof/internal/report/report.go1682
-rw-r--r--src/cmd/pprof/internal/report/source.go454
-rw-r--r--src/cmd/pprof/internal/report/source_html.go77
-rw-r--r--src/cmd/pprof/internal/svg/svg.go71
-rw-r--r--src/cmd/pprof/internal/svg/svgpan.go291
-rw-r--r--src/cmd/pprof/internal/symbolizer/symbolizer.go195
-rw-r--r--src/cmd/pprof/internal/symbolz/symbolz.go111
-rw-r--r--src/cmd/pprof/internal/tempfile/tempfile.go45
-rw-r--r--src/cmd/pprof/pprof.go14
22 files changed, 7 insertions, 8003 deletions
diff --git a/src/cmd/pprof/internal/commands/commands.go b/src/cmd/pprof/internal/commands/commands.go
deleted file mode 100644
index 9aeee5762e..0000000000
--- a/src/cmd/pprof/internal/commands/commands.go
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package commands defines and manages the basic pprof commands
-package commands
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "runtime"
- "strings"
- "time"
-
- "cmd/pprof/internal/plugin"
- "cmd/pprof/internal/report"
- "cmd/pprof/internal/svg"
- "cmd/pprof/internal/tempfile"
-)
-
-// Commands describes the commands accepted by pprof.
-type Commands map[string]*Command
-
-// Command describes the actions for a pprof command. Includes a
-// function for command-line completion, the report format to use
-// during report generation, any postprocessing functions, and whether
-// the command expects a regexp parameter (typically a function name).
-type Command struct {
- Complete Completer // autocomplete for interactive mode
- Format int // report format to generate
- PostProcess PostProcessor // postprocessing to run on report
- HasParam bool // Collect a parameter from the CLI
- Usage string // Help text
-}
-
-// Completer is a function for command-line autocompletion
-type Completer func(prefix string) string
-
-// PostProcessor is a function that applies post-processing to the report output
-type PostProcessor func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error
-
-// PProf returns the basic pprof report-generation commands
-func PProf(c Completer, interactive **bool) Commands {
- return Commands{
- // Commands that require no post-processing.
- "tags": {nil, report.Tags, nil, false, "Outputs all tags in the profile"},
- "raw": {c, report.Raw, nil, false, "Outputs a text representation of the raw profile"},
- "dot": {c, report.Dot, nil, false, "Outputs a graph in DOT format"},
- "top": {c, report.Text, nil, false, "Outputs top entries in text form"},
- "tree": {c, report.Tree, nil, false, "Outputs a text rendering of call graph"},
- "text": {c, report.Text, nil, false, "Outputs top entries in text form"},
- "disasm": {c, report.Dis, nil, true, "Output annotated assembly for functions matching regexp or address"},
- "list": {c, report.List, nil, true, "Output annotated source for functions matching regexp"},
- "peek": {c, report.Tree, nil, true, "Output callers/callees of functions matching regexp"},
-
- // Save binary formats to a file
- "callgrind": {c, report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format"},
- "proto": {c, report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format"},
-
- // Generate report in DOT format and postprocess with dot
- "gif": {c, report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format"},
- "pdf": {c, report.Dot, invokeDot("pdf"), false, "Outputs a graph in PDF format"},
- "png": {c, report.Dot, invokeDot("png"), false, "Outputs a graph image in PNG format"},
- "ps": {c, report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format"},
-
- // Save SVG output into a file after including svgpan library
- "svg": {c, report.Dot, saveSVGToFile(), false, "Outputs a graph in SVG format"},
-
- // Visualize postprocessed dot output
- "eog": {c, report.Dot, invokeVisualizer(interactive, invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog"},
- "evince": {c, report.Dot, invokeVisualizer(interactive, invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince"},
- "gv": {c, report.Dot, invokeVisualizer(interactive, invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv"},
- "web": {c, report.Dot, invokeVisualizer(interactive, saveSVGToFile(), "svg", browsers()), false, "Visualize graph through web browser"},
-
- // Visualize HTML directly generated by report.
- "weblist": {c, report.WebList, invokeVisualizer(interactive, awayFromTTY("html"), "html", browsers()), true, "Output annotated source in HTML for functions matching regexp or address"},
- }
-}
-
-// browsers returns a list of commands to attempt for web visualization
-// on the current platform
-func browsers() []string {
- var cmds []string
- if exe := os.Getenv("BROWSER"); exe != "" {
- cmds = append(cmds, exe)
- }
- switch runtime.GOOS {
- case "darwin":
- cmds = append(cmds, "/usr/bin/open")
- case "windows":
- cmds = append(cmds, "cmd /c start")
- default:
- cmds = append(cmds, "xdg-open")
- }
- cmds = append(cmds, "chrome", "google-chrome", "firefox")
- return cmds
-}
-
-// NewCompleter creates an autocompletion function for a set of commands.
-func NewCompleter(cs Commands) Completer {
- return func(line string) string {
- switch tokens := strings.Fields(line); len(tokens) {
- case 0:
- // Nothing to complete
- case 1:
- // Single token -- complete command name
- found := ""
- for c := range cs {
- if strings.HasPrefix(c, tokens[0]) {
- if found != "" {
- return line
- }
- found = c
- }
- }
- if found != "" {
- return found
- }
- default:
- // Multiple tokens -- complete using command completer
- if c, ok := cs[tokens[0]]; ok {
- if c.Complete != nil {
- lastTokenIdx := len(tokens) - 1
- lastToken := tokens[lastTokenIdx]
- if strings.HasPrefix(lastToken, "-") {
- lastToken = "-" + c.Complete(lastToken[1:])
- } else {
- lastToken = c.Complete(lastToken)
- }
- return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
- }
- }
- }
- return line
- }
-}
-
-// awayFromTTY saves the output in a file if it would otherwise go to
-// the terminal screen. This is used to avoid dumping binary data on
-// the screen.
-func awayFromTTY(format string) PostProcessor {
- return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
- if output == os.Stdout && ui.IsTerminal() {
- tempFile, err := tempfile.New("", "profile", "."+format)
- if err != nil {
- return err
- }
- ui.PrintErr("Generating report in ", tempFile.Name())
- _, err = fmt.Fprint(tempFile, input)
- return err
- }
- _, err := fmt.Fprint(output, input)
- return err
- }
-}
-
-func invokeDot(format string) PostProcessor {
- divert := awayFromTTY(format)
- return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
- if _, err := exec.LookPath("dot"); err != nil {
- ui.PrintErr("Cannot find dot, have you installed Graphviz?")
- return err
- }
- cmd := exec.Command("dot", "-T"+format)
- var buf bytes.Buffer
- cmd.Stdin, cmd.Stdout, cmd.Stderr = input, &buf, os.Stderr
- if err := cmd.Run(); err != nil {
- return err
- }
- return divert(&buf, output, ui)
- }
-}
-
-func saveSVGToFile() PostProcessor {
- generateSVG := invokeDot("svg")
- divert := awayFromTTY("svg")
- return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
- baseSVG := &bytes.Buffer{}
- generateSVG(input, baseSVG, ui)
- massaged := &bytes.Buffer{}
- fmt.Fprint(massaged, svg.Massage(*baseSVG))
- return divert(massaged, output, ui)
- }
-}
-
-var vizTmpDir string
-
-func makeVizTmpDir() error {
- if vizTmpDir != "" {
- return nil
- }
- name, err := ioutil.TempDir("", "pprof-")
- if err != nil {
- return err
- }
- vizTmpDir = name
- return nil
-}
-
-func invokeVisualizer(interactive **bool, format PostProcessor, suffix string, visualizers []string) PostProcessor {
- return func(input *bytes.Buffer, output io.Writer, ui plugin.UI) error {
- if err := makeVizTmpDir(); err != nil {
- return err
- }
- tempFile, err := tempfile.New(vizTmpDir, "pprof", "."+suffix)
- if err != nil {
- return err
- }
- tempfile.DeferDelete(tempFile.Name())
- if err = format(input, tempFile, ui); err != nil {
- return err
- }
- tempFile.Close() // on windows, if the file is Open, start cannot access it.
- // Try visualizers until one is successful
- for _, v := range visualizers {
- // Separate command and arguments for exec.Command.
- args := strings.Split(v, " ")
- if len(args) == 0 {
- continue
- }
- viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)
- viewer.Stderr = os.Stderr
- if err = viewer.Start(); err == nil {
- // The viewer might just send a message to another program
- // to open the file. Give that program a little time to open the
- // file before we remove it.
- time.Sleep(1 * time.Second)
-
- if !**interactive {
- // In command-line mode, wait for the viewer to be closed
- // before proceeding
- return viewer.Wait()
- }
- return nil
- }
- }
- return err
- }
-}
diff --git a/src/cmd/pprof/internal/driver/driver.go b/src/cmd/pprof/internal/driver/driver.go
deleted file mode 100644
index 7cd1ddc928..0000000000
--- a/src/cmd/pprof/internal/driver/driver.go
+++ /dev/null
@@ -1,1041 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package driver implements the core pprof functionality. It can be
-// parameterized with a flag implementation, fetch and symbolize
-// mechanisms.
-package driver
-
-import (
- "bytes"
- "fmt"
- "io"
- "net/url"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "cmd/pprof/internal/commands"
- "cmd/pprof/internal/plugin"
- "cmd/pprof/internal/profile"
- "cmd/pprof/internal/report"
- "cmd/pprof/internal/tempfile"
-)
-
-// PProf acquires a profile, and symbolizes it using a profile
-// manager. Then it generates a report formatted according to the
-// options selected through the flags package.
-func PProf(flagset plugin.FlagSet, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, overrides commands.Commands) error {
- // Remove any temporary files created during pprof processing.
- defer tempfile.Cleanup()
-
- f, err := getFlags(flagset, overrides, ui)
- if err != nil {
- return err
- }
-
- obj.SetConfig(*f.flagTools)
-
- sources := f.profileSource
- if len(sources) > 1 {
- source := sources[0]
- // If the first argument is a supported object file, treat as executable.
- if file, err := obj.Open(source, 0); err == nil {
- file.Close()
- f.profileExecName = source
- sources = sources[1:]
- } else if *f.flagBuildID == "" && isBuildID(source) {
- f.flagBuildID = &source
- sources = sources[1:]
- }
- }
-
- // errMu protects concurrent accesses to errset and err. errset is set if an
- // error is encountered by one of the goroutines grabbing a profile.
- errMu, errset := sync.Mutex{}, false
-
- // Fetch profiles.
- wg := sync.WaitGroup{}
- profs := make([]*profile.Profile, len(sources))
- for i, source := range sources {
- wg.Add(1)
- go func(i int, src string) {
- defer wg.Done()
- p, grabErr := grabProfile(src, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f)
- if grabErr != nil {
- errMu.Lock()
- defer errMu.Unlock()
- errset, err = true, grabErr
- return
- }
- profs[i] = p
- }(i, source)
- }
- wg.Wait()
- if errset {
- return err
- }
-
- // Merge profiles.
- prof := profs[0]
- for _, p := range profs[1:] {
- if err = prof.Merge(p, 1); err != nil {
- return err
- }
- }
-
- if *f.flagBase != "" {
- // Fetch base profile and subtract from current profile.
- base, err := grabProfile(*f.flagBase, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f)
- if err != nil {
- return err
- }
-
- if err = prof.Merge(base, -1); err != nil {
- return err
- }
- }
-
- if err := processFlags(prof, ui, f); err != nil {
- return err
- }
-
- if !*f.flagRuntime {
- prof.RemoveUninteresting()
- }
-
- if *f.flagInteractive {
- return interactive(prof, obj, ui, f)
- }
-
- return generate(false, prof, obj, ui, f)
-}
-
-// isBuildID determines if the profile may contain a build ID, by
-// checking that it is a string of hex digits.
-func isBuildID(id string) bool {
- return strings.Trim(id, "0123456789abcdefABCDEF") == ""
-}
-
-// adjustURL updates the profile source URL based on heuristics. It
-// will append ?seconds=sec for CPU profiles if not already
-// specified. Returns the hostname if the profile is remote.
-func adjustURL(source string, sec int, ui plugin.UI) (adjusted, host string, duration time.Duration) {
- // If there is a local file with this name, just use it.
- if _, err := os.Stat(source); err == nil {
- return source, "", 0
- }
-
- url, err := url.Parse(source)
-
- // Automatically add http:// to URLs of the form hostname:port/path.
- // url.Parse treats "hostname" as the Scheme.
- if err != nil || (url.Host == "" && url.Scheme != "" && url.Scheme != "file") {
- url, err = url.Parse("http://" + source)
- if err != nil {
- return source, "", 0
- }
- }
- if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" {
- url.Scheme = ""
- return url.String(), "", 0
- }
-
- values := url.Query()
- if urlSeconds := values.Get("seconds"); urlSeconds != "" {
- if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
- if sec >= 0 {
- ui.PrintErr("Overriding -seconds for URL ", source)
- }
- sec = int(us)
- }
- }
-
- switch strings.ToLower(url.Path) {
- case "", "/":
- // Apply default /profilez.
- url.Path = "/profilez"
- case "/protoz":
- // Rewrite to /profilez?type=proto
- url.Path = "/profilez"
- values.Set("type", "proto")
- }
-
- if hasDuration(url.Path) {
- if sec > 0 {
- duration = time.Duration(sec) * time.Second
- values.Set("seconds", fmt.Sprintf("%d", sec))
- } else {
- // Assume default duration: 30 seconds
- duration = 30 * time.Second
- }
- }
- url.RawQuery = values.Encode()
- return url.String(), url.Host, duration
-}
-
-func hasDuration(path string) bool {
- for _, trigger := range []string{"profilez", "wallz", "/profile"} {
- if strings.Contains(path, trigger) {
- return true
- }
- }
- return false
-}
-
-// preprocess does filtering and aggregation of a profile based on the
-// requested options.
-func preprocess(prof *profile.Profile, ui plugin.UI, f *flags) error {
- if *f.flagFocus != "" || *f.flagIgnore != "" || *f.flagHide != "" {
- focus, ignore, hide, err := compileFocusIgnore(*f.flagFocus, *f.flagIgnore, *f.flagHide)
- if err != nil {
- return err
- }
- fm, im, hm := prof.FilterSamplesByName(focus, ignore, hide)
-
- warnNoMatches(fm, *f.flagFocus, "Focus", ui)
- warnNoMatches(im, *f.flagIgnore, "Ignore", ui)
- warnNoMatches(hm, *f.flagHide, "Hide", ui)
- }
-
- if *f.flagTagFocus != "" || *f.flagTagIgnore != "" {
- focus, err := compileTagFilter(*f.flagTagFocus, ui)
- if err != nil {
- return err
- }
- ignore, err := compileTagFilter(*f.flagTagIgnore, ui)
- if err != nil {
- return err
- }
- fm, im := prof.FilterSamplesByTag(focus, ignore)
-
- warnNoMatches(fm, *f.flagTagFocus, "TagFocus", ui)
- warnNoMatches(im, *f.flagTagIgnore, "TagIgnore", ui)
- }
-
- return aggregate(prof, f)
-}
-
-func compileFocusIgnore(focus, ignore, hide string) (f, i, h *regexp.Regexp, err error) {
- if focus != "" {
- if f, err = regexp.Compile(focus); err != nil {
- return nil, nil, nil, fmt.Errorf("parsing focus regexp: %v", err)
- }
- }
-
- if ignore != "" {
- if i, err = regexp.Compile(ignore); err != nil {
- return nil, nil, nil, fmt.Errorf("parsing ignore regexp: %v", err)
- }
- }
-
- if hide != "" {
- if h, err = regexp.Compile(hide); err != nil {
- return nil, nil, nil, fmt.Errorf("parsing hide regexp: %v", err)
- }
- }
- return
-}
-
-func compileTagFilter(filter string, ui plugin.UI) (f func(string, string, int64) bool, err error) {
- if filter == "" {
- return nil, nil
- }
- if numFilter := parseTagFilterRange(filter); numFilter != nil {
- ui.PrintErr("Interpreted '", filter, "' as range, not regexp")
- return func(key, val string, num int64) bool {
- if val != "" {
- return false
- }
- return numFilter(num, key)
- }, nil
- }
- fx, err := regexp.Compile(filter)
- if err != nil {
- return nil, err
- }
-
- return func(key, val string, num int64) bool {
- if val == "" {
- return false
- }
- return fx.MatchString(key + ":" + val)
- }, nil
-}
-
-var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)")
-
-// parseTagFilterRange returns a function to checks if a value is
-// contained on the range described by a string. It can recognize
-// strings of the form:
-// "32kb" -- matches values == 32kb
-// ":64kb" -- matches values <= 64kb
-// "4mb:" -- matches values >= 4mb
-// "12kb:64mb" -- matches values between 12kb and 64mb (both included).
-func parseTagFilterRange(filter string) func(int64, string) bool {
- ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2)
- if len(ranges) == 0 {
- return nil // No ranges were identified
- }
- v, err := strconv.ParseInt(ranges[0][1], 10, 64)
- if err != nil {
- panic(fmt.Errorf("Failed to parse int %s: %v", ranges[0][1], err))
- }
- value, unit := report.ScaleValue(v, ranges[0][2], ranges[0][2])
- if len(ranges) == 1 {
- switch match := ranges[0][0]; filter {
- case match:
- return func(v int64, u string) bool {
- sv, su := report.ScaleValue(v, u, unit)
- return su == unit && sv == value
- }
- case match + ":":
- return func(v int64, u string) bool {
- sv, su := report.ScaleValue(v, u, unit)
- return su == unit && sv >= value
- }
- case ":" + match:
- return func(v int64, u string) bool {
- sv, su := report.ScaleValue(v, u, unit)
- return su == unit && sv <= value
- }
- }
- return nil
- }
- if filter != ranges[0][0]+":"+ranges[1][0] {
- return nil
- }
- if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil {
- panic(fmt.Errorf("Failed to parse int %s: %v", ranges[1][1], err))
- }
- value2, unit2 := report.ScaleValue(v, ranges[1][2], unit)
- if unit != unit2 {
- return nil
- }
- return func(v int64, u string) bool {
- sv, su := report.ScaleValue(v, u, unit)
- return su == unit && sv >= value && sv <= value2
- }
-}
-
-func warnNoMatches(match bool, rx, option string, ui plugin.UI) {
- if !match && rx != "" && rx != "." {
- ui.PrintErr(option + " expression matched no samples: " + rx)
- }
-}
-
-// grabProfile fetches and symbolizes a profile.
-func grabProfile(source, exec, buildid string, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, f *flags) (*profile.Profile, error) {
- source, host, duration := adjustURL(source, *f.flagSeconds, ui)
- remote := host != ""
-
- if remote {
- ui.Print("Fetching profile from ", source)
- if duration != 0 {
- ui.Print("Please wait... (" + duration.String() + ")")
- }
- }
-
- now := time.Now()
- // Fetch profile from source.
- // Give 50% slack on the timeout.
- p, err := fetch(source, duration+duration/2, ui)
- if err != nil {
- return nil, err
- }
-
- // Update the time/duration if the profile source doesn't include it.
- // TODO(rsilvera): Remove this when we remove support for legacy profiles.
- if remote {
- if p.TimeNanos == 0 {
- p.TimeNanos = now.UnixNano()
- }
- if duration != 0 && p.DurationNanos == 0 {
- p.DurationNanos = int64(duration)
- }
- }
-
- // Replace executable/buildID with the options provided in the
- // command line. Assume the executable is the first Mapping entry.
- if exec != "" || buildid != "" {
- if len(p.Mapping) == 0 {
- // Create a fake mapping to hold the user option, and associate
- // all samples to it.
- m := &profile.Mapping{
- ID: 1,
- }
- for _, l := range p.Location {
- l.Mapping = m
- }
- p.Mapping = []*profile.Mapping{m}
- }
- if exec != "" {
- p.Mapping[0].File = exec
- }
- if buildid != "" {
- p.Mapping[0].BuildID = buildid
- }
- }
-
- if err := sym(*f.flagSymbolize, source, p, obj, ui); err != nil {
- return nil, err
- }
-
- // Save a copy of any remote profiles, unless the user is explicitly
- // saving it.
- if remote && !f.isFormat("proto") {
- prefix := "pprof."
- if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
- prefix = prefix + filepath.Base(p.Mapping[0].File) + "."
- }
- if !strings.ContainsRune(host, os.PathSeparator) {
- prefix = prefix + host + "."
- }
- for _, s := range p.SampleType {
- prefix = prefix + s.Type + "."
- }
-
- dir := os.Getenv("PPROF_TMPDIR")
- tempFile, err := tempfile.New(dir, prefix, ".pb.gz")
- if err == nil {
- if err = p.Write(tempFile); err == nil {
- ui.PrintErr("Saved profile in ", tempFile.Name())
- }
- }
- if err != nil {
- ui.PrintErr("Could not save profile: ", err)
- }
- }
-
- if err := p.Demangle(obj.Demangle); err != nil {
- ui.PrintErr("Failed to demangle profile: ", err)
- }
-
- if err := p.CheckValid(); err != nil {
- return nil, fmt.Errorf("Grab %s: %v", source, err)
- }
-
- return p, nil
-}
-
-type flags struct {
- flagInteractive *bool // Accept commands interactively
- flagCommands map[string]*bool // pprof commands without parameters
- flagParamCommands map[string]*string // pprof commands with parameters
-
- flagOutput *string // Output file name
-
- flagCum *bool // Sort by cumulative data
- flagCallTree *bool // generate a context-sensitive call tree
-
- flagAddresses *bool // Report at address level
- flagLines *bool // Report at source line level
- flagFiles *bool // Report at file level
- flagFunctions *bool // Report at function level [default]
-
- flagSymbolize *string // Symbolization options (=none to disable)
- flagBuildID *string // Override build if for first mapping
-
- flagNodeCount *int // Max number of nodes to show
- flagNodeFraction *float64 // Hide nodes below <f>*total
- flagEdgeFraction *float64 // Hide edges below <f>*total
- flagTrim *bool // Set to false to ignore NodeCount/*Fraction
- flagRuntime *bool // Show runtime call frames in memory profiles
- flagFocus *string // Restricts to paths going through a node matching regexp
- flagIgnore *string // Skips paths going through any nodes matching regexp
- flagHide *string // Skips sample locations matching regexp
- flagTagFocus *string // Restrict to samples tagged with key:value matching regexp
- flagTagIgnore *string // Discard samples tagged with key:value matching regexp
- flagDropNegative *bool // Skip negative values
-
- flagBase *string // Source for base profile to user for comparison
-
- flagSeconds *int // Length of time for dynamic profiles
-
- flagTotalDelay *bool // Display total delay at each region
- flagContentions *bool // Display number of delays at each region
- flagMeanDelay *bool // Display mean delay at each region
-
- flagInUseSpace *bool // Display in-use memory size
- flagInUseObjects *bool // Display in-use object counts
- flagAllocSpace *bool // Display allocated memory size
- flagAllocObjects *bool // Display allocated object counts
- flagDisplayUnit *string // Measurement unit to use on reports
- flagDivideBy *float64 // Ratio to divide sample values
-
- flagSampleIndex *int // Sample value to use in reports.
- flagMean *bool // Use mean of sample_index over count
-
- flagTools *string
- profileSource []string
- profileExecName string
-
- extraUsage string
- commands commands.Commands
-}
-
-func (f *flags) isFormat(format string) bool {
- if fl := f.flagCommands[format]; fl != nil {
- return *fl
- }
- if fl := f.flagParamCommands[format]; fl != nil {
- return *fl != ""
- }
- return false
-}
-
-// String provides a printable representation for the current set of flags.
-func (f *flags) String(p *profile.Profile) string {
- var ret string
-
- if ix := *f.flagSampleIndex; ix != -1 {
- ret += fmt.Sprintf(" %-25s : %d (%s)\n", "sample_index", ix, p.SampleType[ix].Type)
- }
- if ix := *f.flagMean; ix {
- ret += boolFlagString("mean")
- }
- if *f.flagDisplayUnit != "minimum" {
- ret += stringFlagString("unit", *f.flagDisplayUnit)
- }
-
- switch {
- case *f.flagInteractive:
- ret += boolFlagString("interactive")
- }
- for name, fl := range f.flagCommands {
- if *fl {
- ret += boolFlagString(name)
- }
- }
-
- if *f.flagCum {
- ret += boolFlagString("cum")
- }
- if *f.flagCallTree {
- ret += boolFlagString("call_tree")
- }
-
- switch {
- case *f.flagAddresses:
- ret += boolFlagString("addresses")
- case *f.flagLines:
- ret += boolFlagString("lines")
- case *f.flagFiles:
- ret += boolFlagString("files")
- case *f.flagFunctions:
- ret += boolFlagString("functions")
- }
-
- if *f.flagNodeCount != -1 {
- ret += intFlagString("nodecount", *f.flagNodeCount)
- }
-
- ret += floatFlagString("nodefraction", *f.flagNodeFraction)
- ret += floatFlagString("edgefraction", *f.flagEdgeFraction)
-
- if *f.flagFocus != "" {
- ret += stringFlagString("focus", *f.flagFocus)
- }
- if *f.flagIgnore != "" {
- ret += stringFlagString("ignore", *f.flagIgnore)
- }
- if *f.flagHide != "" {
- ret += stringFlagString("hide", *f.flagHide)
- }
-
- if *f.flagTagFocus != "" {
- ret += stringFlagString("tagfocus", *f.flagTagFocus)
- }
- if *f.flagTagIgnore != "" {
- ret += stringFlagString("tagignore", *f.flagTagIgnore)
- }
-
- return ret
-}
-
-func boolFlagString(label string) string {
- return fmt.Sprintf(" %-25s : true\n", label)
-}
-
-func stringFlagString(label, value string) string {
- return fmt.Sprintf(" %-25s : %s\n", label, value)
-}
-
-func intFlagString(label string, value int) string {
- return fmt.Sprintf(" %-25s : %d\n", label, value)
-}
-
-func floatFlagString(label string, value float64) string {
- return fmt.Sprintf(" %-25s : %f\n", label, value)
-}
-
-// Utility routines to set flag values.
-func newBool(b bool) *bool {
- return &b
-}
-
-func newString(s string) *string {
- return &s
-}
-
-func newFloat64(fl float64) *float64 {
- return &fl
-}
-
-func newInt(i int) *int {
- return &i
-}
-
-func (f *flags) usage(ui plugin.UI) {
- var commandMsg []string
- for name, cmd := range f.commands {
- if cmd.HasParam {
- name = name + "=p"
- }
- commandMsg = append(commandMsg,
- fmt.Sprintf(" -%-16s %s", name, cmd.Usage))
- }
-
- sort.Strings(commandMsg)
-
- text := usageMsgHdr + strings.Join(commandMsg, "\n") + "\n" + usageMsg + "\n"
- if f.extraUsage != "" {
- text += f.extraUsage + "\n"
- }
- text += usageMsgVars
- ui.Print(text)
-}
-
-func getFlags(flag plugin.FlagSet, overrides commands.Commands, ui plugin.UI) (*flags, error) {
- f := &flags{
- flagInteractive: flag.Bool("interactive", false, "Accepts commands interactively"),
- flagCommands: make(map[string]*bool),
- flagParamCommands: make(map[string]*string),
-
- // Filename for file-based output formats, stdout by default.
- flagOutput: flag.String("output", "", "Output filename for file-based outputs "),
- // Comparisons.
- flagBase: flag.String("base", "", "Source for base profile for comparison"),
- flagDropNegative: flag.Bool("drop_negative", false, "Ignore negative differences"),
-
- // Data sorting criteria.
- flagCum: flag.Bool("cum", false, "Sort by cumulative data"),
- // Graph handling options.
- flagCallTree: flag.Bool("call_tree", false, "Create a context-sensitive call tree"),
- // Granularity of output resolution.
- flagAddresses: flag.Bool("addresses", false, "Report at address level"),
- flagLines: flag.Bool("lines", false, "Report at source line level"),
- flagFiles: flag.Bool("files", false, "Report at source file level"),
- flagFunctions: flag.Bool("functions", false, "Report at function level [default]"),
- // Internal options.
- flagSymbolize: flag.String("symbolize", "", "Options for profile symbolization"),
- flagBuildID: flag.String("buildid", "", "Override build id for first mapping"),
- // Filtering options
- flagNodeCount: flag.Int("nodecount", -1, "Max number of nodes to show"),
- flagNodeFraction: flag.Float64("nodefraction", 0.005, "Hide nodes below <f>*total"),
- flagEdgeFraction: flag.Float64("edgefraction", 0.001, "Hide edges below <f>*total"),
- flagTrim: flag.Bool("trim", true, "Honor nodefraction/edgefraction/nodecount defaults"),
- flagRuntime: flag.Bool("runtime", false, "Show runtime call frames in memory profiles"),
- flagFocus: flag.String("focus", "", "Restricts to paths going through a node matching regexp"),
- flagIgnore: flag.String("ignore", "", "Skips paths going through any nodes matching regexp"),
- flagHide: flag.String("hide", "", "Skips nodes matching regexp"),
- flagTagFocus: flag.String("tagfocus", "", "Restrict to samples with tags in range or matched by regexp"),
- flagTagIgnore: flag.String("tagignore", "", "Discard samples with tags in range or matched by regexp"),
- // CPU profile options
- flagSeconds: flag.Int("seconds", -1, "Length of time for dynamic profiles"),
- // Heap profile options
- flagInUseSpace: flag.Bool("inuse_space", false, "Display in-use memory size"),
- flagInUseObjects: flag.Bool("inuse_objects", false, "Display in-use object counts"),
- flagAllocSpace: flag.Bool("alloc_space", false, "Display allocated memory size"),
- flagAllocObjects: flag.Bool("alloc_objects", false, "Display allocated object counts"),
- flagDisplayUnit: flag.String("unit", "minimum", "Measurement units to display"),
- flagDivideBy: flag.Float64("divide_by", 1.0, "Ratio to divide all samples before visualization"),
- flagSampleIndex: flag.Int("sample_index", -1, "Index of sample value to report"),
- flagMean: flag.Bool("mean", false, "Average sample value over first value (count)"),
- // Contention profile options
- flagTotalDelay: flag.Bool("total_delay", false, "Display total delay at each region"),
- flagContentions: flag.Bool("contentions", false, "Display number of delays at each region"),
- flagMeanDelay: flag.Bool("mean_delay", false, "Display mean delay at each region"),
- flagTools: flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames"),
- extraUsage: flag.ExtraUsage(),
- }
-
- // Flags used during command processing
- interactive := &f.flagInteractive
- f.commands = commands.PProf(functionCompleter, interactive)
-
- // Override commands
- for name, cmd := range overrides {
- f.commands[name] = cmd
- }
-
- for name, cmd := range f.commands {
- if cmd.HasParam {
- f.flagParamCommands[name] = flag.String(name, "", "Generate a report in "+name+" format, matching regexp")
- } else {
- f.flagCommands[name] = flag.Bool(name, false, "Generate a report in "+name+" format")
- }
- }
-
- args := flag.Parse(func() { f.usage(ui) })
- if len(args) == 0 {
- return nil, fmt.Errorf("no profile source specified")
- }
-
- f.profileSource = args
-
- // Instruct legacy heapz parsers to grab historical allocation data,
- // instead of the default in-use data. Not available with tcmalloc.
- if *f.flagAllocSpace || *f.flagAllocObjects {
- profile.LegacyHeapAllocated = true
- }
-
- if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir == "" {
- profileDir = os.Getenv("HOME") + "/pprof"
- os.Setenv("PPROF_TMPDIR", profileDir)
- if err := os.MkdirAll(profileDir, 0755); err != nil {
- return nil, fmt.Errorf("failed to access temp dir %s: %v", profileDir, err)
- }
- }
-
- return f, nil
-}
-
-func processFlags(p *profile.Profile, ui plugin.UI, f *flags) error {
- flagDis := f.isFormat("disasm")
- flagPeek := f.isFormat("peek")
- flagWebList := f.isFormat("weblist")
- flagList := f.isFormat("list")
-
- if flagDis || flagWebList {
- // Collect all samples at address granularity for assembly
- // listing.
- f.flagNodeCount = newInt(0)
- f.flagAddresses = newBool(true)
- f.flagLines = newBool(false)
- f.flagFiles = newBool(false)
- f.flagFunctions = newBool(false)
- }
-
- if flagPeek {
- // Collect all samples at function granularity for peek command
- f.flagNodeCount = newInt(0)
- f.flagAddresses = newBool(false)
- f.flagLines = newBool(false)
- f.flagFiles = newBool(false)
- f.flagFunctions = newBool(true)
- }
-
- if flagList {
- // Collect all samples at fileline granularity for source
- // listing.
- f.flagNodeCount = newInt(0)
- f.flagAddresses = newBool(false)
- f.flagLines = newBool(true)
- f.flagFiles = newBool(false)
- f.flagFunctions = newBool(false)
- }
-
- if !*f.flagTrim {
- f.flagNodeCount = newInt(0)
- f.flagNodeFraction = newFloat64(0)
- f.flagEdgeFraction = newFloat64(0)
- }
-
- if oc := countFlagMap(f.flagCommands, f.flagParamCommands); oc == 0 {
- f.flagInteractive = newBool(true)
- } else if oc > 1 {
- f.usage(ui)
- return fmt.Errorf("must set at most one output format")
- }
-
- // Apply nodecount defaults for non-interactive mode. The
- // interactive shell will apply defaults for the interactive mode.
- if *f.flagNodeCount < 0 && !*f.flagInteractive {
- switch {
- default:
- f.flagNodeCount = newInt(80)
- case f.isFormat("text"):
- f.flagNodeCount = newInt(0)
- }
- }
-
- // Apply legacy options and diagnose conflicts.
- if rc := countFlags([]*bool{f.flagAddresses, f.flagLines, f.flagFiles, f.flagFunctions}); rc == 0 {
- f.flagFunctions = newBool(true)
- } else if rc > 1 {
- f.usage(ui)
- return fmt.Errorf("must set at most one granularity option")
- }
-
- var err error
- si, sm := *f.flagSampleIndex, *f.flagMean || *f.flagMeanDelay
- si, err = sampleIndex(p, &f.flagTotalDelay, si, 1, "delay", "-total_delay", err)
- si, err = sampleIndex(p, &f.flagMeanDelay, si, 1, "delay", "-mean_delay", err)
- si, err = sampleIndex(p, &f.flagContentions, si, 0, "contentions", "-contentions", err)
-
- si, err = sampleIndex(p, &f.flagInUseSpace, si, 1, "inuse_space", "-inuse_space", err)
- si, err = sampleIndex(p, &f.flagInUseObjects, si, 0, "inuse_objects", "-inuse_objects", err)
- si, err = sampleIndex(p, &f.flagAllocSpace, si, 1, "alloc_space", "-alloc_space", err)
- si, err = sampleIndex(p, &f.flagAllocObjects, si, 0, "alloc_objects", "-alloc_objects", err)
-
- if si == -1 {
- // Use last value if none is requested.
- si = len(p.SampleType) - 1
- } else if si < 0 || si >= len(p.SampleType) {
- err = fmt.Errorf("sample_index value %d out of range [0..%d]", si, len(p.SampleType)-1)
- }
-
- if err != nil {
- f.usage(ui)
- return err
- }
- f.flagSampleIndex, f.flagMean = newInt(si), newBool(sm)
- return nil
-}
-
-func sampleIndex(p *profile.Profile, flag **bool,
- sampleIndex int,
- newSampleIndex int,
- sampleType, option string,
- err error) (int, error) {
- if err != nil || !**flag {
- return sampleIndex, err
- }
- *flag = newBool(false)
- if sampleIndex != -1 {
- return 0, fmt.Errorf("set at most one sample value selection option")
- }
- if newSampleIndex >= len(p.SampleType) ||
- p.SampleType[newSampleIndex].Type != sampleType {
- return 0, fmt.Errorf("option %s not valid for this profile", option)
- }
- return newSampleIndex, nil
-}
-
-func countFlags(bs []*bool) int {
- var c int
- for _, b := range bs {
- if *b {
- c++
- }
- }
- return c
-}
-
-func countFlagMap(bms map[string]*bool, bmrxs map[string]*string) int {
- var c int
- for _, b := range bms {
- if *b {
- c++
- }
- }
- for _, s := range bmrxs {
- if *s != "" {
- c++
- }
- }
- return c
-}
-
-var usageMsgHdr = "usage: pprof [options] [binary] <profile source> ...\n" +
- "Output format (only set one):\n"
-
-var usageMsg = "Output file parameters (for file-based output formats):\n" +
- " -output=f Generate output on file f (stdout by default)\n" +
- "Output granularity (only set one):\n" +
- " -functions Report at function level [default]\n" +
- " -files Report at source file level\n" +
- " -lines Report at source line level\n" +
- " -addresses Report at address level\n" +
- "Comparison options:\n" +
- " -base <profile> Show delta from this profile\n" +
- " -drop_negative Ignore negative differences\n" +
- "Sorting options:\n" +
- " -cum Sort by cumulative data\n\n" +
- "Dynamic profile options:\n" +
- " -seconds=N Length of time for dynamic profiles\n" +
- "Profile trimming options:\n" +
- " -nodecount=N Max number of nodes to show\n" +
- " -nodefraction=f Hide nodes below <f>*total\n" +
- " -edgefraction=f Hide edges below <f>*total\n" +
- "Sample value selection option (by index):\n" +
- " -sample_index Index of sample value to display\n" +
- " -mean Average sample value over first value\n" +
- "Sample value selection option (for heap profiles):\n" +
- " -inuse_space Display in-use memory size\n" +
- " -inuse_objects Display in-use object counts\n" +
- " -alloc_space Display allocated memory size\n" +
- " -alloc_objects Display allocated object counts\n" +
- "Sample value selection option (for contention profiles):\n" +
- " -total_delay Display total delay at each region\n" +
- " -contentions Display number of delays at each region\n" +
- " -mean_delay Display mean delay at each region\n" +
- "Filtering options:\n" +
- " -runtime Show runtime call frames in memory profiles\n" +
- " -focus=r Restricts to paths going through a node matching regexp\n" +
- " -ignore=r Skips paths going through any nodes matching regexp\n" +
- " -tagfocus=r Restrict to samples tagged with key:value matching regexp\n" +
- " Restrict to samples with numeric tags in range (eg \"32kb:1mb\")\n" +
- " -tagignore=r Discard samples tagged with key:value matching regexp\n" +
- " Avoid samples with numeric tags in range (eg \"1mb:\")\n" +
- "Miscellaneous:\n" +
- " -call_tree Generate a context-sensitive call tree\n" +
- " -unit=u Convert all samples to unit u for display\n" +
- " -divide_by=f Scale all samples by dividing them by f\n" +
- " -buildid=id Override build id for main binary in profile\n" +
- " -tools=path Search path for object-level tools\n" +
- " -help This message"
-
-var usageMsgVars = "Environment Variables:\n" +
- " PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" +
- " PPROF_TOOLS Search path for object-level tools\n" +
- " PPROF_BINARY_PATH Search path for local binary files\n" +
- " default: $HOME/pprof/binaries\n" +
- " finds binaries by $name and $buildid/$name"
-
-func aggregate(prof *profile.Profile, f *flags) error {
- switch {
- case f.isFormat("proto"), f.isFormat("raw"):
- // No aggregation for raw profiles.
- case f.isFormat("callgrind"):
- // Aggregate to file/line for callgrind.
- fallthrough
- case *f.flagLines:
- return prof.Aggregate(true, true, true, true, false)
- case *f.flagFiles:
- return prof.Aggregate(true, false, true, false, false)
- case *f.flagFunctions:
- return prof.Aggregate(true, true, false, false, false)
- case f.isFormat("weblist"), f.isFormat("disasm"):
- return prof.Aggregate(false, true, true, true, true)
- }
- return nil
-}
-
-// parseOptions parses the options into report.Options
-// Returns a function to postprocess the report after generation.
-func parseOptions(f *flags) (o *report.Options, p commands.PostProcessor, err error) {
-
- if *f.flagDivideBy == 0 {
- return nil, nil, fmt.Errorf("zero divisor specified")
- }
-
- o = &report.Options{
- CumSort: *f.flagCum,
- CallTree: *f.flagCallTree,
- PrintAddresses: *f.flagAddresses,
- DropNegative: *f.flagDropNegative,
- Ratio: 1 / *f.flagDivideBy,
-
- NodeCount: *f.flagNodeCount,
- NodeFraction: *f.flagNodeFraction,
- EdgeFraction: *f.flagEdgeFraction,
- OutputUnit: *f.flagDisplayUnit,
- }
-
- for cmd, b := range f.flagCommands {
- if *b {
- pcmd := f.commands[cmd]
- o.OutputFormat = pcmd.Format
- return o, pcmd.PostProcess, nil
- }
- }
-
- for cmd, rx := range f.flagParamCommands {
- if *rx != "" {
- pcmd := f.commands[cmd]
- if o.Symbol, err = regexp.Compile(*rx); err != nil {
- return nil, nil, fmt.Errorf("parsing -%s regexp: %v", cmd, err)
- }
- o.OutputFormat = pcmd.Format
- return o, pcmd.PostProcess, nil
- }
- }
-
- return nil, nil, fmt.Errorf("no output format selected")
-}
-
-type sampleValueFunc func(*profile.Sample) int64
-
-// sampleFormat returns a function to extract values out of a profile.Sample,
-// and the type/units of those values.
-func sampleFormat(p *profile.Profile, f *flags) (sampleValueFunc, string, string) {
- valueIndex := *f.flagSampleIndex
-
- if *f.flagMean {
- return meanExtractor(valueIndex), "mean_" + p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit
- }
-
- return valueExtractor(valueIndex), p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit
-}
-
-func valueExtractor(ix int) sampleValueFunc {
- return func(s *profile.Sample) int64 {
- return s.Value[ix]
- }
-}
-
-func meanExtractor(ix int) sampleValueFunc {
- return func(s *profile.Sample) int64 {
- if s.Value[0] == 0 {
- return 0
- }
- return s.Value[ix] / s.Value[0]
- }
-}
-
-func generate(interactive bool, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
- o, postProcess, err := parseOptions(f)
- if err != nil {
- return err
- }
-
- var w io.Writer
- if *f.flagOutput == "" {
- w = os.Stdout
- } else {
- ui.PrintErr("Generating report in ", *f.flagOutput)
- outputFile, err := os.Create(*f.flagOutput)
- if err != nil {
- return err
- }
- defer outputFile.Close()
- w = outputFile
- }
-
- if prof.Empty() {
- return fmt.Errorf("profile is empty")
- }
-
- value, stype, unit := sampleFormat(prof, f)
- o.SampleType = stype
- rpt := report.New(prof, *o, value, unit)
-
- // Do not apply filters if we're just generating a proto, so we
- // still have all the data.
- if o.OutputFormat != report.Proto {
- // Delay applying focus/ignore until after creating the report so
- // the report reflects the total number of samples.
- if err := preprocess(prof, ui, f); err != nil {
- return err
- }
- }
-
- if postProcess == nil {
- return report.Generate(w, rpt, obj)
- }
-
- var dot bytes.Buffer
- if err = report.Generate(&dot, rpt, obj); err != nil {
- return err
- }
-
- return postProcess(&dot, w, ui)
-}
diff --git a/src/cmd/pprof/internal/driver/interactive.go b/src/cmd/pprof/internal/driver/interactive.go
deleted file mode 100644
index 13009bf7e9..0000000000
--- a/src/cmd/pprof/internal/driver/interactive.go
+++ /dev/null
@@ -1,492 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package driver
-
-import (
- "fmt"
- "io"
- "regexp"
- "sort"
- "strconv"
- "strings"
-
- "cmd/pprof/internal/commands"
- "cmd/pprof/internal/plugin"
- "cmd/pprof/internal/profile"
-)
-
-var profileFunctionNames = []string{}
-
-// functionCompleter replaces provided substring with a function
-// name retrieved from a profile if a single match exists. Otherwise,
-// it returns unchanged substring. It defaults to no-op if the profile
-// is not specified.
-func functionCompleter(substring string) string {
- found := ""
- for _, fName := range profileFunctionNames {
- if strings.Contains(fName, substring) {
- if found != "" {
- return substring
- }
- found = fName
- }
- }
- if found != "" {
- return found
- }
- return substring
-}
-
-// updateAutoComplete enhances autocompletion with information that can be
-// retrieved from the profile
-func updateAutoComplete(p *profile.Profile) {
- profileFunctionNames = nil // remove function names retrieved previously
- for _, fn := range p.Function {
- profileFunctionNames = append(profileFunctionNames, fn.Name)
- }
-}
-
-// splitCommand splits the command line input into tokens separated by
-// spaces. Takes care to separate commands of the form 'top10' into
-// two tokens: 'top' and '10'
-func splitCommand(input string) []string {
- fields := strings.Fields(input)
- if num := strings.IndexAny(fields[0], "0123456789"); num != -1 {
- inputNumber := fields[0][num:]
- fields[0] = fields[0][:num]
- fields = append([]string{fields[0], inputNumber}, fields[1:]...)
- }
- return fields
-}
-
-// interactive displays a prompt and reads commands for profile
-// manipulation/visualization.
-func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
- updateAutoComplete(p)
-
- // Enter command processing loop.
- ui.Print("Entering interactive mode (type \"help\" for commands)")
- ui.SetAutoComplete(commands.NewCompleter(f.commands))
-
- for {
- input, err := readCommand(p, ui, f)
- if err != nil {
- if err != io.EOF {
- return err
- }
- if input == "" {
- return nil
- }
- }
- // Process simple commands.
- switch input {
- case "":
- continue
- case ":":
- f.flagFocus = newString("")
- f.flagIgnore = newString("")
- f.flagTagFocus = newString("")
- f.flagTagIgnore = newString("")
- f.flagHide = newString("")
- continue
- }
-
- fields := splitCommand(input)
- // Process report generation commands.
- if _, ok := f.commands[fields[0]]; ok {
- if err := generateReport(p, fields, obj, ui, f); err != nil {
- if err == io.EOF {
- return nil
- }
- ui.PrintErr(err)
- }
- continue
- }
-
- switch cmd := fields[0]; cmd {
- case "help":
- commandHelp(fields, ui, f)
- continue
- case "exit", "quit":
- return nil
- }
-
- // Process option settings.
- if of, err := optFlags(p, input, f); err == nil {
- f = of
- } else {
- ui.PrintErr("Error: ", err.Error())
- }
- }
-}
-
-func generateReport(p *profile.Profile, cmd []string, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
- prof := p.Copy()
-
- cf, err := cmdFlags(prof, cmd, ui, f)
- if err != nil {
- return err
- }
-
- return generate(true, prof, obj, ui, cf)
-}
-
-// validateRegex checks if a string is a valid regular expression.
-func validateRegex(v string) error {
- _, err := regexp.Compile(v)
- return err
-}
-
-// readCommand prompts for and reads the next command.
-func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) {
- //ui.Print("Options:\n", f.String(p))
- s, err := ui.ReadLine()
- return strings.TrimSpace(s), err
-}
-
-func commandHelp(_ []string, ui plugin.UI, f *flags) error {
- help := `
- Commands:
- cmd [n] [--cum] [focus_regex]* [-ignore_regex]*
- Produce a text report with the top n entries.
- Include samples matching focus_regex, and exclude ignore_regex.
- Add --cum to sort using cumulative data.
- Available commands:
-`
- var commands []string
- for name, cmd := range f.commands {
- commands = append(commands, fmt.Sprintf(" %-12s %s", name, cmd.Usage))
- }
- sort.Strings(commands)
-
- help = help + strings.Join(commands, "\n") + `
- peek func_regex
- Display callers and callees of functions matching func_regex.
-
- dot [n] [focus_regex]* [-ignore_regex]* [>file]
- Produce an annotated callgraph with the top n entries.
- Include samples matching focus_regex, and exclude ignore_regex.
- For other outputs, replace dot with:
- - Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file)
- - Graph viewer: gv, web, evince, eog
-
- callgrind [n] [focus_regex]* [-ignore_regex]* [>file]
- Produce a file in callgrind-compatible format.
- Include samples matching focus_regex, and exclude ignore_regex.
-
- weblist func_regex [-ignore_regex]*
- Show annotated source with interspersed assembly in a web browser.
-
- list func_regex [-ignore_regex]*
- Print source for routines matching func_regex, and exclude ignore_regex.
-
- disasm func_regex [-ignore_regex]*
- Disassemble routines matching func_regex, and exclude ignore_regex.
-
- tags tag_regex [-ignore_regex]*
- List tags with key:value matching tag_regex and exclude ignore_regex.
-
- quit/exit/^D
- Exit pprof.
-
- option=value
- The following options can be set individually:
- cum/flat: Sort entries based on cumulative or flat data
- call_tree: Build context-sensitive call trees
- nodecount: Max number of entries to display
- nodefraction: Min frequency ratio of nodes to display
- edgefraction: Min frequency ratio of edges to display
- focus/ignore: Regexp to include/exclude samples by name/file
- tagfocus/tagignore: Regexp or value range to filter samples by tag
- eg "1mb", "1mb:2mb", ":64kb"
-
- functions: Level of aggregation for sample data
- files:
- lines:
- addresses:
-
- unit: Measurement unit to use on reports
-
- Sample value selection by index:
- sample_index: Index of sample value to display
- mean: Average sample value over first value
-
- Sample value selection by name:
- alloc_space for heap profiles
- alloc_objects
- inuse_space
- inuse_objects
-
- total_delay for contention profiles
- mean_delay
- contentions
-
- : Clear focus/ignore/hide/tagfocus/tagignore`
-
- ui.Print(help)
- return nil
-}
-
-// cmdFlags parses the options of an interactive command and returns
-// an updated flags object.
-func cmdFlags(prof *profile.Profile, input []string, ui plugin.UI, f *flags) (*flags, error) {
- cf := *f
-
- var focus, ignore string
- output := *cf.flagOutput
- nodeCount := *cf.flagNodeCount
- cmd := input[0]
-
- // Update output flags based on parameters.
- tokens := input[1:]
- for p := 0; p < len(tokens); p++ {
- t := tokens[p]
- if t == "" {
- continue
- }
- if c, err := strconv.ParseInt(t, 10, 32); err == nil {
- nodeCount = int(c)
- continue
- }
- switch t[0] {
- case '>':
- if len(t) > 1 {
- output = t[1:]
- continue
- }
- // find next token
- for p++; p < len(tokens); p++ {
- if tokens[p] != "" {
- output = tokens[p]
- break
- }
- }
- case '-':
- if t == "--cum" || t == "-cum" {
- cf.flagCum = newBool(true)
- continue
- }
- ignore = catRegex(ignore, t[1:])
- default:
- focus = catRegex(focus, t)
- }
- }
-
- pcmd, ok := f.commands[cmd]
- if !ok {
- return nil, fmt.Errorf("Unexpected parse failure: %v", input)
- }
- // Reset flags
- cf.flagCommands = make(map[string]*bool)
- cf.flagParamCommands = make(map[string]*string)
-
- if !pcmd.HasParam {
- cf.flagCommands[cmd] = newBool(true)
-
- switch cmd {
- case "tags":
- cf.flagTagFocus = newString(focus)
- cf.flagTagIgnore = newString(ignore)
- default:
- cf.flagFocus = newString(catRegex(*cf.flagFocus, focus))
- cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore))
- }
- } else {
- if focus == "" {
- focus = "."
- }
- cf.flagParamCommands[cmd] = newString(focus)
- cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore))
- }
-
- if nodeCount < 0 {
- switch cmd {
- case "text", "top":
- // Default text/top to 10 nodes on interactive mode
- nodeCount = 10
- default:
- nodeCount = 80
- }
- }
-
- cf.flagNodeCount = newInt(nodeCount)
- cf.flagOutput = newString(output)
-
- // Do regular flags processing
- if err := processFlags(prof, ui, &cf); err != nil {
- cf.usage(ui)
- return nil, err
- }
-
- return &cf, nil
-}
-
-func catRegex(a, b string) string {
- if a == "" {
- return b
- }
- if b == "" {
- return a
- }
- return a + "|" + b
-}
-
-// optFlags parses an interactive option setting and returns
-// an updated flags object.
-func optFlags(p *profile.Profile, input string, f *flags) (*flags, error) {
- inputs := strings.SplitN(input, "=", 2)
- option := strings.ToLower(strings.TrimSpace(inputs[0]))
- var value string
- if len(inputs) == 2 {
- value = strings.TrimSpace(inputs[1])
- }
-
- of := *f
-
- var err error
- var bv bool
- var uv uint64
- var fv float64
-
- switch option {
- case "cum":
- if bv, err = parseBool(value); err != nil {
- return nil, err
- }
- of.flagCum = newBool(bv)
- case "flat":
- if bv, err = parseBool(value); err != nil {
- return nil, err
- }
- of.flagCum = newBool(!bv)
- case "call_tree":
- if bv, err = parseBool(value); err != nil {
- return nil, err
- }
- of.flagCallTree = newBool(bv)
- case "unit":
- of.flagDisplayUnit = newString(value)
- case "sample_index":
- if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
- return nil, err
- }
- if ix := int(uv); ix < 0 || ix >= len(p.SampleType) {
- return nil, fmt.Errorf("sample_index out of range [0..%d]", len(p.SampleType)-1)
- }
- of.flagSampleIndex = newInt(int(uv))
- case "mean":
- if bv, err = parseBool(value); err != nil {
- return nil, err
- }
- of.flagMean = newBool(bv)
- case "nodecount":
- if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
- return nil, err
- }
- of.flagNodeCount = newInt(int(uv))
- case "nodefraction":
- if fv, err = strconv.ParseFloat(value, 64); err != nil {
- return nil, err
- }
- of.flagNodeFraction = newFloat64(fv)
- case "edgefraction":
- if fv, err = strconv.ParseFloat(value, 64); err != nil {
- return nil, err
- }
- of.flagEdgeFraction = newFloat64(fv)
- case "focus":
- if err = validateRegex(value); err != nil {
- return nil, err
- }
- of.flagFocus = newString(value)
- case "ignore":
- if err = validateRegex(value); err != nil {
- return nil, err
- }
- of.flagIgnore = newString(value)
- case "tagfocus":
- if err = validateRegex(value); err != nil {
- return nil, err
- }
- of.flagTagFocus = newString(value)
- case "tagignore":
- if err = validateRegex(value); err != nil {
- return nil, err
- }
- of.flagTagIgnore = newString(value)
- case "hide":
- if err = validateRegex(value); err != nil {
- return nil, err
- }
- of.flagHide = newString(value)
- case "addresses", "files", "lines", "functions":
- if bv, err = parseBool(value); err != nil {
- return nil, err
- }
- if !bv {
- return nil, fmt.Errorf("select one of addresses/files/lines/functions")
- }
- setGranularityToggle(option, &of)
- default:
- if ix := findSampleIndex(p, "", option); ix >= 0 {
- of.flagSampleIndex = newInt(ix)
- } else if ix := findSampleIndex(p, "total_", option); ix >= 0 {
- of.flagSampleIndex = newInt(ix)
- of.flagMean = newBool(false)
- } else if ix := findSampleIndex(p, "mean_", option); ix >= 1 {
- of.flagSampleIndex = newInt(ix)
- of.flagMean = newBool(true)
- } else {
- return nil, fmt.Errorf("unrecognized command: %s", input)
- }
- }
- return &of, nil
-}
-
-// parseBool parses a string as a boolean value.
-func parseBool(v string) (bool, error) {
- switch strings.ToLower(v) {
- case "true", "t", "yes", "y", "1", "":
- return true, nil
- case "false", "f", "no", "n", "0":
- return false, nil
- }
- return false, fmt.Errorf(`illegal input "%s" for bool value`, v)
-}
-
-func findSampleIndex(p *profile.Profile, prefix, sampleType string) int {
- if !strings.HasPrefix(sampleType, prefix) {
- return -1
- }
- sampleType = strings.TrimPrefix(sampleType, prefix)
- for i, r := range p.SampleType {
- if r.Type == sampleType {
- return i
- }
- }
- return -1
-}
-
-// setGranularityToggle manages the set of granularity options. These
-// operate as a toggle; turning one on turns the others off.
-func setGranularityToggle(o string, fl *flags) {
- t, f := newBool(true), newBool(false)
- fl.flagFunctions = f
- fl.flagFiles = f
- fl.flagLines = f
- fl.flagAddresses = f
- switch o {
- case "functions":
- fl.flagFunctions = t
- case "files":
- fl.flagFiles = t
- case "lines":
- fl.flagLines = t
- case "addresses":
- fl.flagAddresses = t
- default:
- panic(fmt.Errorf("unexpected option %s", o))
- }
-}
diff --git a/src/cmd/pprof/internal/fetch/fetch.go b/src/cmd/pprof/internal/fetch/fetch.go
deleted file mode 100644
index ec4a6383c6..0000000000
--- a/src/cmd/pprof/internal/fetch/fetch.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package fetch provides an extensible mechanism to fetch a profile
-// from a data source.
-package fetch
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "strings"
- "time"
-
- "cmd/pprof/internal/plugin"
- "cmd/pprof/internal/profile"
-)
-
-// FetchProfile reads from a data source (network, file) and generates a
-// profile.
-func FetchProfile(source string, timeout time.Duration) (*profile.Profile, error) {
- return Fetcher(source, timeout, plugin.StandardUI())
-}
-
-// Fetcher is the plugin.Fetcher version of FetchProfile.
-func Fetcher(source string, timeout time.Duration, ui plugin.UI) (*profile.Profile, error) {
- var f io.ReadCloser
- var err error
-
- url, err := url.Parse(source)
- if err == nil && url.Host != "" {
- f, err = FetchURL(source, timeout)
- } else {
- f, err = os.Open(source)
- }
- if err != nil {
- return nil, err
- }
- defer f.Close()
- return profile.Parse(f)
-}
-
-// FetchURL fetches a profile from a URL using HTTP.
-func FetchURL(source string, timeout time.Duration) (io.ReadCloser, error) {
- resp, err := httpGet(source, timeout)
- if err != nil {
- return nil, fmt.Errorf("http fetch %s: %v", source, err)
- }
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("server response: %s", resp.Status)
- }
-
- return resp.Body, nil
-}
-
-// PostURL issues a POST to a URL over HTTP.
-func PostURL(source, post string) ([]byte, error) {
- resp, err := http.Post(source, "application/octet-stream", strings.NewReader(post))
- if err != nil {
- return nil, fmt.Errorf("http post %s: %v", source, err)
- }
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("server response: %s", resp.Status)
- }
- defer resp.Body.Close()
- return ioutil.ReadAll(resp.Body)
-}
-
-// httpGet is a wrapper around http.Get; it is defined as a variable
-// so it can be redefined during for testing.
-var httpGet = func(url string, timeout time.Duration) (*http.Response, error) {
- client := &http.Client{
- Transport: &http.Transport{
- ResponseHeaderTimeout: timeout + 5*time.Second,
- },
- }
- return client.Get(url)
-}
diff --git a/src/cmd/pprof/internal/plugin/plugin.go b/src/cmd/pprof/internal/plugin/plugin.go
deleted file mode 100644
index a22ec5f3c5..0000000000
--- a/src/cmd/pprof/internal/plugin/plugin.go
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package plugin defines the plugin implementations that the main pprof driver requires.
-package plugin
-
-import (
- "bufio"
- "fmt"
- "os"
- "regexp"
- "strings"
- "time"
-
- "cmd/pprof/internal/profile"
-)
-
-// A FlagSet creates and parses command-line flags.
-// It is similar to the standard flag.FlagSet.
-type FlagSet interface {
- // Bool, Int, Float64, and String define new flags,
- // like the functions of the same name in package flag.
- Bool(name string, def bool, usage string) *bool
- Int(name string, def int, usage string) *int
- Float64(name string, def float64, usage string) *float64
- String(name string, def string, usage string) *string
-
- // ExtraUsage returns any additional text that should be
- // printed after the standard usage message.
- // The typical use of ExtraUsage is to show any custom flags
- // defined by the specific pprof plugins being used.
- ExtraUsage() string
-
- // Parse initializes the flags with their values for this run
- // and returns the non-flag command line arguments.
- // If an unknown flag is encountered or there are no arguments,
- // Parse should call usage and return nil.
- Parse(usage func()) []string
-}
-
-// An ObjTool inspects shared libraries and executable files.
-type ObjTool interface {
- // Open opens the named object file.
- // If the object is a shared library, start is the address where
- // it is mapped into memory in the address space being inspected.
- Open(file string, start uint64) (ObjFile, error)
-
- // Demangle translates a batch of symbol names from mangled
- // form to human-readable form.
- Demangle(names []string) (map[string]string, error)
-
- // Disasm disassembles the named object file, starting at
- // the start address and stopping at (before) the end address.
- Disasm(file string, start, end uint64) ([]Inst, error)
-
- // SetConfig configures the tool.
- // The implementation defines the meaning of the string
- // and can ignore it entirely.
- SetConfig(config string)
-}
-
-// NoObjTool returns a trivial implementation of the ObjTool interface.
-// Open returns an error indicating that the requested file does not exist.
-// Demangle returns an empty map and a nil error.
-// Disasm returns an error.
-// SetConfig is a no-op.
-func NoObjTool() ObjTool {
- return noObjTool{}
-}
-
-type noObjTool struct{}
-
-func (noObjTool) Open(file string, start uint64) (ObjFile, error) {
- return nil, &os.PathError{Op: "open", Path: file, Err: os.ErrNotExist}
-}
-
-func (noObjTool) Demangle(name []string) (map[string]string, error) {
- return make(map[string]string), nil
-}
-
-func (noObjTool) Disasm(file string, start, end uint64) ([]Inst, error) {
- return nil, fmt.Errorf("disassembly not supported")
-}
-
-func (noObjTool) SetConfig(config string) {
-}
-
-// An ObjFile is a single object file: a shared library or executable.
-type ObjFile interface {
- // Name returns the underlyinf file name, if available
- Name() string
-
- // Base returns the base address to use when looking up symbols in the file.
- Base() uint64
-
- // BuildID returns the GNU build ID of the file, or an empty string.
- BuildID() string
-
- // SourceLine reports the source line information for a given
- // address in the file. Due to inlining, the source line information
- // is in general a list of positions representing a call stack,
- // with the leaf function first.
- SourceLine(addr uint64) ([]Frame, error)
-
- // Symbols returns a list of symbols in the object file.
- // If r is not nil, Symbols restricts the list to symbols
- // with names matching the regular expression.
- // If addr is not zero, Symbols restricts the list to symbols
- // containing that address.
- Symbols(r *regexp.Regexp, addr uint64) ([]*Sym, error)
-
- // Close closes the file, releasing associated resources.
- Close() error
-}
-
-// A Frame describes a single line in a source file.
-type Frame struct {
- Func string // name of function
- File string // source file name
- Line int // line in file
-}
-
-// A Sym describes a single symbol in an object file.
-type Sym struct {
- Name []string // names of symbol (many if symbol was dedup'ed)
- File string // object file containing symbol
- Start uint64 // start virtual address
- End uint64 // virtual address of last byte in sym (Start+size-1)
-}
-
-// An Inst is a single instruction in an assembly listing.
-type Inst struct {
- Addr uint64 // virtual address of instruction
- Text string // instruction text
- File string // source file
- Line int // source line
-}
-
-// A UI manages user interactions.
-type UI interface {
- // Read returns a line of text (a command) read from the user.
- ReadLine() (string, error)
-
- // Print shows a message to the user.
- // It formats the text as fmt.Print would and adds a final \n if not already present.
- // For line-based UI, Print writes to standard error.
- // (Standard output is reserved for report data.)
- Print(...interface{})
-
- // PrintErr shows an error message to the user.
- // It formats the text as fmt.Print would and adds a final \n if not already present.
- // For line-based UI, PrintErr writes to standard error.
- PrintErr(...interface{})
-
- // IsTerminal returns whether the UI is known to be tied to an
- // interactive terminal (as opposed to being redirected to a file).
- IsTerminal() bool
-
- // SetAutoComplete instructs the UI to call complete(cmd) to obtain
- // the auto-completion of cmd, if the UI supports auto-completion at all.
- SetAutoComplete(complete func(string) string)
-}
-
-// StandardUI returns a UI that reads from standard input,
-// prints messages to standard output,
-// prints errors to standard error, and doesn't use auto-completion.
-func StandardUI() UI {
- return &stdUI{r: bufio.NewReader(os.Stdin)}
-}
-
-type stdUI struct {
- r *bufio.Reader
-}
-
-func (ui *stdUI) ReadLine() (string, error) {
- os.Stdout.WriteString("(pprof) ")
- return ui.r.ReadString('\n')
-}
-
-func (ui *stdUI) Print(args ...interface{}) {
- ui.fprint(os.Stderr, args)
-}
-
-func (ui *stdUI) PrintErr(args ...interface{}) {
- ui.fprint(os.Stderr, args)
-}
-
-func (ui *stdUI) IsTerminal() bool {
- return false
-}
-
-func (ui *stdUI) SetAutoComplete(func(string) string) {
-}
-
-func (ui *stdUI) fprint(f *os.File, args []interface{}) {
- text := fmt.Sprint(args...)
- if !strings.HasSuffix(text, "\n") {
- text += "\n"
- }
- f.WriteString(text)
-}
-
-// A Fetcher reads and returns the profile named by src.
-// It gives up after the given timeout, unless src contains a timeout override
-// (as defined by the implementation).
-// It can print messages to ui.
-type Fetcher func(src string, timeout time.Duration, ui UI) (*profile.Profile, error)
-
-// A Symbolizer annotates a profile with symbol information.
-// The profile was fetch from src.
-// The meaning of mode is defined by the implementation.
-type Symbolizer func(mode, src string, prof *profile.Profile, obj ObjTool, ui UI) error
diff --git a/src/cmd/pprof/internal/profile/encode.go b/src/cmd/pprof/internal/profile/encode.go
deleted file mode 100644
index 6b879a84ac..0000000000
--- a/src/cmd/pprof/internal/profile/encode.go
+++ /dev/null
@@ -1,470 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package profile
-
-import (
- "errors"
- "fmt"
- "sort"
-)
-
-func (p *Profile) decoder() []decoder {
- return profileDecoder
-}
-
-// preEncode populates the unexported fields to be used by encode
-// (with suffix X) from the corresponding exported fields. The
-// exported fields are cleared up to facilitate testing.
-func (p *Profile) preEncode() {
- strings := make(map[string]int)
- addString(strings, "")
-
- for _, st := range p.SampleType {
- st.typeX = addString(strings, st.Type)
- st.unitX = addString(strings, st.Unit)
- }
-
- for _, s := range p.Sample {
- s.labelX = nil
- var keys []string
- for k := range s.Label {
- keys = append(keys, k)
- }
- sort.Strings(keys)
- for _, k := range keys {
- vs := s.Label[k]
- for _, v := range vs {
- s.labelX = append(s.labelX,
- Label{
- keyX: addString(strings, k),
- strX: addString(strings, v),
- },
- )
- }
- }
- var numKeys []string
- for k := range s.NumLabel {
- numKeys = append(numKeys, k)
- }
- sort.Strings(numKeys)
- for _, k := range numKeys {
- vs := s.NumLabel[k]
- for _, v := range vs {
- s.labelX = append(s.labelX,
- Label{
- keyX: addString(strings, k),
- numX: v,
- },
- )
- }
- }
- s.locationIDX = nil
- for _, l := range s.Location {
- s.locationIDX = append(s.locationIDX, l.ID)
- }
- }
-
- for _, m := range p.Mapping {
- m.fileX = addString(strings, m.File)
- m.buildIDX = addString(strings, m.BuildID)
- }
-
- for _, l := range p.Location {
- for i, ln := range l.Line {
- if ln.Function != nil {
- l.Line[i].functionIDX = ln.Function.ID
- } else {
- l.Line[i].functionIDX = 0
- }
- }
- if l.Mapping != nil {
- l.mappingIDX = l.Mapping.ID
- } else {
- l.mappingIDX = 0
- }
- }
- for _, f := range p.Function {
- f.nameX = addString(strings, f.Name)
- f.systemNameX = addString(strings, f.SystemName)
- f.filenameX = addString(strings, f.Filename)
- }
-
- p.dropFramesX = addString(strings, p.DropFrames)
- p.keepFramesX = addString(strings, p.KeepFrames)
-
- if pt := p.PeriodType; pt != nil {
- pt.typeX = addString(strings, pt.Type)
- pt.unitX = addString(strings, pt.Unit)
- }
-
- p.stringTable = make([]string, len(strings))
- for s, i := range strings {
- p.stringTable[i] = s
- }
-}
-
-func (p *Profile) encode(b *buffer) {
- for _, x := range p.SampleType {
- encodeMessage(b, 1, x)
- }
- for _, x := range p.Sample {
- encodeMessage(b, 2, x)
- }
- for _, x := range p.Mapping {
- encodeMessage(b, 3, x)
- }
- for _, x := range p.Location {
- encodeMessage(b, 4, x)
- }
- for _, x := range p.Function {
- encodeMessage(b, 5, x)
- }
- encodeStrings(b, 6, p.stringTable)
- encodeInt64Opt(b, 7, p.dropFramesX)
- encodeInt64Opt(b, 8, p.keepFramesX)
- encodeInt64Opt(b, 9, p.TimeNanos)
- encodeInt64Opt(b, 10, p.DurationNanos)
- if pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) {
- encodeMessage(b, 11, p.PeriodType)
- }
- encodeInt64Opt(b, 12, p.Period)
-}
-
-var profileDecoder = []decoder{
- nil, // 0
- // repeated ValueType sample_type = 1
- func(b *buffer, m message) error {
- x := new(ValueType)
- pp := m.(*Profile)
- pp.SampleType = append(pp.SampleType, x)
- return decodeMessage(b, x)
- },
- // repeated Sample sample = 2
- func(b *buffer, m message) error {
- x := new(Sample)
- pp := m.(*Profile)
- pp.Sample = append(pp.Sample, x)
- return decodeMessage(b, x)
- },
- // repeated Mapping mapping = 3
- func(b *buffer, m message) error {
- x := new(Mapping)
- pp := m.(*Profile)
- pp.Mapping = append(pp.Mapping, x)
- return decodeMessage(b, x)
- },
- // repeated Location location = 4
- func(b *buffer, m message) error {
- x := new(Location)
- pp := m.(*Profile)
- pp.Location = append(pp.Location, x)
- return decodeMessage(b, x)
- },
- // repeated Function function = 5
- func(b *buffer, m message) error {
- x := new(Function)
- pp := m.(*Profile)
- pp.Function = append(pp.Function, x)
- return decodeMessage(b, x)
- },
- // repeated string string_table = 6
- func(b *buffer, m message) error {
- err := decodeStrings(b, &m.(*Profile).stringTable)
- if err != nil {
- return err
- }
- if *&m.(*Profile).stringTable[0] != "" {
- return errors.New("string_table[0] must be ''")
- }
- return nil
- },
- // repeated int64 drop_frames = 7
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) },
- // repeated int64 keep_frames = 8
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) },
- // repeated int64 time_nanos = 9
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).TimeNanos) },
- // repeated int64 duration_nanos = 10
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) },
- // optional string period_type = 11
- func(b *buffer, m message) error {
- x := new(ValueType)
- pp := m.(*Profile)
- pp.PeriodType = x
- return decodeMessage(b, x)
- },
- // repeated int64 period = 12
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) },
-}
-
-// postDecode takes the unexported fields populated by decode (with
-// suffix X) and populates the corresponding exported fields.
-// The unexported fields are cleared up to facilitate testing.
-func (p *Profile) postDecode() error {
- var err error
-
- mappings := make(map[uint64]*Mapping)
- for _, m := range p.Mapping {
- m.File, err = getString(p.stringTable, &m.fileX, err)
- m.BuildID, err = getString(p.stringTable, &m.buildIDX, err)
- mappings[m.ID] = m
- }
-
- functions := make(map[uint64]*Function)
- for _, f := range p.Function {
- f.Name, err = getString(p.stringTable, &f.nameX, err)
- f.SystemName, err = getString(p.stringTable, &f.systemNameX, err)
- f.Filename, err = getString(p.stringTable, &f.filenameX, err)
- functions[f.ID] = f
- }
-
- locations := make(map[uint64]*Location)
- for _, l := range p.Location {
- l.Mapping = mappings[l.mappingIDX]
- l.mappingIDX = 0
- for i, ln := range l.Line {
- if id := ln.functionIDX; id != 0 {
- l.Line[i].Function = functions[id]
- if l.Line[i].Function == nil {
- return fmt.Errorf("Function ID %d not found", id)
- }
- l.Line[i].functionIDX = 0
- }
- }
- locations[l.ID] = l
- }
-
- for _, st := range p.SampleType {
- st.Type, err = getString(p.stringTable, &st.typeX, err)
- st.Unit, err = getString(p.stringTable, &st.unitX, err)
- }
-
- for _, s := range p.Sample {
- labels := make(map[string][]string)
- numLabels := make(map[string][]int64)
- for _, l := range s.labelX {
- var key, value string
- key, err = getString(p.stringTable, &l.keyX, err)
- if l.strX != 0 {
- value, err = getString(p.stringTable, &l.strX, err)
- labels[key] = append(labels[key], value)
- } else {
- numLabels[key] = append(numLabels[key], l.numX)
- }
- }
- if len(labels) > 0 {
- s.Label = labels
- }
- if len(numLabels) > 0 {
- s.NumLabel = numLabels
- }
- s.Location = nil
- for _, lid := range s.locationIDX {
- s.Location = append(s.Location, locations[lid])
- }
- s.locationIDX = nil
- }
-
- p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err)
- p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err)
-
- if pt := p.PeriodType; pt == nil {
- p.PeriodType = &ValueType{}
- }
-
- if pt := p.PeriodType; pt != nil {
- pt.Type, err = getString(p.stringTable, &pt.typeX, err)
- pt.Unit, err = getString(p.stringTable, &pt.unitX, err)
- }
- p.stringTable = nil
- return nil
-}
-
-func (p *ValueType) decoder() []decoder {
- return valueTypeDecoder
-}
-
-func (p *ValueType) encode(b *buffer) {
- encodeInt64Opt(b, 1, p.typeX)
- encodeInt64Opt(b, 2, p.unitX)
-}
-
-var valueTypeDecoder = []decoder{
- nil, // 0
- // optional int64 type = 1
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) },
- // optional int64 unit = 2
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) },
-}
-
-func (p *Sample) decoder() []decoder {
- return sampleDecoder
-}
-
-func (p *Sample) encode(b *buffer) {
- encodeUint64s(b, 1, p.locationIDX)
- for _, x := range p.Value {
- encodeInt64(b, 2, x)
- }
- for _, x := range p.labelX {
- encodeMessage(b, 3, x)
- }
-}
-
-var sampleDecoder = []decoder{
- nil, // 0
- // repeated uint64 location = 1
- func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) },
- // repeated int64 value = 2
- func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) },
- // repeated Label label = 3
- func(b *buffer, m message) error {
- s := m.(*Sample)
- n := len(s.labelX)
- s.labelX = append(s.labelX, Label{})
- return decodeMessage(b, &s.labelX[n])
- },
-}
-
-func (p Label) decoder() []decoder {
- return labelDecoder
-}
-
-func (p Label) encode(b *buffer) {
- encodeInt64Opt(b, 1, p.keyX)
- encodeInt64Opt(b, 2, p.strX)
- encodeInt64Opt(b, 3, p.numX)
-}
-
-var labelDecoder = []decoder{
- nil, // 0
- // optional int64 key = 1
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).keyX) },
- // optional int64 str = 2
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).strX) },
- // optional int64 num = 3
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).numX) },
-}
-
-func (p *Mapping) decoder() []decoder {
- return mappingDecoder
-}
-
-func (p *Mapping) encode(b *buffer) {
- encodeUint64Opt(b, 1, p.ID)
- encodeUint64Opt(b, 2, p.Start)
- encodeUint64Opt(b, 3, p.Limit)
- encodeUint64Opt(b, 4, p.Offset)
- encodeInt64Opt(b, 5, p.fileX)
- encodeInt64Opt(b, 6, p.buildIDX)
- encodeBoolOpt(b, 7, p.HasFunctions)
- encodeBoolOpt(b, 8, p.HasFilenames)
- encodeBoolOpt(b, 9, p.HasLineNumbers)
- encodeBoolOpt(b, 10, p.HasInlineFrames)
-}
-
-var mappingDecoder = []decoder{
- nil, // 0
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) }, // optional uint64 id = 1
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) }, // optional uint64 memory_offset = 2
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) }, // optional uint64 memory_limit = 3
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) }, // optional uint64 file_offset = 4
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) }, // optional int64 filename = 5
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) }, // optional int64 build_id = 6
- func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) }, // optional bool has_functions = 7
- func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) }, // optional bool has_filenames = 8
- func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) }, // optional bool has_line_numbers = 9
- func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10
-}
-
-func (p *Location) decoder() []decoder {
- return locationDecoder
-}
-
-func (p *Location) encode(b *buffer) {
- encodeUint64Opt(b, 1, p.ID)
- encodeUint64Opt(b, 2, p.mappingIDX)
- encodeUint64Opt(b, 3, p.Address)
- for i := range p.Line {
- encodeMessage(b, 4, &p.Line[i])
- }
-}
-
-var locationDecoder = []decoder{
- nil, // 0
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) }, // optional uint64 id = 1;
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2;
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) }, // optional uint64 address = 3;
- func(b *buffer, m message) error { // repeated Line line = 4
- pp := m.(*Location)
- n := len(pp.Line)
- pp.Line = append(pp.Line, Line{})
- return decodeMessage(b, &pp.Line[n])
- },
-}
-
-func (p *Line) decoder() []decoder {
- return lineDecoder
-}
-
-func (p *Line) encode(b *buffer) {
- encodeUint64Opt(b, 1, p.functionIDX)
- encodeInt64Opt(b, 2, p.Line)
-}
-
-var lineDecoder = []decoder{
- nil, // 0
- // optional uint64 function_id = 1
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },
- // optional int64 line = 2
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },
-}
-
-func (p *Function) decoder() []decoder {
- return functionDecoder
-}
-
-func (p *Function) encode(b *buffer) {
- encodeUint64Opt(b, 1, p.ID)
- encodeInt64Opt(b, 2, p.nameX)
- encodeInt64Opt(b, 3, p.systemNameX)
- encodeInt64Opt(b, 4, p.filenameX)
- encodeInt64Opt(b, 5, p.StartLine)
-}
-
-var functionDecoder = []decoder{
- nil, // 0
- // optional uint64 id = 1
- func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) },
- // optional int64 function_name = 2
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) },
- // optional int64 function_system_name = 3
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) },
- // repeated int64 filename = 4
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) },
- // optional int64 start_line = 5
- func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) },
-}
-
-func addString(strings map[string]int, s string) int64 {
- i, ok := strings[s]
- if !ok {
- i = len(strings)
- strings[s] = i
- }
- return int64(i)
-}
-
-func getString(strings []string, strng *int64, err error) (string, error) {
- if err != nil {
- return "", err
- }
- s := int(*strng)
- if s < 0 || s >= len(strings) {
- return "", errMalformed
- }
- *strng = 0
- return strings[s], nil
-}
diff --git a/src/cmd/pprof/internal/profile/filter.go b/src/cmd/pprof/internal/profile/filter.go
deleted file mode 100644
index 1baa096a49..0000000000
--- a/src/cmd/pprof/internal/profile/filter.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Implements methods to filter samples from profiles.
-
-package profile
-
-import "regexp"
-
-// FilterSamplesByName filters the samples in a profile and only keeps
-// samples where at least one frame matches focus but none match ignore.
-// Returns true is the corresponding regexp matched at least one sample.
-func (p *Profile) FilterSamplesByName(focus, ignore, hide *regexp.Regexp) (fm, im, hm bool) {
- focusOrIgnore := make(map[uint64]bool)
- hidden := make(map[uint64]bool)
- for _, l := range p.Location {
- if ignore != nil && l.matchesName(ignore) {
- im = true
- focusOrIgnore[l.ID] = false
- } else if focus == nil || l.matchesName(focus) {
- fm = true
- focusOrIgnore[l.ID] = true
- }
- if hide != nil && l.matchesName(hide) {
- hm = true
- l.Line = l.unmatchedLines(hide)
- if len(l.Line) == 0 {
- hidden[l.ID] = true
- }
- }
- }
-
- s := make([]*Sample, 0, len(p.Sample))
- for _, sample := range p.Sample {
- if focusedAndNotIgnored(sample.Location, focusOrIgnore) {
- if len(hidden) > 0 {
- var locs []*Location
- for _, loc := range sample.Location {
- if !hidden[loc.ID] {
- locs = append(locs, loc)
- }
- }
- if len(locs) == 0 {
- // Remove sample with no locations (by not adding it to s).
- continue
- }
- sample.Location = locs
- }
- s = append(s, sample)
- }
- }
- p.Sample = s
-
- return
-}
-
-// matchesName returns whether the function name or file in the
-// location matches the regular expression.
-func (loc *Location) matchesName(re *regexp.Regexp) bool {
- for _, ln := range loc.Line {
- if fn := ln.Function; fn != nil {
- if re.MatchString(fn.Name) {
- return true
- }
- if re.MatchString(fn.Filename) {
- return true
- }
- }
- }
- return false
-}
-
-// unmatchedLines returns the lines in the location that do not match
-// the regular expression.
-func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {
- var lines []Line
- for _, ln := range loc.Line {
- if fn := ln.Function; fn != nil {
- if re.MatchString(fn.Name) {
- continue
- }
- if re.MatchString(fn.Filename) {
- continue
- }
- }
- lines = append(lines, ln)
- }
- return lines
-}
-
-// focusedAndNotIgnored looks up a slice of ids against a map of
-// focused/ignored locations. The map only contains locations that are
-// explicitly focused or ignored. Returns whether there is at least
-// one focused location but no ignored locations.
-func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool {
- var f bool
- for _, loc := range locs {
- if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore {
- if focus {
- // Found focused location. Must keep searching in case there
- // is an ignored one as well.
- f = true
- } else {
- // Found ignored location. Can return false right away.
- return false
- }
- }
- }
- return f
-}
-
-// TagMatch selects tags for filtering
-type TagMatch func(key, val string, nval int64) bool
-
-// FilterSamplesByTag removes all samples from the profile, except
-// those that match focus and do not match the ignore regular
-// expression.
-func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) {
- samples := make([]*Sample, 0, len(p.Sample))
- for _, s := range p.Sample {
- focused, ignored := focusedSample(s, focus, ignore)
- fm = fm || focused
- im = im || ignored
- if focused && !ignored {
- samples = append(samples, s)
- }
- }
- p.Sample = samples
- return
-}
-
-// focusedTag checks a sample against focus and ignore regexps.
-// Returns whether the focus/ignore regexps match any tags
-func focusedSample(s *Sample, focus, ignore TagMatch) (fm, im bool) {
- fm = focus == nil
- for key, vals := range s.Label {
- for _, val := range vals {
- if ignore != nil && ignore(key, val, 0) {
- im = true
- }
- if !fm && focus(key, val, 0) {
- fm = true
- }
- }
- }
- for key, vals := range s.NumLabel {
- for _, val := range vals {
- if ignore != nil && ignore(key, "", val) {
- im = true
- }
- if !fm && focus(key, "", val) {
- fm = true
- }
- }
- }
- return fm, im
-}
diff --git a/src/cmd/pprof/internal/profile/legacy_profile.go b/src/cmd/pprof/internal/profile/legacy_profile.go
deleted file mode 100644
index e1f24c4c6d..0000000000
--- a/src/cmd/pprof/internal/profile/legacy_profile.go
+++ /dev/null
@@ -1,1251 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// This file implements parsers to convert legacy profiles into the
-// profile.proto format.
-
-package profile
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "math"
- "regexp"
- "strconv"
- "strings"
-)
-
-var (
- countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\n\z`)
- countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`)
-
- heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`)
- heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`)
-
- contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`)
-
- hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`)
-
- growthHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`)
-
- fragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`)
-
- threadzStartRE = regexp.MustCompile(`--- threadz \d+ ---`)
- threadStartRE = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`)
-
- procMapsRE = regexp.MustCompile(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`)
-
- briefMapsRE = regexp.MustCompile(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`)
-
- // LegacyHeapAllocated instructs the heapz parsers to use the
- // allocated memory stats instead of the default in-use memory. Note
- // that tcmalloc doesn't provide all allocated memory, only in-use
- // stats.
- LegacyHeapAllocated bool
-)
-
-func isSpaceOrComment(line string) bool {
- trimmed := strings.TrimSpace(line)
- return len(trimmed) == 0 || trimmed[0] == '#'
-}
-
-// parseGoCount parses a Go count profile (e.g., threadcreate or
-// goroutine) and returns a new Profile.
-func parseGoCount(b []byte) (*Profile, error) {
- r := bytes.NewBuffer(b)
-
- var line string
- var err error
- for {
- // Skip past comments and empty lines seeking a real header.
- line, err = r.ReadString('\n')
- if err != nil {
- return nil, err
- }
- if !isSpaceOrComment(line) {
- break
- }
- }
-
- m := countStartRE.FindStringSubmatch(line)
- if m == nil {
- return nil, errUnrecognized
- }
- profileType := string(m[1])
- p := &Profile{
- PeriodType: &ValueType{Type: profileType, Unit: "count"},
- Period: 1,
- SampleType: []*ValueType{{Type: profileType, Unit: "count"}},
- }
- locations := make(map[uint64]*Location)
- for {
- line, err = r.ReadString('\n')
- if err != nil {
- if err == io.EOF {
- break
- }
- return nil, err
- }
- if isSpaceOrComment(line) {
- continue
- }
- if strings.HasPrefix(line, "---") {
- break
- }
- m := countRE.FindStringSubmatch(line)
- if m == nil {
- return nil, errMalformed
- }
- n, err := strconv.ParseInt(string(m[1]), 0, 64)
- if err != nil {
- return nil, errMalformed
- }
- fields := strings.Fields(string(m[2]))
- locs := make([]*Location, 0, len(fields))
- for _, stk := range fields {
- addr, err := strconv.ParseUint(stk, 0, 64)
- if err != nil {
- return nil, errMalformed
- }
- // Adjust all frames by -1 (except the leaf) to land on top of
- // the call instruction.
- if len(locs) > 0 {
- addr--
- }
- loc := locations[addr]
- if loc == nil {
- loc = &Location{
- Address: addr,
- }
- locations[addr] = loc
- p.Location = append(p.Location, loc)
- }
- locs = append(locs, loc)
- }
- p.Sample = append(p.Sample, &Sample{
- Location: locs,
- Value: []int64{n},
- })
- }
-
- if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil {
- return nil, err
- }
- return p, nil
-}
-
-// remapLocationIDs ensures there is a location for each address
-// referenced by a sample, and remaps the samples to point to the new
-// location ids.
-func (p *Profile) remapLocationIDs() {
- seen := make(map[*Location]bool, len(p.Location))
- var locs []*Location
-
- for _, s := range p.Sample {
- for _, l := range s.Location {
- if seen[l] {
- continue
- }
- l.ID = uint64(len(locs) + 1)
- locs = append(locs, l)
- seen[l] = true
- }
- }
- p.Location = locs
-}
-
-func (p *Profile) remapFunctionIDs() {
- seen := make(map[*Function]bool, len(p.Function))
- var fns []*Function
-
- for _, l := range p.Location {
- for _, ln := range l.Line {
- fn := ln.Function
- if fn == nil || seen[fn] {
- continue
- }
- fn.ID = uint64(len(fns) + 1)
- fns = append(fns, fn)
- seen[fn] = true
- }
- }
- p.Function = fns
-}
-
-// remapMappingIDs matches location addresses with existing mappings
-// and updates them appropriately. This is O(N*M), if this ever shows
-// up as a bottleneck, evaluate sorting the mappings and doing a
-// binary search, which would make it O(N*log(M)).
-func (p *Profile) remapMappingIDs() {
- if len(p.Mapping) == 0 {
- return
- }
-
- // Some profile handlers will incorrectly set regions for the main
- // executable if its section is remapped. Fix them through heuristics.
-
- // Remove the initial mapping if named '/anon_hugepage' and has a
- // consecutive adjacent mapping.
- if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") {
- if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start {
- p.Mapping = p.Mapping[1:]
- }
- }
-
- // Subtract the offset from the start of the main mapping if it
- // ends up at a recognizable start address.
- const expectedStart = 0x400000
- if m := p.Mapping[0]; m.Start-m.Offset == expectedStart {
- m.Start = expectedStart
- m.Offset = 0
- }
-
- for _, l := range p.Location {
- if a := l.Address; a != 0 {
- for _, m := range p.Mapping {
- if m.Start <= a && a < m.Limit {
- l.Mapping = m
- break
- }
- }
- }
- }
-
- // Reset all mapping IDs.
- for i, m := range p.Mapping {
- m.ID = uint64(i + 1)
- }
-}
-
-var cpuInts = []func([]byte) (uint64, []byte){
- get32l,
- get32b,
- get64l,
- get64b,
-}
-
-func get32l(b []byte) (uint64, []byte) {
- if len(b) < 4 {
- return 0, nil
- }
- return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:]
-}
-
-func get32b(b []byte) (uint64, []byte) {
- if len(b) < 4 {
- return 0, nil
- }
- return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:]
-}
-
-func get64l(b []byte) (uint64, []byte) {
- if len(b) < 8 {
- return 0, nil
- }
- return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:]
-}
-
-func get64b(b []byte) (uint64, []byte) {
- if len(b) < 8 {
- return 0, nil
- }
- return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:]
-}
-
-// ParseTracebacks parses a set of tracebacks and returns a newly
-// populated profile. It will accept any text file and generate a
-// Profile out of it with any hex addresses it can identify, including
-// a process map if it can recognize one. Each sample will include a
-// tag "source" with the addresses recognized in string format.
-func ParseTracebacks(b []byte) (*Profile, error) {
- r := bytes.NewBuffer(b)
-
- p := &Profile{
- PeriodType: &ValueType{Type: "trace", Unit: "count"},
- Period: 1,
- SampleType: []*ValueType{
- {Type: "trace", Unit: "count"},
- },
- }
-
- var sources []string
- var sloc []*Location
-
- locs := make(map[uint64]*Location)
- for {
- l, err := r.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- return nil, err
- }
- if l == "" {
- break
- }
- }
- if sectionTrigger(l) == memoryMapSection {
- break
- }
- if s, addrs := extractHexAddresses(l); len(s) > 0 {
- for _, addr := range addrs {
- // Addresses from stack traces point to the next instruction after
- // each call. Adjust by -1 to land somewhere on the actual call
- // (except for the leaf, which is not a call).
- if len(sloc) > 0 {
- addr--
- }
- loc := locs[addr]
- if locs[addr] == nil {
- loc = &Location{
- Address: addr,
- }
- p.Location = append(p.Location, loc)
- locs[addr] = loc
- }
- sloc = append(sloc, loc)
- }
-
- sources = append(sources, s...)
- } else {
- if len(sources) > 0 || len(sloc) > 0 {
- addTracebackSample(sloc, sources, p)
- sloc, sources = nil, nil
- }
- }
- }
-
- // Add final sample to save any leftover data.
- if len(sources) > 0 || len(sloc) > 0 {
- addTracebackSample(sloc, sources, p)
- }
-
- if err := p.ParseMemoryMap(r); err != nil {
- return nil, err
- }
- return p, nil
-}
-
-func addTracebackSample(l []*Location, s []string, p *Profile) {
- p.Sample = append(p.Sample,
- &Sample{
- Value: []int64{1},
- Location: l,
- Label: map[string][]string{"source": s},
- })
-}
-
-// parseCPU parses a profilez legacy profile and returns a newly
-// populated Profile.
-//
-// The general format for profilez samples is a sequence of words in
-// binary format. The first words are a header with the following data:
-// 1st word -- 0
-// 2nd word -- 3
-// 3rd word -- 0 if a c++ application, 1 if a java application.
-// 4th word -- Sampling period (in microseconds).
-// 5th word -- Padding.
-func parseCPU(b []byte) (*Profile, error) {
- var parse func([]byte) (uint64, []byte)
- var n1, n2, n3, n4, n5 uint64
- for _, parse = range cpuInts {
- var tmp []byte
- n1, tmp = parse(b)
- n2, tmp = parse(tmp)
- n3, tmp = parse(tmp)
- n4, tmp = parse(tmp)
- n5, tmp = parse(tmp)
-
- if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 {
- b = tmp
- return cpuProfile(b, int64(n4), parse)
- }
- }
- return nil, errUnrecognized
-}
-
-// cpuProfile returns a new Profile from C++ profilez data.
-// b is the profile bytes after the header, period is the profiling
-// period, and parse is a function to parse 8-byte chunks from the
-// profile in its native endianness.
-func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {
- p := &Profile{
- Period: period * 1000,
- PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
- SampleType: []*ValueType{
- {Type: "samples", Unit: "count"},
- {Type: "cpu", Unit: "nanoseconds"},
- },
- }
- var err error
- if b, _, err = parseCPUSamples(b, parse, true, p); err != nil {
- return nil, err
- }
-
- // If all samples have the same second-to-the-bottom frame, it
- // strongly suggests that it is an uninteresting artifact of
- // measurement -- a stack frame pushed by the signal handler. The
- // bottom frame is always correct as it is picked up from the signal
- // structure, not the stack. Check if this is the case and if so,
- // remove.
- if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 {
- allSame := true
- id1 := p.Sample[0].Location[1].Address
- for _, s := range p.Sample {
- if len(s.Location) < 2 || id1 != s.Location[1].Address {
- allSame = false
- break
- }
- }
- if allSame {
- for _, s := range p.Sample {
- s.Location = append(s.Location[:1], s.Location[2:]...)
- }
- }
- }
-
- if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil {
- return nil, err
- }
- return p, nil
-}
-
-// parseCPUSamples parses a collection of profilez samples from a
-// profile.
-//
-// profilez samples are a repeated sequence of stack frames of the
-// form:
-// 1st word -- The number of times this stack was encountered.
-// 2nd word -- The size of the stack (StackSize).
-// 3rd word -- The first address on the stack.
-// ...
-// StackSize + 2 -- The last address on the stack
-// The last stack trace is of the form:
-// 1st word -- 0
-// 2nd word -- 1
-// 3rd word -- 0
-//
-// Addresses from stack traces may point to the next instruction after
-// each call. Optionally adjust by -1 to land somewhere on the actual
-// call (except for the leaf, which is not a call).
-func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) {
- locs := make(map[uint64]*Location)
- for len(b) > 0 {
- var count, nstk uint64
- count, b = parse(b)
- nstk, b = parse(b)
- if b == nil || nstk > uint64(len(b)/4) {
- return nil, nil, errUnrecognized
- }
- var sloc []*Location
- addrs := make([]uint64, nstk)
- for i := 0; i < int(nstk); i++ {
- addrs[i], b = parse(b)
- }
-
- if count == 0 && nstk == 1 && addrs[0] == 0 {
- // End of data marker
- break
- }
- for i, addr := range addrs {
- if adjust && i > 0 {
- addr--
- }
- loc := locs[addr]
- if loc == nil {
- loc = &Location{
- Address: addr,
- }
- locs[addr] = loc
- p.Location = append(p.Location, loc)
- }
- sloc = append(sloc, loc)
- }
- p.Sample = append(p.Sample,
- &Sample{
- Value: []int64{int64(count), int64(count) * int64(p.Period)},
- Location: sloc,
- })
- }
- // Reached the end without finding the EOD marker.
- return b, locs, nil
-}
-
-// parseHeap parses a heapz legacy or a growthz profile and
-// returns a newly populated Profile.
-func parseHeap(b []byte) (p *Profile, err error) {
- r := bytes.NewBuffer(b)
- l, err := r.ReadString('\n')
- if err != nil {
- return nil, errUnrecognized
- }
-
- sampling := ""
-
- if header := heapHeaderRE.FindStringSubmatch(l); header != nil {
- p = &Profile{
- SampleType: []*ValueType{
- {Type: "objects", Unit: "count"},
- {Type: "space", Unit: "bytes"},
- },
- PeriodType: &ValueType{Type: "objects", Unit: "bytes"},
- }
-
- var period int64
- if len(header[6]) > 0 {
- if period, err = strconv.ParseInt(string(header[6]), 10, 64); err != nil {
- return nil, errUnrecognized
- }
- }
-
- switch header[5] {
- case "heapz_v2", "heap_v2":
- sampling, p.Period = "v2", period
- case "heapprofile":
- sampling, p.Period = "", 1
- case "heap":
- sampling, p.Period = "v2", period/2
- default:
- return nil, errUnrecognized
- }
- } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil {
- p = &Profile{
- SampleType: []*ValueType{
- {Type: "objects", Unit: "count"},
- {Type: "space", Unit: "bytes"},
- },
- PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"},
- Period: 1,
- }
- } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil {
- p = &Profile{
- SampleType: []*ValueType{
- {Type: "objects", Unit: "count"},
- {Type: "space", Unit: "bytes"},
- },
- PeriodType: &ValueType{Type: "allocations", Unit: "count"},
- Period: 1,
- }
- } else {
- return nil, errUnrecognized
- }
-
- if LegacyHeapAllocated {
- for _, st := range p.SampleType {
- st.Type = "alloc_" + st.Type
- }
- } else {
- for _, st := range p.SampleType {
- st.Type = "inuse_" + st.Type
- }
- }
-
- locs := make(map[uint64]*Location)
- for {
- l, err = r.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- return nil, err
- }
-
- if l == "" {
- break
- }
- }
-
- if isSpaceOrComment(l) {
- continue
- }
- l = strings.TrimSpace(l)
-
- if sectionTrigger(l) != unrecognizedSection {
- break
- }
-
- value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling)
- if err != nil {
- return nil, err
- }
- var sloc []*Location
- for i, addr := range addrs {
- // Addresses from stack traces point to the next instruction after
- // each call. Adjust by -1 to land somewhere on the actual call
- // (except for the leaf, which is not a call).
- if i > 0 {
- addr--
- }
- loc := locs[addr]
- if locs[addr] == nil {
- loc = &Location{
- Address: addr,
- }
- p.Location = append(p.Location, loc)
- locs[addr] = loc
- }
- sloc = append(sloc, loc)
- }
-
- p.Sample = append(p.Sample, &Sample{
- Value: value,
- Location: sloc,
- NumLabel: map[string][]int64{"bytes": {blocksize}},
- })
- }
-
- if err = parseAdditionalSections(l, r, p); err != nil {
- return nil, err
- }
- return p, nil
-}
-
-// parseHeapSample parses a single row from a heap profile into a new Sample.
-func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) {
- sampleData := heapSampleRE.FindStringSubmatch(line)
- if len(sampleData) != 6 {
- return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData))
- }
-
- // Use first two values by default; tcmalloc sampling generates the
- // same value for both, only the older heap-profile collect separate
- // stats for in-use and allocated objects.
- valueIndex := 1
- if LegacyHeapAllocated {
- valueIndex = 3
- }
-
- var v1, v2 int64
- if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil {
- return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
- }
- if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil {
- return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
- }
-
- if v1 == 0 {
- if v2 != 0 {
- return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2)
- }
- } else {
- blocksize = v2 / v1
- if sampling == "v2" {
- v1, v2 = scaleHeapSample(v1, v2, rate)
- }
- }
-
- value = []int64{v1, v2}
- addrs = parseHexAddresses(sampleData[5])
-
- return value, blocksize, addrs, nil
-}
-
-// extractHexAddresses extracts hex numbers from a string and returns
-// them, together with their numeric value, in a slice.
-func extractHexAddresses(s string) ([]string, []uint64) {
- hexStrings := hexNumberRE.FindAllString(s, -1)
- var ids []uint64
- for _, s := range hexStrings {
- if id, err := strconv.ParseUint(s, 0, 64); err == nil {
- ids = append(ids, id)
- } else {
- // Do not expect any parsing failures due to the regexp matching.
- panic("failed to parse hex value:" + s)
- }
- }
- return hexStrings, ids
-}
-
-// parseHexAddresses parses hex numbers from a string and returns them
-// in a slice.
-func parseHexAddresses(s string) []uint64 {
- _, ids := extractHexAddresses(s)
- return ids
-}
-
-// scaleHeapSample adjusts the data from a heapz Sample to
-// account for its probability of appearing in the collected
-// data. heapz profiles are a sampling of the memory allocations
-// requests in a program. We estimate the unsampled value by dividing
-// each collected sample by its probability of appearing in the
-// profile. heapz v2 profiles rely on a poisson process to determine
-// which samples to collect, based on the desired average collection
-// rate R. The probability of a sample of size S to appear in that
-// profile is 1-exp(-S/R).
-func scaleHeapSample(count, size, rate int64) (int64, int64) {
- if count == 0 || size == 0 {
- return 0, 0
- }
-
- if rate <= 1 {
- // if rate==1 all samples were collected so no adjustment is needed.
- // if rate<1 treat as unknown and skip scaling.
- return count, size
- }
-
- avgSize := float64(size) / float64(count)
- scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
-
- return int64(float64(count) * scale), int64(float64(size) * scale)
-}
-
-// parseContention parses a contentionz profile and returns a newly
-// populated Profile.
-func parseContention(b []byte) (p *Profile, err error) {
- r := bytes.NewBuffer(b)
- l, err := r.ReadString('\n')
- if err != nil {
- return nil, errUnrecognized
- }
-
- if !strings.HasPrefix(l, "--- contention") {
- return nil, errUnrecognized
- }
-
- p = &Profile{
- PeriodType: &ValueType{Type: "contentions", Unit: "count"},
- Period: 1,
- SampleType: []*ValueType{
- {Type: "contentions", Unit: "count"},
- {Type: "delay", Unit: "nanoseconds"},
- },
- }
-
- var cpuHz int64
- // Parse text of the form "attribute = value" before the samples.
- const delimiter = "="
- for {
- l, err = r.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- return nil, err
- }
-
- if l == "" {
- break
- }
- }
-
- if l = strings.TrimSpace(l); l == "" {
- continue
- }
-
- if strings.HasPrefix(l, "---") {
- break
- }
-
- attr := strings.SplitN(l, delimiter, 2)
- if len(attr) != 2 {
- break
- }
- key, val := strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1])
- var err error
- switch key {
- case "cycles/second":
- if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil {
- return nil, errUnrecognized
- }
- case "sampling period":
- if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil {
- return nil, errUnrecognized
- }
- case "ms since reset":
- ms, err := strconv.ParseInt(val, 0, 64)
- if err != nil {
- return nil, errUnrecognized
- }
- p.DurationNanos = ms * 1000 * 1000
- case "format":
- // CPP contentionz profiles don't have format.
- return nil, errUnrecognized
- case "resolution":
- // CPP contentionz profiles don't have resolution.
- return nil, errUnrecognized
- case "discarded samples":
- default:
- return nil, errUnrecognized
- }
- }
-
- locs := make(map[uint64]*Location)
- for {
- if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") {
- break
- }
- value, addrs, err := parseContentionSample(l, p.Period, cpuHz)
- if err != nil {
- return nil, err
- }
- var sloc []*Location
- for i, addr := range addrs {
- // Addresses from stack traces point to the next instruction after
- // each call. Adjust by -1 to land somewhere on the actual call
- // (except for the leaf, which is not a call).
- if i > 0 {
- addr--
- }
- loc := locs[addr]
- if locs[addr] == nil {
- loc = &Location{
- Address: addr,
- }
- p.Location = append(p.Location, loc)
- locs[addr] = loc
- }
- sloc = append(sloc, loc)
- }
- p.Sample = append(p.Sample, &Sample{
- Value: value,
- Location: sloc,
- })
-
- if l, err = r.ReadString('\n'); err != nil {
- if err != io.EOF {
- return nil, err
- }
- if l == "" {
- break
- }
- }
- }
-
- if err = parseAdditionalSections(l, r, p); err != nil {
- return nil, err
- }
-
- return p, nil
-}
-
-// parseContentionSample parses a single row from a contention profile
-// into a new Sample.
-func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) {
- sampleData := contentionSampleRE.FindStringSubmatch(line)
- if sampleData == nil {
- return value, addrs, errUnrecognized
- }
-
- v1, err := strconv.ParseInt(sampleData[1], 10, 64)
- if err != nil {
- return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
- }
- v2, err := strconv.ParseInt(sampleData[2], 10, 64)
- if err != nil {
- return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
- }
-
- // Unsample values if period and cpuHz are available.
- // - Delays are scaled to cycles and then to nanoseconds.
- // - Contentions are scaled to cycles.
- if period > 0 {
- if cpuHz > 0 {
- cpuGHz := float64(cpuHz) / 1e9
- v1 = int64(float64(v1) * float64(period) / cpuGHz)
- }
- v2 = v2 * period
- }
-
- value = []int64{v2, v1}
- addrs = parseHexAddresses(sampleData[3])
-
- return value, addrs, nil
-}
-
-// parseThread parses a Threadz profile and returns a new Profile.
-func parseThread(b []byte) (*Profile, error) {
- r := bytes.NewBuffer(b)
-
- var line string
- var err error
- for {
- // Skip past comments and empty lines seeking a real header.
- line, err = r.ReadString('\n')
- if err != nil {
- return nil, err
- }
- if !isSpaceOrComment(line) {
- break
- }
- }
-
- if m := threadzStartRE.FindStringSubmatch(line); m != nil {
- // Advance over initial comments until first stack trace.
- for {
- line, err = r.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- return nil, err
- }
-
- if line == "" {
- break
- }
- }
- if sectionTrigger(line) != unrecognizedSection || line[0] == '-' {
- break
- }
- }
- } else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {
- return nil, errUnrecognized
- }
-
- p := &Profile{
- SampleType: []*ValueType{{Type: "thread", Unit: "count"}},
- PeriodType: &ValueType{Type: "thread", Unit: "count"},
- Period: 1,
- }
-
- locs := make(map[uint64]*Location)
- // Recognize each thread and populate profile samples.
- for sectionTrigger(line) == unrecognizedSection {
- if strings.HasPrefix(line, "---- no stack trace for") {
- line = ""
- break
- }
- if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {
- return nil, errUnrecognized
- }
-
- var addrs []uint64
- line, addrs, err = parseThreadSample(r)
- if err != nil {
- return nil, errUnrecognized
- }
- if len(addrs) == 0 {
- // We got a --same as previous threads--. Bump counters.
- if len(p.Sample) > 0 {
- s := p.Sample[len(p.Sample)-1]
- s.Value[0]++
- }
- continue
- }
-
- var sloc []*Location
- for i, addr := range addrs {
- // Addresses from stack traces point to the next instruction after
- // each call. Adjust by -1 to land somewhere on the actual call
- // (except for the leaf, which is not a call).
- if i > 0 {
- addr--
- }
- loc := locs[addr]
- if locs[addr] == nil {
- loc = &Location{
- Address: addr,
- }
- p.Location = append(p.Location, loc)
- locs[addr] = loc
- }
- sloc = append(sloc, loc)
- }
-
- p.Sample = append(p.Sample, &Sample{
- Value: []int64{1},
- Location: sloc,
- })
- }
-
- if err = parseAdditionalSections(line, r, p); err != nil {
- return nil, err
- }
-
- return p, nil
-}
-
-// parseThreadSample parses a symbolized or unsymbolized stack trace.
-// Returns the first line after the traceback, the sample (or nil if
-// it hits a 'same-as-previous' marker) and an error.
-func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) {
- var l string
- sameAsPrevious := false
- for {
- if l, err = b.ReadString('\n'); err != nil {
- if err != io.EOF {
- return "", nil, err
- }
- if l == "" {
- break
- }
- }
- if l = strings.TrimSpace(l); l == "" {
- continue
- }
-
- if strings.HasPrefix(l, "---") {
- break
- }
- if strings.Contains(l, "same as previous thread") {
- sameAsPrevious = true
- continue
- }
-
- addrs = append(addrs, parseHexAddresses(l)...)
- }
-
- if sameAsPrevious {
- return l, nil, nil
- }
- return l, addrs, nil
-}
-
-// parseAdditionalSections parses any additional sections in the
-// profile, ignoring any unrecognized sections.
-func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) {
- for {
- if sectionTrigger(l) == memoryMapSection {
- break
- }
- // Ignore any unrecognized sections.
- if l, err := b.ReadString('\n'); err != nil {
- if err != io.EOF {
- return err
- }
- if l == "" {
- break
- }
- }
- }
- return p.ParseMemoryMap(b)
-}
-
-// ParseMemoryMap parses a memory map in the format of
-// /proc/self/maps, and overrides the mappings in the current profile.
-// It renumbers the samples and locations in the profile correspondingly.
-func (p *Profile) ParseMemoryMap(rd io.Reader) error {
- b := bufio.NewReader(rd)
-
- var attrs []string
- var r *strings.Replacer
- const delimiter = "="
- for {
- l, err := b.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- return err
- }
- if l == "" {
- break
- }
- }
- if l = strings.TrimSpace(l); l == "" {
- continue
- }
-
- if r != nil {
- l = r.Replace(l)
- }
- m, err := parseMappingEntry(l)
- if err != nil {
- if err == errUnrecognized {
- // Recognize assignments of the form: attr=value, and replace
- // $attr with value on subsequent mappings.
- if attr := strings.SplitN(l, delimiter, 2); len(attr) == 2 {
- attrs = append(attrs, "$"+strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1]))
- r = strings.NewReplacer(attrs...)
- }
- // Ignore any unrecognized entries
- continue
- }
- return err
- }
- if m == nil || (m.File == "" && len(p.Mapping) != 0) {
- // In some cases the first entry may include the address range
- // but not the name of the file. It should be followed by
- // another entry with the name.
- continue
- }
- if len(p.Mapping) == 1 && p.Mapping[0].File == "" {
- // Update the name if this is the entry following that empty one.
- p.Mapping[0].File = m.File
- continue
- }
- p.Mapping = append(p.Mapping, m)
- }
- p.remapLocationIDs()
- p.remapFunctionIDs()
- p.remapMappingIDs()
- return nil
-}
-
-func parseMappingEntry(l string) (*Mapping, error) {
- mapping := &Mapping{}
- var err error
- if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 {
- if !strings.Contains(me[3], "x") {
- // Skip non-executable entries.
- return nil, nil
- }
- if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil {
- return nil, errUnrecognized
- }
- if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil {
- return nil, errUnrecognized
- }
- if me[4] != "" {
- if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil {
- return nil, errUnrecognized
- }
- }
- mapping.File = me[8]
- return mapping, nil
- }
-
- if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 {
- if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil {
- return nil, errUnrecognized
- }
- if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil {
- return nil, errUnrecognized
- }
- mapping.File = me[3]
- if me[5] != "" {
- if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil {
- return nil, errUnrecognized
- }
- }
- return mapping, nil
- }
-
- return nil, errUnrecognized
-}
-
-type sectionType int
-
-const (
- unrecognizedSection sectionType = iota
- memoryMapSection
-)
-
-var memoryMapTriggers = []string{
- "--- Memory map: ---",
- "MAPPED_LIBRARIES:",
-}
-
-func sectionTrigger(line string) sectionType {
- for _, trigger := range memoryMapTriggers {
- if strings.Contains(line, trigger) {
- return memoryMapSection
- }
- }
- return unrecognizedSection
-}
-
-func (p *Profile) addLegacyFrameInfo() {
- switch {
- case isProfileType(p, heapzSampleTypes) ||
- isProfileType(p, heapzInUseSampleTypes) ||
- isProfileType(p, heapzAllocSampleTypes):
- p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr
- case isProfileType(p, contentionzSampleTypes):
- p.DropFrames, p.KeepFrames = lockRxStr, ""
- default:
- p.DropFrames, p.KeepFrames = cpuProfilerRxStr, ""
- }
-}
-
-var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles
-var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"}
-var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"}
-var contentionzSampleTypes = []string{"contentions", "delay"}
-
-func isProfileType(p *Profile, t []string) bool {
- st := p.SampleType
- if len(st) != len(t) {
- return false
- }
-
- for i := range st {
- if st[i].Type != t[i] {
- return false
- }
- }
- return true
-}
-
-var allocRxStr = strings.Join([]string{
- // POSIX entry points.
- `calloc`,
- `cfree`,
- `malloc`,
- `free`,
- `memalign`,
- `do_memalign`,
- `(__)?posix_memalign`,
- `pvalloc`,
- `valloc`,
- `realloc`,
-
- // TC malloc.
- `tcmalloc::.*`,
- `tc_calloc`,
- `tc_cfree`,
- `tc_malloc`,
- `tc_free`,
- `tc_memalign`,
- `tc_posix_memalign`,
- `tc_pvalloc`,
- `tc_valloc`,
- `tc_realloc`,
- `tc_new`,
- `tc_delete`,
- `tc_newarray`,
- `tc_deletearray`,
- `tc_new_nothrow`,
- `tc_newarray_nothrow`,
-
- // Memory-allocation routines on OS X.
- `malloc_zone_malloc`,
- `malloc_zone_calloc`,
- `malloc_zone_valloc`,
- `malloc_zone_realloc`,
- `malloc_zone_memalign`,
- `malloc_zone_free`,
-
- // Go runtime
- `runtime\..*`,
-
- // Other misc. memory allocation routines
- `BaseArena::.*`,
- `(::)?do_malloc_no_errno`,
- `(::)?do_malloc_pages`,
- `(::)?do_malloc`,
- `DoSampledAllocation`,
- `MallocedMemBlock::MallocedMemBlock`,
- `_M_allocate`,
- `__builtin_(vec_)?delete`,
- `__builtin_(vec_)?new`,
- `__gnu_cxx::new_allocator::allocate`,
- `__libc_malloc`,
- `__malloc_alloc_template::allocate`,
- `allocate`,
- `cpp_alloc`,
- `operator new(\[\])?`,
- `simple_alloc::allocate`,
-}, `|`)
-
-var allocSkipRxStr = strings.Join([]string{
- // Preserve Go runtime frames that appear in the middle/bottom of
- // the stack.
- `runtime\.panic`,
-}, `|`)
-
-var cpuProfilerRxStr = strings.Join([]string{
- `ProfileData::Add`,
- `ProfileData::prof_handler`,
- `CpuProfiler::prof_handler`,
- `__pthread_sighandler`,
- `__restore`,
-}, `|`)
-
-var lockRxStr = strings.Join([]string{
- `RecordLockProfileData`,
- `(base::)?RecordLockProfileData.*`,
- `(base::)?SubmitMutexProfileData.*`,
- `(base::)?SubmitSpinLockProfileData.*`,
- `(Mutex::)?AwaitCommon.*`,
- `(Mutex::)?Unlock.*`,
- `(Mutex::)?UnlockSlow.*`,
- `(Mutex::)?ReaderUnlock.*`,
- `(MutexLock::)?~MutexLock.*`,
- `(SpinLock::)?Unlock.*`,
- `(SpinLock::)?SlowUnlock.*`,
- `(SpinLockHolder::)?~SpinLockHolder.*`,
-}, `|`)
diff --git a/src/cmd/pprof/internal/profile/profile.go b/src/cmd/pprof/internal/profile/profile.go
deleted file mode 100644
index 28e713d7be..0000000000
--- a/src/cmd/pprof/internal/profile/profile.go
+++ /dev/null
@@ -1,572 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package profile provides a representation of profile.proto and
-// methods to encode/decode profiles in this format.
-package profile
-
-import (
- "bytes"
- "compress/gzip"
- "fmt"
- "io"
- "io/ioutil"
- "regexp"
- "strings"
- "time"
-)
-
-// Profile is an in-memory representation of profile.proto.
-type Profile struct {
- SampleType []*ValueType
- Sample []*Sample
- Mapping []*Mapping
- Location []*Location
- Function []*Function
-
- DropFrames string
- KeepFrames string
-
- TimeNanos int64
- DurationNanos int64
- PeriodType *ValueType
- Period int64
-
- dropFramesX int64
- keepFramesX int64
- stringTable []string
-}
-
-// ValueType corresponds to Profile.ValueType
-type ValueType struct {
- Type string // cpu, wall, inuse_space, etc
- Unit string // seconds, nanoseconds, bytes, etc
-
- typeX int64
- unitX int64
-}
-
-// Sample corresponds to Profile.Sample
-type Sample struct {
- Location []*Location
- Value []int64
- Label map[string][]string
- NumLabel map[string][]int64
-
- locationIDX []uint64
- labelX []Label
-}
-
-// Label corresponds to Profile.Label
-type Label struct {
- keyX int64
- // Exactly one of the two following values must be set
- strX int64
- numX int64 // Integer value for this label
-}
-
-// Mapping corresponds to Profile.Mapping
-type Mapping struct {
- ID uint64
- Start uint64
- Limit uint64
- Offset uint64
- File string
- BuildID string
- HasFunctions bool
- HasFilenames bool
- HasLineNumbers bool
- HasInlineFrames bool
-
- fileX int64
- buildIDX int64
-}
-
-// Location corresponds to Profile.Location
-type Location struct {
- ID uint64
- Mapping *Mapping
- Address uint64
- Line []Line
-
- mappingIDX uint64
-}
-
-// Line corresponds to Profile.Line
-type Line struct {
- Function *Function
- Line int64
-
- functionIDX uint64
-}
-
-// Function corresponds to Profile.Function
-type Function struct {
- ID uint64
- Name string
- SystemName string
- Filename string
- StartLine int64
-
- nameX int64
- systemNameX int64
- filenameX int64
-}
-
-// Parse parses a profile and checks for its validity. The input
-// may be a gzip-compressed encoded protobuf or one of many legacy
-// profile formats which may be unsupported in the future.
-func Parse(r io.Reader) (*Profile, error) {
- orig, err := ioutil.ReadAll(r)
- if err != nil {
- return nil, err
- }
-
- var p *Profile
- if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b {
- gz, err := gzip.NewReader(bytes.NewBuffer(orig))
- if err != nil {
- return nil, fmt.Errorf("decompressing profile: %v", err)
- }
- data, err := ioutil.ReadAll(gz)
- if err != nil {
- return nil, fmt.Errorf("decompressing profile: %v", err)
- }
- orig = data
- }
- if p, err = parseUncompressed(orig); err != nil {
- if p, err = parseLegacy(orig); err != nil {
- return nil, fmt.Errorf("parsing profile: %v", err)
- }
- }
-
- if err := p.CheckValid(); err != nil {
- return nil, fmt.Errorf("malformed profile: %v", err)
- }
- return p, nil
-}
-
-var errUnrecognized = fmt.Errorf("unrecognized profile format")
-var errMalformed = fmt.Errorf("malformed profile format")
-
-func parseLegacy(data []byte) (*Profile, error) {
- parsers := []func([]byte) (*Profile, error){
- parseCPU,
- parseHeap,
- parseGoCount, // goroutine, threadcreate
- parseThread,
- parseContention,
- }
-
- for _, parser := range parsers {
- p, err := parser(data)
- if err == nil {
- p.setMain()
- p.addLegacyFrameInfo()
- return p, nil
- }
- if err != errUnrecognized {
- return nil, err
- }
- }
- return nil, errUnrecognized
-}
-
-func parseUncompressed(data []byte) (*Profile, error) {
- p := &Profile{}
- if err := unmarshal(data, p); err != nil {
- return nil, err
- }
-
- if err := p.postDecode(); err != nil {
- return nil, err
- }
-
- return p, nil
-}
-
-var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
-
-// setMain scans Mapping entries and guesses which entry is main
-// because legacy profiles don't obey the convention of putting main
-// first.
-func (p *Profile) setMain() {
- for i := 0; i < len(p.Mapping); i++ {
- file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1))
- if len(file) == 0 {
- continue
- }
- if len(libRx.FindStringSubmatch(file)) > 0 {
- continue
- }
- if strings.HasPrefix(file, "[") {
- continue
- }
- // Swap what we guess is main to position 0.
- tmp := p.Mapping[i]
- p.Mapping[i] = p.Mapping[0]
- p.Mapping[0] = tmp
- break
- }
-}
-
-// Write writes the profile as a gzip-compressed marshaled protobuf.
-func (p *Profile) Write(w io.Writer) error {
- p.preEncode()
- b := marshal(p)
- zw := gzip.NewWriter(w)
- defer zw.Close()
- _, err := zw.Write(b)
- return err
-}
-
-// CheckValid tests whether the profile is valid. Checks include, but are
-// not limited to:
-// - len(Profile.Sample[n].value) == len(Profile.value_unit)
-// - Sample.id has a corresponding Profile.Location
-func (p *Profile) CheckValid() error {
- // Check that sample values are consistent
- sampleLen := len(p.SampleType)
- if sampleLen == 0 && len(p.Sample) != 0 {
- return fmt.Errorf("missing sample type information")
- }
- for _, s := range p.Sample {
- if len(s.Value) != sampleLen {
- return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
- }
- }
-
- // Check that all mappings/locations/functions are in the tables
- // Check that there are no duplicate ids
- mappings := make(map[uint64]*Mapping, len(p.Mapping))
- for _, m := range p.Mapping {
- if m.ID == 0 {
- return fmt.Errorf("found mapping with reserved ID=0")
- }
- if mappings[m.ID] != nil {
- return fmt.Errorf("multiple mappings with same id: %d", m.ID)
- }
- mappings[m.ID] = m
- }
- functions := make(map[uint64]*Function, len(p.Function))
- for _, f := range p.Function {
- if f.ID == 0 {
- return fmt.Errorf("found function with reserved ID=0")
- }
- if functions[f.ID] != nil {
- return fmt.Errorf("multiple functions with same id: %d", f.ID)
- }
- functions[f.ID] = f
- }
- locations := make(map[uint64]*Location, len(p.Location))
- for _, l := range p.Location {
- if l.ID == 0 {
- return fmt.Errorf("found location with reserved id=0")
- }
- if locations[l.ID] != nil {
- return fmt.Errorf("multiple locations with same id: %d", l.ID)
- }
- locations[l.ID] = l
- if m := l.Mapping; m != nil {
- if m.ID == 0 || mappings[m.ID] != m {
- return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
- }
- }
- for _, ln := range l.Line {
- if f := ln.Function; f != nil {
- if f.ID == 0 || functions[f.ID] != f {
- return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
- }
- }
- }
- }
- return nil
-}
-
-// Aggregate merges the locations in the profile into equivalence
-// classes preserving the request attributes. It also updates the
-// samples to point to the merged locations.
-func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
- for _, m := range p.Mapping {
- m.HasInlineFrames = m.HasInlineFrames && inlineFrame
- m.HasFunctions = m.HasFunctions && function
- m.HasFilenames = m.HasFilenames && filename
- m.HasLineNumbers = m.HasLineNumbers && linenumber
- }
-
- // Aggregate functions
- if !function || !filename {
- for _, f := range p.Function {
- if !function {
- f.Name = ""
- f.SystemName = ""
- }
- if !filename {
- f.Filename = ""
- }
- }
- }
-
- // Aggregate locations
- if !inlineFrame || !address || !linenumber {
- for _, l := range p.Location {
- if !inlineFrame && len(l.Line) > 1 {
- l.Line = l.Line[len(l.Line)-1:]
- }
- if !linenumber {
- for i := range l.Line {
- l.Line[i].Line = 0
- }
- }
- if !address {
- l.Address = 0
- }
- }
- }
-
- return p.CheckValid()
-}
-
-// Print dumps a text representation of a profile. Intended mainly
-// for debugging purposes.
-func (p *Profile) String() string {
-
- ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
- if pt := p.PeriodType; pt != nil {
- ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
- }
- ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
- if p.TimeNanos != 0 {
- ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
- }
- if p.DurationNanos != 0 {
- ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos)))
- }
-
- ss = append(ss, "Samples:")
- var sh1 string
- for _, s := range p.SampleType {
- sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit)
- }
- ss = append(ss, strings.TrimSpace(sh1))
- for _, s := range p.Sample {
- var sv string
- for _, v := range s.Value {
- sv = fmt.Sprintf("%s %10d", sv, v)
- }
- sv = sv + ": "
- for _, l := range s.Location {
- sv = sv + fmt.Sprintf("%d ", l.ID)
- }
- ss = append(ss, sv)
- const labelHeader = " "
- if len(s.Label) > 0 {
- ls := labelHeader
- for k, v := range s.Label {
- ls = ls + fmt.Sprintf("%s:%v ", k, v)
- }
- ss = append(ss, ls)
- }
- if len(s.NumLabel) > 0 {
- ls := labelHeader
- for k, v := range s.NumLabel {
- ls = ls + fmt.Sprintf("%s:%v ", k, v)
- }
- ss = append(ss, ls)
- }
- }
-
- ss = append(ss, "Locations")
- for _, l := range p.Location {
- locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
- if m := l.Mapping; m != nil {
- locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
- }
- if len(l.Line) == 0 {
- ss = append(ss, locStr)
- }
- for li := range l.Line {
- lnStr := "??"
- if fn := l.Line[li].Function; fn != nil {
- lnStr = fmt.Sprintf("%s %s:%d s=%d",
- fn.Name,
- fn.Filename,
- l.Line[li].Line,
- fn.StartLine)
- if fn.Name != fn.SystemName {
- lnStr = lnStr + "(" + fn.SystemName + ")"
- }
- }
- ss = append(ss, locStr+lnStr)
- // Do not print location details past the first line
- locStr = " "
- }
- }
-
- ss = append(ss, "Mappings")
- for _, m := range p.Mapping {
- bits := ""
- if m.HasFunctions {
- bits = bits + "[FN]"
- }
- if m.HasFilenames {
- bits = bits + "[FL]"
- }
- if m.HasLineNumbers {
- bits = bits + "[LN]"
- }
- if m.HasInlineFrames {
- bits = bits + "[IN]"
- }
- ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
- m.ID,
- m.Start, m.Limit, m.Offset,
- m.File,
- m.BuildID,
- bits))
- }
-
- return strings.Join(ss, "\n") + "\n"
-}
-
-// Merge adds profile p adjusted by ratio r into profile p. Profiles
-// must be compatible (same Type and SampleType).
-// TODO(rsilvera): consider normalizing the profiles based on the
-// total samples collected.
-func (p *Profile) Merge(pb *Profile, r float64) error {
- if err := p.Compatible(pb); err != nil {
- return err
- }
-
- pb = pb.Copy()
-
- // Keep the largest of the two periods.
- if pb.Period > p.Period {
- p.Period = pb.Period
- }
-
- p.DurationNanos += pb.DurationNanos
-
- p.Mapping = append(p.Mapping, pb.Mapping...)
- for i, m := range p.Mapping {
- m.ID = uint64(i + 1)
- }
- p.Location = append(p.Location, pb.Location...)
- for i, l := range p.Location {
- l.ID = uint64(i + 1)
- }
- p.Function = append(p.Function, pb.Function...)
- for i, f := range p.Function {
- f.ID = uint64(i + 1)
- }
-
- if r != 1.0 {
- for _, s := range pb.Sample {
- for i, v := range s.Value {
- s.Value[i] = int64((float64(v) * r))
- }
- }
- }
- p.Sample = append(p.Sample, pb.Sample...)
- return p.CheckValid()
-}
-
-// Compatible determines if two profiles can be compared/merged.
-// returns nil if the profiles are compatible; otherwise an error with
-// details on the incompatibility.
-func (p *Profile) Compatible(pb *Profile) error {
- if !compatibleValueTypes(p.PeriodType, pb.PeriodType) {
- return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
- }
-
- if len(p.SampleType) != len(pb.SampleType) {
- return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
- }
-
- for i := range p.SampleType {
- if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) {
- return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
- }
- }
-
- return nil
-}
-
-// HasFunctions determines if all locations in this profile have
-// symbolized function information.
-func (p *Profile) HasFunctions() bool {
- for _, l := range p.Location {
- if l.Mapping == nil || !l.Mapping.HasFunctions {
- return false
- }
- }
- return true
-}
-
-// HasFileLines determines if all locations in this profile have
-// symbolized file and line number information.
-func (p *Profile) HasFileLines() bool {
- for _, l := range p.Location {
- if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
- return false
- }
- }
- return true
-}
-
-func compatibleValueTypes(v1, v2 *ValueType) bool {
- if v1 == nil || v2 == nil {
- return true // No grounds to disqualify.
- }
- return v1.Type == v2.Type && v1.Unit == v2.Unit
-}
-
-// Copy makes a fully independent copy of a profile.
-func (p *Profile) Copy() *Profile {
- p.preEncode()
- b := marshal(p)
-
- pp := &Profile{}
- if err := unmarshal(b, pp); err != nil {
- panic(err)
- }
- if err := pp.postDecode(); err != nil {
- panic(err)
- }
-
- return pp
-}
-
-// Demangler maps symbol names to a human-readable form. This may
-// include C++ demangling and additional simplification. Names that
-// are not demangled may be missing from the resulting map.
-type Demangler func(name []string) (map[string]string, error)
-
-// Demangle attempts to demangle and optionally simplify any function
-// names referenced in the profile. It works on a best-effort basis:
-// it will silently preserve the original names in case of any errors.
-func (p *Profile) Demangle(d Demangler) error {
- // Collect names to demangle.
- var names []string
- for _, fn := range p.Function {
- names = append(names, fn.SystemName)
- }
-
- // Update profile with demangled names.
- demangled, err := d(names)
- if err != nil {
- return err
- }
- for _, fn := range p.Function {
- if dd, ok := demangled[fn.SystemName]; ok {
- fn.Name = dd
- }
- }
- return nil
-}
-
-// Empty returns true if the profile contains no samples.
-func (p *Profile) Empty() bool {
- return len(p.Sample) == 0
-}
diff --git a/src/cmd/pprof/internal/profile/profile_test.go b/src/cmd/pprof/internal/profile/profile_test.go
deleted file mode 100644
index 09b11a456f..0000000000
--- a/src/cmd/pprof/internal/profile/profile_test.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package profile
-
-import (
- "bytes"
- "testing"
-)
-
-func TestEmptyProfile(t *testing.T) {
- var buf bytes.Buffer
- p, err := Parse(&buf)
- if err != nil {
- t.Error("Want no error, got", err)
- }
- if p == nil {
- t.Fatal("Want a valid profile, got <nil>")
- }
- if !p.Empty() {
- t.Errorf("Profile should be empty, got %#v", p)
- }
-}
diff --git a/src/cmd/pprof/internal/profile/proto.go b/src/cmd/pprof/internal/profile/proto.go
deleted file mode 100644
index 11d7f9ff9b..0000000000
--- a/src/cmd/pprof/internal/profile/proto.go
+++ /dev/null
@@ -1,360 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// This file is a simple protocol buffer encoder and decoder.
-//
-// A protocol message must implement the message interface:
-// decoder() []decoder
-// encode(*buffer)
-//
-// The decode method returns a slice indexed by field number that gives the
-// function to decode that field.
-// The encode method encodes its receiver into the given buffer.
-//
-// The two methods are simple enough to be implemented by hand rather than
-// by using a protocol compiler.
-//
-// See profile.go for examples of messages implementing this interface.
-//
-// There is no support for groups, message sets, or "has" bits.
-
-package profile
-
-import "errors"
-
-type buffer struct {
- field int
- typ int
- u64 uint64
- data []byte
- tmp [16]byte
-}
-
-type decoder func(*buffer, message) error
-
-type message interface {
- decoder() []decoder
- encode(*buffer)
-}
-
-func marshal(m message) []byte {
- var b buffer
- m.encode(&b)
- return b.data
-}
-
-func encodeVarint(b *buffer, x uint64) {
- for x >= 128 {
- b.data = append(b.data, byte(x)|0x80)
- x >>= 7
- }
- b.data = append(b.data, byte(x))
-}
-
-func encodeLength(b *buffer, tag int, len int) {
- encodeVarint(b, uint64(tag)<<3|2)
- encodeVarint(b, uint64(len))
-}
-
-func encodeUint64(b *buffer, tag int, x uint64) {
- // append varint to b.data
- encodeVarint(b, uint64(tag)<<3|0)
- encodeVarint(b, x)
-}
-
-func encodeUint64s(b *buffer, tag int, x []uint64) {
- if len(x) > 2 {
- // Use packed encoding
- n1 := len(b.data)
- for _, u := range x {
- encodeVarint(b, u)
- }
- n2 := len(b.data)
- encodeLength(b, tag, n2-n1)
- n3 := len(b.data)
- copy(b.tmp[:], b.data[n2:n3])
- copy(b.data[n1+(n3-n2):], b.data[n1:n2])
- copy(b.data[n1:], b.tmp[:n3-n2])
- return
- }
- for _, u := range x {
- encodeUint64(b, tag, u)
- }
-}
-
-func encodeUint64Opt(b *buffer, tag int, x uint64) {
- if x == 0 {
- return
- }
- encodeUint64(b, tag, x)
-}
-
-func encodeInt64(b *buffer, tag int, x int64) {
- u := uint64(x)
- encodeUint64(b, tag, u)
-}
-
-func encodeInt64Opt(b *buffer, tag int, x int64) {
- if x == 0 {
- return
- }
- encodeInt64(b, tag, x)
-}
-
-func encodeInt64s(b *buffer, tag int, x []int64) {
- if len(x) > 2 {
- // Use packed encoding
- n1 := len(b.data)
- for _, u := range x {
- encodeVarint(b, uint64(u))
- }
- n2 := len(b.data)
- encodeLength(b, tag, n2-n1)
- n3 := len(b.data)
- copy(b.tmp[:], b.data[n2:n3])
- copy(b.data[n1+(n3-n2):], b.data[n1:n2])
- copy(b.data[n1:], b.tmp[:n3-n2])
- return
- }
- for _, u := range x {
- encodeInt64(b, tag, u)
- }
-}
-
-func encodeString(b *buffer, tag int, x string) {
- encodeLength(b, tag, len(x))
- b.data = append(b.data, x...)
-}
-
-func encodeStrings(b *buffer, tag int, x []string) {
- for _, s := range x {
- encodeString(b, tag, s)
- }
-}
-
-func encodeStringOpt(b *buffer, tag int, x string) {
- if x == "" {
- return
- }
- encodeString(b, tag, x)
-}
-
-func encodeBool(b *buffer, tag int, x bool) {
- if x {
- encodeUint64(b, tag, 1)
- } else {
- encodeUint64(b, tag, 0)
- }
-}
-
-func encodeBoolOpt(b *buffer, tag int, x bool) {
- if x == false {
- return
- }
- encodeBool(b, tag, x)
-}
-
-func encodeMessage(b *buffer, tag int, m message) {
- n1 := len(b.data)
- m.encode(b)
- n2 := len(b.data)
- encodeLength(b, tag, n2-n1)
- n3 := len(b.data)
- copy(b.tmp[:], b.data[n2:n3])
- copy(b.data[n1+(n3-n2):], b.data[n1:n2])
- copy(b.data[n1:], b.tmp[:n3-n2])
-}
-
-func unmarshal(data []byte, m message) (err error) {
- b := buffer{data: data, typ: 2}
- return decodeMessage(&b, m)
-}
-
-func le64(p []byte) uint64 {
- return uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56
-}
-
-func le32(p []byte) uint32 {
- return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24
-}
-
-func decodeVarint(data []byte) (uint64, []byte, error) {
- var i int
- var u uint64
- for i = 0; ; i++ {
- if i >= 10 || i >= len(data) {
- return 0, nil, errors.New("bad varint")
- }
- u |= uint64(data[i]&0x7F) << uint(7*i)
- if data[i]&0x80 == 0 {
- return u, data[i+1:], nil
- }
- }
-}
-
-func decodeField(b *buffer, data []byte) ([]byte, error) {
- x, data, err := decodeVarint(data)
- if err != nil {
- return nil, err
- }
- b.field = int(x >> 3)
- b.typ = int(x & 7)
- b.data = nil
- b.u64 = 0
- switch b.typ {
- case 0:
- b.u64, data, err = decodeVarint(data)
- if err != nil {
- return nil, err
- }
- case 1:
- if len(data) < 8 {
- return nil, errors.New("not enough data")
- }
- b.u64 = le64(data[:8])
- data = data[8:]
- case 2:
- var n uint64
- n, data, err = decodeVarint(data)
- if err != nil {
- return nil, err
- }
- if n > uint64(len(data)) {
- return nil, errors.New("too much data")
- }
- b.data = data[:n]
- data = data[n:]
- case 5:
- if len(data) < 4 {
- return nil, errors.New("not enough data")
- }
- b.u64 = uint64(le32(data[:4]))
- data = data[4:]
- default:
- return nil, errors.New("unknown type: " + string(b.typ))
- }
-
- return data, nil
-}
-
-func checkType(b *buffer, typ int) error {
- if b.typ != typ {
- return errors.New("type mismatch")
- }
- return nil
-}
-
-func decodeMessage(b *buffer, m message) error {
- if err := checkType(b, 2); err != nil {
- return err
- }
- dec := m.decoder()
- data := b.data
- for len(data) > 0 {
- // pull varint field# + type
- var err error
- data, err = decodeField(b, data)
- if err != nil {
- return err
- }
- if b.field >= len(dec) || dec[b.field] == nil {
- continue
- }
- if err := dec[b.field](b, m); err != nil {
- return err
- }
- }
- return nil
-}
-
-func decodeInt64(b *buffer, x *int64) error {
- if err := checkType(b, 0); err != nil {
- return err
- }
- *x = int64(b.u64)
- return nil
-}
-
-func decodeInt64s(b *buffer, x *[]int64) error {
- if b.typ == 2 {
- // Packed encoding
- data := b.data
- for len(data) > 0 {
- var u uint64
- var err error
-
- if u, data, err = decodeVarint(data); err != nil {
- return err
- }
- *x = append(*x, int64(u))
- }
- return nil
- }
- var i int64
- if err := decodeInt64(b, &i); err != nil {
- return err
- }
- *x = append(*x, i)
- return nil
-}
-
-func decodeUint64(b *buffer, x *uint64) error {
- if err := checkType(b, 0); err != nil {
- return err
- }
- *x = b.u64
- return nil
-}
-
-func decodeUint64s(b *buffer, x *[]uint64) error {
- if b.typ == 2 {
- data := b.data
- // Packed encoding
- for len(data) > 0 {
- var u uint64
- var err error
-
- if u, data, err = decodeVarint(data); err != nil {
- return err
- }
- *x = append(*x, u)
- }
- return nil
- }
- var u uint64
- if err := decodeUint64(b, &u); err != nil {
- return err
- }
- *x = append(*x, u)
- return nil
-}
-
-func decodeString(b *buffer, x *string) error {
- if err := checkType(b, 2); err != nil {
- return err
- }
- *x = string(b.data)
- return nil
-}
-
-func decodeStrings(b *buffer, x *[]string) error {
- var s string
- if err := decodeString(b, &s); err != nil {
- return err
- }
- *x = append(*x, s)
- return nil
-}
-
-func decodeBool(b *buffer, x *bool) error {
- if err := checkType(b, 0); err != nil {
- return err
- }
- if int64(b.u64) == 0 {
- *x = false
- } else {
- *x = true
- }
- return nil
-}
diff --git a/src/cmd/pprof/internal/profile/proto_test.go b/src/cmd/pprof/internal/profile/proto_test.go
deleted file mode 100644
index c2613fc375..0000000000
--- a/src/cmd/pprof/internal/profile/proto_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package profile
-
-import (
- "reflect"
- "testing"
-)
-
-func TestPackedEncoding(t *testing.T) {
-
- type testcase struct {
- uint64s []uint64
- int64s []int64
- encoded []byte
- }
- for i, tc := range []testcase{
- {
- []uint64{0, 1, 10, 100, 1000, 10000},
- []int64{1000, 0, 1000},
- []byte{10, 8, 0, 1, 10, 100, 232, 7, 144, 78, 18, 5, 232, 7, 0, 232, 7},
- },
- {
- []uint64{10000},
- nil,
- []byte{8, 144, 78},
- },
- {
- nil,
- []int64{-10000},
- []byte{16, 240, 177, 255, 255, 255, 255, 255, 255, 255, 1},
- },
- } {
- source := &packedInts{tc.uint64s, tc.int64s}
- if got, want := marshal(source), tc.encoded; !reflect.DeepEqual(got, want) {
- t.Errorf("failed encode %d, got %v, want %v", i, got, want)
- }
-
- dest := new(packedInts)
- if err := unmarshal(tc.encoded, dest); err != nil {
- t.Errorf("failed decode %d: %v", i, err)
- continue
- }
- if got, want := dest.uint64s, tc.uint64s; !reflect.DeepEqual(got, want) {
- t.Errorf("failed decode uint64s %d, got %v, want %v", i, got, want)
- }
- if got, want := dest.int64s, tc.int64s; !reflect.DeepEqual(got, want) {
- t.Errorf("failed decode int64s %d, got %v, want %v", i, got, want)
- }
- }
-}
-
-type packedInts struct {
- uint64s []uint64
- int64s []int64
-}
-
-func (u *packedInts) decoder() []decoder {
- return []decoder{
- nil,
- func(b *buffer, m message) error { return decodeUint64s(b, &m.(*packedInts).uint64s) },
- func(b *buffer, m message) error { return decodeInt64s(b, &m.(*packedInts).int64s) },
- }
-}
-
-func (u *packedInts) encode(b *buffer) {
- encodeUint64s(b, 1, u.uint64s)
- encodeInt64s(b, 2, u.int64s)
-}
diff --git a/src/cmd/pprof/internal/profile/prune.go b/src/cmd/pprof/internal/profile/prune.go
deleted file mode 100644
index 1924fada7a..0000000000
--- a/src/cmd/pprof/internal/profile/prune.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Implements methods to remove frames from profiles.
-
-package profile
-
-import (
- "fmt"
- "regexp"
-)
-
-// Prune removes all nodes beneath a node matching dropRx, and not
-// matching keepRx. If the root node of a Sample matches, the sample
-// will have an empty stack.
-func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) {
- prune := make(map[uint64]bool)
- pruneBeneath := make(map[uint64]bool)
-
- for _, loc := range p.Location {
- var i int
- for i = len(loc.Line) - 1; i >= 0; i-- {
- if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
- funcName := fn.Name
- // Account for leading '.' on the PPC ELF v1 ABI.
- if funcName[0] == '.' {
- funcName = funcName[1:]
- }
- if dropRx.MatchString(funcName) {
- if keepRx == nil || !keepRx.MatchString(funcName) {
- break
- }
- }
- }
- }
-
- if i >= 0 {
- // Found matching entry to prune.
- pruneBeneath[loc.ID] = true
-
- // Remove the matching location.
- if i == len(loc.Line)-1 {
- // Matched the top entry: prune the whole location.
- prune[loc.ID] = true
- } else {
- loc.Line = loc.Line[i+1:]
- }
- }
- }
-
- // Prune locs from each Sample
- for _, sample := range p.Sample {
- // Scan from the root to the leaves to find the prune location.
- // Do not prune frames before the first user frame, to avoid
- // pruning everything.
- foundUser := false
- for i := len(sample.Location) - 1; i >= 0; i-- {
- id := sample.Location[i].ID
- if !prune[id] && !pruneBeneath[id] {
- foundUser = true
- continue
- }
- if !foundUser {
- continue
- }
- if prune[id] {
- sample.Location = sample.Location[i+1:]
- break
- }
- if pruneBeneath[id] {
- sample.Location = sample.Location[i:]
- break
- }
- }
- }
-}
-
-// RemoveUninteresting prunes and elides profiles using built-in
-// tables of uninteresting function names.
-func (p *Profile) RemoveUninteresting() error {
- var keep, drop *regexp.Regexp
- var err error
-
- if p.DropFrames != "" {
- if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil {
- return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err)
- }
- if p.KeepFrames != "" {
- if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil {
- return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err)
- }
- }
- p.Prune(drop, keep)
- }
- return nil
-}
diff --git a/src/cmd/pprof/internal/report/report.go b/src/cmd/pprof/internal/report/report.go
deleted file mode 100644
index 86bd4a280b..0000000000
--- a/src/cmd/pprof/internal/report/report.go
+++ /dev/null
@@ -1,1682 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package report summarizes a performance profile into a
-// human-readable report.
-package report
-
-import (
- "fmt"
- "io"
- "math"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "time"
-
- "cmd/pprof/internal/plugin"
- "cmd/pprof/internal/profile"
-)
-
-// Generate generates a report as directed by the Report.
-func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
- o := rpt.options
-
- switch o.OutputFormat {
- case Dot:
- return printDOT(w, rpt)
- case Tree:
- return printTree(w, rpt)
- case Text:
- return printText(w, rpt)
- case Raw:
- fmt.Fprint(w, rpt.prof.String())
- return nil
- case Tags:
- return printTags(w, rpt)
- case Proto:
- return rpt.prof.Write(w)
- case Dis:
- return printAssembly(w, rpt, obj)
- case List:
- return printSource(w, rpt)
- case WebList:
- return printWebSource(w, rpt, obj)
- case Callgrind:
- return printCallgrind(w, rpt)
- }
- return fmt.Errorf("unexpected output format")
-}
-
-// printAssembly prints an annotated assembly listing.
-func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
- g, err := newGraph(rpt)
- if err != nil {
- return err
- }
-
- o := rpt.options
- prof := rpt.prof
-
- // If the regexp source can be parsed as an address, also match
- // functions that land on that address.
- var address *uint64
- if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
- address = &hex
- }
-
- fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
- symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
- symNodes := nodesPerSymbol(g.ns, symbols)
- // Sort function names for printing.
- var syms objSymbols
- for s := range symNodes {
- syms = append(syms, s)
- }
- sort.Sort(syms)
-
- // Correlate the symbols from the binary with the profile samples.
- for _, s := range syms {
- sns := symNodes[s]
-
- // Gather samples for this symbol.
- flatSum, cumSum := sumNodes(sns)
-
- // Get the function assembly.
- insns, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End)
- if err != nil {
- return err
- }
-
- ns := annotateAssembly(insns, sns, s.base)
-
- fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
- for _, name := range s.sym.Name[1:] {
- fmt.Fprintf(w, " AKA ======================== %s\n", name)
- }
- fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
- rpt.formatValue(flatSum), rpt.formatValue(cumSum),
- percentage(cumSum, rpt.total))
-
- for _, n := range ns {
- fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.flat, rpt), valueOrDot(n.cum, rpt), n.info.address, n.info.name)
- }
- }
- return nil
-}
-
-// symbolsFromBinaries examines the binaries listed on the profile
-// that have associated samples, and identifies symbols matching rx.
-func symbolsFromBinaries(prof *profile.Profile, g graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
- hasSamples := make(map[string]bool)
- // Only examine mappings that have samples that match the
- // regexp. This is an optimization to speed up pprof.
- for _, n := range g.ns {
- if name := n.info.prettyName(); rx.MatchString(name) && n.info.objfile != "" {
- hasSamples[n.info.objfile] = true
- }
- }
-
- // Walk all mappings looking for matching functions with samples.
- var objSyms []*objSymbol
- for _, m := range prof.Mapping {
- if !hasSamples[filepath.Base(m.File)] {
- if address == nil || !(m.Start <= *address && *address <= m.Limit) {
- continue
- }
- }
-
- f, err := obj.Open(m.File, m.Start)
- if err != nil {
- fmt.Printf("%v\n", err)
- continue
- }
-
- // Find symbols in this binary matching the user regexp.
- var addr uint64
- if address != nil {
- addr = *address
- }
- msyms, err := f.Symbols(rx, addr)
- base := f.Base()
- f.Close()
- if err != nil {
- continue
- }
- for _, ms := range msyms {
- objSyms = append(objSyms,
- &objSymbol{
- sym: ms,
- base: base,
- },
- )
- }
- }
-
- return objSyms
-}
-
-// objSym represents a symbol identified from a binary. It includes
-// the SymbolInfo from the disasm package and the base that must be
-// added to correspond to sample addresses
-type objSymbol struct {
- sym *plugin.Sym
- base uint64
-}
-
-// objSymbols is a wrapper type to enable sorting of []*objSymbol.
-type objSymbols []*objSymbol
-
-func (o objSymbols) Len() int {
- return len(o)
-}
-
-func (o objSymbols) Less(i, j int) bool {
- if namei, namej := o[i].sym.Name[0], o[j].sym.Name[0]; namei != namej {
- return namei < namej
- }
- return o[i].sym.Start < o[j].sym.Start
-}
-
-func (o objSymbols) Swap(i, j int) {
- o[i], o[j] = o[j], o[i]
-}
-
-// nodesPerSymbol classifies nodes into a group of symbols.
-func nodesPerSymbol(ns nodes, symbols []*objSymbol) map[*objSymbol]nodes {
- symNodes := make(map[*objSymbol]nodes)
- for _, s := range symbols {
- // Gather samples for this symbol.
- for _, n := range ns {
- address := n.info.address - s.base
- if address >= s.sym.Start && address < s.sym.End {
- symNodes[s] = append(symNodes[s], n)
- }
- }
- }
- return symNodes
-}
-
-// annotateAssembly annotates a set of assembly instructions with a
-// set of samples. It returns a set of nodes to display. base is an
-// offset to adjust the sample addresses.
-func annotateAssembly(insns []plugin.Inst, samples nodes, base uint64) nodes {
- // Add end marker to simplify printing loop.
- insns = append(insns, plugin.Inst{^uint64(0), "", "", 0})
-
- // Ensure samples are sorted by address.
- samples.sort(addressOrder)
-
- var s int
- var asm nodes
- for ix, in := range insns[:len(insns)-1] {
- n := node{
- info: nodeInfo{
- address: in.Addr,
- name: in.Text,
- file: trimPath(in.File),
- lineno: in.Line,
- },
- }
-
- // Sum all the samples until the next instruction (to account
- // for samples attributed to the middle of an instruction).
- for next := insns[ix+1].Addr; s < len(samples) && samples[s].info.address-base < next; s++ {
- n.flat += samples[s].flat
- n.cum += samples[s].cum
- if samples[s].info.file != "" {
- n.info.file = trimPath(samples[s].info.file)
- n.info.lineno = samples[s].info.lineno
- }
- }
- asm = append(asm, &n)
- }
-
- return asm
-}
-
-// valueOrDot formats a value according to a report, intercepting zero
-// values.
-func valueOrDot(value int64, rpt *Report) string {
- if value == 0 {
- return "."
- }
- return rpt.formatValue(value)
-}
-
-// printTags collects all tags referenced in the profile and prints
-// them in a sorted table.
-func printTags(w io.Writer, rpt *Report) error {
- p := rpt.prof
-
- // Hashtable to keep accumulate tags as key,value,count.
- tagMap := make(map[string]map[string]int64)
- for _, s := range p.Sample {
- for key, vals := range s.Label {
- for _, val := range vals {
- if valueMap, ok := tagMap[key]; ok {
- valueMap[val] = valueMap[val] + s.Value[0]
- continue
- }
- valueMap := make(map[string]int64)
- valueMap[val] = s.Value[0]
- tagMap[key] = valueMap
- }
- }
- for key, vals := range s.NumLabel {
- for _, nval := range vals {
- val := scaledValueLabel(nval, key, "auto")
- if valueMap, ok := tagMap[key]; ok {
- valueMap[val] = valueMap[val] + s.Value[0]
- continue
- }
- valueMap := make(map[string]int64)
- valueMap[val] = s.Value[0]
- tagMap[key] = valueMap
- }
- }
- }
-
- tagKeys := make(tags, 0, len(tagMap))
- for key := range tagMap {
- tagKeys = append(tagKeys, &tag{name: key})
- }
- sort.Sort(tagKeys)
-
- for _, tagKey := range tagKeys {
- var total int64
- key := tagKey.name
- tags := make(tags, 0, len(tagMap[key]))
- for t, c := range tagMap[key] {
- total += c
- tags = append(tags, &tag{name: t, weight: c})
- }
-
- sort.Sort(tags)
- fmt.Fprintf(w, "%s: Total %d\n", key, total)
- for _, t := range tags {
- if total > 0 {
- fmt.Fprintf(w, " %8d (%s): %s\n", t.weight,
- percentage(t.weight, total), t.name)
- } else {
- fmt.Fprintf(w, " %8d: %s\n", t.weight, t.name)
- }
- }
- fmt.Fprintln(w)
- }
- return nil
-}
-
-// printText prints a flat text report for a profile.
-func printText(w io.Writer, rpt *Report) error {
- g, err := newGraph(rpt)
- if err != nil {
- return err
- }
-
- origCount, droppedNodes, _ := g.preprocess(rpt)
- fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n"))
-
- fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n",
- "flat", "flat", "sum", "cum", "cum")
-
- var flatSum int64
- for _, n := range g.ns {
- name, flat, cum := n.info.prettyName(), n.flat, n.cum
-
- flatSum += flat
- fmt.Fprintf(w, "%10s %s %s %10s %s %s\n",
- rpt.formatValue(flat),
- percentage(flat, rpt.total),
- percentage(flatSum, rpt.total),
- rpt.formatValue(cum),
- percentage(cum, rpt.total),
- name)
- }
- return nil
-}
-
-// printCallgrind prints a graph for a profile on callgrind format.
-func printCallgrind(w io.Writer, rpt *Report) error {
- g, err := newGraph(rpt)
- if err != nil {
- return err
- }
-
- o := rpt.options
- rpt.options.NodeFraction = 0
- rpt.options.EdgeFraction = 0
- rpt.options.NodeCount = 0
-
- g.preprocess(rpt)
-
- fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
-
- files := make(map[string]int)
- names := make(map[string]int)
- for _, n := range g.ns {
- fmt.Fprintln(w, "fl="+callgrindName(files, n.info.file))
- fmt.Fprintln(w, "fn="+callgrindName(names, n.info.name))
- sv, _ := ScaleValue(n.flat, o.SampleUnit, o.OutputUnit)
- fmt.Fprintf(w, "%d %d\n", n.info.lineno, int(sv))
-
- // Print outgoing edges.
- for _, out := range sortedEdges(n.out) {
- c, _ := ScaleValue(out.weight, o.SampleUnit, o.OutputUnit)
- count := fmt.Sprintf("%d", int(c))
- callee := out.dest
- fmt.Fprintln(w, "cfl="+callgrindName(files, callee.info.file))
- fmt.Fprintln(w, "cfn="+callgrindName(names, callee.info.name))
- fmt.Fprintln(w, "calls="+count, callee.info.lineno)
- fmt.Fprintln(w, n.info.lineno, count)
- }
- fmt.Fprintln(w)
- }
-
- return nil
-}
-
-// callgrindName implements the callgrind naming compression scheme.
-// For names not previously seen returns "(N) name", where N is a
-// unique index. For names previously seen returns "(N)" where N is
-// the index returned the first time.
-func callgrindName(names map[string]int, name string) string {
- if name == "" {
- return ""
- }
- if id, ok := names[name]; ok {
- return fmt.Sprintf("(%d)", id)
- }
- id := len(names) + 1
- names[name] = id
- return fmt.Sprintf("(%d) %s", id, name)
-}
-
-// printTree prints a tree-based report in text form.
-func printTree(w io.Writer, rpt *Report) error {
- const separator = "----------------------------------------------------------+-------------"
- const legend = " flat flat% sum% cum cum% calls calls% + context "
-
- g, err := newGraph(rpt)
- if err != nil {
- return err
- }
-
- origCount, droppedNodes, _ := g.preprocess(rpt)
- fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n"))
-
- fmt.Fprintln(w, separator)
- fmt.Fprintln(w, legend)
- var flatSum int64
-
- rx := rpt.options.Symbol
- for _, n := range g.ns {
- name, flat, cum := n.info.prettyName(), n.flat, n.cum
-
- // Skip any entries that do not match the regexp (for the "peek" command).
- if rx != nil && !rx.MatchString(name) {
- continue
- }
-
- fmt.Fprintln(w, separator)
- // Print incoming edges.
- inEdges := sortedEdges(n.in)
- inSum := inEdges.sum()
- for _, in := range inEdges {
- fmt.Fprintf(w, "%50s %s | %s\n", rpt.formatValue(in.weight),
- percentage(in.weight, inSum), in.src.info.prettyName())
- }
-
- // Print current node.
- flatSum += flat
- fmt.Fprintf(w, "%10s %s %s %10s %s | %s\n",
- rpt.formatValue(flat),
- percentage(flat, rpt.total),
- percentage(flatSum, rpt.total),
- rpt.formatValue(cum),
- percentage(cum, rpt.total),
- name)
-
- // Print outgoing edges.
- outEdges := sortedEdges(n.out)
- outSum := outEdges.sum()
- for _, out := range outEdges {
- fmt.Fprintf(w, "%50s %s | %s\n", rpt.formatValue(out.weight),
- percentage(out.weight, outSum), out.dest.info.prettyName())
- }
- }
- if len(g.ns) > 0 {
- fmt.Fprintln(w, separator)
- }
- return nil
-}
-
-// printDOT prints an annotated callgraph in DOT format.
-func printDOT(w io.Writer, rpt *Report) error {
- g, err := newGraph(rpt)
- if err != nil {
- return err
- }
-
- origCount, droppedNodes, droppedEdges := g.preprocess(rpt)
-
- prof := rpt.prof
- graphname := "unnamed"
- if len(prof.Mapping) > 0 {
- graphname = filepath.Base(prof.Mapping[0].File)
- }
- fmt.Fprintln(w, `digraph "`+graphname+`" {`)
- fmt.Fprintln(w, `node [style=filled fillcolor="#f8f8f8"]`)
- fmt.Fprintln(w, dotLegend(rpt, g, origCount, droppedNodes, droppedEdges))
-
- if len(g.ns) == 0 {
- fmt.Fprintln(w, "}")
- return nil
- }
-
- // Make sure nodes have a unique consistent id.
- nodeIndex := make(map[*node]int)
- maxFlat := float64(g.ns[0].flat)
- for i, n := range g.ns {
- nodeIndex[n] = i + 1
- if float64(n.flat) > maxFlat {
- maxFlat = float64(n.flat)
- }
- }
- var edges edgeList
- for _, n := range g.ns {
- node := dotNode(rpt, maxFlat, nodeIndex[n], n)
- fmt.Fprintln(w, node)
- if nodelets := dotNodelets(rpt, nodeIndex[n], n); nodelets != "" {
- fmt.Fprint(w, nodelets)
- }
-
- // Collect outgoing edges.
- for _, e := range n.out {
- edges = append(edges, e)
- }
- }
- // Sort edges by frequency as a hint to the graph layout engine.
- sort.Sort(edges)
- for _, e := range edges {
- fmt.Fprintln(w, dotEdge(rpt, nodeIndex[e.src], nodeIndex[e.dest], e))
- }
- fmt.Fprintln(w, "}")
- return nil
-}
-
-// percentage computes the percentage of total of a value, and encodes
-// it as a string. At least two digits of precision are printed.
-func percentage(value, total int64) string {
- var ratio float64
- if total != 0 {
- ratio = float64(value) / float64(total) * 100
- }
- switch {
- case ratio >= 99.95:
- return " 100%"
- case ratio >= 1.0:
- return fmt.Sprintf("%5.2f%%", ratio)
- default:
- return fmt.Sprintf("%5.2g%%", ratio)
- }
-}
-
-// dotLegend generates the overall graph label for a report in DOT format.
-func dotLegend(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) string {
- label := legendLabels(rpt)
- label = append(label, legendDetailLabels(rpt, g, origCount, droppedNodes, droppedEdges)...)
- return fmt.Sprintf(`subgraph cluster_L { L [shape=box fontsize=32 label="%s\l"] }`, strings.Join(label, `\l`))
-}
-
-// legendLabels generates labels exclusive to graph visualization.
-func legendLabels(rpt *Report) []string {
- prof := rpt.prof
- o := rpt.options
- var label []string
- if len(prof.Mapping) > 0 {
- if prof.Mapping[0].File != "" {
- label = append(label, "File: "+filepath.Base(prof.Mapping[0].File))
- }
- if prof.Mapping[0].BuildID != "" {
- label = append(label, "Build ID: "+prof.Mapping[0].BuildID)
- }
- }
- if o.SampleType != "" {
- label = append(label, "Type: "+o.SampleType)
- }
- if prof.TimeNanos != 0 {
- const layout = "Jan 2, 2006 at 3:04pm (MST)"
- label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout))
- }
- if prof.DurationNanos != 0 {
- label = append(label, fmt.Sprintf("Duration: %v", time.Duration(prof.DurationNanos)))
- }
- return label
-}
-
-// legendDetailLabels generates labels common to graph and text visualization.
-func legendDetailLabels(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) []string {
- nodeFraction := rpt.options.NodeFraction
- edgeFraction := rpt.options.EdgeFraction
- nodeCount := rpt.options.NodeCount
-
- label := []string{}
-
- var flatSum int64
- for _, n := range g.ns {
- flatSum = flatSum + n.flat
- }
-
- label = append(label, fmt.Sprintf("%s of %s total (%s)", rpt.formatValue(flatSum), rpt.formatValue(rpt.total), percentage(flatSum, rpt.total)))
-
- if rpt.total > 0 {
- if droppedNodes > 0 {
- label = append(label, genLabel(droppedNodes, "node", "cum",
- rpt.formatValue(int64(float64(rpt.total)*nodeFraction))))
- }
- if droppedEdges > 0 {
- label = append(label, genLabel(droppedEdges, "edge", "freq",
- rpt.formatValue(int64(float64(rpt.total)*edgeFraction))))
- }
- if nodeCount > 0 && nodeCount < origCount {
- label = append(label, fmt.Sprintf("Showing top %d nodes out of %d (cum >= %s)",
- nodeCount, origCount,
- rpt.formatValue(g.ns[len(g.ns)-1].cum)))
- }
- }
- return label
-}
-
-func genLabel(d int, n, l, f string) string {
- if d > 1 {
- n = n + "s"
- }
- return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f)
-}
-
-// dotNode generates a graph node in DOT format.
-func dotNode(rpt *Report, maxFlat float64, rIndex int, n *node) string {
- flat, cum := n.flat, n.cum
-
- labels := strings.Split(n.info.prettyName(), "::")
- label := strings.Join(labels, `\n`) + `\n`
-
- flatValue := rpt.formatValue(flat)
- if flat > 0 {
- label = label + fmt.Sprintf(`%s(%s)`,
- flatValue,
- strings.TrimSpace(percentage(flat, rpt.total)))
- } else {
- label = label + "0"
- }
- cumValue := flatValue
- if cum != flat {
- if flat > 0 {
- label = label + `\n`
- } else {
- label = label + " "
- }
- cumValue = rpt.formatValue(cum)
- label = label + fmt.Sprintf(`of %s(%s)`,
- cumValue,
- strings.TrimSpace(percentage(cum, rpt.total)))
- }
-
- // Scale font sizes from 8 to 24 based on percentage of flat frequency.
- // Use non linear growth to emphasize the size difference.
- baseFontSize, maxFontGrowth := 8, 16.0
- fontSize := baseFontSize
- if maxFlat > 0 && flat > 0 && float64(flat) <= maxFlat {
- fontSize += int(math.Ceil(maxFontGrowth * math.Sqrt(float64(flat)/maxFlat)))
- }
- return fmt.Sprintf(`N%d [label="%s" fontsize=%d shape=box tooltip="%s (%s)"]`,
- rIndex,
- label,
- fontSize, n.info.prettyName(), cumValue)
-}
-
-// dotEdge generates a graph edge in DOT format.
-func dotEdge(rpt *Report, from, to int, e *edgeInfo) string {
- w := rpt.formatValue(e.weight)
- attr := fmt.Sprintf(`label=" %s"`, w)
- if rpt.total > 0 {
- if weight := 1 + int(e.weight*100/rpt.total); weight > 1 {
- attr = fmt.Sprintf(`%s weight=%d`, attr, weight)
- }
- if width := 1 + int(e.weight*5/rpt.total); width > 1 {
- attr = fmt.Sprintf(`%s penwidth=%d`, attr, width)
- }
- }
- arrow := "->"
- if e.residual {
- arrow = "..."
- }
- tooltip := fmt.Sprintf(`"%s %s %s (%s)"`,
- e.src.info.prettyName(), arrow, e.dest.info.prettyName(), w)
- attr = fmt.Sprintf(`%s tooltip=%s labeltooltip=%s`,
- attr, tooltip, tooltip)
-
- if e.residual {
- attr = attr + ` style="dotted"`
- }
-
- if len(e.src.tags) > 0 {
- // Separate children further if source has tags.
- attr = attr + " minlen=2"
- }
- return fmt.Sprintf("N%d -> N%d [%s]", from, to, attr)
-}
-
-// dotNodelets generates the DOT boxes for the node tags.
-func dotNodelets(rpt *Report, rIndex int, n *node) (dot string) {
- const maxNodelets = 4 // Number of nodelets for alphanumeric labels
- const maxNumNodelets = 4 // Number of nodelets for numeric labels
-
- var ts, nts tags
- for _, t := range n.tags {
- if t.unit == "" {
- ts = append(ts, t)
- } else {
- nts = append(nts, t)
- }
- }
-
- // Select the top maxNodelets alphanumeric labels by weight
- sort.Sort(ts)
- if len(ts) > maxNodelets {
- ts = ts[:maxNodelets]
- }
- for i, t := range ts {
- weight := rpt.formatValue(t.weight)
- dot += fmt.Sprintf(`N%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight)
- dot += fmt.Sprintf(`N%d -> N%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight)
- }
-
- // Collapse numeric labels into maxNumNodelets buckets, of the form:
- // 1MB..2MB, 3MB..5MB, ...
- nts = collapseTags(nts, maxNumNodelets)
- sort.Sort(nts)
- for i, t := range nts {
- weight := rpt.formatValue(t.weight)
- dot += fmt.Sprintf(`NN%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight)
- dot += fmt.Sprintf(`N%d -> NN%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight)
- }
-
- return dot
-}
-
-// graph summarizes a performance profile into a format that is
-// suitable for visualization.
-type graph struct {
- ns nodes
-}
-
-// nodes is an ordered collection of graph nodes.
-type nodes []*node
-
-// tags represent sample annotations
-type tags []*tag
-type tagMap map[string]*tag
-
-type tag struct {
- name string
- unit string // Describe the value, "" for non-numeric tags
- value int64
- weight int64
-}
-
-func (t tags) Len() int { return len(t) }
-func (t tags) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
-func (t tags) Less(i, j int) bool {
- if t[i].weight == t[j].weight {
- return t[i].name < t[j].name
- }
- return t[i].weight > t[j].weight
-}
-
-// node is an entry on a profiling report. It represents a unique
-// program location. It can include multiple names to represent
-// inlined functions.
-type node struct {
- info nodeInfo // Information associated to this entry.
-
- // values associated to this node.
- // flat is exclusive to this node, cum includes all descendents.
- flat, cum int64
-
- // in and out contains the nodes immediately reaching or reached by this nodes.
- in, out edgeMap
-
- // tags provide additional information about subsets of a sample.
- tags tagMap
-}
-
-type nodeInfo struct {
- name string
- origName string
- address uint64
- file string
- startLine, lineno int
- inline bool
- lowPriority bool
- objfile string
- parent *node // Used only if creating a calltree
-}
-
-func (n *node) addTags(s *profile.Sample, weight int64) {
- // Add a tag with all string labels
- var labels []string
- for key, vals := range s.Label {
- for _, v := range vals {
- labels = append(labels, key+":"+v)
- }
- }
- if len(labels) > 0 {
- sort.Strings(labels)
- l := n.tags.findOrAddTag(strings.Join(labels, `\n`), "", 0)
- l.weight += weight
- }
-
- for key, nvals := range s.NumLabel {
- for _, v := range nvals {
- label := scaledValueLabel(v, key, "auto")
- l := n.tags.findOrAddTag(label, key, v)
- l.weight += weight
- }
- }
-}
-
-func (m tagMap) findOrAddTag(label, unit string, value int64) *tag {
- if l := m[label]; l != nil {
- return l
- }
- l := &tag{
- name: label,
- unit: unit,
- value: value,
- }
- m[label] = l
- return l
-}
-
-// collapseTags reduces the number of entries in a tagMap by merging
-// adjacent nodes into ranges. It uses a greedy approach to merge
-// starting with the entries with the lowest weight.
-func collapseTags(ts tags, count int) tags {
- if len(ts) <= count {
- return ts
- }
-
- sort.Sort(ts)
- tagGroups := make([]tags, count)
- for i, t := range ts[:count] {
- tagGroups[i] = tags{t}
- }
- for _, t := range ts[count:] {
- g, d := 0, tagDistance(t, tagGroups[0][0])
- for i := 1; i < count; i++ {
- if nd := tagDistance(t, tagGroups[i][0]); nd < d {
- g, d = i, nd
- }
- }
- tagGroups[g] = append(tagGroups[g], t)
- }
-
- var nts tags
- for _, g := range tagGroups {
- l, w := tagGroupLabel(g)
- nts = append(nts, &tag{
- name: l,
- weight: w,
- })
- }
- return nts
-}
-
-func tagDistance(t, u *tag) float64 {
- v, _ := ScaleValue(u.value, u.unit, t.unit)
- if v < float64(t.value) {
- return float64(t.value) - v
- }
- return v - float64(t.value)
-}
-
-func tagGroupLabel(g tags) (string, int64) {
- if len(g) == 1 {
- t := g[0]
- return scaledValueLabel(t.value, t.unit, "auto"), t.weight
- }
- min := g[0]
- max := g[0]
- w := min.weight
- for _, t := range g[1:] {
- if v, _ := ScaleValue(t.value, t.unit, min.unit); int64(v) < min.value {
- min = t
- }
- if v, _ := ScaleValue(t.value, t.unit, max.unit); int64(v) > max.value {
- max = t
- }
- w += t.weight
- }
- return scaledValueLabel(min.value, min.unit, "auto") + ".." +
- scaledValueLabel(max.value, max.unit, "auto"), w
-}
-
-// sumNodes adds the flat and sum values on a report.
-func sumNodes(ns nodes) (flat int64, cum int64) {
- for _, n := range ns {
- flat += n.flat
- cum += n.cum
- }
- return
-}
-
-type edgeMap map[*node]*edgeInfo
-
-// edgeInfo contains any attributes to be represented about edges in a graph/
-type edgeInfo struct {
- src, dest *node
- // The summary weight of the edge
- weight int64
- // residual edges connect nodes that were connected through a
- // separate node, which has been removed from the report.
- residual bool
-}
-
-// bumpWeight increases the weight of an edge. If there isn't such an
-// edge in the map one is created.
-func bumpWeight(from, to *node, w int64, residual bool) {
- if from.out[to] != to.in[from] {
- panic(fmt.Errorf("asymmetric edges %v %v", *from, *to))
- }
-
- if n := from.out[to]; n != nil {
- n.weight += w
- if n.residual && !residual {
- n.residual = false
- }
- return
- }
-
- info := &edgeInfo{src: from, dest: to, weight: w, residual: residual}
- from.out[to] = info
- to.in[from] = info
-}
-
-// Output formats.
-const (
- Proto = iota
- Dot
- Tags
- Tree
- Text
- Raw
- Dis
- List
- WebList
- Callgrind
-)
-
-// Options are the formatting and filtering options used to generate a
-// profile.
-type Options struct {
- OutputFormat int
-
- CumSort bool
- CallTree bool
- PrintAddresses bool
- DropNegative bool
- Ratio float64
-
- NodeCount int
- NodeFraction float64
- EdgeFraction float64
-
- SampleType string
- SampleUnit string // Unit for the sample data from the profile.
- OutputUnit string // Units for data formatting in report.
-
- Symbol *regexp.Regexp // Symbols to include on disassembly report.
-}
-
-// newGraph summarizes performance data from a profile into a graph.
-func newGraph(rpt *Report) (g graph, err error) {
- prof := rpt.prof
- o := rpt.options
-
- // Generate a tree for graphical output if requested.
- buildTree := o.CallTree && o.OutputFormat == Dot
-
- locations := make(map[uint64][]nodeInfo)
- for _, l := range prof.Location {
- locations[l.ID] = newLocInfo(l)
- }
-
- nm := make(nodeMap)
- for _, sample := range prof.Sample {
- if sample.Location == nil {
- continue
- }
-
- // Construct list of node names for sample.
- var stack []nodeInfo
- for _, loc := range sample.Location {
- id := loc.ID
- stack = append(stack, locations[id]...)
- }
-
- // Upfront pass to update the parent chains, to prevent the
- // merging of nodes with different parents.
- if buildTree {
- var nn *node
- for i := len(stack); i > 0; i-- {
- n := &stack[i-1]
- n.parent = nn
- nn = nm.findOrInsertNode(*n)
- }
- }
-
- leaf := nm.findOrInsertNode(stack[0])
- weight := rpt.sampleValue(sample)
- leaf.addTags(sample, weight)
-
- // Aggregate counter data.
- leaf.flat += weight
- seen := make(map[*node]bool)
- var nn *node
- for _, s := range stack {
- n := nm.findOrInsertNode(s)
- if !seen[n] {
- seen[n] = true
- n.cum += weight
-
- if nn != nil {
- bumpWeight(n, nn, weight, false)
- }
- }
- nn = n
- }
- }
-
- // Collect new nodes into a report.
- ns := make(nodes, 0, len(nm))
- for _, n := range nm {
- if rpt.options.DropNegative && n.flat < 0 {
- continue
- }
- ns = append(ns, n)
- }
-
- return graph{ns}, nil
-}
-
-// Create a slice of formatted names for a location.
-func newLocInfo(l *profile.Location) []nodeInfo {
- var objfile string
-
- if m := l.Mapping; m != nil {
- objfile = filepath.Base(m.File)
- }
-
- if len(l.Line) == 0 {
- return []nodeInfo{
- {
- address: l.Address,
- objfile: objfile,
- },
- }
- }
- var info []nodeInfo
- numInlineFrames := len(l.Line) - 1
- for li, line := range l.Line {
- ni := nodeInfo{
- address: l.Address,
- lineno: int(line.Line),
- inline: li < numInlineFrames,
- objfile: objfile,
- }
-
- if line.Function != nil {
- ni.name = line.Function.Name
- ni.origName = line.Function.SystemName
- ni.file = line.Function.Filename
- ni.startLine = int(line.Function.StartLine)
- }
-
- info = append(info, ni)
- }
- return info
-}
-
-// nodeMap maps from a node info struct to a node. It is used to merge
-// report entries with the same info.
-type nodeMap map[nodeInfo]*node
-
-func (m nodeMap) findOrInsertNode(info nodeInfo) *node {
- rr := m[info]
- if rr == nil {
- rr = &node{
- info: info,
- in: make(edgeMap),
- out: make(edgeMap),
- tags: make(map[string]*tag),
- }
- m[info] = rr
- }
- return rr
-}
-
-// preprocess does any required filtering/sorting according to the
-// report options. Returns the mapping from each node to any nodes
-// removed by path compression and statistics on the nodes/edges removed.
-func (g *graph) preprocess(rpt *Report) (origCount, droppedNodes, droppedEdges int) {
- o := rpt.options
-
- // Compute total weight of current set of nodes.
- // This is <= rpt.total because of node filtering.
- var totalValue int64
- for _, n := range g.ns {
- totalValue += n.flat
- }
-
- // Remove nodes with value <= total*nodeFraction
- if nodeFraction := o.NodeFraction; nodeFraction > 0 {
- var removed nodes
- minValue := int64(float64(totalValue) * nodeFraction)
- kept := make(nodes, 0, len(g.ns))
- for _, n := range g.ns {
- if n.cum < minValue {
- removed = append(removed, n)
- } else {
- kept = append(kept, n)
- tagsKept := make(map[string]*tag)
- for s, t := range n.tags {
- if t.weight >= minValue {
- tagsKept[s] = t
- }
- }
- n.tags = tagsKept
- }
- }
- droppedNodes = len(removed)
- removeNodes(removed, false, false)
- g.ns = kept
- }
-
- // Remove edges below minimum frequency.
- if edgeFraction := o.EdgeFraction; edgeFraction > 0 {
- minEdge := int64(float64(totalValue) * edgeFraction)
- for _, n := range g.ns {
- for src, e := range n.in {
- if e.weight < minEdge {
- delete(n.in, src)
- delete(src.out, n)
- droppedEdges++
- }
- }
- }
- }
-
- sortOrder := flatName
- if o.CumSort {
- // Force cum sorting for graph output, to preserve connectivity.
- sortOrder = cumName
- }
-
- // Nodes that have flat==0 and a single in/out do not provide much
- // information. Give them first chance to be removed. Do not consider edges
- // from/to nodes that are expected to be removed.
- maxNodes := o.NodeCount
- if o.OutputFormat == Dot {
- if maxNodes > 0 && maxNodes < len(g.ns) {
- sortOrder = cumName
- g.ns.sort(cumName)
- cumCutoff := g.ns[maxNodes].cum
- for _, n := range g.ns {
- if n.flat == 0 {
- if count := countEdges(n.out, cumCutoff); count > 1 {
- continue
- }
- if count := countEdges(n.in, cumCutoff); count != 1 {
- continue
- }
- n.info.lowPriority = true
- }
- }
- }
- }
-
- g.ns.sort(sortOrder)
- if maxNodes > 0 {
- origCount = len(g.ns)
- for index, nodes := 0, 0; index < len(g.ns); index++ {
- nodes++
- // For DOT output, count the tags as nodes since we will draw
- // boxes for them.
- if o.OutputFormat == Dot {
- nodes += len(g.ns[index].tags)
- }
- if nodes > maxNodes {
- // Trim to the top n nodes. Create dotted edges to bridge any
- // broken connections.
- removeNodes(g.ns[index:], true, true)
- g.ns = g.ns[:index]
- break
- }
- }
- }
- removeRedundantEdges(g.ns)
-
- // Select best unit for profile output.
- // Find the appropriate units for the smallest non-zero sample
- if o.OutputUnit == "minimum" && len(g.ns) > 0 {
- var maxValue, minValue int64
-
- for _, n := range g.ns {
- if n.flat > 0 && (minValue == 0 || n.flat < minValue) {
- minValue = n.flat
- }
- if n.cum > maxValue {
- maxValue = n.cum
- }
- }
- if r := o.Ratio; r > 0 && r != 1 {
- minValue = int64(float64(minValue) * r)
- maxValue = int64(float64(maxValue) * r)
- }
-
- _, minUnit := ScaleValue(minValue, o.SampleUnit, "minimum")
- _, maxUnit := ScaleValue(maxValue, o.SampleUnit, "minimum")
-
- unit := minUnit
- if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind {
- // Minimum and maximum values have different units. Scale
- // minimum by 100 to use larger units, allowing minimum value to
- // be scaled down to 0.01, except for callgrind reports since
- // they can only represent integer values.
- _, unit = ScaleValue(100*minValue, o.SampleUnit, "minimum")
- }
-
- if unit != "" {
- o.OutputUnit = unit
- } else {
- o.OutputUnit = o.SampleUnit
- }
- }
- return
-}
-
-// countEdges counts the number of edges below the specified cutoff.
-func countEdges(el edgeMap, cutoff int64) int {
- count := 0
- for _, e := range el {
- if e.weight > cutoff {
- count++
- }
- }
- return count
-}
-
-// removeNodes removes nodes from a report, optionally bridging
-// connections between in/out edges and spreading out their weights
-// proportionally. residual marks new bridge edges as residual
-// (dotted).
-func removeNodes(toRemove nodes, bridge, residual bool) {
- for _, n := range toRemove {
- for ei := range n.in {
- delete(ei.out, n)
- }
- if bridge {
- for ei, wi := range n.in {
- for eo, wo := range n.out {
- var weight int64
- if n.cum != 0 {
- weight = int64(float64(wo.weight) * (float64(wi.weight) / float64(n.cum)))
- }
- bumpWeight(ei, eo, weight, residual)
- }
- }
- }
- for eo := range n.out {
- delete(eo.in, n)
- }
- }
-}
-
-// removeRedundantEdges removes residual edges if the destination can
-// be reached through another path. This is done to simplify the graph
-// while preserving connectivity.
-func removeRedundantEdges(ns nodes) {
- // Walk the nodes and outgoing edges in reverse order to prefer
- // removing edges with the lowest weight.
- for i := len(ns); i > 0; i-- {
- n := ns[i-1]
- in := sortedEdges(n.in)
- for j := len(in); j > 0; j-- {
- if e := in[j-1]; e.residual && isRedundant(e) {
- delete(e.src.out, e.dest)
- delete(e.dest.in, e.src)
- }
- }
- }
-}
-
-// isRedundant determines if an edge can be removed without impacting
-// connectivity of the whole graph. This is implemented by checking if the
-// nodes have a common ancestor after removing the edge.
-func isRedundant(e *edgeInfo) bool {
- destPred := predecessors(e, e.dest)
- if len(destPred) == 1 {
- return false
- }
- srcPred := predecessors(e, e.src)
-
- for n := range srcPred {
- if destPred[n] && n != e.dest {
- return true
- }
- }
- return false
-}
-
-// predecessors collects all the predecessors to node n, excluding edge e.
-func predecessors(e *edgeInfo, n *node) map[*node]bool {
- seen := map[*node]bool{n: true}
- queue := []*node{n}
- for len(queue) > 0 {
- n := queue[0]
- queue = queue[1:]
- for _, ie := range n.in {
- if e == ie || seen[ie.src] {
- continue
- }
- seen[ie.src] = true
- queue = append(queue, ie.src)
- }
- }
- return seen
-}
-
-// nodeSorter is a mechanism used to allow a report to be sorted
-// in different ways.
-type nodeSorter struct {
- rs nodes
- less func(i, j int) bool
-}
-
-func (s nodeSorter) Len() int { return len(s.rs) }
-func (s nodeSorter) Swap(i, j int) { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] }
-func (s nodeSorter) Less(i, j int) bool { return s.less(i, j) }
-
-type nodeOrder int
-
-const (
- flatName nodeOrder = iota
- flatCumName
- cumName
- nameOrder
- fileOrder
- addressOrder
-)
-
-// sort reorders the entries in a report based on the specified
-// ordering criteria. The result is sorted in decreasing order for
-// numeric quantities, alphabetically for text, and increasing for
-// addresses.
-func (ns nodes) sort(o nodeOrder) error {
- var s nodeSorter
-
- switch o {
- case flatName:
- s = nodeSorter{ns,
- func(i, j int) bool {
- if iv, jv := ns[i].flat, ns[j].flat; iv != jv {
- return iv > jv
- }
- if ns[i].info.prettyName() != ns[j].info.prettyName() {
- return ns[i].info.prettyName() < ns[j].info.prettyName()
- }
- iv, jv := ns[i].cum, ns[j].cum
- return iv > jv
- },
- }
- case flatCumName:
- s = nodeSorter{ns,
- func(i, j int) bool {
- if iv, jv := ns[i].flat, ns[j].flat; iv != jv {
- return iv > jv
- }
- if iv, jv := ns[i].cum, ns[j].cum; iv != jv {
- return iv > jv
- }
- return ns[i].info.prettyName() < ns[j].info.prettyName()
- },
- }
- case cumName:
- s = nodeSorter{ns,
- func(i, j int) bool {
- if ns[i].info.lowPriority != ns[j].info.lowPriority {
- return ns[j].info.lowPriority
- }
- if iv, jv := ns[i].cum, ns[j].cum; iv != jv {
- return iv > jv
- }
- if ns[i].info.prettyName() != ns[j].info.prettyName() {
- return ns[i].info.prettyName() < ns[j].info.prettyName()
- }
- iv, jv := ns[i].flat, ns[j].flat
- return iv > jv
- },
- }
- case nameOrder:
- s = nodeSorter{ns,
- func(i, j int) bool {
- return ns[i].info.name < ns[j].info.name
- },
- }
- case fileOrder:
- s = nodeSorter{ns,
- func(i, j int) bool {
- return ns[i].info.file < ns[j].info.file
- },
- }
- case addressOrder:
- s = nodeSorter{ns,
- func(i, j int) bool {
- return ns[i].info.address < ns[j].info.address
- },
- }
- default:
- return fmt.Errorf("report: unrecognized sort ordering: %d", o)
- }
- sort.Sort(s)
- return nil
-}
-
-type edgeList []*edgeInfo
-
-// sortedEdges return a slice of the edges in the map, sorted for
-// visualization. The sort order is first based on the edge weight
-// (higher-to-lower) and then by the node names to avoid flakiness.
-func sortedEdges(edges map[*node]*edgeInfo) edgeList {
- el := make(edgeList, 0, len(edges))
- for _, w := range edges {
- el = append(el, w)
- }
-
- sort.Sort(el)
- return el
-}
-
-func (el edgeList) Len() int {
- return len(el)
-}
-
-func (el edgeList) Less(i, j int) bool {
- if el[i].weight != el[j].weight {
- return el[i].weight > el[j].weight
- }
-
- from1 := el[i].src.info.prettyName()
- from2 := el[j].src.info.prettyName()
- if from1 != from2 {
- return from1 < from2
- }
-
- to1 := el[i].dest.info.prettyName()
- to2 := el[j].dest.info.prettyName()
-
- return to1 < to2
-}
-
-func (el edgeList) Swap(i, j int) {
- el[i], el[j] = el[j], el[i]
-}
-
-func (el edgeList) sum() int64 {
- var ret int64
- for _, e := range el {
- ret += e.weight
- }
- return ret
-}
-
-// ScaleValue reformats a value from a unit to a different unit.
-func ScaleValue(value int64, fromUnit, toUnit string) (sv float64, su string) {
- // Avoid infinite recursion on overflow.
- if value < 0 && -value > 0 {
- v, u := ScaleValue(-value, fromUnit, toUnit)
- return -v, u
- }
- if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok {
- return m, u
- }
- if t, u, ok := timeLabel(value, fromUnit, toUnit); ok {
- return t, u
- }
- // Skip non-interesting units.
- switch toUnit {
- case "count", "sample", "unit", "minimum":
- return float64(value), ""
- default:
- return float64(value), toUnit
- }
-}
-
-func scaledValueLabel(value int64, fromUnit, toUnit string) string {
- v, u := ScaleValue(value, fromUnit, toUnit)
-
- sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
- if sv == "0" || sv == "-0" {
- return "0"
- }
- return sv + u
-}
-
-func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
- fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s")
- toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s")
-
- switch fromUnit {
- case "byte", "b":
- case "kilobyte", "kb":
- value *= 1024
- case "megabyte", "mb":
- value *= 1024 * 1024
- case "gigabyte", "gb":
- value *= 1024 * 1024 * 1024
- default:
- return 0, "", false
- }
-
- if toUnit == "minimum" || toUnit == "auto" {
- switch {
- case value < 1024:
- toUnit = "b"
- case value < 1024*1024:
- toUnit = "kb"
- case value < 1024*1024*1024:
- toUnit = "mb"
- default:
- toUnit = "gb"
- }
- }
-
- var output float64
- switch toUnit {
- default:
- output, toUnit = float64(value), "B"
- case "kb", "kbyte", "kilobyte":
- output, toUnit = float64(value)/1024, "kB"
- case "mb", "mbyte", "megabyte":
- output, toUnit = float64(value)/(1024*1024), "MB"
- case "gb", "gbyte", "gigabyte":
- output, toUnit = float64(value)/(1024*1024*1024), "GB"
- }
- return output, toUnit, true
-}
-
-func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
- fromUnit = strings.ToLower(fromUnit)
- if len(fromUnit) > 2 {
- fromUnit = strings.TrimSuffix(fromUnit, "s")
- }
-
- toUnit = strings.ToLower(toUnit)
- if len(toUnit) > 2 {
- toUnit = strings.TrimSuffix(toUnit, "s")
- }
-
- var d time.Duration
- switch fromUnit {
- case "nanosecond", "ns":
- d = time.Duration(value) * time.Nanosecond
- case "microsecond":
- d = time.Duration(value) * time.Microsecond
- case "millisecond", "ms":
- d = time.Duration(value) * time.Millisecond
- case "second", "sec":
- d = time.Duration(value) * time.Second
- case "cycle":
- return float64(value), "", true
- default:
- return 0, "", false
- }
-
- if toUnit == "minimum" || toUnit == "auto" {
- switch {
- case d < 1*time.Microsecond:
- toUnit = "ns"
- case d < 1*time.Millisecond:
- toUnit = "us"
- case d < 1*time.Second:
- toUnit = "ms"
- case d < 1*time.Minute:
- toUnit = "sec"
- case d < 1*time.Hour:
- toUnit = "min"
- case d < 24*time.Hour:
- toUnit = "hour"
- case d < 15*24*time.Hour:
- toUnit = "day"
- case d < 120*24*time.Hour:
- toUnit = "week"
- default:
- toUnit = "year"
- }
- }
-
- var output float64
- dd := float64(d)
- switch toUnit {
- case "ns", "nanosecond":
- output, toUnit = dd/float64(time.Nanosecond), "ns"
- case "us", "microsecond":
- output, toUnit = dd/float64(time.Microsecond), "us"
- case "ms", "millisecond":
- output, toUnit = dd/float64(time.Millisecond), "ms"
- case "min", "minute":
- output, toUnit = dd/float64(time.Minute), "mins"
- case "hour", "hr":
- output, toUnit = dd/float64(time.Hour), "hrs"
- case "day":
- output, toUnit = dd/float64(24*time.Hour), "days"
- case "week", "wk":
- output, toUnit = dd/float64(7*24*time.Hour), "wks"
- case "year", "yr":
- output, toUnit = dd/float64(365*7*24*time.Hour), "yrs"
- default:
- fallthrough
- case "sec", "second", "s":
- output, toUnit = dd/float64(time.Second), "s"
- }
- return output, toUnit, true
-}
-
-// prettyName determines the printable name to be used for a node.
-func (info *nodeInfo) prettyName() string {
- var name string
- if info.address != 0 {
- name = fmt.Sprintf("%016x", info.address)
- }
-
- if info.name != "" {
- name = name + " " + info.name
- }
-
- if info.file != "" {
- name += " " + trimPath(info.file)
- if info.lineno != 0 {
- name += fmt.Sprintf(":%d", info.lineno)
- }
- }
-
- if info.inline {
- name = name + " (inline)"
- }
-
- if name = strings.TrimSpace(name); name == "" && info.objfile != "" {
- name = "[" + info.objfile + "]"
- }
- return name
-}
-
-// New builds a new report indexing the sample values interpreting the
-// samples with the provided function.
-func New(prof *profile.Profile, options Options, value func(s *profile.Sample) int64, unit string) *Report {
- o := &options
- if o.SampleUnit == "" {
- o.SampleUnit = unit
- }
- format := func(v int64) string {
- if r := o.Ratio; r > 0 && r != 1 {
- fv := float64(v) * r
- v = int64(fv)
- }
- return scaledValueLabel(v, o.SampleUnit, o.OutputUnit)
- }
- return &Report{prof, computeTotal(prof, value), o, value, format}
-}
-
-// NewDefault builds a new report indexing the sample values with the
-// last value available.
-func NewDefault(prof *profile.Profile, options Options) *Report {
- index := len(prof.SampleType) - 1
- o := &options
- if o.SampleUnit == "" {
- o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit)
- }
- value := func(s *profile.Sample) int64 {
- return s.Value[index]
- }
- format := func(v int64) string {
- if r := o.Ratio; r > 0 && r != 1 {
- fv := float64(v) * r
- v = int64(fv)
- }
- return scaledValueLabel(v, o.SampleUnit, o.OutputUnit)
- }
- return &Report{prof, computeTotal(prof, value), o, value, format}
-}
-
-func computeTotal(prof *profile.Profile, value func(s *profile.Sample) int64) int64 {
- var ret int64
- for _, sample := range prof.Sample {
- ret += value(sample)
- }
- return ret
-}
-
-// Report contains the data and associated routines to extract a
-// report from a profile.
-type Report struct {
- prof *profile.Profile
- total int64
- options *Options
- sampleValue func(*profile.Sample) int64
- formatValue func(int64) string
-}
diff --git a/src/cmd/pprof/internal/report/source.go b/src/cmd/pprof/internal/report/source.go
deleted file mode 100644
index 908be21424..0000000000
--- a/src/cmd/pprof/internal/report/source.go
+++ /dev/null
@@ -1,454 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package report
-
-// This file contains routines related to the generation of annotated
-// source listings.
-
-import (
- "bufio"
- "fmt"
- "html/template"
- "io"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
-
- "cmd/pprof/internal/plugin"
-)
-
-// printSource prints an annotated source listing, include all
-// functions with samples that match the regexp rpt.options.symbol.
-// The sources are sorted by function name and then by filename to
-// eliminate potential nondeterminism.
-func printSource(w io.Writer, rpt *Report) error {
- o := rpt.options
- g, err := newGraph(rpt)
- if err != nil {
- return err
- }
-
- // Identify all the functions that match the regexp provided.
- // Group nodes for each matching function.
- var functions nodes
- functionNodes := make(map[string]nodes)
- for _, n := range g.ns {
- if !o.Symbol.MatchString(n.info.name) {
- continue
- }
- if functionNodes[n.info.name] == nil {
- functions = append(functions, n)
- }
- functionNodes[n.info.name] = append(functionNodes[n.info.name], n)
- }
- functions.sort(nameOrder)
-
- fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
- for _, fn := range functions {
- name := fn.info.name
-
- // Identify all the source files associated to this function.
- // Group nodes for each source file.
- var sourceFiles nodes
- fileNodes := make(map[string]nodes)
- for _, n := range functionNodes[name] {
- if n.info.file == "" {
- continue
- }
- if fileNodes[n.info.file] == nil {
- sourceFiles = append(sourceFiles, n)
- }
- fileNodes[n.info.file] = append(fileNodes[n.info.file], n)
- }
-
- if len(sourceFiles) == 0 {
- fmt.Printf("No source information for %s\n", name)
- continue
- }
-
- sourceFiles.sort(fileOrder)
-
- // Print each file associated with this function.
- for _, fl := range sourceFiles {
- filename := fl.info.file
- fns := fileNodes[filename]
- flatSum, cumSum := sumNodes(fns)
-
- fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0)
- fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path)
- fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
- rpt.formatValue(flatSum), rpt.formatValue(cumSum),
- percentage(cumSum, rpt.total))
-
- if err != nil {
- fmt.Fprintf(w, " Error: %v\n", err)
- continue
- }
-
- for _, fn := range fnodes {
- fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), fn.info.lineno, fn.info.name)
- }
- }
- }
- return nil
-}
-
-// printWebSource prints an annotated source listing, include all
-// functions with samples that match the regexp rpt.options.symbol.
-func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
- o := rpt.options
- g, err := newGraph(rpt)
- if err != nil {
- return err
- }
-
- // If the regexp source can be parsed as an address, also match
- // functions that land on that address.
- var address *uint64
- if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
- address = &hex
- }
-
- // Extract interesting symbols from binary files in the profile and
- // classify samples per symbol.
- symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj)
- symNodes := nodesPerSymbol(g.ns, symbols)
-
- // Sort symbols for printing.
- var syms objSymbols
- for s := range symNodes {
- syms = append(syms, s)
- }
- sort.Sort(syms)
-
- if len(syms) == 0 {
- return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String())
- }
-
- printHeader(w, rpt)
- for _, s := range syms {
- name := s.sym.Name[0]
- // Identify sources associated to a symbol by examining
- // symbol samples. Classify samples per source file.
- var sourceFiles nodes
- fileNodes := make(map[string]nodes)
- for _, n := range symNodes[s] {
- if n.info.file == "" {
- continue
- }
- if fileNodes[n.info.file] == nil {
- sourceFiles = append(sourceFiles, n)
- }
- fileNodes[n.info.file] = append(fileNodes[n.info.file], n)
- }
-
- if len(sourceFiles) == 0 {
- fmt.Printf("No source information for %s\n", name)
- continue
- }
-
- sourceFiles.sort(fileOrder)
-
- // Print each file associated with this function.
- for _, fl := range sourceFiles {
- filename := fl.info.file
- fns := fileNodes[filename]
-
- asm := assemblyPerSourceLine(symbols, fns, filename, obj)
- start, end := sourceCoordinates(asm)
-
- fnodes, path, err := getFunctionSource(name, filename, fns, start, end)
- if err != nil {
- fnodes, path = getMissingFunctionSource(filename, asm, start, end)
- }
-
- flatSum, cumSum := sumNodes(fnodes)
- printFunctionHeader(w, name, path, flatSum, cumSum, rpt)
- for _, fn := range fnodes {
- printFunctionSourceLine(w, fn, asm[fn.info.lineno], rpt)
- }
- printFunctionClosing(w)
- }
- }
- printPageClosing(w)
- return nil
-}
-
-// sourceCoordinates returns the lowest and highest line numbers from
-// a set of assembly statements.
-func sourceCoordinates(asm map[int]nodes) (start, end int) {
- for l := range asm {
- if start == 0 || l < start {
- start = l
- }
- if end == 0 || l > end {
- end = l
- }
- }
- return start, end
-}
-
-// assemblyPerSourceLine disassembles the binary containing a symbol
-// and classifies the assembly instructions according to its
-// corresponding source line, annotating them with a set of samples.
-func assemblyPerSourceLine(objSyms []*objSymbol, rs nodes, src string, obj plugin.ObjTool) map[int]nodes {
- assembly := make(map[int]nodes)
- // Identify symbol to use for this collection of samples.
- o := findMatchingSymbol(objSyms, rs)
- if o == nil {
- return assembly
- }
-
- // Extract assembly for matched symbol
- insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
- if err != nil {
- return assembly
- }
-
- srcBase := filepath.Base(src)
- anodes := annotateAssembly(insns, rs, o.base)
- var lineno = 0
- for _, an := range anodes {
- if filepath.Base(an.info.file) == srcBase {
- lineno = an.info.lineno
- }
- if lineno != 0 {
- assembly[lineno] = append(assembly[lineno], an)
- }
- }
-
- return assembly
-}
-
-// findMatchingSymbol looks for the symbol that corresponds to a set
-// of samples, by comparing their addresses.
-func findMatchingSymbol(objSyms []*objSymbol, ns nodes) *objSymbol {
- for _, n := range ns {
- for _, o := range objSyms {
- if filepath.Base(o.sym.File) == n.info.objfile &&
- o.sym.Start <= n.info.address-o.base &&
- n.info.address-o.base <= o.sym.End {
- return o
- }
- }
- }
- return nil
-}
-
-// printHeader prints the page header for a weblist report.
-func printHeader(w io.Writer, rpt *Report) {
- fmt.Fprintln(w, weblistPageHeader)
-
- var labels []string
- for _, l := range legendLabels(rpt) {
- labels = append(labels, template.HTMLEscapeString(l))
- }
-
- fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
- strings.Join(labels, "<br>\n"),
- rpt.formatValue(rpt.total),
- )
-}
-
-// printFunctionHeader prints a function header for a weblist report.
-func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
- fmt.Fprintf(w, `<h1>%s</h1>%s
-<pre onClick="pprof_toggle_asm()">
- Total: %10s %10s (flat, cum) %s
-`,
- template.HTMLEscapeString(name), template.HTMLEscapeString(path),
- rpt.formatValue(flatSum), rpt.formatValue(cumSum),
- percentage(cumSum, rpt.total))
-}
-
-// printFunctionSourceLine prints a source line and the corresponding assembly.
-func printFunctionSourceLine(w io.Writer, fn *node, assembly nodes, rpt *Report) {
- if len(assembly) == 0 {
- fmt.Fprintf(w,
- "<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n",
- fn.info.lineno,
- valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
- template.HTMLEscapeString(fn.info.name))
- return
- }
-
- fmt.Fprintf(w,
- "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>",
- fn.info.lineno,
- valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
- template.HTMLEscapeString(fn.info.name))
- fmt.Fprint(w, "<span class=asm>")
- for _, an := range assembly {
- var fileline string
- class := "disasmloc"
- if an.info.file != "" {
- fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.info.file), an.info.lineno)
- if an.info.lineno != fn.info.lineno {
- class = "unimportant"
- }
- }
- fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
- valueOrDot(an.flat, rpt), valueOrDot(an.cum, rpt),
- an.info.address,
- template.HTMLEscapeString(an.info.name),
- class,
- template.HTMLEscapeString(fileline))
- }
- fmt.Fprintln(w, "</span>")
-}
-
-// printFunctionClosing prints the end of a function in a weblist report.
-func printFunctionClosing(w io.Writer) {
- fmt.Fprintln(w, "</pre>")
-}
-
-// printPageClosing prints the end of the page in a weblist report.
-func printPageClosing(w io.Writer) {
- fmt.Fprintln(w, weblistPageClosing)
-}
-
-// getFunctionSource collects the sources of a function from a source
-// file and annotates it with the samples in fns. Returns the sources
-// as nodes, using the info.name field to hold the source code.
-func getFunctionSource(fun, file string, fns nodes, start, end int) (nodes, string, error) {
- f, file, err := adjustSourcePath(file)
- if err != nil {
- return nil, file, err
- }
-
- lineNodes := make(map[int]nodes)
-
- // Collect source coordinates from profile.
- const margin = 5 // Lines before first/after last sample.
- if start == 0 {
- if fns[0].info.startLine != 0 {
- start = fns[0].info.startLine
- } else {
- start = fns[0].info.lineno - margin
- }
- } else {
- start -= margin
- }
- if end == 0 {
- end = fns[0].info.lineno
- }
- end += margin
- for _, n := range fns {
- lineno := n.info.lineno
- nodeStart := n.info.startLine
- if nodeStart == 0 {
- nodeStart = lineno - margin
- }
- nodeEnd := lineno + margin
- if nodeStart < start {
- start = nodeStart
- } else if nodeEnd > end {
- end = nodeEnd
- }
- lineNodes[lineno] = append(lineNodes[lineno], n)
- }
-
- var src nodes
- buf := bufio.NewReader(f)
- lineno := 1
- for {
- line, err := buf.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- return nil, file, err
- }
- if line == "" {
- // end was at or past EOF; that's okay
- break
- }
- }
- if lineno >= start {
- flat, cum := sumNodes(lineNodes[lineno])
-
- src = append(src, &node{
- info: nodeInfo{
- name: strings.TrimRight(line, "\n"),
- lineno: lineno,
- },
- flat: flat,
- cum: cum,
- })
- }
- lineno++
- if lineno > end {
- break
- }
- }
- return src, file, nil
-}
-
-// getMissingFunctionSource creates a dummy function body to point to
-// the source file and annotates it with the samples in asm.
-func getMissingFunctionSource(filename string, asm map[int]nodes, start, end int) (nodes, string) {
- var fnodes nodes
- for i := start; i <= end; i++ {
- lrs := asm[i]
- if len(lrs) == 0 {
- continue
- }
- flat, cum := sumNodes(lrs)
- fnodes = append(fnodes, &node{
- info: nodeInfo{
- name: "???",
- lineno: i,
- },
- flat: flat,
- cum: cum,
- })
- }
- return fnodes, filename
-}
-
-// adjustSourcePath adjusts the path for a source file by trimming
-// known prefixes and searching for the file on all parents of the
-// current working dir.
-func adjustSourcePath(path string) (*os.File, string, error) {
- path = trimPath(path)
- f, err := os.Open(path)
- if err == nil {
- return f, path, nil
- }
-
- if dir, wderr := os.Getwd(); wderr == nil {
- for {
- parent := filepath.Dir(dir)
- if parent == dir {
- break
- }
- if f, err := os.Open(filepath.Join(parent, path)); err == nil {
- return f, filepath.Join(parent, path), nil
- }
-
- dir = parent
- }
- }
-
- return nil, path, err
-}
-
-// trimPath cleans up a path by removing prefixes that are commonly
-// found on profiles.
-func trimPath(path string) string {
- basePaths := []string{
- "/proc/self/cwd/./",
- "/proc/self/cwd/",
- }
-
- sPath := filepath.ToSlash(path)
-
- for _, base := range basePaths {
- if strings.HasPrefix(sPath, base) {
- return filepath.FromSlash(sPath[len(base):])
- }
- }
- return path
-}
diff --git a/src/cmd/pprof/internal/report/source_html.go b/src/cmd/pprof/internal/report/source_html.go
deleted file mode 100644
index 267fabdc4b..0000000000
--- a/src/cmd/pprof/internal/report/source_html.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package report
-
-const weblistPageHeader = `
-<!DOCTYPE html>
-<html>
-<head>
-<title>Pprof listing</title>
-<style type="text/css">
-body {
-font-family: sans-serif;
-}
-h1 {
- font-size: 1.5em;
- margin-bottom: 4px;
-}
-.legend {
- font-size: 1.25em;
-}
-.line {
-color: #aaaaaa;
-}
-.nop {
-color: #aaaaaa;
-}
-.unimportant {
-color: #cccccc;
-}
-.disasmloc {
-color: #000000;
-}
-.deadsrc {
-cursor: pointer;
-}
-.deadsrc:hover {
-background-color: #eeeeee;
-}
-.livesrc {
-color: #0000ff;
-cursor: pointer;
-}
-.livesrc:hover {
-background-color: #eeeeee;
-}
-.asm {
-color: #008800;
-display: none;
-}
-</style>
-<script type="text/javascript">
-function pprof_toggle_asm(e) {
- var target;
- if (!e) e = window.event;
- if (e.target) target = e.target;
- else if (e.srcElement) target = e.srcElement;
-
- if (target) {
- var asm = target.nextSibling;
- if (asm && asm.className == "asm") {
- asm.style.display = (asm.style.display == "block" ? "" : "block");
- e.preventDefault();
- return false;
- }
- }
-}
-</script>
-</head>
-<body>
-`
-
-const weblistPageClosing = `
-</body>
-</html>
-`
diff --git a/src/cmd/pprof/internal/svg/svg.go b/src/cmd/pprof/internal/svg/svg.go
deleted file mode 100644
index 04f6ff1870..0000000000
--- a/src/cmd/pprof/internal/svg/svg.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package svg provides tools related to handling of SVG files
-package svg
-
-import (
- "bytes"
- "regexp"
- "strings"
-)
-
-var (
- viewBox = regexp.MustCompile(`<svg\s*width="[^"]+"\s*height="[^"]+"\s*viewBox="[^"]+"`)
- graphId = regexp.MustCompile(`<g id="graph\d"`)
- svgClose = regexp.MustCompile(`</svg>`)
-)
-
-// Massage enhances the SVG output from DOT to provide better
-// panning inside a web browser. It uses the SVGPan library, which is
-// included directly.
-func Massage(in bytes.Buffer) string {
- svg := string(in.Bytes())
-
- // Work around for dot bug which misses quoting some ampersands,
- // resulting on unparsable SVG.
- svg = strings.Replace(svg, "&;", "&amp;;", -1)
-
- //Dot's SVG output is
- //
- // <svg width="___" height="___"
- // viewBox="___" xmlns=...>
- // <g id="graph0" transform="...">
- // ...
- // </g>
- // </svg>
- //
- // Change it to
- //
- // <svg width="100%" height="100%"
- // xmlns=...>
- // <script>...</script>
- // <g id="viewport" transform="translate(0,0)">
- // <g id="graph0" transform="...">
- // ...
- // </g>
- // </g>
- // </svg>
-
- if loc := viewBox.FindStringIndex(svg); loc != nil {
- svg = svg[:loc[0]] +
- `<svg width="100%" height="100%"` +
- svg[loc[1]:]
- }
-
- if loc := graphId.FindStringIndex(svg); loc != nil {
- svg = svg[:loc[0]] +
- `<script type="text/ecmascript"><![CDATA[` + svgPanJS + `]]></script>` +
- `<g id="viewport" transform="scale(0.5,0.5) translate(0,0)">` +
- svg[loc[0]:]
- }
-
- if loc := svgClose.FindStringIndex(svg); loc != nil {
- svg = svg[:loc[0]] +
- `</g>` +
- svg[loc[0]:]
- }
-
- return svg
-}
diff --git a/src/cmd/pprof/internal/svg/svgpan.go b/src/cmd/pprof/internal/svg/svgpan.go
deleted file mode 100644
index 4975b103e3..0000000000
--- a/src/cmd/pprof/internal/svg/svgpan.go
+++ /dev/null
@@ -1,291 +0,0 @@
-// SVG pan and zoom library.
-// See copyright notice in string constant below.
-
-package svg
-
-// https://www.cyberz.org/projects/SVGPan/SVGPan.js
-
-const svgPanJS = `
-/**
- * SVGPan library 1.2.1
- * ======================
- *
- * Given an unique existing element with id "viewport" (or when missing, the first g
- * element), including the the library into any SVG adds the following capabilities:
- *
- * - Mouse panning
- * - Mouse zooming (using the wheel)
- * - Object dragging
- *
- * You can configure the behaviour of the pan/zoom/drag with the variables
- * listed in the CONFIGURATION section of this file.
- *
- * Known issues:
- *
- * - Zooming (while panning) on Safari has still some issues
- *
- * Releases:
- *
- * 1.2.1, Mon Jul 4 00:33:18 CEST 2011, Andrea Leofreddi
- * - Fixed a regression with mouse wheel (now working on Firefox 5)
- * - Working with viewBox attribute (#4)
- * - Added "use strict;" and fixed resulting warnings (#5)
- * - Added configuration variables, dragging is disabled by default (#3)
- *
- * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
- * Fixed a bug with browser mouse handler interaction
- *
- * 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui
- * Updated the zoom code to support the mouse wheel on Safari/Chrome
- *
- * 1.0, Andrea Leofreddi
- * First release
- *
- * This code is licensed under the following BSD license:
- *
- * Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com>. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are
- * permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list
- * of conditions and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ` + "``AS IS''" + ` AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
- * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and documentation are those of the
- * authors and should not be interpreted as representing official policies, either expressed
- * or implied, of Andrea Leofreddi.
- */
-
-"use strict";
-
-/// CONFIGURATION
-/// ====>
-
-var enablePan = 1; // 1 or 0: enable or disable panning (default enabled)
-var enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled)
-var enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled)
-
-/// <====
-/// END OF CONFIGURATION
-
-var root = document.documentElement;
-
-var state = 'none', svgRoot, stateTarget, stateOrigin, stateTf;
-
-setupHandlers(root);
-
-/**
- * Register handlers
- */
-function setupHandlers(root){
- setAttributes(root, {
- "onmouseup" : "handleMouseUp(evt)",
- "onmousedown" : "handleMouseDown(evt)",
- "onmousemove" : "handleMouseMove(evt)",
- //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
- });
-
- if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
- window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
- else
- window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
-}
-
-/**
- * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable.
- */
-function getRoot(root) {
- if(typeof(svgRoot) == "undefined") {
- var g = null;
-
- g = root.getElementById("viewport");
-
- if(g == null)
- g = root.getElementsByTagName('g')[0];
-
- if(g == null)
- alert('Unable to obtain SVG root element');
-
- setCTM(g, g.getCTM());
-
- g.removeAttribute("viewBox");
-
- svgRoot = g;
- }
-
- return svgRoot;
-}
-
-/**
- * Instance an SVGPoint object with given event coordinates.
- */
-function getEventPoint(evt) {
- var p = root.createSVGPoint();
-
- p.x = evt.clientX;
- p.y = evt.clientY;
-
- return p;
-}
-
-/**
- * Sets the current transform matrix of an element.
- */
-function setCTM(element, matrix) {
- var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
-
- element.setAttribute("transform", s);
-}
-
-/**
- * Dumps a matrix to a string (useful for debug).
- */
-function dumpMatrix(matrix) {
- var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]";
-
- return s;
-}
-
-/**
- * Sets attributes of an element.
- */
-function setAttributes(element, attributes){
- for (var i in attributes)
- element.setAttributeNS(null, i, attributes[i]);
-}
-
-/**
- * Handle mouse wheel event.
- */
-function handleMouseWheel(evt) {
- if(!enableZoom)
- return;
-
- if(evt.preventDefault)
- evt.preventDefault();
-
- evt.returnValue = false;
-
- var svgDoc = evt.target.ownerDocument;
-
- var delta;
-
- if(evt.wheelDelta)
- delta = evt.wheelDelta / 3600; // Chrome/Safari
- else
- delta = evt.detail / -90; // Mozilla
-
- var z = 1 + delta; // Zoom factor: 0.9/1.1
-
- var g = getRoot(svgDoc);
-
- var p = getEventPoint(evt);
-
- p = p.matrixTransform(g.getCTM().inverse());
-
- // Compute new scale matrix in current mouse position
- var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
-
- setCTM(g, g.getCTM().multiply(k));
-
- if(typeof(stateTf) == "undefined")
- stateTf = g.getCTM().inverse();
-
- stateTf = stateTf.multiply(k.inverse());
-}
-
-/**
- * Handle mouse move event.
- */
-function handleMouseMove(evt) {
- if(evt.preventDefault)
- evt.preventDefault();
-
- evt.returnValue = false;
-
- var svgDoc = evt.target.ownerDocument;
-
- var g = getRoot(svgDoc);
-
- if(state == 'pan' && enablePan) {
- // Pan mode
- var p = getEventPoint(evt).matrixTransform(stateTf);
-
- setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
- } else if(state == 'drag' && enableDrag) {
- // Drag mode
- var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
-
- setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
-
- stateOrigin = p;
- }
-}
-
-/**
- * Handle click event.
- */
-function handleMouseDown(evt) {
- if(evt.preventDefault)
- evt.preventDefault();
-
- evt.returnValue = false;
-
- var svgDoc = evt.target.ownerDocument;
-
- var g = getRoot(svgDoc);
-
- if(
- evt.target.tagName == "svg"
- || !enableDrag // Pan anyway when drag is disabled and the user clicked on an element
- ) {
- // Pan mode
- state = 'pan';
-
- stateTf = g.getCTM().inverse();
-
- stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
- } else {
- // Drag mode
- state = 'drag';
-
- stateTarget = evt.target;
-
- stateTf = g.getCTM().inverse();
-
- stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
- }
-}
-
-/**
- * Handle mouse button release event.
- */
-function handleMouseUp(evt) {
- if(evt.preventDefault)
- evt.preventDefault();
-
- evt.returnValue = false;
-
- var svgDoc = evt.target.ownerDocument;
-
- if(state == 'pan' || state == 'drag') {
- // Quit pan mode
- state = '';
- }
-}
-
-`
diff --git a/src/cmd/pprof/internal/symbolizer/symbolizer.go b/src/cmd/pprof/internal/symbolizer/symbolizer.go
deleted file mode 100644
index 86de5640d2..0000000000
--- a/src/cmd/pprof/internal/symbolizer/symbolizer.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package symbolizer provides a routine to populate a profile with
-// symbol, file and line number information. It relies on the
-// addr2liner and demangler packages to do the actual work.
-package symbolizer
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "cmd/pprof/internal/plugin"
- "cmd/pprof/internal/profile"
-)
-
-// Symbolize adds symbol and line number information to all locations
-// in a profile. mode enables some options to control
-// symbolization. Currently only recognizes "force", which causes it
-// to overwrite any existing data.
-func Symbolize(mode string, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error {
- force := false
- // Disable some mechanisms based on mode string.
- for _, o := range strings.Split(strings.ToLower(mode), ":") {
- switch o {
- case "force":
- force = true
- default:
- }
- }
-
- if len(prof.Mapping) == 0 {
- return fmt.Errorf("no known mappings")
- }
-
- mt, err := newMapping(prof, obj, ui, force)
- if err != nil {
- return err
- }
- defer mt.close()
-
- functions := make(map[profile.Function]*profile.Function)
- for _, l := range mt.prof.Location {
- m := l.Mapping
- segment := mt.segments[m]
- if segment == nil {
- // Nothing to do
- continue
- }
-
- stack, err := segment.SourceLine(l.Address)
- if err != nil || len(stack) == 0 {
- // No answers from addr2line
- continue
- }
-
- l.Line = make([]profile.Line, len(stack))
- for i, frame := range stack {
- if frame.Func != "" {
- m.HasFunctions = true
- }
- if frame.File != "" {
- m.HasFilenames = true
- }
- if frame.Line != 0 {
- m.HasLineNumbers = true
- }
- f := &profile.Function{
- Name: frame.Func,
- SystemName: frame.Func,
- Filename: frame.File,
- }
- if fp := functions[*f]; fp != nil {
- f = fp
- } else {
- functions[*f] = f
- f.ID = uint64(len(mt.prof.Function)) + 1
- mt.prof.Function = append(mt.prof.Function, f)
- }
- l.Line[i] = profile.Line{
- Function: f,
- Line: int64(frame.Line),
- }
- }
-
- if len(stack) > 0 {
- m.HasInlineFrames = true
- }
- }
- return nil
-}
-
-// newMapping creates a mappingTable for a profile.
-func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
- mt := &mappingTable{
- prof: prof,
- segments: make(map[*profile.Mapping]plugin.ObjFile),
- }
-
- // Identify used mappings
- mappings := make(map[*profile.Mapping]bool)
- for _, l := range prof.Location {
- mappings[l.Mapping] = true
- }
-
- for _, m := range prof.Mapping {
- if !mappings[m] {
- continue
- }
- // Do not attempt to re-symbolize a mapping that has already been symbolized.
- if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
- continue
- }
-
- f, err := locateFile(obj, m.File, m.BuildID, m.Start)
- if err != nil {
- ui.PrintErr("Local symbolization failed for ", filepath.Base(m.File), ": ", err)
- // Move on to other mappings
- continue
- }
-
- if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
- // Build ID mismatch - ignore.
- f.Close()
- continue
- }
-
- mt.segments[m] = f
- }
-
- return mt, nil
-}
-
-// locateFile opens a local file for symbolization on the search path
-// at $PPROF_BINARY_PATH. Looks inside these directories for files
-// named $BUILDID/$BASENAME and $BASENAME (if build id is available).
-func locateFile(obj plugin.ObjTool, file, buildID string, start uint64) (plugin.ObjFile, error) {
- // Construct search path to examine
- searchPath := os.Getenv("PPROF_BINARY_PATH")
- if searchPath == "" {
- // Use $HOME/pprof/binaries as default directory for local symbolization binaries
- searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries")
- }
-
- // Collect names to search: {buildid/basename, basename}
- var fileNames []string
- if baseName := filepath.Base(file); buildID != "" {
- fileNames = []string{filepath.Join(buildID, baseName), baseName}
- } else {
- fileNames = []string{baseName}
- }
- for _, path := range filepath.SplitList(searchPath) {
- for nameIndex, name := range fileNames {
- file := filepath.Join(path, name)
- if f, err := obj.Open(file, start); err == nil {
- fileBuildID := f.BuildID()
- if buildID == "" || buildID == fileBuildID {
- return f, nil
- }
- f.Close()
- if nameIndex == 0 {
- // If this is the first name, the path includes the build id. Report inconsistency.
- return nil, fmt.Errorf("found file %s with inconsistent build id %s", file, fileBuildID)
- }
- }
- }
- }
- // Try original file name
- f, err := obj.Open(file, start)
- if err == nil && buildID != "" {
- if fileBuildID := f.BuildID(); fileBuildID != "" && fileBuildID != buildID {
- // Mismatched build IDs, ignore
- f.Close()
- return nil, fmt.Errorf("mismatched build ids %s != %s", fileBuildID, buildID)
- }
- }
- return f, err
-}
-
-// mappingTable contains the mechanisms for symbolization of a
-// profile.
-type mappingTable struct {
- prof *profile.Profile
- segments map[*profile.Mapping]plugin.ObjFile
-}
-
-// Close releases any external processes being used for the mapping.
-func (mt *mappingTable) close() {
- for _, segment := range mt.segments {
- segment.Close()
- }
-}
diff --git a/src/cmd/pprof/internal/symbolz/symbolz.go b/src/cmd/pprof/internal/symbolz/symbolz.go
deleted file mode 100644
index 15b3b6df26..0000000000
--- a/src/cmd/pprof/internal/symbolz/symbolz.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package symbolz symbolizes a profile using the output from the symbolz
-// service.
-package symbolz
-
-import (
- "bytes"
- "fmt"
- "io"
- "net/url"
- "regexp"
- "strconv"
- "strings"
-
- "cmd/pprof/internal/profile"
-)
-
-var (
- symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`)
-)
-
-// Symbolize symbolizes profile p by parsing data returned by a
-// symbolz handler. syms receives the symbolz query (hex addresses
-// separated by '+') and returns the symbolz output in a string. It
-// symbolizes all locations based on their addresses, regardless of
-// mapping.
-func Symbolize(source string, syms func(string, string) ([]byte, error), p *profile.Profile) error {
- if source = symbolz(source, p); source == "" {
- // If the source is not a recognizable URL, do nothing.
- return nil
- }
-
- // Construct query of addresses to symbolize.
- var a []string
- for _, l := range p.Location {
- if l.Address != 0 && len(l.Line) == 0 {
- a = append(a, fmt.Sprintf("%#x", l.Address))
- }
- }
-
- if len(a) == 0 {
- // No addresses to symbolize.
- return nil
- }
- lines := make(map[uint64]profile.Line)
- functions := make(map[string]*profile.Function)
- if b, err := syms(source, strings.Join(a, "+")); err == nil {
- buf := bytes.NewBuffer(b)
- for {
- l, err := buf.ReadString('\n')
-
- if err != nil {
- if err == io.EOF {
- break
- }
- return err
- }
-
- if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
- addr, err := strconv.ParseUint(symbol[1], 0, 64)
- if err != nil {
- return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
- }
-
- name := symbol[2]
- fn := functions[name]
- if fn == nil {
- fn = &profile.Function{
- ID: uint64(len(p.Function) + 1),
- Name: name,
- SystemName: name,
- }
- functions[name] = fn
- p.Function = append(p.Function, fn)
- }
-
- lines[addr] = profile.Line{Function: fn}
- }
- }
- }
-
- for _, l := range p.Location {
- if line, ok := lines[l.Address]; ok {
- l.Line = []profile.Line{line}
- if l.Mapping != nil {
- l.Mapping.HasFunctions = true
- }
- }
- }
-
- return nil
-}
-
-// symbolz returns the corresponding symbolz source for a profile URL.
-func symbolz(source string, p *profile.Profile) string {
- if url, err := url.Parse(source); err == nil && url.Host != "" {
- if last := strings.LastIndex(url.Path, "/"); last != -1 {
- if strings.HasSuffix(url.Path[:last], "pprof") {
- url.Path = url.Path[:last] + "/symbol"
- } else {
- url.Path = url.Path[:last] + "/symbolz"
- }
- return url.String()
- }
- }
-
- return ""
-}
diff --git a/src/cmd/pprof/internal/tempfile/tempfile.go b/src/cmd/pprof/internal/tempfile/tempfile.go
deleted file mode 100644
index 31c117690a..0000000000
--- a/src/cmd/pprof/internal/tempfile/tempfile.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package tempfile provides tools to create and delete temporary files
-package tempfile
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "sync"
-)
-
-// New returns an unused filename for output files.
-func New(dir, prefix, suffix string) (*os.File, error) {
- for index := 1; index < 10000; index++ {
- path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
- if _, err := os.Stat(path); err != nil {
- return os.Create(path)
- }
- }
- // Give up
- return nil, fmt.Errorf("could not create file of the form %s%03d%s", prefix, 1, suffix)
-}
-
-var tempFiles []string
-var tempFilesMu = sync.Mutex{}
-
-// DeferDelete marks a file to be deleted by next call to Cleanup()
-func DeferDelete(path string) {
- tempFilesMu.Lock()
- tempFiles = append(tempFiles, path)
- tempFilesMu.Unlock()
-}
-
-// Cleanup removes any temporary files selected for deferred cleaning.
-func Cleanup() {
- tempFilesMu.Lock()
- for _, f := range tempFiles {
- os.Remove(f)
- }
- tempFiles = nil
- tempFilesMu.Unlock()
-}
diff --git a/src/cmd/pprof/pprof.go b/src/cmd/pprof/pprof.go
index 1c55d05d5d..bce37dcb97 100644
--- a/src/cmd/pprof/pprof.go
+++ b/src/cmd/pprof/pprof.go
@@ -15,13 +15,13 @@ import (
"sync"
"cmd/internal/objfile"
- "cmd/pprof/internal/commands"
- "cmd/pprof/internal/driver"
- "cmd/pprof/internal/fetch"
- "cmd/pprof/internal/plugin"
- "cmd/pprof/internal/profile"
- "cmd/pprof/internal/symbolizer"
- "cmd/pprof/internal/symbolz"
+ "cmd/internal/pprof/commands"
+ "cmd/internal/pprof/driver"
+ "cmd/internal/pprof/fetch"
+ "cmd/internal/pprof/plugin"
+ "cmd/internal/pprof/profile"
+ "cmd/internal/pprof/symbolizer"
+ "cmd/internal/pprof/symbolz"
)
func main() {