]> Cypherpunks repositories - gostls13.git/commitdiff
cmd: update github.com/google/pprof dependencies
authorEmmanuel T Odeke <emmanuel@orijtech.com>
Sun, 10 Nov 2024 20:31:16 +0000 (12:31 -0800)
committerEmmanuel Odeke <emmanuel@orijtech.com>
Tue, 12 Nov 2024 01:08:33 +0000 (01:08 +0000)
Spun out of CL 626397, this change vendors in the latest
github.com/google/pprof and that also required updating
golang.org/x/sys to v0.27.

Change-Id: I72ee514494a9e7c36a8943d78f15bdd0445c5cd5
Reviewed-on: https://go-review.googlesource.com/c/go/+/626995
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
29 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/vendor/github.com/google/pprof/driver/driver.go
src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go
src/cmd/vendor/github.com/google/pprof/internal/driver/config.go
src/cmd/vendor/github.com/google/pprof/internal/driver/driver.go
src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.css
src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html
src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.css
src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.html
src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.js
src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go
src/cmd/vendor/github.com/google/pprof/internal/driver/stacks.go
src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go
src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go
src/cmd/vendor/github.com/google/pprof/internal/plugin/plugin.go
src/cmd/vendor/github.com/google/pprof/internal/report/report.go
src/cmd/vendor/github.com/google/pprof/internal/report/shortnames.go
src/cmd/vendor/github.com/google/pprof/internal/report/stacks.go
src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go
src/cmd/vendor/github.com/google/pprof/internal/symbolz/symbolz.go
src/cmd/vendor/github.com/google/pprof/profile/encode.go
src/cmd/vendor/github.com/google/pprof/profile/merge.go
src/cmd/vendor/github.com/google/pprof/profile/profile.go
src/cmd/vendor/github.com/ianlancetaylor/demangle/demangle.go
src/cmd/vendor/modules.txt
src/go.mod
src/go.sum
src/vendor/modules.txt

index b15cd62ab18402b095b789cb64ccb0157ce19e50..9686e4a4a871fa73b84206bff403e0e19b91d903 100644 (file)
@@ -3,19 +3,19 @@ module cmd
 go 1.24
 
 require (
-       github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8
+       github.com/google/pprof v0.0.0-20241101162523-b92577c0c142
        golang.org/x/arch v0.11.1-0.20241106162200-f977c2e4e3f4
        golang.org/x/build v0.0.0-20240722200705-b9910f320300
        golang.org/x/mod v0.20.0
        golang.org/x/sync v0.8.0
-       golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443
+       golang.org/x/sys v0.27.0
        golang.org/x/telemetry v0.0.0-20240828202201-a797f331ea97
        golang.org/x/term v0.22.1-0.20240716160707-d4346f0be292
        golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c
 )
 
 require (
-       github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 // indirect
+       github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd // indirect
        golang.org/x/text v0.19.0 // indirect
        rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect
 )
index 55e0d64eea7f1240920c4afbd2c52339280d53cc..5f704d5a14c8c1b2bb166f2bba71713beb3e9bf9 100644 (file)
@@ -1,9 +1,9 @@
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8 h1:ssNFCCVmib/GQSzx3uCWyfMgOamLGWuGqlMS77Y1m3Y=
-github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
-github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE=
-github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
+github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 h1:sAGdeJj0bnMgUNVeUpp6AYlVdCt3/GdI3pGRqsNSQLs=
+github.com/google/pprof v0.0.0-20241101162523-b92577c0c142/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd h1:EVX1s+XNss9jkRW9K6XGJn2jL2lB1h5H804oKPsxOec=
+github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
 github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/arch v0.11.1-0.20241106162200-f977c2e4e3f4 h1:B9d6SEXeIaY1QC4c7Gsf88ratIIcxShKAlz60Urrqzw=
@@ -14,8 +14,8 @@ golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
 golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443 h1:FPr/RpnBOqfdMWoEYvMQp58uqHLtb3rUd6mnuuowZEE=
-golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
+golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/telemetry v0.0.0-20240828202201-a797f331ea97 h1:5xPN7d0u5VdgF2gFFXUDaeD3NP1pPgFMHocnCQGAh5M=
 golang.org/x/telemetry v0.0.0-20240828202201-a797f331ea97/go.mod h1:m7R/r+o5h7UvF2JD9n2iLSGY4v8v+zNSyTJ6xynLrqs=
 golang.org/x/term v0.22.1-0.20240716160707-d4346f0be292 h1:BOrQi08eIX3cDgGcMgFONf27MxXigcYa9x+iW5JuCXw=
index 6cbf66939deb57a828400a7d5d71974db72f453c..989aac32ffef4cf8ec46fe44083cdbc1b6c4bac9 100644 (file)
@@ -186,10 +186,11 @@ type ObjFile interface {
 
 // A Frame describes a single line in a source file.
 type Frame struct {
-       Func   string // name of function
-       File   string // source file name
-       Line   int    // line in file
-       Column int    // column in file
+       Func      string // name of function
+       File      string // source file name
+       Line      int    // line in file
+       Column    int    // column in file
+       StartLine int    // start line of function (if available)
 }
 
 // A Sym describes a single symbol in an object file.
index 3049545b6b78fd41a4303dbf3ce9c4304925a426..2f5d97e89a669c061ac306cb570def1cfc73f977 100644 (file)
@@ -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,68 @@ 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 <file_name>:<line_number>:<column_number>.
-       // 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"`
+                       StartLine     int    `json:"StartLine"`
+               } `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, StartLine: s.StartLine})
+       }
+       return stack, nil
 }
 
 // addrInfo returns the stack frame information for a specific program
@@ -170,18 +177,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()
 }
index f7d227416e027ec027a909d6aaa40524a8e8c26a..090230e2a787752d96902b560f40084be3d875e4 100644 (file)
@@ -68,7 +68,7 @@ func defaultConfig() config {
                Trim:         true,
                DivideBy:     1.0,
                Sort:         "flat",
-               Granularity:  "functions",
+               Granularity:  "", // Default depends on the display format
        }
 }
 
index 18941926c5d3517a85bbf238aaec206cb615e26f..99e6ba458a23c6dc79ba6bf4607ffc0778bd2717 100644 (file)
@@ -254,6 +254,8 @@ func aggregate(prof *profile.Profile, cfg config) error {
        var function, filename, linenumber, address bool
        inlines := !cfg.NoInlines
        switch cfg.Granularity {
+       case "":
+               function = true // Default granularity is "functions"
        case "addresses":
                if inlines {
                        return nil
index 14f836ff10f1d9de661c830d97e831ed80fd1f2f..0a897ce291692799cede5c5662f27cdd2d8d2fcc 100644 (file)
@@ -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;
index e946e6b882e394afa3fff2917aa6afe512da43b1..5405a0be955421ea82654fee7971f5d2e61265d8 100644 (file)
       {{range .Legend}}<div>{{.}}</div>{{end}}
     </div>
   </div>
+
+  {{if .DocURL}}
+     <div class="menu-item">
+       <div class="help menu-name"><a title="Profile documentation" href="{{.DocURL}}" target="_blank">Help&nbsp;⤇</a></div>
+     </div>
+  {{end}}
 </div>
 
 <div id="dialog-overlay"></div>
index 34c54ebb492ac0410080890d4cf3bc570e540a47..1df4f719a5f876a46a6118cda1e537f3bb094a83 100644 (file)
@@ -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 {
index c2f8cf26b1e0c0072ff87255ee17ddba3123b462..a4e4077ed8ce147457b016c9410c0d6a66563f54 100644 (file)
@@ -8,9 +8,12 @@
 </head>
 <body>
   {{template "header" .}}
+  <div id="current-details">
+    <div id="current-details-left"></div>
+    <div id="current-details-right"> </div>
+  </div>
   <div id="stack-holder">
     <div id="stack-chart"></div>
-    <div id="current-details"></div>
   </div>
   <div id="action-menu" class="submenu">
     <span id="action-title"></span>
index ced7151e8edd19b76c4c82cbb393c3a14e27ddf9..484c2d75908a4d1b47f7acc3859175075b1af5a0 100644 (file)
@@ -21,6 +21,7 @@ function stackViewer(stacks, nodes) {
   let actionMenuOn = false; // Is action menu visible?
   let actionTarget = null;  // Box on which action menu is operating.
   let diff = false;         // Are we displaying a diff?
+  let shown = 0;            // How many profile values are being displayed?
 
   for (const stack of stacks.Stacks) {
     if (stack.Value < 0) {
@@ -39,7 +40,8 @@ function stackViewer(stacks, nodes) {
   const search = find('search');
   const actions = find('action-menu');
   const actionTitle = find('action-title');
-  const detailBox = find('current-details');
+  const leftDetailBox = find('current-details-left');
+  const rightDetailBox = find('current-details-right');
 
   window.addEventListener('resize', render);
   window.addEventListener('popstate', render);
@@ -69,6 +71,7 @@ function stackViewer(stacks, nodes) {
     }});
 
   render();
+  clearDetails();
 
   // Helper functions follow:
 
@@ -176,17 +179,27 @@ function stackViewer(stacks, nodes) {
     if (actionMenuOn) return;
     const src = stacks.Sources[box.src];
     div.title = details(box) + ' â”‚ ' + src.FullName + (src.Inlined ? "\n(inlined)" : "");
-    detailBox.innerText = summary(box.sumpos, box.sumneg);
+    leftDetailBox.innerText = src.FullName + (src.Inlined ? " (inlined)" : "");
+    let timing = summary(box.sumpos, box.sumneg);
+    if (box.self != 0) {
+      timing = "self " + unitText(box.self) + " â”‚ " + timing;
+    }
+    rightDetailBox.innerText = timing;
     // Highlight all boxes that have the same source as box.
     toggleClass(box.src, 'hilite2', true);
   }
 
   function handleLeave(box) {
     if (actionMenuOn) return;
-    detailBox.innerText = '';
+    clearDetails();
     toggleClass(box.src, 'hilite2', false);
   }
 
+  function clearDetails() {
+    leftDetailBox.innerText = '';
+    rightDetailBox.innerText = percentText(shown);
+  }
+
   // Return list of sources that match the regexp given by the 'p' URL parameter.
   function urlPivots() {
     const pivots = [];
@@ -231,10 +244,14 @@ function stackViewer(stacks, nodes) {
     const x = PADDING;
     const y = 0;
 
+    // Show summary for pivots if we are actually pivoting.
+    const showPivotSummary = !(pivots.length == 1 && pivots[0] == 0);
+
+    shown = pos + neg;
     displayList.length = 0;
     renderStacks(0, xscale, x, y, places, +1);  // Callees
     renderStacks(0, xscale, x, y-ROW, places, -1);  // Callers (ROW left for separator)
-    display(xscale, pos, neg, displayList);
+    display(xscale, pos, neg, displayList, showPivotSummary);
   }
 
   // renderStacks creates boxes with top-left at x,y with children drawn as
@@ -262,22 +279,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 +314,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 +371,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);
@@ -372,7 +389,7 @@ function stackViewer(stacks, nodes) {
     return groups;
   }
 
-  function display(xscale, posTotal, negTotal, list) {
+  function display(xscale, posTotal, negTotal, list, showPivotSummary) {
     // Sort boxes so that text selection follows a predictable order.
     list.sort(function(a, b) {
       if (a.y != b.y) return a.y - b.y;
@@ -381,14 +398,15 @@ function stackViewer(stacks, nodes) {
 
     // Adjust Y coordinates so that zero is at top.
     let adjust = (list.length > 0) ? list[0].y : 0;
-    adjust -= ROW + 2*PADDING;  // Room for details
 
     const divs = [];
     for (const box of list) {
       box.y -= adjust;
       divs.push(drawBox(xscale, box));
     }
-    divs.push(drawSep(-adjust, posTotal, negTotal));
+    if (showPivotSummary) {
+      divs.push(drawSep(-adjust, posTotal, negTotal));
+    }
 
     const h = (list.length > 0 ?  list[list.length-1].y : 0) + 4*ROW;
     chart.style.height = h+'px';
@@ -423,8 +441,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 +540,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];
index e6e865f385d7083503f012879e9dc4c4689bf202..f9b197bc39863c93d2ed645dc6351eb5524f9ac3 100644 (file)
@@ -206,6 +206,9 @@ func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
                        // Add quotes for empty values.
                        v = `""`
                }
+               if n == "granularity" && v == "" {
+                       v = "(default)"
+               }
                if comment != "" {
                        comment = commentStart + " " + comment
                }
index a7936107d1366501586d7663899e4e4d1d3a6b73..9d3324e3e08432164b4c26cc63e3be29a4835ab1 100644 (file)
@@ -28,7 +28,9 @@ func (ui *webInterface) stackView(w http.ResponseWriter, req *http.Request) {
        rpt, errList := ui.makeReport(w, req, []string{"svg"}, func(cfg *config) {
                cfg.CallTree = true
                cfg.Trim = false
-               cfg.Granularity = "filefunctions"
+               if cfg.Granularity == "" {
+                       cfg.Granularity = "filefunctions"
+               }
        })
        if rpt == nil {
                return // error already reported
index 2a2d7fb1d23eab03e9b3061ce8e1db6e1fc3273f..dd628f7c2dd7c32e4261a919d221bf47124fb47a 100644 (file)
@@ -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)
 }
index 10436a2256168c62df787db25c25f581e630a9a5..3f5b09b5e707f86fb180b83e0994ff68e09816d2 100644 (file)
@@ -181,7 +181,7 @@ func kernelBase(loadSegment *elf.ProgHeader, stextOffset *uint64, start, limit,
                // stextOffset=0xffffffff80200198
                return start - *stextOffset, true
        }
-       if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
+       if start >= 0x8000000000000000 && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
                // Some kernels look like:
                //       VADDR=0xffffffff80200000
                // stextOffset=0xffffffff80200198
index f2ef9871858bffb4c686c8369a4483c23a3b7e7f..2692c722763f392efde05767359aa27f0a0c54d6 100644 (file)
@@ -159,10 +159,11 @@ type ObjFile interface {
 
 // A Frame describes a location in a single line in a source file.
 type Frame struct {
-       Func   string // name of function
-       File   string // source file name
-       Line   int    // line in file
-       Column int    // column in line (if available)
+       Func          string // name of function
+       File          string // source file name
+       Line          int    // line in file
+       Column        int    // column in line (if available)
+       StartLine     int    // start line of function (if available)
 }
 
 // A Sym describes a single symbol in an object file.
index e21ce859d429381ffeb11a60ebb16cd1f04eca79..8e73f179ecd09b619a509a5f11aee38c37eda508 100644 (file)
@@ -19,6 +19,7 @@ package report
 import (
        "fmt"
        "io"
+       "net/url"
        "path/filepath"
        "regexp"
        "sort"
@@ -1168,8 +1169,11 @@ 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)"
+               const layout = "2006-01-02 15:04:05 MST"
                label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout))
        }
        if prof.DurationNanos != 0 {
@@ -1331,6 +1335,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
index 3d9f3f48ea17f3e00539cfced3faaaf4281ad9a3..4663573a43de7cf25cede2993f7fcb47e32fa3cd 100644 (file)
 package report
 
 import (
+       "path/filepath"
        "regexp"
 
        "github.com/google/pprof/internal/graph"
 )
 
-var sepRE = regexp.MustCompile(`::|\.`)
+var (
+       sepRE     = regexp.MustCompile(`::|\.`)
+       fileSepRE = regexp.MustCompile(`/`)
+)
+
+// fileNameSuffixes returns a non-empty sequence of shortened file names
+// (in decreasing preference) that can be used to represent name.
+func fileNameSuffixes(name string) []string {
+       if name == "" {
+               // Avoid returning "." when symbol info is missing
+               return []string{""}
+       }
+       return allSuffixes(filepath.ToSlash(filepath.Clean(name)), fileSepRE)
+}
 
 // shortNameList returns a non-empty sequence of shortened names
 // (in decreasing preference) that can be used to represent name.
 func shortNameList(name string) []string {
        name = graph.ShortenFunctionName(name)
-       seps := sepRE.FindAllStringIndex(name, -1)
+       return allSuffixes(name, sepRE)
+}
+
+// allSuffixes returns a list of suffixes (in order of decreasing length)
+// found by splitting at re.
+func allSuffixes(name string, re *regexp.Regexp) []string {
+       seps := re.FindAllStringIndex(name, -1)
        result := make([]string, 0, len(seps)+1)
        result = append(result, name)
        for _, sep := range seps {
index c6b07b86decd9459d03e7fd098f192d68407f238..dbf20bebe988468f6f75cf4b21edaeae4f3f03a0 100644 (file)
@@ -18,6 +18,7 @@ import (
        "crypto/sha256"
        "encoding/binary"
        "fmt"
+       "path/filepath"
 
        "github.com/google/pprof/internal/measurement"
        "github.com/google/pprof/profile"
@@ -99,41 +100,57 @@ func (rpt *Report) Stacks() StackSet {
        }
        s.makeInitialStacks(rpt)
        s.fillPlaces()
-       s.assignColors()
        return *s
 }
 
 func (s *StackSet) makeInitialStacks(rpt *Report) {
        type key struct {
-               line    profile.Line
-               inlined bool
+               funcName string
+               fileName string
+               line     int64
+               column   int64
+               inlined  bool
        }
        srcs := map[key]int{} // Sources identified so far.
        seenFunctions := map[string]bool{}
        unknownIndex := 1
+
        getSrc := func(line profile.Line, inlined bool) int {
-               k := key{line, inlined}
+               fn := line.Function
+               if fn == nil {
+                       fn = &profile.Function{Name: fmt.Sprintf("?%d?", unknownIndex)}
+                       unknownIndex++
+               }
+
+               k := key{fn.Name, fn.Filename, line.Line, line.Column, inlined}
                if i, ok := srcs[k]; ok {
                        return i
                }
-               x := StackSource{Places: []StackSlot{}} // Ensure Places is non-nil
-               if fn := line.Function; fn != nil {
-                       x.FullName = fn.Name
-                       x.FileName = fn.Filename
-                       if !seenFunctions[fn.Name] {
-                               x.UniqueName = fn.Name
-                               seenFunctions[fn.Name] = true
-                       } else {
-                               // Assign a different name so pivoting picks this function.
-                               x.UniqueName = fmt.Sprint(fn.Name, "#", fn.ID)
-                       }
-               } else {
-                       x.FullName = fmt.Sprintf("?%d?", unknownIndex)
+
+               fileName := trimPath(fn.Filename, rpt.options.TrimPath, rpt.options.SourcePath)
+               x := StackSource{
+                       FileName: fileName,
+                       Inlined:  inlined,
+                       Places:   []StackSlot{}, // Ensure Places is non-nil
+               }
+               if fn.Name != "" {
+                       x.FullName = addLineInfo(fn.Name, line)
+                       x.Display = shortNameList(x.FullName)
+                       x.Color = pickColor(packageName(fn.Name))
+               } else { // Use file name, e.g., for file granularity display.
+                       x.FullName = addLineInfo(fileName, line)
+                       x.Display = fileNameSuffixes(x.FullName)
+                       x.Color = pickColor(filepath.Dir(fileName))
+               }
+
+               if !seenFunctions[x.FullName] {
                        x.UniqueName = x.FullName
-                       unknownIndex++
+                       seenFunctions[x.FullName] = true
+               } else {
+                       // Assign a different name so pivoting picks this function.
+                       x.UniqueName = fmt.Sprint(x.FullName, "#", fn.ID)
                }
-               x.Inlined = inlined
-               x.Display = shortNameList(x.FullName)
+
                s.Sources = append(s.Sources, x)
                srcs[k] = len(s.Sources) - 1
                return len(s.Sources) - 1
@@ -179,18 +196,25 @@ func (s *StackSet) fillPlaces() {
        }
 }
 
-func (s *StackSet) assignColors() {
-       // Assign different color indices to different packages.
+// pickColor picks a color for key.
+func pickColor(key string) int {
        const numColors = 1048576
-       for i, src := range s.Sources {
-               pkg := packageName(src.FullName)
-               h := sha256.Sum256([]byte(pkg))
-               index := binary.LittleEndian.Uint32(h[:])
-               s.Sources[i].Color = int(index % numColors)
-       }
+       h := sha256.Sum256([]byte(key))
+       index := binary.LittleEndian.Uint32(h[:])
+       return 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)
 }
+
+func addLineInfo(str string, line profile.Line) string {
+       if line.Column != 0 {
+               return fmt.Sprint(str, ":", line.Line, ":", line.Column)
+       }
+       if line.Line != 0 {
+               return fmt.Sprint(str, ":", line.Line)
+       }
+       return str
+}
index 70b4047269789d75063da214fa38a0194d1931e4..0d451364619381929278d56960fc95d368c45538 100644 (file)
@@ -228,6 +228,7 @@ func symbolizeOneMapping(m *profile.Mapping, locs []*profile.Location, obj plugi
                                Name:       frame.Func,
                                SystemName: frame.Func,
                                Filename:   frame.File,
+                               StartLine:  int64(frame.StartLine),
                        })
                        l.Line[i] = profile.Line{
                                Function: f,
index 7be304866fdc57535c4b4d3a53ee33d4e2574675..97d29e45d113b989fc4fcfa0e069085ac38156d3 100644 (file)
@@ -37,19 +37,15 @@ var (
 // Symbolize symbolizes profile p by parsing data returned by a symbolz
 // handler. syms receives the symbolz query (hex addresses separated by '+')
 // and returns the symbolz output in a string. If force is false, it will only
-// symbolize locations from mappings not already marked as HasFunctions. Never
-// attempts symbolization of addresses from unsymbolizable system
-// mappings as those may look negative - e.g. "[vsyscall]".
+// symbolize locations from mappings not already marked as HasFunctions. Does
+// not skip unsymbolizable files since the symbolz handler can be flexible
+// enough to handle some of those cases such as JIT locations in //anon.
 func Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error {
        for _, m := range p.Mapping {
                if !force && m.HasFunctions {
                        // Only check for HasFunctions as symbolz only populates function names.
                        continue
                }
-               // Skip well-known system mappings.
-               if m.Unsymbolizable() {
-                       continue
-               }
                mappingSources := sources[m.File]
                if m.BuildID != "" {
                        mappingSources = append(mappingSources, sources[m.BuildID]...)
@@ -93,7 +89,7 @@ func symbolz(source string) string {
                if strings.Contains(url.Path, "/debug/pprof/") || hasGperftoolsSuffix(url.Path) {
                        url.Path = path.Clean(url.Path + "/../symbol")
                } else {
-                       url.Path = "/symbolz"
+                       url.Path = path.Clean(url.Path + "/../symbolz")
                }
                url.RawQuery = ""
                return url.String()
index 860bb304c349adc32a474a3ee3fb3da568676b27..8ce9d3cf3b7ee3e86769a954438c55ec426851fe 100644 (file)
@@ -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
 }
index eee0132e74064a091bb1fbc8ed5c137446d566bf..ba4d746407c13375c8e43307eec3f552101d25cb 100644 (file)
@@ -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
index 5551eb0bfa469448ea348adf1760844bc1330656..f47a243903e4059116e17668a61bd151a1f7fdbb 100644 (file)
@@ -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))
        }
@@ -844,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"
index 4ca57e62a4d740a6034ef3c735514137d3c24dec..dc238e07734d270df907a7c775ae1a87d862f949 100644 (file)
@@ -906,6 +906,9 @@ func (st *state) unqualifiedName(module AST) (r AST, isCast bool) {
        if len(st.str) > 0 && st.str[0] == 'F' {
                st.advance(1)
                friend = true
+               if len(st.str) < 1 {
+                       st.fail("expected unqualified name")
+               }
        }
 
        var a AST
@@ -3149,6 +3152,7 @@ func (st *state) closureTypeName() AST {
 // templateParamDecl parses:
 //
 //     <template-param-decl> ::= Ty                          # type parameter
+//                           ::= Tk <concept name> [<template-args>] # constrained type parameter
 //                           ::= Tn <type>                   # non-type parameter
 //                           ::= Tt <template-param-decl>* E # template parameter
 //                           ::= Tp <template-param-decl>    # parameter pack
@@ -3178,6 +3182,13 @@ func (st *state) templateParamDecl() (AST, AST) {
                }
                return tp, name
        case 'k':
+               // We don't track enclosing template parameter levels.
+               // Don't try to demangle template parameter substitutions
+               // in constraints.
+               hold := st.parsingConstraint
+               st.parsingConstraint = true
+               defer func() { st.parsingConstraint = hold }()
+
                st.advance(2)
                constraint, _ := st.name()
                name := mk("$T", &st.typeTemplateParamCount)
index 019b7229d9016db4b5887de6e4b40f06c127774c..a645e6421ee90a61a197b47acf9cab93e58a2353 100644 (file)
@@ -1,5 +1,5 @@
-# github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8
-## explicit; go 1.19
+# github.com/google/pprof v0.0.0-20241101162523-b92577c0c142
+## explicit; go 1.22
 github.com/google/pprof/driver
 github.com/google/pprof/internal/binutils
 github.com/google/pprof/internal/driver
@@ -13,7 +13,7 @@ github.com/google/pprof/internal/symbolz
 github.com/google/pprof/internal/transport
 github.com/google/pprof/profile
 github.com/google/pprof/third_party/svgpan
-# github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465
+# github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd
 ## explicit; go 1.13
 github.com/ianlancetaylor/demangle
 # golang.org/x/arch v0.11.1-0.20241106162200-f977c2e4e3f4
@@ -43,7 +43,7 @@ golang.org/x/mod/zip
 ## explicit; go 1.18
 golang.org/x/sync/errgroup
 golang.org/x/sync/semaphore
-# golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443
+# golang.org/x/sys v0.27.0
 ## explicit; go 1.18
 golang.org/x/sys/plan9
 golang.org/x/sys/unix
index d010f4fe50e8c02cff6577d37b7a169485a2e46e..7a7b99150fc297e8444cc7c0daf1c01956291c40 100644 (file)
@@ -8,6 +8,6 @@ require (
 )
 
 require (
-       golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443 // indirect
+       golang.org/x/sys v0.27.0 // indirect
        golang.org/x/text v0.19.0 // indirect
 )
index ca48b2bfc58d32ae491edddf25cab2e95ddb6956..26a27ac8099c789050ecc4b522ca3d5527c384ee 100644 (file)
@@ -2,7 +2,7 @@ golang.org/x/crypto v0.25.1-0.20240722173533-bb80217080b0 h1:wxHbFWyu21uEPJJnYaS
 golang.org/x/crypto v0.25.1-0.20240722173533-bb80217080b0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 golang.org/x/net v0.27.1-0.20240722181819-765c7e89b3bd h1:pHzwejE8Zkb94bG4nA+fUeskKPFp1HPldrhv62dabro=
 golang.org/x/net v0.27.1-0.20240722181819-765c7e89b3bd/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
-golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443 h1:FPr/RpnBOqfdMWoEYvMQp58uqHLtb3rUd6mnuuowZEE=
-golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
+golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
 golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
index 58940688fbd34ebe2c9464475096d2371b629761..2e10edb2b06ccf188ed64e5de1114104769dfa8d 100644 (file)
@@ -18,7 +18,7 @@ golang.org/x/net/idna
 golang.org/x/net/lif
 golang.org/x/net/nettest
 golang.org/x/net/route
-# golang.org/x/sys v0.26.1-0.20241105152852-e0753d469443
+# golang.org/x/sys v0.27.0
 ## explicit; go 1.18
 golang.org/x/sys/cpu
 # golang.org/x/text v0.19.0