From e7cbbbe9bb878b6ca4ce04fde645df1c8f1845bd Mon Sep 17 00:00:00 2001 From: Daniel Martí Date: Mon, 12 Feb 2018 16:34:48 +0000 Subject: cmd/vendor/github.com/google/pprof: refresh from upstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updating to commit 0e0e5b7254e076a62326ab7305ba49e8515f0c91 from github.com/google/pprof Recent modifications to the vendored pprof, such as skipping TestWebInterface to avoid starting a web browser, have all been fixed upstream. Change-Id: I72e11108c438e1573bf2f9216e76d157378e8d45 Reviewed-on: https://go-review.googlesource.com/93375 Run-TryBot: Daniel Martí TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- .../google/pprof/internal/binutils/binutils.go | 27 +- .../pprof/internal/binutils/binutils_test.go | 147 ++- .../google/pprof/internal/binutils/disasm.go | 34 +- .../pprof/internal/binutils/testdata/exe_linux_64 | Bin 0 -> 9503 bytes .../google/pprof/internal/binutils/testdata/hello | Bin 9503 -> 0 bytes .../google/pprof/internal/driver/commands.go | 3 +- .../google/pprof/internal/driver/driver.go | 2 +- .../google/pprof/internal/driver/driver_test.go | 10 +- .../google/pprof/internal/driver/fetch_test.go | 9 +- .../google/pprof/internal/driver/flamegraph.go | 99 ++ .../google/pprof/internal/driver/interactive.go | 23 + .../pprof/internal/driver/interactive_test.go | 58 +- .../github.com/google/pprof/internal/driver/svg.go | 80 ++ .../testdata/pprof.cpu.flat.addresses.weblist | 4 +- .../google/pprof/internal/driver/webhtml.go | 1214 +++++++++++--------- .../google/pprof/internal/driver/webui.go | 79 +- .../google/pprof/internal/driver/webui_test.go | 77 +- .../google/pprof/internal/elfexec/elfexec.go | 16 + .../google/pprof/internal/graph/dotgraph.go | 21 +- .../pprof/internal/measurement/measurement.go | 18 + .../google/pprof/internal/proftest/proftest.go | 13 +- .../google/pprof/internal/report/report.go | 42 +- .../google/pprof/internal/report/report_test.go | 4 +- .../google/pprof/internal/report/source.go | 7 +- .../google/pprof/internal/report/source_test.go | 4 +- .../google/pprof/internal/symbolizer/symbolizer.go | 1 + .../google/pprof/internal/symbolz/symbolz.go | 21 +- .../google/pprof/internal/symbolz/symbolz_test.go | 11 + 28 files changed, 1297 insertions(+), 727 deletions(-) create mode 100755 src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/exe_linux_64 delete mode 100755 src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/hello create mode 100644 src/cmd/vendor/github.com/google/pprof/internal/driver/flamegraph.go create mode 100644 src/cmd/vendor/github.com/google/pprof/internal/driver/svg.go (limited to 'src/cmd/vendor/github.com/google/pprof/internal') diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go index 9a82cb8e92..390f952feb 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils.go @@ -175,20 +175,35 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) { of, err := macho.Open(name) if err != nil { - return nil, fmt.Errorf("Parsing %s: %v", name, err) + return nil, fmt.Errorf("error parsing %s: %v", name, err) } defer of.Close() + // Subtract the load address of the __TEXT section. Usually 0 for shared + // libraries or 0x100000000 for executables. You can check this value by + // running `objdump -private-headers `. + + textSegment := of.Segment("__TEXT") + if textSegment == nil { + return nil, fmt.Errorf("could not identify base for %s: no __TEXT segment", name) + } + if textSegment.Addr > start { + return nil, fmt.Errorf("could not identify base for %s: __TEXT segment address (0x%x) > mapping start address (0x%x)", + name, textSegment.Addr, start) + } + + base := start - textSegment.Addr + if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) { - return &fileNM{file: file{b: b, name: name}}, nil + return &fileNM{file: file{b: b, name: name, base: base}}, nil } - return &fileAddr2Line{file: file{b: b, name: name}}, nil + return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil } func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) { ef, err := elf.Open(name) if err != nil { - return nil, fmt.Errorf("Parsing %s: %v", name, err) + return nil, fmt.Errorf("error parsing %s: %v", name, err) } defer ef.Close() @@ -215,9 +230,9 @@ func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFi } } - base, err := elfexec.GetBase(&ef.FileHeader, nil, stextOffset, start, limit, offset) + base, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset) if err != nil { - return nil, fmt.Errorf("Could not identify base for %s: %v", name, err) + return nil, fmt.Errorf("could not identify base for %s: %v", name, err) } buildID := "" diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils_test.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils_test.go index 989a290071..0317cf5126 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils_test.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/binutils_test.go @@ -177,14 +177,20 @@ func TestSetFastSymbolization(t *testing.T) { func skipUnlessLinuxAmd64(t *testing.T) { if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { - t.Skip("Disasm only tested on x86-64 linux") + t.Skip("This test only works on x86-64 Linux") + } +} + +func skipUnlessDarwinAmd64(t *testing.T) { + if runtime.GOOS != "darwin" || runtime.GOARCH != "amd64" { + t.Skip("This test only works on x86-64 Mac") } } func TestDisasm(t *testing.T) { skipUnlessLinuxAmd64(t) bu := &Binutils{} - insts, err := bu.Disasm(filepath.Join("testdata", "hello"), 0, math.MaxUint64) + insts, err := bu.Disasm(filepath.Join("testdata", "exe_linux_64"), 0, math.MaxUint64) if err != nil { t.Fatalf("Disasm: unexpected error %v", err) } @@ -199,42 +205,119 @@ func TestDisasm(t *testing.T) { } } +func findSymbol(syms []*plugin.Sym, name string) *plugin.Sym { + for _, s := range syms { + for _, n := range s.Name { + if n == name { + return s + } + } + } + return nil +} + func TestObjFile(t *testing.T) { skipUnlessLinuxAmd64(t) - bu := &Binutils{} - f, err := bu.Open(filepath.Join("testdata", "hello"), 0, math.MaxUint64, 0) - if err != nil { - t.Fatalf("Open: unexpected error %v", err) - } - defer f.Close() - syms, err := f.Symbols(regexp.MustCompile("main"), 0) - if err != nil { - t.Fatalf("Symbols: unexpected error %v", err) - } + for _, tc := range []struct { + desc string + start, limit, offset uint64 + addr uint64 + }{ + {"fake mapping", 0, math.MaxUint64, 0, 0x40052d}, + {"fixed load address", 0x400000, 0x4006fc, 0, 0x40052d}, + // True user-mode ASLR binaries are ET_DYN rather than ET_EXEC so this case + // is a bit artificial except that it approximates the + // vmlinux-with-kernel-ASLR case where the binary *is* ET_EXEC. + {"simulated ASLR address", 0x500000, 0x5006fc, 0, 0x50052d}, + } { + t.Run(tc.desc, func(t *testing.T) { + bu := &Binutils{} + f, err := bu.Open(filepath.Join("testdata", "exe_linux_64"), tc.start, tc.limit, tc.offset) + if err != nil { + t.Fatalf("Open: unexpected error %v", err) + } + defer f.Close() + syms, err := f.Symbols(regexp.MustCompile("main"), 0) + if err != nil { + t.Fatalf("Symbols: unexpected error %v", err) + } - find := func(name string) *plugin.Sym { - for _, s := range syms { - for _, n := range s.Name { - if n == name { - return s + m := findSymbol(syms, "main") + if m == nil { + t.Fatalf("Symbols: did not find main") + } + for _, addr := range []uint64{m.Start + f.Base(), tc.addr} { + gotFrames, err := f.SourceLine(addr) + if err != nil { + t.Fatalf("SourceLine: unexpected error %v", err) + } + wantFrames := []plugin.Frame{ + {Func: "main", File: "/tmp/hello.c", Line: 3}, + } + if !reflect.DeepEqual(gotFrames, wantFrames) { + t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, wantFrames) } } - } - return nil - } - m := find("main") - if m == nil { - t.Fatalf("Symbols: did not find main") - } - frames, err := f.SourceLine(m.Start) - if err != nil { - t.Fatalf("SourceLine: unexpected error %v", err) - } - expect := []plugin.Frame{ - {Func: "main", File: "/tmp/hello.c", Line: 3}, + }) } - if !reflect.DeepEqual(frames, expect) { - t.Fatalf("SourceLine for main: expect %v; got %v\n", expect, frames) +} + +func TestMachoFiles(t *testing.T) { + skipUnlessDarwinAmd64(t) + + t.Skip("Disabled because of issues with addr2line (see https://github.com/google/pprof/pull/313#issuecomment-364073010)") + + // Load `file`, pretending it was mapped at `start`. Then get the symbol + // table. Check that it contains the symbol `sym` and that the address + // `addr` gives the `expected` stack trace. + for _, tc := range []struct { + desc string + file string + start, limit, offset uint64 + addr uint64 + sym string + expected []plugin.Frame + }{ + {"normal mapping", "exe_mac_64", 0x100000000, math.MaxUint64, 0, + 0x100000f50, "_main", + []plugin.Frame{ + {Func: "main", File: "/tmp/hello.c", Line: 3}, + }}, + {"other mapping", "exe_mac_64", 0x200000000, math.MaxUint64, 0, + 0x200000f50, "_main", + []plugin.Frame{ + {Func: "main", File: "/tmp/hello.c", Line: 3}, + }}, + {"lib normal mapping", "lib_mac_64", 0, math.MaxUint64, 0, + 0xfa0, "_bar", + []plugin.Frame{ + {Func: "bar", File: "/tmp/lib.c", Line: 6}, + }}, + } { + t.Run(tc.desc, func(t *testing.T) { + bu := &Binutils{} + f, err := bu.Open(filepath.Join("testdata", tc.file), tc.start, tc.limit, tc.offset) + if err != nil { + t.Fatalf("Open: unexpected error %v", err) + } + defer f.Close() + syms, err := f.Symbols(nil, 0) + if err != nil { + t.Fatalf("Symbols: unexpected error %v", err) + } + + m := findSymbol(syms, tc.sym) + if m == nil { + t.Fatalf("Symbols: could not find symbol %v", tc.sym) + } + gotFrames, err := f.SourceLine(tc.addr) + if err != nil { + t.Fatalf("SourceLine: unexpected error %v", err) + } + if !reflect.DeepEqual(gotFrames, tc.expected) { + t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, tc.expected) + } + }) } } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go index 1a3b6f8d6a..28c89aa163 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go @@ -34,24 +34,48 @@ var ( func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) { // Collect all symbols from the nm output, grouping names mapped to // the same address into a single symbol. + + // The symbols to return. var symbols []*plugin.Sym + + // The current group of symbol names, and the address they are all at. names, start := []string{}, uint64(0) + buf := bytes.NewBuffer(syms) - for symAddr, name, err := nextSymbol(buf); err == nil; symAddr, name, err = nextSymbol(buf) { + + for { + symAddr, name, err := nextSymbol(buf) + if err == io.EOF { + // Done. If there was an unfinished group, append it. + if len(names) != 0 { + if match := matchSymbol(names, start, symAddr-1, r, address); match != nil { + symbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1}) + } + } + + // And return the symbols. + return symbols, nil + } + if err != nil { + // There was some kind of serious error reading nm's output. return nil, err } - if start == symAddr { + + // If this symbol is at the same address as the current group, add it to the group. + if symAddr == start { names = append(names, name) continue } + + // Otherwise append the current group to the list of symbols. if match := matchSymbol(names, start, symAddr-1, r, address); match != nil { symbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1}) } + + // And start a new group. names, start = []string{name}, symAddr } - - return symbols, nil } // matchSymbol checks if a symbol is to be selected by checking its @@ -62,7 +86,7 @@ func matchSymbol(names []string, start, end uint64, r *regexp.Regexp, address ui return names } for _, name := range names { - if r.MatchString(name) { + if r == nil || r.MatchString(name) { return []string{name} } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/exe_linux_64 b/src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/exe_linux_64 new file mode 100755 index 0000000000..d86dc7cdfc Binary files /dev/null and b/src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/exe_linux_64 differ diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/hello b/src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/hello deleted file mode 100755 index d86dc7cdfc..0000000000 Binary files a/src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/hello and /dev/null differ 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(``) +) + +// 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, "&;", "&;", -1) + + // Dot's SVG output is + // + // + // + // ... + // + // + // + // Change it to + // + // + + // ` + // + // + // ... + // + // + // + + if loc := viewBox.FindStringIndex(svg); loc != nil { + svg = svg[:loc[0]] + + `` + string(svgpan.JSSource) + `` + + `` + + svg[loc[0]:] + } + + if loc := svgClose.FindStringIndex(svg); loc != nil { + svg = svg[:loc[0]] + + `` + + 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) {
File: testbinary
Type: cpu
-Duration: 10s, Total samples = 1.12s (11.20%)
Total: 1.12s

