diff options
author | Hana (Hyang-Ah) Kim <hyangah@gmail.com> | 2018-05-09 09:49:58 +0800 |
---|---|---|
committer | Hyang-Ah Hana Kim <hyangah@gmail.com> | 2018-05-09 16:42:28 +0000 |
commit | 46047e64479c30602f0fb42c6915638006d7335d (patch) | |
tree | 92664dc56d9e4c833da2a2a5d81afde43dbdbadc /src/cmd/vendor/github.com/google | |
parent | d540da105c799a8fa010ee83419d6cb24d6627b4 (diff) | |
download | go-git-46047e64479c30602f0fb42c6915638006d7335d.tar.gz |
cmd/vendor/.../pprof: update to 520140b6bf47519c766e8380e5f094576347b016
Change-Id: If431dfa59496b86f58f2ba2a83ca544a28a2a972
Reviewed-on: https://go-review.googlesource.com/112435
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/cmd/vendor/github.com/google')
32 files changed, 2037 insertions, 1111 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/README.md b/src/cmd/vendor/github.com/google/pprof/README.md index e8c2f0329d..81e5d3939d 100644 --- a/src/cmd/vendor/github.com/google/pprof/README.md +++ b/src/cmd/vendor/github.com/google/pprof/README.md @@ -102,8 +102,8 @@ pprof can read `perf.data` files generated by the ## Further documentation -See [doc/pprof.md](doc/pprof.md) for more detailed end-user documentation. +See [doc/README.md](doc/README.md) for more detailed end-user documentation. -See [doc/developer/pprof.dev.md](doc/developer/pprof.dev.md) for developer documentation. +See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution documentation. -See [doc/developer/profile.proto.md](doc/developer/profile.proto.md) for a description of the profile.proto format. +See [proto/README.md](proto/README.md) for a description of the profile.proto format. diff --git a/src/cmd/vendor/github.com/google/pprof/doc/pprof.md b/src/cmd/vendor/github.com/google/pprof/doc/README.md index 57613fad96..de7c393e78 100644 --- a/src/cmd/vendor/github.com/google/pprof/doc/pprof.md +++ b/src/cmd/vendor/github.com/google/pprof/doc/README.md @@ -94,6 +94,8 @@ Some common pprof options are: *regex*. * **-ignore= _regex_:** Do not include samples that include a report entry matching *regex*. +* **-show\_from= _regex_:** Do not show entries above the first one that + matches *regex*. * **-show= _regex_:** Only show entries that match *regex*. * **-hide= _regex_:** Do not show entries that match *regex*. diff --git a/src/cmd/vendor/github.com/google/pprof/doc/developer/pprof.dev.md b/src/cmd/vendor/github.com/google/pprof/doc/developer/pprof.dev.md deleted file mode 100644 index b2a197f669..0000000000 --- a/src/cmd/vendor/github.com/google/pprof/doc/developer/pprof.dev.md +++ /dev/null @@ -1,14 +0,0 @@ -This is pprof's developer documentation. It discusses how to maintain and extend -pprof. It has yet to be written. - -# How is pprof code structured? - -Internal vs external packages. - -# External interface - -## Plugins - -## Legacy formats - -# Overview of internal packages 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 e2e8c6c936..c3c22e7c96 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 @@ -316,5 +316,5 @@ var usageMsgVars = "\n\n" + " PPROF_TOOLS Search path for object-level tools\n" + " PPROF_BINARY_PATH Search path for local binary files\n" + " default: $HOME/pprof/binaries\n" + - " finds binaries by $name and $buildid/$name\n" + + " searches $name, $path, $buildid/$name, $path/$buildid\n" + " * On Windows, %USERPROFILE% is used instead of $HOME" 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 16b0b0a3b5..91d32d1e71 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 @@ -135,14 +135,6 @@ var pprofVariables = variables{ "Ignore negative differences", "Do not show any locations with values <0.")}, - // 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.")}, - // Graph handling options. "call_tree": &variable{boolKind, "f", "", helpText( "Create a context-sensitive call tree", @@ -161,6 +153,7 @@ var pprofVariables = variables{ "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"}, + "trim_path": &variable{stringKind, "", "", "Path to trim from source paths before search"}, // Filtering options "nodecount": &variable{intKind, "-1", "", helpText( @@ -193,6 +186,10 @@ var pprofVariables = variables{ "Only show nodes matching regexp", "If set, only show nodes that match this location.", "Matching includes the function name, filename or object name.")}, + "show_from": &variable{stringKind, "", "", helpText( + "Drops functions above the highest matched frame.", + "If set, all frames above the highest match are dropped from every sample.", + "Matching includes the function name, filename or object name.")}, "tagfocus": &variable{stringKind, "", "", helpText( "Restricts to samples with tags in range or matched by regexp", "Use name=value syntax to limit the matching to a specific tag.", 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 3b7439fc9a..acc0b4ad8a 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 @@ -150,11 +150,11 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin. } func applyCommandOverrides(cmd []string, v variables) variables { - trim, focus, tagfocus, hide := v["trim"].boolValue(), true, true, true + trim, tagfilter, filter := v["trim"].boolValue(), true, true switch cmd[0] { case "proto", "raw": - trim, focus, tagfocus, hide = false, false, false, false + trim, tagfilter, filter = false, false, false v.set("addresses", "t") case "callgrind", "kcachegrind": trim = false @@ -163,7 +163,7 @@ func applyCommandOverrides(cmd []string, v variables) variables { trim = false v.set("addressnoinlines", "t") case "peek": - trim, focus, hide = false, false, false + trim, filter = false, false case "list": v.set("nodecount", "0") v.set("lines", "t") @@ -181,17 +181,16 @@ func applyCommandOverrides(cmd []string, v variables) variables { v.set("nodefraction", "0") v.set("edgefraction", "0") } - if !focus { - v.set("focus", "") - v.set("ignore", "") - } - if !tagfocus { + if !tagfilter { v.set("tagfocus", "") v.set("tagignore", "") } - if !hide { + if !filter { + v.set("focus", "") + v.set("ignore", "") v.set("hide", "") v.set("show", "") + v.set("show_from", "") } return v } @@ -242,7 +241,7 @@ func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars var } var filters []string - for _, k := range []string{"focus", "ignore", "hide", "show", "tagfocus", "tagignore", "tagshow", "taghide"} { + for _, k := range []string{"focus", "ignore", "hide", "show", "show_from", "tagfocus", "tagignore", "tagshow", "taghide"} { v := vars[k].value if v != "" { filters = append(filters, k+"="+v) @@ -250,10 +249,9 @@ func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars var } ropt := &report.Options{ - CumSort: vars["cum"].boolValue(), - CallTree: vars["call_tree"].boolValue(), - DropNegative: vars["drop_negative"].boolValue(), - PositivePercentages: vars["positive_percentages"].boolValue(), + CumSort: vars["cum"].boolValue(), + CallTree: vars["call_tree"].boolValue(), + DropNegative: vars["drop_negative"].boolValue(), CompactLabels: vars["compact_labels"].boolValue(), Ratio: 1 / vars["divide_by"].floatValue(), @@ -273,6 +271,7 @@ func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars var OutputUnit: vars["unit"].value, SourcePath: vars["source_path"].stringValue(), + TrimPath: vars["trim_path"].stringValue(), } if len(p.Mapping) > 0 && p.Mapping[0].File != "" { 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 ba5b502ad9..b23ee81058 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 @@ -33,6 +33,7 @@ func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variab ignore, err := compileRegexOption("ignore", v["ignore"].value, err) hide, err := compileRegexOption("hide", v["hide"].value, err) show, err := compileRegexOption("show", v["show"].value, err) + showfrom, err := compileRegexOption("show_from", v["show_from"].value, 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) @@ -46,6 +47,9 @@ func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variab warnNoMatches(hide == nil || hm, "Hide", ui) warnNoMatches(show == nil || hnm, "Show", ui) + sfm := prof.ShowFrom(showfrom) + warnNoMatches(showfrom == nil || sfm, "ShowFrom", ui) + tfm, tim := prof.FilterSamplesByTag(tagfocus, tagignore) warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui) warnNoMatches(tagignore == nil || tim, "TagIgnore", ui) 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 06219eae64..309e9950b6 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 @@ -65,6 +65,7 @@ func TestParse(t *testing.T) { {"topproto,lines,cum,hide=mangled[X3]0", "cpu"}, {"tree,lines,cum,focus=[24]00", "heap"}, {"tree,relative_percentages,cum,focus=[24]00", "heap"}, + {"tree,lines,cum,show_from=line2", "cpu"}, {"callgrind", "cpu"}, {"callgrind,call_tree", "cpu"}, {"callgrind", "heap"}, @@ -261,6 +262,9 @@ func solutionFilename(source string, f *testFlags) string { if f.strings["ignore"] != "" || f.strings["tagignore"] != "" { name = append(name, "ignore") } + if f.strings["show_from"] != "" { + name = append(name, "show_from") + } name = addString(name, f, []string{"hide", "show"}) if f.strings["unit"] != "minimum" { name = addString(name, f, []string{"unit"}) 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 1b34e70bea..7c576de614 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 @@ -599,9 +599,9 @@ var httpGet = func(source string, timeout time.Duration) (*http.Response, error) client := &http.Client{ Transport: &http.Transport{ - ResponseHeaderTimeout: timeout + 5*time.Second, Proxy: http.ProxyFromEnvironment, TLSClientConfig: tlsConfig, + ResponseHeaderTimeout: timeout + 5*time.Second, }, } return client.Get(source) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go index 29a41011bb..c9b9a5398f 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go @@ -27,6 +27,7 @@ import ( type treeNode struct { Name string `json:"n"` + FullName string `json:"f"` Cum int64 `json:"v"` CumFormat string `json:"l"` Percent string `json:"p"` @@ -52,8 +53,10 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) { // Make all nodes and the map, collect the roots. for _, n := range g.Nodes { v := n.CumValue() + fullName := n.Info.PrintableName() node := &treeNode{ - Name: n.Info.PrintableName(), + Name: getNodeShortName(fullName), + FullName: fullName, Cum: v, CumFormat: config.FormatValue(v), Percent: strings.TrimSpace(measurement.Percentage(v, config.Total)), @@ -78,6 +81,7 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) { rootNode := &treeNode{ Name: "root", + FullName: "root", Cum: rootValue, CumFormat: config.FormatValue(rootValue), Percent: strings.TrimSpace(measurement.Percentage(rootValue, config.Total)), @@ -97,3 +101,19 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) { Nodes: nodeArr, }) } + +// getNodeShortName builds a short node name from fullName. +func getNodeShortName(name string) string { + chunks := strings.SplitN(name, "(", 2) + head := chunks[0] + pathSep := strings.LastIndexByte(head, '/') + if pathSep == -1 || pathSep+1 >= len(head) { + return name + } + // Check if name is a stdlib package, i.e. doesn't have "." before "/" + if dot := strings.IndexByte(head, '.'); dot == -1 || dot > pathSep { + return name + } + // Trim package path prefix from node name + return name[pathSep+1:] +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph_test.go new file mode 100644 index 0000000000..c1a887c830 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph_test.go @@ -0,0 +1,46 @@ +package driver + +import "testing" + +func TestGetNodeShortName(t *testing.T) { + type testCase struct { + name string + want string + } + testcases := []testCase{ + { + "root", + "root", + }, + { + "syscall.Syscall", + "syscall.Syscall", + }, + { + "net/http.(*conn).serve", + "net/http.(*conn).serve", + }, + { + "github.com/blah/foo.Foo", + "foo.Foo", + }, + { + "github.com/blah/foo_bar.(*FooBar).Foo", + "foo_bar.(*FooBar).Foo", + }, + { + "encoding/json.(*structEncoder).(encoding/json.encode)-fm", + "encoding/json.(*structEncoder).(encoding/json.encode)-fm", + }, + { + "github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm", + "redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm", + }, + } + for _, tc := range testcases { + name := getNodeShortName(tc.name) + if got, want := name, tc.want; got != want { + t.Errorf("for %s, got %q, want %q", tc.name, got, want) + } + } +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from new file mode 100644 index 0000000000..112b49b383 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from @@ -0,0 +1,16 @@ +Active filters: + show_from=line2 +Showing nodes accounting for 1.01s, 90.18% of 1.12s total +----------------------------------------------------------+------------- + flat flat% sum% cum cum% calls calls% + context +----------------------------------------------------------+------------- + 0 0% 0% 1.01s 90.18% | line2000 testdata/file2000.src:4 + 1.01s 100% | line2001 testdata/file2000.src:9 (inline) +----------------------------------------------------------+------------- + 1.01s 100% | line2000 testdata/file2000.src:4 (inline) + 0.01s 0.89% 0.89% 1.01s 90.18% | line2001 testdata/file2000.src:9 + 1s 99.01% | line1000 testdata/file1000.src:1 +----------------------------------------------------------+------------- + 1s 100% | line2001 testdata/file2000.src:9 + 1s 89.29% 90.18% 1s 89.29% | line1000 testdata/file1000.src:1 +----------------------------------------------------------+------------- 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 index e9bc872035..c3f9c384f8 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go @@ -17,13 +17,11 @@ package driver import "html/template" import "github.com/google/pprof/third_party/d3" -import "github.com/google/pprof/third_party/d3tip" import "github.com/google/pprof/third_party/d3flamegraph" // addTemplates adds a set of template definitions to templates. func addTemplates(templates *template.Template) { template.Must(templates.Parse(`{{define "d3script"}}` + d3.JSSource + `{{end}}`)) - template.Must(templates.Parse(`{{define "d3tipscript"}}` + d3tip.JSSource + `{{end}}`)) template.Must(templates.Parse(`{{define "d3flamegraphscript"}}` + d3flamegraph.JSSource + `{{end}}`)) template.Must(templates.Parse(`{{define "d3flamegraphcss"}}` + d3flamegraph.CSSSource + `{{end}}`)) template.Must(templates.Parse(` @@ -224,7 +222,7 @@ table tr td { cursor: ns-resize; } .hilite { - background-color: #ebf5fb; + background-color: #ebf5fb; font-weight: bold; } </style> @@ -1031,49 +1029,51 @@ function viewer(baseUrl, nodes) { width: 90%; min-width: 90%; margin-left: 5%; - padding-bottom: 41px; + padding: 15px 0 35px; } </style> </head> <body> {{template "header" .}} <div id="bodycontainer"> + <div id="flamegraphdetails" class="flamegraph-details"></div> <div class="flamegraph-content"> <div id="chart"></div> </div> - <div id="flamegraphdetails" class="flamegraph-details"></div> </div> {{template "script" .}} <script>viewer(new URL(window.location.href), {{.Nodes}});</script> <script>{{template "d3script" .}}</script> - <script>{{template "d3tipscript" .}}</script> <script>{{template "d3flamegraphscript" .}}</script> - <script type="text/javascript"> + <script> var data = {{.FlameGraph}}; - var label = function(d) { - return d.data.n + ' (' + d.data.p + ', ' + d.data.l + ')'; - }; var width = document.getElementById('chart').clientWidth; - var flameGraph = d3.flameGraph() + var flameGraph = d3.flamegraph() .width(width) .cellHeight(18) .minFrameSize(1) .transitionDuration(750) .transitionEase(d3.easeCubic) - .sort(true) + .inverted(true) .title('') - .label(label) + .tooltip(false) .details(document.getElementById('flamegraphdetails')); - var tip = d3.tip() - .direction('s') - .offset([8, 0]) - .attr('class', 'd3-flame-graph-tip') - .html(function(d) { return 'name: ' + d.data.n + ', value: ' + d.data.l; }); + // <full name> (percentage, value) + flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')'); + + (function(flameGraph) { + var oldColorMapper = flameGraph.color(); + function colorMapper(d) { + // Hack to force default color mapper to use 'warm' color scheme by not passing libtype + const { data, highlight } = d; + return oldColorMapper({ data: { n: data.n }, highlight }); + } - flameGraph.tooltip(tip); + flameGraph.color(colorMapper); + }(flameGraph)); d3.select('#chart') .datum(data) 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 index 328f1596d9..58681bea8f 100644 --- 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 @@ -33,7 +33,7 @@ import ( ) func TestWebInterface(t *testing.T) { - if runtime.GOOS == "nacl" { + if runtime.GOOS == "nacl" || runtime.GOOS == "js" { t.Skip("test assumes tcp available") } @@ -81,7 +81,7 @@ func TestWebInterface(t *testing.T) { []string{"300ms.*F1", "200ms.*300ms.*F2"}, false}, {"/disasm?f=" + url.QueryEscape("F[12]"), []string{"f1:asm", "f2:asm"}, false}, - {"/flamegraph", []string{"File: testbin", "\"n\":\"root\"", "\"n\":\"F1\"", "function tip", "function flameGraph", "function hierarchy"}, false}, + {"/flamegraph", []string{"File: testbin", "\"n\":\"root\"", "\"n\":\"F1\"", "var flamegraph = function", "function hierarchy"}, false}, } for _, c := range testcases { if c.needDot && !haveDot { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go index 297bb24b1c..4750ec8129 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go @@ -208,13 +208,13 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6 if loadSegment.Vaddr == start-offset { return offset, nil } - if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64) { + if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) { // Some kernels look like: // VADDR=0xffffffff80200000 // stextOffset=0xffffffff80200198 // Start=0xffffffff83200000 // Limit=0xffffffff84200000 - // Offset=0 (0xc000000000000000 for PowerPC64) + // Offset=0 (0xc000000000000000 for PowerPC64) (== Start for ASLR kernel) // So the base should be: if stextOffset != nil && (start%pageSize) == (*stextOffset%pageSize) { // perf uses the address of _stext as start. Some tools may diff --git a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec_test.go b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec_test.go index c6b8fe4c22..71edd8e51b 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec_test.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec_test.go @@ -55,7 +55,9 @@ func TestGetBase(t *testing.T) { {"exec offset 2", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false}, {"exec nomap", fhExec, nil, nil, 0, 0, 0, 0, false}, {"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false}, - {"exec PPC64 kernel", fhExec, ppc64KernelHeader, uint64p(0xc000000000000000), 0xc000000000000000, 0xd00000001a730000, 0xc000000000000000, 0x0, false}, + {"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0x0, 0x0, false}, + {"exec kernel ASLR", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0xffffffff81000000, 0x0, false}, + {"exec PPC64 kernel", fhExec, ppc64KernelHeader, uint64p(0xc000000000000000), 0xc000000000000000, 0xd00000001a730000, 0x0, 0x0, false}, {"exec chromeos kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10197, 0, 0x7efffe68, false}, {"exec chromeos kernel 2", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10198, 0, 0x7efffe68, false}, {"exec chromeos kernel 3", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0x198, 0x100000, 0, 0x7f000000, false}, diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go index e127f7fcec..15cadfb548 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go @@ -55,16 +55,15 @@ const ( type Options struct { OutputFormat int - CumSort bool - CallTree bool - DropNegative bool - PositivePercentages bool - CompactLabels bool - Ratio float64 - Title string - ProfileLabels []string - ActiveFilters []string - NumLabelUnits map[string]string + CumSort bool + CallTree bool + DropNegative bool + CompactLabels bool + Ratio float64 + Title string + ProfileLabels []string + ActiveFilters []string + NumLabelUnits map[string]string NodeCount int NodeFraction float64 @@ -79,6 +78,7 @@ type Options struct { Symbol *regexp.Regexp // Symbols to include on disassembly report. SourcePath string // Search path for source files. + TrimPath string // Paths to trim from source file paths. } // Generate generates a report as directed by the Report. @@ -239,7 +239,7 @@ func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph { // Clean up file paths using heuristics. prof := rpt.prof for _, f := range prof.Function { - f.Filename = trimPath(f.Filename) + f.Filename = trimPath(f.Filename, o.TrimPath, o.SourcePath) } // Removes all numeric tags except for the bytes tag prior // to making graph. @@ -1192,7 +1192,7 @@ func New(prof *profile.Profile, o *Options) *Report { } return measurement.ScaledLabel(v, o.SampleUnit, o.OutputUnit) } - return &Report{prof, computeTotal(prof, o.SampleValue, o.SampleMeanDivisor, !o.PositivePercentages), + return &Report{prof, computeTotal(prof, o.SampleValue, o.SampleMeanDivisor), o, format} } @@ -1213,11 +1213,8 @@ func NewDefault(prof *profile.Profile, options Options) *Report { } // computeTotal computes the sum of all sample values. This will be -// used to compute percentages. If includeNegative is set, use use -// absolute values to provide a meaningful percentage for both -// negative and positive values. Otherwise only use positive values, -// which is useful when comparing profiles from different jobs. -func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64, includeNegative bool) int64 { +// used to compute percentages. +func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 { var div, ret int64 for _, sample := range prof.Sample { var d, v int64 @@ -1225,13 +1222,11 @@ func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64, i if meanDiv != nil { d = meanDiv(sample.Value) } - if v >= 0 { - ret += v - div += d - } else if includeNegative { - ret -= v - div += d + if v < 0 { + v = -v } + ret += v + div += d } if div != 0 { return ret / div diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go b/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go index c243e20c2b..49c6e4934f 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go @@ -46,6 +46,7 @@ func TestSource(t *testing.T) { &Options{ OutputFormat: List, Symbol: regexp.MustCompile(`.`), + TrimPath: "/some/path", SampleValue: sampleValue1, SampleUnit: testProfile.SampleType[1].Unit, @@ -60,6 +61,7 @@ func TestSource(t *testing.T) { OutputFormat: Dot, CallTree: true, Symbol: regexp.MustCompile(`.`), + TrimPath: "/some/path", SampleValue: sampleValue1, SampleUnit: testProfile.SampleType[1].Unit, @@ -119,7 +121,7 @@ var testF = []*profile.Function{ { ID: 4, Name: "tee", - Filename: "testdata/source2", + Filename: "/some/path/testdata/source2", }, } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go index 5295839973..835badfcae 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go @@ -63,7 +63,7 @@ func printSource(w io.Writer, rpt *Report) error { } sourcePath = wd } - reader := newSourceReader(sourcePath) + reader := newSourceReader(sourcePath, o.TrimPath) fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total)) for _, fn := range functions { @@ -146,7 +146,7 @@ func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) er } sourcePath = wd } - reader := newSourceReader(sourcePath) + reader := newSourceReader(sourcePath, o.TrimPath) type fileFunction struct { fileName, functionName string @@ -263,7 +263,7 @@ func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj // // E.g., suppose we are printing source code for F and this // instruction is from H where F called G called H and both - // of those calls were inlined. We want to use the line + // of those calls were inlined. We want to use the line // number from F, not from H (which is what Disasm gives us). // // So find the outer-most linenumber in the source file. @@ -391,8 +391,7 @@ func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyIns continue } curCalls = nil - fname := trimPath(c.file) - fline, ok := reader.line(fname, c.line) + fline, ok := reader.line(c.file, c.line) if !ok { fline = "" } @@ -400,7 +399,7 @@ func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyIns fmt.Fprintf(w, " %8s %10s %10s %8s <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n", "", "", "", "", template.HTMLEscapeString(fmt.Sprintf("%-80s", text)), - template.HTMLEscapeString(filepath.Base(fname)), c.line) + template.HTMLEscapeString(filepath.Base(c.file)), c.line) } curCalls = an.inlineCalls text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction @@ -426,7 +425,6 @@ func printPageClosing(w io.Writer) { // file and annotates it with the samples in fns. Returns the sources // as nodes, using the info.name field to hold the source code. func getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start, end int) (graph.Nodes, string, error) { - file = trimPath(file) lineNodes := make(map[int]graph.Nodes) // Collect source coordinates from profile. @@ -516,20 +514,26 @@ func getMissingFunctionSource(filename string, asm map[int][]assemblyInstruction // sourceReader provides access to source code with caching of file contents. type sourceReader struct { + // searchPath is a filepath.ListSeparator-separated list of directories where + // source files should be searched. searchPath string + // trimPath is a filepath.ListSeparator-separated list of paths to trim. + trimPath string + // files maps from path name to a list of lines. // files[*][0] is unused since line numbering starts at 1. files map[string][]string - // errors collects errors encountered per file. These errors are + // errors collects errors encountered per file. These errors are // consulted before returning out of these module. errors map[string]error } -func newSourceReader(searchPath string) *sourceReader { +func newSourceReader(searchPath, trimPath string) *sourceReader { return &sourceReader{ searchPath, + trimPath, make(map[string][]string), make(map[string]error), } @@ -544,7 +548,7 @@ func (reader *sourceReader) line(path string, lineno int) (string, bool) { if !ok { // Read and cache file contents. lines = []string{""} // Skip 0th line - f, err := openSourceFile(path, reader.searchPath) + f, err := openSourceFile(path, reader.searchPath, reader.trimPath) if err != nil { reader.errors[path] = err } else { @@ -565,17 +569,20 @@ func (reader *sourceReader) line(path string, lineno int) (string, bool) { return lines[lineno], true } -// openSourceFile opens a source file from a name encoded in a -// profile. File names in a profile after often relative paths, so -// search them in each of the paths in searchPath (or CWD by default), -// and their parents. -func openSourceFile(path, searchPath string) (*os.File, error) { +// openSourceFile opens a source file from a name encoded in a profile. File +// names in a profile after can be relative paths, so search them in each of +// the paths in searchPath and their parents. In case the profile contains +// absolute paths, additional paths may be configured to trim from the source +// paths in the profile. This effectively turns the path into a relative path +// searching it using searchPath as usual). +func openSourceFile(path, searchPath, trim string) (*os.File, error) { + path = trimPath(path, trim, searchPath) + // If file is still absolute, require file to exist. if filepath.IsAbs(path) { f, err := os.Open(path) return f, err } - - // Scan each component of the path + // Scan each component of the path. for _, dir := range filepath.SplitList(searchPath) { // Search up for every parent of each possible path. for { @@ -595,18 +602,34 @@ func openSourceFile(path, searchPath string) (*os.File, error) { } // trimPath cleans up a path by removing prefixes that are commonly -// found on profiles. -func trimPath(path string) string { - basePaths := []string{ - "/proc/self/cwd/./", - "/proc/self/cwd/", +// found on profiles plus configured prefixes. +// TODO(aalexand): Consider optimizing out the redundant work done in this +// function if it proves to matter. +func trimPath(path, trimPath, searchPath string) string { + // Keep path variable intact as it's used below to form the return value. + sPath, searchPath := filepath.ToSlash(path), filepath.ToSlash(searchPath) + if trimPath == "" { + // If the trim path is not configured, try to guess it heuristically: + // search for basename of each search path in the original path and, if + // found, strip everything up to and including the basename. So, for + // example, given original path "/some/remote/path/my-project/foo/bar.c" + // and search path "/my/local/path/my-project" the heuristic will return + // "/my/local/path/my-project/foo/bar.c". + for _, dir := range filepath.SplitList(searchPath) { + want := "/" + filepath.Base(dir) + "/" + if found := strings.Index(sPath, want); found != -1 { + return path[found+len(want):] + } + } } - - sPath := filepath.ToSlash(path) - - for _, base := range basePaths { - if strings.HasPrefix(sPath, base) { - return filepath.FromSlash(sPath[len(base):]) + // Trim configured trim prefixes. + trimPaths := append(filepath.SplitList(filepath.ToSlash(trimPath)), "/proc/self/cwd/./", "/proc/self/cwd/") + for _, trimPath := range trimPaths { + if !strings.HasSuffix(trimPath, "/") { + trimPath += "/" + } + if strings.HasPrefix(sPath, trimPath) { + return path[len(trimPath):] } } return path diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go index 682bfe0a1e..f1dd5c70dd 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go @@ -48,40 +48,56 @@ func TestOpenSourceFile(t *testing.T) { for _, tc := range []struct { desc string searchPath string + trimPath string fs []string path string wantPath string // If empty, error is wanted. }{ { desc: "exact absolute path is found", - fs: []string{"foo/bar.txt"}, - path: "$dir/foo/bar.txt", - wantPath: "$dir/foo/bar.txt", + fs: []string{"foo/bar.cc"}, + path: "$dir/foo/bar.cc", + wantPath: "$dir/foo/bar.cc", }, { desc: "exact relative path is found", searchPath: "$dir", - fs: []string{"foo/bar.txt"}, - path: "foo/bar.txt", - wantPath: "$dir/foo/bar.txt", + fs: []string{"foo/bar.cc"}, + path: "foo/bar.cc", + wantPath: "$dir/foo/bar.cc", }, { desc: "multiple search path", searchPath: "some/path" + lsep + "$dir", - fs: []string{"foo/bar.txt"}, - path: "foo/bar.txt", - wantPath: "$dir/foo/bar.txt", + fs: []string{"foo/bar.cc"}, + path: "foo/bar.cc", + wantPath: "$dir/foo/bar.cc", }, { desc: "relative path is found in parent dir", searchPath: "$dir/foo/bar", - fs: []string{"bar.txt", "foo/bar/baz.txt"}, - path: "bar.txt", - wantPath: "$dir/bar.txt", + fs: []string{"bar.cc", "foo/bar/baz.cc"}, + path: "bar.cc", + wantPath: "$dir/bar.cc", + }, + { + desc: "trims configured prefix", + searchPath: "$dir", + trimPath: "some-path" + lsep + "/some/remote/path", + fs: []string{"my-project/foo/bar.cc"}, + path: "/some/remote/path/my-project/foo/bar.cc", + wantPath: "$dir/my-project/foo/bar.cc", + }, + { + desc: "trims heuristically", + searchPath: "$dir/my-project", + fs: []string{"my-project/foo/bar.cc"}, + path: "/some/remote/path/my-project/foo/bar.cc", + wantPath: "$dir/my-project/foo/bar.cc", }, { desc: "error when not found", - path: "foo.txt", + path: "foo.cc", }, } { t.Run(tc.desc, func(t *testing.T) { @@ -103,15 +119,15 @@ func TestOpenSourceFile(t *testing.T) { tc.searchPath = filepath.FromSlash(strings.Replace(tc.searchPath, "$dir", tempdir, -1)) tc.path = filepath.FromSlash(strings.Replace(tc.path, "$dir", tempdir, 1)) tc.wantPath = filepath.FromSlash(strings.Replace(tc.wantPath, "$dir", tempdir, 1)) - if file, err := openSourceFile(tc.path, tc.searchPath); err != nil && tc.wantPath != "" { - t.Errorf("openSourceFile(%q, %q) = err %v, want path %q", tc.path, tc.searchPath, err, tc.wantPath) + if file, err := openSourceFile(tc.path, tc.searchPath, tc.trimPath); err != nil && tc.wantPath != "" { + t.Errorf("openSourceFile(%q, %q, %q) = err %v, want path %q", tc.path, tc.searchPath, tc.trimPath, err, tc.wantPath) } else if err == nil { defer file.Close() gotPath := file.Name() if tc.wantPath == "" { - t.Errorf("openSourceFile(%q, %q) = %q, want error", tc.path, tc.searchPath, gotPath) + t.Errorf("openSourceFile(%q, %q, %q) = %q, want error", tc.path, tc.searchPath, tc.trimPath, gotPath) } else if gotPath != tc.wantPath { - t.Errorf("openSourceFile(%q, %q) = %q, want path %q", tc.path, tc.searchPath, gotPath, tc.wantPath) + t.Errorf("openSourceFile(%q, %q, %q) = %q, want path %q", tc.path, tc.searchPath, tc.trimPath, gotPath, tc.wantPath) } } }) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go b/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go index 086c0ccb04..638c4968ec 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go @@ -34,17 +34,22 @@ var ( symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`) ) -// Symbolize symbolizes profile p by parsing data returned by a -// symbolz handler. syms receives the symbolz query (hex addresses -// separated by '+') and returns the symbolz output in a string. If -// force is false, it will only symbolize locations from mappings -// not already marked as HasFunctions. +// Symbolize symbolizes profile p by parsing data returned by a symbolz +// handler. syms receives the symbolz query (hex addresses separated by '+') +// and returns the symbolz output in a string. If force is false, it will only +// symbolize locations from mappings not already marked as HasFunctions. Never +// attempts symbolization of addresses from unsymbolizable system +// mappings as those may look negative - e.g. "[vsyscall]". func Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error { for _, m := range p.Mapping { if !force && m.HasFunctions { // Only check for HasFunctions as symbolz only populates function names. continue } + // Skip well-known system mappings. + if m.Unsymbolizable() { + continue + } mappingSources := sources[m.File] if m.BuildID != "" { mappingSources = append(mappingSources, sources[m.BuildID]...) diff --git a/src/cmd/vendor/github.com/google/pprof/pprof.go b/src/cmd/vendor/github.com/google/pprof/pprof.go index 57ec28a32c..df4f831b76 100644 --- a/src/cmd/vendor/github.com/google/pprof/pprof.go +++ b/src/cmd/vendor/github.com/google/pprof/pprof.go @@ -19,13 +19,88 @@ package main import ( "fmt" "os" + "strings" + "github.com/chzyer/readline" "github.com/google/pprof/driver" ) func main() { - if err := driver.PProf(&driver.Options{}); err != nil { + if err := driver.PProf(&driver.Options{UI: newUI()}); err != nil { fmt.Fprintf(os.Stderr, "pprof: %v\n", err) os.Exit(2) } } + +// readlineUI implements the driver.UI interface using the +// github.com/chzyer/readline library. +// This is contained in pprof.go to avoid adding the readline +// dependency in the vendored copy of pprof in the Go distribution, +// which does not use this file. +type readlineUI struct { + rl *readline.Instance +} + +func newUI() driver.UI { + rl, err := readline.New("") + if err != nil { + fmt.Fprintf(os.Stderr, "readline: %v", err) + return nil + } + return &readlineUI{ + rl: rl, + } +} + +// Read returns a line of text (a command) read from the user. +// prompt is printed before reading the command. +func (r *readlineUI) ReadLine(prompt string) (string, error) { + r.rl.SetPrompt(prompt) + return r.rl.Readline() +} + +// Print shows a message to the user. +// It is printed over stderr as stdout is reserved for regular output. +func (r *readlineUI) Print(args ...interface{}) { + text := fmt.Sprint(args...) + if !strings.HasSuffix(text, "\n") { + text += "\n" + } + fmt.Fprint(r.rl.Stderr(), text) +} + +// Print shows a message to the user, colored in red for emphasis. +// It is printed over stderr as stdout is reserved for regular output. +func (r *readlineUI) PrintErr(args ...interface{}) { + text := fmt.Sprint(args...) + if !strings.HasSuffix(text, "\n") { + text += "\n" + } + fmt.Fprint(r.rl.Stderr(), colorize(text)) +} + +// colorize the msg using ANSI color escapes. +func colorize(msg string) string { + var red = 31 + var colorEscape = fmt.Sprintf("\033[0;%dm", red) + var colorResetEscape = "\033[0m" + return colorEscape + msg + colorResetEscape +} + +// IsTerminal returns whether the UI is known to be tied to an +// interactive terminal (as opposed to being redirected to a file). +func (r *readlineUI) IsTerminal() bool { + const stdout = 1 + return readline.IsTerminal(stdout) +} + +// Start a browser on interactive mode. +func (r *readlineUI) WantBrowser() bool { + return r.IsTerminal() +} + +// SetAutoComplete instructs the UI to call complete(cmd) to obtain +// the auto-completion of cmd, if the UI supports auto-completion at all. +func (r *readlineUI) SetAutoComplete(complete func(string) string) { + // TODO: Implement auto-completion support. +} diff --git a/src/cmd/vendor/github.com/google/pprof/profile/filter.go b/src/cmd/vendor/github.com/google/pprof/profile/filter.go index f857fdf8f8..ea8e66c68d 100644 --- a/src/cmd/vendor/github.com/google/pprof/profile/filter.go +++ b/src/cmd/vendor/github.com/google/pprof/profile/filter.go @@ -74,6 +74,71 @@ func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp) return } +// ShowFrom drops all stack frames above the highest matching frame and returns +// whether a match was found. If showFrom is nil it returns false and does not +// modify the profile. +// +// Example: consider a sample with frames [A, B, C, B], where A is the root. +// ShowFrom(nil) returns false and has frames [A, B, C, B]. +// ShowFrom(A) returns true and has frames [A, B, C, B]. +// ShowFrom(B) returns true and has frames [B, C, B]. +// ShowFrom(C) returns true and has frames [C, B]. +// ShowFrom(D) returns false and drops the sample because no frames remain. +func (p *Profile) ShowFrom(showFrom *regexp.Regexp) (matched bool) { + if showFrom == nil { + return false + } + // showFromLocs stores location IDs that matched ShowFrom. + showFromLocs := make(map[uint64]bool) + // Apply to locations. + for _, loc := range p.Location { + if filterShowFromLocation(loc, showFrom) { + showFromLocs[loc.ID] = true + matched = true + } + } + // For all samples, strip locations after the highest matching one. + s := make([]*Sample, 0, len(p.Sample)) + for _, sample := range p.Sample { + for i := len(sample.Location) - 1; i >= 0; i-- { + if showFromLocs[sample.Location[i].ID] { + sample.Location = sample.Location[:i+1] + s = append(s, sample) + break + } + } + } + p.Sample = s + return matched +} + +// filterShowFromLocation tests a showFrom regex against a location, removes +// lines after the last match and returns whether a match was found. If the +// mapping is matched, then all lines are kept. +func filterShowFromLocation(loc *Location, showFrom *regexp.Regexp) bool { + if m := loc.Mapping; m != nil && showFrom.MatchString(m.File) { + return true + } + if i := loc.lastMatchedLineIndex(showFrom); i >= 0 { + loc.Line = loc.Line[:i+1] + return true + } + return false +} + +// lastMatchedLineIndex returns the index of the last line that matches a regex, +// or -1 if no match is found. +func (loc *Location) lastMatchedLineIndex(re *regexp.Regexp) int { + for i := len(loc.Line) - 1; i >= 0; i-- { + if fn := loc.Line[i].Function; fn != nil { + if re.MatchString(fn.Name) || re.MatchString(fn.Filename) { + return i + } + } + } + return -1 +} + // FilterTagsByName filters the tags in a profile and only keeps // tags that match show and not hide. func (p *Profile) FilterTagsByName(show, hide *regexp.Regexp) (sm, hm bool) { @@ -142,6 +207,9 @@ func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line { // matchedLines returns the lines in the location that match // the regular expression. func (loc *Location) matchedLines(re *regexp.Regexp) []Line { + if m := loc.Mapping; m != nil && re.MatchString(m.File) { + return loc.Line + } var lines []Line for _, ln := range loc.Line { if fn := ln.Function; fn != nil { diff --git a/src/cmd/vendor/github.com/google/pprof/profile/filter_test.go b/src/cmd/vendor/github.com/google/pprof/profile/filter_test.go new file mode 100644 index 0000000000..3fd1787e8b --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/profile/filter_test.go @@ -0,0 +1,599 @@ +// Copyright 2018 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 profile + +import ( + "fmt" + "regexp" + "strings" + "testing" + + "github.com/google/pprof/internal/proftest" +) + +var mappings = []*Mapping{ + {ID: 1, Start: 0x10000, Limit: 0x40000, File: "map0", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true}, + {ID: 2, Start: 0x50000, Limit: 0x70000, File: "map1", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true}, +} + +var functions = []*Function{ + {ID: 1, Name: "fun0", SystemName: "fun0", Filename: "file0"}, + {ID: 2, Name: "fun1", SystemName: "fun1", Filename: "file1"}, + {ID: 3, Name: "fun2", SystemName: "fun2", Filename: "file2"}, + {ID: 4, Name: "fun3", SystemName: "fun3", Filename: "file3"}, + {ID: 5, Name: "fun4", SystemName: "fun4", Filename: "file4"}, + {ID: 6, Name: "fun5", SystemName: "fun5", Filename: "file5"}, + {ID: 7, Name: "fun6", SystemName: "fun6", Filename: "file6"}, + {ID: 8, Name: "fun7", SystemName: "fun7", Filename: "file7"}, + {ID: 9, Name: "fun8", SystemName: "fun8", Filename: "file8"}, + {ID: 10, Name: "fun9", SystemName: "fun9", Filename: "file9"}, + {ID: 11, Name: "fun10", SystemName: "fun10", Filename: "file10"}, +} + +var noInlinesLocs = []*Location{ + {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}}}, + {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[1], Line: 1}}}, + {ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[2], Line: 1}}}, + {ID: 4, Mapping: mappings[0], Address: 0x4000, Line: []Line{{Function: functions[3], Line: 1}}}, + {ID: 5, Mapping: mappings[0], Address: 0x5000, Line: []Line{{Function: functions[4], Line: 1}}}, + {ID: 6, Mapping: mappings[0], Address: 0x6000, Line: []Line{{Function: functions[5], Line: 1}}}, + {ID: 7, Mapping: mappings[0], Address: 0x7000, Line: []Line{{Function: functions[6], Line: 1}}}, + {ID: 8, Mapping: mappings[0], Address: 0x8000, Line: []Line{{Function: functions[7], Line: 1}}}, + {ID: 9, Mapping: mappings[0], Address: 0x9000, Line: []Line{{Function: functions[8], Line: 1}}}, + {ID: 10, Mapping: mappings[0], Address: 0x10000, Line: []Line{{Function: functions[9], Line: 1}}}, + {ID: 11, Mapping: mappings[1], Address: 0x11000, Line: []Line{{Function: functions[10], Line: 1}}}, +} + +var noInlinesProfile = &Profile{ + TimeNanos: 10000, + PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"}, + Period: 1, + DurationNanos: 10e9, + SampleType: []*ValueType{{Type: "samples", Unit: "count"}}, + Mapping: mappings, + Function: functions, + Location: noInlinesLocs, + Sample: []*Sample{ + {Value: []int64{1}, Location: []*Location{noInlinesLocs[0], noInlinesLocs[1], noInlinesLocs[2], noInlinesLocs[3]}}, + {Value: []int64{2}, Location: []*Location{noInlinesLocs[4], noInlinesLocs[5], noInlinesLocs[1], noInlinesLocs[6]}}, + {Value: []int64{3}, Location: []*Location{noInlinesLocs[7], noInlinesLocs[8]}}, + {Value: []int64{4}, Location: []*Location{noInlinesLocs[9], noInlinesLocs[4], noInlinesLocs[10], noInlinesLocs[7]}}, + }, +} + +var allNoInlinesSampleFuncs = []string{ + "fun0 fun1 fun2 fun3: 1", + "fun4 fun5 fun1 fun6: 2", + "fun7 fun8: 3", + "fun9 fun4 fun10 fun7: 4", +} + +var inlinesLocs = []*Location{ + {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}}, + {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[2], Line: 1}, {Function: functions[3], Line: 1}}}, + {ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[4], Line: 1}, {Function: functions[5], Line: 1}, {Function: functions[6], Line: 1}}}, +} + +var inlinesProfile = &Profile{ + TimeNanos: 10000, + PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"}, + Period: 1, + DurationNanos: 10e9, + SampleType: []*ValueType{{Type: "samples", Unit: "count"}}, + Mapping: mappings, + Function: functions, + Location: inlinesLocs, + Sample: []*Sample{ + {Value: []int64{1}, Location: []*Location{inlinesLocs[0], inlinesLocs[1]}}, + {Value: []int64{2}, Location: []*Location{inlinesLocs[2]}}, + }, +} + +var emptyLinesLocs = []*Location{ + {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}}, + {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{}}, + {ID: 3, Mapping: mappings[1], Address: 0x2000, Line: []Line{}}, +} + +var emptyLinesProfile = &Profile{ + TimeNanos: 10000, + PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"}, + Period: 1, + DurationNanos: 10e9, + SampleType: []*ValueType{{Type: "samples", Unit: "count"}}, + Mapping: mappings, + Function: functions, + Location: emptyLinesLocs, + Sample: []*Sample{ + {Value: []int64{1}, Location: []*Location{emptyLinesLocs[0], emptyLinesLocs[1]}}, + {Value: []int64{2}, Location: []*Location{emptyLinesLocs[2]}}, + {Value: []int64{3}, Location: []*Location{}}, + }, +} + +func TestFilterSamplesByName(t *testing.T) { + for _, tc := range []struct { + // name is the name of the test case. + name string + // profile is the profile that gets filtered. + profile *Profile + // These are the inputs to FilterSamplesByName(). + focus, ignore, hide, show *regexp.Regexp + // want{F,I,S,H}m are expected return values from FilterSamplesByName. + wantFm, wantIm, wantSm, wantHm bool + // wantSampleFuncs contains expected stack functions and sample value after + // filtering, in the same order as in the profile. The format is as + // returned by sampleFuncs function below, which is "callee caller: <num>". + wantSampleFuncs []string + }{ + // No Filters + { + name: "empty filters keep all frames", + profile: noInlinesProfile, + wantFm: true, + wantSampleFuncs: allNoInlinesSampleFuncs, + }, + // Focus + { + name: "focus with no matches", + profile: noInlinesProfile, + focus: regexp.MustCompile("unknown"), + }, + { + name: "focus matches function names", + profile: noInlinesProfile, + focus: regexp.MustCompile("fun1"), + wantFm: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2 fun3: 1", + "fun4 fun5 fun1 fun6: 2", + "fun9 fun4 fun10 fun7: 4", + }, + }, + { + name: "focus matches file names", + profile: noInlinesProfile, + focus: regexp.MustCompile("file1"), + wantFm: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2 fun3: 1", + "fun4 fun5 fun1 fun6: 2", + "fun9 fun4 fun10 fun7: 4", + }, + }, + { + name: "focus matches mapping names", + profile: noInlinesProfile, + focus: regexp.MustCompile("map1"), + wantFm: true, + wantSampleFuncs: []string{ + "fun9 fun4 fun10 fun7: 4", + }, + }, + { + name: "focus matches inline functions", + profile: inlinesProfile, + focus: regexp.MustCompile("fun5"), + wantFm: true, + wantSampleFuncs: []string{ + "fun4 fun5 fun6: 2", + }, + }, + // Ignore + { + name: "ignore with no matches matches all samples", + profile: noInlinesProfile, + ignore: regexp.MustCompile("unknown"), + wantFm: true, + wantSampleFuncs: allNoInlinesSampleFuncs, + }, + { + name: "ignore matches function names", + profile: noInlinesProfile, + ignore: regexp.MustCompile("fun1"), + wantFm: true, + wantIm: true, + wantSampleFuncs: []string{ + "fun7 fun8: 3", + }, + }, + { + name: "ignore matches file names", + profile: noInlinesProfile, + ignore: regexp.MustCompile("file1"), + wantFm: true, + wantIm: true, + wantSampleFuncs: []string{ + "fun7 fun8: 3", + }, + }, + { + name: "ignore matches mapping names", + profile: noInlinesProfile, + ignore: regexp.MustCompile("map1"), + wantFm: true, + wantIm: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2 fun3: 1", + "fun4 fun5 fun1 fun6: 2", + "fun7 fun8: 3", + }, + }, + { + name: "ignore matches inline functions", + profile: inlinesProfile, + ignore: regexp.MustCompile("fun5"), + wantFm: true, + wantIm: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2 fun3: 1", + }, + }, + // Show + { + name: "show with no matches", + profile: noInlinesProfile, + show: regexp.MustCompile("unknown"), + wantFm: true, + }, + { + name: "show matches function names", + profile: noInlinesProfile, + show: regexp.MustCompile("fun1|fun2"), + wantFm: true, + wantSm: true, + wantSampleFuncs: []string{ + "fun1 fun2: 1", + "fun1: 2", + "fun10: 4", + }, + }, + { + name: "show matches file names", + profile: noInlinesProfile, + show: regexp.MustCompile("file1|file3"), + wantFm: true, + wantSm: true, + wantSampleFuncs: []string{ + "fun1 fun3: 1", + "fun1: 2", + "fun10: 4", + }, + }, + { + name: "show matches mapping names", + profile: noInlinesProfile, + show: regexp.MustCompile("map1"), + wantFm: true, + wantSm: true, + wantSampleFuncs: []string{ + "fun10: 4", + }, + }, + { + name: "show matches inline functions", + profile: inlinesProfile, + show: regexp.MustCompile("fun[03]"), + wantFm: true, + wantSm: true, + wantSampleFuncs: []string{ + "fun0 fun3: 1", + }, + }, + { + name: "show keeps all lines when matching both mapping and function", + profile: inlinesProfile, + show: regexp.MustCompile("map0|fun5"), + wantFm: true, + wantSm: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2 fun3: 1", + "fun4 fun5 fun6: 2", + }, + }, + // Hide + { + name: "hide with no matches", + profile: noInlinesProfile, + hide: regexp.MustCompile("unknown"), + wantFm: true, + wantSampleFuncs: allNoInlinesSampleFuncs, + }, + { + name: "hide matches function names", + profile: noInlinesProfile, + hide: regexp.MustCompile("fun1|fun2"), + wantFm: true, + wantHm: true, + wantSampleFuncs: []string{ + "fun0 fun3: 1", + "fun4 fun5 fun6: 2", + "fun7 fun8: 3", + "fun9 fun4 fun7: 4", + }, + }, + { + name: "hide matches file names", + profile: noInlinesProfile, + hide: regexp.MustCompile("file1|file3"), + wantFm: true, + wantHm: true, + wantSampleFuncs: []string{ + "fun0 fun2: 1", + "fun4 fun5 fun6: 2", + "fun7 fun8: 3", + "fun9 fun4 fun7: 4", + }, + }, + { + name: "hide matches mapping names", + profile: noInlinesProfile, + hide: regexp.MustCompile("map1"), + wantFm: true, + wantHm: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2 fun3: 1", + "fun4 fun5 fun1 fun6: 2", + "fun7 fun8: 3", + "fun9 fun4 fun7: 4", + }, + }, + { + name: "hide matches inline functions", + profile: inlinesProfile, + hide: regexp.MustCompile("fun[125]"), + wantFm: true, + wantHm: true, + wantSampleFuncs: []string{ + "fun0 fun3: 1", + "fun4 fun6: 2", + }, + }, + { + name: "hide drops all lines when matching both mapping and function", + profile: inlinesProfile, + hide: regexp.MustCompile("map0|fun5"), + wantFm: true, + wantHm: true, + }, + // Compound filters + { + name: "hides a stack matched by both focus and ignore", + profile: noInlinesProfile, + focus: regexp.MustCompile("fun1|fun7"), + ignore: regexp.MustCompile("fun1"), + wantFm: true, + wantIm: true, + wantSampleFuncs: []string{ + "fun7 fun8: 3", + }, + }, + { + name: "hides a function if both show and hide match it", + profile: noInlinesProfile, + show: regexp.MustCompile("fun1"), + hide: regexp.MustCompile("fun10"), + wantFm: true, + wantSm: true, + wantHm: true, + wantSampleFuncs: []string{ + "fun1: 1", + "fun1: 2", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + p := tc.profile.Copy() + fm, im, hm, sm := p.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show) + + type match struct{ fm, im, hm, sm bool } + if got, want := (match{fm: fm, im: im, hm: hm, sm: sm}), (match{fm: tc.wantFm, im: tc.wantIm, hm: tc.wantHm, sm: tc.wantSm}); got != want { + t.Errorf("match got %+v want %+v", got, want) + } + + if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want { + diff, err := proftest.Diff([]byte(want), []byte(got)) + if err != nil { + t.Fatalf("failed to get diff: %v", err) + } + t.Errorf("FilterSamplesByName: got diff(want->got):\n%s", diff) + } + }) + } +} + +func TestShowFrom(t *testing.T) { + for _, tc := range []struct { + name string + profile *Profile + showFrom *regexp.Regexp + // wantMatch is the expected return value. + wantMatch bool + // wantSampleFuncs contains expected stack functions and sample value after + // filtering, in the same order as in the profile. The format is as + // returned by sampleFuncs function below, which is "callee caller: <num>". + wantSampleFuncs []string + }{ + { + name: "nil showFrom keeps all frames", + profile: noInlinesProfile, + wantMatch: false, + wantSampleFuncs: allNoInlinesSampleFuncs, + }, + { + name: "showFrom with no matches drops all samples", + profile: noInlinesProfile, + showFrom: regexp.MustCompile("unknown"), + wantMatch: false, + }, + { + name: "showFrom matches function names", + profile: noInlinesProfile, + showFrom: regexp.MustCompile("fun1"), + wantMatch: true, + wantSampleFuncs: []string{ + "fun0 fun1: 1", + "fun4 fun5 fun1: 2", + "fun9 fun4 fun10: 4", + }, + }, + { + name: "showFrom matches file names", + profile: noInlinesProfile, + showFrom: regexp.MustCompile("file1"), + wantMatch: true, + wantSampleFuncs: []string{ + "fun0 fun1: 1", + "fun4 fun5 fun1: 2", + "fun9 fun4 fun10: 4", + }, + }, + { + name: "showFrom matches mapping names", + profile: noInlinesProfile, + showFrom: regexp.MustCompile("map1"), + wantMatch: true, + wantSampleFuncs: []string{ + "fun9 fun4 fun10: 4", + }, + }, + { + name: "showFrom drops frames above highest of multiple matches", + profile: noInlinesProfile, + showFrom: regexp.MustCompile("fun[12]"), + wantMatch: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2: 1", + "fun4 fun5 fun1: 2", + "fun9 fun4 fun10: 4", + }, + }, + { + name: "showFrom matches inline functions", + profile: inlinesProfile, + showFrom: regexp.MustCompile("fun0|fun5"), + wantMatch: true, + wantSampleFuncs: []string{ + "fun0: 1", + "fun4 fun5: 2", + }, + }, + { + name: "showFrom drops frames above highest of multiple inline matches", + profile: inlinesProfile, + showFrom: regexp.MustCompile("fun[1245]"), + wantMatch: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2: 1", + "fun4 fun5: 2", + }, + }, + { + name: "showFrom keeps all lines when matching mapping and function", + profile: inlinesProfile, + showFrom: regexp.MustCompile("map0|fun5"), + wantMatch: true, + wantSampleFuncs: []string{ + "fun0 fun1 fun2 fun3: 1", + "fun4 fun5 fun6: 2", + }, + }, + { + name: "showFrom matches location with empty lines", + profile: emptyLinesProfile, + showFrom: regexp.MustCompile("map1"), + wantMatch: true, + wantSampleFuncs: []string{ + ": 2", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + p := tc.profile.Copy() + + if gotMatch := p.ShowFrom(tc.showFrom); gotMatch != tc.wantMatch { + t.Errorf("match got %+v, want %+v", gotMatch, tc.wantMatch) + } + + if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want { + diff, err := proftest.Diff([]byte(want), []byte(got)) + if err != nil { + t.Fatalf("failed to get diff: %v", err) + } + t.Errorf("profile samples got diff(want->got):\n%s", diff) + } + }) + } +} + +// sampleFuncs returns a slice of strings where each string represents one +// profile sample in the format "<fun1> <fun2> <fun3>: <value>". This allows +// the expected values for test cases to be specifed in human-readable strings. +func sampleFuncs(p *Profile) []string { + var ret []string + for _, s := range p.Sample { + var funcs []string + for _, loc := range s.Location { + for _, line := range loc.Line { + funcs = append(funcs, line.Function.Name) + } + } + ret = append(ret, fmt.Sprintf("%s: %d", strings.Join(funcs, " "), s.Value[0])) + } + return ret +} + +func TestTagFilter(t *testing.T) { + // Perform several forms of tag filtering on the test profile. + + type filterTestcase struct { + include, exclude *regexp.Regexp + im, em bool + count int + } + + countTags := func(p *Profile) map[string]bool { + tags := make(map[string]bool) + + for _, s := range p.Sample { + for l := range s.Label { + tags[l] = true + } + for l := range s.NumLabel { + tags[l] = true + } + } + return tags + } + + for tx, tc := range []filterTestcase{ + {nil, nil, true, false, 3}, + {regexp.MustCompile("notfound"), nil, false, false, 0}, + {regexp.MustCompile("key1"), nil, true, false, 1}, + {nil, regexp.MustCompile("key[12]"), true, true, 1}, + } { + prof := testProfile1.Copy() + gim, gem := prof.FilterTagsByName(tc.include, tc.exclude) + if gim != tc.im { + t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im) + } + if gem != tc.em { + t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em) + } + if tags := countTags(prof); len(tags) != tc.count { + t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count) + } + } +} diff --git a/src/cmd/vendor/github.com/google/pprof/profile/merge.go b/src/cmd/vendor/github.com/google/pprof/profile/merge.go index a5440b521e..4dcc27f48e 100644 --- a/src/cmd/vendor/github.com/google/pprof/profile/merge.go +++ b/src/cmd/vendor/github.com/google/pprof/profile/merge.go @@ -294,20 +294,11 @@ func (pm *profileMerger) mapMapping(src *Mapping) mapInfo { } // Check memoization tables. - bk, pk := src.key() - if src.BuildID != "" { - if m, ok := pm.mappings[bk]; ok { - mi := mapInfo{m, int64(m.Start) - int64(src.Start)} - pm.mappingsByID[src.ID] = mi - return mi - } - } - if src.File != "" { - if m, ok := pm.mappings[pk]; ok { - mi := mapInfo{m, int64(m.Start) - int64(src.Start)} - pm.mappingsByID[src.ID] = mi - return mi - } + mk := src.key() + if m, ok := pm.mappings[mk]; ok { + mi := mapInfo{m, int64(m.Start) - int64(src.Start)} + pm.mappingsByID[src.ID] = mi + return mi } m := &Mapping{ ID: uint64(len(pm.p.Mapping) + 1), @@ -324,21 +315,15 @@ func (pm *profileMerger) mapMapping(src *Mapping) mapInfo { pm.p.Mapping = append(pm.p.Mapping, m) // Update memoization tables. - if m.BuildID != "" { - pm.mappings[bk] = m - } - if m.File != "" { - pm.mappings[pk] = m - } + pm.mappings[mk] = m mi := mapInfo{m, 0} pm.mappingsByID[src.ID] = mi return mi } // key generates encoded strings of Mapping to be used as a key for -// maps. The first key represents only the build id, while the second -// represents only the file path. -func (m *Mapping) key() (buildIDKey, pathKey mappingKey) { +// maps. +func (m *Mapping) key() mappingKey { // Normalize addresses to handle address space randomization. // Round up to next 4K boundary to avoid minor discrepancies. const mapsizeRounding = 0x1000 @@ -346,24 +331,27 @@ func (m *Mapping) key() (buildIDKey, pathKey mappingKey) { size := m.Limit - m.Start size = size + mapsizeRounding - 1 size = size - (size % mapsizeRounding) - - buildIDKey = mappingKey{ - size, - m.Offset, - m.BuildID, + key := mappingKey{ + size: size, + offset: m.Offset, } - pathKey = mappingKey{ - size, - m.Offset, - m.File, + switch { + case m.BuildID != "": + key.buildIDOrFile = m.BuildID + case m.File != "": + key.buildIDOrFile = m.File + default: + // A mapping containing neither build ID nor file name is a fake mapping. A + // key with empty buildIDOrFile is used for fake mappings so that they are + // treated as the same mapping during merging. } - return + return key } type mappingKey struct { - size, offset uint64 - buildidIDOrFile string + size, offset uint64 + buildIDOrFile string } func (pm *profileMerger) mapLine(src Line) Line { diff --git a/src/cmd/vendor/github.com/google/pprof/profile/merge_test.go b/src/cmd/vendor/github.com/google/pprof/profile/merge_test.go new file mode 100644 index 0000000000..6a04db2f34 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/profile/merge_test.go @@ -0,0 +1,167 @@ +// Copyright 2018 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 profile + +import ( + "testing" +) + +func TestMapMapping(t *testing.T) { + pm := &profileMerger{ + p: &Profile{}, + mappings: make(map[mappingKey]*Mapping), + mappingsByID: make(map[uint64]mapInfo), + } + for _, tc := range []struct { + desc string + m1 Mapping + m2 Mapping + wantMerged bool + }{ + { + desc: "same file name", + m1: Mapping{ + ID: 1, + File: "test-file-1", + }, + m2: Mapping{ + ID: 2, + File: "test-file-1", + }, + wantMerged: true, + }, + { + desc: "same build ID", + m1: Mapping{ + ID: 3, + BuildID: "test-build-id-1", + }, + m2: Mapping{ + ID: 4, + BuildID: "test-build-id-1", + }, + wantMerged: true, + }, + { + desc: "same fake mapping", + m1: Mapping{ + ID: 5, + }, + m2: Mapping{ + ID: 6, + }, + wantMerged: true, + }, + { + desc: "different start", + m1: Mapping{ + ID: 7, + Start: 0x1000, + Limit: 0x2000, + BuildID: "test-build-id-2", + }, + m2: Mapping{ + ID: 8, + Start: 0x3000, + Limit: 0x4000, + BuildID: "test-build-id-2", + }, + wantMerged: true, + }, + { + desc: "different file name", + m1: Mapping{ + ID: 9, + File: "test-file-2", + }, + m2: Mapping{ + ID: 10, + File: "test-file-3", + }, + }, + { + desc: "different build id", + m1: Mapping{ + ID: 11, + BuildID: "test-build-id-3", + }, + m2: Mapping{ + ID: 12, + BuildID: "test-build-id-4", + }, + }, + { + desc: "different size", + m1: Mapping{ + ID: 13, + Start: 0x1000, + Limit: 0x3000, + BuildID: "test-build-id-5", + }, + m2: Mapping{ + ID: 14, + Start: 0x1000, + Limit: 0x5000, + BuildID: "test-build-id-5", + }, + }, + { + desc: "different offset", + m1: Mapping{ + ID: 15, + Offset: 1, + BuildID: "test-build-id-6", + }, + m2: Mapping{ + ID: 16, + Offset: 2, + BuildID: "test-build-id-6", + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + info1 := pm.mapMapping(&tc.m1) + info2 := pm.mapMapping(&tc.m2) + gotM1, gotM2 := *info1.m, *info2.m + + wantM1 := tc.m1 + wantM1.ID = gotM1.ID + if gotM1 != wantM1 { + t.Errorf("first mapping got %v, want %v", gotM1, wantM1) + } + + if tc.wantMerged { + if gotM1 != gotM2 { + t.Errorf("first mapping got %v, second mapping got %v, want equal", gotM1, gotM2) + } + if info1.offset != 0 { + t.Errorf("first mapping info got offset %d, want 0", info1.offset) + } + if wantOffset := int64(tc.m1.Start) - int64(tc.m2.Start); wantOffset != info2.offset { + t.Errorf("second mapping info got offset %d, want %d", info2.offset, wantOffset) + } + } else { + if gotM1.ID == gotM2.ID { + t.Errorf("first mapping got %v, second mapping got %v, want different IDs", gotM1, gotM2) + } + wantM2 := tc.m2 + wantM2.ID = gotM2.ID + if gotM2 != wantM2 { + t.Errorf("second mapping got %v, want %v", gotM2, wantM2) + } + } + }) + } +} diff --git a/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go b/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go index ab020275cb..8ed67b1dd6 100644 --- a/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go +++ b/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go @@ -20,7 +20,6 @@ import ( "io/ioutil" "path/filepath" "reflect" - "regexp" "strings" "sync" "testing" @@ -902,101 +901,6 @@ func TestNormalizeIncompatibleProfiles(t *testing.T) { } } -func TestFilter(t *testing.T) { - // Perform several forms of filtering on the test profile. - - type filterTestcase struct { - focus, ignore, hide, show *regexp.Regexp - fm, im, hm, hnm bool - } - - for tx, tc := range []filterTestcase{ - { - fm: true, // nil focus matches every sample - }, - { - focus: regexp.MustCompile("notfound"), - }, - { - ignore: regexp.MustCompile("foo.c"), - fm: true, - im: true, - }, - { - hide: regexp.MustCompile("lib.so"), - fm: true, - hm: true, - }, - { - show: regexp.MustCompile("foo.c"), - fm: true, - hnm: true, - }, - { - show: regexp.MustCompile("notfound"), - fm: true, - }, - } { - prof := *testProfile1.Copy() - gf, gi, gh, gnh := prof.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show) - if gf != tc.fm { - t.Errorf("Filter #%d, got fm=%v, want %v", tx, gf, tc.fm) - } - if gi != tc.im { - t.Errorf("Filter #%d, got im=%v, want %v", tx, gi, tc.im) - } - if gh != tc.hm { - t.Errorf("Filter #%d, got hm=%v, want %v", tx, gh, tc.hm) - } - if gnh != tc.hnm { - t.Errorf("Filter #%d, got hnm=%v, want %v", tx, gnh, tc.hnm) - } - } -} - -func TestTagFilter(t *testing.T) { - // Perform several forms of tag filtering on the test profile. - - type filterTestcase struct { - include, exclude *regexp.Regexp - im, em bool - count int - } - - countTags := func(p *Profile) map[string]bool { - tags := make(map[string]bool) - - for _, s := range p.Sample { - for l := range s.Label { - tags[l] = true - } - for l := range s.NumLabel { - tags[l] = true - } - } - return tags - } - - for tx, tc := range []filterTestcase{ - {nil, nil, true, false, 3}, - {regexp.MustCompile("notfound"), nil, false, false, 0}, - {regexp.MustCompile("key1"), nil, true, false, 1}, - {nil, regexp.MustCompile("key[12]"), true, true, 1}, - } { - prof := testProfile1.Copy() - gim, gem := prof.FilterTagsByName(tc.include, tc.exclude) - if gim != tc.im { - t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im) - } - if gem != tc.em { - t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em) - } - if tags := countTags(prof); len(tags) != tc.count { - t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count) - } - } -} - // locationHash constructs a string to use as a hashkey for a sample, based on its locations func locationHash(s *Sample) string { var tb string diff --git a/src/cmd/vendor/github.com/google/pprof/doc/developer/profile.proto.md b/src/cmd/vendor/github.com/google/pprof/proto/README.md index d3b0b84191..d3b0b84191 100644 --- a/src/cmd/vendor/github.com/google/pprof/doc/developer/profile.proto.md +++ b/src/cmd/vendor/github.com/google/pprof/proto/README.md diff --git a/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/d3_flame_graph.go b/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/d3_flame_graph.go index d340f7076f..58a7fb4c40 100644 --- a/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/d3_flame_graph.go +++ b/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/d3_flame_graph.go @@ -1,620 +1,918 @@ // A D3.js plugin that produces flame graphs from hierarchical data. // https://github.com/spiermar/d3-flame-graph -// Version 1.0.11 +// Version 2.0.0-alpha4 // See LICENSE file for license details package d3flamegraph -// JSSource returns the d3.flameGraph.js file +// JSSource returns the d3-flamegraph.js file const JSSource = ` -/**! -* -* Copyright 2017 Martin Spier <spiermar@gmail.com> -* -* 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. -* -*/ -(function() { - 'use strict'; - - /*jshint eqnull:true */ - // https://tc39.github.io/ecma262/#sec-array.prototype.find - if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - value: function(predicate) { - // 1. Let O be ? ToObject(this value). - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3')) : + typeof define === 'function' && define.amd ? define(['exports', 'd3'], factory) : + (factory((global.d3 = global.d3 || {}),global.d3)); +}(this, (function (exports,d3) { 'use strict'; - var o = Object(this); +var d3__default = 'default' in d3 ? d3['default'] : d3; - // 2. Let len be ? ToLength(? Get(O, "length")). - var len = o.length >>> 0; +var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. - var thisArg = arguments[1]; - - // 5. Let k be 0. - var k = 0; - - // 6. Repeat, while k < len - while (k < len) { - // a. Let Pk be ! ToString(k). - // b. Let kValue be ? Get(O, Pk). - // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). - // d. If testResult is true, return kValue. - var kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return kValue; - } - // e. Increase k by 1. - k++; - } - // 7. Return undefined. - return undefined; - } - }); - } - if (!Array.prototype.filter) - Array.prototype.filter = function(func, thisArg) { - if ( ! ((typeof func === 'function') && this) ) - throw new TypeError(); - - var len = this.length >>> 0, - res = new Array(len), // preallocate array - c = 0, i = -1; - if (thisArg === undefined) - while (++i !== len) - // checks to see if the key was set - if (i in this) - if (func(t[i], i, t)) - res[c++] = t[i]; - else - while (++i !== len) - // checks to see if the key was set - if (i in this) - if (func.call(thisArg, t[i], i, t)) - res[c++] = t[i]; - - res.length = c; // shrink down array to proper size - return res; - }; - /*jshint eqnull:false */ - // Node/CommonJS - require D3 - if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') { - d3 = require('d3'); - } +function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; +} - // Node/CommonJS - require d3-tip - if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3.tip) == 'undefined') { - d3.tip = require('d3-tip'); +var d3Tip = createCommonjsModule(function (module) { +// d3.tip +// Copyright (c) 2013 Justin Palmer +// +// Tooltips for d3.js SVG visualizations + +(function (root, factory) { + if (typeof undefined === 'function' && undefined.amd) { + // AMD. Register as an anonymous module with d3 as a dependency. + undefined(['d3'], factory); + } else if ('object' === 'object' && module.exports) { + // CommonJS + var d3$$1 = d3__default; + module.exports = factory(d3$$1); + } else { + // Browser global. + root.d3.tip = factory(root.d3); } +}(commonjsGlobal, function (d3$$1) { + + // Public - contructs a new tooltip + // + // Returns a tip + return function() { + var direction = d3_tip_direction, + offset = d3_tip_offset, + html = d3_tip_html, + node = initNode(), + svg = null, + point = null, + target = null; + + function tip(vis) { + svg = getSVGNode(vis); + point = svg.createSVGPoint(); + document.body.appendChild(node); + } - function flameGraph() { + // Public - show the tooltip on the screen + // + // Returns a tip + tip.show = function() { + var args = Array.prototype.slice.call(arguments); + if(args[args.length - 1] instanceof SVGElement) target = args.pop(); + + var content = html.apply(this, args), + poffset = offset.apply(this, args), + dir = direction.apply(this, args), + nodel = getNodeEl(), + i = directions.length, + coords, + scrollTop = document.documentElement.scrollTop || document.body.scrollTop, + scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; + + nodel.html(content) + .style('opacity', 1).style('pointer-events', 'all'); + + while(i--) nodel.classed(directions[i], false); + coords = direction_callbacks.get(dir).apply(this); + nodel.classed(dir, true) + .style('top', (coords.top + poffset[0]) + scrollTop + 'px') + .style('left', (coords.left + poffset[1]) + scrollLeft + 'px'); + + return tip; + }; - var w = 960, // graph width - h = null, // graph height - c = 18, // cell height - selection = null, // selection - tooltip = true, // enable tooltip - title = "", // graph title - transitionDuration = 750, - transitionEase = d3.easeCubic, // tooltip offset - sort = false, - reversed = false, // reverse the graph direction - clickHandler = null, - minFrameSize = 0, - details = null; + // Public - hide the tooltip + // + // Returns a tip + tip.hide = function() { + var nodel = getNodeEl(); + nodel.style('opacity', 0).style('pointer-events', 'none'); + return tip + }; - var tip = d3.tip() - .direction("s") - .offset([8, 0]) - .attr('class', 'd3-flame-graph-tip') - .html(function(d) { return label(d); }); + // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. + // + // n - name of the attribute + // v - value of the attribute + // + // Returns tip or attribute value + tip.attr = function(n, v) { + if (arguments.length < 2 && typeof n === 'string') { + return getNodeEl().attr(n) + } else { + var args = Array.prototype.slice.call(arguments); + d3$$1.selection.prototype.attr.apply(getNodeEl(), args); + } - var svg; + return tip + }; - function name(d) { - return d.data.n || d.data.name; - } + // Public: Proxy style calls to the d3 tip container. Sets or gets a style value. + // + // n - name of the property + // v - value of the property + // + // Returns tip or style property value + tip.style = function(n, v) { + if (arguments.length < 2 && typeof n === 'string') { + return getNodeEl().style(n) + } else { + var args = Array.prototype.slice.call(arguments); + d3$$1.selection.prototype.style.apply(getNodeEl(), args); + } - function children(d) { - return d.c || d.children; - } + return tip + }; - function value(d) { - return d.v || d.value; - } + // Public: Set or get the direction of the tooltip + // + // v - One of n(north), s(south), e(east), or w(west), nw(northwest), + // sw(southwest), ne(northeast) or se(southeast) + // + // Returns tip or direction + tip.direction = function(v) { + if (!arguments.length) return direction + direction = v == null ? v : functor(v); + + return tip + }; + + // Public: Sets or gets the offset of the tip + // + // v - Array of [x, y] offset + // + // Returns offset or + tip.offset = function(v) { + if (!arguments.length) return offset + offset = v == null ? v : functor(v); - var label = function(d) { - return name(d) + " (" + d3.format(".3f")(100 * (d.x1 - d.x0), 3) + "%, " + value(d) + " samples)"; + return tip }; - function setDetails(t) { - if (details) - details.innerHTML = t; - } + // Public: sets or gets the html value of the tooltip + // + // v - String value of the tip + // + // Returns html value or tip + tip.html = function(v) { + if (!arguments.length) return html + html = v == null ? v : functor(v); - var colorMapper = function(d) { - return d.highlight ? "#E600E6" : colorHash(name(d)); + return tip }; - function generateHash(name) { - // Return a vector (0.0->1.0) that is a hash of the input string. - // The hash is computed to favor early characters over later ones, so - // that strings with similar starts have similar vectors. Only the first - // 6 characters are considered. - var hash = 0, weight = 1, max_hash = 0, mod = 10, max_char = 6; - if (name) { - for (var i = 0; i < name.length; i++) { - if (i > max_char) { break; } - hash += weight * (name.charCodeAt(i) % mod); - max_hash += weight * (mod - 1); - weight *= 0.70; - } - if (max_hash > 0) { hash = hash / max_hash; } + // Public: destroys the tooltip and removes it from the DOM + // + // Returns a tip + tip.destroy = function() { + if(node) { + getNodeEl().remove(); + node = null; } - return hash; - } + return tip; + }; - function colorHash(name) { - // Return an rgb() color string that is a hash of the provided name, - // and with a warm palette. - var vector = 0; - if (name) { - var nameArr = name.split('` + "`" + `'); - if (nameArr.length > 1) { - name = nameArr[nameArr.length -1]; // drop module name if present - } - name = name.split('(')[0]; // drop extra info - vector = generateHash(name); + function d3_tip_direction() { return 'n' } + function d3_tip_offset() { return [0, 0] } + function d3_tip_html() { return ' ' } + + var direction_callbacks = d3$$1.map({ + n: direction_n, + s: direction_s, + e: direction_e, + w: direction_w, + nw: direction_nw, + ne: direction_ne, + sw: direction_sw, + se: direction_se + }), + + directions = direction_callbacks.keys(); + + function direction_n() { + var bbox = getScreenBBox(); + return { + top: bbox.n.y - node.offsetHeight, + left: bbox.n.x - node.offsetWidth / 2 } - var r = 200 + Math.round(55 * vector); - var g = 0 + Math.round(230 * (1 - vector)); - var b = 0 + Math.round(55 * (1 - vector)); - return "rgb(" + r + "," + g + "," + b + ")"; } - function hide(d) { - d.data.hide = true; - if(children(d)) { - children(d).forEach(hide); + function direction_s() { + var bbox = getScreenBBox(); + return { + top: bbox.s.y, + left: bbox.s.x - node.offsetWidth / 2 } } - function show(d) { - d.data.fade = false; - d.data.hide = false; - if(children(d)) { - children(d).forEach(show); + function direction_e() { + var bbox = getScreenBBox(); + return { + top: bbox.e.y - node.offsetHeight / 2, + left: bbox.e.x } } - function getSiblings(d) { - var siblings = []; - if (d.parent) { - var me = d.parent.children.indexOf(d); - siblings = d.parent.children.slice(0); - siblings.splice(me, 1); + function direction_w() { + var bbox = getScreenBBox(); + return { + top: bbox.w.y - node.offsetHeight / 2, + left: bbox.w.x - node.offsetWidth } - return siblings; } - function hideSiblings(d) { - var siblings = getSiblings(d); - siblings.forEach(function(s) { - hide(s); - }); - if(d.parent) { - hideSiblings(d.parent); + function direction_nw() { + var bbox = getScreenBBox(); + return { + top: bbox.nw.y - node.offsetHeight, + left: bbox.nw.x - node.offsetWidth } } - function fadeAncestors(d) { - if(d.parent) { - d.parent.data.fade = true; - fadeAncestors(d.parent); + function direction_ne() { + var bbox = getScreenBBox(); + return { + top: bbox.ne.y - node.offsetHeight, + left: bbox.ne.x } } - function getRoot(d) { - if(d.parent) { - return getRoot(d.parent); + function direction_sw() { + var bbox = getScreenBBox(); + return { + top: bbox.sw.y, + left: bbox.sw.x - node.offsetWidth } - return d; } - function zoom(d) { - tip.hide(d); - hideSiblings(d); - show(d); - fadeAncestors(d); - update(); - if (typeof clickHandler === 'function') { - clickHandler(d); + function direction_se() { + var bbox = getScreenBBox(); + return { + top: bbox.se.y, + left: bbox.e.x } } - function searchTree(d, term) { - var re = new RegExp(term), - searchResults = []; + function initNode() { + var node = d3$$1.select(document.createElement('div')); + node.style('position', 'absolute').style('top', 0).style('opacity', 0) + .style('pointer-events', 'none').style('box-sizing', 'border-box'); - function searchInner(d) { - var label = name(d); + return node.node() + } - if (children(d)) { - children(d).forEach(function (child) { - searchInner(child); - }); - } + function getSVGNode(el) { + el = el.node(); + if(el.tagName.toLowerCase() === 'svg') + return el - if (label.match(re)) { - d.highlight = true; - searchResults.push(d); - } else { - d.highlight = false; - } + return el.ownerSVGElement + } + + function getNodeEl() { + if(node === null) { + node = initNode(); + // re-add node to DOM + document.body.appendChild(node); + } + return d3$$1.select(node); + } + + // Private - gets the screen coordinates of a shape + // + // Given a shape on the screen, will return an SVGPoint for the directions + // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest), + // sw(southwest). + // + // +-+-+ + // | | + // + + + // | | + // +-+-+ + // + // Returns an Object {n, s, e, w, nw, sw, ne, se} + function getScreenBBox() { + var targetel = target || d3$$1.event.target; + + while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) { + targetel = targetel.parentNode; } - searchInner(d); - return searchResults; + var bbox = {}, + matrix = targetel.getScreenCTM(), + tbbox = targetel.getBBox(), + width = tbbox.width, + height = tbbox.height, + x = tbbox.x, + y = tbbox.y; + + point.x = x; + point.y = y; + bbox.nw = point.matrixTransform(matrix); + point.x += width; + bbox.ne = point.matrixTransform(matrix); + point.y += height; + bbox.se = point.matrixTransform(matrix); + point.x -= width; + bbox.sw = point.matrixTransform(matrix); + point.y -= height / 2; + bbox.w = point.matrixTransform(matrix); + point.x += width; + bbox.e = point.matrixTransform(matrix); + point.x -= width / 2; + point.y -= height / 2; + bbox.n = point.matrixTransform(matrix); + point.y += height; + bbox.s = point.matrixTransform(matrix); + + return bbox + } + + // Private - replace D3JS 3.X d3.functor() function + function functor(v) { + return typeof v === "function" ? v : function() { + return v + } } - function clear(d) { - d.highlight = false; - if(children(d)) { - children(d).forEach(function(child) { - clear(child); - }); + return tip + }; + +})); +}); + +var flamegraph = function () { + var w = 960; // graph width + var h = null; // graph height + var c = 18; // cell height + var selection = null; // selection + var tooltip = true; // enable tooltip + var title = ''; // graph title + var transitionDuration = 750; + var transitionEase = d3.easeCubic; // tooltip offset + var sort = false; + var inverted = false; // invert the graph direction + var clickHandler = null; + var minFrameSize = 0; + var details = null; + + var tip = d3Tip() + .direction('s') + .offset([8, 0]) + .attr('class', 'd3-flame-graph-tip') + .html(function (d) { return label(d) }); + + var svg; + + function name (d) { + return d.data.n || d.data.name + } + + function libtype (d) { + return d.data.l || d.data.libtype + } + + function children (d) { + return d.c || d.children + } + + function value (d) { + return d.v || d.value + } + + var label = function (d) { + return name(d) + ' (' + d3.format('.3f')(100 * (d.x1 - d.x0), 3) + '%, ' + value(d) + ' samples)' + }; + + function setDetails (t) { + if (details) { details.innerHTML = t; } + } + + var colorMapper = function (d) { + return d.highlight ? '#E600E6' : colorHash(name(d), libtype(d)) + }; + + function generateHash (name) { + // Return a vector (0.0->1.0) that is a hash of the input string. + // The hash is computed to favor early characters over later ones, so + // that strings with similar starts have similar vectors. Only the first + // 6 characters are considered. + const MAX_CHAR = 6; + + var hash = 0; + var maxHash = 0; + var weight = 1; + var mod = 10; + + if (name) { + for (var i = 0; i < name.length; i++) { + if (i > MAX_CHAR) { break } + hash += weight * (name.charCodeAt(i) % mod); + maxHash += weight * (mod - 1); + weight *= 0.70; + } + if (maxHash > 0) { hash = hash / maxHash; } + } + return hash + } + + function colorHash (name, libtype) { + // Return a color for the given name and library type. The library type + // selects the hue, and the name is hashed to a color in that hue. + + var r; + var g; + var b; + + // Select hue. Order is important. + var hue; + if (typeof libtype === 'undefined' || libtype === '') { + // default when libtype is not in use + hue = 'warm'; + } else { + hue = 'red'; + if (name.match(/::/)) { + hue = 'yellow'; + } + if (libtype === 'kernel') { + hue = 'orange'; + } else if (libtype === 'jit') { + hue = 'green'; + } else if (libtype === 'inlined') { + hue = 'aqua'; } } - function doSort(a, b) { - if (typeof sort === 'function') { - return sort(a, b); - } else if (sort) { - return d3.ascending(name(a), name(b)); + // calculate hash + var vector = 0; + if (name) { + var nameArr = name.split('` + "`" + `'); + if (nameArr.length > 1) { + name = nameArr[nameArr.length - 1]; // drop module name if present } + name = name.split('(')[0]; // drop extra info + vector = generateHash(name); + } + + // calculate color + if (hue === 'red') { + r = 200 + Math.round(55 * vector); + g = 50 + Math.round(80 * vector); + b = g; + } else if (hue === 'orange') { + r = 190 + Math.round(65 * vector); + g = 90 + Math.round(65 * vector); + b = 0; + } else if (hue === 'yellow') { + r = 175 + Math.round(55 * vector); + g = r; + b = 50 + Math.round(20 * vector); + } else if (hue === 'green') { + r = 50 + Math.round(60 * vector); + g = 200 + Math.round(55 * vector); + b = r; + } else if (hue === 'aqua') { + r = 50 + Math.round(60 * vector); + g = 165 + Math.round(55 * vector); + b = g; + } else { + // original warm palette + r = 200 + Math.round(55 * vector); + g = 0 + Math.round(230 * (1 - vector)); + b = 0 + Math.round(55 * (1 - vector)); } - var partition = d3.partition(); + return 'rgb(' + r + ',' + g + ',' + b + ')' + } + + function hide (d) { + d.data.hide = true; + if (children(d)) { + children(d).forEach(hide); + } + } + + function show (d) { + d.data.fade = false; + d.data.hide = false; + if (children(d)) { + children(d).forEach(show); + } + } + + function getSiblings (d) { + var siblings = []; + if (d.parent) { + var me = d.parent.children.indexOf(d); + siblings = d.parent.children.slice(0); + siblings.splice(me, 1); + } + return siblings + } - function filterNodes(root) { - var nodeList = root.descendants(); - if (minFrameSize > 0) { - var kx = w / (root.x1 - root.x0); - nodeList = nodeList.filter(function(el) { - return ((el.x1 - el.x0) * kx) > minFrameSize; + function hideSiblings (d) { + var siblings = getSiblings(d); + siblings.forEach(function (s) { + hide(s); + }); + if (d.parent) { + hideSiblings(d.parent); + } + } + + function fadeAncestors (d) { + if (d.parent) { + d.parent.data.fade = true; + fadeAncestors(d.parent); + } + } + + // function getRoot (d) { + // if (d.parent) { + // return getRoot(d.parent) + // } + // return d + // } + + function zoom (d) { + tip.hide(d); + hideSiblings(d); + show(d); + fadeAncestors(d); + update(); + if (typeof clickHandler === 'function') { + clickHandler(d); + } + } + + function searchTree (d, term) { + var re = new RegExp(term); + var searchResults = []; + + function searchInner (d) { + var label = name(d); + + if (children(d)) { + children(d).forEach(function (child) { + searchInner(child); }); } - return nodeList; + + if (label.match(re)) { + d.highlight = true; + searchResults.push(d); + } else { + d.highlight = false; + } } - function update() { - selection.each(function(root) { - var x = d3.scaleLinear().range([0, w]), - y = d3.scaleLinear().range([0, c]); + searchInner(d); + return searchResults + } - if (sort) root.sort(doSort); - root.sum(function(d) { - if (d.fade || d.hide) { - return 0; - } - // The node's self value is its total value minus all children. - var v = value(d); - if (children(d)) { - var c = children(d); - for (var i = 0; i < c.length; i++) { - v -= value(c[i]); - } - } - return v; - }); - partition(root); - - var kx = w / (root.x1 - root.x0); - function width(d) { return (d.x1 - d.x0) * kx; } - - var descendants = filterNodes(root); - var g = d3.select(this).select("svg").selectAll("g").data(descendants, function(d) { return d.id; }); - - g.transition() - .duration(transitionDuration) - .ease(transitionEase) - .attr("transform", function(d) { return "translate(" + x(d.x0) + "," + (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; }); - - g.select("rect") - .attr("width", width); - - var node = g.enter() - .append("svg:g") - .attr("transform", function(d) { return "translate(" + x(d.x0) + "," + (reversed ? y(d.depth) : (h - y(d.depth) - c)) + ")"; }); - - node.append("svg:rect") - .transition() - .delay(transitionDuration / 2) - .attr("width", width); - - if (!tooltip) - node.append("svg:title"); - - node.append("foreignObject") - .append("xhtml:div"); - - // Now we have to re-select to see the new elements (why?). - g = d3.select(this).select("svg").selectAll("g").data(descendants, function(d) { return d.id; }); - - g.attr("width", width) - .attr("height", function(d) { return c; }) - .attr("name", function(d) { return name(d); }) - .attr("class", function(d) { return d.data.fade ? "frame fade" : "frame"; }); - - g.select("rect") - .attr("height", function(d) { return c; }) - .attr("fill", function(d) { return colorMapper(d); }); - - if (!tooltip) - g.select("title") - .text(label); - - g.select("foreignObject") - .attr("width", width) - .attr("height", function(d) { return c; }) - .select("div") - .attr("class", "d3-flame-graph-label") - .style("display", function(d) { return (width(d) < 35) ? "none" : "block";}) - .transition() - .delay(transitionDuration) - .text(name); - - g.on('click', zoom); - - g.exit() - .remove(); - - g.on('mouseover', function(d) { - if (tooltip) tip.show(d); - setDetails(label(d)); - }).on('mouseout', function(d) { - if (tooltip) tip.hide(d); - setDetails(""); - }); + function clear (d) { + d.highlight = false; + if (children(d)) { + children(d).forEach(function (child) { + clear(child); }); } + } - function merge(data, samples) { - samples.forEach(function (sample) { - var node = data.find(function (element) { - return (element.name === sample.name); - }); + function doSort (a, b) { + if (typeof sort === 'function') { + return sort(a, b) + } else if (sort) { + return d3.ascending(name(a), name(b)) + } + } - if (node) { - if (node.original) { - node.original += sample.value; - } else { - node.value += sample.value; - } - if (sample.children) { - if (!node.children) { - node.children = []; - } - merge(node.children, sample.children); + var p = d3.partition(); + + function filterNodes (root) { + var nodeList = root.descendants(); + if (minFrameSize > 0) { + var kx = w / (root.x1 - root.x0); + nodeList = nodeList.filter(function (el) { + return ((el.x1 - el.x0) * kx) > minFrameSize + }); + } + return nodeList + } + + function update () { + selection.each(function (root) { + var x = d3.scaleLinear().range([0, w]); + var y = d3.scaleLinear().range([0, c]); + + if (sort) root.sort(doSort); + root.sum(function (d) { + if (d.fade || d.hide) { + return 0 + } + // The node's self value is its total value minus all children. + var v = value(d); + if (children(d)) { + var c = children(d); + for (var i = 0; i < c.length; i++) { + v -= value(c[i]); } - } else { - data.push(sample); } + return v }); - } + p(root); - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } + var kx = w / (root.x1 - root.x0); + function width (d) { return (d.x1 - d.x0) * kx } - function injectIds(node) { - node.id = s4() + "-" + s4() + "-" + "-" + s4() + "-" + s4(); - var children = node.c || node.children || []; - for (var i = 0; i < children.length; i++) { - injectIds(children[i]); - } - } + var descendants = filterNodes(root); + var g = d3.select(this).select('svg').selectAll('g').data(descendants, function (d) { return d.id }); + + g.transition() + .duration(transitionDuration) + .ease(transitionEase) + .attr('transform', function (d) { return 'translate(' + x(d.x0) + ',' + (inverted ? y(d.depth) : (h - y(d.depth) - c)) + ')' }); - function chart(s) { - var root = d3.hierarchy( - s.datum(), function(d) { return children(d); } - ); - injectIds(root); - selection = s.datum(root); + g.select('rect') + .attr('width', width); - if (!arguments.length) return chart; + var node = g.enter() + .append('svg:g') + .attr('transform', function (d) { return 'translate(' + x(d.x0) + ',' + (inverted ? y(d.depth) : (h - y(d.depth) - c)) + ')' }); - if (!h) { - h = (root.height + 2) * c; + node.append('svg:rect') + .transition() + .delay(transitionDuration / 2) + .attr('width', width); + + if (!tooltip) { node.append('svg:title'); } + + node.append('foreignObject') + .append('xhtml:div'); + + // Now we have to re-select to see the new elements (why?). + g = d3.select(this).select('svg').selectAll('g').data(descendants, function (d) { return d.id }); + + g.attr('width', width) + .attr('height', function (d) { return c }) + .attr('name', function (d) { return name(d) }) + .attr('class', function (d) { return d.data.fade ? 'frame fade' : 'frame' }); + + g.select('rect') + .attr('height', function (d) { return c }) + .attr('fill', function (d) { return colorMapper(d) }); + + if (!tooltip) { + g.select('title') + .text(label); } - selection.each(function(data) { - - if (!svg) { - svg = d3.select(this) - .append("svg:svg") - .attr("width", w) - .attr("height", h) - .attr("class", "partition d3-flame-graph") - .call(tip); - - svg.append("svg:text") - .attr("class", "title") - .attr("text-anchor", "middle") - .attr("y", "25") - .attr("x", w/2) - .attr("fill", "#808080") - .text(title); - } + g.select('foreignObject') + .attr('width', width) + .attr('height', function (d) { return c }) + .select('div') + .attr('class', 'd3-flame-graph-label') + .style('display', function (d) { return (width(d) < 35) ? 'none' : 'block' }) + .transition() + .delay(transitionDuration) + .text(name); + + g.on('click', zoom); + + g.exit() + .remove(); + + g.on('mouseover', function (d) { + if (tooltip) tip.show(d, this); + setDetails(label(d)); + }).on('mouseout', function (d) { + if (tooltip) tip.hide(d); + setDetails(''); }); + }); + } - // first draw - update(); + function merge (data, samples) { + samples.forEach(function (sample) { + var node = data.find(function (element) { + return (element.name === sample.name) + }); + + if (node) { + if (node.original) { + node.original += sample.value; + } else { + node.value += sample.value; + } + if (sample.children) { + if (!node.children) { + node.children = []; + } + merge(node.children, sample.children); + } + } else { + data.push(sample); + } + }); + } + + function s4 () { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1) + } + + function injectIds (node) { + node.id = s4() + '-' + s4() + '-' + '-' + s4() + '-' + s4(); + var children = node.c || node.children || []; + for (var i = 0; i < children.length; i++) { + injectIds(children[i]); } + } - chart.height = function (_) { - if (!arguments.length) { return h; } - h = _; - return chart; - }; + function chart (s) { + var root = d3.hierarchy( + s.datum(), function (d) { return children(d) } + ); + injectIds(root); + selection = s.datum(root); - chart.width = function (_) { - if (!arguments.length) { return w; } - w = _; - return chart; - }; + if (!arguments.length) return chart - chart.cellHeight = function (_) { - if (!arguments.length) { return c; } - c = _; - return chart; - }; + if (!h) { + h = (root.height + 2) * c; + } - chart.tooltip = function (_) { - if (!arguments.length) { return tooltip; } - if (typeof _ === "function") { - tip = _; + selection.each(function (data) { + if (!svg) { + svg = d3.select(this) + .append('svg:svg') + .attr('width', w) + .attr('height', h) + .attr('class', 'partition d3-flame-graph') + .call(tip); + + svg.append('svg:text') + .attr('class', 'title') + .attr('text-anchor', 'middle') + .attr('y', '25') + .attr('x', w / 2) + .attr('fill', '#808080') + .text(title); } - tooltip = !!_; - return chart; - }; + }); - chart.title = function (_) { - if (!arguments.length) { return title; } - title = _; - return chart; - }; + // first draw + update(); + } - chart.transitionDuration = function (_) { - if (!arguments.length) { return transitionDuration; } - transitionDuration = _; - return chart; - }; + chart.height = function (_) { + if (!arguments.length) { return h } + h = _; + return chart + }; - chart.transitionEase = function (_) { - if (!arguments.length) { return transitionEase; } - transitionEase = _; - return chart; - }; + chart.width = function (_) { + if (!arguments.length) { return w } + w = _; + return chart + }; - chart.sort = function (_) { - if (!arguments.length) { return sort; } - sort = _; - return chart; - }; + chart.cellHeight = function (_) { + if (!arguments.length) { return c } + c = _; + return chart + }; - chart.reversed = function (_) { - if (!arguments.length) { return reversed; } - reversed = _; - return chart; - }; + chart.tooltip = function (_) { + if (!arguments.length) { return tooltip } + if (typeof _ === 'function') { + tip = _; + } + tooltip = !!_; + return chart + }; - chart.label = function(_) { - if (!arguments.length) { return label; } - label = _; - return chart; - }; + chart.title = function (_) { + if (!arguments.length) { return title } + title = _; + return chart + }; - chart.search = function(term) { - var searchResults = []; - selection.each(function(data) { - searchResults = searchTree(data, term); - update(); - }); - return searchResults; - }; + chart.transitionDuration = function (_) { + if (!arguments.length) { return transitionDuration } + transitionDuration = _; + return chart + }; - chart.clear = function() { - selection.each(function(data) { - clear(data); - update(); - }); - }; + chart.transitionEase = function (_) { + if (!arguments.length) { return transitionEase } + transitionEase = _; + return chart + }; - chart.zoomTo = function(d) { - zoom(d); - }; + chart.sort = function (_) { + if (!arguments.length) { return sort } + sort = _; + return chart + }; - chart.resetZoom = function() { - selection.each(function (data) { - zoom(data); // zoom to root - }); - }; + chart.inverted = function (_) { + if (!arguments.length) { return inverted } + inverted = _; + return chart + }; - chart.onClick = function(_) { - if (!arguments.length) { - return clickHandler; - } - clickHandler = _; - return chart; - }; - - chart.merge = function(samples) { - var newRoot; // Need to re-create hierarchy after data changes. - selection.each(function (root) { - merge([root.data], [samples]); - newRoot = d3.hierarchy(root.data, function(d) { return children(d); }); - injectIds(newRoot); - }); - selection = selection.datum(newRoot); + chart.label = function (_) { + if (!arguments.length) { return label } + label = _; + return chart + }; + + chart.search = function (term) { + var searchResults = []; + selection.each(function (data) { + searchResults = searchTree(data, term); update(); - }; - - chart.color = function(_) { - if (!arguments.length) { return colorMapper; } - colorMapper = _; - return chart; - }; + }); + return searchResults + }; - chart.minFrameSize = function (_) { - if (!arguments.length) { return minFrameSize; } - minFrameSize = _; - return chart; - }; + chart.clear = function () { + selection.each(function (data) { + clear(data); + update(); + }); + }; - chart.details = function (_) { - if (!arguments.length) { return details; } - details = _; - return chart; - }; + chart.zoomTo = function (d) { + zoom(d); + }; - return chart; - } + chart.resetZoom = function () { + selection.each(function (data) { + zoom(data); // zoom to root + }); + }; - // Node/CommonJS exports - if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') { - module.exports = flameGraph; - } else { - d3.flameGraph = flameGraph; - } -})(); + chart.onClick = function (_) { + if (!arguments.length) { + return clickHandler + } + clickHandler = _; + return chart + }; + + chart.merge = function (samples) { + var newRoot; // Need to re-create hierarchy after data changes. + selection.each(function (root) { + merge([root.data], [samples]); + newRoot = d3.hierarchy(root.data, function (d) { return children(d) }); + injectIds(newRoot); + }); + selection = selection.datum(newRoot); + update(); + }; + + chart.color = function (_) { + if (!arguments.length) { return colorMapper } + colorMapper = _; + return chart + }; + + chart.minFrameSize = function (_) { + if (!arguments.length) { return minFrameSize } + minFrameSize = _; + return chart + }; + + chart.details = function (_) { + if (!arguments.length) { return details } + details = _; + return chart + }; + + return chart +}; + +exports.flamegraph = flamegraph; + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); ` -// CSSSource returns the d3.flameGraph.css file +// CSSSource returns the d3-flamegraph.css file const CSSSource = ` .d3-flame-graph rect { stroke: #EEEEEE; diff --git a/src/cmd/vendor/github.com/google/pprof/third_party/d3tip/LICENSE b/src/cmd/vendor/github.com/google/pprof/third_party/d3tip/LICENSE deleted file mode 100644 index 2b5c6194ac..0000000000 --- a/src/cmd/vendor/github.com/google/pprof/third_party/d3tip/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -The MIT License (MIT) -Copyright (c) 2013 Justin Palmer - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/src/cmd/vendor/github.com/google/pprof/third_party/d3tip/d3_tip.go b/src/cmd/vendor/github.com/google/pprof/third_party/d3tip/d3_tip.go deleted file mode 100644 index 276cde362a..0000000000 --- a/src/cmd/vendor/github.com/google/pprof/third_party/d3tip/d3_tip.go +++ /dev/null @@ -1,328 +0,0 @@ -// Tooltips for d3.js visualizations -// https://github.com/Caged/d3-tip -// Version 0.7.1 -// See LICENSE file for license details - -package d3tip - -// JSSource returns the d3-tip.js file -const JSSource = ` -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module with d3 as a dependency. - define(['d3'], factory) - } else if (typeof module === 'object' && module.exports) { - // CommonJS - var d3 = require('d3') - module.exports = factory(d3) - } else { - // Browser global. - root.d3.tip = factory(root.d3) - } -}(this, function (d3) { - - // Public - contructs a new tooltip - // - // Returns a tip - return function() { - var direction = d3_tip_direction, - offset = d3_tip_offset, - html = d3_tip_html, - node = initNode(), - svg = null, - point = null, - target = null - - function tip(vis) { - svg = getSVGNode(vis) - point = svg.createSVGPoint() - document.body.appendChild(node) - } - - // Public - show the tooltip on the screen - // - // Returns a tip - tip.show = function() { - var args = Array.prototype.slice.call(arguments) - if(args[args.length - 1] instanceof SVGElement) target = args.pop() - - var content = html.apply(this, args), - poffset = offset.apply(this, args), - dir = direction.apply(this, args), - nodel = getNodeEl(), - i = directions.length, - coords, - scrollTop = document.documentElement.scrollTop || document.body.scrollTop, - scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft - - nodel.html(content) - .style('opacity', 1).style('pointer-events', 'all') - - while(i--) nodel.classed(directions[i], false) - coords = direction_callbacks.get(dir).apply(this) - nodel.classed(dir, true) - .style('top', (coords.top + poffset[0]) + scrollTop + 'px') - .style('left', (coords.left + poffset[1]) + scrollLeft + 'px') - - return tip; - }; - - // Public - hide the tooltip - // - // Returns a tip - tip.hide = function() { - var nodel = getNodeEl() - nodel.style('opacity', 0).style('pointer-events', 'none') - return tip - } - - // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. - // - // n - name of the attribute - // v - value of the attribute - // - // Returns tip or attribute value - tip.attr = function(n, v) { - if (arguments.length < 2 && typeof n === 'string') { - return getNodeEl().attr(n) - } else { - var args = Array.prototype.slice.call(arguments) - d3.selection.prototype.attr.apply(getNodeEl(), args) - } - - return tip - } - - // Public: Proxy style calls to the d3 tip container. Sets or gets a style value. - // - // n - name of the property - // v - value of the property - // - // Returns tip or style property value - tip.style = function(n, v) { - if (arguments.length < 2 && typeof n === 'string') { - return getNodeEl().style(n) - } else { - var args = Array.prototype.slice.call(arguments) - d3.selection.prototype.style.apply(getNodeEl(), args) - } - - return tip - } - - // Public: Set or get the direction of the tooltip - // - // v - One of n(north), s(south), e(east), or w(west), nw(northwest), - // sw(southwest), ne(northeast) or se(southeast) - // - // Returns tip or direction - tip.direction = function(v) { - if (!arguments.length) return direction - direction = v == null ? v : functor(v) - - return tip - } - - // Public: Sets or gets the offset of the tip - // - // v - Array of [x, y] offset - // - // Returns offset or - tip.offset = function(v) { - if (!arguments.length) return offset - offset = v == null ? v : functor(v) - - return tip - } - - // Public: sets or gets the html value of the tooltip - // - // v - String value of the tip - // - // Returns html value or tip - tip.html = function(v) { - if (!arguments.length) return html - html = v == null ? v : functor(v) - - return tip - } - - // Public: destroys the tooltip and removes it from the DOM - // - // Returns a tip - tip.destroy = function() { - if(node) { - getNodeEl().remove(); - node = null; - } - return tip; - } - - function d3_tip_direction() { return 'n' } - function d3_tip_offset() { return [0, 0] } - function d3_tip_html() { return ' ' } - - var direction_callbacks = d3.map({ - n: direction_n, - s: direction_s, - e: direction_e, - w: direction_w, - nw: direction_nw, - ne: direction_ne, - sw: direction_sw, - se: direction_se - }), - - directions = direction_callbacks.keys() - - function direction_n() { - var bbox = getScreenBBox() - return { - top: bbox.n.y - node.offsetHeight, - left: bbox.n.x - node.offsetWidth / 2 - } - } - - function direction_s() { - var bbox = getScreenBBox() - return { - top: bbox.s.y, - left: bbox.s.x - node.offsetWidth / 2 - } - } - - function direction_e() { - var bbox = getScreenBBox() - return { - top: bbox.e.y - node.offsetHeight / 2, - left: bbox.e.x - } - } - - function direction_w() { - var bbox = getScreenBBox() - return { - top: bbox.w.y - node.offsetHeight / 2, - left: bbox.w.x - node.offsetWidth - } - } - - function direction_nw() { - var bbox = getScreenBBox() - return { - top: bbox.nw.y - node.offsetHeight, - left: bbox.nw.x - node.offsetWidth - } - } - - function direction_ne() { - var bbox = getScreenBBox() - return { - top: bbox.ne.y - node.offsetHeight, - left: bbox.ne.x - } - } - - function direction_sw() { - var bbox = getScreenBBox() - return { - top: bbox.sw.y, - left: bbox.sw.x - node.offsetWidth - } - } - - function direction_se() { - var bbox = getScreenBBox() - return { - top: bbox.se.y, - left: bbox.e.x - } - } - - function initNode() { - var node = d3.select(document.createElement('div')); - node.style('position', 'absolute').style('top', 0).style('opacity', 0) - .style('pointer-events', 'none').style('box-sizing', 'border-box') - - return node.node() - } - - function getSVGNode(el) { - el = el.node() - if(el.tagName.toLowerCase() === 'svg') - return el - - return el.ownerSVGElement - } - - function getNodeEl() { - if(node === null) { - node = initNode(); - // re-add node to DOM - document.body.appendChild(node); - }; - return d3.select(node); - } - - // Private - gets the screen coordinates of a shape - // - // Given a shape on the screen, will return an SVGPoint for the directions - // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest), - // sw(southwest). - // - // +-+-+ - // | | - // + + - // | | - // +-+-+ - // - // Returns an Object {n, s, e, w, nw, sw, ne, se} - function getScreenBBox() { - var targetel = target || d3.event.target; - - while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) { - targetel = targetel.parentNode; - } - - var bbox = {}, - matrix = targetel.getScreenCTM(), - tbbox = targetel.getBBox(), - width = tbbox.width, - height = tbbox.height, - x = tbbox.x, - y = tbbox.y - - point.x = x - point.y = y - bbox.nw = point.matrixTransform(matrix) - point.x += width - bbox.ne = point.matrixTransform(matrix) - point.y += height - bbox.se = point.matrixTransform(matrix) - point.x -= width - bbox.sw = point.matrixTransform(matrix) - point.y -= height / 2 - bbox.w = point.matrixTransform(matrix) - point.x += width - bbox.e = point.matrixTransform(matrix) - point.x -= width / 2 - point.y -= height / 2 - bbox.n = point.matrixTransform(matrix) - point.y += height - bbox.s = point.matrixTransform(matrix) - - return bbox - } - - // Private - replace D3JS 3.X d3.functor() function - function functor(v) { - return typeof v === "function" ? v : function() { - return v - } - } - - return tip - }; - -})); -` diff --git a/src/cmd/vendor/github.com/google/pprof/third_party/update_d3flamegraph.sh b/src/cmd/vendor/github.com/google/pprof/third_party/update_d3flamegraph.sh new file mode 100755 index 0000000000..d742b188f1 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/third_party/update_d3flamegraph.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -eu +set -o pipefail + +D3FLAMEGRAPH_REPO="https://raw.githubusercontent.com/spiermar/d3-flame-graph" +D3FLAMEGRAPH_VERSION="2.0.0-alpha4" +D3FLAMEGRAPH_JS="d3-flamegraph.js" +D3FLAMEGRAPH_CSS="d3-flamegraph.css" + +cd $(dirname $0) + +D3FLAMEGRAPH_DIR=d3flamegraph + +generate_d3flamegraph_go() { + local d3_js=$(curl -s "${D3FLAMEGRAPH_REPO}/${D3FLAMEGRAPH_VERSION}/dist/${D3FLAMEGRAPH_JS}" | sed 's/`/`+"`"+`/g') + local d3_css=$(curl -s "${D3FLAMEGRAPH_REPO}/${D3FLAMEGRAPH_VERSION}/dist/${D3FLAMEGRAPH_CSS}") + + cat <<-EOF > $D3FLAMEGRAPH_DIR/d3_flame_graph.go +// A D3.js plugin that produces flame graphs from hierarchical data. +// https://github.com/spiermar/d3-flame-graph +// Version $D3FLAMEGRAPH_VERSION +// See LICENSE file for license details + +package d3flamegraph + +// JSSource returns the $D3FLAMEGRAPH_JS file +const JSSource = \` +$d3_js +\` + +// CSSSource returns the $D3FLAMEGRAPH_CSS file +const CSSSource = \` +$d3_css +\` +EOF + gofmt -w $D3FLAMEGRAPH_DIR/d3_flame_graph.go +} + +get_license() { + curl -s -o $D3FLAMEGRAPH_DIR/LICENSE "${D3FLAMEGRAPH_REPO}/${D3FLAMEGRAPH_VERSION}/LICENSE" +} + +mkdir -p $D3FLAMEGRAPH_DIR +get_license +generate_d3flamegraph_go |