aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/github.com/google/pprof/internal/driver
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/vendor/github.com/google/pprof/internal/driver')
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go3
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go2
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go10
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go9
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go99
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go23
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/interactive_test.go58
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/svg.go80
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist4
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go1200
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go79
-rw-r--r--src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go77
12 files changed, 1025 insertions, 619 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go
index 66e5c86b9d..16b0b0a3b5 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go
@@ -28,7 +28,6 @@ import (
"github.com/google/pprof/internal/plugin"
"github.com/google/pprof/internal/report"
- "github.com/google/pprof/third_party/svg"
)
// commands describes the commands accepted by pprof.
@@ -398,7 +397,7 @@ func massageDotSVG() PostProcessor {
if err := generateSVG(input, baseSVG, ui); err != nil {
return err
}
- _, err := output.Write([]byte(svg.Massage(baseSVG.String())))
+ _, err := output.Write([]byte(massageSVG(baseSVG.String())))
return err
}
}
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go
index bc5f366128..c2b1cd082b 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go
@@ -54,7 +54,7 @@ func PProf(eo *plugin.Options) error {
}
if src.HTTPHostport != "" {
- return serveWebInterface(src.HTTPHostport, p, o)
+ return serveWebInterface(src.HTTPHostport, p, o, true)
}
return interactive(p, o)
}
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go
index 1289a096b8..0604da911c 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go
@@ -1487,8 +1487,14 @@ func (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error)
switch r.String() {
case "line[13]":
return []*plugin.Sym{
- {[]string{"line1000"}, m.name, 0x1000, 0x1003},
- {[]string{"line3000"}, m.name, 0x3000, 0x3004},
+ {
+ Name: []string{"line1000"}, File: m.name,
+ Start: 0x1000, End: 0x1003,
+ },
+ {
+ Name: []string{"line3000"}, File: m.name,
+ Start: 0x3000, End: 0x3004,
+ },
}, nil
}
return nil, fmt.Errorf("unimplemented")
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go
index abce5b5c70..c80a0dbc1d 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/fetch_test.go
@@ -423,16 +423,9 @@ func TestHttpsInsecure(t *testing.T) {
Timeout: 10,
Symbolize: "remote",
}
- rx := "Saved profile in"
- if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") ||
- runtime.GOOS == "android" {
- // On iOS, $HOME points to the app root directory and is not writable.
- // On Android, $HOME points to / which is not writable.
- rx += "|Could not use temp dir"
- }
o := &plugin.Options{
Obj: &binutils.Binutils{},
- UI: &proftest.TestUI{T: t, AllowRx: rx},
+ UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"},
}
o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI}
p, err := fetchProfiles(s, o)
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go
new file mode 100644
index 0000000000..10588d6262
--- /dev/null
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go
@@ -0,0 +1,99 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package driver
+
+import (
+ "encoding/json"
+ "html/template"
+ "net/http"
+ "strings"
+
+ "github.com/google/pprof/internal/graph"
+ "github.com/google/pprof/internal/measurement"
+ "github.com/google/pprof/internal/report"
+)
+
+type treeNode struct {
+ Name string `json:"n"`
+ Cum int64 `json:"v"`
+ CumFormat string `json:"l"`
+ Percent string `json:"p"`
+ Children []*treeNode `json:"c"`
+}
+
+// flamegraph generates a web page containing a flamegraph.
+func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
+ // Force the call tree so that the graph is a tree.
+ // Also do not trim the tree so that the flame graph contains all functions.
+ rpt, errList := ui.makeReport(w, req, []string{"svg"}, "call_tree", "true", "trim", "false")
+ if rpt == nil {
+ return // error already reported
+ }
+
+ // Generate dot graph.
+ g, config := report.GetDOT(rpt)
+ var nodes []*treeNode
+ nroots := 0
+ rootValue := int64(0)
+ nodeArr := []string{}
+ nodeMap := map[*graph.Node]*treeNode{}
+ // Make all nodes and the map, collect the roots.
+ for _, n := range g.Nodes {
+ v := n.CumValue()
+ node := &treeNode{
+ Name: n.Info.PrintableName(),
+ Cum: v,
+ CumFormat: config.FormatValue(v),
+ Percent: strings.TrimSpace(measurement.Percentage(v, config.Total)),
+ }
+ nodes = append(nodes, node)
+ if len(n.In) == 0 {
+ nodes[nroots], nodes[len(nodes)-1] = nodes[len(nodes)-1], nodes[nroots]
+ nroots++
+ rootValue += v
+ }
+ nodeMap[n] = node
+ // Get all node names into an array.
+ nodeArr = append(nodeArr, n.Info.Name)
+ }
+ // Populate the child links.
+ for _, n := range g.Nodes {
+ node := nodeMap[n]
+ for child := range n.Out {
+ node.Children = append(node.Children, nodeMap[child])
+ }
+ }
+
+ rootNode := &treeNode{
+ Name: "root",
+ Cum: rootValue,
+ CumFormat: config.FormatValue(rootValue),
+ Percent: strings.TrimSpace(measurement.Percentage(rootValue, config.Total)),
+ Children: nodes[0:nroots],
+ }
+
+ // JSON marshalling flame graph
+ b, err := json.Marshal(rootNode)
+ if err != nil {
+ http.Error(w, "error serializing flame graph", http.StatusInternalServerError)
+ ui.options.UI.PrintErr(err)
+ return
+ }
+
+ ui.render(w, "/flamegraph", "flamegraph", rpt, errList, config.Labels, webArgs{
+ FlameGraph: template.JS(b),
+ Nodes: nodeArr,
+ })
+}
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go
index 2c36b64cc7..b893697b62 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go
@@ -42,6 +42,9 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
interactiveMode = true
shortcuts := profileShortcuts(p)
+ // Get all groups in pprofVariables to allow for clearer error messages.
+ groups := groupOptions(pprofVariables)
+
greetings(p, o.UI)
for {
input, err := o.UI.ReadLine("(pprof) ")
@@ -87,6 +90,9 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
o.UI.PrintErr(err)
}
continue
+ } else if okValues := groups[name]; okValues != nil {
+ o.UI.PrintErr(fmt.Errorf("Unrecognized value for %s: %q. Use one of %s", name, value, strings.Join(okValues, ", ")))
+ continue
}
}
@@ -118,6 +124,23 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
}
}
+// groupOptions returns a map containing all non-empty groups
+// mapped to an array of the option names in that group in
+// sorted order.
+func groupOptions(vars variables) map[string][]string {
+ groups := make(map[string][]string)
+ for name, option := range vars {
+ group := option.group
+ if group != "" {
+ groups[group] = append(groups[group], name)
+ }
+ }
+ for _, names := range groups {
+ sort.Strings(names)
+ }
+ return groups
+}
+
var generateReportWrapper = generateReport // For testing purposes.
// greetings prints a brief welcome and some overall profile
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive_test.go
index ba80741a35..db26862c7d 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive_test.go
@@ -16,12 +16,12 @@ package driver
import (
"fmt"
- "io"
"math/rand"
"strings"
"testing"
"github.com/google/pprof/internal/plugin"
+ "github.com/google/pprof/internal/proftest"
"github.com/google/pprof/internal/report"
"github.com/google/pprof/profile"
)
@@ -70,6 +70,21 @@ func TestShell(t *testing.T) {
t.Error("second shortcut attempt:", err)
}
+ // Group with invalid value
+ pprofVariables = testVariables(savedVariables)
+ ui := &proftest.TestUI{
+ T: t,
+ Input: []string{"cumulative=this"},
+ AllowRx: `Unrecognized value for cumulative: "this". Use one of cum, flat`,
+ }
+ o.UI = ui
+ if err := interactive(p, o); err != nil {
+ t.Error("invalid group value:", err)
+ }
+ // Confirm error message written out once.
+ if ui.NumAllowRxMatches != 1 {
+ t.Errorf("want error message to be printed 1 time, got %v", ui.NumAllowRxMatches)
+ }
// Verify propagation of IO errors
pprofVariables = testVariables(savedVariables)
o.UI = newUI(t, []string{"**error**"})
@@ -144,45 +159,10 @@ func makeShortcuts(input []string, seed int) (shortcuts, []string) {
}
func newUI(t *testing.T, input []string) plugin.UI {
- return &testUI{
- t: t,
- input: input,
- }
-}
-
-type testUI struct {
- t *testing.T
- input []string
- index int
-}
-
-func (ui *testUI) ReadLine(_ string) (string, error) {
- if ui.index >= len(ui.input) {
- return "", io.EOF
+ return &proftest.TestUI{
+ T: t,
+ Input: input,
}
- input := ui.input[ui.index]
- if input == "**error**" {
- return "", fmt.Errorf("Error: %s", input)
- }
- ui.index++
- return input, nil
-}
-
-func (ui *testUI) Print(args ...interface{}) {
-}
-
-func (ui *testUI) PrintErr(args ...interface{}) {
- output := fmt.Sprint(args)
- if output != "" {
- ui.t.Error(output)
- }
-}
-
-func (ui *testUI) IsTerminal() bool {
- return false
-}
-
-func (ui *testUI) SetAutoComplete(func(string) string) {
}
func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/svg.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/svg.go
new file mode 100644
index 0000000000..62767e726d
--- /dev/null
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/svg.go
@@ -0,0 +1,80 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package driver
+
+import (
+ "regexp"
+ "strings"
+
+ "github.com/google/pprof/third_party/svgpan"
+)
+
+var (
+ viewBox = regexp.MustCompile(`<svg\s*width="[^"]+"\s*height="[^"]+"\s*viewBox="[^"]+"`)
+ graphID = regexp.MustCompile(`<g id="graph\d"`)
+ svgClose = regexp.MustCompile(`</svg>`)
+)
+
+// massageSVG enhances the SVG output from DOT to provide better
+// panning inside a web browser. It uses the svgpan library, which is
+// embedded into the svgpan.JSSource variable.
+func massageSVG(svg string) string {
+ // Work around for dot bug which misses quoting some ampersands,
+ // resulting on unparsable SVG.
+ svg = strings.Replace(svg, "&;", "&amp;;", -1)
+
+ // Dot's SVG output is
+ //
+ // <svg width="___" height="___"
+ // viewBox="___" xmlns=...>
+ // <g id="graph0" transform="...">
+ // ...
+ // </g>
+ // </svg>
+ //
+ // Change it to
+ //
+ // <svg width="100%" height="100%"
+ // xmlns=...>
+
+ // <script type="text/ecmascript"><![CDATA[` ..$(svgpan.JSSource)... `]]></script>`
+ // <g id="viewport" transform="translate(0,0)">
+ // <g id="graph0" transform="...">
+ // ...
+ // </g>
+ // </g>
+ // </svg>
+
+ if loc := viewBox.FindStringIndex(svg); loc != nil {
+ svg = svg[:loc[0]] +
+ `<svg width="100%" height="100%"` +
+ svg[loc[1]:]
+ }
+
+ if loc := graphID.FindStringIndex(svg); loc != nil {
+ svg = svg[:loc[0]] +
+ `<script type="text/ecmascript"><![CDATA[` + string(svgpan.JSSource) + `]]></script>` +
+ `<g id="viewport" transform="scale(0.5,0.5) translate(0,0)">` +
+ svg[loc[0]:]
+ }
+
+ if loc := svgClose.FindStringIndex(svg); loc != nil {
+ svg = svg[:loc[0]] +
+ `</g>` +
+ svg[loc[0]:]
+ }
+
+ return svg
+}
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist
index befc412db3..0284292745 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist
@@ -61,7 +61,7 @@ function pprof_toggle_asm(e) {
<div class="legend">File: testbinary<br>
Type: cpu<br>
-Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000</h1>testdata/file1000.src
+Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h2>line1000</h2><p class="filename">testdata/file1000.src</p>
<pre onClick="pprof_toggle_asm(event)">
Total: 1.10s 1.10s (flat, cum) 98.21%
<span class=line> 1</span> <span class=deadsrc> 1.10s 1.10s line1 </span><span class=asm> 1.10s 1.10s 1000: instruction one <span class=unimportant>file1000.src:1</span>
@@ -77,7 +77,7 @@ Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000<
<span class=line> 6</span> <span class=nop> . . line6 </span>
<span class=line> 7</span> <span class=nop> . . line7 </span>
</pre>
-<h1>line3000</h1>testdata/file3000.src
+<h2>line3000</h2><p class="filename">testdata/file3000.src</p>
<pre onClick="pprof_toggle_asm(event)">
Total: 10ms 1.12s (flat, cum) 100%
<span class=line> 1</span> <span class=nop> . . line1 </span>
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go
index 48f0fa1cfa..5d2821cd42 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go
@@ -16,208 +16,267 @@ 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(`
{{define "css"}}
<style type="text/css">
-html {
- height: 100%;
- min-height: 100%;
- margin: 0px;
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
}
-body {
- margin: 0px;
- width: 100%;
+html, body {
height: 100%;
- min-height: 100%;
- overflow: hidden;
}
-#graphcontainer {
+body {
+ font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+ font-size: 13px;
+ line-height: 1.4;
display: flex;
flex-direction: column;
- height: 100%;
- min-height: 100%;
- width: 100%;
- min-width: 100%;
- margin: 0px;
}
-#graph {
- flex: 1 1 auto;
- overflow: hidden;
+a {
+ color: #2a66d9;
+}
+.header {
+ display: flex;
+ align-items: center;
+ height: 44px;
+ min-height: 44px;
+ background-color: #eee;
+ color: #212121;
+ padding: 0 1rem;
+}
+.header > div {
+ margin: 0 0.125em;
+}
+.header .title h1 {
+ font-size: 1.75em;
+ margin-right: 1rem;
+}
+.header .title a {
+ color: #212121;
+ text-decoration: none;
}
-svg {
+.header .title a:hover {
+ text-decoration: underline;
+}
+.header .description {
width: 100%;
- height: auto;
+ text-align: right;
+ white-space: nowrap;
}
-button {
- margin-top: 5px;
- margin-bottom: 5px;
+@media screen and (max-width: 799px) {
+ .header input {
+ display: none;
+ }
}
-#detailtext {
+#detailsbox {
display: none;
+ z-index: 1;
position: fixed;
- top: 20px;
- right: 10px;
+ top: 40px;
+ right: 20px;
background-color: #ffffff;
- min-width: 160px;
- border: 1px solid #888;
- box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2);
- z-index: 1;
-}
-#closedetails {
- float: right;
- margin: 2px;
+ box-shadow: 0 1px 5px rgba(0,0,0,.3);
+ line-height: 24px;
+ padding: 1em;
+ text-align: left;
}
-#home {
- font-size: 14pt;
- padding-left: 0.5em;
- padding-right: 0.5em;
- float: right;
+.header input {
+ background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' style='pointer-events:none;display:block;width:100%25;height:100%25;fill:#757575'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61.0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat 4px center/20px 20px;
+ border: 1px solid #d1d2d3;
+ border-radius: 2px 0 0 2px;
+ padding: 0.25em;
+ padding-left: 28px;
+ margin-left: 1em;
+ font-family: 'Roboto', 'Noto', sans-serif;
+ font-size: 1em;
+ line-height: 24px;
+ color: #212121;
}
-.menubar {
- display: inline-block;
- background-color: #f8f8f8;
- border: 1px solid #ccc;
- width: 100%;
+.downArrow {
+ border-top: .36em solid #ccc;
+ border-left: .36em solid transparent;
+ border-right: .36em solid transparent;
+ margin-bottom: .05em;
+ margin-left: .5em;
+ transition: border-top-color 200ms;
}
-.menu-header {
+.menu-item {
+ height: 100%;
+ text-transform: uppercase;
+ font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
position: relative;
- display: inline-block;
- padding: 2px 2px;
- font-size: 14pt;
}
-.menu {
+.menu-item .menu-name:hover {
+ opacity: 0.75;
+}
+.menu-item .menu-name:hover .downArrow {
+ border-top-color: #666;
+}
+.menu-name {
+ height: 100%;
+ padding: 0 0.5em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.submenu {
display: none;
- position: absolute;
- background-color: #f8f8f8;
- border: 1px solid #888;
- box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2);
z-index: 1;
- margin-top: 2px;
+ margin-top: -4px;
+ min-width: 10em;
+ position: absolute;
left: 0px;
- min-width: 5em;
+ background-color: white;
+ box-shadow: 0 1px 5px rgba(0,0,0,.3);
+ font-size: 100%;
+ text-transform: none;
}
-.menu-header, .menu {
- cursor: default;
+.menu-item, .submenu {
user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
-.menu hr {
- background-color: #fff;
- margin-top: 0px;
- margin-bottom: 0px;
+.submenu hr {
+ border: 0;
+ border-top: 2px solid #eee;
}
-.menu a, .menu button {
+.submenu a {
display: block;
- width: 100%;
- margin: 0px;
- padding: 2px 0px 2px 0px;
- text-align: left;
+ padding: .5em 1em;
text-decoration: none;
- color: #000;
- background-color: #f8f8f8;
- font-size: 12pt;
- border: none;
}
-.menu-header:hover {
- background-color: #ccc;
+.submenu a:hover, .submenu a.active {
+ color: white;
+ background-color: #6b82d6;
}
-.menu a:hover, .menu button:hover {
- background-color: #ccc;
-}
-.menu a.disabled {
+.submenu a.disabled {
color: gray;
pointer-events: none;
}
-#searchbox {
- margin-left: 10pt;
+
+#content {
+ overflow-y: scroll;
+ padding: 1em;
+}
+#top {
+ overflow-y: scroll;
+}
+#graph {
+ overflow: hidden;
}
-#bodycontainer {
+#graph svg {
width: 100%;
- height: 100%;
- max-height: 100%;
- overflow: scroll;
- padding-top: 5px;
+ height: auto;
+ padding: 10px;
}
-#toptable {
+#content.source .filename {
+ margin-top: 0;
+ margin-bottom: 1em;
+ font-size: 120%;
+}
+#content.source pre {
+ margin-bottom: 3em;
+}
+table {
border-spacing: 0px;
width: 100%;
padding-bottom: 1em;
+ white-space: nowrap;
+}
+table thead {
+ font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
-#toptable tr th {
- border-bottom: 1px solid black;
+table tr th {
+ background-color: #ddd;
text-align: right;
- padding-left: 1em;
- padding-top: 0.2em;
- padding-bottom: 0.2em;
+ padding: .3em .5em;
}
-#toptable tr td {
- padding-left: 1em;
- font: monospace;
+table tr td {
+ padding: .3em .5em;
text-align: right;
- white-space: nowrap;
- cursor: default;
}
-#toptable tr th:nth-child(6),
-#toptable tr th:nth-child(7),
-#toptable tr td:nth-child(6),
-#toptable tr td:nth-child(7) {
+#top table tr th:nth-child(6),
+#top table tr th:nth-child(7),
+#top table tr td:nth-child(6),
+#top table tr td:nth-child(7) {
text-align: left;
}
-#toptable tr td:nth-child(6) {
- max-width: 30em; // Truncate very long names
+#top table tr td:nth-child(6) {
+ width: 100%;
+ text-overflow: ellipsis;
overflow: hidden;
+ white-space: nowrap;
}
#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {
cursor: ns-resize;
}
.hilite {
- background-color: #ccf;
+ background-color: #ebf5fb;
+ font-weight: bold;
}
</style>
{{end}}
{{define "header"}}
-<div id="detailtext">
-<button id="closedetails">Close</button>
-{{range .Legend}}<div>{{.}}</div>{{end}}
-</div>
+<div class="header">
+ <div class="title">
+ <h1><a href="/">pprof</a></h1>
+ </div>
-<div class="menubar">
+ <div id="view" class="menu-item">
+ <div class="menu-name">
+ View
+ <i class="downArrow"></i>
+ </div>
+ <div class="submenu">
+ <a title="{{.Help.top}}" href="/top" id="topbtn">Top</a>
+ <a title="{{.Help.graph}}" href="/" id="graphbtn">Graph</a>
+ <a title="{{.Help.flamegraph}}" href="/flamegraph" id="flamegraph">Flame Graph</a>
+ <a title="{{.Help.peek}}" href="/peek" id="peek">Peek</a>
+ <a title="{{.Help.list}}" href="/source" id="list">Source</a>
+ <a title="{{.Help.disasm}}" href="/disasm" id="disasm">Disassemble</a>
+ </div>
+ </div>
-<div class="menu-header">
-View
-<div class="menu">
-<a title="{{.Help.top}}" href="/top" id="topbtn">Top</a>
-<a title="{{.Help.graph}}" href="/" id="graphbtn">Graph</a>
-<a title="{{.Help.peek}}" href="/peek" id="peek">Peek</a>
-<a title="{{.Help.list}}" href="/source" id="list">Source</a>
-<a title="{{.Help.disasm}}" href="/disasm" id="disasm">Disassemble</a>
-<hr>
-<button title="{{.Help.details}}" id="details">Details</button>
-</div>
-</div>
+ <div id="refine" class="menu-item">
+ <div class="menu-name">
+ Refine
+ <i class="downArrow"></i>
+ </div>
+ <div class="submenu">
+ <a title="{{.Help.focus}}" href="{{.BaseURL}}" id="focus">Focus</a>
+ <a title="{{.Help.ignore}}" href="{{.BaseURL}}" id="ignore">Ignore</a>
+ <a title="{{.Help.hide}}" href="{{.BaseURL}}" id="hide">Hide</a>
+ <a title="{{.Help.show}}" href="{{.BaseURL}}" id="show">Show</a>
+ <hr>
+ <a title="{{.Help.reset}}" href="{{.BaseURL}}">Reset</a>
+ </div>
+ </div>
-<div class="menu-header">
-Refine
-<div class="menu">
-<a title="{{.Help.focus}}" href="{{.BaseURL}}" id="focus">Focus</a>
-<a title="{{.Help.ignore}}" href="{{.BaseURL}}" id="ignore">Ignore</a>
-<a title="{{.Help.hide}}" href="{{.BaseURL}}" id="hide">Hide</a>
-<a title="{{.Help.show}}" href="{{.BaseURL}}" id="show">Show</a>
-<hr>
-<a title="{{.Help.reset}}" href="{{.BaseURL}}">Reset</a>
-</div>
-</div>
+ <div>
+ <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
+ </div>
-<input id="searchbox" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
-
-<span id="home">{{.Title}}</span>
-
-</div> <!-- menubar -->
+ <div class="description">
+ <a title="{{.Help.details}}" href="#" id="details">{{.Title}}</a>
+ <div id="detailsbox">
+ {{range .Legend}}<div>{{.}}</div>{{end}}
+ </div>
+ </div>
+</div>
<div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
{{end}}
@@ -226,21 +285,17 @@ Refine
<!DOCTYPE html>
<html>
<head>
-<meta charset="utf-8">
-<title>{{.Title}}</title>
-{{template "css" .}}
+ <meta charset="utf-8">
+ <title>{{.Title}}</title>
+ {{template "css" .}}
</head>
<body>
-
-{{template "header" .}}
-<div id="graphcontainer">
-<div id="graph">
-{{.HTMLBody}}
-</div>
-
-</div>
-{{template "script" .}}
-<script>viewer({{.BaseURL}}, {{.Nodes}})</script>
+ {{template "header" .}}
+ <div id="graph">
+ {{.HTMLBody}}
+ </div>
+ {{template "script" .}}
+ <script>viewer({{.BaseURL}}, {{.Nodes}});</script>
</body>
</html>
{{end}}
@@ -253,58 +308,58 @@ function initPanAndZoom(svg, clickHandler) {
'use strict';
// Current mouse/touch handling mode
- const IDLE = 0
- const MOUSEPAN = 1
- const TOUCHPAN = 2
- const TOUCHZOOM = 3
- let mode = IDLE
+ const IDLE = 0;
+ const MOUSEPAN = 1;
+ const TOUCHPAN = 2;
+ const TOUCHZOOM = 3;
+ let mode = IDLE;
// State needed to implement zooming.
- let currentScale = 1.0
- const initWidth = svg.viewBox.baseVal.width
- const initHeight = svg.viewBox.baseVal.height
+ let currentScale = 1.0;
+ const initWidth = svg.viewBox.baseVal.width;
+ const initHeight = svg.viewBox.baseVal.height;
// State needed to implement panning.
- let panLastX = 0 // Last event X coordinate
- let panLastY = 0 // Last event Y coordinate
- let moved = false // Have we seen significant movement
- let touchid = null // Current touch identifier
+ let panLastX = 0; // Last event X coordinate
+ let panLastY = 0; // Last event Y coordinate
+ let moved = false; // Have we seen significant movement
+ let touchid = null; // Current touch identifier
// State needed for pinch zooming
- let touchid2 = null // Second id for pinch zooming
- let initGap = 1.0 // Starting gap between two touches
- let initScale = 1.0 // currentScale when pinch zoom started
- let centerPoint = null // Center point for scaling
+ let touchid2 = null; // Second id for pinch zooming
+ let initGap = 1.0; // Starting gap between two touches
+ let initScale = 1.0; // currentScale when pinch zoom started
+ let centerPoint = null; // Center point for scaling
// Convert event coordinates to svg coordinates.
function toSvg(x, y) {
- const p = svg.createSVGPoint()
- p.x = x
- p.y = y
- let m = svg.getCTM()
- if (m == null) m = svg.getScreenCTM() // Firefox workaround.
- return p.matrixTransform(m.inverse())
+ const p = svg.createSVGPoint();
+ p.x = x;
+ p.y = y;
+ let m = svg.getCTM();
+ if (m == null) m = svg.getScreenCTM(); // Firefox workaround.
+ return p.matrixTransform(m.inverse());
}
// Change the scaling for the svg to s, keeping the point denoted
// by u (in svg coordinates]) fixed at the same screen location.
function rescale(s, u) {
// Limit to a good range.
- if (s < 0.2) s = 0.2
- if (s > 10.0) s = 10.0
+ if (s < 0.2) s = 0.2;
+ if (s > 10.0) s = 10.0;
- currentScale = s
+ currentScale = s;
// svg.viewBox defines the visible portion of the user coordinate
// system. So to magnify by s, divide the visible portion by s,
// which will then be stretched to fit the viewport.
- const vb = svg.viewBox
- const w1 = vb.baseVal.width
- const w2 = initWidth / s
- const h1 = vb.baseVal.height
- const h2 = initHeight / s
- vb.baseVal.width = w2
- vb.baseVal.height = h2
+ const vb = svg.viewBox;
+ const w1 = vb.baseVal.width;
+ const w2 = initWidth / s;
+ const h1 = vb.baseVal.height;
+ const h2 = initHeight / s;
+ vb.baseVal.width = w2;
+ vb.baseVal.height = h2;
// We also want to adjust vb.baseVal.x so that u.x remains at same
// screen X coordinate. In other words, want to change it from x1 to x2
@@ -313,154 +368,154 @@ function initPanAndZoom(svg, clickHandler) {
// Simplifying that, we get
// (u.x - x1) * (w2 / w1) = u.x - x2
// x2 = u.x - (u.x - x1) * (w2 / w1)
- vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1)
- vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1)
+ vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1);
+ vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1);
}
function handleWheel(e) {
- if (e.deltaY == 0) return
+ if (e.deltaY == 0) return;
// Change scale factor by 1.1 or 1/1.1
rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),
- toSvg(e.offsetX, e.offsetY))
+ toSvg(e.offsetX, e.offsetY));
}
function setMode(m) {
- mode = m
- touchid = null
- touchid2 = null
+ mode = m;
+ touchid = null;
+ touchid2 = null;
}
function panStart(x, y) {
- moved = false
- panLastX = x
- panLastY = y
+ moved = false;
+ panLastX = x;
+ panLastY = y;
}
function panMove(x, y) {
- let dx = x - panLastX
- let dy = y - panLastY
- if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return // Ignore tiny moves
+ let dx = x - panLastX;
+ let dy = y - panLastY;
+ if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return; // Ignore tiny moves
- moved = true
- panLastX = x
- panLastY = y
+ moved = true;
+ panLastX = x;
+ panLastY = y;
// Firefox workaround: get dimensions from parentNode.
- const swidth = svg.clientWidth || svg.parentNode.clientWidth
- const sheight = svg.clientHeight || svg.parentNode.clientHeight
+ const swidth = svg.clientWidth || svg.parentNode.clientWidth;
+ const sheight = svg.clientHeight || svg.parentNode.clientHeight;
// Convert deltas from screen space to svg space.
- dx *= (svg.viewBox.baseVal.width / swidth)
- dy *= (svg.viewBox.baseVal.height / sheight)
+ dx *= (svg.viewBox.baseVal.width / swidth);
+ dy *= (svg.viewBox.baseVal.height / sheight);
- svg.viewBox.baseVal.x -= dx
- svg.viewBox.baseVal.y -= dy
+ svg.viewBox.baseVal.x -= dx;
+ svg.viewBox.baseVal.y -= dy;
}
function handleScanStart(e) {
- if (e.button != 0) return // Do not catch right-clicks etc.
- setMode(MOUSEPAN)
- panStart(e.clientX, e.clientY)
- e.preventDefault()
- svg.addEventListener("mousemove", handleScanMove)
+ if (e.button != 0) return; // Do not catch right-clicks etc.
+ setMode(MOUSEPAN);
+ panStart(e.clientX, e.clientY);
+ e.preventDefault();
+ svg.addEventListener('mousemove', handleScanMove);
}
function handleScanMove(e) {
if (e.buttons == 0) {
// Missed an end event, perhaps because mouse moved outside window.
- setMode(IDLE)
- svg.removeEventListener("mousemove", handleScanMove)
- return
+ setMode(IDLE);
+ svg.removeEventListener('mousemove', handleScanMove);
+ return;
}
- if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
+ if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
}
function handleScanEnd(e) {
- if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
- setMode(IDLE)
- svg.removeEventListener("mousemove", handleScanMove)
- if (!moved) clickHandler(e.target)
+ if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
+ setMode(IDLE);
+ svg.removeEventListener('mousemove', handleScanMove);
+ if (!moved) clickHandler(e.target);
}
// Find touch object with specified identifier.
function findTouch(tlist, id) {
for (const t of tlist) {
- if (t.identifier == id) return t
+ if (t.identifier == id) return t;
}
- return null
+ return null;
}
- // Return distance between two touch points
+ // Return distance between two touch points
function touchGap(t1, t2) {
- const dx = t1.clientX - t2.clientX
- const dy = t1.clientY - t2.clientY
- return Math.hypot(dx, dy)
+ const dx = t1.clientX - t2.clientX;
+ const dy = t1.clientY - t2.clientY;
+ return Math.hypot(dx, dy);
}
function handleTouchStart(e) {
if (mode == IDLE && e.changedTouches.length == 1) {
// Start touch based panning
- const t = e.changedTouches[0]
- setMode(TOUCHPAN)
- touchid = t.identifier
- panStart(t.clientX, t.clientY)
- e.preventDefault()
+ const t = e.changedTouches[0];
+ setMode(TOUCHPAN);
+ touchid = t.identifier;
+ panStart(t.clientX, t.clientY);
+ e.preventDefault();
} else if (mode == TOUCHPAN && e.touches.length == 2) {
// Start pinch zooming
- setMode(TOUCHZOOM)
- const t1 = e.touches[0]
- const t2 = e.touches[1]
- touchid = t1.identifier
- touchid2 = t2.identifier
- initScale = currentScale
- initGap = touchGap(t1, t2)
+ setMode(TOUCHZOOM);
+ const t1 = e.touches[0];
+ const t2 = e.touches[1];
+ touchid = t1.identifier;
+ touchid2 = t2.identifier;
+ initScale = currentScale;
+ initGap = touchGap(t1, t2);
centerPoint = toSvg((t1.clientX + t2.clientX) / 2,
- (t1.clientY + t2.clientY) / 2)
- e.preventDefault()
+ (t1.clientY + t2.clientY) / 2);
+ e.preventDefault();
}
}
function handleTouchMove(e) {
if (mode == TOUCHPAN) {
- const t = findTouch(e.changedTouches, touchid)
- if (t == null) return
+ const t = findTouch(e.changedTouches, touchid);
+ if (t == null) return;
if (e.touches.length != 1) {
- setMode(IDLE)
- return
+ setMode(IDLE);
+ return;
}
- panMove(t.clientX, t.clientY)
- e.preventDefault()
+ panMove(t.clientX, t.clientY);
+ e.preventDefault();
} else if (mode == TOUCHZOOM) {
// Get two touches; new gap; rescale to ratio.
- const t1 = findTouch(e.touches, touchid)
- const t2 = findTouch(e.touches, touchid2)
- if (t1 == null || t2 == null) return
- const gap = touchGap(t1, t2)
- rescale(initScale * gap / initGap, centerPoint)
- e.preventDefault()
+ const t1 = findTouch(e.touches, touchid);
+ const t2 = findTouch(e.touches, touchid2);
+ if (t1 == null || t2 == null) return;
+ const gap = touchGap(t1, t2);
+ rescale(initScale * gap / initGap, centerPoint);
+ e.preventDefault();
}
}
function handleTouchEnd(e) {
if (mode == TOUCHPAN) {
- const t = findTouch(e.changedTouches, touchid)
- if (t == null) return
- panMove(t.clientX, t.clientY)
- setMode(IDLE)
- e.preventDefault()
- if (!moved) clickHandler(t.target)
+ const t = findTouch(e.changedTouches, touchid);
+ if (t == null) return;
+ panMove(t.clientX, t.clientY);
+ setMode(IDLE);
+ e.preventDefault();
+ if (!moved) clickHandler(t.target);
} else if (mode == TOUCHZOOM) {
- setMode(IDLE)
- e.preventDefault()
+ setMode(IDLE);
+ e.preventDefault();
}
}
- svg.addEventListener("mousedown", handleScanStart)
- svg.addEventListener("mouseup", handleScanEnd)
- svg.addEventListener("touchstart", handleTouchStart)
- svg.addEventListener("touchmove", handleTouchMove)
- svg.addEventListener("touchend", handleTouchEnd)
- svg.addEventListener("wheel", handleWheel, true)
+ svg.addEventListener('mousedown', handleScanStart);
+ svg.addEventListener('mouseup', handleScanEnd);
+ svg.addEventListener('touchstart', handleTouchStart);
+ svg.addEventListener('touchmove', handleTouchMove);
+ svg.addEventListener('touchend', handleTouchEnd);
+ svg.addEventListener('wheel', handleWheel, true);
}
function initMenus() {
@@ -471,41 +526,42 @@ function initMenus() {
function cancelActiveMenu() {
if (activeMenu == null) return;
- activeMenu.style.display = "none";
+ activeMenu.style.display = 'none';
activeMenu = null;
activeMenuHdr = null;
}
// Set click handlers on every menu header.
- for (const menu of document.getElementsByClassName("menu")) {
+ for (const menu of document.getElementsByClassName('submenu')) {
const hdr = menu.parentElement;
if (hdr == null) return;
+ if (hdr.classList.contains('disabled')) return;
function showMenu(e) {
// menu is a child of hdr, so this event can fire for clicks
// inside menu. Ignore such clicks.
- if (e.target != hdr) return;
+ if (e.target.parentElement != hdr) return;
activeMenu = menu;
activeMenuHdr = hdr;
- menu.style.display = "block";
+ menu.style.display = 'block';
}
- hdr.addEventListener("mousedown", showMenu);
- hdr.addEventListener("touchstart", showMenu);
+ hdr.addEventListener('mousedown', showMenu);
+ hdr.addEventListener('touchstart', showMenu);
}
// If there is an active menu and a down event outside, retract the menu.
- for (const t of ["mousedown", "touchstart"]) {
+ for (const t of ['mousedown', 'touchstart']) {
document.addEventListener(t, (e) => {
// Note: to avoid unnecessary flicker, if the down event is inside
// the active menu header, do not retract the menu.
- if (activeMenuHdr != e.target.closest(".menu-header")) {
+ if (activeMenuHdr != e.target.closest('.menu-item')) {
cancelActiveMenu();
}
}, { passive: true, capture: true });
}
// If there is an active menu and an up event inside, retract the menu.
- document.addEventListener("mouseup", (e) => {
- if (activeMenu == e.target.closest(".menu")) {
+ document.addEventListener('mouseup', (e) => {
+ if (activeMenu == e.target.closest('.submenu')) {
cancelActiveMenu();
}
}, { passive: true, capture: true });
@@ -515,282 +571,283 @@ function viewer(baseUrl, nodes) {
'use strict';
// Elements
- const search = document.getElementById("searchbox")
- const graph0 = document.getElementById("graph0")
- const svg = (graph0 == null ? null : graph0.parentElement)
- const toptable = document.getElementById("toptable")
+ const search = document.getElementById('search');
+ const graph0 = document.getElementById('graph0');
+ const svg = (graph0 == null ? null : graph0.parentElement);
+ const toptable = document.getElementById('toptable');
- let regexpActive = false
- let selected = new Map()
- let origFill = new Map()
- let searchAlarm = null
- let buttonsEnabled = true
+ let regexpActive = false;
+ let selected = new Map();
+ let origFill = new Map();
+ let searchAlarm = null;
+ let buttonsEnabled = true;
- function handleDetails() {
- const detailsText = document.getElementById("detailtext")
- if (detailsText != null) detailsText.style.display = "block"
- }
-
- function handleCloseDetails() {
- const detailsText = document.getElementById("detailtext")
- if (detailsText != null) detailsText.style.display = "none"
+ function handleDetails(e) {
+ e.preventDefault();
+ const detailsText = document.getElementById('detailsbox');
+ if (detailsText != null) {
+ if (detailsText.style.display === 'block') {
+ detailsText.style.display = 'none';
+ } else {
+ detailsText.style.display = 'block';
+ }
+ }
}
function handleKey(e) {
- if (e.keyCode != 13) return
+ if (e.keyCode != 13) return;
window.location.href =
- updateUrl(new URL({{.BaseURL}}, window.location.href), "f")
- e.preventDefault()
+ updateUrl(new URL({{.BaseURL}}, window.location.href), 'f');
+ e.preventDefault();
}
function handleSearch() {
// Delay expensive processing so a flurry of key strokes is handled once.
if (searchAlarm != null) {
- clearTimeout(searchAlarm)
+ clearTimeout(searchAlarm);
}
- searchAlarm = setTimeout(selectMatching, 300)
+ searchAlarm = setTimeout(selectMatching, 300);
- regexpActive = true
- updateButtons()
+ regexpActive = true;
+ updateButtons();
}
function selectMatching() {
- searchAlarm = null
- let re = null
- if (search.value != "") {
+ searchAlarm = null;
+ let re = null;
+ if (search.value != '') {
try {
- re = new RegExp(search.value)
+ re = new RegExp(search.value);
} catch (e) {
// TODO: Display error state in search box
- return
+ return;
}
}
function match(text) {
- return re != null && re.test(text)
+ return re != null && re.test(text);
}
// drop currently selected items that do not match re.
selected.forEach(function(v, n) {
if (!match(nodes[n])) {
- unselect(n, document.getElementById("node" + n))
+ unselect(n, document.getElementById('node' + n));
}
})
// add matching items that are not currently selected.
for (let n = 0; n < nodes.length; n++) {
if (!selected.has(n) && match(nodes[n])) {
- select(n, document.getElementById("node" + n))
+ select(n, document.getElementById('node' + n));
}
}
- updateButtons()
+ updateButtons();
}
function toggleSvgSelect(elem) {
// Walk up to immediate child of graph0
while (elem != null && elem.parentElement != graph0) {
- elem = elem.parentElement
+ elem = elem.parentElement;
}
- if (!elem) return
+ if (!elem) return;
// Disable regexp mode.
- regexpActive = false
+ regexpActive = false;
- const n = nodeId(elem)
- if (n < 0) return
+ const n = nodeId(elem);
+ if (n < 0) return;
if (selected.has(n)) {
- unselect(n, elem)
+ unselect(n, elem);
} else {
- select(n, elem)
+ select(n, elem);
}
- updateButtons()
+ updateButtons();
}
function unselect(n, elem) {
- if (elem == null) return
- selected.delete(n)
- setBackground(elem, false)
+ if (elem == null) return;
+ selected.delete(n);
+ setBackground(elem, false);
}
function select(n, elem) {
- if (elem == null) return
- selected.set(n, true)
- setBackground(elem, true)
+ if (elem == null) return;
+ selected.set(n, true);
+ setBackground(elem, true);
}
function nodeId(elem) {
- const id = elem.id
- if (!id) return -1
- if (!id.startsWith("node")) return -1
- const n = parseInt(id.slice(4), 10)
- if (isNaN(n)) return -1
- if (n < 0 || n >= nodes.length) return -1
- return n
+ const id = elem.id;
+ if (!id) return -1;
+ if (!id.startsWith('node')) return -1;
+ const n = parseInt(id.slice(4), 10);
+ if (isNaN(n)) return -1;
+ if (n < 0 || n >= nodes.length) return -1;
+ return n;
}
function setBackground(elem, set) {
// Handle table row highlighting.
- if (elem.nodeName == "TR") {
- elem.classList.toggle("hilite", set)
- return
+ if (elem.nodeName == 'TR') {
+ elem.classList.toggle('hilite', set);
+ return;
}
// Handle svg element highlighting.
- const p = findPolygon(elem)
+ const p = findPolygon(elem);
if (p != null) {
if (set) {
- origFill.set(p, p.style.fill)
- p.style.fill = "#ccccff"
+ origFill.set(p, p.style.fill);
+ p.style.fill = '#ccccff';
} else if (origFill.has(p)) {
- p.style.fill = origFill.get(p)
+ p.style.fill = origFill.get(p);
}
}
}
function findPolygon(elem) {
- if (elem.localName == "polygon") return elem
+ if (elem.localName == 'polygon') return elem;
for (const c of elem.children) {
- const p = findPolygon(c)
- if (p != null) return p
+ const p = findPolygon(c);
+ if (p != null) return p;
}
- return null
+ return null;
}
// convert a string to a regexp that matches that string.
function quotemeta(str) {
- return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1')
+ return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
}
// Update id's href to reflect current selection whenever it is
// liable to be followed.
function makeLinkDynamic(id) {
- const elem = document.getElementById(id)
- if (elem == null) return
+ const elem = document.getElementById(id);
+ if (elem == null) return;
- // Most links copy current selection into the "f" parameter,
+ // Most links copy current selection into the 'f' parameter,
// but Refine menu links are different.
- let param = "f"
- if (id == "ignore") param = "i"
- if (id == "hide") param = "h"
- if (id == "show") param = "s"
+ let param = 'f';
+ if (id == 'ignore') param = 'i';
+ if (id == 'hide') param = 'h';
+ if (id == 'show') param = 's';
// We update on mouseenter so middle-click/right-click work properly.
- elem.addEventListener("mouseenter", updater)
- elem.addEventListener("touchstart", updater)
+ elem.addEventListener('mouseenter', updater);
+ elem.addEventListener('touchstart', updater);
function updater() {
- elem.href = updateUrl(new URL(elem.href), param)
+ elem.href = updateUrl(new URL(elem.href), param);
}
}
// Update URL to reflect current selection.
function updateUrl(url, param) {
- url.hash = ""
+ url.hash = '';
// The selection can be in one of two modes: regexp-based or
// list-based. Construct regular expression depending on mode.
let re = regexpActive
- ? search.value
- : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join("|")
+ ? search.value
+ : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|');
// Copy params from this page's URL.
- const params = url.searchParams
+ const params = url.searchParams;
for (const p of new URLSearchParams(window.location.search)) {
- params.set(p[0], p[1])
+ params.set(p[0], p[1]);
}
- if (re != "") {
+ if (re != '') {
// For focus/show, forget old parameter. For others, add to re.
- if (param != "f" && param != "s" && params.has(param)) {
- const old = params.get(param)
- if (old != "") {
- re += "|" + old
+ if (param != 'f' && param != 's' && params.has(param)) {
+ const old = params.get(param);
+ if (old != '') {
+ re += '|' + old;
}
}
- params.set(param, re)
+ params.set(param, re);
} else {
- params.delete(param)
+ params.delete(param);
}
- return url.toString()
+ return url.toString();
}
function handleTopClick(e) {
// Walk back until we find TR and then get the Name column (index 5)
- let elem = e.target
- while (elem != null && elem.nodeName != "TR") {
- elem = elem.parentElement
+ let elem = e.target;
+ while (elem != null && elem.nodeName != 'TR') {
+ elem = elem.parentElement;
}
- if (elem == null || elem.children.length < 6) return
+ if (elem == null || elem.children.length < 6) return;
- e.preventDefault()
- const tr = elem
- const td = elem.children[5]
- if (td.nodeName != "TD") return
- const name = td.innerText
- const index = nodes.indexOf(name)
- if (index < 0) return
+ e.preventDefault();
+ const tr = elem;
+ const td = elem.children[5];
+ if (td.nodeName != 'TD') return;
+ const name = td.innerText;
+ const index = nodes.indexOf(name);
+ if (index < 0) return;
// Disable regexp mode.
- regexpActive = false
+ regexpActive = false;
if (selected.has(index)) {
- unselect(index, elem)
+ unselect(index, elem);
} else {
- select(index, elem)
+ select(index, elem);
}
- updateButtons()
+ updateButtons();
}
function updateButtons() {
- const enable = (search.value != "" || selected.size != 0)
- if (buttonsEnabled == enable) return
- buttonsEnabled = enable
- for (const id of ["focus", "ignore", "hide", "show"]) {
- const link = document.getElementById(id)
+ const enable = (search.value != '' || selected.size != 0);
+ if (buttonsEnabled == enable) return;
+ buttonsEnabled = enable;
+ for (const id of ['focus', 'ignore', 'hide', 'show']) {
+ const link = document.getElementById(id);
if (link != null) {
- link.classList.toggle("disabled", !enable)
+ link.classList.toggle('disabled', !enable);
}
}
}
// Initialize button states
- updateButtons()
+ updateButtons();
// Setup event handlers
- initMenus()
+ initMenus();
if (svg != null) {
- initPanAndZoom(svg, toggleSvgSelect)
+ initPanAndZoom(svg, toggleSvgSelect);
}
if (toptable != null) {
- toptable.addEventListener("mousedown", handleTopClick)
- toptable.addEventListener("touchstart", handleTopClick)
+ toptable.addEventListener('mousedown', handleTopClick);
+ toptable.addEventListener('touchstart', handleTopClick);
}
- const ids = ["topbtn", "graphbtn", "peek", "list", "disasm",
- "focus", "ignore", "hide", "show"]
- ids.forEach(makeLinkDynamic)
+ const ids = ['topbtn', 'graphbtn', 'peek', 'list', 'disasm',
+ 'focus', 'ignore', 'hide', 'show'];
+ ids.forEach(makeLinkDynamic);
// Bind action to button with specified id.
function addAction(id, action) {
- const btn = document.getElementById(id)
+ const btn = document.getElementById(id);
if (btn != null) {
- btn.addEventListener("click", action)
- btn.addEventListener("touchstart", action)
+ btn.addEventListener('click', action);
+ btn.addEventListener('touchstart', action);
}
}
- addAction("details", handleDetails)
- addAction("closedetails", handleCloseDetails)
+ addAction('details', handleDetails);
- search.addEventListener("input", handleSearch)
- search.addEventListener("keydown", handleKey)
+ search.addEventListener('input', handleSearch);
+ search.addEventListener('keydown', handleKey);
// Give initial focus to main container so it can be scrolled using keys.
- const main = document.getElementById("bodycontainer")
+ const main = document.getElementById('bodycontainer');
if (main) {
- main.focus()
+ main.focus();
}
}
</script>
@@ -800,116 +857,115 @@ function viewer(baseUrl, nodes) {
<!DOCTYPE html>
<html>
<head>
-<meta charset="utf-8">
-<title>{{.Title}}</title>
-{{template "css" .}}
-<style type="text/css">
-</style>
+ <meta charset="utf-8">
+ <title>{{.Title}}</title>
+ {{template "css" .}}
+ <style type="text/css">
+ </style>
</head>
<body>
+ {{template "header" .}}
+ <div id="top">
+ <table id="toptable">
+ <thead>
+ <tr>
+ <th id="flathdr1">Flat</th>
+ <th id="flathdr2">Flat%</th>
+ <th>Sum%</th>
+ <th id="cumhdr1">Cum</th>
+ <th id="cumhdr2">Cum%</th>
+ <th id="namehdr">Name</th>
+ <th>Inlined?</th>
+ </tr>
+ </thead>
+ <tbody id="rows"></tbody>
+ </table>
+ </div>
+ {{template "script" .}}
+ <script>
+ function makeTopTable(total, entries) {
+ const rows = document.getElementById('rows');
+ if (rows == null) return;
-{{template "header" .}}
-
-<div id="bodycontainer">
-<table id="toptable">
-<tr>
-<th id="flathdr1">Flat
-<th id="flathdr2">Flat%
-<th>Sum%
-<th id="cumhdr1">Cum
-<th id="cumhdr2">Cum%
-<th id="namehdr">Name
-<th>Inlined?</tr>
-<tbody id="rows">
-</tbody>
-</table>
-</div>
+ // Store initial index in each entry so we have stable node ids for selection.
+ for (let i = 0; i < entries.length; i++) {
+ entries[i].Id = 'node' + i;
+ }
-{{template "script" .}}
-<script>
-function makeTopTable(total, entries) {
- const rows = document.getElementById("rows")
- if (rows == null) return
+ // Which column are we currently sorted by and in what order?
+ let currentColumn = '';
+ let descending = false;
+ sortBy('Flat');
- // Store initial index in each entry so we have stable node ids for selection.
- for (let i = 0; i < entries.length; i++) {
- entries[i].Id = "node" + i
- }
+ function sortBy(column) {
+ // Update sort criteria
+ if (column == currentColumn) {
+ descending = !descending; // Reverse order
+ } else {
+ currentColumn = column;
+ descending = (column != 'Name');
+ }
- // Which column are we currently sorted by and in what order?
- let currentColumn = ""
- let descending = false
- sortBy("Flat")
+ // Sort according to current criteria.
+ function cmp(a, b) {
+ const av = a[currentColumn];
+ const bv = b[currentColumn];
+ if (av < bv) return -1;
+ if (av > bv) return +1;
+ return 0;
+ }
+ entries.sort(cmp);
+ if (descending) entries.reverse();
- function sortBy(column) {
- // Update sort criteria
- if (column == currentColumn) {
- descending = !descending // Reverse order
- } else {
- currentColumn = column
- descending = (column != "Name")
- }
+ function addCell(tr, val) {
+ const td = document.createElement('td');
+ td.textContent = val;
+ tr.appendChild(td);
+ }
- // Sort according to current criteria.
- function cmp(a, b) {
- const av = a[currentColumn]
- const bv = b[currentColumn]
- if (av < bv) return -1
- if (av > bv) return +1
- return 0
- }
- entries.sort(cmp)
- if (descending) entries.reverse()
+ function percent(v) {
+ return (v * 100.0 / total).toFixed(2) + '%';
+ }
- function addCell(tr, val) {
- const td = document.createElement('td')
- td.textContent = val
- tr.appendChild(td)
- }
+ // Generate rows
+ const fragment = document.createDocumentFragment();
+ let sum = 0;
+ for (const row of entries) {
+ const tr = document.createElement('tr');
+ tr.id = row.Id;
+ sum += row.Flat;
+ addCell(tr, row.FlatFormat);
+ addCell(tr, percent(row.Flat));
+ addCell(tr, percent(sum));
+ addCell(tr, row.CumFormat);
+ addCell(tr, percent(row.Cum));
+ addCell(tr, row.Name);
+ addCell(tr, row.InlineLabel);
+ fragment.appendChild(tr);
+ }
- function percent(v) {
- return (v * 100.0 / total).toFixed(2) + "%"
- }
+ rows.textContent = ''; // Remove old rows
+ rows.appendChild(fragment);
+ }
- // Generate rows
- const fragment = document.createDocumentFragment()
- let sum = 0
- for (const row of entries) {
- const tr = document.createElement('tr')
- tr.id = row.Id
- sum += row.Flat
- addCell(tr, row.FlatFormat)
- addCell(tr, percent(row.Flat))
- addCell(tr, percent(sum))
- addCell(tr, row.CumFormat)
- addCell(tr, percent(row.Cum))
- addCell(tr, row.Name)
- addCell(tr, row.InlineLabel)
- fragment.appendChild(tr)
+ // Make different column headers trigger sorting.
+ function bindSort(id, column) {
+ const hdr = document.getElementById(id);
+ if (hdr == null) return;
+ const fn = function() { sortBy(column) };
+ hdr.addEventListener('click', fn);
+ hdr.addEventListener('touch', fn);
+ }
+ bindSort('flathdr1', 'Flat');
+ bindSort('flathdr2', 'Flat');
+ bindSort('cumhdr1', 'Cum');
+ bindSort('cumhdr2', 'Cum');
+ bindSort('namehdr', 'Name');
}
- rows.textContent = '' // Remove old rows
- rows.appendChild(fragment)
- }
-
- // Make different column headers trigger sorting.
- function bindSort(id, column) {
- const hdr = document.getElementById(id)
- if (hdr == null) return
- const fn = function() { sortBy(column) }
- hdr.addEventListener("click", fn)
- hdr.addEventListener("touch", fn)
- }
- bindSort("flathdr1", "Flat")
- bindSort("flathdr2", "Flat")
- bindSort("cumhdr1", "Cum")
- bindSort("cumhdr2", "Cum")
- bindSort("namehdr", "Name")
-}
-
-viewer({{.BaseURL}}, {{.Nodes}})
-makeTopTable({{.Total}}, {{.Top}})
-</script>
+ viewer({{.BaseURL}}, {{.Nodes}});
+ makeTopTable({{.Total}}, {{.Top}});
+ </script>
</body>
</html>
{{end}}
@@ -918,22 +974,19 @@ makeTopTable({{.Total}}, {{.Top}})
<!DOCTYPE html>
<html>
<head>
-<meta charset="utf-8">
-<title>{{.Title}}</title>
-{{template "css" .}}
-{{template "weblistcss" .}}
-{{template "weblistjs" .}}
+ <meta charset="utf-8">
+ <title>{{.Title}}</title>
+ {{template "css" .}}
+ {{template "weblistcss" .}}
+ {{template "weblistjs" .}}
</head>
<body>
-
-{{template "header" .}}
-
-<div id="bodycontainer">
-{{.HTMLBody}}
-</div>
-
-{{template "script" .}}
-<script>viewer({{.BaseURL}}, null)</script>
+ {{template "header" .}}
+ <div id="content" class="source">
+ {{.HTMLBody}}
+ </div>
+ {{template "script" .}}
+ <script>viewer({{.BaseURL}}, null);</script>
</body>
</html>
{{end}}
@@ -942,22 +995,131 @@ makeTopTable({{.Total}}, {{.Top}})
<!DOCTYPE html>
<html>
<head>
-<meta charset="utf-8">
-<title>{{.Title}}</title>
-{{template "css" .}}
+ <meta charset="utf-8">
+ <title>{{.Title}}</title>
+ {{template "css" .}}
+</head>
+<body>
+ {{template "header" .}}
+ <div id="content">
+ <pre>
+ {{.TextBody}}
+ </pre>
+ </div>
+ {{template "script" .}}
+ <script>viewer({{.BaseURL}}, null);</script>
+</body>
+</html>
+{{end}}
+
+{{define "flamegraph" -}}
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>{{.Title}}</title>
+ {{template "css" .}}
+ <style type="text/css">{{template "d3flamegraphcss" .}}</style>
+ <style type="text/css">
+ .flamegraph-content {
+ width: 90%;
+ min-width: 80%;
+ margin-left: 5%;
+ }
+ .flamegraph-details {
+ height: 1.2em;
+ width: 90%;
+ min-width: 90%;
+ margin-left: 5%;
+ padding-bottom: 41px;
+ }
+ </style>
</head>
<body>
+ {{template "header" .}}
+ <div id="bodycontainer">
+ <div class="flamegraph-content">
+ <div id="chart"></div>
+ </div>
+ <div id="flamegraphdetails" class="flamegraph-details"></div>
+ </div>
+ {{template "script" .}}
+ <script>viewer({{.BaseURL}}, {{.Nodes}});</script>
+ <script>{{template "d3script" .}}</script>
+ <script>{{template "d3tipscript" .}}</script>
+ <script>{{template "d3flamegraphscript" .}}</script>
+ <script type="text/javascript">
+ var data = {{.FlameGraph}};
+ var label = function(d) {
+ return d.data.n + ' (' + d.data.p + ', ' + d.data.l + ')';
+ };
-{{template "header" .}}
+ var width = document.getElementById('chart').clientWidth;
-<div id="bodycontainer">
-<pre>
-{{.TextBody}}
-</pre>
-</div>
+ var flameGraph = d3.flameGraph()
+ .width(width)
+ .cellHeight(18)
+ .minFrameSize(1)
+ .transitionDuration(750)
+ .transitionEase(d3.easeCubic)
+ .sort(true)
+ .title('')
+ .label(label)
+ .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; });
+
+ flameGraph.tooltip(tip);
+
+ d3.select('#chart')
+ .datum(data)
+ .call(flameGraph);
+
+ function clear() {
+ flameGraph.clear();
+ }
+
+ function resetZoom() {
+ flameGraph.resetZoom();
+ }
+
+ window.addEventListener('resize', function() {
+ var width = document.getElementById('chart').clientWidth;
+ var graphs = document.getElementsByClassName('d3-flame-graph');
+ if (graphs.length > 0) {
+ graphs[0].setAttribute('width', width);
+ }
+ flameGraph.width(width);
+ flameGraph.resetZoom();
+ }, true);
+
+ var search = document.getElementById('search');
+ var searchAlarm = null;
+
+ function selectMatching() {
+ searchAlarm = null;
+
+ if (search.value != '') {
+ flameGraph.search(search.value);
+ } else {
+ flameGraph.clear();
+ }
+ }
+
+ function handleSearch() {
+ // Delay expensive processing so a flurry of key strokes is handled once.
+ if (searchAlarm != null) {
+ clearTimeout(searchAlarm);
+ }
+ searchAlarm = setTimeout(selectMatching, 300);
+ }
-{{template "script" .}}
-<script>viewer({{.BaseURL}}, null)</script>
+ search.addEventListener('input', handleSearch);
+ </script>
</body>
</html>
{{end}}
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go
index 67ae262882..20d4e025f4 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go
@@ -69,31 +69,24 @@ func (ec *errorCatcher) PrintErr(args ...interface{}) {
// webArgs contains arguments passed to templates in webhtml.go.
type webArgs struct {
- BaseURL string
- Title string
- Errors []string
- Total int64
- Legend []string
- Help map[string]string
- Nodes []string
- HTMLBody template.HTML
- TextBody string
- Top []report.TextItem
+ BaseURL string
+ Title string
+ Errors []string
+ Total int64
+ Legend []string
+ Help map[string]string
+ Nodes []string
+ HTMLBody template.HTML
+ TextBody string
+ Top []report.TextItem
+ FlameGraph template.JS
}
-func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
- host, portStr, err := net.SplitHostPort(hostport)
- if err != nil {
- return fmt.Errorf("could not split http address: %v", err)
- }
- port, err := strconv.Atoi(portStr)
+func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, wantBrowser bool) error {
+ host, port, err := getHostAndPort(hostport)
if err != nil {
- return fmt.Errorf("invalid port number: %v", err)
- }
- if host == "" {
- host = "localhost"
+ return err
}
-
interactiveMode = true
ui := makeWebInterface(p, o)
for n, c := range pprofCommands {
@@ -111,22 +104,52 @@ func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) e
server = defaultWebServer
}
args := &plugin.HTTPServerArgs{
- Hostport: net.JoinHostPort(host, portStr),
+ Hostport: net.JoinHostPort(host, strconv.Itoa(port)),
Host: host,
Port: port,
Handlers: map[string]http.Handler{
- "/": http.HandlerFunc(ui.dot),
- "/top": http.HandlerFunc(ui.top),
- "/disasm": http.HandlerFunc(ui.disasm),
- "/source": http.HandlerFunc(ui.source),
- "/peek": http.HandlerFunc(ui.peek),
+ "/": http.HandlerFunc(ui.dot),
+ "/top": http.HandlerFunc(ui.top),
+ "/disasm": http.HandlerFunc(ui.disasm),
+ "/source": http.HandlerFunc(ui.source),
+ "/peek": http.HandlerFunc(ui.peek),
+ "/flamegraph": http.HandlerFunc(ui.flamegraph),
},
}
- go openBrowser("http://"+args.Hostport, o)
+ if wantBrowser {
+ go openBrowser("http://"+args.Hostport, o)
+ }
return server(args)
}
+func getHostAndPort(hostport string) (string, int, error) {
+ host, portStr, err := net.SplitHostPort(hostport)
+ if err != nil {
+ return "", 0, fmt.Errorf("could not split http address: %v", err)
+ }
+ if host == "" {
+ host = "localhost"
+ }
+ var port int
+ if portStr == "" {
+ ln, err := net.Listen("tcp", net.JoinHostPort(host, "0"))
+ if err != nil {
+ return "", 0, fmt.Errorf("could not generate random port: %v", err)
+ }
+ port = ln.Addr().(*net.TCPAddr).Port
+ err = ln.Close()
+ if err != nil {
+ return "", 0, fmt.Errorf("could not generate random port: %v", err)
+ }
+ } else {
+ port, err = strconv.Atoi(portStr)
+ if err != nil {
+ return "", 0, fmt.Errorf("invalid port number: %v", err)
+ }
+ }
+ return host, port, nil
+}
func defaultWebServer(args *plugin.HTTPServerArgs) error {
ln, err := net.Listen("tcp", args.Hostport)
if err != nil {
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go
index 96380a01b3..424752fd1f 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webui_test.go
@@ -23,24 +23,15 @@ import (
"net/url"
"os/exec"
"regexp"
+ "runtime"
"sync"
"testing"
- "time"
-
- "runtime"
"github.com/google/pprof/internal/plugin"
"github.com/google/pprof/profile"
)
func TestWebInterface(t *testing.T) {
- // This test starts a web browser in a background goroutine
- // after a 500ms delay. Sometimes the test exits before it
- // can run the browser, but sometimes the browser does open.
- // That's obviously unacceptable.
- defer time.Sleep(2 * time.Second) // to see the browser open
- t.Skip("golang.org/issue/22651")
-
if runtime.GOOS == "nacl" {
t.Skip("test assumes tcp available")
}
@@ -66,7 +57,7 @@ func TestWebInterface(t *testing.T) {
Obj: fakeObjTool{},
UI: &stdUI{},
HTTPServer: creator,
- })
+ }, false)
<-serverCreated
defer server.Close()
@@ -89,6 +80,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},
}
for _, c := range testcases {
if c.needDot && !haveDot {
@@ -127,14 +119,19 @@ func TestWebInterface(t *testing.T) {
for count := 0; count < 2; count++ {
wg.Add(1)
go func() {
- http.Get(path)
- wg.Done()
+ defer wg.Done()
+ res, err := http.Get(path)
+ if err != nil {
+ t.Error("could not fetch", c.path, err)
+ return
+ }
+ if _, err = ioutil.ReadAll(res.Body); err != nil {
+ t.Error("could not read response", c.path, err)
+ }
}()
}
}
wg.Wait()
-
- time.Sleep(5 * time.Second)
}
// Implement fake object file support.
@@ -153,9 +150,18 @@ func (f fakeObj) SourceLine(addr uint64) ([]plugin.Frame, error) {
}
func (f fakeObj) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
return []*plugin.Sym{
- {[]string{"F1"}, fakeSource, addrBase, addrBase + 10},
- {[]string{"F2"}, fakeSource, addrBase + 10, addrBase + 20},
- {[]string{"F3"}, fakeSource, addrBase + 20, addrBase + 30},
+ {
+ Name: []string{"F1"}, File: fakeSource,
+ Start: addrBase, End: addrBase + 10,
+ },
+ {
+ Name: []string{"F2"}, File: fakeSource,
+ Start: addrBase + 10, End: addrBase + 20,
+ },
+ {
+ Name: []string{"F3"}, File: fakeSource,
+ Start: addrBase + 20, End: addrBase + 30,
+ },
}, nil
}
@@ -229,6 +235,41 @@ func makeFakeProfile() *profile.Profile {
}
}
+func TestGetHostAndPort(t *testing.T) {
+ if runtime.GOOS == "nacl" {
+ t.Skip("test assumes tcp available")
+ }
+
+ type testCase struct {
+ hostport string
+ wantHost string
+ wantPort int
+ wantRandomPort bool
+ }
+
+ testCases := []testCase{
+ {":", "localhost", 0, true},
+ {":4681", "localhost", 4681, false},
+ {"localhost:4681", "localhost", 4681, false},
+ }
+ for _, tc := range testCases {
+ host, port, err := getHostAndPort(tc.hostport)
+ if err != nil {
+ t.Errorf("could not get host and port for %q: %v", tc.hostport, err)
+ }
+ if got, want := host, tc.wantHost; got != want {
+ t.Errorf("for %s, got host %s, want %s", tc.hostport, got, want)
+ continue
+ }
+ if !tc.wantRandomPort {
+ if got, want := port, tc.wantPort; got != want {
+ t.Errorf("for %s, got port %d, want %d", tc.hostport, got, want)
+ continue
+ }
+ }
+ }
+}
+
func TestIsLocalHost(t *testing.T) {
for _, s := range []string{"localhost:10000", "[::1]:10000", "127.0.0.1:10000"} {
host, _, err := net.SplitHostPort(s)