line1000

testdata/file1000.src +Duration: 10s, Total samples = 1.12s (11.20%)
Total: 1.12s

line1000

testdata/file1000.src

   Total:       1.10s      1.10s (flat, cum) 98.21%
       1        1.10s      1.10s           line1                1.10s      1.10s     1000:     instruction one                                                              file1000.src:1
@@ -77,7 +77,7 @@ Duration: 10s, Total samples = 1.12s (11.20%)
Total: 1.12s

line1000< 6 . . line6 7 . . line7

-

line3000

testdata/file3000.src +

line3000

testdata/file3000.src

   Total:        10ms      1.12s (flat, cum)   100%
       1            .          .           line1 
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,209 +16,268 @@ 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"}}
 
 {{end}}
 
 {{define "header"}}
-
- -{{range .Legend}}
{{.}}
{{end}} +
+
+

pprof

+
+ + + + + +
+ +
+ +
+ {{.Title}} +
+ {{range .Legend}}
{{.}}
{{end}} +
+
- -
{{range .Errors}}
{{.}}
{{end}}
{{end}} @@ -226,21 +285,17 @@ Refine - -{{.Title}} -{{template "css" .}} + + {{.Title}} + {{template "css" .}} - -{{template "header" .}} -
-
-{{.HTMLBody}} -
- -
-{{template "script" .}} - + {{template "header" .}} +
+ {{.HTMLBody}} +
+ {{template "script" .}} + {{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") - - 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" + 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; + + 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(); } } @@ -800,116 +857,115 @@ function viewer(baseUrl, nodes) { - -{{.Title}} -{{template "css" .}} - + + {{.Title}} + {{template "css" .}} + + {{template "header" .}} +
+ + + + + + + + + + + + + +
FlatFlat%Sum%CumCum%NameInlined?
+
+ {{template "script" .}} + + viewer({{.BaseURL}}, {{.Nodes}}); + makeTopTable({{.Total}}, {{.Top}}); + {{end}} @@ -918,22 +974,19 @@ makeTopTable({{.Total}}, {{.Top}}) - -{{.Title}} -{{template "css" .}} -{{template "weblistcss" .}} -{{template "weblistjs" .}} + + {{.Title}} + {{template "css" .}} + {{template "weblistcss" .}} + {{template "weblistjs" .}} - -{{template "header" .}} - -
-{{.HTMLBody}} -
- -{{template "script" .}} - + {{template "header" .}} +
+ {{.HTMLBody}} +
+ {{template "script" .}} + {{end}} @@ -942,22 +995,131 @@ makeTopTable({{.Total}}, {{.Top}}) - -{{.Title}} -{{template "css" .}} + + {{.Title}} + {{template "css" .}} + + + {{template "header" .}} +
+
+      {{.TextBody}}
+    
+
+ {{template "script" .}} + + + +{{end}} + +{{define "flamegraph" -}} + + + + + {{.Title}} + {{template "css" .}} + + + {{template "header" .}} +
+
+
+
+
+
+ {{template "script" .}} + + + + + + search.addEventListener('input', handleSearch); + {{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) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go index 9b238c5b87..7e42c88d14 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go @@ -259,3 +259,19 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6 } return 0, fmt.Errorf("Don't know how to handle FileHeader.Type %v", fh.Type) } + +// FindTextProgHeader finds the program segment header containing the .text +// section or nil if the segment cannot be found. +func FindTextProgHeader(f *elf.File) *elf.ProgHeader { + for _, s := range f.Sections { + if s.Name == ".text" { + // Find the LOAD segment containing the .text section. + for _, p := range f.Progs { + if p.Type == elf.PT_LOAD && p.Flags&elf.PF_X != 0 && s.Addr >= p.Vaddr && s.Addr < p.Vaddr+p.Memsz { + return &p.ProgHeader + } + } + } + } + return nil +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go b/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go index 4e5d12f6cd..f3721e92f5 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/graph/dotgraph.go @@ -154,7 +154,7 @@ func (b *builder) addNode(node *Node, nodeID int, maxFlat float64) { if flat != 0 { label = label + fmt.Sprintf(`%s (%s)`, flatValue, - strings.TrimSpace(percentage(flat, b.config.Total))) + strings.TrimSpace(measurement.Percentage(flat, b.config.Total))) } else { label = label + "0" } @@ -168,7 +168,7 @@ func (b *builder) addNode(node *Node, nodeID int, maxFlat float64) { cumValue = b.config.FormatValue(cum) label = label + fmt.Sprintf(`of %s (%s)`, cumValue, - strings.TrimSpace(percentage(cum, b.config.Total))) + strings.TrimSpace(measurement.Percentage(cum, b.config.Total))) } // Scale font sizes from 8 to 24 based on percentage of flat frequency. @@ -380,23 +380,6 @@ func dotColor(score float64, isBackground bool) string { return fmt.Sprintf("#%02x%02x%02x", uint8(r*255.0), uint8(g*255.0), uint8(b*255.0)) } -// percentage computes the percentage of total of a value, and encodes -// it as a string. At least two digits of precision are printed. -func percentage(value, total int64) string { - var ratio float64 - if total != 0 { - ratio = math.Abs(float64(value)/float64(total)) * 100 - } - switch { - case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05: - return " 100%" - case math.Abs(ratio) >= 1.0: - return fmt.Sprintf("%5.2f%%", ratio) - default: - return fmt.Sprintf("%5.2g%%", ratio) - } -} - func multilinePrintableName(info *NodeInfo) string { infoCopy := *info infoCopy.Name = strings.Replace(infoCopy.Name, "::", `\n`, -1) diff --git a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go index 0a60435644..3e3bcb8c25 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go @@ -17,6 +17,7 @@ package measurement import ( "fmt" + "math" "strings" "time" @@ -154,6 +155,23 @@ func ScaledLabel(value int64, fromUnit, toUnit string) string { return sv + u } +// Percentage computes the percentage of total of a value, and encodes +// it as a string. At least two digits of precision are printed. +func Percentage(value, total int64) string { + var ratio float64 + if total != 0 { + ratio = math.Abs(float64(value)/float64(total)) * 100 + } + switch { + case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05: + return " 100%" + case math.Abs(ratio) >= 1.0: + return fmt.Sprintf("%5.2f%%", ratio) + default: + return fmt.Sprintf("%5.2g%%", ratio) + } +} + // isMemoryUnit returns whether a name is recognized as a memory size // unit. func isMemoryUnit(unit string) bool { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/proftest/proftest.go b/src/cmd/vendor/github.com/google/pprof/internal/proftest/proftest.go index 7f9dcab61a..03fac7e330 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/proftest/proftest.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/proftest/proftest.go @@ -19,6 +19,7 @@ package proftest import ( "encoding/json" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -80,11 +81,21 @@ type TestUI struct { Ignore int AllowRx string NumAllowRxMatches int + Input []string + index int } // ReadLine returns no input, as no input is expected during testing. func (ui *TestUI) ReadLine(_ string) (string, error) { - return "", fmt.Errorf("no input") + if ui.index >= len(ui.Input) { + return "", io.EOF + } + input := ui.Input[ui.index] + ui.index++ + if input == "**error**" { + return "", fmt.Errorf("Error: %s", input) + } + return input, nil } // Print messages are discarded by the test UI. diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go index f434554dd9..e127f7fcec 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/report.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/report.go @@ -19,7 +19,6 @@ package report import ( "fmt" "io" - "math" "path/filepath" "regexp" "sort" @@ -425,7 +424,7 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e } fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", rpt.formatValue(flatSum), rpt.formatValue(cumSum), - percentage(cumSum, rpt.total)) + measurement.Percentage(cumSum, rpt.total)) function, file, line := "", "", 0 for _, n := range ns { @@ -704,7 +703,7 @@ func printTags(w io.Writer, rpt *Report) error { for _, t := range graph.SortTags(tags, true) { f, u := measurement.Scale(t.FlatValue(), o.SampleUnit, o.OutputUnit) if total > 0 { - fmt.Fprintf(tabw, " \t%.1f%s (%s):\t %s\n", f, u, percentage(t.FlatValue(), total), t.Name) + fmt.Fprintf(tabw, " \t%.1f%s (%s):\t %s\n", f, u, measurement.Percentage(t.FlatValue(), total), t.Name) } else { fmt.Fprintf(tabw, " \t%.1f%s:\t %s\n", f, u, t.Name) } @@ -789,9 +788,9 @@ func printText(w io.Writer, rpt *Report) error { } flatSum += item.Flat fmt.Fprintf(w, "%10s %s %s %10s %s %s%s\n", - item.FlatFormat, percentage(item.Flat, rpt.total), - percentage(flatSum, rpt.total), - item.CumFormat, percentage(item.Cum, rpt.total), + item.FlatFormat, measurement.Percentage(item.Flat, rpt.total), + measurement.Percentage(flatSum, rpt.total), + item.CumFormat, measurement.Percentage(item.Cum, rpt.total), item.Name, inl) } return nil @@ -1030,17 +1029,17 @@ func printTree(w io.Writer, rpt *Report) error { inline = " (inline)" } fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(in.Weight), - percentage(in.Weight, cum), in.Src.Info.PrintableName(), inline) + measurement.Percentage(in.Weight, cum), in.Src.Info.PrintableName(), inline) } // Print current node. flatSum += flat fmt.Fprintf(w, "%10s %s %s %10s %s | %s\n", rpt.formatValue(flat), - percentage(flat, rpt.total), - percentage(flatSum, rpt.total), + measurement.Percentage(flat, rpt.total), + measurement.Percentage(flatSum, rpt.total), rpt.formatValue(cum), - percentage(cum, rpt.total), + measurement.Percentage(cum, rpt.total), name) // Print outgoing edges. @@ -1051,7 +1050,7 @@ func printTree(w io.Writer, rpt *Report) error { inline = " (inline)" } fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(out.Weight), - percentage(out.Weight, cum), out.Dest.Info.PrintableName(), inline) + measurement.Percentage(out.Weight, cum), out.Dest.Info.PrintableName(), inline) } } if len(g.Nodes) > 0 { @@ -1083,23 +1082,6 @@ func printDOT(w io.Writer, rpt *Report) error { return nil } -// percentage computes the percentage of total of a value, and encodes -// it as a string. At least two digits of precision are printed. -func percentage(value, total int64) string { - var ratio float64 - if total != 0 { - ratio = math.Abs(float64(value)/float64(total)) * 100 - } - switch { - case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05: - return " 100%" - case math.Abs(ratio) >= 1.0: - return fmt.Sprintf("%5.2f%%", ratio) - default: - return fmt.Sprintf("%5.2g%%", ratio) - } -} - // ProfileLabels returns printable labels for a profile. func ProfileLabels(rpt *Report) []string { label := []string{} @@ -1131,7 +1113,7 @@ func ProfileLabels(rpt *Report) []string { totalNanos, totalUnit := measurement.Scale(rpt.total, o.SampleUnit, "nanoseconds") var ratio string if totalUnit == "ns" && totalNanos != 0 { - ratio = "(" + percentage(int64(totalNanos), prof.DurationNanos) + ")" + ratio = "(" + measurement.Percentage(int64(totalNanos), prof.DurationNanos) + ")" } label = append(label, fmt.Sprintf("Duration: %s, Total samples = %s %s", duration, rpt.formatValue(rpt.total), ratio)) } @@ -1162,7 +1144,7 @@ func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedE label = append(label, activeFilters...) } - label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(flatSum), strings.TrimSpace(percentage(flatSum, rpt.total)), rpt.formatValue(rpt.total))) + label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(flatSum), strings.TrimSpace(measurement.Percentage(flatSum, rpt.total)), rpt.formatValue(rpt.total))) if rpt.total != 0 { if droppedNodes > 0 { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go b/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go index e05cf5ad08..c243e20c2b 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/report_test.go @@ -68,8 +68,8 @@ func TestSource(t *testing.T) { want: path + "source.dot", }, } { - b := bytes.NewBuffer(nil) - if err := Generate(b, tc.rpt, &binutils.Binutils{}); err != nil { + var b bytes.Buffer + if err := Generate(&b, tc.rpt, &binutils.Binutils{}); err != nil { t.Fatalf("%s: %v", tc.want, err) } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go index ce82ae55c4..9a98f7460b 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/report/source.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source.go @@ -28,6 +28,7 @@ import ( "strings" "github.com/google/pprof/internal/graph" + "github.com/google/pprof/internal/measurement" "github.com/google/pprof/internal/plugin" ) @@ -99,7 +100,7 @@ func printSource(w io.Writer, rpt *Report) error { fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename) fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", rpt.formatValue(flatSum), rpt.formatValue(cumSum), - percentage(cumSum, rpt.total)) + measurement.Percentage(cumSum, rpt.total)) if err != nil { fmt.Fprintf(w, " Error: %v\n", err) @@ -337,13 +338,13 @@ func printHeader(w io.Writer, rpt *Report) { // printFunctionHeader prints a function header for a weblist report. func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) { - fmt.Fprintf(w, `

