]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/vendor/.../pprof: update to 520140b6bf47519c766e8380e5f094576347b016
authorHana (Hyang-Ah) Kim <hyangah@gmail.com>
Wed, 9 May 2018 01:49:58 +0000 (09:49 +0800)
committerHyang-Ah Hana Kim <hyangah@gmail.com>
Wed, 9 May 2018 16:42:28 +0000 (16:42 +0000)
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>

33 files changed:
src/cmd/vendor/github.com/google/pprof/README.md
src/cmd/vendor/github.com/google/pprof/doc/README.md [moved from src/cmd/vendor/github.com/google/pprof/doc/pprof.md with 99% similarity]
src/cmd/vendor/github.com/google/pprof/doc/developer/pprof.dev.md [deleted file]
src/cmd/vendor/github.com/google/pprof/internal/driver/cli.go
src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go
src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go
src/cmd/vendor/github.com/google/pprof/internal/driver/driver_focus.go
src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go
src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go
src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go
src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph_test.go [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go
src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go
src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go
src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec_test.go
src/cmd/vendor/github.com/google/pprof/internal/report/report.go
src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go
src/cmd/vendor/github.com/google/pprof/internal/report/source.go
src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go
src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go
src/cmd/vendor/github.com/google/pprof/pprof.go
src/cmd/vendor/github.com/google/pprof/profile/filter.go
src/cmd/vendor/github.com/google/pprof/profile/filter_test.go [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/profile/merge.go
src/cmd/vendor/github.com/google/pprof/profile/merge_test.go [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/profile/profile_test.go
src/cmd/vendor/github.com/google/pprof/proto/README.md [moved from src/cmd/vendor/github.com/google/pprof/doc/developer/profile.proto.md with 100% similarity]
src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/d3_flame_graph.go
src/cmd/vendor/github.com/google/pprof/third_party/d3tip/LICENSE [deleted file]
src/cmd/vendor/github.com/google/pprof/third_party/d3tip/d3_tip.go [deleted file]
src/cmd/vendor/github.com/google/pprof/third_party/update_d3flamegraph.sh [new file with mode: 0755]
src/cmd/vendor/vendor.json

index e8c2f0329da01957a3322fbaf640f1d60f7a039e..81e5d3939dda799c81c24cf3b83ce5fad57249d2 100644 (file)
@@ -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.
similarity index 99%
rename from src/cmd/vendor/github.com/google/pprof/doc/pprof.md
rename to src/cmd/vendor/github.com/google/pprof/doc/README.md
index 57613fad9637a6e3ee9bb0769d1b81584706f75b..de7c393e78fcb3b4c2f218acc4289a14a4a40682 100644 (file)
@@ -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 (file)
index b2a197f..0000000
+++ /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
index e2e8c6c93684d9c594de92e3490e0c99949f40db..c3c22e7c96b4336f4ff21da9f96b38476fda9646 100644 (file)
@@ -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"
index 16b0b0a3b55a3926fb517caa5b50828aa7eade10..91d32d1e716d47e390e867c458e5286e2f863e8c 100644 (file)
@@ -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.",
index 3b7439fc9a4e7848448a1d6aa2ecae0a565cb390..acc0b4ad8ac993f7da99736abfe27751f36c9030 100644 (file)
@@ -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 != "" {
index ba5b502ad9e29e8b4eb2ba8080e9121cb95a985b..b23ee8105853b45218332a1fa8feee45fe25fbc1 100644 (file)
@@ -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)
index 06219eae644b9938af57bfc4e8144c83a310373e..309e9950b667a2cd7bf4e1b2c4198391462b4faf 100644 (file)
@@ -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"})
index 1b34e70beaa639a1359075bd409f37aea817a59e..7c576de61498b57af9ea54e1e1bb741cb1ce35e0 100644 (file)
@@ -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)
index 29a41011bbabe8f60cf60887ebd77eca7f091405..c9b9a5398f4dbbeaa3d88a3822b456d9e519a911 100644 (file)
@@ -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 (file)
index 0000000..c1a887c
--- /dev/null
@@ -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 (file)
index 0000000..112b49b
--- /dev/null
@@ -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
+----------------------------------------------------------+-------------
index e9bc8720356456553486a3cf53594a0ecf7a4c89..c3f9c384f8a663d70c1ec0ca76c739b04e5fef7b 100644 (file)
@@ -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)
index 328f1596d9fbfd2a13c1f020e72d196141397d78..58681bea8f1bf488e8e7e6b16ffdb713c3e024db 100644 (file)
@@ -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 {
index 297bb24b1ce0efea09198e8220e134b8efb34e33..4750ec81298838b2de61f543f025953e6cd880d5 100644 (file)
@@ -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
index c6b8fe4c22e7316ea2f48f88779d2da63989b941..71edd8e51b181ce7b2ee8830ae5ac6304bc8138e 100644 (file)
@@ -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},
index e127f7fcecf857da1396c883adb1f2f14cfb86d1..15cadfb5480693eadbfc5366203bb82060808b42 100644 (file)
@@ -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
index c243e20c2b3b01f0e6f47e3ba9c2595043436588..49c6e4934f5c20f64733ac18ca256a514b72bf46 100644 (file)
@@ -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",
        },
 }
 
