From 186aa0362fba6bd2b993f87327140d61341a6345 Mon Sep 17 00:00:00 2001 From: alingse Date: Tue, 28 May 2024 10:51:55 +0800 Subject: [PATCH 01/27] fix: fix miss makezero bug (#867) Signed-off-by: alingse --- internal/graph/graph.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/graph/graph.go b/internal/graph/graph.go index 5ad10a2a..8abbd83f 100644 --- a/internal/graph/graph.go +++ b/internal/graph/graph.go @@ -444,7 +444,7 @@ func newTree(prof *profile.Profile, o *Options) (g *Graph) { } } - nodes := make(Nodes, len(prof.Location)) + nodes := make(Nodes, 0, len(prof.Location)) for _, nm := range parentNodeMap { nodes = append(nodes, nm.nodes()...) } From d3b898a103f8445b6c5e8310155effee21790f7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:40:19 -0700 Subject: [PATCH 02/27] Bump actions/checkout from 4.1.6 to 4.1.7 (#873) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/a5ac7e51b41094c92402da3b24376905380afc29...692973e3d937129bcbf40652eb9f2f61becf3332) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4e98b861..c0bd6bcd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,7 +47,7 @@ jobs: echo "$HOME/gotip/bin:$PATH" >> $GITHUB_PATH - name: Checkout the repo - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: path: ${{ env.WORKING_DIR }} @@ -106,7 +106,7 @@ jobs: echo "$HOME/gotip/bin" >> $GITHUB_PATH - name: Checkout the repo - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: path: ${{ env.WORKING_DIR }} @@ -147,7 +147,7 @@ jobs: go-version: ${{ matrix.go }} - name: Checkout the repo - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: path: ${{ env.WORKING_DIR }} From c177fd99eaa9abe24f3b0de11df101d6701dc03c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 22 Jun 2024 07:43:29 -0700 Subject: [PATCH 03/27] Bump codecov/codecov-action from 4.3.1 to 4.5.0 (#872) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.3.1 to 4.5.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/5ecb98a3c6b747ed38dc09f787459979aebb39be...e28ff129e5465c2c0dcc6f003fc735cb6ae0c673) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexey Alexandrov --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c0bd6bcd..0c901fb3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -72,7 +72,7 @@ jobs: ./test.sh - name: Code coverage - uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: file: ${{ env.WORKING_DIR }}/coverage.txt @@ -130,7 +130,7 @@ jobs: ./test.sh - name: Code coverage - uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: file: ${{ env.WORKING_DIR }}/coverage.txt From 27f56978b8b0a8c1441ba458ef27b0a5869dc4a0 Mon Sep 17 00:00:00 2001 From: Sanjay Ghemawat Date: Mon, 24 Jun 2024 20:09:39 -0700 Subject: [PATCH 04/27] Allow text selection in flamegraph view. (#874) Previously, the click handler would fire after some text was selected with the mouse. This would switch pivots and forget the selected text. We now switch the pivot only if the mouse did not move significantly between mouse-down and mouse-up. Fixes #870. --- internal/driver/html/stacks.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/driver/html/stacks.js b/internal/driver/html/stacks.js index df0f0649..ced7151e 100644 --- a/internal/driver/html/stacks.js +++ b/internal/driver/html/stacks.js @@ -436,13 +436,29 @@ function stackViewer(stacks, nodes) { r.appendChild(t); } - r.addEventListener('click', () => { switchPivots(pprofQuoteMeta(src.UniqueName)); }); + onClick(r, () => { switchPivots(pprofQuoteMeta(src.UniqueName)); }); r.addEventListener('mouseenter', () => { handleEnter(box, r); }); r.addEventListener('mouseleave', () => { handleLeave(box); }); r.addEventListener('contextmenu', (e) => { showActionMenu(e, box); }); return r; } + // Handle clicks, but only if the mouse did not move during the click. + function onClick(target, handler) { + // Disable click if mouse moves more than threshold pixels since mousedown. + const threshold = 3; + let [x, y] = [-1, -1]; + target.addEventListener('mousedown', (e) => { + [x, y] = [e.clientX, e.clientY]; + }); + target.addEventListener('click', (e) => { + if (Math.abs(e.clientX - x) <= threshold && + Math.abs(e.clientY - y) <= threshold) { + handler(); + } + }); + } + function drawSep(y, posTotal, negTotal) { const m = document.createElement('div'); m.innerText = summary(posTotal, negTotal); From f6c9dda6c6da638264f96f1097bce50fd82b4927 Mon Sep 17 00:00:00 2001 From: Sanjay Ghemawat Date: Wed, 10 Jul 2024 21:17:43 -0700 Subject: [PATCH 05/27] Fix stacking order of profile details box. (#878) Previously the profile details box (displayed when clicking button at top-right of web view), would partially overlap flame graph text. Tweak z-index values to fix this problem. Also, colocate all z-index CSS entries to make the stacking order of different things easier to compare. --- internal/driver/html/common.css | 10 ++++++---- internal/driver/html/stacks.css | 3 --- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/driver/html/common.css b/internal/driver/html/common.css index e0de53c1..14f836ff 100644 --- a/internal/driver/html/common.css +++ b/internal/driver/html/common.css @@ -52,7 +52,6 @@ a { } #detailsbox { display: none; - z-index: 1; position: fixed; top: 40px; right: 20px; @@ -107,7 +106,6 @@ a { } .submenu { display: none; - z-index: 1; margin-top: -4px; min-width: 10em; position: absolute; @@ -169,8 +167,6 @@ a { top: 60px; left: 50%; transform: translateX(-50%); - - z-index: 3; font-size: 125%; background-color: #ffffff; box-shadow: 0 1px 5px rgba(0,0,0,.3); @@ -271,3 +267,9 @@ table tr td { background-color: #ebf5fb; font-weight: bold; } +/* stacking order */ +.boxtext { z-index: 2; } /* flame graph box text */ +#current-details { z-index: 2; } /* flame graph current box info */ +#detailsbox { z-index: 3; } /* profile details */ +.submenu { z-index: 4; } +.dialog { z-index: 5; } diff --git a/internal/driver/html/stacks.css b/internal/driver/html/stacks.css index f5aeb985..34c54ebb 100644 --- a/internal/driver/html/stacks.css +++ b/internal/driver/html/stacks.css @@ -19,7 +19,6 @@ body { position: absolute; top: 5px; right: 5px; - z-index: 2; font-size: 12pt; } /* Background of a single flame-graph frame */ @@ -57,8 +56,6 @@ body { font-size: 12pt; font-weight: bold; } -/* Ensure that pprof menu is above boxes */ -.submenu { z-index: 3; } /* Right-click menu */ #action-menu { max-width: 15em; From 7089f98c1d14f3e423867defa159919a38e54d50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 20:33:54 -0700 Subject: [PATCH 06/27] Bump actions/setup-go from 5.0.1 to 5.0.2 (#880) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.0.1 to 5.0.2. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/cdcb36043654635271a94b9a6d1392de5bb323a7...0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0c901fb3..42e93cf6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,7 +29,7 @@ jobs: xcode-version: ['14.2', '14.1', '14.0.1', '13.4.1', '13.3.1', '13.2.1', '13.1'] steps: - name: Update Go version using setup-go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 if: matrix.go != 'tip' with: go-version: ${{ matrix.go }} @@ -88,7 +88,7 @@ jobs: os: ['ubuntu-22.04', 'ubuntu-20.04'] steps: - name: Update Go version using setup-go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 if: matrix.go != 'tip' with: go-version: ${{ matrix.go }} @@ -142,7 +142,7 @@ jobs: go: ['1.20', '1.21'] steps: - name: Update Go version using setup-go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ matrix.go }} From 304e4f0156b8fc5a61243869dc94ea7aa62b4884 Mon Sep 17 00:00:00 2001 From: Sanjay Ghemawat Date: Mon, 22 Jul 2024 08:39:45 -0700 Subject: [PATCH 07/27] Speed-up flame graph generation by skipping unneeded work. (#881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, we used to call report.TextItems() during flame graph generation just so we could get a hand on the legend to print in the profile details box. All the work done by TextItems() to produce a trimmed graph was discarded. This change separates out the legend generation into a separate routine so that we can avoid doing the unnecessary work. Benchmark result: ``` name old time/op new time/op delta Flame-12 6.10s ± 3% 0.39s ± 4% -93.59% (p=0.000 n=10+10) ``` --- internal/driver/stacks.go | 4 +--- internal/report/report.go | 24 +++++++++++++----------- internal/report/stacks.go | 7 +++++++ internal/report/stacks_test.go | 17 +++++++++++++++++ 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/internal/driver/stacks.go b/internal/driver/stacks.go index 355b8f2e..a7936107 100644 --- a/internal/driver/stacks.go +++ b/internal/driver/stacks.go @@ -20,7 +20,6 @@ import ( "net/http" "github.com/google/pprof/internal/measurement" - "github.com/google/pprof/internal/report" ) // stackView generates the flamegraph view. @@ -51,8 +50,7 @@ func (ui *webInterface) stackView(w http.ResponseWriter, req *http.Request) { } nodes[0] = "" // root is not a real node - _, legend := report.TextItems(rpt) - ui.render(w, req, "stacks", rpt, errList, legend, webArgs{ + ui.render(w, req, "stacks", rpt, errList, stacks.Legend(), webArgs{ Stacks: template.JS(b), Nodes: nodes, UnitDefs: measurement.UnitTypes, diff --git a/internal/report/report.go b/internal/report/report.go index d72ebe91..e21ce859 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -781,7 +781,7 @@ type TextItem struct { func TextItems(rpt *Report) ([]TextItem, []string) { g, origCount, droppedNodes, _ := rpt.newTrimmedGraph() rpt.selectOutputUnit(g) - labels := reportLabels(rpt, g, origCount, droppedNodes, 0, false) + labels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false) var items []TextItem var flatSum int64 @@ -1064,7 +1064,7 @@ func printTree(w io.Writer, rpt *Report) error { g, origCount, droppedNodes, _ := rpt.newTrimmedGraph() rpt.selectOutputUnit(g) - fmt.Fprintln(w, strings.Join(reportLabels(rpt, g, origCount, droppedNodes, 0, false), "\n")) + fmt.Fprintln(w, strings.Join(reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false), "\n")) fmt.Fprintln(w, separator) fmt.Fprintln(w, legend) @@ -1128,7 +1128,7 @@ func printTree(w io.Writer, rpt *Report) error { func GetDOT(rpt *Report) (*graph.Graph, *graph.DotConfig) { g, origCount, droppedNodes, droppedEdges := rpt.newTrimmedGraph() rpt.selectOutputUnit(g) - labels := reportLabels(rpt, g, origCount, droppedNodes, droppedEdges, true) + labels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, droppedEdges, true) c := &graph.DotConfig{ Title: rpt.options.Title, @@ -1184,12 +1184,19 @@ func ProfileLabels(rpt *Report) []string { return label } +func graphTotal(g *graph.Graph) int64 { + var total int64 + for _, n := range g.Nodes { + total += n.FlatValue() + } + return total +} + // reportLabels returns printable labels for a report. Includes // profileLabels. -func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string { +func reportLabels(rpt *Report, shownTotal int64, nodeCount, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string { nodeFraction := rpt.options.NodeFraction edgeFraction := rpt.options.EdgeFraction - nodeCount := len(g.Nodes) var label []string if len(rpt.options.ProfileLabels) > 0 { @@ -1198,17 +1205,12 @@ func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedE label = ProfileLabels(rpt) } - var flatSum int64 - for _, n := range g.Nodes { - flatSum = flatSum + n.FlatValue() - } - if len(rpt.options.ActiveFilters) > 0 { activeFilters := legendActiveFilters(rpt.options.ActiveFilters) label = append(label, activeFilters...) } - 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))) + label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(shownTotal), strings.TrimSpace(measurement.Percentage(shownTotal, rpt.total)), rpt.formatValue(rpt.total))) if rpt.total != 0 { if droppedNodes > 0 { diff --git a/internal/report/stacks.go b/internal/report/stacks.go index aa3bf80f..c6b07b86 100644 --- a/internal/report/stacks.go +++ b/internal/report/stacks.go @@ -34,6 +34,7 @@ type StackSet struct { Unit string // One of "B", "s", "GCU", or "" (if unknown) Stacks []Stack // List of stored stacks Sources []StackSource // Mapping from source index to info + report *Report } // Stack holds a single stack instance. @@ -94,6 +95,7 @@ func (rpt *Report) Stacks() StackSet { Unit: unit, Stacks: []Stack{}, // Ensure non-nil Sources: []StackSource{}, // Ensure non-nil + report: rpt, } s.makeInitialStacks(rpt) s.fillPlaces() @@ -187,3 +189,8 @@ func (s *StackSet) assignColors() { s.Sources[i].Color = int(index % numColors) } } + +// Legend returns the list of lines to display as the legend. +func (s *StackSet) Legend() []string { + return reportLabels(s.report, s.report.total, len(s.Sources), len(s.Sources), 0, 0, false) +} diff --git a/internal/report/stacks_test.go b/internal/report/stacks_test.go index 660b76d0..22d024ba 100644 --- a/internal/report/stacks_test.go +++ b/internal/report/stacks_test.go @@ -3,6 +3,7 @@ package report import ( "fmt" "reflect" + "strings" "testing" "github.com/google/pprof/profile" @@ -164,6 +165,22 @@ func TestStackSources(t *testing.T) { } } +func TestLegend(t *testing.T) { + // See report_test.go for the functions available to use in tests. + main, foo, bar, tee := testL[0], testL[1], testL[2], testL[3] + stacks := makeTestStacks( + testSample(100, bar, foo, main), + testSample(200, tee, foo, main), + ) + got := strings.Join(stacks.Legend(), "\n") + expectStrings := []string{"Type: samples", "Showing nodes", "100% of 300 total"} + for _, expect := range expectStrings { + if !strings.Contains(got, expect) { + t.Errorf("missing expected string %q in legend %q", expect, got) + } + } +} + func findSource(stacks StackSet, name string) StackSource { for _, src := range stacks.Sources { if src.FullName == name { From 813a5fbdbec8a66f7a5aedb876e1b2c3ee0f99ac Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Sat, 27 Jul 2024 17:45:55 +0200 Subject: [PATCH 08/27] Use llvm-symbolizer's JSON output for symbolizing (#879) In some edge cases (e.g. injected JIT symbols), function names can have new lines. This breaks the llvm-symbolizer output parsing, and makes pprof hang. Conveniently, as of LLVM 13, llvm-symbolizer has a JSON output mode, which is robust against all kinds of weirdness like new lines. We can use this instead of the line-based parsing, and as a bonus we get much simpler handling of multiple frames in a stack, as the JSON output already returns these as an array. This also requires splitting the CODE and DATA processing into separate functions, since their JSON output is incompatible. For now, we keep the DATA output as before, a slightly hacky but functional concatenation of start + size, but this could be improved. Co-authored-by: Alexey Alexandrov --- internal/binutils/addr2liner_llvm.go | 118 +++++++++--------- .../binutils/testdata/fake-llvm-symbolizer | 16 +-- 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/internal/binutils/addr2liner_llvm.go b/internal/binutils/addr2liner_llvm.go index 3049545b..5e51644f 100644 --- a/internal/binutils/addr2liner_llvm.go +++ b/internal/binutils/addr2liner_llvm.go @@ -16,6 +16,7 @@ package binutils import ( "bufio" + "encoding/json" "fmt" "io" "os/exec" @@ -37,6 +38,7 @@ type llvmSymbolizer struct { filename string rw lineReaderWriter base uint64 + isData bool } type llvmSymbolizerJob struct { @@ -76,7 +78,7 @@ func newLLVMSymbolizer(cmd, file string, base uint64, isData bool) (*llvmSymboli } j := &llvmSymbolizerJob{ - cmd: exec.Command(cmd, "--inlining", "-demangle=false"), + cmd: exec.Command(cmd, "--inlining", "-demangle=false", "--output-style=JSON"), symType: "CODE", } if isData { @@ -102,63 +104,67 @@ func newLLVMSymbolizer(cmd, file string, base uint64, isData bool) (*llvmSymboli filename: file, rw: j, base: base, + isData: isData, } return a, nil } -// readFrame parses the llvm-symbolizer output for a single address. It -// returns a populated plugin.Frame and whether it has reached the end of the -// data. -func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) { - funcname, err := d.rw.readLine() +// readDataFrames parses the llvm-symbolizer DATA output for a single address. It +// returns a populated plugin.Frame array with a single entry. +func (d *llvmSymbolizer) readDataFrames() ([]plugin.Frame, error) { + line, err := d.rw.readLine() if err != nil { - return plugin.Frame{}, true + return nil, err } - - switch funcname { - case "": - return plugin.Frame{}, true - case "??": - funcname = "" + var frame struct { + Address string `json:"Address"` + ModuleName string `json:"ModuleName"` + Data struct { + Start string `json:"Start"` + Size string `json:"Size"` + Name string `json:"Name"` + } `json:"Data"` + } + if err := json.Unmarshal([]byte(line), &frame); err != nil { + return nil, err + } + // Match non-JSON output behaviour of stuffing the start/size into the filename of a single frame, + // with the size being a decimal value. + size, err := strconv.ParseInt(frame.Data.Size, 0, 0) + if err != nil { + return nil, err } + var stack []plugin.Frame + stack = append(stack, plugin.Frame{Func: frame.Data.Name, File: fmt.Sprintf("%s %d", frame.Data.Start, size)}) + return stack, nil +} - fileline, err := d.rw.readLine() +// readCodeFrames parses the llvm-symbolizer CODE output for a single address. It +// returns a populated plugin.Frame array. +func (d *llvmSymbolizer) readCodeFrames() ([]plugin.Frame, error) { + line, err := d.rw.readLine() if err != nil { - return plugin.Frame{Func: funcname}, true - } - - linenumber := 0 - columnnumber := 0 - // The llvm-symbolizer outputs the ::. - // When it cannot identify the source code location, it outputs "??:0:0". - // Older versions output just the filename and line number, so we check for - // both conditions here. - if fileline == "??:0" || fileline == "??:0:0" { - fileline = "" - } else { - switch split := strings.Split(fileline, ":"); len(split) { - case 3: - // filename:line:column - if col, err := strconv.Atoi(split[2]); err == nil { - columnnumber = col - } - fallthrough - case 2: - // filename:line - if line, err := strconv.Atoi(split[1]); err == nil { - linenumber = line - } - fallthrough - case 1: - // filename - fileline = split[0] - default: - // Unrecognized, ignore - } - } - - return plugin.Frame{Func: funcname, File: fileline, Line: linenumber, Column: columnnumber}, false + return nil, err + } + var frame struct { + Address string `json:"Address"` + ModuleName string `json:"ModuleName"` + Symbol []struct { + Line int `json:"Line"` + Column int `json:"Column"` + FunctionName string `json:"FunctionName"` + FileName string `json:"FileName"` + } `json:"Symbol"` + } + if err := json.Unmarshal([]byte(line), &frame); err != nil { + return nil, err + } + var stack []plugin.Frame + for _, s := range frame.Symbol { + stack = append(stack, plugin.Frame{Func: s.FunctionName, File: s.FileName, Line: s.Line, Column: s.Column}) + } + return stack, nil } // addrInfo returns the stack frame information for a specific program @@ -170,18 +176,8 @@ func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) { if err := d.rw.write(fmt.Sprintf("%s 0x%x", d.filename, addr-d.base)); err != nil { return nil, err } - - var stack []plugin.Frame - for { - frame, end := d.readFrame() - if end { - break - } - - if frame != (plugin.Frame{}) { - stack = append(stack, frame) - } + if d.isData { + return d.readDataFrames() } - - return stack, nil + return d.readCodeFrames() } diff --git a/internal/binutils/testdata/fake-llvm-symbolizer b/internal/binutils/testdata/fake-llvm-symbolizer index a3b4546d..507761c9 100755 --- a/internal/binutils/testdata/fake-llvm-symbolizer +++ b/internal/binutils/testdata/fake-llvm-symbolizer @@ -22,22 +22,18 @@ IFS=" " while read line; do # line has form: # filename 0xaddr - # Emit dummy output that matches llvm-symbolizer output format. + # Emit dummy output that matches llvm-symbolizer JSON output format. set -- ${line} kind=$1 fname=$2 addr=$3 case ${kind} in CODE) - echo "Inlined_${addr}" - echo "${fname}.h" - echo "Func_${addr}" - echo "${fname}.c:2:1" - echo;; + echo "{\"Address\":\"${addr}\",\"ModuleName\":\"${fname}\",\"Symbol\":[{\"Column\":0,\"FileName\":\"${fname}.h\",\"FunctionName\":\"Inlined_${addr}\",\"Line\":0},{\"Column\":1,\"FileName\":\"${fname}.c\",\"FunctionName\":\"Func_${addr}\",\"Line\":2}]}" + ;; DATA) - echo "${fname}_${addr}" - echo "${addr} 8" - echo;; - *) echo ${kind} ${fname} ${addr};; + echo "{\"Address\":\"${addr}\",\"ModuleName\":\"${fname}\",\"Data\":{\"Name\":\"${fname}_${addr}\",\"Size\":\"0x8\",\"Start\":\"${addr}\"}}" + ;; + *) exit 1;; esac done From fa2c70bbbfe53334fe0fd4c80336583970707379 Mon Sep 17 00:00:00 2001 From: Sanjay Ghemawat Date: Tue, 27 Aug 2024 10:19:23 -0700 Subject: [PATCH 09/27] Update runner to Go 1.22,1.23 (two most recent releases). (#887) * Update runner to Go 1.22,1.23 (two most recent releases). * Update runner to Go 1.22,1.23 (two most recent releases). * Update Go mod to 1.22 as well. * Update staticcheck to 0.5.1 so it works with 1.23 code * Update golangci-lint to 1.60.3. v1.51 fails with errors related to old slices package. * Require Go 1.22 for bootstrapping Go tip * Github actions do not support else statements * Another attempt at Workflow syntax * Another attempt at Workflow syntax * Fix caching for Go bootstrap compiler * Fix caching for Go bootstrap compiler * Fix caching for Go bootstrap compiler * Fix caching for Go bootstrap compiler * Fix caching for Go bootstrap compiler * Fix caching for Go bootstrap compiler * Fix caching for Go bootstrap compiler * Add comments saying why we need cache directives --- .github/workflows/ci.yaml | 78 ++++++++++++++++++++++++++++----------- browsertests/go.mod | 2 +- go.mod | 2 +- 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 42e93cf6..61ab05ae 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.20', '1.21', 'tip'] + go: ['1.22', '1.23', 'tip'] # Supported macOS versions can be found in # https://github.com/actions/virtual-environments#available-environments. # TODO: Add macos-13. As of now there are build errors when installing graphviz. @@ -28,11 +28,31 @@ jobs: # - https://github.com/actions/virtual-environments/blob/main/images/macos/macos-13-Readme.md#xcode xcode-version: ['14.2', '14.1', '14.0.1', '13.4.1', '13.3.1', '13.2.1', '13.1'] steps: + - name: Checkout the repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + path: ${{ env.WORKING_DIR }} + - name: Update Go version using setup-go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 if: matrix.go != 'tip' with: + # Include cache directives to allow proper caching. Without them, we + # get setup-go "Restore cache failed" warnings. go-version: ${{ matrix.go }} + cache: true + cache-dependency-path: '**/go.sum' + + - name: Install Go bootstrap compiler + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + if: matrix.go == 'tip' + with: + # Bootstrapping go tip requires 1.22.6 + # Include cache directives to allow proper caching. Without them, we + # get setup-go "Restore cache failed" warnings. + go-version: 1.22 + cache: true + cache-dependency-path: '**/go.sum' - name: Update Go version manually if: matrix.go == 'tip' @@ -46,11 +66,6 @@ jobs: echo "RUN_GOLANGCI_LINTER=false" >> $GITHUB_ENV echo "$HOME/gotip/bin:$PATH" >> $GITHUB_PATH - - name: Checkout the repo - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - path: ${{ env.WORKING_DIR }} - - name: Set up Xcode uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 with: @@ -61,8 +76,8 @@ jobs: brew install graphviz # Do not let tools interfere with the main module's go.mod. cd && go mod init tools - go install honnef.co/go/tools/cmd/staticcheck@v0.4.6 - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0 + go install honnef.co/go/tools/cmd/staticcheck@v0.5.1 + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 # Add PATH for installed tools. echo "$GOPATH/bin:$PATH" >> $GITHUB_PATH @@ -84,14 +99,34 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.20', '1.21', 'tip'] + go: ['1.22', '1.23', 'tip'] os: ['ubuntu-22.04', 'ubuntu-20.04'] steps: + - name: Checkout the repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + path: ${{ env.WORKING_DIR }} + - name: Update Go version using setup-go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 if: matrix.go != 'tip' with: + # Include cache directives to allow proper caching. Without them, we + # get setup-go "Restore cache failed" warnings. go-version: ${{ matrix.go }} + cache: true + cache-dependency-path: '**/go.sum' + + - name: Install Go bootstrap compiler + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + if: matrix.go == 'tip' + with: + # Bootstrapping go tip requires 1.22.6 + # Include cache directives to allow proper caching. Without them, we + # get setup-go "Restore cache failed" warnings. + go-version: 1.22 + cache: true + cache-dependency-path: '**/go.sum' - name: Update Go version manually if: matrix.go == 'tip' @@ -105,11 +140,6 @@ jobs: echo "RUN_GOLANGCI_LINTER=false" >> $GITHUB_ENV echo "$HOME/gotip/bin" >> $GITHUB_PATH - - name: Checkout the repo - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - path: ${{ env.WORKING_DIR }} - - name: Check chrome for browser tests run: | google-chrome --version @@ -119,8 +149,8 @@ jobs: sudo apt-get install graphviz # Do not let tools interfere with the main module's go.mod. cd && go mod init tools - go install honnef.co/go/tools/cmd/staticcheck@v0.4.6 - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0 + go install honnef.co/go/tools/cmd/staticcheck@v0.5.1 + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 # Add PATH for installed tools. echo "$GOPATH/bin:$PATH" >> $GITHUB_PATH @@ -139,18 +169,22 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.20', '1.21'] + go: ['1.22', '1.23'] steps: - - name: Update Go version using setup-go - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 - with: - go-version: ${{ matrix.go }} - - name: Checkout the repo uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: path: ${{ env.WORKING_DIR }} + - name: Update Go version using setup-go + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + # Include cache directives to allow proper caching. Without them, we + # get setup-go "Restore cache failed" warnings. + go-version: ${{ matrix.go }} + cache: true + cache-dependency-path: '**/go.sum' + - name: Fetch Windows dependency uses: crazy-max/ghaction-chocolatey@0e015857dd851f84fcb7fb53380eb5c4c8202333 # v3.0.0 with: diff --git a/browsertests/go.mod b/browsertests/go.mod index 947ece64..2d571a03 100644 --- a/browsertests/go.mod +++ b/browsertests/go.mod @@ -1,6 +1,6 @@ module github.com/google/pprof/browsertests -go 1.19 +go 1.22 // Use the version of pprof in this directory tree. replace github.com/google/pprof => ../ diff --git a/go.mod b/go.mod index f294479b..ea9ed8e6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/google/pprof -go 1.19 +go 1.22 require ( github.com/chzyer/readline v1.5.1 From da1f7e9f2b251de696b28108b5a2b1c0bbce44ed Mon Sep 17 00:00:00 2001 From: Sanjay Ghemawat Date: Thu, 29 Aug 2024 09:03:00 -0700 Subject: [PATCH 10/27] Add doc URL to profile format and use it display help link. (#888) * Add doc URL to profile format and use it display help link. * Add test for Report.DocURL * Update new proto field comment --- internal/driver/html/common.css | 4 +++ internal/driver/html/header.html | 6 ++++ internal/driver/webui.go | 2 ++ internal/report/report.go | 17 +++++++++ internal/report/report_test.go | 29 +++++++++++++++ profile/encode.go | 5 +++ profile/merge.go | 5 +++ profile/merge_test.go | 61 ++++++++++++++++++++++++++++++++ profile/profile.go | 5 +++ profile/profile_test.go | 22 ++++++++++++ proto/profile.proto | 6 ++++ 11 files changed, 162 insertions(+) diff --git a/internal/driver/html/common.css b/internal/driver/html/common.css index 14f836ff..0a897ce2 100644 --- a/internal/driver/html/common.css +++ b/internal/driver/html/common.css @@ -148,6 +148,10 @@ a { right: 2px; } +.help { + padding-left: 1em; +} + {{/* Used to disable events when a modal dialog is displayed */}} #dialog-overlay { display: none; diff --git a/internal/driver/html/header.html b/internal/driver/html/header.html index e946e6b8..5405a0be 100644 --- a/internal/driver/html/header.html +++ b/internal/driver/html/header.html @@ -83,6 +83,12 @@

pprof

{{range .Legend}}
{{.}}
{{end}} + + {{if .DocURL}} + + {{end}}
diff --git a/internal/driver/webui.go b/internal/driver/webui.go index 2a2d7fb1..dd628f7c 100644 --- a/internal/driver/webui.go +++ b/internal/driver/webui.go @@ -79,6 +79,7 @@ type webArgs struct { Total int64 SampleTypes []string Legend []string + DocURL string Standalone bool // True for command-line generation of HTML Help map[string]string Nodes []string @@ -290,6 +291,7 @@ func renderHTML(dst io.Writer, tmpl string, rpt *report.Report, errList, legend data.Title = file + " " + profile data.Errors = errList data.Total = rpt.Total() + data.DocURL = rpt.DocURL() data.Legend = legend return getHTMLTemplates().ExecuteTemplate(dst, tmpl, data) } diff --git a/internal/report/report.go b/internal/report/report.go index e21ce859..7035e231 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -19,6 +19,7 @@ package report import ( "fmt" "io" + "net/url" "path/filepath" "regexp" "sort" @@ -1331,6 +1332,22 @@ func (rpt *Report) Total() int64 { return rpt.total } // OutputFormat returns the output format for the report. func (rpt *Report) OutputFormat() int { return rpt.options.OutputFormat } +// DocURL returns the documentation URL for Report, or "" if not available. +func (rpt *Report) DocURL() string { + u := rpt.prof.DocURL + if u == "" || !absoluteURL(u) { + return "" + } + return u +} + +func absoluteURL(str string) bool { + // Avoid returning relative URLs to prevent unwanted local navigation + // within pprof server. + u, err := url.Parse(str) + return err == nil && (u.Scheme == "https" || u.Scheme == "http") +} + func abs64(i int64) int64 { if i < 0 { return -i diff --git a/internal/report/report_test.go b/internal/report/report_test.go index e21189b4..08ce9da1 100644 --- a/internal/report/report_test.go +++ b/internal/report/report_test.go @@ -547,3 +547,32 @@ func TestPrintAssemblyErrorMessage(t *testing.T) { } } } + +func TestDocURL(t *testing.T) { + type testCase struct { + input string + want string + } + for name, c := range map[string]testCase{ + "empty": {"", ""}, + "http": {"http://example.com/pprof-help", "http://example.com/pprof-help"}, + "https": {"https://example.com/pprof-help", "https://example.com/pprof-help"}, + "relative": {"/foo", ""}, + "nonhttp": {"mailto:nobody@example.com", ""}, + } { + t.Run(name, func(t *testing.T) { + profile := testProfile.Copy() + profile.DocURL = c.input + rpt := New(profile, &Options{ + OutputFormat: Dot, + Symbol: regexp.MustCompile(`.`), + TrimPath: "/some/path", + SampleValue: func(v []int64) int64 { return v[1] }, + SampleUnit: testProfile.SampleType[1].Unit, + }) + if got := rpt.DocURL(); got != c.want { + t.Errorf("bad doc URL %q, expecting %q", got, c.want) + } + }) + } +} diff --git a/profile/encode.go b/profile/encode.go index 860bb304..8ce9d3cf 100644 --- a/profile/encode.go +++ b/profile/encode.go @@ -122,6 +122,7 @@ func (p *Profile) preEncode() { } p.defaultSampleTypeX = addString(strings, p.DefaultSampleType) + p.docURLX = addString(strings, p.DocURL) p.stringTable = make([]string, len(strings)) for s, i := range strings { @@ -156,6 +157,7 @@ func (p *Profile) encode(b *buffer) { encodeInt64Opt(b, 12, p.Period) encodeInt64s(b, 13, p.commentX) encodeInt64(b, 14, p.defaultSampleTypeX) + encodeInt64Opt(b, 15, p.docURLX) } var profileDecoder = []decoder{ @@ -237,6 +239,8 @@ var profileDecoder = []decoder{ func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Profile).commentX) }, // int64 defaultSampleType = 14 func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).defaultSampleTypeX) }, + // string doc_link = 15; + func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).docURLX) }, } // postDecode takes the unexported fields populated by decode (with @@ -384,6 +388,7 @@ func (p *Profile) postDecode() error { p.commentX = nil p.DefaultSampleType, err = getString(p.stringTable, &p.defaultSampleTypeX, err) + p.DocURL, err = getString(p.stringTable, &p.docURLX, err) p.stringTable = nil return err } diff --git a/profile/merge.go b/profile/merge.go index eee0132e..ba4d7464 100644 --- a/profile/merge.go +++ b/profile/merge.go @@ -476,6 +476,7 @@ func combineHeaders(srcs []*Profile) (*Profile, error) { var timeNanos, durationNanos, period int64 var comments []string seenComments := map[string]bool{} + var docURL string var defaultSampleType string for _, s := range srcs { if timeNanos == 0 || s.TimeNanos < timeNanos { @@ -494,6 +495,9 @@ func combineHeaders(srcs []*Profile) (*Profile, error) { if defaultSampleType == "" { defaultSampleType = s.DefaultSampleType } + if docURL == "" { + docURL = s.DocURL + } } p := &Profile{ @@ -509,6 +513,7 @@ func combineHeaders(srcs []*Profile) (*Profile, error) { Comments: comments, DefaultSampleType: defaultSampleType, + DocURL: docURL, } copy(p.SampleType, srcs[0].SampleType) return p, nil diff --git a/profile/merge_test.go b/profile/merge_test.go index 8c181f0c..d2d05e63 100644 --- a/profile/merge_test.go +++ b/profile/merge_test.go @@ -17,6 +17,7 @@ package profile import ( "bytes" "fmt" + "reflect" "testing" "github.com/google/pprof/internal/proftest" @@ -443,3 +444,63 @@ func TestCompatibilizeSampleTypes(t *testing.T) { }) } } + +func TestDocURLMerge(t *testing.T) { + const url1 = "http://example.com/url1" + const url2 = "http://example.com/url2" + type testCase struct { + name string + profiles []*Profile + want string + } + profile := func(url string) *Profile { + return &Profile{ + PeriodType: &ValueType{Type: "cpu", Unit: "seconds"}, + DocURL: url, + } + } + for _, test := range []testCase{ + { + name: "nolinks", + profiles: []*Profile{ + profile(""), + profile(""), + }, + want: "", + }, + { + name: "single", + profiles: []*Profile{ + profile(url1), + }, + want: url1, + }, + { + name: "mix", + profiles: []*Profile{ + profile(""), + profile(url1), + }, + want: url1, + }, + { + name: "different", + profiles: []*Profile{ + profile(url1), + profile(url2), + }, + want: url1, + }, + } { + t.Run(test.name, func(t *testing.T) { + merged, err := combineHeaders(test.profiles) + if err != nil { + t.Fatal(err) + } + got := merged.DocURL + if !reflect.DeepEqual(test.want, got) { + t.Errorf("unexpected links; want: %#v, got: %#v", test.want, got) + } + }) + } +} diff --git a/profile/profile.go b/profile/profile.go index 5551eb0b..0983656c 100644 --- a/profile/profile.go +++ b/profile/profile.go @@ -39,6 +39,7 @@ type Profile struct { Location []*Location Function []*Function Comments []string + DocURL string DropFrames string KeepFrames string @@ -53,6 +54,7 @@ type Profile struct { encodeMu sync.Mutex commentX []int64 + docURLX int64 dropFramesX int64 keepFramesX int64 stringTable []string @@ -555,6 +557,9 @@ func (p *Profile) String() string { for _, c := range p.Comments { ss = append(ss, "Comment: "+c) } + if url := p.DocURL; url != "" { + ss = append(ss, fmt.Sprintf("Doc: %s", url)) + } if pt := p.PeriodType; pt != nil { ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit)) } diff --git a/profile/profile_test.go b/profile/profile_test.go index c9e39a62..31adb66f 100644 --- a/profile/profile_test.go +++ b/profile/profile_test.go @@ -1869,6 +1869,28 @@ func TestParseKernelRelocation(t *testing.T) { } } +func TestEncodeDecodeDocURL(t *testing.T) { + input := testProfile1.Copy() + input.DocURL = "http://example.comp/url" + + // Encode/decode. + var buf bytes.Buffer + if err := input.Write(&buf); err != nil { + t.Fatal("encode: ", err) + } + output, err := Parse(&buf) + if err != nil { + t.Fatal("decode: ", err) + } + if want, got := input.String(), output.String(); want != got { + d, err := proftest.Diff([]byte(want), []byte(got)) + if err != nil { + t.Fatal(err) + } + t.Errorf("wrong result of encode/decode (-want,+got):\n%s\n", string(d)) + } +} + // parallel runs n copies of fn in parallel. func parallel(n int, fn func()) { var wg sync.WaitGroup diff --git a/proto/profile.proto b/proto/profile.proto index ff987a61..9cb816e6 100644 --- a/proto/profile.proto +++ b/proto/profile.proto @@ -93,6 +93,12 @@ message Profile { // Index into the string table of the type of the preferred sample // value. If unset, clients should default to the last sample value. int64 default_sample_type = 14; + // Documentation link for this profile. The URL must be absolute, + // e.g., http://pprof.example.com/cpu-profile.html + // + // The URL may be missing if the profile was generated by older code or code + // that did not bother to supply a link. + int64 doc_url = 15; // Index into string table. } // ValueType describes the semantics and measurement units of a value. From a8630aee4ab9e36dfc54c2e0a4df49abb8345dbd Mon Sep 17 00:00:00 2001 From: Sanjay Ghemawat Date: Tue, 3 Sep 2024 08:56:34 -0700 Subject: [PATCH 11/27] Added doc link to profile header. (#892) Output looks something like: ``` % pprof -top ... File: ... Build ID: ... Type: cpu Doc: http://example.com/cpuprofile Duration: 6.71s, Total samples = 5.72s (85.30%) Showing nodes accounting for 5.17s, 90.38% of 5.72s total Dropped 326 nodes (cum <= 0.03s) ... ``` --- internal/report/report.go | 3 +++ internal/report/report_test.go | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/internal/report/report.go b/internal/report/report.go index 7035e231..5ab0da65 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -1169,6 +1169,9 @@ func ProfileLabels(rpt *Report) []string { if o.SampleType != "" { label = append(label, "Type: "+o.SampleType) } + if url := prof.DocURL; url != "" { + label = append(label, "Doc: "+url) + } if prof.TimeNanos != 0 { const layout = "Jan 2, 2006 at 3:04pm (MST)" label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout)) diff --git a/internal/report/report_test.go b/internal/report/report_test.go index 08ce9da1..845c3690 100644 --- a/internal/report/report_test.go +++ b/internal/report/report_test.go @@ -16,6 +16,7 @@ package report import ( "bytes" + "fmt" "os" "path/filepath" "regexp" @@ -576,3 +577,21 @@ func TestDocURL(t *testing.T) { }) } } + +func TestDocURLInLabels(t *testing.T) { + const url = "http://example.com/pprof-help" + profile := testProfile.Copy() + profile.DocURL = url + rpt := New(profile, &Options{ + OutputFormat: Text, + Symbol: regexp.MustCompile(`.`), + TrimPath: "/some/path", + SampleValue: func(v []int64) int64 { return v[1] }, + SampleUnit: testProfile.SampleType[1].Unit, + }) + + labels := fmt.Sprintf("%v", ProfileLabels(rpt)) + if !strings.Contains(labels, url) { + t.Errorf("expected URL %q not found in %s", url, labels) + } +} From a0b0bb1d4134f37be44de6c328680339b0fc13ad Mon Sep 17 00:00:00 2001 From: cui fliter <15921519+cuishuang@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:07:28 +0800 Subject: [PATCH 12/27] Remove unnecessary symbols and add missing symbols (#893) Signed-off-by: cuishuang --- browsertests/testdata/testfixture.js | 2 +- profile/profile.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browsertests/testdata/testfixture.js b/browsertests/testdata/testfixture.js index 960866cf..ad31d05f 100644 --- a/browsertests/testdata/testfixture.js +++ b/browsertests/testdata/testfixture.js @@ -1,6 +1,6 @@ // TestFixture records log messages and errors in an array that will // be returned to Go code. Each element in the result array is either -// an array of the form ["LOG", ...]", or ["ERROR", ...]. +// an array of the form ["LOG", ...], or ["ERROR", ...]. class TestFixture { constructor() { this.context = ""; // Added to front of all log and error messages. diff --git a/profile/profile.go b/profile/profile.go index 0983656c..f47a2439 100644 --- a/profile/profile.go +++ b/profile/profile.go @@ -849,7 +849,7 @@ func (p *Profile) HasFileLines() bool { // Unsymbolizable returns true if a mapping points to a binary for which // locations can't be symbolized in principle, at least now. Examples are -// "[vdso]", [vsyscall]" and some others, see the code. +// "[vdso]", "[vsyscall]" and some others, see the code. func (m *Mapping) Unsymbolizable() bool { name := filepath.Base(m.File) return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") || m.File == "//anon" From e2af92caf866685b99dc3b98e79fcd7e17efbbfe Mon Sep 17 00:00:00 2001 From: Alexey Alexandrov Date: Wed, 25 Sep 2024 15:23:11 -0700 Subject: [PATCH 13/27] Replace remaining tabs with spaces in stacks.js. (#898) --- internal/driver/html/stacks.js | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/internal/driver/html/stacks.js b/internal/driver/html/stacks.js index ced7151e..c89faf19 100644 --- a/internal/driver/html/stacks.js +++ b/internal/driver/html/stacks.js @@ -262,22 +262,22 @@ function stackViewer(stacks, nodes) { // // Group represents a displayed (sub)tree. // interface Group { // name: string; // Full name of source - // src: number; // Index in stacks.Sources + // src: number; // Index in stacks.Sources // self: number; // Contribution as leaf (may be < 0 for diffs) - // sumpos: number; // Sum of |self| of positive nodes in tree (>= 0) - // sumneg: number; // Sum of |self| of negative nodes in tree (>= 0) + // sumpos: number; // Sum of |self| of positive nodes in tree (>= 0) + // sumneg: number; // Sum of |self| of negative nodes in tree (>= 0) // places: Place[]; // Stack slots that contributed to this group // } // // // Box is a rendered item. // interface Box { - // x: number; // X coordinate of top-left - // y: number; // Y coordinate of top-left - // width: number; // Width of box to display - // src: number; // Index in stacks.Sources - // sumpos: number; // From corresponding Group - // sumneg: number; // From corresponding Group - // self: number; // From corresponding Group + // x: number; // X coordinate of top-left + // y: number; // Y coordinate of top-left + // width: number; // Width of box to display + // src: number; // Index in stacks.Sources + // sumpos: number; // From corresponding Group + // sumneg: number; // From corresponding Group + // self: number; // From corresponding Group // }; function groupWidth(xscale, g) { @@ -297,14 +297,14 @@ function stackViewer(stacks, nodes) { y: y, width: width, src: g.src, - sumpos: g.sumpos, - sumneg: g.sumneg, + sumpos: g.sumpos, + sumneg: g.sumneg, self: g.self, }; displayList.push(box); if (direction > 0) { - // Leave gap on left hand side to indicate self contribution. - x += xscale*Math.abs(g.self); + // Leave gap on left hand side to indicate self contribution. + x += xscale*Math.abs(g.self); } } y += direction * ROW; @@ -354,9 +354,9 @@ function stackViewer(stacks, nodes) { groups.push(group); } if (stack.Value < 0) { - group.sumneg += -stack.Value; + group.sumneg += -stack.Value; } else { - group.sumpos += stack.Value; + group.sumpos += stack.Value; } group.self += (place.Pos == stack.Sources.length-1) ? stack.Value : 0; group.places.push(place); @@ -423,8 +423,8 @@ function stackViewer(stacks, nodes) { const delta = box.sumpos - box.sumneg; const partWidth = xscale * Math.abs(delta); if (partWidth >= MIN_WIDTH) { - r.appendChild(makeRect((delta < 0 ? 'negative' : 'positive'), - 0, 0, partWidth, ROW-1)); + r.appendChild(makeRect((delta < 0 ? 'negative' : 'positive'), + 0, 0, partWidth, ROW-1)); } } @@ -522,9 +522,9 @@ function stackViewer(stacks, nodes) { seen.add(place.Stack); const stack = stacks.Stacks[place.Stack]; if (stack.Value < 0) { - neg += -stack.Value; + neg += -stack.Value; } else { - pos += stack.Value; + pos += stack.Value; } } return [pos, neg]; From fa3061bff0bcf0d611f07dbdba73665bd2bbac97 Mon Sep 17 00:00:00 2001 From: Sanjay Ghemawat Date: Wed, 25 Sep 2024 15:39:30 -0700 Subject: [PATCH 14/27] Display hovered box details immediately. (#897) Display the details (name + samples) of the currently hovered box above the flame graph. If no box is currently being hovered over, we display the total samples in the flame graph. Co-authored-by: Alexey Alexandrov --- internal/driver/html/stacks.css | 22 ++++++++++++++++++---- internal/driver/html/stacks.html | 5 ++++- internal/driver/html/stacks.js | 32 +++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/internal/driver/html/stacks.css b/internal/driver/html/stacks.css index 34c54ebb..1df4f719 100644 --- a/internal/driver/html/stacks.css +++ b/internal/driver/html/stacks.css @@ -14,12 +14,26 @@ body { width: 100%; position: relative; /* Allows absolute positioning of child boxes */ } -/* Shows details of frame that is under the mouse */ +/* Holder for current frame details. */ #current-details { - position: absolute; - top: 5px; - right: 5px; + position: relative; + background: #eee; /* Light grey gives better contrast with boxes */ font-size: 12pt; + padding: 0 4px; + width: 100%; +} +/* Shows details of frame that is under the mouse */ +#current-details-left { + float: left; + max-width: 60%; + white-space: nowrap; + overflow-x: hidden; +} +#current-details-right { + float: right; + max-width: 40%; + white-space: nowrap; + overflow-x: hidden; } /* Background of a single flame-graph frame */ .boxbg { diff --git a/internal/driver/html/stacks.html b/internal/driver/html/stacks.html index c2f8cf26..a4e4077e 100644 --- a/internal/driver/html/stacks.html +++ b/internal/driver/html/stacks.html @@ -8,9 +8,12 @@ {{template "header" .}} +
+
+
 
+
-