%s

%s + fmt.Fprintf(w, `

%s

%s

   Total:  %10s %10s (flat, cum) %s
 `,
 		template.HTMLEscapeString(name), template.HTMLEscapeString(path),
 		rpt.formatValue(flatSum), rpt.formatValue(cumSum),
-		percentage(cumSum, rpt.total))
+		measurement.Percentage(cumSum, rpt.total))
 }
 
 // printFunctionSourceLine prints a source line and the corresponding assembly.
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go b/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go
index 9a2b5a21c4..d45d4c5815 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/report/source_test.go
@@ -25,8 +25,8 @@ func TestWebList(t *testing.T) {
 		SampleValue:  func(v []int64) int64 { return v[1] },
 		SampleUnit:   cpu.SampleType[1].Unit,
 	})
-	buf := bytes.NewBuffer(nil)
-	if err := Generate(buf, rpt, &binutils.Binutils{}); err != nil {
+	var buf bytes.Buffer
+	if err := Generate(&buf, rpt, &binutils.Binutils{}); err != nil {
 		t.Fatalf("could not generate weblist: %v", err)
 	}
 	output := buf.String()
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go b/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go
index 727b7e8c56..5994c11ac7 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go
@@ -168,6 +168,7 @@ func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjToo
 		}
 
 		l.Line = make([]profile.Line, len(stack))