index 529583997360aea214832098eac7b3fb5e5bc6fb..835badfcae2eb1759c833109b2aab1ae062ef634 100644 (file)
@@ -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
index 682bfe0a1e338d6e48f9343281efdda9521b1fba..f1dd5c70dda0854af52b0d1d2bdf288d86fb1da7 100644 (file)
@@ -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)
                                }
                        }
                })
index 086c0ccb043bf7bbd7921796f6651096bce84779..638c4968ec2631483c075e76e83d4f3407ff38db 100644 (file)
@@ -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]...)
index 57ec28a32c6855fcff9c3eab693a9248d57730a6..df4f831b760be5f605e11d66c8a7f9d8a85b2f37 100644 (file)
@@ -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.
+}
index f857fdf8f86e4e7b356393c4d4e0d80d9b36cef2..ea8e66c68d25546dd2f5cd1a882c5261f526a3c4 100644 (file)
@@ -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 (file)
index 0000000..3fd1787
--- /dev/null
@@ -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)
+               }
+       }
+}
index a5440b521e519a816284b6566266807610486f8f..4dcc27f48eb0d7284c350fae59524bec8600b7ed 100644 (file)
@@ -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 (file)
index 0000000..6a04db2
--- /dev/null
@@ -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)
+                               }
+                       }
+               })
+       }
+}
index ab020275cb1c364fb7c5deb858a2b0002f645559..8ed67b1dd6c0ec8445fa4b5252a41c4e1be4bf32 100644 (file)
@@ -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
index d340f7076f1ad691bb7e8ef1c28443e3fefe6059..58a7fb4c40a8a9a5e3f09cc2f9b644925843fd31 100644 (file)
 // 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 (file)
index 2b5c619..0000000
+++ /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 (file)
index 276cde3..0000000
+++ /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 (executable)
index 0000000..d742b18
--- /dev/null
@@ -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
index 866466a27e274c3d5d373bb5b4f152f25b902429..82ba2a10be2203d2ac258785d12e6a0f4ffa8b7e 100644 (file)
@@ -9,8 +9,8 @@
                {
                        "canonical": "github.com/google/pprof",
                        "local": "github.com/google/pprof",
-                       "revision": "9e20b5b106e946f4cd1df94c1f6fe3f88456628d",
-                       "revisionTime": "2017-11-08T17:47:23Z"
+                       "revision": "520140b6bf47519c766e8380e5f094576347b016",
+                       "revisionTime": "2018-05-08T15:00:43Z"
                },
                {
                        "canonical": "golang.org/x/arch/x86/x86asm",