aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/github.com/google/pprof/internal/driver
diff options
context:
space:
mode:
authorAlberto Donizetti <alb.donizetti@gmail.com>2017-08-20 12:27:32 +0200
committerBrad Fitzpatrick <bradfitz@golang.org>2017-11-02 23:51:45 +0000
commitaec345d638fa624f08b7d758e9e173897edc80e8 (patch)
treed782d951af4f34de34a08c4775a37f869af25b81 /src/cmd/vendor/github.com/google/pprof/internal/driver
parent3039bff9d07ce05dc9af8c155c6929ae5e53a231 (diff)
downloadgo-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')
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/cli.go82
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go33
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go71
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/driver_focus.go73
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go561
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go126
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go245
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go3
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.contention24
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/cppbench.small.contention19
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.cum.files.dot6
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore8
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.call_tree.callgrind99
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.comments1
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.focus.hide8
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.hide2
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.show2
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide2
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.disasm6
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist69
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.call_tree.dot21
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.dot30
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.text12
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.peek14
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags20
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags.focus.ignore8
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.traces32
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.lines.tree.focus2
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus22
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text.focus8
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_objects.text12
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus18
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore22
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.lines.dot.focus18
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags10
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags.unit10
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text12
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot14
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus26
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide12
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_request.tags.focus8
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_sizetags.dot30
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_tags.traces32
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.call_tree.text8
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.text8
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go965
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go393
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go232
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("&amp;;"), -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)
+ }
+ }
+}