+		l.IsFolded = false
 		for i, frame := range stack {
 			if frame.Func != "" {
 				m.HasFunctions = true
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go b/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go
index 34c119c4c2..086c0ccb04 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go
@@ -63,10 +63,29 @@ func Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, sy
 	return nil
 }
 
+// Check whether path ends with one of the suffixes listed in
+// pprof_remote_servers.html from the gperftools distribution
+func hasGperftoolsSuffix(path string) bool {
+	suffixes := []string{
+		"/pprof/heap",
+		"/pprof/growth",
+		"/pprof/profile",
+		"/pprof/pmuprofile",
+		"/pprof/contention",
+	}
+	for _, s := range suffixes {
+		if strings.HasSuffix(path, s) {
+			return true
+		}
+	}
+	return false
+}
+
 // symbolz returns the corresponding symbolz source for a profile URL.
 func symbolz(source string) string {
 	if url, err := url.Parse(source); err == nil && url.Host != "" {
-		if strings.Contains(url.Path, "/debug/pprof/") {
+		// All paths in the net/http/pprof Go package contain /debug/pprof/
+		if strings.Contains(url.Path, "/debug/pprof/") || hasGperftoolsSuffix(url.Path) {
 			url.Path = path.Clean(url.Path + "/../symbol")
 		} else {
 			url.Path = "/symbolz"
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz_test.go b/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz_test.go
index 270a6198c9..2d13c889b6 100644
--- a/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz_test.go
@@ -34,6 +34,17 @@ func TestSymbolzURL(t *testing.T) {
 		"http://host:8000/debug/pprof/profile?seconds=10":                         "http://host:8000/debug/pprof/symbol",
 		"http://host:8000/debug/pprof/heap":                                       "http://host:8000/debug/pprof/symbol",
 		"http://some.host:8080/some/deeper/path/debug/pprof/endpoint?param=value": "http://some.host:8080/some/deeper/path/debug/pprof/symbol",
+		"http://host:8000/pprof/profile":                                          "http://host:8000/pprof/symbol",
+		"http://host:8000/pprof/profile?seconds=15":                               "http://host:8000/pprof/symbol",
+		"http://host:8000/pprof/heap":                                             "http://host:8000/pprof/symbol",
+		"http://host:8000/debug/pprof/block":                                      "http://host:8000/debug/pprof/symbol",
+		"http://host:8000/debug/pprof/trace?seconds=5":                            "http://host:8000/debug/pprof/symbol",
+		"http://host:8000/debug/pprof/mutex":                                      "http://host:8000/debug/pprof/symbol",
+		"http://host/whatever/pprof/heap":                                         "http://host/whatever/pprof/symbol",
+		"http://host/whatever/pprof/growth":                                       "http://host/whatever/pprof/symbol",
+		"http://host/whatever/pprof/profile":                                      "http://host/whatever/pprof/symbol",
+		"http://host/whatever/pprof/pmuprofile":                                   "http://host/whatever/pprof/symbol",
+		"http://host/whatever/pprof/contention":                                   "http://host/whatever/pprof/symbol",
 	} {
 		if got := symbolz(try); got != want {
 			t.Errorf(`symbolz(%s)=%s, want "%s"`, try, got, want)
-- 
cgit v1.3