diff options
| author | Alberto Donizetti <alb.donizetti@gmail.com> | 2017-08-20 12:27:32 +0200 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@golang.org> | 2017-11-02 23:51:45 +0000 |
| commit | aec345d638fa624f08b7d758e9e173897edc80e8 (patch) | |
| tree | d782d951af4f34de34a08c4775a37f869af25b81 /src/cmd/vendor/github.com/google/pprof/internal/driver | |
| parent | 3039bff9d07ce05dc9af8c155c6929ae5e53a231 (diff) | |
| download | go-aec345d638fa624f08b7d758e9e173897edc80e8.tar.xz | |
cmd/vendor/github.com/google/pprof: refresh from upstream
Update vendored pprof to commit 4fc39a00b6b8c1aad05260f01429ec70e127252c
from github.com/google/pprof (2017-11-01).
Fixes #19380
Updates #21047
Change-Id: Ib64a94a45209039e5945acbcfa0392790c8ee41e
Reviewed-on: https://go-review.googlesource.com/57370
Run-TryBot: Alberto Donizetti <alb.donizetti@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'src/cmd/vendor/github.com/google/pprof/internal/driver')
48 files changed, 3070 insertions, 369 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/cli.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/cli.go index 0005ead70b..e2e8c6c936 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/cli.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/cli.go @@ -24,14 +24,17 @@ import ( ) type source struct { - Sources []string - ExecName string - BuildID string - Base []string + Sources []string + ExecName string + BuildID string + Base []string + Normalize bool - Seconds int - Timeout int - Symbolize string + Seconds int + Timeout int + Symbolize string + HTTPHostport string + Comment string } // Parse parses the command lines through the specified flags package @@ -41,9 +44,11 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { flag := o.Flagset // Comparisons. flagBase := flag.StringList("base", "", "Source for base profile for comparison") - // Internal options. + // Source options. flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization") flagBuildID := flag.String("buildid", "", "Override build id for first mapping") + flagTimeout := flag.Int("timeout", -1, "Timeout in seconds for fetching a profile") + flagAddComment := flag.String("add_comment", "", "Annotation string to record in the profile") // CPU profile options flagSeconds := flag.Int("seconds", -1, "Length of time for dynamic profiles") // Heap profile options @@ -57,7 +62,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { 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") - flagTimeout := flag.Int("timeout", -1, "Timeout in seconds for fetching a profile") + flagHTTP := flag.String("http", "", "Present interactive web based UI at the specified http host:port") // Flags used during command processing installedFlags := installFlags(flag) @@ -106,6 +111,9 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { if err != nil { return nil, nil, err } + if cmd != nil && *flagHTTP != "" { + return nil, nil, fmt.Errorf("-http is not compatible with an output format on the command line") + } si := pprofVariables["sample_index"].value si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI) @@ -122,12 +130,14 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { } source := &source{ - Sources: args, - ExecName: execName, - BuildID: *flagBuildID, - Seconds: *flagSeconds, - Timeout: *flagTimeout, - Symbolize: *flagSymbolize, + Sources: args, + ExecName: execName, + BuildID: *flagBuildID, + Seconds: *flagSeconds, + Timeout: *flagTimeout, + Symbolize: *flagSymbolize, + HTTPHostport: *flagHTTP, + Comment: *flagAddComment, } for _, s := range *flagBase { @@ -136,6 +146,12 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { } } + normalize := pprofVariables["normalize"].boolValue() + if normalize && len(source.Base) == 0 { + return nil, nil, fmt.Errorf("Must have base profile to normalize by") + } + source.Normalize = normalize + if bu, ok := o.Obj.(*binutils.Binutils); ok { bu.SetTools(*flagTools) } @@ -240,13 +256,33 @@ func outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string, return cmd, nil } -var usageMsgHdr = "usage: pprof [options] [-base source] [binary] <source> ...\n" +var usageMsgHdr = `usage: + +Produce output in the specified format. + + pprof <format> [options] [binary] <source> ... + +Omit the format to get an interactive shell whose commands can be used +to generate various views of a profile + + pprof [options] [binary] <source> ... + +Omit the format and provide the "-http" flag to get an interactive web +interface at the specified host:port that can be used to navigate through +various views of a profile. + + pprof -http [host]:[port] [options] [binary] <source> ... + +Details: +` var usageMsgSrc = "\n\n" + " Source options:\n" + " -seconds Duration for time-based profile collection\n" + " -timeout Timeout in seconds for profile collection\n" + " -buildid Override build id for main binary\n" + + " -add_comment Free-form annotation to add to the profile\n" + + " Displayed on some reports or with pprof -comments\n" + " -base source Source of profile to use as baseline\n" + " profile.pb.gz Profile in compressed protobuf format\n" + " legacy_profile Profile in legacy pprof format\n" + @@ -261,7 +297,19 @@ var usageMsgSrc = "\n\n" + var usageMsgVars = "\n\n" + " Misc options:\n" + - " -tools Search path for object tools\n" + + " -http Provide web based interface at host:port.\n" + + " Host is optional and 'localhost' by default.\n" + + " Port is optional and a randomly available port by default.\n" + + " -tools Search path for object tools\n" + + "\n" + + " Legacy convenience options:\n" + + " -inuse_space Same as -sample_index=inuse_space\n" + + " -inuse_objects Same as -sample_index=inuse_objects\n" + + " -alloc_space Same as -sample_index=alloc_space\n" + + " -alloc_objects Same as -sample_index=alloc_objects\n" + + " -total_delay Same as -sample_index=delay\n" + + " -contentions Same as -sample_index=contentions\n" + + " -mean_delay Same as -mean -sample_index=delay\n" + "\n" + " Environment Variables:\n" + " PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" + diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go index 5e54062771..66e5c86b9d 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go @@ -139,10 +139,10 @@ var pprofVariables = variables{ // Comparisons. "positive_percentages": &variable{boolKind, "f", "", helpText( "Ignore negative samples when computing percentages", - " Do not count negative samples when computing the total value", - " of the profile, used to compute percentages. If set, and the -base", - " option is used, percentages reported will be computed against the", - " main profile, ignoring the base profile.")}, + "Do not count negative samples when computing the total value", + "of the profile, used to compute percentages. If set, and the -base", + "option is used, percentages reported will be computed against the", + "main profile, ignoring the base profile.")}, // Graph handling options. "call_tree": &variable{boolKind, "f", "", helpText( @@ -157,9 +157,9 @@ var pprofVariables = variables{ "unit": &variable{stringKind, "minimum", "", helpText( "Measurement units to display", "Scale the sample values to this unit.", - " For time-based profiles, use seconds, milliseconds, nanoseconds, etc.", - " For memory profiles, use megabytes, kilobytes, bytes, etc.", - " auto will scale each value independently to the most natural unit.")}, + "For time-based profiles, use seconds, milliseconds, nanoseconds, etc.", + "For memory profiles, use megabytes, kilobytes, bytes, etc.", + "Using auto will scale each value independently to the most natural unit.")}, "compact_labels": &variable{boolKind, "f", "", "Show minimal headers"}, "source_path": &variable{stringKind, "", "", "Search path for source files"}, @@ -195,11 +195,15 @@ var pprofVariables = variables{ "If set, only show nodes that match this location.", "Matching includes the function name, filename or object name.")}, "tagfocus": &variable{stringKind, "", "", helpText( - "Restrict to samples with tags in range or matched by regexp", - "Discard samples that do not include a node with a tag matching this regexp.")}, + "Restricts to samples with tags in range or matched by regexp", + "Use name=value syntax to limit the matching to a specific tag.", + "Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:", + "String tag filter examples: foo, foo.*bar, mytag=foo.*bar")}, "tagignore": &variable{stringKind, "", "", helpText( "Discard samples with tags in range or matched by regexp", - "Discard samples that do include a node with a tag matching this regexp.")}, + "Use name=value syntax to limit the matching to a specific tag.", + "Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:", + "String tag filter examples: foo, foo.*bar, mytag=foo.*bar")}, "tagshow": &variable{stringKind, "", "", helpText( "Only consider tags matching this regexp", "Discard tags that do not match this regexp")}, @@ -218,6 +222,8 @@ var pprofVariables = variables{ "Sample value to report (0-based index or name)", "Profiles contain multiple values per sample.", "Use sample_index=i to select the ith value (starting at 0).")}, + "normalize": &variable{boolKind, "f", "", helpText( + "Scales profile based on the base profile.")}, // Data sorting criteria "flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")}, @@ -227,9 +233,6 @@ var pprofVariables = variables{ "functions": &variable{boolKind, "t", "granularity", helpText( "Aggregate at the function level.", "Takes into account the filename/lineno where the function was defined.")}, - "functionnameonly": &variable{boolKind, "f", "granularity", helpText( - "Aggregate at the function level.", - "Ignores the filename/lineno where the function was defined.")}, "files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."}, "lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."}, "addresses": &variable{boolKind, "f", "granularity", helpText( @@ -266,7 +269,7 @@ func usage(commandLine bool) string { var help string if commandLine { - help = " Output formats (select only one):\n" + help = " Output formats (select at most one):\n" } else { help = " Commands:\n" commands = append(commands, fmtHelp("o/options", "List options and their current values")) @@ -471,7 +474,7 @@ func (vars variables) set(name, value string) error { case boolKind: var b bool if b, err = stringToBool(value); err == nil { - if v.group != "" && b == false { + if v.group != "" && !b { err = fmt.Errorf("%q can only be set to true", name) } } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go index 2ca09dfa32..bc5f366128 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/report" @@ -52,24 +53,30 @@ func PProf(eo *plugin.Options) error { return generateReport(p, cmd, pprofVariables, o) } + if src.HTTPHostport != "" { + return serveWebInterface(src.HTTPHostport, p, o) + } return interactive(p, o) } -func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error { +func generateRawReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) (*command, *report.Report, error) { p = p.Copy() // Prevent modification to the incoming profile. + // Identify units of numeric tags in profile. + numLabelUnits := identifyNumLabelUnits(p, o.UI) + vars = applyCommandOverrides(cmd, vars) // Delay focus after configuring report to get percentages on all samples. relative := vars["relative_percentages"].boolValue() if relative { - if err := applyFocus(p, vars, o.UI); err != nil { - return err + if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil { + return nil, nil, err } } - ropt, err := reportOptions(p, vars) + ropt, err := reportOptions(p, numLabelUnits, vars) if err != nil { - return err + return nil, nil, err } c := pprofCommands[cmd[0]] if c == nil { @@ -79,18 +86,27 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin. if len(cmd) == 2 { s, err := regexp.Compile(cmd[1]) if err != nil { - return fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err) + return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err) } ropt.Symbol = s } rpt := report.New(p, ropt) if !relative { - if err := applyFocus(p, vars, o.UI); err != nil { - return err + if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil { + return nil, nil, err } } if err := aggregate(p, vars); err != nil { + return nil, nil, err + } + + return c, rpt, nil +} + +func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error { + c, rpt, err := generateRawReport(p, cmd, vars, o) + if err != nil { return err } @@ -160,20 +176,20 @@ func applyCommandOverrides(cmd []string, v variables) variables { v.set("nodecount", "80") } } - if trim == false { + if !trim { v.set("nodecount", "0") v.set("nodefraction", "0") v.set("edgefraction", "0") } - if focus == false { + if !focus { v.set("focus", "") v.set("ignore", "") } - if tagfocus == false { + if !tagfocus { v.set("tagfocus", "") v.set("tagignore", "") } - if hide == false { + if !hide { v.set("hide", "") v.set("show", "") } @@ -196,25 +212,20 @@ func aggregate(prof *profile.Profile, v variables) error { case v["functions"].boolValue(): inlines = true function = true - filename = true case v["noinlines"].boolValue(): function = true - filename = true case v["addressnoinlines"].boolValue(): function = true filename = true linenumber = true address = true - case v["functionnameonly"].boolValue(): - inlines = true - function = true default: return fmt.Errorf("unexpected granularity") } return prof.Aggregate(inlines, function, filename, linenumber, address) } -func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) { +func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars variables) (*report.Options, error) { si, mean := vars["sample_index"].value, vars["mean"].boolValue() value, meanDiv, sample, err := sampleFormat(p, si, mean) if err != nil { @@ -230,6 +241,14 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) return nil, fmt.Errorf("zero divisor specified") } + var filters []string + for _, k := range []string{"focus", "ignore", "hide", "show", "tagfocus", "tagignore", "tagshow", "taghide"} { + v := vars[k].value + if v != "" { + filters = append(filters, k+"="+v) + } + } + ropt := &report.Options{ CumSort: vars["cum"].boolValue(), CallTree: vars["call_tree"].boolValue(), @@ -243,6 +262,9 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) NodeFraction: vars["nodefraction"].floatValue(), EdgeFraction: vars["edgefraction"].floatValue(), + ActiveFilters: filters, + NumLabelUnits: numLabelUnits, + SampleValue: value, SampleMeanDivisor: meanDiv, SampleType: stype, @@ -260,6 +282,19 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) return ropt, nil } +// identifyNumLabelUnits returns a map of numeric label keys to the units +// associated with those keys. +func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string { + numLabelUnits, ignoredUnits := p.NumLabelUnits() + + // Print errors for tags with multiple units associated with + // a single key. + for k, units := range ignoredUnits { + ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", "))) + } + return numLabelUnits +} + type sampleValueFunc func([]int64) int64 // sampleFormat returns a function to extract values out of a profile.Sample, diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_focus.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_focus.go index c60ad8157e..ba5b502ad9 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_focus.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_focus.go @@ -28,13 +28,13 @@ import ( var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)") // applyFocus filters samples based on the focus/ignore options -func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error { +func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variables, ui plugin.UI) error { focus, err := compileRegexOption("focus", v["focus"].value, nil) ignore, err := compileRegexOption("ignore", v["ignore"].value, err) hide, err := compileRegexOption("hide", v["hide"].value, err) show, err := compileRegexOption("show", v["show"].value, err) - tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, ui, err) - tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, ui, err) + tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err) + tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err) prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err) if err != nil { return err @@ -59,7 +59,7 @@ func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error { if prunefrom != nil { prof.PruneFrom(prunefrom) } - return nil + return err } func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) { @@ -73,23 +73,49 @@ func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) { return rx, nil } -func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) { +func compileTagFilter(name, value string, numLabelUnits map[string]string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) { if value == "" || err != nil { return nil, err } + + tagValuePair := strings.SplitN(value, "=", 2) + var wantKey string + if len(tagValuePair) == 2 { + wantKey = tagValuePair[0] + value = tagValuePair[1] + } + if numFilter := parseTagFilterRange(value); numFilter != nil { ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp") - return func(s *profile.Sample) bool { - for key, vals := range s.NumLabel { - for _, val := range vals { - if numFilter(val, key) { + labelFilter := func(vals []int64, unit string) bool { + for _, val := range vals { + if numFilter(val, unit) { + return true + } + } + return false + } + numLabelUnit := func(key string) string { + return numLabelUnits[key] + } + if wantKey == "" { + return func(s *profile.Sample) bool { + for key, vals := range s.NumLabel { + if labelFilter(vals, numLabelUnit(key)) { return true } } + return false + }, nil + } + return func(s *profile.Sample) bool { + if vals, ok := s.NumLabel[wantKey]; ok { + return labelFilter(vals, numLabelUnit(wantKey)) } return false }, nil } + var rfx []*regexp.Regexp for _, tagf := range strings.Split(value, ",") { fx, err := regexp.Compile(tagf) @@ -98,19 +124,34 @@ func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profil } rfx = append(rfx, fx) } + if wantKey == "" { + return func(s *profile.Sample) bool { + matchedrx: + for _, rx := range rfx { + for key, vals := range s.Label { + for _, val := range vals { + // TODO: Match against val, not key:val in future + if rx.MatchString(key + ":" + val) { + continue matchedrx + } + } + } + return false + } + return true + }, nil + } return func(s *profile.Sample) bool { - matchedrx: - for _, rx := range rfx { - for key, vals := range s.Label { + if vals, ok := s.Label[wantKey]; ok { + for _, rx := range rfx { for _, val := range vals { - if rx.MatchString(key + ":" + val) { - continue matchedrx + if rx.MatchString(val) { + return true } } } - return false } - return true + return false }, nil } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go index 75eaebec39..1289a096b8 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go @@ -16,9 +16,13 @@ package driver import ( "bytes" + "flag" "fmt" "io/ioutil" + "net" + _ "net/http/pprof" "os" + "reflect" "regexp" "runtime" "strconv" @@ -32,52 +36,61 @@ import ( "github.com/google/pprof/profile" ) +var updateFlag = flag.Bool("update", false, "Update the golden files") + func TestParse(t *testing.T) { // Override weblist command to collect output in buffer pprofCommands["weblist"].postProcess = nil // Our mockObjTool.Open will always return success, causing - // driver.locateBinaries to "find" the binaries below in a non-existant + // driver.locateBinaries to "find" the binaries below in a non-existent // directory. As a workaround, point the search path to the fake // directory containing out fake binaries. savePath := os.Getenv("PPROF_BINARY_PATH") os.Setenv("PPROF_BINARY_PATH", "/path/to") defer os.Setenv("PPROF_BINARY_PATH", savePath) - testcase := []struct { flags, source string }{ {"text,functions,flat", "cpu"}, {"tree,addresses,flat,nodecount=4", "cpusmall"}, - {"text,functions,flat", "unknown"}, + {"text,functions,flat,nodecount=5,call_tree", "unknown"}, {"text,alloc_objects,flat", "heap_alloc"}, {"text,files,flat", "heap"}, + {"text,files,flat,focus=[12]00,taghide=[X3]00", "heap"}, {"text,inuse_objects,flat", "heap"}, {"text,lines,cum,hide=line[X3]0", "cpu"}, {"text,lines,cum,show=[12]00", "cpu"}, + {"text,lines,cum,hide=line[X3]0,focus=[12]00", "cpu"}, {"topproto,lines,cum,hide=mangled[X3]0", "cpu"}, {"tree,lines,cum,focus=[24]00", "heap"}, {"tree,relative_percentages,cum,focus=[24]00", "heap"}, {"callgrind", "cpu"}, + {"callgrind,call_tree", "cpu"}, {"callgrind", "heap"}, {"dot,functions,flat", "cpu"}, + {"dot,functions,flat,call_tree", "cpu"}, {"dot,lines,flat,focus=[12]00", "heap"}, + {"dot,unit=minimum", "heap_sizetags"}, {"dot,addresses,flat,ignore=[X3]002,focus=[X1]000", "contention"}, {"dot,files,cum", "contention"}, - {"comments", "cpu"}, + {"comments,add_comment=some-comment", "cpu"}, {"comments", "heap"}, {"tags", "cpu"}, {"tags,tagignore=tag[13],tagfocus=key[12]", "cpu"}, {"tags", "heap"}, {"tags,unit=bytes", "heap"}, {"traces", "cpu"}, + {"traces", "heap_tags"}, {"dot,alloc_space,flat,focus=[234]00", "heap_alloc"}, + {"dot,alloc_space,flat,tagshow=[2]00", "heap_alloc"}, {"dot,alloc_space,flat,hide=line.*1?23?", "heap_alloc"}, {"dot,inuse_space,flat,tagfocus=1mb:2gb", "heap"}, {"dot,inuse_space,flat,tagfocus=30kb:,tagignore=1mb:2mb", "heap"}, {"disasm=line[13],addresses,flat", "cpu"}, {"peek=line.*01", "cpu"}, {"weblist=line[13],addresses,flat", "cpu"}, + {"tags,tagfocus=400kb:", "heap_request"}, } baseVars := pprofVariables @@ -99,6 +112,7 @@ func TestParse(t *testing.T) { if err != nil { t.Errorf("cannot create tempfile: %v", err) } + defer os.Remove(protoTempFile.Name()) defer protoTempFile.Close() f.strings["output"] = protoTempFile.Name() @@ -124,6 +138,7 @@ func TestParse(t *testing.T) { if err != nil { t.Errorf("cannot create tempfile: %v", err) } + defer os.Remove(outputTempFile.Name()) defer outputTempFile.Close() f.strings["output"] = outputTempFile.Name() f.args = []string{protoTempFile.Name()} @@ -140,6 +155,8 @@ func TestParse(t *testing.T) { addFlags(&f, flags[:1]) solution = solutionFilename(tc.source, &f) } + // The add_comment flag is not idempotent so only apply it on the first run. + delete(f.strings, "add_comment") // Second pprof invocation to read the profile from profile.proto // and generate a report. @@ -180,6 +197,12 @@ func TestParse(t *testing.T) { t.Fatalf("diff %s %v", solution, err) } t.Errorf("%s\n%s\n", solution, d) + if *updateFlag { + err := ioutil.WriteFile(solution, b, 0644) + if err != nil { + t.Errorf("failed to update the solution file %q: %v", solution, err) + } + } } } } @@ -214,14 +237,19 @@ func addFlags(f *testFlags, flags []string) { } } +func testSourceURL(port int) string { + return fmt.Sprintf("http://%s/", net.JoinHostPort(testSourceAddress, strconv.Itoa(port))) +} + // solutionFilename returns the name of the solution file for the test func solutionFilename(source string, f *testFlags) string { - name := []string{"pprof", strings.TrimPrefix(source, "http://host:8000/")} + name := []string{"pprof", strings.TrimPrefix(source, testSourceURL(8000))} name = addString(name, f, []string{"flat", "cum"}) name = addString(name, f, []string{"functions", "files", "lines", "addresses"}) name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"}) name = addString(name, f, []string{"relative_percentages"}) name = addString(name, f, []string{"seconds"}) + name = addString(name, f, []string{"call_tree"}) name = addString(name, f, []string{"text", "tree", "callgrind", "dot", "svg", "tags", "dot", "traces", "disasm", "peek", "weblist", "topproto", "comments"}) if f.strings["focus"] != "" || f.strings["tagfocus"] != "" { name = append(name, "focus") @@ -247,11 +275,12 @@ func addString(name []string, f *testFlags, components []string) []string { // testFlags implements the plugin.FlagSet interface. type testFlags struct { - bools map[string]bool - ints map[string]int - floats map[string]float64 - strings map[string]string - args []string + bools map[string]bool + ints map[string]int + floats map[string]float64 + strings map[string]string + args []string + stringLists map[string][]*string } func (testFlags) ExtraUsage() string { return "" } @@ -317,6 +346,9 @@ func (f testFlags) StringVar(p *string, s, d, c string) { } func (f testFlags) StringList(s, d, c string) *[]*string { + if t, ok := f.stringLists[s]; ok { + return &t + } return &[]*string{} } @@ -345,9 +377,6 @@ func baseFlags() testFlags { } } -type testProfile struct { -} - const testStart = 0x1000 const testOffset = 0x5000 @@ -355,7 +384,6 @@ type testFetcher struct{} func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) { var p *profile.Profile - s = strings.TrimPrefix(s, "http://host:8000/") switch s { case "cpu", "unknown": p = cpuProfile() @@ -369,21 +397,36 @@ func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string {Type: "alloc_objects", Unit: "count"}, {Type: "alloc_space", Unit: "bytes"}, } + case "heap_request": + p = heapProfile() + for _, s := range p.Sample { + s.NumLabel["request"] = s.NumLabel["bytes"] + } + case "heap_sizetags": + p = heapProfile() + tags := []int64{2, 4, 8, 16, 32, 64, 128, 256} + for _, s := range p.Sample { + numValues := append(s.NumLabel["bytes"], tags...) + s.NumLabel["bytes"] = numValues + } + case "heap_tags": + p = heapProfile() + for i := 0; i < len(p.Sample); i += 2 { + s := p.Sample[i] + if s.Label == nil { + s.Label = make(map[string][]string) + } + s.NumLabel["request"] = s.NumLabel["bytes"] + s.Label["key1"] = []string{"tag"} + } case "contention": p = contentionProfile() case "symbolz": p = symzProfile() - case "http://host2/symbolz": - p = symzProfile() - p.Mapping[0].Start += testOffset - p.Mapping[0].Limit += testOffset - for i := range p.Location { - p.Location[i].Address += testOffset - } default: return nil, "", fmt.Errorf("unexpected source: %s", s) } - return p, s, nil + return p, testSourceURL(8000) + s, nil } type testSymbolizer struct{} @@ -406,7 +449,19 @@ func (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *pr func testFetchSymbols(source, post string) ([]byte, error) { var buf bytes.Buffer - if source == "http://host2/symbolz" { + switch source { + case testSourceURL(8000) + "symbolz": + for _, address := range strings.Split(post, "+") { + a, _ := strconv.ParseInt(address, 0, 64) + fmt.Fprintf(&buf, "%v\t", address) + if a-testStart > testOffset { + fmt.Fprintf(&buf, "wrong_source_%v_", address) + continue + } + fmt.Fprintf(&buf, "%#x\n", a-testStart) + } + return buf.Bytes(), nil + case testSourceURL(8001) + "symbolz": for _, address := range strings.Split(post, "+") { a, _ := strconv.ParseInt(address, 0, 64) fmt.Fprintf(&buf, "%v\t", address) @@ -417,23 +472,15 @@ func testFetchSymbols(source, post string) ([]byte, error) { fmt.Fprintf(&buf, "%#x\n", a-testStart-testOffset) } return buf.Bytes(), nil + default: + return nil, fmt.Errorf("unexpected source: %s", source) } - for _, address := range strings.Split(post, "+") { - a, _ := strconv.ParseInt(address, 0, 64) - fmt.Fprintf(&buf, "%v\t", address) - if a-testStart > testOffset { - fmt.Fprintf(&buf, "wrong_source_%v_", address) - continue - } - fmt.Fprintf(&buf, "%#x\n", a-testStart) - } - return buf.Bytes(), nil } type testSymbolzSymbolizer struct{} func (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error { - return symbolz.Symbolize(sources, testFetchSymbols, p, nil) + return symbolz.Symbolize(p, false, sources, testFetchSymbols, nil) } func fakeDemangler(name string) string { @@ -543,32 +590,32 @@ func cpuProfile() *profile.Profile { Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]}, Value: []int64{1000, 1000}, Label: map[string][]string{ - "key1": []string{"tag1"}, - "key2": []string{"tag1"}, + "key1": {"tag1"}, + "key2": {"tag1"}, }, }, { Location: []*profile.Location{cpuL[0], cpuL[3]}, Value: []int64{100, 100}, Label: map[string][]string{ - "key1": []string{"tag2"}, - "key3": []string{"tag2"}, + "key1": {"tag2"}, + "key3": {"tag2"}, }, }, { Location: []*profile.Location{cpuL[1], cpuL[4]}, Value: []int64{10, 10}, Label: map[string][]string{ - "key1": []string{"tag3"}, - "key2": []string{"tag2"}, + "key1": {"tag3"}, + "key2": {"tag2"}, }, }, { Location: []*profile.Location{cpuL[2]}, Value: []int64{10, 10}, Label: map[string][]string{ - "key1": []string{"tag4"}, - "key2": []string{"tag1"}, + "key1": {"tag4"}, + "key2": {"tag1"}, }, }, }, @@ -744,30 +791,22 @@ func heapProfile() *profile.Profile { { Location: []*profile.Location{heapL[0], heapL[1], heapL[2]}, Value: []int64{10, 1024000}, - NumLabel: map[string][]int64{ - "bytes": []int64{102400}, - }, + NumLabel: map[string][]int64{"bytes": {102400}}, }, { Location: []*profile.Location{heapL[0], heapL[3]}, Value: []int64{20, 4096000}, - NumLabel: map[string][]int64{ - "bytes": []int64{204800}, - }, + NumLabel: map[string][]int64{"bytes": {204800}}, }, { Location: []*profile.Location{heapL[1], heapL[4]}, Value: []int64{40, 65536000}, - NumLabel: map[string][]int64{ - "bytes": []int64{1638400}, - }, + NumLabel: map[string][]int64{"bytes": {1638400}}, }, { Location: []*profile.Location{heapL[2]}, Value: []int64{80, 32768000}, - NumLabel: map[string][]int64{ - "bytes": []int64{409600}, - }, + NumLabel: map[string][]int64{"bytes": {409600}}, }, }, DropFrames: ".*operator new.*|malloc", @@ -950,31 +989,394 @@ func TestAutoComplete(t *testing.T) { func TestTagFilter(t *testing.T) { var tagFilterTests = []struct { - name, value string + desc, value string tags map[string][]string want bool }{ - {"test1", "tag2", map[string][]string{"value1": {"tag1", "tag2"}}, true}, - {"test2", "tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false}, - {"test3", "tag1,tag3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true}, - {"test4", "t..[12],t..3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true}, - {"test5", "tag2,tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false}, + { + "1 key with 1 matching value", + "tag2", + map[string][]string{"value1": {"tag1", "tag2"}}, + true, + }, + { + "1 key with no matching values", + "tag3", + map[string][]string{"value1": {"tag1", "tag2"}}, + false, + }, + { + "two keys, each with value matching different one value in list", + "tag1,tag3", + map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, + true, + }, + {"two keys, all value matching different regex value in list", + "t..[12],t..3", + map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, + true, + }, + { + "one key, not all values in list matched", + "tag2,tag3", + map[string][]string{"value1": {"tag1", "tag2"}}, + false, + }, + { + "key specified, list of tags where all tags in list matched", + "key1=tag1,tag2", + map[string][]string{"key1": {"tag1", "tag2"}}, + true, + }, + {"key specified, list of tag values where not all are matched", + "key1=tag1,tag2", + map[string][]string{"key1": {"tag1"}}, + true, + }, + { + "key included for regex matching, list of values where all values in list matched", + "key1:tag1,tag2", + map[string][]string{"key1": {"tag1", "tag2"}}, + true, + }, + { + "key included for regex matching, list of values where not only second value matched", + "key1:tag1,tag2", + map[string][]string{"key1": {"tag2"}}, + false, + }, + { + "key included for regex matching, list of values where not only first value matched", + "key1:tag1,tag2", + map[string][]string{"key1": {"tag1"}}, + false, + }, + } + for _, test := range tagFilterTests { + t.Run(test.desc, func(*testing.T) { + filter, err := compileTagFilter(test.desc, test.value, nil, &proftest.TestUI{T: t}, nil) + if err != nil { + t.Fatalf("tagFilter %s:%v", test.desc, err) + } + s := profile.Sample{ + Label: test.tags, + } + if got := filter(&s); got != test.want { + t.Errorf("tagFilter %s: got %v, want %v", test.desc, got, test.want) + } + }) } +} +func TestIdentifyNumLabelUnits(t *testing.T) { + var tagFilterTests = []struct { + desc string + tagVals []map[string][]int64 + tagUnits []map[string][]string + wantUnits map[string]string + allowedRx string + wantIgnoreErrCount int + }{ + { + "Multiple keys, no units for all keys", + []map[string][]int64{{"keyA": {131072}, "keyB": {128}}}, + []map[string][]string{{"keyA": {}, "keyB": {""}}}, + map[string]string{"keyA": "keyA", "keyB": "keyB"}, + "", + 0, + }, + { + "Multiple keys, different units for each key", + []map[string][]int64{{"keyA": {131072}, "keyB": {128}}}, + []map[string][]string{{"keyA": {"bytes"}, "keyB": {"kilobytes"}}}, + map[string]string{"keyA": "bytes", "keyB": "kilobytes"}, + "", + 0, + }, + { + "Multiple keys with multiple values, different units for each key", + []map[string][]int64{{"keyC": {131072, 1}, "keyD": {128, 252}}}, + []map[string][]string{{"keyC": {"bytes", "bytes"}, "keyD": {"kilobytes", "kilobytes"}}}, + map[string]string{"keyC": "bytes", "keyD": "kilobytes"}, + "", + 0, + }, + { + "Multiple keys with multiple values, some units missing", + []map[string][]int64{{"key1": {131072, 1}, "A": {128, 252}, "key3": {128}, "key4": {1}}, {"key3": {128}, "key4": {1}}}, + []map[string][]string{{"key1": {"", "bytes"}, "A": {"kilobytes", ""}, "key3": {""}, "key4": {"hour"}}, {"key3": {"seconds"}, "key4": {""}}}, + map[string]string{"key1": "bytes", "A": "kilobytes", "key3": "seconds", "key4": "hour"}, + "", + 0, + }, + { + "One key with three units in same sample", + []map[string][]int64{{"key": {8, 8, 16}}}, + []map[string][]string{{"key": {"bytes", "megabytes", "kilobytes"}}}, + map[string]string{"key": "bytes"}, + `(For tag key used unit bytes, also encountered unit\(s\) kilobytes, megabytes)`, + 1, + }, + { + "One key with four units in same sample", + []map[string][]int64{{"key": {8, 8, 16, 32}}}, + []map[string][]string{{"key": {"bytes", "kilobytes", "a", "megabytes"}}}, + map[string]string{"key": "bytes"}, + `(For tag key used unit bytes, also encountered unit\(s\) a, kilobytes, megabytes)`, + 1, + }, + { + "One key with two units in same sample", + []map[string][]int64{{"key": {8, 8}}}, + []map[string][]string{{"key": {"bytes", "seconds"}}}, + map[string]string{"key": "bytes"}, + `(For tag key used unit bytes, also encountered unit\(s\) seconds)`, + 1, + }, + { + "One key with different units in different samples", + []map[string][]int64{{"key1": {8}}, {"key1": {8}}, {"key1": {8}}}, + []map[string][]string{{"key1": {"bytes"}}, {"key1": {"kilobytes"}}, {"key1": {"megabytes"}}}, + map[string]string{"key1": "bytes"}, + `(For tag key1 used unit bytes, also encountered unit\(s\) kilobytes, megabytes)`, + 1, + }, + { + "Key alignment, unit not specified", + []map[string][]int64{{"alignment": {8}}}, + []map[string][]string{nil}, + map[string]string{"alignment": "bytes"}, + "", + 0, + }, + { + "Key request, unit not specified", + []map[string][]int64{{"request": {8}}, {"request": {8, 8}}}, + []map[string][]string{nil, nil}, + map[string]string{"request": "bytes"}, + "", + 0, + }, + { + "Check units not over-written for keys with default units", + []map[string][]int64{{ + "alignment": {8}, + "request": {8}, + "bytes": {8}, + }}, + []map[string][]string{{ + "alignment": {"seconds"}, + "request": {"minutes"}, + "bytes": {"hours"}, + }}, + map[string]string{ + "alignment": "seconds", + "request": "minutes", + "bytes": "hours", + }, + "", + 0, + }, + } for _, test := range tagFilterTests { - filter, err := compileTagFilter(test.name, test.value, &proftest.TestUI{T: t}, nil) - if err != nil { - t.Errorf("tagFilter %s:%v", test.name, err) - continue - } - s := profile.Sample{ - Label: test.tags, - } + t.Run(test.desc, func(*testing.T) { + p := profile.Profile{Sample: make([]*profile.Sample, len(test.tagVals))} + for i, numLabel := range test.tagVals { + s := profile.Sample{ + NumLabel: numLabel, + NumUnit: test.tagUnits[i], + } + p.Sample[i] = &s + } + testUI := &proftest.TestUI{T: t, AllowRx: test.allowedRx} + units := identifyNumLabelUnits(&p, testUI) + if !reflect.DeepEqual(test.wantUnits, units) { + t.Errorf("got %v units, want %v", units, test.wantUnits) + } + if got, want := testUI.NumAllowRxMatches, test.wantIgnoreErrCount; want != got { + t.Errorf("got %d errors logged, want %d errors logged", got, want) + } + }) + } +} + +func TestNumericTagFilter(t *testing.T) { + var tagFilterTests = []struct { + desc, value string + tags map[string][]int64 + identifiedUnits map[string]string + want bool + }{ + { + "Match when unit conversion required", + "128kb", + map[string][]int64{"key1": {131072}, "key2": {128}}, + map[string]string{"key1": "bytes", "key2": "kilobytes"}, + true, + }, + { + "Match only when values equal after unit conversion", + "512kb", + map[string][]int64{"key1": {512}, "key2": {128}}, + map[string]string{"key1": "bytes", "key2": "kilobytes"}, + false, + }, + { + "Match when values and units initially equal", + "10bytes", + map[string][]int64{"key1": {10}, "key2": {128}}, + map[string]string{"key1": "bytes", "key2": "kilobytes"}, + true, + }, + { + "Match range without lower bound, no unit conversion required", + ":10bytes", + map[string][]int64{"key1": {8}}, + map[string]string{"key1": "bytes"}, + true, + }, + { + "Match range without lower bound, unit conversion required", + ":10kb", + map[string][]int64{"key1": {8}}, + map[string]string{"key1": "bytes"}, + true, + }, + { + "Match range without upper bound, unit conversion required", + "10b:", + map[string][]int64{"key1": {8}}, + map[string]string{"key1": "kilobytes"}, + true, + }, + { + "Match range without upper bound, no unit conversion required", + "10b:", + map[string][]int64{"key1": {12}}, + map[string]string{"key1": "bytes"}, + true, + }, + { + "Don't match range without upper bound, no unit conversion required", + "10b:", + map[string][]int64{"key1": {8}}, + map[string]string{"key1": "bytes"}, + false, + }, + { + "Multiple keys with different units, don't match range without upper bound", + "10kb:", + map[string][]int64{"key1": {8}}, + map[string]string{"key1": "bytes", "key2": "kilobytes"}, + false, + }, + { + "Match range without upper bound, unit conversion required", + "10b:", + map[string][]int64{"key1": {8}}, + map[string]string{"key1": "kilobytes"}, + true, + }, + { + "Don't match range without lower bound, no unit conversion required", + ":10b", + map[string][]int64{"key1": {12}}, + map[string]string{"key1": "bytes"}, + false, + }, + { + "Match specific key, key present, one of two values match", + "bytes=5b", + map[string][]int64{"bytes": {10, 5}}, + map[string]string{"bytes": "bytes"}, + true, + }, + { + "Match specific key, key present and value matches", + "bytes=1024b", + map[string][]int64{"bytes": {1024}}, + map[string]string{"bytes": "kilobytes"}, + false, + }, + { + "Match specific key, matching key present and value matches, also non-matching key", + "bytes=1024b", + map[string][]int64{"bytes": {1024}, "key2": {5}}, + map[string]string{"bytes": "bytes", "key2": "bytes"}, + true, + }, + { + "Match specific key and range of values, value matches", + "bytes=512b:1024b", + map[string][]int64{"bytes": {780}}, + map[string]string{"bytes": "bytes"}, + true, + }, + { + "Match specific key and range of values, value too large", + "key1=1kb:2kb", + map[string][]int64{"key1": {4096}}, + map[string]string{"key1": "bytes"}, + false, + }, + { + "Match specific key and range of values, value too small", + "key1=1kb:2kb", + map[string][]int64{"key1": {256}}, + map[string]string{"key1": "bytes"}, + false, + }, + { + "Match specific key and value, unit conversion required", + "bytes=1024b", + map[string][]int64{"bytes": {1}}, + map[string]string{"bytes": "kilobytes"}, + true, + }, + { + "Match specific key and value, key does not appear", + "key2=256bytes", + map[string][]int64{"key1": {256}}, + map[string]string{"key1": "bytes"}, + false, + }, + } + for _, test := range tagFilterTests { + t.Run(test.desc, func(*testing.T) { + wantErrMsg := strings.Join([]string{"(", test.desc, ":Interpreted '", test.value[strings.Index(test.value, "=")+1:], "' as range, not regexp", ")"}, "") + filter, err := compileTagFilter(test.desc, test.value, test.identifiedUnits, &proftest.TestUI{T: t, + AllowRx: wantErrMsg}, nil) + if err != nil { + t.Fatalf("%v", err) + } + s := profile.Sample{ + NumLabel: test.tags, + } + if got := filter(&s); got != test.want { + t.Fatalf("got %v, want %v", got, test.want) + } + }) + } +} + +type testSymbolzMergeFetcher struct{} - if got := filter(&s); got != test.want { - t.Errorf("tagFilter %s: got %v, want %v", test.name, got, test.want) +func (testSymbolzMergeFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) { + var p *profile.Profile + switch s { + case testSourceURL(8000) + "symbolz": + p = symzProfile() + case testSourceURL(8001) + "symbolz": + p = symzProfile() + p.Mapping[0].Start += testOffset + p.Mapping[0].Limit += testOffset + for i := range p.Location { + p.Location[i].Address += testOffset } + default: + return nil, "", fmt.Errorf("unexpected source: %s", s) } + return p, s, nil } func TestSymbolzAfterMerge(t *testing.T) { @@ -983,7 +1385,10 @@ func TestSymbolzAfterMerge(t *testing.T) { defer func() { pprofVariables = baseVars }() f := baseFlags() - f.args = []string{"symbolz", "http://host2/symbolz"} + f.args = []string{ + testSourceURL(8000) + "symbolz", + testSourceURL(8001) + "symbolz", + } o := setDefaults(nil) o.Flagset = f @@ -997,7 +1402,7 @@ func TestSymbolzAfterMerge(t *testing.T) { t.Fatalf("parseFlags returned command %v, want [proto]", cmd) } - o.Fetch = testFetcher{} + o.Fetch = testSymbolzMergeFetcher{} o.Sym = testSymbolzSymbolizer{} p, err := fetchProfiles(src, o) if err != nil { @@ -1028,10 +1433,10 @@ func (m *mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, err switch start { case 0x1000: return []plugin.Inst{ - {Addr: 0x1000, Text: "instruction one"}, - {Addr: 0x1001, Text: "instruction two"}, - {Addr: 0x1002, Text: "instruction three"}, - {Addr: 0x1003, Text: "instruction four"}, + {Addr: 0x1000, Text: "instruction one", File: "file1000.src", Line: 1}, + {Addr: 0x1001, Text: "instruction two", File: "file1000.src", Line: 1}, + {Addr: 0x1002, Text: "instruction three", File: "file1000.src", Line: 2}, + {Addr: 0x1003, Text: "instruction four", File: "file1000.src", Line: 1}, }, nil case 0x3000: return []plugin.Inst{ @@ -1046,7 +1451,7 @@ func (m *mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, err } type mockFile struct { - name, buildId string + name, buildID string base uint64 } @@ -1062,7 +1467,7 @@ func (m *mockFile) Base() uint64 { // BuildID returns the GNU build ID of the file, or an empty string. func (m *mockFile) BuildID() string { - return m.buildId + return m.buildID } // SourceLine reports the source line information for a given diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go index f9e8231419..2b1d90dafd 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go @@ -41,39 +41,52 @@ import ( // there are some failures. It will return an error if it is unable to // fetch any profiles. func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) { - sources := make([]profileSource, 0, len(s.Sources)+len(s.Base)) + sources := make([]profileSource, 0, len(s.Sources)) for _, src := range s.Sources { sources = append(sources, profileSource{ addr: src, source: s, - scale: 1, }) } + + bases := make([]profileSource, 0, len(s.Base)) for _, src := range s.Base { - sources = append(sources, profileSource{ + bases = append(bases, profileSource{ addr: src, source: s, - scale: -1, }) } - p, msrcs, save, cnt, err := chunkedGrab(sources, o.Fetch, o.Obj, o.UI) + + p, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI) if err != nil { return nil, err } - if cnt == 0 { - return nil, fmt.Errorf("failed to fetch any profiles") - } - if want, got := len(sources), cnt; want != got { - o.UI.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", got, want)) + + if pbase != nil { + if s.Normalize { + err := p.Normalize(pbase) + if err != nil { + return nil, err + } + } + pbase.Scale(-1) + p, m, err = combineProfiles([]*profile.Profile{p, pbase}, []plugin.MappingSources{m, mbase}) + if err != nil { + return nil, err + } } // Symbolize the merged profile. - if err := o.Sym.Symbolize(s.Symbolize, msrcs, p); err != nil { + if err := o.Sym.Symbolize(s.Symbolize, m, p); err != nil { return nil, err } p.RemoveUninteresting() unsourceMappings(p) + if s.Comment != "" { + p.Comments = append(p.Comments, s.Comment) + } + // Save a copy of the merged profile if there is at least one remote source. if save { dir, err := setTmpDir(o.UI) @@ -107,6 +120,47 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) { return p, nil } +func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, *profile.Profile, plugin.MappingSources, plugin.MappingSources, bool, error) { + wg := sync.WaitGroup{} + wg.Add(2) + var psrc, pbase *profile.Profile + var msrc, mbase plugin.MappingSources + var savesrc, savebase bool + var errsrc, errbase error + var countsrc, countbase int + go func() { + defer wg.Done() + psrc, msrc, savesrc, countsrc, errsrc = chunkedGrab(sources, fetch, obj, ui) + }() + go func() { + defer wg.Done() + pbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui) + }() + wg.Wait() + save := savesrc || savebase + + if errsrc != nil { + return nil, nil, nil, nil, false, fmt.Errorf("problem fetching source profiles: %v", errsrc) + } + if errbase != nil { + return nil, nil, nil, nil, false, fmt.Errorf("problem fetching base profiles: %v,", errbase) + } + if countsrc == 0 { + return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any source profiles") + } + if countbase == 0 && len(bases) > 0 { + return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any base profiles") + } + if want, got := len(sources), countsrc; want != got { + ui.PrintErr(fmt.Sprintf("Fetched %d source profiles out of %d", got, want)) + } + if want, got := len(bases), countbase; want != got { + ui.PrintErr(fmt.Sprintf("Fetched %d base profiles out of %d", got, want)) + } + + return psrc, pbase, msrc, mbase, save, nil +} + // chunkedGrab fetches the profiles described in source and merges them into // a single profile. It fetches a chunk of profiles concurrently, with a maximum // chunk size to limit its memory usage. @@ -142,6 +196,7 @@ func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTo count += chunkCount } } + return p, msrc, save, count, nil } @@ -152,7 +207,7 @@ func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.Ob for i := range sources { go func(s *profileSource) { defer wg.Done() - s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, s.scale, fetch, obj, ui) + s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui) }(&sources[i]) } wg.Wait() @@ -207,7 +262,6 @@ func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources) type profileSource struct { addr string source *source - scale float64 p *profile.Profile msrc plugin.MappingSources @@ -227,12 +281,18 @@ func homeEnv() string { } // setTmpDir prepares the directory to use to save profiles retrieved -// remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof. +// remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof, and, if +// $HOME is not set, falls back to os.TempDir(). func setTmpDir(ui plugin.UI) (string, error) { + var dirs []string if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" { - return profileDir, nil + dirs = append(dirs, profileDir) } - for _, tmpDir := range []string{os.Getenv(homeEnv()) + "/pprof", os.TempDir()} { + if homeDir := os.Getenv(homeEnv()); homeDir != "" { + dirs = append(dirs, filepath.Join(homeDir, "pprof")) + } + dirs = append(dirs, os.TempDir()) + for _, tmpDir := range dirs { if err := os.MkdirAll(tmpDir, 0755); err != nil { ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error()) continue @@ -242,10 +302,12 @@ func setTmpDir(ui plugin.UI) (string, error) { return "", fmt.Errorf("failed to identify temp dir") } +const testSourceAddress = "pproftest.local" + // grabProfile fetches a profile. Returns the profile, sources for the // profile mappings, a bool indicating if the profile was fetched // remotely, and an error. -func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) { +func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) { var src string duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second if fetcher != nil { @@ -266,9 +328,6 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher return } - // Apply local changes to the profile. - p.Scale(scale) - // Update the binary locations from command line and paths. locateBinaries(p, s, obj, ui) @@ -276,6 +335,11 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher if src != "" { msrc = collectMappingSources(p, src) remote = true + if strings.HasPrefix(src, "http://"+testSourceAddress) { + // Treat test inputs as local to avoid saving + // testcase profiles during driver testing. + remote = false + } } return } @@ -366,20 +430,20 @@ mapping: } } } + if len(p.Mapping) == 0 { + // If there are no mappings, add a fake mapping to attempt symbolization. + // This is useful for some profiles generated by the golang runtime, which + // do not include any mappings. Symbolization with a fake mapping will only + // be successful against a non-PIE binary. + m := &profile.Mapping{ID: 1} + p.Mapping = []*profile.Mapping{m} + for _, l := range p.Location { + l.Mapping = m + } + } // Replace executable filename/buildID with the overrides from source. // Assumes the executable is the first Mapping entry. if execName, buildID := s.ExecName, s.BuildID; execName != "" || buildID != "" { - if len(p.Mapping) == 0 { - // If there are no mappings, add a fake mapping to attempt symbolization. - // This is useful for some profiles generated by the golang runtime, which - // do not include any mappings. Symbolization with a fake mapping will only - // be successful against a non-PIE binary. - m := &profile.Mapping{ID: 1} - p.Mapping = []*profile.Mapping{m} - for _, l := range p.Location { - l.Mapping = m - } - } m := p.Mapping[0] if execName != "" { m.File = execName diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go index 90b84b27c5..dd78bc7a7d 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go @@ -15,8 +15,15 @@ package driver import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "encoding/pem" "fmt" "io/ioutil" + "math/big" "net/http" "net/url" "os" @@ -24,11 +31,14 @@ import ( "reflect" "regexp" "runtime" + "strings" "testing" "time" + "github.com/google/pprof/internal/binutils" "github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/proftest" + "github.com/google/pprof/internal/symbolizer" "github.com/google/pprof/profile" ) @@ -165,6 +175,8 @@ func TestFetch(t *testing.T) { const path = "testdata/" // Intercept http.Get calls from HTTPFetcher. + savedHTTPGet := httpGet + defer func() { httpGet = savedHTTPGet }() httpGet = stubHTTPGet type testcase struct { @@ -176,7 +188,7 @@ func TestFetch(t *testing.T) { {path + "go.nomappings.crash", "/bin/gotest.exe"}, {"http://localhost/profile?file=cppbench.cpu", ""}, } { - p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, 0, nil, testObj{}, &proftest.TestUI{T: t}) + p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t}) if err != nil { t.Fatalf("%s: %s", tc.source, err) } @@ -194,6 +206,117 @@ func TestFetch(t *testing.T) { } } +func TestFetchWithBase(t *testing.T) { + baseVars := pprofVariables + defer func() { pprofVariables = baseVars }() + + const path = "testdata/" + type testcase struct { + desc string + sources []string + bases []string + normalize bool + expectedSamples [][]int64 + } + + testcases := []testcase{ + { + "not normalized base is same as source", + []string{path + "cppbench.contention"}, + []string{path + "cppbench.contention"}, + false, + [][]int64{}, + }, + { + "not normalized single source, multiple base (all profiles same)", + []string{path + "cppbench.contention"}, + []string{path + "cppbench.contention", path + "cppbench.contention"}, + false, + [][]int64{{-2700, -608881724}, {-100, -23992}, {-200, -179943}, {-100, -17778444}, {-100, -75976}, {-300, -63568134}}, + }, + { + "not normalized, different base and source", + []string{path + "cppbench.contention"}, + []string{path + "cppbench.small.contention"}, + false, + [][]int64{{1700, 608878600}, {100, 23992}, {200, 179943}, {100, 17778444}, {100, 75976}, {300, 63568134}}, + }, + { + "normalized base is same as source", + []string{path + "cppbench.contention"}, + []string{path + "cppbench.contention"}, + true, + [][]int64{}, + }, + { + "normalized single source, multiple base (all profiles same)", + []string{path + "cppbench.contention"}, + []string{path + "cppbench.contention", path + "cppbench.contention"}, + true, + [][]int64{}, + }, + { + "normalized different base and source", + []string{path + "cppbench.contention"}, + []string{path + "cppbench.small.contention"}, + true, + [][]int64{{-229, -370}, {28, 0}, {57, 0}, {28, 80}, {28, 0}, {85, 287}}, + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + pprofVariables = baseVars.makeCopy() + + base := make([]*string, len(tc.bases)) + for i, s := range tc.bases { + base[i] = &s + } + + f := testFlags{ + stringLists: map[string][]*string{ + "base": base, + }, + bools: map[string]bool{ + "normalize": tc.normalize, + }, + } + f.args = tc.sources + + o := setDefaults(nil) + o.Flagset = f + src, _, err := parseFlags(o) + + if err != nil { + t.Fatalf("%s: %v", tc.desc, err) + } + + p, err := fetchProfiles(src, o) + pprofVariables = baseVars + if err != nil { + t.Fatal(err) + } + + if want, got := len(tc.expectedSamples), len(p.Sample); want != got { + t.Fatalf("want %d samples got %d", want, got) + } + + if len(p.Sample) > 0 { + for i, sample := range p.Sample { + if want, got := len(tc.expectedSamples[i]), len(sample.Value); want != got { + t.Errorf("want %d values for sample %d, got %d", want, i, got) + } + for j, value := range sample.Value { + if want, got := tc.expectedSamples[i][j], value; want != got { + t.Errorf("want value of %d for value %d of sample %d, got %d", want, j, i, got) + } + } + } + } + }) + } +} + // mappingSources creates MappingSources map with a single item. func mappingSources(key, source string, start uint64) plugin.MappingSources { return plugin.MappingSources{ @@ -227,3 +350,123 @@ func stubHTTPGet(source string, _ time.Duration) (*http.Response, error) { c := &http.Client{Transport: t} return c.Get("file:///" + file) } + +func TestHttpsInsecure(t *testing.T) { + if runtime.GOOS == "nacl" { + t.Skip("test assumes tcp available") + } + + baseVars := pprofVariables + pprofVariables = baseVars.makeCopy() + defer func() { pprofVariables = baseVars }() + + tlsConfig := &tls.Config{Certificates: []tls.Certificate{selfSignedCert(t)}} + + l, err := tls.Listen("tcp", "localhost:0", tlsConfig) + if err != nil { + t.Fatalf("net.Listen: got error %v, want no error", err) + } + + donec := make(chan error, 1) + go func(donec chan<- error) { + donec <- http.Serve(l, nil) + }(donec) + defer func() { + if got, want := <-donec, "use of closed"; !strings.Contains(got.Error(), want) { + t.Fatalf("Serve got error %v, want %q", got, want) + } + }() + defer l.Close() + + go func() { + deadline := time.Now().Add(5 * time.Second) + for time.Now().Before(deadline) { + // Simulate a hotspot function. Spin in the inner loop for 100M iterations + // to ensure we get most of the samples landed here rather than in the + // library calls. We assume Go compiler won't elide the empty loop. + for i := 0; i < 1e8; i++ { + } + runtime.Gosched() + } + }() + + outputTempFile, err := ioutil.TempFile("", "profile_output") + if err != nil { + t.Fatalf("Failed to create tempfile: %v", err) + } + defer os.Remove(outputTempFile.Name()) + defer outputTempFile.Close() + + address := "https+insecure://" + l.Addr().String() + "/debug/pprof/profile" + s := &source{ + Sources: []string{address}, + Seconds: 10, + Timeout: 10, + Symbolize: "remote", + } + o := &plugin.Options{ + Obj: &binutils.Binutils{}, + UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"}, + } + o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI} + p, err := fetchProfiles(s, o) + if err != nil { + t.Fatal(err) + } + if len(p.SampleType) == 0 { + t.Fatalf("fetchProfiles(%s) got empty profile: len(p.SampleType)==0", address) + } + if len(p.Function) == 0 { + t.Fatalf("fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0", address) + } + if err := checkProfileHasFunction(p, "TestHttpsInsecure"); !badSigprofOS[runtime.GOOS] && err != nil { + t.Fatalf("fetchProfiles(%s) %v", address, err) + } +} + +// Some operating systems don't trigger the profiling signal right. +// See https://github.com/golang/go/issues/13841. +var badSigprofOS = map[string]bool{ + "darwin": true, + "netbsd": true, + "plan9": true, +} + +func checkProfileHasFunction(p *profile.Profile, fname string) error { + for _, f := range p.Function { + if strings.Contains(f.Name, fname) { + return nil + } + } + return fmt.Errorf("got %s, want function %q", p.String(), fname) +} + +func selfSignedCert(t *testing.T) tls.Certificate { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate private key: %v", err) + } + b, err := x509.MarshalECPrivateKey(privKey) + if err != nil { + t.Fatalf("failed to marshal private key: %v", err) + } + bk := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) + + tmpl := x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now(), + NotAfter: time.Now().Add(10 * time.Minute), + } + + b, err = x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privKey.Public(), privKey) + if err != nil { + t.Fatalf("failed to create cert: %v", err) + } + bc := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: b}) + + cert, err := tls.X509KeyPair(bc, bk) + if err != nil { + t.Fatalf("failed to create TLS key pair: %v", err) + } + return cert +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go index aa9c5b824b..2c36b64cc7 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go @@ -123,7 +123,8 @@ var generateReportWrapper = generateReport // For testing purposes. // greetings prints a brief welcome and some overall profile // information before accepting interactive commands. func greetings(p *profile.Profile, ui plugin.UI) { - ropt, err := reportOptions(p, pprofVariables) + numLabelUnits := identifyNumLabelUnits(p, ui) + ropt, err := reportOptions(p, numLabelUnits, pprofVariables) if err == nil { ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n")) } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.contention b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.contention new file mode 100644 index 0000000000..66a64c950c --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.contention @@ -0,0 +1,24 @@ +--- contentionz 1 --- +cycles/second = 3201000000 +sampling period = 100 +ms since reset = 16502830 +discarded samples = 0 + 19490304 27 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e + 768 1 @ 0xbccc97 0xa42dc7 0xa456e4 0x7fcdc2ff214e + 5760 2 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x4e969d 0x4faa17 0x4fc5f6 0x4fd028 0x4fd230 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e + 569088 1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87f08 0xb8814c 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e + 2432 1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x7aa74c 0x7ab844 0x7ab914 0x79e9e9 0x79e326 0x4d299e 0x4d4b7b 0x4b7be8 0x4b7ff1 0x4d2dae 0x79e80a + 2034816 3 @ 0xbccc97 0xb82f0f 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e +--- Memory map: --- + 00400000-00fcb000: cppbench_server_main + 7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so + 7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so + 7fcdc272f000-7fcdc28dd000: /libc-2.15.so + 7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so + 7fcdc2de3000-7fcdc2dea000: /librt-2.15.so + 7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so + 7fcdc3208000-7fcdc320a000: /libdl-2.15.so + 7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so + 7fcdc3645000-7fcdc3669000: /ld-2.15.so + 7fff86bff000-7fff86c00000: [vdso] + ffffffffff600000-ffffffffff601000: [vsyscall] diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.small.contention b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.small.contention new file mode 100644 index 0000000000..230cd90200 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.small.contention @@ -0,0 +1,19 @@ +--- contentionz 1 --- +cycles/second = 3201000000 +sampling period = 100 +ms since reset = 16502830 +discarded samples = 0 + 100 10 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e +--- Memory map: --- + 00400000-00fcb000: cppbench_server_main + 7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so + 7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so + 7fcdc272f000-7fcdc28dd000: /libc-2.15.so + 7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so + 7fcdc2de3000-7fcdc2dea000: /librt-2.15.so + 7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so + 7fcdc3208000-7fcdc320a000: /libdl-2.15.so + 7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so + 7fcdc3645000-7fcdc3669000: /ld-2.15.so + 7fff86bff000-7fff86c00000: [vdso] + ffffffffff600000-ffffffffff601000: [vsyscall] diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.cum.files.dot b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.cum.files.dot index 2e130c809f..30cece7a37 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.cum.files.dot +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.cum.files.dot @@ -1,9 +1,9 @@ digraph "unnamed" { node [style=filled fillcolor="#f8f8f8"] subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lShowing nodes accounting for 149.50ms, 100% of 149.50ms total\l"] } -N1 [label="file3000.src\n32.77ms (21.92%)\nof 149.50ms (100%)" fontsize=20 shape=box tooltip="testdata/file3000.src (149.50ms)" color="#b20000" fillcolor="#edd5d5"] -N2 [label="file1000.src\n51.20ms (34.25%)" fontsize=23 shape=box tooltip="testdata/file1000.src (51.20ms)" color="#b23100" fillcolor="#eddbd5"] -N3 [label="file2000.src\n65.54ms (43.84%)\nof 75.78ms (50.68%)" fontsize=24 shape=box tooltip="testdata/file2000.src (75.78ms)" color="#b22000" fillcolor="#edd9d5"] +N1 [label="file3000.src\n32.77ms (21.92%)\nof 149.50ms (100%)" id="node1" fontsize=20 shape=box tooltip="testdata/file3000.src (149.50ms)" color="#b20000" fillcolor="#edd5d5"] +N2 [label="file1000.src\n51.20ms (34.25%)" id="node2" fontsize=23 shape=box tooltip="testdata/file1000.src (51.20ms)" color="#b23100" fillcolor="#eddbd5"] +N3 [label="file2000.src\n65.54ms (43.84%)\nof 75.78ms (50.68%)" id="node3" fontsize=24 shape=box tooltip="testdata/file2000.src (75.78ms)" color="#b22000" fillcolor="#edd9d5"] N1 -> N3 [label=" 75.78ms" weight=51 penwidth=3 color="#b22000" tooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)" labeltooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)"] N1 -> N2 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)" labeltooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)"] N3 -> N2 [label=" 10.24ms" weight=7 color="#b29775" tooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)" labeltooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)"] diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore index aa08a41c99..03fbbb5296 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore @@ -1,9 +1,9 @@ digraph "unnamed" { node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lShowing nodes accounting for 40.96ms, 27.40% of 149.50ms total\l"] } -N1 [label="0000000000001000\nline1000\nfile1000.src:1\n40.96ms (27.40%)" fontsize=24 shape=box tooltip="0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" color="#b23900" fillcolor="#edddd5"] -N2 [label="0000000000003001\nline3000\nfile3000.src:5\n0 of 40.96ms (27.40%)" fontsize=8 shape=box tooltip="0000000000003001 line3000 testdata/file3000.src:5 (40.96ms)" color="#b23900" fillcolor="#edddd5"] -N3 [label="0000000000003001\nline3001\nfile3000.src:3\n0 of 40.96ms (27.40%)" fontsize=8 shape=box tooltip="0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" color="#b23900" fillcolor="#edddd5"] +subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lActive filters:\l focus=[X1]000\l ignore=[X3]002\lShowing nodes accounting for 40.96ms, 27.40% of 149.50ms total\l"] } +N1 [label="0000000000001000\nline1000\nfile1000.src:1\n40.96ms (27.40%)" id="node1" fontsize=24 shape=box tooltip="0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" color="#b23900" fillcolor="#edddd5"] +N2 [label="0000000000003001\nline3000\nfile3000.src:5\n0 of 40.96ms (27.40%)" id="node2" fontsize=8 shape=box tooltip="0000000000003001 line3000 testdata/file3000.src:5 (40.96ms)" color="#b23900" fillcolor="#edddd5"] +N3 [label="0000000000003001\nline3001\nfile3000.src:3\n0 of 40.96ms (27.40%)" id="node3" fontsize=8 shape=box tooltip="0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" color="#b23900" fillcolor="#edddd5"] N2 -> N3 [label=" 40.96ms\n (inline)" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" labeltooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)"] N3 -> N1 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" labeltooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)"] } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.call_tree.callgrind b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.call_tree.callgrind new file mode 100644 index 0000000000..e2286f631a --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.call_tree.callgrind @@ -0,0 +1,99 @@ +positions: instr line +events: cpu(ms) + +ob=(1) /path/to/testbinary +fl=(1) testdata/file1000.src +fn=(1) line1000 +0x1000 1 1000 +* 1 100 + +ob=(1) +fl=(2) testdata/file2000.src +fn=(2) line2001 ++4096 9 10 + +ob=(1) +fl=(3) testdata/file3000.src +fn=(3) line3002 ++4096 2 10 +cfl=(2) +cfn=(4) line2000 [1/2] +calls=0 * 4 +* * 1000 + +ob=(1) +fl=(2) +fn=(5) line2000 +-4096 4 0 +cfl=(2) +cfn=(6) line2001 [2/2] +calls=0 -4096 9 +* * 1000 +* 4 0 +cfl=(2) +cfn=(7) line2001 [1/2] +calls=0 * 9 +* * 10 + +ob=(1) +fl=(2) +fn=(2) +* 9 0 +cfl=(1) +cfn=(8) line1000 [1/2] +calls=0 -4096 1 +* * 1000 + +ob=(1) +fl=(3) +fn=(9) line3000 ++4096 6 0 +cfl=(3) +cfn=(10) line3001 [1/2] +calls=0 +4096 5 +* * 1010 + +ob=(1) +fl=(3) +fn=(11) line3001 +* 5 0 +cfl=(3) +cfn=(12) line3002 [1/2] +calls=0 * 2 +* * 1010 + +ob=(1) +fl=(3) +fn=(9) ++1 9 0 +cfl=(3) +cfn=(13) line3001 [2/2] +calls=0 +1 8 +* * 100 + +ob=(1) +fl=(3) +fn=(11) +* 8 0 +cfl=(1) +cfn=(14) line1000 [2/2] +calls=0 -8193 1 +* * 100 + +ob=(1) +fl=(3) +fn=(9) ++1 9 0 +cfl=(3) +cfn=(15) line3002 [2/2] +calls=0 +1 5 +* * 10 + +ob=(1) +fl=(3) +fn=(3) +* 5 0 +cfl=(2) +cfn=(16) line2000 [2/2] +calls=0 -4098 4 +* * 10 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.comments b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.comments index e69de29bb2..e6d9824e1b 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.comments +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.comments @@ -0,0 +1 @@ +some-comment diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.focus.hide b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.focus.hide new file mode 100644 index 0000000000..f0d928d76f --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.focus.hide @@ -0,0 +1,8 @@ +Active filters: + focus=[12]00 + hide=line[X3]0 +Showing nodes accounting for 1.11s, 99.11% of 1.12s total + flat flat% sum% cum cum% + 1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1 + 0 0% 98.21% 1.01s 90.18% line2000 testdata/file2000.src:4 + 0.01s 0.89% 99.11% 1.01s 90.18% line2001 testdata/file2000.src:9 (inline) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.hide b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.hide index 9d172713e5..bf503a57db 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.hide +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.hide @@ -1,3 +1,5 @@ +Active filters: + hide=line[X3]0 Showing nodes accounting for 1.11s, 99.11% of 1.12s total flat flat% sum% cum cum% 1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.show b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.show index 9d172713e5..7604cb8d7b 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.show +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.show @@ -1,3 +1,5 @@ +Active filters: + show=[12]00 Showing nodes accounting for 1.11s, 99.11% of 1.12s total flat flat% sum% cum cum% 1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide index 33bf6814a4..94b9be83df 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide @@ -1,3 +1,5 @@ +Active filters: + hide=mangled[X3]0 Showing nodes accounting for 1s, 100% of 1s total flat flat% sum% cum cum% 1s 100% 100% 1s 100% mangled1000 testdata/file1000.src:1 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.disasm b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.disasm index 9c8e603195..e1df7b1b64 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.disasm +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.disasm @@ -2,9 +2,9 @@ Total: 1.12s ROUTINE ======================== line1000 1.10s 1.10s (flat, cum) 98.21% of Total 1.10s 1.10s 1000: instruction one ;line1000 file1000.src:1 - . . 1001: instruction two - . . 1002: instruction three - . . 1003: instruction four + . . 1001: instruction two ;file1000.src:1 + . . 1002: instruction three ;file1000.src:2 + . . 1003: instruction four ;file1000.src:1 ROUTINE ======================== line3000 10ms 1.12s (flat, cum) 100% of Total 10ms 1.01s 3000: instruction one ;line3000 file3000.src:6 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist index ccf4ee8449..befc412db3 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist @@ -2,6 +2,7 @@ <!DOCTYPE html> <html> <head> +<meta charset="UTF-8"> <title>Pprof listing</title> <style type="text/css"> body { @@ -14,17 +15,11 @@ h1 { .legend { font-size: 1.25em; } -.line { -color: #aaaaaa; +.line, .nop, .unimportant { + color: #aaaaaa; } -.nop { -color: #aaaaaa; -} -.unimportant { -color: #cccccc; -} -.disasmloc { -color: #000000; +.inlinesrc { + color: #000066; } .deadsrc { cursor: pointer; @@ -69,39 +64,41 @@ Type: cpu<br> Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000</h1>testdata/file1000.src <pre onClick="pprof_toggle_asm(event)"> Total: 1.10s 1.10s (flat, cum) 98.21% -<span class=line> 1</span> <span class=deadsrc> 1.10s 1.10s line1 </span><span class=asm> 1.10s 1.10s 1000: instruction one <span class=disasmloc>file1000.src:1</span> - . . 1001: instruction two <span class=disasmloc></span> - . . 1002: instruction three <span class=disasmloc></span> - . . 1003: instruction four <span class=disasmloc></span> +<span class=line> 1</span> <span class=deadsrc> 1.10s 1.10s line1 </span><span class=asm> 1.10s 1.10s 1000: instruction one <span class=unimportant>file1000.src:1</span> + . . 1001: instruction two <span class=unimportant>file1000.src:1</span> + â‹® + . . 1003: instruction four <span class=unimportant>file1000.src:1</span> +</span> +<span class=line> 2</span> <span class=deadsrc> . . line2 </span><span class=asm> . . 1002: instruction three <span class=unimportant>file1000.src:2</span> </span> -<span class=line> 2</span> <span class=nop> . . line2 </span> -<span class=line> 3</span> <span class=nop> . . line3 </span> -<span class=line> 4</span> <span class=nop> . . line4 </span> -<span class=line> 5</span> <span class=nop> . . line5 </span> -<span class=line> 6</span> <span class=nop> . . line6 </span> +<span class=line> 3</span> <span class=nop> . . line3 </span> +<span class=line> 4</span> <span class=nop> . . line4 </span> +<span class=line> 5</span> <span class=nop> . . line5 </span> +<span class=line> 6</span> <span class=nop> . . line6 </span> +<span class=line> 7</span> <span class=nop> . . line7 </span> </pre> <h1>line3000</h1>testdata/file3000.src <pre onClick="pprof_toggle_asm(event)"> Total: 10ms 1.12s (flat, cum) 100% -<span class=line> 1</span> <span class=nop> . . line1 </span> -<span class=line> 2</span> <span class=nop> . . line2 </span> -<span class=line> 3</span> <span class=nop> . . line3 </span> -<span class=line> 4</span> <span class=nop> . . line4 </span> -<span class=line> 5</span> <span class=nop> . . line5 </span> -<span class=line> 6</span> <span class=deadsrc> 10ms 1.01s line6 </span><span class=asm> 10ms 1.01s 3000: instruction one <span class=disasmloc>file3000.src:6</span> +<span class=line> 1</span> <span class=nop> . . line1 </span> +<span class=line> 2</span> <span class=nop> . . line2 </span> +<span class=line> 3</span> <span class=nop> . . line3 </span> +<span class=line> 4</span> <span class=nop> . . line4 </span> +<span class=line> 5</span> <span class=nop> . . line5 </span> +<span class=line> 6</span> <span class=deadsrc> 10ms 1.01s line6 </span><span class=asm> 10ms 1.01s 3000: instruction one <span class=unimportant>file3000.src:6</span> </span> -<span class=line> 7</span> <span class=nop> . . line7 </span> -<span class=line> 8</span> <span class=nop> . . line8 </span> -<span class=line> 9</span> <span class=deadsrc> . 110ms line9 </span><span class=asm> . 100ms 3001: instruction two <span class=disasmloc>file3000.src:9</span> - . 10ms 3002: instruction three <span class=disasmloc>file3000.src:9</span> - . . 3003: instruction four <span class=disasmloc></span> - . . 3004: instruction five <span class=disasmloc></span> +<span class=line> 7</span> <span class=nop> . . line7 </span> +<span class=line> 8</span> <span class=nop> . . line8 </span> +<span class=line> 9</span> <span class=deadsrc> . 110ms line9 </span><span class=asm> . 100ms 3001: instruction two <span class=unimportant>file3000.src:9</span> + . 10ms 3002: instruction three <span class=unimportant>file3000.src:9</span> + . . 3003: instruction four <span class=unimportant></span> + . . 3004: instruction five <span class=unimportant></span> </span> -<span class=line> 10</span> <span class=nop> . . line0 </span> -<span class=line> 11</span> <span class=nop> . . line1 </span> -<span class=line> 12</span> <span class=nop> . . line2 </span> -<span class=line> 13</span> <span class=nop> . . line3 </span> -<span class=line> 14</span> <span class=nop> . . line4 </span> +<span class=line> 10</span> <span class=nop> . . line0 </span> +<span class=line> 11</span> <span class=nop> . . line1 </span> +<span class=line> 12</span> <span class=nop> . . line2 </span> +<span class=line> 13</span> <span class=nop> . . line3 </span> +<span class=line> 14</span> <span class=nop> . . line4 </span> </pre> </body> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.call_tree.dot b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.call_tree.dot new file mode 100644 index 0000000000..e854b5d6fa --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.call_tree.dot @@ -0,0 +1,21 @@ +digraph "testbinary" { +node [style=filled fillcolor="#f8f8f8"] +subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.11s, 99.11% of 1.12s total\lDropped 3 nodes (cum <= 0.06s)\l" tooltip="testbinary"] } +N1 [label="line1000\n1s (89.29%)" id="node1" fontsize=24 shape=box tooltip="line1000 (1s)" color="#b20500" fillcolor="#edd6d5"] +N1_0 [label = "key1:tag1\nkey2:tag1" id="N1_0" fontsize=8 shape=box3d tooltip="1s"] +N1 -> N1_0 [label=" 1s" weight=100 tooltip="1s" labeltooltip="1s"] +N2 [label="line3000\n0 of 1.12s (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (1.12s)" color="#b20000" fillcolor="#edd5d5"] +N3 [label="line3001\n0 of 1.11s (99.11%)" id="node3" fontsize=8 shape=box tooltip="line3001 (1.11s)" color="#b20000" fillcolor="#edd5d5"] +N4 [label="line1000\n0.10s (8.93%)" id="node4" fontsize=14 shape=box tooltip="line1000 (0.10s)" color="#b28b62" fillcolor="#ede8e2"] +N4_0 [label = "key1:tag2\nkey3:tag2" id="N4_0" fontsize=8 shape=box3d tooltip="0.10s"] +N4 -> N4_0 [label=" 0.10s" weight=100 tooltip="0.10s" labeltooltip="0.10s"] +N5 [label="line3002\n0.01s (0.89%)\nof 1.01s (90.18%)" id="node5" fontsize=10 shape=box tooltip="line3002 (1.01s)" color="#b20500" fillcolor="#edd6d5"] +N6 [label="line2000\n0 of 1s (89.29%)" id="node6" fontsize=8 shape=box tooltip="line2000 (1s)" color="#b20500" fillcolor="#edd6d5"] +N7 [label="line2001\n0 of 1s (89.29%)" id="node7" fontsize=8 shape=box tooltip="line2001 (1s)" color="#b20500" fillcolor="#edd6d5"] +N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 -> line3001 (1.11s)" labeltooltip="line3000 -> line3001 (1.11s)"] +N3 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 -> line3002 (1.01s)" labeltooltip="line3001 -> line3002 (1.01s)"] +N6 -> N7 [label=" 1s\n (inline)" weight=90 penwidth=5 color="#b20500" tooltip="line2000 -> line2001 (1s)" labeltooltip="line2000 -> line2001 (1s)"] +N7 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 -> line1000 (1s)" labeltooltip="line2001 -> line1000 (1s)"] +N5 -> N6 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line3002 -> line2000 (1s)" labeltooltip="line3002 -> line2000 (1s)"] +N3 -> N4 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 -> line1000 (0.10s)" labeltooltip="line3001 -> line1000 (0.10s)"] +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.dot b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.dot index 18b1abf54a..f0a5226b89 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.dot +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.dot @@ -1,20 +1,20 @@ digraph "testbinary" { node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.12s, 100% of 1.12s total\l"] } -N1 [label="line1000\nfile1000.src\n1.10s (98.21%)" fontsize=24 shape=box tooltip="line1000 testdata/file1000.src (1.10s)" color="#b20000" fillcolor="#edd5d5"] -N1_0 [label = "key1:tag1\nkey2:tag1" fontsize=8 shape=box3d tooltip="1s"] +subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.12s, 100% of 1.12s total\l" tooltip="testbinary"] } +N1 [label="line1000\n1.10s (98.21%)" id="node1" fontsize=24 shape=box tooltip="line1000 (1.10s)" color="#b20000" fillcolor="#edd5d5"] +N1_0 [label = "key1:tag1\nkey2:tag1" id="N1_0" fontsize=8 shape=box3d tooltip="1s"] N1 -> N1_0 [label=" 1s" weight=100 tooltip="1s" labeltooltip="1s"] -N1_1 [label = "key1:tag2\nkey3:tag2" fontsize=8 shape=box3d tooltip="0.10s"] +N1_1 [label = "key1:tag2\nkey3:tag2" id="N1_1" fontsize=8 shape=box3d tooltip="0.10s"] N1 -> N1_1 [label=" 0.10s" weight=100 tooltip="0.10s" labeltooltip="0.10s"] -N2 [label="line3000\nfile3000.src\n0 of 1.12s (100%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (1.12s)" color="#b20000" fillcolor="#edd5d5"] -N3 [label="line3001\nfile3000.src\n0 of 1.11s (99.11%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (1.11s)" color="#b20000" fillcolor="#edd5d5"] -N4 [label="line3002\nfile3000.src\n0.01s (0.89%)\nof 1.02s (91.07%)" fontsize=10 shape=box tooltip="line3002 testdata/file3000.src (1.02s)" color="#b20400" fillcolor="#edd6d5"] -N5 [label="line2001\nfile2000.src\n0.01s (0.89%)\nof 1.01s (90.18%)" fontsize=10 shape=box tooltip="line2001 testdata/file2000.src (1.01s)" color="#b20500" fillcolor="#edd6d5"] -N6 [label="line2000\nfile2000.src\n0 of 1.01s (90.18%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (1.01s)" color="#b20500" fillcolor="#edd6d5"] -N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)"] -N6 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)"] -N3 -> N4 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (1.01s)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (1.01s)"] -N4 -> N6 [label=" 1.01s" weight=91 penwidth=5 color="#b20500" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (1.01s)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (1.01s)"] -N5 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 testdata/file2000.src -> line1000 testdata/file1000.src (1s)" labeltooltip="line2001 testdata/file2000.src -> line1000 testdata/file1000.src (1s)"] -N3 -> N1 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)"] +N2 [label="line3000\n0 of 1.12s (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (1.12s)" color="#b20000" fillcolor="#edd5d5"] +N3 [label="line3001\n0 of 1.11s (99.11%)" id="node3" fontsize=8 shape=box tooltip="line3001 (1.11s)" color="#b20000" fillcolor="#edd5d5"] +N4 [label="line3002\n0.01s (0.89%)\nof 1.02s (91.07%)" id="node4" fontsize=10 shape=box tooltip="line3002 (1.02s)" color="#b20400" fillcolor="#edd6d5"] +N5 [label="line2001\n0.01s (0.89%)\nof 1.01s (90.18%)" id="node5" fontsize=10 shape=box tooltip="line2001 (1.01s)" color="#b20500" fillcolor="#edd6d5"] +N6 [label="line2000\n0 of 1.01s (90.18%)" id="node6" fontsize=8 shape=box tooltip="line2000 (1.01s)" color="#b20500" fillcolor="#edd6d5"] +N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 -> line3001 (1.11s)" labeltooltip="line3000 -> line3001 (1.11s)"] +N6 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line2000 -> line2001 (1.01s)" labeltooltip="line2000 -> line2001 (1.01s)"] +N3 -> N4 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 -> line3002 (1.01s)" labeltooltip="line3001 -> line3002 (1.01s)"] +N4 -> N6 [label=" 1.01s" weight=91 penwidth=5 color="#b20500" tooltip="line3002 -> line2000 (1.01s)" labeltooltip="line3002 -> line2000 (1.01s)"] +N5 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 -> line1000 (1s)" labeltooltip="line2001 -> line1000 (1s)"] +N3 -> N1 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 -> line1000 (0.10s)" labeltooltip="line3001 -> line1000 (0.10s)"] } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.text b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.text index 0807ed2325..66e4189e0a 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.text +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.text @@ -1,8 +1,8 @@ Showing nodes accounting for 1.12s, 100% of 1.12s total flat flat% sum% cum cum% - 1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src - 0.01s 0.89% 99.11% 1.01s 90.18% line2001 testdata/file2000.src (inline) - 0.01s 0.89% 100% 1.02s 91.07% line3002 testdata/file3000.src (inline) - 0 0% 100% 1.01s 90.18% line2000 testdata/file2000.src - 0 0% 100% 1.12s 100% line3000 testdata/file3000.src - 0 0% 100% 1.11s 99.11% line3001 testdata/file3000.src (inline) + 1.10s 98.21% 98.21% 1.10s 98.21% line1000 + 0.01s 0.89% 99.11% 1.01s 90.18% line2001 (inline) + 0.01s 0.89% 100% 1.02s 91.07% line3002 (inline) + 0 0% 100% 1.01s 90.18% line2000 + 0 0% 100% 1.12s 100% line3000 + 0 0% 100% 1.11s 99.11% line3001 (inline) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.peek b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.peek index 1a4a70c4d7..3b8a3537b4 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.peek +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.peek @@ -2,12 +2,12 @@ Showing nodes accounting for 1.12s, 100% of 1.12s total ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- - 1.01s 100% | line2000 testdata/file2000.src (inline) - 0.01s 0.89% 0.89% 1.01s 90.18% | line2001 testdata/file2000.src - 1s 99.01% | line1000 testdata/file1000.src + 1.01s 100% | line2000 (inline) + 0.01s 0.89% 0.89% 1.01s 90.18% | line2001 + 1s 99.01% | line1000 ----------------------------------------------------------+------------- - 1.11s 100% | line3000 testdata/file3000.src (inline) - 0 0% 0.89% 1.11s 99.11% | line3001 testdata/file3000.src - 1.01s 90.99% | line3002 testdata/file3000.src (inline) - 0.10s 9.01% | line1000 testdata/file1000.src + 1.11s 100% | line3000 (inline) + 0 0% 0.89% 1.11s 99.11% | line3001 + 1.01s 90.99% | line3002 (inline) + 0.10s 9.01% | line1000 ----------------------------------------------------------+------------- diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags index fc784f0c4c..5998b5ba5b 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags @@ -1,13 +1,13 @@ -key1: Total 1120 - 1000 (89.29%): tag1 - 100 ( 8.93%): tag2 - 10 ( 0.89%): tag3 - 10 ( 0.89%): tag4 + key1: Total 1.1s + 1.0s (89.29%): tag1 + 100.0ms ( 8.93%): tag2 + 10.0ms ( 0.89%): tag3 + 10.0ms ( 0.89%): tag4 -key2: Total 1020 - 1010 (99.02%): tag1 - 10 ( 0.98%): tag2 + key2: Total 1.0s + 1.0s (99.02%): tag1 + 10.0ms ( 0.98%): tag2 -key3: Total 100 - 100 ( 100%): tag2 + key3: Total 100.0ms + 100.0ms ( 100%): tag2 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags.focus.ignore b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags.focus.ignore index 650ebb1fdb..9b99d4368c 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags.focus.ignore +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags.focus.ignore @@ -1,6 +1,6 @@ -key1: Total 100 - 100 ( 100%): tag2 + key1: Total 100.0ms + 100.0ms ( 100%): tag2 -key3: Total 100 - 100 ( 100%): tag2 + key3: Total 100.0ms + 100.0ms ( 100%): tag2 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.traces b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.traces index d59fe30fe0..d9637c0e42 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.traces +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.traces @@ -4,29 +4,29 @@ Duration: 10s, Total samples = 1.12s (11.20%) -----------+------------------------------------------------------- key1: tag1 key2: tag1 - 1s line1000 testdata/file1000.src - line2001 testdata/file2000.src - line2000 testdata/file2000.src - line3002 testdata/file3000.src - line3001 testdata/file3000.src - line3000 testdata/file3000.src + 1s line1000 + line2001 + line2000 + line3002 + line3001 + line3000 -----------+------------------------------------------------------- key1: tag2 key3: tag2 - 100ms line1000 testdata/file1000.src - line3001 testdata/file3000.src - line3000 testdata/file3000.src + 100ms line1000 + line3001 + line3000 -----------+------------------------------------------------------- key1: tag3 key2: tag2 - 10ms line2001 testdata/file2000.src - line2000 testdata/file2000.src - line3002 testdata/file3000.src - line3000 testdata/file3000.src + 10ms line2001 + line2000 + line3002 + line3000 -----------+------------------------------------------------------- key1: tag4 key2: tag1 - 10ms line3002 testdata/file3000.src - line3001 testdata/file3000.src - line3000 testdata/file3000.src + 10ms line3002 + line3001 + line3000 -----------+------------------------------------------------------- diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.lines.tree.focus b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.lines.tree.focus index cda6d65b38..9d4ba72b1f 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.lines.tree.focus +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.lines.tree.focus @@ -1,3 +1,5 @@ +Active filters: + focus=[24]00 Showing nodes accounting for 62.50MB, 63.37% of 98.63MB total Dropped 2 nodes (cum <= 4.93MB) ----------------------------------------------------------+------------- diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus index 35f0bf5762..c2d11838fe 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus @@ -1,19 +1,21 @@ +Active filters: + focus=[24]00 Showing nodes accounting for 62.50MB, 98.46% of 63.48MB total Dropped 2 nodes (cum <= 3.17MB) ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- - 63.48MB 100% | line3002 testdata/file3000.src - 0 0% 0% 63.48MB 100% | line2000 testdata/file2000.src - 63.48MB 100% | line2001 testdata/file2000.src (inline) + 63.48MB 100% | line3002 + 0 0% 0% 63.48MB 100% | line2000 + 63.48MB 100% | line2001 (inline) ----------------------------------------------------------+------------- - 63.48MB 100% | line2000 testdata/file2000.src (inline) - 62.50MB 98.46% 98.46% 63.48MB 100% | line2001 testdata/file2000.src + 63.48MB 100% | line2000 (inline) + 62.50MB 98.46% 98.46% 63.48MB 100% | line2001 ----------------------------------------------------------+------------- - 0 0% 98.46% 63.48MB 100% | line3000 testdata/file3000.src - 63.48MB 100% | line3002 testdata/file3000.src (inline) + 0 0% 98.46% 63.48MB 100% | line3000 + 63.48MB 100% | line3002 (inline) ----------------------------------------------------------+------------- - 63.48MB 100% | line3000 testdata/file3000.src (inline) - 0 0% 98.46% 63.48MB 100% | line3002 testdata/file3000.src - 63.48MB 100% | line2000 testdata/file2000.src + 63.48MB 100% | line3000 (inline) + 0 0% 98.46% 63.48MB 100% | line3002 + 63.48MB 100% | line2000 ----------------------------------------------------------+------------- diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text.focus b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text.focus new file mode 100644 index 0000000000..20a503f9b4 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text.focus @@ -0,0 +1,8 @@ +Active filters: + focus=[12]00 + taghide=[X3]00 +Showing nodes accounting for 67.38MB, 68.32% of 98.63MB total + flat flat% sum% cum cum% + 62.50MB 63.37% 63.37% 63.48MB 64.36% testdata/file2000.src + 4.88MB 4.95% 68.32% 4.88MB 4.95% testdata/file1000.src + 0 0% 68.32% 67.38MB 68.32% testdata/file3000.src diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_objects.text b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_objects.text index bc061ad733..929461a3c1 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_objects.text +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_objects.text @@ -1,8 +1,8 @@ Showing nodes accounting for 150, 100% of 150 total flat flat% sum% cum cum% - 80 53.33% 53.33% 130 86.67% line3002 testdata/file3000.src (inline) - 40 26.67% 80.00% 50 33.33% line2001 testdata/file2000.src (inline) - 30 20.00% 100% 30 20.00% line1000 testdata/file1000.src - 0 0% 100% 50 33.33% line2000 testdata/file2000.src - 0 0% 100% 150 100% line3000 testdata/file3000.src - 0 0% 100% 110 73.33% line3001 testdata/file3000.src (inline) + 80 53.33% 53.33% 130 86.67% line3002 (inline) + 40 26.67% 80.00% 50 33.33% line2001 (inline) + 30 20.00% 100% 30 20.00% line1000 + 0 0% 100% 50 33.33% line2000 + 0 0% 100% 150 100% line3000 + 0 0% 100% 110 73.33% line3001 (inline) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus index c8533f3d44..909a824f1e 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus @@ -1,13 +1,13 @@ digraph "unnamed" { node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\l"] } -N1 [label="line2001\nfile2000.src\n62.50MB (63.37%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"] -NN1_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"] +subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lActive filters:\l tagfocus=1mb:2gb\lShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\l"] } +N1 [label="line2001\n62.50MB (63.37%)" id="node1" fontsize=24 shape=box tooltip="line2001 (62.50MB)" color="#b21600" fillcolor="#edd8d5"] +NN1_0 [label = "1.56MB" id="NN1_0" fontsize=8 shape=box3d tooltip="62.50MB"] N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"] -N2 [label="line3000\nfile3000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"] -N3 [label="line2000\nfile2000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"] -N4 [label="line3002\nfile3000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"] -N3 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)"] -N2 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)"] -N4 -> N3 [label=" 62.50MB" weight=64 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (62.50MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (62.50MB)"] +N2 [label="line3000\n0 of 62.50MB (63.37%)" id="node2" fontsize=8 shape=box tooltip="line3000 (62.50MB)" color="#b21600" fillcolor="#edd8d5"] +N3 [label="line2000\n0 of 62.50MB (63.37%)" id="node3" fontsize=8 shape=box tooltip="line2000 (62.50MB)" color="#b21600" fillcolor="#edd8d5"] +N4 [label="line3002\n0 of 62.50MB (63.37%)" id="node4" fontsize=8 shape=box tooltip="line3002 (62.50MB)" color="#b21600" fillcolor="#edd8d5"] +N3 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (62.50MB)" labeltooltip="line2000 -> line2001 (62.50MB)"] +N2 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"] +N4 -> N3 [label=" 62.50MB" weight=64 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (62.50MB)" labeltooltip="line3002 -> line2000 (62.50MB)"] } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore index 40354dd35d..b2929ae667 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore @@ -1,16 +1,16 @@ digraph "unnamed" { node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 36.13MB, 36.63% of 98.63MB total\lDropped 2 nodes (cum <= 4.93MB)\l"] } -N1 [label="line3002\nfile3000.src\n31.25MB (31.68%)\nof 32.23MB (32.67%)" fontsize=24 shape=box tooltip="line3002 testdata/file3000.src (32.23MB)" color="#b23200" fillcolor="#eddcd5"] -NN1_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"] +subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lActive filters:\l tagfocus=30kb:\l tagignore=1mb:2mb\lShowing nodes accounting for 36.13MB, 36.63% of 98.63MB total\lDropped 2 nodes (cum <= 4.93MB)\l"] } +N1 [label="line3002\n31.25MB (31.68%)\nof 32.23MB (32.67%)" id="node1" fontsize=24 shape=box tooltip="line3002 (32.23MB)" color="#b23200" fillcolor="#eddcd5"] +NN1_0 [label = "400kB" id="NN1_0" fontsize=8 shape=box3d tooltip="31.25MB"] N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"] -N2 [label="line3000\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] -N3 [label="line3001\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] -N4 [label="line1000\nfile1000.src\n4.88MB (4.95%)" fontsize=15 shape=box tooltip="line1000 testdata/file1000.src (4.88MB)" color="#b2a086" fillcolor="#edeae7"] -NN4_0 [label = "200kB" fontsize=8 shape=box3d tooltip="3.91MB"] +N2 [label="line3000\n0 of 36.13MB (36.63%)" id="node2" fontsize=8 shape=box tooltip="line3000 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] +N3 [label="line3001\n0 of 36.13MB (36.63%)" id="node3" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] +N4 [label="line1000\n4.88MB (4.95%)" id="node4" fontsize=15 shape=box tooltip="line1000 (4.88MB)" color="#b2a086" fillcolor="#edeae7"] +NN4_0 [label = "200kB" id="NN4_0" fontsize=8 shape=box3d tooltip="3.91MB"] N4 -> NN4_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"] -N2 -> N3 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)"] -N3 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"] -N3 -> N4 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)"] -N1 -> N4 [label=" 0.98MB" color="#b2b0a9" tooltip="line3002 testdata/file3000.src ... line1000 testdata/file1000.src (0.98MB)" labeltooltip="line3002 testdata/file3000.src ... line1000 testdata/file1000.src (0.98MB)" style="dotted" minlen=2] +N2 -> N3 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"] +N3 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"] +N3 -> N4 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 -> line1000 (3.91MB)" labeltooltip="line3001 -> line1000 (3.91MB)"] +N1 -> N4 [label=" 0.98MB" color="#b2b0a9" tooltip="line3002 ... line1000 (0.98MB)" labeltooltip="line3002 ... line1000 (0.98MB)" style="dotted" minlen=2] } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.lines.dot.focus b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.lines.dot.focus index f05969cfef..9af0341076 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.lines.dot.focus +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.lines.dot.focus @@ -1,16 +1,16 @@ digraph "unnamed" { node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\l"] } -N1 [label="line3000\nfile3000.src:4\n0 of 67.38MB (68.32%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src:4 (67.38MB)" color="#b21300" fillcolor="#edd7d5"] -N2 [label="line2001\nfile2000.src:2\n62.50MB (63.37%)\nof 63.48MB (64.36%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src:2 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] -NN2_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"] +subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lActive filters:\l focus=[12]00\lShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\l"] } +N1 [label="line3000\nfile3000.src:4\n0 of 67.38MB (68.32%)" id="node1" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src:4 (67.38MB)" color="#b21300" fillcolor="#edd7d5"] +N2 [label="line2001\nfile2000.src:2\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node2" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src:2 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +NN2_0 [label = "1.56MB" id="NN2_0" fontsize=8 shape=box3d tooltip="62.50MB"] N2 -> NN2_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"] -N3 [label="line1000\nfile1000.src:1\n4.88MB (4.95%)" fontsize=13 shape=box tooltip="line1000 testdata/file1000.src:1 (4.88MB)" color="#b2a086" fillcolor="#edeae7"] -NN3_0 [label = "200kB" fontsize=8 shape=box3d tooltip="3.91MB"] +N3 [label="line1000\nfile1000.src:1\n4.88MB (4.95%)" id="node3" fontsize=13 shape=box tooltip="line1000 testdata/file1000.src:1 (4.88MB)" color="#b2a086" fillcolor="#edeae7"] +NN3_0 [label = "200kB" id="NN3_0" fontsize=8 shape=box3d tooltip="3.91MB"] N3 -> NN3_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"] -N4 [label="line3002\nfile3000.src:3\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] -N5 [label="line3001\nfile3000.src:2\n0 of 4.88MB (4.95%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src:2 (4.88MB)" color="#b2a086" fillcolor="#edeae7"] -N6 [label="line2000\nfile2000.src:3\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +N4 [label="line3002\nfile3000.src:3\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +N5 [label="line3001\nfile3000.src:2\n0 of 4.88MB (4.95%)" id="node5" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src:2 (4.88MB)" color="#b2a086" fillcolor="#edeae7"] +N6 [label="line2000\nfile2000.src:3\n0 of 63.48MB (64.36%)" id="node6" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] N6 -> N2 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)" labeltooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)"] N4 -> N6 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)" labeltooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)"] N1 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)" labeltooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)"] diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags index 7a6f0a78f9..630e452a9f 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags @@ -1,6 +1,6 @@ -bytes: Total 150 - 80 (53.33%): 400kB - 40 (26.67%): 1.56MB - 20 (13.33%): 200kB - 10 ( 6.67%): 100kB + bytes: Total 98.6MB + 62.5MB (63.37%): 1.56MB + 31.2MB (31.68%): 400kB + 3.9MB ( 3.96%): 200kB + 1000.0kB ( 0.99%): 100kB diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags.unit b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags.unit index 7238b36710..5e565fc019 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags.unit +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags.unit @@ -1,6 +1,6 @@ -bytes: Total 150 - 80 (53.33%): 409600B - 40 (26.67%): 1638400B - 20 (13.33%): 204800B - 10 ( 6.67%): 102400B + bytes: Total 103424000.0B + 65536000.0B (63.37%): 1638400B + 32768000.0B (31.68%): 409600B + 4096000.0B ( 3.96%): 204800B + 1024000.0B ( 0.99%): 102400B diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text index bc061ad733..929461a3c1 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text @@ -1,8 +1,8 @@ Showing nodes accounting for 150, 100% of 150 total flat flat% sum% cum cum% - 80 53.33% 53.33% 130 86.67% line3002 testdata/file3000.src (inline) - 40 26.67% 80.00% 50 33.33% line2001 testdata/file2000.src (inline) - 30 20.00% 100% 30 20.00% line1000 testdata/file1000.src - 0 0% 100% 50 33.33% line2000 testdata/file2000.src - 0 0% 100% 150 100% line3000 testdata/file3000.src - 0 0% 100% 110 73.33% line3001 testdata/file3000.src (inline) + 80 53.33% 53.33% 130 86.67% line3002 (inline) + 40 26.67% 80.00% 50 33.33% line2001 (inline) + 30 20.00% 100% 30 20.00% line1000 + 0 0% 100% 50 33.33% line2000 + 0 0% 100% 150 100% line3000 + 0 0% 100% 110 73.33% line3001 (inline) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot new file mode 100644 index 0000000000..f0621a0e3c --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot @@ -0,0 +1,14 @@ +digraph "unnamed" { +node [style=filled fillcolor="#f8f8f8"] +subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lActive filters:\l tagshow=[2]00\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] } +N1 [label="line3002\n31.25MB (31.68%)\nof 94.73MB (96.04%)" id="node1" fontsize=20 shape=box tooltip="line3002 (94.73MB)" color="#b20200" fillcolor="#edd5d5"] +N2 [label="line3000\n0 of 98.63MB (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"] +N3 [label="line2001\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node3" fontsize=24 shape=box tooltip="line2001 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +N4 [label="line2000\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line2000 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +N5 [label="line3001\n0 of 36.13MB (36.63%)" id="node5" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] +N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (63.48MB)" labeltooltip="line2000 -> line2001 (63.48MB)"] +N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (63.48MB)" labeltooltip="line3002 -> line2000 (63.48MB)"] +N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"] +N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"] +N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"] +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus index c693ef3478..e412ff4813 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus @@ -1,18 +1,18 @@ digraph "unnamed" { node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] } -N1 [label="line3002\nfile3000.src\n31.25MB (31.68%)\nof 94.73MB (96.04%)" fontsize=20 shape=box tooltip="line3002 testdata/file3000.src (94.73MB)" color="#b20200" fillcolor="#edd5d5"] -NN1_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"] +subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lActive filters:\l focus=[234]00\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] } +N1 [label="line3002\n31.25MB (31.68%)\nof 94.73MB (96.04%)" id="node1" fontsize=20 shape=box tooltip="line3002 (94.73MB)" color="#b20200" fillcolor="#edd5d5"] +NN1_0 [label = "400kB" id="NN1_0" fontsize=8 shape=box3d tooltip="31.25MB"] N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"] -N2 [label="line3000\nfile3000.src\n0 of 98.63MB (100%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (98.63MB)" color="#b20000" fillcolor="#edd5d5"] -N3 [label="line2001\nfile2000.src\n62.50MB (63.37%)\nof 63.48MB (64.36%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src (63.48MB)" color="#b21600" fillcolor="#edd8d5"] -NN3_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"] +N2 [label="line3000\n0 of 98.63MB (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"] +N3 [label="line2001\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node3" fontsize=24 shape=box tooltip="line2001 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +NN3_0 [label = "1.56MB" id="NN3_0" fontsize=8 shape=box3d tooltip="62.50MB"] N3 -> NN3_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"] -N4 [label="line2000\nfile2000.src\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (63.48MB)" color="#b21600" fillcolor="#edd8d5"] -N5 [label="line3001\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] -N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)"] -N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" minlen=2] -N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)"] -N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)"] -N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"] +N4 [label="line2000\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line2000 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +N5 [label="line3001\n0 of 36.13MB (36.63%)" id="node5" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] +N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (63.48MB)" labeltooltip="line2000 -> line2001 (63.48MB)"] +N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (63.48MB)" labeltooltip="line3002 -> line2000 (63.48MB)" minlen=2] +N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"] +N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"] +N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"] } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide index 26a51c57a0..6110b114b9 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide @@ -1,11 +1,11 @@ digraph "unnamed" { node [style=filled fillcolor="#f8f8f8"] -subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] } -N1 [label="line3000\nfile3000.src\n62.50MB (63.37%)\nof 98.63MB (100%)" fontsize=24 shape=box tooltip="line3000 testdata/file3000.src (98.63MB)" color="#b20000" fillcolor="#edd5d5"] -NN1_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"] +subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lActive filters:\l hide=line.*1?23?\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] } +N1 [label="line3000\n62.50MB (63.37%)\nof 98.63MB (100%)" id="node1" fontsize=24 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"] +NN1_0 [label = "1.56MB" id="NN1_0" fontsize=8 shape=box3d tooltip="62.50MB"] N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"] -N2 [label="line3001\nfile3000.src\n31.25MB (31.68%)\nof 36.13MB (36.63%)" fontsize=20 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] -NN2_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"] +N2 [label="line3001\n31.25MB (31.68%)\nof 36.13MB (36.63%)" id="node2" fontsize=20 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] +NN2_0 [label = "400kB" id="NN2_0" fontsize=8 shape=box3d tooltip="31.25MB"] N2 -> NN2_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"] -N1 -> N2 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" minlen=2] +N1 -> N2 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)" minlen=2] } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_request.tags.focus b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_request.tags.focus new file mode 100644 index 0000000000..b1a5f444d8 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_request.tags.focus @@ -0,0 +1,8 @@ + bytes: Total 93.8MB + 62.5MB (66.67%): 1.56MB + 31.2MB (33.33%): 400kB + + request: Total 93.8MB + 62.5MB (66.67%): 1.56MB + 31.2MB (33.33%): 400kB + diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_sizetags.dot b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_sizetags.dot new file mode 100644 index 0000000000..6be6112fd4 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_sizetags.dot @@ -0,0 +1,30 @@ +digraph "unnamed" { +node [style=filled fillcolor="#f8f8f8"] +subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] } +N1 [label="line3002\n31.25MB (31.68%)\nof 94.73MB (96.04%)" id="node1" fontsize=20 shape=box tooltip="line3002 (94.73MB)" color="#b20200" fillcolor="#edd5d5"] +NN1_0 [label = "16B..64B" id="NN1_0" fontsize=8 shape=box3d tooltip="93.75MB"] +N1 -> NN1_0 [label=" 93.75MB" weight=100 tooltip="93.75MB" labeltooltip="93.75MB"] +NN1_1 [label = "2B..8B" id="NN1_1" fontsize=8 shape=box3d tooltip="93.75MB"] +N1 -> NN1_1 [label=" 93.75MB" weight=100 tooltip="93.75MB" labeltooltip="93.75MB"] +NN1_2 [label = "256B..1.56MB" id="NN1_2" fontsize=8 shape=box3d tooltip="62.50MB"] +N1 -> NN1_2 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"] +NN1_3 [label = "128B" id="NN1_3" fontsize=8 shape=box3d tooltip="31.25MB"] +N1 -> NN1_3 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"] +N2 [label="line3000\n0 of 98.63MB (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"] +N3 [label="line2001\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node3" fontsize=24 shape=box tooltip="line2001 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +NN3_0 [label = "16B..64B" id="NN3_0" fontsize=8 shape=box3d tooltip="190.43MB"] +N3 -> NN3_0 [label=" 190.43MB" weight=100 tooltip="190.43MB" labeltooltip="190.43MB" style="dotted"] +NN3_1 [label = "2B..8B" id="NN3_1" fontsize=8 shape=box3d tooltip="190.43MB"] +N3 -> NN3_1 [label=" 190.43MB" weight=100 tooltip="190.43MB" labeltooltip="190.43MB" style="dotted"] +NN3_2 [label = "256B..1.56MB" id="NN3_2" fontsize=8 shape=box3d tooltip="125.98MB"] +N3 -> NN3_2 [label=" 125.98MB" weight=100 tooltip="125.98MB" labeltooltip="125.98MB" style="dotted"] +NN3_3 [label = "128B" id="NN3_3" fontsize=8 shape=box3d tooltip="63.48MB"] +N3 -> NN3_3 [label=" 63.48MB" weight=100 tooltip="63.48MB" labeltooltip="63.48MB" style="dotted"] +N4 [label="line2000\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line2000 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] +N5 [label="line3001\n0 of 36.13MB (36.63%)" id="node5" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] +N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (63.48MB)" labeltooltip="line2000 -> line2001 (63.48MB)"] +N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (63.48MB)" labeltooltip="line3002 -> line2000 (63.48MB)" minlen=2] +N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"] +N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"] +N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"] +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_tags.traces b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_tags.traces new file mode 100644 index 0000000000..547aea74c3 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_tags.traces @@ -0,0 +1,32 @@ +Build ID: buildid +comment +Type: inuse_space +-----------+------------------------------------------------------- + key1: tag + bytes: 100kB + request: 100kB + 1000kB line1000 + line2001 + line2000 + line3002 + line3001 + line3000 +-----------+------------------------------------------------------- + bytes: 200kB + 3.91MB line1000 + line3001 + line3000 +-----------+------------------------------------------------------- + key1: tag + bytes: 1.56MB + request: 1.56MB + 62.50MB line2001 + line2000 + line3002 + line3000 +-----------+------------------------------------------------------- + bytes: 400kB + 31.25MB line3002 + line3001 + line3000 +-----------+------------------------------------------------------- diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.call_tree.text b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.call_tree.text new file mode 100644 index 0000000000..78a2298f95 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.call_tree.text @@ -0,0 +1,8 @@ +Showing nodes accounting for 1.12s, 100% of 1.12s total +Showing top 5 nodes out of 6 + flat flat% sum% cum cum% + 1.10s 98.21% 98.21% 1.10s 98.21% line1000 + 0.01s 0.89% 99.11% 1.01s 90.18% line2001 (inline) + 0.01s 0.89% 100% 1.02s 91.07% line3002 (inline) + 0 0% 100% 1.01s 90.18% line2000 + 0 0% 100% 1.12s 100% line3000 diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.text b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.text deleted file mode 100644 index 0807ed2325..0000000000 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.text +++ /dev/null @@ -1,8 +0,0 @@ -Showing nodes accounting for 1.12s, 100% of 1.12s total - flat flat% sum% cum cum% - 1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src - 0.01s 0.89% 99.11% 1.01s 90.18% line2001 testdata/file2000.src (inline) - 0.01s 0.89% 100% 1.02s 91.07% line3002 testdata/file3000.src (inline) - 0 0% 100% 1.01s 90.18% line2000 testdata/file2000.src - 0 0% 100% 1.12s 100% line3000 testdata/file3000.src - 0 0% 100% 1.11s 99.11% line3001 testdata/file3000.src (inline) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go new file mode 100644 index 0000000000..48f0fa1cfa --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go @@ -0,0 +1,965 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import "html/template" + +// addTemplates adds a set of template definitions to templates. +func addTemplates(templates *template.Template) { + template.Must(templates.Parse(` +{{define "css"}} +<style type="text/css"> +html { + height: 100%; + min-height: 100%; + margin: 0px; +} +body { + margin: 0px; + width: 100%; + height: 100%; + min-height: 100%; + overflow: hidden; +} +#graphcontainer { + display: flex; + flex-direction: column; + height: 100%; + min-height: 100%; + width: 100%; + min-width: 100%; + margin: 0px; +} +#graph { + flex: 1 1 auto; + overflow: hidden; +} +svg { + width: 100%; + height: auto; +} +button { + margin-top: 5px; + margin-bottom: 5px; +} +#detailtext { + display: none; + position: fixed; + top: 20px; + right: 10px; + background-color: #ffffff; + min-width: 160px; + border: 1px solid #888; + box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2); + z-index: 1; +} +#closedetails { + float: right; + margin: 2px; +} +#home { + font-size: 14pt; + padding-left: 0.5em; + padding-right: 0.5em; + float: right; +} +.menubar { + display: inline-block; + background-color: #f8f8f8; + border: 1px solid #ccc; + width: 100%; +} +.menu-header { + position: relative; + display: inline-block; + padding: 2px 2px; + font-size: 14pt; +} +.menu { + display: none; + position: absolute; + background-color: #f8f8f8; + border: 1px solid #888; + box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2); + z-index: 1; + margin-top: 2px; + left: 0px; + min-width: 5em; +} +.menu-header, .menu { + cursor: default; + user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} +.menu hr { + background-color: #fff; + margin-top: 0px; + margin-bottom: 0px; +} +.menu a, .menu button { + display: block; + width: 100%; + margin: 0px; + padding: 2px 0px 2px 0px; + text-align: left; + text-decoration: none; + color: #000; + background-color: #f8f8f8; + font-size: 12pt; + border: none; +} +.menu-header:hover { + background-color: #ccc; +} +.menu a:hover, .menu button:hover { + background-color: #ccc; +} +.menu a.disabled { + color: gray; + pointer-events: none; +} +#searchbox { + margin-left: 10pt; +} +#bodycontainer { + width: 100%; + height: 100%; + max-height: 100%; + overflow: scroll; + padding-top: 5px; +} +#toptable { + border-spacing: 0px; + width: 100%; + padding-bottom: 1em; +} +#toptable tr th { + border-bottom: 1px solid black; + text-align: right; + padding-left: 1em; + padding-top: 0.2em; + padding-bottom: 0.2em; +} +#toptable tr td { + padding-left: 1em; + font: monospace; + text-align: right; + white-space: nowrap; + cursor: default; +} +#toptable tr th:nth-child(6), +#toptable tr th:nth-child(7), +#toptable tr td:nth-child(6), +#toptable tr td:nth-child(7) { + text-align: left; +} +#toptable tr td:nth-child(6) { + max-width: 30em; // Truncate very long names + overflow: hidden; +} +#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr { + cursor: ns-resize; +} +.hilite { + background-color: #ccf; +} +</style> +{{end}} + +{{define "header"}} +<div id="detailtext"> +<button id="closedetails">Close</button> +{{range .Legend}}<div>{{.}}</div>{{end}} +</div> + +<div class="menubar"> + +<div class="menu-header"> +View +<div class="menu"> +<a title="{{.Help.top}}" href="/top" id="topbtn">Top</a> +<a title="{{.Help.graph}}" href="/" id="graphbtn">Graph</a> +<a title="{{.Help.peek}}" href="/peek" id="peek">Peek</a> +<a title="{{.Help.list}}" href="/source" id="list">Source</a> +<a title="{{.Help.disasm}}" href="/disasm" id="disasm">Disassemble</a> +<hr> +<button title="{{.Help.details}}" id="details">Details</button> +</div> +</div> + +<div class="menu-header"> +Refine +<div class="menu"> +<a title="{{.Help.focus}}" href="{{.BaseURL}}" id="focus">Focus</a> +<a title="{{.Help.ignore}}" href="{{.BaseURL}}" id="ignore">Ignore</a> +<a title="{{.Help.hide}}" href="{{.BaseURL}}" id="hide">Hide</a> +<a title="{{.Help.show}}" href="{{.BaseURL}}" id="show">Show</a> +<hr> +<a title="{{.Help.reset}}" href="{{.BaseURL}}">Reset</a> +</div> +</div> + +<input id="searchbox" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40> + +<span id="home">{{.Title}}</span> + +</div> <!-- menubar --> + +<div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div> +{{end}} + +{{define "graph" -}} +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>{{.Title}}</title> +{{template "css" .}} +</head> +<body> + +{{template "header" .}} +<div id="graphcontainer"> +<div id="graph"> +{{.HTMLBody}} +</div> + +</div> +{{template "script" .}} +<script>viewer({{.BaseURL}}, {{.Nodes}})</script> +</body> +</html> +{{end}} + +{{define "script"}} +<script> +// Make svg pannable and zoomable. +// Call clickHandler(t) if a click event is caught by the pan event handlers. +function initPanAndZoom(svg, clickHandler) { + 'use strict'; + + // Current mouse/touch handling mode + const IDLE = 0 + const MOUSEPAN = 1 + const TOUCHPAN = 2 + const TOUCHZOOM = 3 + let mode = IDLE + + // State needed to implement zooming. + let currentScale = 1.0 + const initWidth = svg.viewBox.baseVal.width + const initHeight = svg.viewBox.baseVal.height + + // State needed to implement panning. + let panLastX = 0 // Last event X coordinate + let panLastY = 0 // Last event Y coordinate + let moved = false // Have we seen significant movement + let touchid = null // Current touch identifier + + // State needed for pinch zooming + let touchid2 = null // Second id for pinch zooming + let initGap = 1.0 // Starting gap between two touches + let initScale = 1.0 // currentScale when pinch zoom started + let centerPoint = null // Center point for scaling + + // Convert event coordinates to svg coordinates. + function toSvg(x, y) { + const p = svg.createSVGPoint() + p.x = x + p.y = y + let m = svg.getCTM() + if (m == null) m = svg.getScreenCTM() // Firefox workaround. + return p.matrixTransform(m.inverse()) + } + + // Change the scaling for the svg to s, keeping the point denoted + // by u (in svg coordinates]) fixed at the same screen location. + function rescale(s, u) { + // Limit to a good range. + if (s < 0.2) s = 0.2 + if (s > 10.0) s = 10.0 + + currentScale = s + + // svg.viewBox defines the visible portion of the user coordinate + // system. So to magnify by s, divide the visible portion by s, + // which will then be stretched to fit the viewport. + const vb = svg.viewBox + const w1 = vb.baseVal.width + const w2 = initWidth / s + const h1 = vb.baseVal.height + const h2 = initHeight / s + vb.baseVal.width = w2 + vb.baseVal.height = h2 + + // We also want to adjust vb.baseVal.x so that u.x remains at same + // screen X coordinate. In other words, want to change it from x1 to x2 + // so that: + // (u.x - x1) / w1 = (u.x - x2) / w2 + // Simplifying that, we get + // (u.x - x1) * (w2 / w1) = u.x - x2 + // x2 = u.x - (u.x - x1) * (w2 / w1) + vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1) + vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1) + } + + function handleWheel(e) { + if (e.deltaY == 0) return + // Change scale factor by 1.1 or 1/1.1 + rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)), + toSvg(e.offsetX, e.offsetY)) + } + + function setMode(m) { + mode = m + touchid = null + touchid2 = null + } + + function panStart(x, y) { + moved = false + panLastX = x + panLastY = y + } + + function panMove(x, y) { + let dx = x - panLastX + let dy = y - panLastY + if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return // Ignore tiny moves + + moved = true + panLastX = x + panLastY = y + + // Firefox workaround: get dimensions from parentNode. + const swidth = svg.clientWidth || svg.parentNode.clientWidth + const sheight = svg.clientHeight || svg.parentNode.clientHeight + + // Convert deltas from screen space to svg space. + dx *= (svg.viewBox.baseVal.width / swidth) + dy *= (svg.viewBox.baseVal.height / sheight) + + svg.viewBox.baseVal.x -= dx + svg.viewBox.baseVal.y -= dy + } + + function handleScanStart(e) { + if (e.button != 0) return // Do not catch right-clicks etc. + setMode(MOUSEPAN) + panStart(e.clientX, e.clientY) + e.preventDefault() + svg.addEventListener("mousemove", handleScanMove) + } + + function handleScanMove(e) { + if (e.buttons == 0) { + // Missed an end event, perhaps because mouse moved outside window. + setMode(IDLE) + svg.removeEventListener("mousemove", handleScanMove) + return + } + if (mode == MOUSEPAN) panMove(e.clientX, e.clientY) + } + + function handleScanEnd(e) { + if (mode == MOUSEPAN) panMove(e.clientX, e.clientY) + setMode(IDLE) + svg.removeEventListener("mousemove", handleScanMove) + if (!moved) clickHandler(e.target) + } + + // Find touch object with specified identifier. + function findTouch(tlist, id) { + for (const t of tlist) { + if (t.identifier == id) return t + } + return null + } + + // Return distance between two touch points + function touchGap(t1, t2) { + const dx = t1.clientX - t2.clientX + const dy = t1.clientY - t2.clientY + return Math.hypot(dx, dy) + } + + function handleTouchStart(e) { + if (mode == IDLE && e.changedTouches.length == 1) { + // Start touch based panning + const t = e.changedTouches[0] + setMode(TOUCHPAN) + touchid = t.identifier + panStart(t.clientX, t.clientY) + e.preventDefault() + } else if (mode == TOUCHPAN && e.touches.length == 2) { + // Start pinch zooming + setMode(TOUCHZOOM) + const t1 = e.touches[0] + const t2 = e.touches[1] + touchid = t1.identifier + touchid2 = t2.identifier + initScale = currentScale + initGap = touchGap(t1, t2) + centerPoint = toSvg((t1.clientX + t2.clientX) / 2, + (t1.clientY + t2.clientY) / 2) + e.preventDefault() + } + } + + function handleTouchMove(e) { + if (mode == TOUCHPAN) { + const t = findTouch(e.changedTouches, touchid) + if (t == null) return + if (e.touches.length != 1) { + setMode(IDLE) + return + } + panMove(t.clientX, t.clientY) + e.preventDefault() + } else if (mode == TOUCHZOOM) { + // Get two touches; new gap; rescale to ratio. + const t1 = findTouch(e.touches, touchid) + const t2 = findTouch(e.touches, touchid2) + if (t1 == null || t2 == null) return + const gap = touchGap(t1, t2) + rescale(initScale * gap / initGap, centerPoint) + e.preventDefault() + } + } + + function handleTouchEnd(e) { + if (mode == TOUCHPAN) { + const t = findTouch(e.changedTouches, touchid) + if (t == null) return + panMove(t.clientX, t.clientY) + setMode(IDLE) + e.preventDefault() + if (!moved) clickHandler(t.target) + } else if (mode == TOUCHZOOM) { + setMode(IDLE) + e.preventDefault() + } + } + + svg.addEventListener("mousedown", handleScanStart) + svg.addEventListener("mouseup", handleScanEnd) + svg.addEventListener("touchstart", handleTouchStart) + svg.addEventListener("touchmove", handleTouchMove) + svg.addEventListener("touchend", handleTouchEnd) + svg.addEventListener("wheel", handleWheel, true) +} + +function initMenus() { + 'use strict'; + + let activeMenu = null; + let activeMenuHdr = null; + + function cancelActiveMenu() { + if (activeMenu == null) return; + activeMenu.style.display = "none"; + activeMenu = null; + activeMenuHdr = null; + } + + // Set click handlers on every menu header. + for (const menu of document.getElementsByClassName("menu")) { + const hdr = menu.parentElement; + if (hdr == null) return; + function showMenu(e) { + // menu is a child of hdr, so this event can fire for clicks + // inside menu. Ignore such clicks. + if (e.target != hdr) return; + activeMenu = menu; + activeMenuHdr = hdr; + menu.style.display = "block"; + } + hdr.addEventListener("mousedown", showMenu); + hdr.addEventListener("touchstart", showMenu); + } + + // If there is an active menu and a down event outside, retract the menu. + for (const t of ["mousedown", "touchstart"]) { + document.addEventListener(t, (e) => { + // Note: to avoid unnecessary flicker, if the down event is inside + // the active menu header, do not retract the menu. + if (activeMenuHdr != e.target.closest(".menu-header")) { + cancelActiveMenu(); + } + }, { passive: true, capture: true }); + } + + // If there is an active menu and an up event inside, retract the menu. + document.addEventListener("mouseup", (e) => { + if (activeMenu == e.target.closest(".menu")) { + cancelActiveMenu(); + } + }, { passive: true, capture: true }); +} + +function viewer(baseUrl, nodes) { + 'use strict'; + + // Elements + const search = document.getElementById("searchbox") + const graph0 = document.getElementById("graph0") + const svg = (graph0 == null ? null : graph0.parentElement) + const toptable = document.getElementById("toptable") + + let regexpActive = false + let selected = new Map() + let origFill = new Map() + let searchAlarm = null + let buttonsEnabled = true + + function handleDetails() { + const detailsText = document.getElementById("detailtext") + if (detailsText != null) detailsText.style.display = "block" + } + + function handleCloseDetails() { + const detailsText = document.getElementById("detailtext") + if (detailsText != null) detailsText.style.display = "none" + } + + function handleKey(e) { + if (e.keyCode != 13) return + window.location.href = + updateUrl(new URL({{.BaseURL}}, window.location.href), "f") + e.preventDefault() + } + + function handleSearch() { + // Delay expensive processing so a flurry of key strokes is handled once. + if (searchAlarm != null) { + clearTimeout(searchAlarm) + } + searchAlarm = setTimeout(selectMatching, 300) + + regexpActive = true + updateButtons() + } + + function selectMatching() { + searchAlarm = null + let re = null + if (search.value != "") { + try { + re = new RegExp(search.value) + } catch (e) { + // TODO: Display error state in search box + return + } + } + + function match(text) { + return re != null && re.test(text) + } + + // drop currently selected items that do not match re. + selected.forEach(function(v, n) { + if (!match(nodes[n])) { + unselect(n, document.getElementById("node" + n)) + } + }) + + // add matching items that are not currently selected. + for (let n = 0; n < nodes.length; n++) { + if (!selected.has(n) && match(nodes[n])) { + select(n, document.getElementById("node" + n)) + } + } + + updateButtons() + } + + function toggleSvgSelect(elem) { + // Walk up to immediate child of graph0 + while (elem != null && elem.parentElement != graph0) { + elem = elem.parentElement + } + if (!elem) return + + // Disable regexp mode. + regexpActive = false + + const n = nodeId(elem) + if (n < 0) return + if (selected.has(n)) { + unselect(n, elem) + } else { + select(n, elem) + } + updateButtons() + } + + function unselect(n, elem) { + if (elem == null) return + selected.delete(n) + setBackground(elem, false) + } + + function select(n, elem) { + if (elem == null) return + selected.set(n, true) + setBackground(elem, true) + } + + function nodeId(elem) { + const id = elem.id + if (!id) return -1 + if (!id.startsWith("node")) return -1 + const n = parseInt(id.slice(4), 10) + if (isNaN(n)) return -1 + if (n < 0 || n >= nodes.length) return -1 + return n + } + + function setBackground(elem, set) { + // Handle table row highlighting. + if (elem.nodeName == "TR") { + elem.classList.toggle("hilite", set) + return + } + + // Handle svg element highlighting. + const p = findPolygon(elem) + if (p != null) { + if (set) { + origFill.set(p, p.style.fill) + p.style.fill = "#ccccff" + } else if (origFill.has(p)) { + p.style.fill = origFill.get(p) + } + } + } + + function findPolygon(elem) { + if (elem.localName == "polygon") return elem + for (const c of elem.children) { + const p = findPolygon(c) + if (p != null) return p + } + return null + } + + // convert a string to a regexp that matches that string. + function quotemeta(str) { + return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1') + } + + // Update id's href to reflect current selection whenever it is + // liable to be followed. + function makeLinkDynamic(id) { + const elem = document.getElementById(id) + if (elem == null) return + + // Most links copy current selection into the "f" parameter, + // but Refine menu links are different. + let param = "f" + if (id == "ignore") param = "i" + if (id == "hide") param = "h" + if (id == "show") param = "s" + + // We update on mouseenter so middle-click/right-click work properly. + elem.addEventListener("mouseenter", updater) + elem.addEventListener("touchstart", updater) + + function updater() { + elem.href = updateUrl(new URL(elem.href), param) + } + } + + // Update URL to reflect current selection. + function updateUrl(url, param) { + url.hash = "" + + // The selection can be in one of two modes: regexp-based or + // list-based. Construct regular expression depending on mode. + let re = regexpActive + ? search.value + : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join("|") + + // Copy params from this page's URL. + const params = url.searchParams + for (const p of new URLSearchParams(window.location.search)) { + params.set(p[0], p[1]) + } + + if (re != "") { + // For focus/show, forget old parameter. For others, add to re. + if (param != "f" && param != "s" && params.has(param)) { + const old = params.get(param) + if (old != "") { + re += "|" + old + } + } + params.set(param, re) + } else { + params.delete(param) + } + + return url.toString() + } + + function handleTopClick(e) { + // Walk back until we find TR and then get the Name column (index 5) + let elem = e.target + while (elem != null && elem.nodeName != "TR") { + elem = elem.parentElement + } + if (elem == null || elem.children.length < 6) return + + e.preventDefault() + const tr = elem + const td = elem.children[5] + if (td.nodeName != "TD") return + const name = td.innerText + const index = nodes.indexOf(name) + if (index < 0) return + + // Disable regexp mode. + regexpActive = false + + if (selected.has(index)) { + unselect(index, elem) + } else { + select(index, elem) + } + updateButtons() + } + + function updateButtons() { + const enable = (search.value != "" || selected.size != 0) + if (buttonsEnabled == enable) return + buttonsEnabled = enable + for (const id of ["focus", "ignore", "hide", "show"]) { + const link = document.getElementById(id) + if (link != null) { + link.classList.toggle("disabled", !enable) + } + } + } + + // Initialize button states + updateButtons() + + // Setup event handlers + initMenus() + if (svg != null) { + initPanAndZoom(svg, toggleSvgSelect) + } + if (toptable != null) { + toptable.addEventListener("mousedown", handleTopClick) + toptable.addEventListener("touchstart", handleTopClick) + } + + const ids = ["topbtn", "graphbtn", "peek", "list", "disasm", + "focus", "ignore", "hide", "show"] + ids.forEach(makeLinkDynamic) + + // Bind action to button with specified id. + function addAction(id, action) { + const btn = document.getElementById(id) + if (btn != null) { + btn.addEventListener("click", action) + btn.addEventListener("touchstart", action) + } + } + + addAction("details", handleDetails) + addAction("closedetails", handleCloseDetails) + + search.addEventListener("input", handleSearch) + search.addEventListener("keydown", handleKey) + + // Give initial focus to main container so it can be scrolled using keys. + const main = document.getElementById("bodycontainer") + if (main) { + main.focus() + } +} +</script> +{{end}} + +{{define "top" -}} +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>{{.Title}}</title> +{{template "css" .}} +<style type="text/css"> +</style> +</head> +<body> + +{{template "header" .}} + +<div id="bodycontainer"> +<table id="toptable"> +<tr> +<th id="flathdr1">Flat +<th id="flathdr2">Flat% +<th>Sum% +<th id="cumhdr1">Cum +<th id="cumhdr2">Cum% +<th id="namehdr">Name +<th>Inlined?</tr> +<tbody id="rows"> +</tbody> +</table> +</div> + +{{template "script" .}} +<script> +function makeTopTable(total, entries) { + const rows = document.getElementById("rows") + if (rows == null) return + + // Store initial index in each entry so we have stable node ids for selection. + for (let i = 0; i < entries.length; i++) { + entries[i].Id = "node" + i + } + + // Which column are we currently sorted by and in what order? + let currentColumn = "" + let descending = false + sortBy("Flat") + + function sortBy(column) { + // Update sort criteria + if (column == currentColumn) { + descending = !descending // Reverse order + } else { + currentColumn = column + descending = (column != "Name") + } + + // Sort according to current criteria. + function cmp(a, b) { + const av = a[currentColumn] + const bv = b[currentColumn] + if (av < bv) return -1 + if (av > bv) return +1 + return 0 + } + entries.sort(cmp) + if (descending) entries.reverse() + + function addCell(tr, val) { + const td = document.createElement('td') + td.textContent = val + tr.appendChild(td) + } + + function percent(v) { + return (v * 100.0 / total).toFixed(2) + "%" + } + + // Generate rows + const fragment = document.createDocumentFragment() + let sum = 0 + for (const row of entries) { + const tr = document.createElement('tr') + tr.id = row.Id + sum += row.Flat + addCell(tr, row.FlatFormat) + addCell(tr, percent(row.Flat)) + addCell(tr, percent(sum)) + addCell(tr, row.CumFormat) + addCell(tr, percent(row.Cum)) + addCell(tr, row.Name) + addCell(tr, row.InlineLabel) + fragment.appendChild(tr) + } + + rows.textContent = '' // Remove old rows + rows.appendChild(fragment) + } + + // Make different column headers trigger sorting. + function bindSort(id, column) { + const hdr = document.getElementById(id) + if (hdr == null) return + const fn = function() { sortBy(column) } + hdr.addEventListener("click", fn) + hdr.addEventListener("touch", fn) + } + bindSort("flathdr1", "Flat") + bindSort("flathdr2", "Flat") + bindSort("cumhdr1", "Cum") + bindSort("cumhdr2", "Cum") + bindSort("namehdr", "Name") +} + +viewer({{.BaseURL}}, {{.Nodes}}) +makeTopTable({{.Total}}, {{.Top}}) +</script> +</body> +</html> +{{end}} + +{{define "sourcelisting" -}} +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>{{.Title}}</title> +{{template "css" .}} +{{template "weblistcss" .}} +{{template "weblistjs" .}} +</head> +<body> + +{{template "header" .}} + +<div id="bodycontainer"> +{{.HTMLBody}} +</div> + +{{template "script" .}} +<script>viewer({{.BaseURL}}, null)</script> +</body> +</html> +{{end}} + +{{define "plaintext" -}} +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>{{.Title}}</title> +{{template "css" .}} +</head> +<body> + +{{template "header" .}} + +<div id="bodycontainer"> +<pre> +{{.TextBody}} +</pre> +</div> + +{{template "script" .}} +<script>viewer({{.BaseURL}}, null)</script> +</body> +</html> +{{end}} +`)) +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go new file mode 100644 index 0000000000..67ae262882 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go @@ -0,0 +1,393 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "bytes" + "fmt" + "html/template" + "net" + "net/http" + gourl "net/url" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/google/pprof/internal/graph" + "github.com/google/pprof/internal/plugin" + "github.com/google/pprof/internal/report" + "github.com/google/pprof/profile" +) + +// webInterface holds the state needed for serving a browser based interface. +type webInterface struct { + prof *profile.Profile + options *plugin.Options + help map[string]string + templates *template.Template +} + +func makeWebInterface(p *profile.Profile, opt *plugin.Options) *webInterface { + templates := template.New("templategroup") + addTemplates(templates) + report.AddSourceTemplates(templates) + return &webInterface{ + prof: p, + options: opt, + help: make(map[string]string), + templates: templates, + } +} + +// maxEntries is the maximum number of entries to print for text interfaces. +const maxEntries = 50 + +// errorCatcher is a UI that captures errors for reporting to the browser. +type errorCatcher struct { + plugin.UI + errors []string +} + +func (ec *errorCatcher) PrintErr(args ...interface{}) { + ec.errors = append(ec.errors, strings.TrimSuffix(fmt.Sprintln(args...), "\n")) + ec.UI.PrintErr(args...) +} + +// webArgs contains arguments passed to templates in webhtml.go. +type webArgs struct { + BaseURL string + Title string + Errors []string + Total int64 + Legend []string + Help map[string]string + Nodes []string + HTMLBody template.HTML + TextBody string + Top []report.TextItem +} + +func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error { + host, portStr, err := net.SplitHostPort(hostport) + if err != nil { + return fmt.Errorf("could not split http address: %v", err) + } + port, err := strconv.Atoi(portStr) + if err != nil { + return fmt.Errorf("invalid port number: %v", err) + } + if host == "" { + host = "localhost" + } + + interactiveMode = true + ui := makeWebInterface(p, o) + for n, c := range pprofCommands { + ui.help[n] = c.description + } + for n, v := range pprofVariables { + ui.help[n] = v.help + } + ui.help["details"] = "Show information about the profile and this view" + ui.help["graph"] = "Display profile as a directed graph" + ui.help["reset"] = "Show the entire profile" + + server := o.HTTPServer + if server == nil { + server = defaultWebServer + } + args := &plugin.HTTPServerArgs{ + Hostport: net.JoinHostPort(host, portStr), + Host: host, + Port: port, + Handlers: map[string]http.Handler{ + "/": http.HandlerFunc(ui.dot), + "/top": http.HandlerFunc(ui.top), + "/disasm": http.HandlerFunc(ui.disasm), + "/source": http.HandlerFunc(ui.source), + "/peek": http.HandlerFunc(ui.peek), + }, + } + + go openBrowser("http://"+args.Hostport, o) + return server(args) +} + +func defaultWebServer(args *plugin.HTTPServerArgs) error { + ln, err := net.Listen("tcp", args.Hostport) + if err != nil { + return err + } + isLocal := isLocalhost(args.Host) + handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if isLocal { + // Only allow local clients + host, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil || !isLocalhost(host) { + http.Error(w, "permission denied", http.StatusForbidden) + return + } + } + h := args.Handlers[req.URL.Path] + if h == nil { + // Fall back to default behavior + h = http.DefaultServeMux + } + h.ServeHTTP(w, req) + }) + s := &http.Server{Handler: handler} + return s.Serve(ln) +} + +func isLocalhost(host string) bool { + for _, v := range []string{"localhost", "127.0.0.1", "[::1]", "::1"} { + if host == v { + return true + } + } + return false +} + +func openBrowser(url string, o *plugin.Options) { + // Construct URL. + u, _ := gourl.Parse(url) + q := u.Query() + for _, p := range []struct{ param, key string }{ + {"f", "focus"}, + {"s", "show"}, + {"i", "ignore"}, + {"h", "hide"}, + } { + if v := pprofVariables[p.key].value; v != "" { + q.Set(p.param, v) + } + } + u.RawQuery = q.Encode() + + // Give server a little time to get ready. + time.Sleep(time.Millisecond * 500) + + for _, b := range browsers() { + args := strings.Split(b, " ") + if len(args) == 0 { + continue + } + viewer := exec.Command(args[0], append(args[1:], u.String())...) + viewer.Stderr = os.Stderr + if err := viewer.Start(); err == nil { + return + } + } + // No visualizer succeeded, so just print URL. + o.UI.PrintErr(u.String()) +} + +func varsFromURL(u *gourl.URL) variables { + vars := pprofVariables.makeCopy() + vars["focus"].value = u.Query().Get("f") + vars["show"].value = u.Query().Get("s") + vars["ignore"].value = u.Query().Get("i") + vars["hide"].value = u.Query().Get("h") + return vars +} + +// makeReport generates a report for the specified command. +func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request, + cmd []string, vars ...string) (*report.Report, []string) { + v := varsFromURL(req.URL) + for i := 0; i+1 < len(vars); i += 2 { + v[vars[i]].value = vars[i+1] + } + catcher := &errorCatcher{UI: ui.options.UI} + options := *ui.options + options.UI = catcher + _, rpt, err := generateRawReport(ui.prof, cmd, v, &options) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + ui.options.UI.PrintErr(err) + return nil, nil + } + return rpt, catcher.errors +} + +// render generates html using the named template based on the contents of data. +func (ui *webInterface) render(w http.ResponseWriter, baseURL, tmpl string, + rpt *report.Report, errList, legend []string, data webArgs) { + file := getFromLegend(legend, "File: ", "unknown") + profile := getFromLegend(legend, "Type: ", "unknown") + data.BaseURL = baseURL + data.Title = file + " " + profile + data.Errors = errList + data.Total = rpt.Total() + data.Legend = legend + data.Help = ui.help + html := &bytes.Buffer{} + if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil { + http.Error(w, "internal template error", http.StatusInternalServerError) + ui.options.UI.PrintErr(err) + return + } + w.Header().Set("Content-Type", "text/html") + w.Write(html.Bytes()) +} + +// dot generates a web page containing an svg diagram. +func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) { + rpt, errList := ui.makeReport(w, req, []string{"svg"}) + if rpt == nil { + return // error already reported + } + + // Generate dot graph. + g, config := report.GetDOT(rpt) + legend := config.Labels + config.Labels = nil + dot := &bytes.Buffer{} + graph.ComposeDot(dot, g, &graph.DotAttributes{}, config) + + // Convert to svg. + svg, err := dotToSvg(dot.Bytes()) + if err != nil { + http.Error(w, "Could not execute dot; may need to install graphviz.", + http.StatusNotImplemented) + ui.options.UI.PrintErr("Failed to execute dot. Is Graphviz installed?\n", err) + return + } + + // Get all node names into an array. + nodes := []string{""} // dot starts with node numbered 1 + for _, n := range g.Nodes { + nodes = append(nodes, n.Info.Name) + } + + ui.render(w, "/", "graph", rpt, errList, legend, webArgs{ + HTMLBody: template.HTML(string(svg)), + Nodes: nodes, + }) +} + +func dotToSvg(dot []byte) ([]byte, error) { + cmd := exec.Command("dot", "-Tsvg") + out := &bytes.Buffer{} + cmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(dot), out, os.Stderr + if err := cmd.Run(); err != nil { + return nil, err + } + + // Fix dot bug related to unquoted amperands. + svg := bytes.Replace(out.Bytes(), []byte("&;"), []byte("&;"), -1) + + // Cleanup for embedding by dropping stuff before the <svg> start. + if pos := bytes.Index(svg, []byte("<svg")); pos >= 0 { + svg = svg[pos:] + } + return svg, nil +} + +func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) { + rpt, errList := ui.makeReport(w, req, []string{"top"}, "nodecount", "500") + if rpt == nil { + return // error already reported + } + top, legend := report.TextItems(rpt) + var nodes []string + for _, item := range top { + nodes = append(nodes, item.Name) + } + + ui.render(w, "/top", "top", rpt, errList, legend, webArgs{ + Top: top, + Nodes: nodes, + }) +} + +// disasm generates a web page containing disassembly. +func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) { + args := []string{"disasm", req.URL.Query().Get("f")} + rpt, errList := ui.makeReport(w, req, args) + if rpt == nil { + return // error already reported + } + + out := &bytes.Buffer{} + if err := report.PrintAssembly(out, rpt, ui.options.Obj, maxEntries); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + ui.options.UI.PrintErr(err) + return + } + + legend := report.ProfileLabels(rpt) + ui.render(w, "/disasm", "plaintext", rpt, errList, legend, webArgs{ + TextBody: out.String(), + }) + +} + +// source generates a web page containing source code annotated with profile +// data. +func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) { + args := []string{"weblist", req.URL.Query().Get("f")} + rpt, errList := ui.makeReport(w, req, args) + if rpt == nil { + return // error already reported + } + + // Generate source listing. + var body bytes.Buffer + if err := report.PrintWebList(&body, rpt, ui.options.Obj, maxEntries); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + ui.options.UI.PrintErr(err) + return + } + + legend := report.ProfileLabels(rpt) + ui.render(w, "/source", "sourcelisting", rpt, errList, legend, webArgs{ + HTMLBody: template.HTML(body.String()), + }) +} + +// peek generates a web page listing callers/callers. +func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) { + args := []string{"peek", req.URL.Query().Get("f")} + rpt, errList := ui.makeReport(w, req, args, "lines", "t") + if rpt == nil { + return // error already reported + } + + out := &bytes.Buffer{} + if err := report.Generate(out, rpt, ui.options.Obj); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + ui.options.UI.PrintErr(err) + return + } + + legend := report.ProfileLabels(rpt) + ui.render(w, "/peek", "plaintext", rpt, errList, legend, webArgs{ + TextBody: out.String(), + }) +} + +// getFromLegend returns the suffix of an entry in legend that starts +// with param. It returns def if no such entry is found. +func getFromLegend(legend []string, param, def string) string { + for _, s := range legend { + if strings.HasPrefix(s, param) { + return s[len(param):] + } + } + return def +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go new file mode 100644 index 0000000000..76565eb8ee --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go @@ -0,0 +1,232 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os/exec" + "regexp" + "sync" + "testing" + + "github.com/google/pprof/internal/plugin" + "github.com/google/pprof/profile" + "runtime" +) + +func TestWebInterface(t *testing.T) { + if runtime.GOOS == "nacl" { + t.Skip("test assumes tcp available") + } + + prof := makeFakeProfile() + + // Custom http server creator + var server *httptest.Server + serverCreated := make(chan bool) + creator := func(a *plugin.HTTPServerArgs) error { + server = httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if h := a.Handlers[r.URL.Path]; h != nil { + h.ServeHTTP(w, r) + } + })) + serverCreated <- true + return nil + } + + // Start server and wait for it to be initialized + go serveWebInterface("unused:1234", prof, &plugin.Options{ + Obj: fakeObjTool{}, + UI: &stdUI{}, + HTTPServer: creator, + }) + <-serverCreated + defer server.Close() + + haveDot := false + if _, err := exec.LookPath("dot"); err == nil { + haveDot = true + } + + type testCase struct { + path string + want []string + needDot bool + } + testcases := []testCase{ + {"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true}, + {"/top", []string{`"Name":"F2","InlineLabel":"","Flat":200,"Cum":300,"FlatFormat":"200ms","CumFormat":"300ms"}`}, false}, + {"/source?f=" + url.QueryEscape("F[12]"), + []string{"F1", "F2", "300ms +line1"}, false}, + {"/peek?f=" + url.QueryEscape("F[12]"), + []string{"300ms.*F1", "200ms.*300ms.*F2"}, false}, + {"/disasm?f=" + url.QueryEscape("F[12]"), + []string{"f1:asm", "f2:asm"}, false}, + } + for _, c := range testcases { + if c.needDot && !haveDot { + t.Log("skipping", c.path, "since dot (graphviz) does not seem to be installed") + continue + } + + res, err := http.Get(server.URL + c.path) + if err != nil { + t.Error("could not fetch", c.path, err) + continue + } + data, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Error("could not read response", c.path, err) + continue + } + result := string(data) + for _, w := range c.want { + if match, _ := regexp.MatchString(w, result); !match { + t.Errorf("response for %s does not match "+ + "expected pattern '%s'; "+ + "actual result:\n%s", c.path, w, result) + } + } + } + + // Also fetch all the test case URLs in parallel to test thread + // safety when run under the race detector. + var wg sync.WaitGroup + for _, c := range testcases { + if c.needDot && !haveDot { + continue + } + path := server.URL + c.path + for count := 0; count < 2; count++ { + wg.Add(1) + go func() { + http.Get(path) + wg.Done() + }() + } + } + wg.Wait() +} + +// Implement fake object file support. + +const addrBase = 0x1000 +const fakeSource = "testdata/file1000.src" + +type fakeObj struct{} + +func (f fakeObj) Close() error { return nil } +func (f fakeObj) Name() string { return "testbin" } +func (f fakeObj) Base() uint64 { return 0 } +func (f fakeObj) BuildID() string { return "" } +func (f fakeObj) SourceLine(addr uint64) ([]plugin.Frame, error) { + return nil, fmt.Errorf("SourceLine unimplemented") +} +func (f fakeObj) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { + return []*plugin.Sym{ + {[]string{"F1"}, fakeSource, addrBase, addrBase + 10}, + {[]string{"F2"}, fakeSource, addrBase + 10, addrBase + 20}, + {[]string{"F3"}, fakeSource, addrBase + 20, addrBase + 30}, + }, nil +} + +type fakeObjTool struct{} + +func (obj fakeObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) { + return fakeObj{}, nil +} + +func (obj fakeObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { + return []plugin.Inst{ + {Addr: addrBase + 0, Text: "f1:asm", Function: "F1"}, + {Addr: addrBase + 10, Text: "f2:asm", Function: "F2"}, + {Addr: addrBase + 20, Text: "d3:asm", Function: "F3"}, + }, nil +} + +func makeFakeProfile() *profile.Profile { + // Three functions: F1, F2, F3 with three lines, 11, 22, 33. + funcs := []*profile.Function{ + {ID: 1, Name: "F1", Filename: fakeSource, StartLine: 3}, + {ID: 2, Name: "F2", Filename: fakeSource, StartLine: 5}, + {ID: 3, Name: "F3", Filename: fakeSource, StartLine: 7}, + } + lines := []profile.Line{ + {Function: funcs[0], Line: 11}, + {Function: funcs[1], Line: 22}, + {Function: funcs[2], Line: 33}, + } + mapping := []*profile.Mapping{ + { + ID: 1, + Start: addrBase, + Limit: addrBase + 10, + Offset: 0, + File: "testbin", + HasFunctions: true, + HasFilenames: true, + HasLineNumbers: true, + }, + } + + // Three interesting addresses: base+{10,20,30} + locs := []*profile.Location{ + {ID: 1, Address: addrBase + 10, Line: lines[0:1], Mapping: mapping[0]}, + {ID: 2, Address: addrBase + 20, Line: lines[1:2], Mapping: mapping[0]}, + {ID: 3, Address: addrBase + 30, Line: lines[2:3], Mapping: mapping[0]}, + } + + // Two stack traces. + return &profile.Profile{ + PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, + Period: 1, + DurationNanos: 10e9, + SampleType: []*profile.ValueType{ + {Type: "cpu", Unit: "milliseconds"}, + }, + Sample: []*profile.Sample{ + { + Location: []*profile.Location{locs[2], locs[1], locs[0]}, + Value: []int64{100}, + }, + { + Location: []*profile.Location{locs[1], locs[0]}, + Value: []int64{200}, + }, + }, + Location: locs, + Function: funcs, + Mapping: mapping, + } +} + +func TestIsLocalHost(t *testing.T) { + for _, s := range []string{"localhost:10000", "[::1]:10000", "127.0.0.1:10000"} { + host, _, err := net.SplitHostPort(s) + if err != nil { + t.Error("unexpected error when splitting", s) + continue + } + if !isLocalhost(host) { + t.Errorf("host %s from %s not considered local", host, s) + } + } +} |
