]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/pprof: update vendored github.com/google/pprof
authorDmitri Shuralyov <dmitshur@golang.org>
Mon, 16 May 2022 22:44:48 +0000 (18:44 -0400)
committerGopher Robot <gobot@golang.org>
Tue, 17 May 2022 14:39:18 +0000 (14:39 +0000)
Pull in the latest published version of github.com/google/pprof
as part of go.dev/issue/36905.

Done with:

go get github.com/google/pprof@upgrade
go mod tidy
go mod vendor

For #36905.

Change-Id: I3c8279fce2f20cb940a4e46b2b850703e1fc7964
Reviewed-on: https://go-review.googlesource.com/c/go/+/406359
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>

19 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.css [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.js [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/html/flamegraph.html [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/html/plaintext.html [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/html/top.html [new file with mode: 0644]
src/cmd/vendor/github.com/google/pprof/internal/driver/tagroot.go
src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go
src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go
src/cmd/vendor/github.com/google/pprof/profile/legacy_profile.go
src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/README.md
src/cmd/vendor/github.com/ianlancetaylor/demangle/ast.go
src/cmd/vendor/github.com/ianlancetaylor/demangle/demangle.go
src/cmd/vendor/github.com/ianlancetaylor/demangle/rust.go
src/cmd/vendor/modules.txt

index c720cde802ea60c6b2d8195eb5559f1303f54265..04c2523a094e2af8603f8b6e5cbf7525be3f3a74 100644 (file)
@@ -3,7 +3,7 @@ module cmd
 go 1.19
 
 require (
-       github.com/google/pprof v0.0.0-20220314021825-5bba342933ea
+       github.com/google/pprof v0.0.0-20220517023622-154dc81eb7b0
        golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15
        golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
        golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
@@ -13,6 +13,6 @@ require (
 )
 
 require (
-       github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d // indirect
+       github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 // indirect
        golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 // indirect
 )
index e768e1ee6c833049feab97caaeb0a631161ecde8..fc81e159e51de37364c75a2041ba46782beee420 100644 (file)
@@ -1,10 +1,7 @@
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/google/pprof v0.0.0-20220314021825-5bba342933ea h1:yVDp4C9ff/qoEAhHNf5cqk/YTxTGECSzGYzahdhEKBI=
-github.com/google/pprof v0.0.0-20220314021825-5bba342933ea/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
-github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d h1:uGg2frlt3IcT7kbV6LEp5ONv4vmoO2FW4qSO+my/aoM=
-github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/google/pprof v0.0.0-20220517023622-154dc81eb7b0 h1:XgEFTOJTsN3Li0Txfhn2UzsysGJfXIDe7wE07uY7ZfI=
+github.com/google/pprof v0.0.0-20220517023622-154dc81eb7b0/go.mod h1:gSuNB+gJaOiQKLEZ+q+PK9Mq3SOzhRcw2GsGS/FhYDk=
+github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 h1:rcanfLhLDA8nozr/K289V1zcntHr3V+SHlXwzz1ZI2g=
+github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU=
 golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 h1:y+mHpWoQJNAHt26Nhh6JP7hvM71IRZureyvZhoVALIs=
@@ -13,7 +10,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVD
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
 golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
 golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.css b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.css
new file mode 100644 (file)
index 0000000..03755ab
--- /dev/null
@@ -0,0 +1,272 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+html, body {
+  height: 100%;
+}
+body {
+  font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+  font-size: 13px;
+  line-height: 1.4;
+  display: flex;
+  flex-direction: column;
+}
+a {
+  color: #2a66d9;
+}
+.header {
+  display: flex;
+  align-items: center;
+  height: 44px;
+  min-height: 44px;
+  background-color: #eee;
+  color: #212121;
+  padding: 0 1rem;
+}
+.header > div {
+  margin: 0 0.125em;
+}
+.header .title h1 {
+  font-size: 1.75em;
+  margin-right: 1rem;
+  margin-bottom: 4px;
+}
+.header .title a {
+  color: #212121;
+  text-decoration: none;
+}
+.header .title a:hover {
+  text-decoration: underline;
+}
+.header .description {
+  width: 100%;
+  text-align: right;
+  white-space: nowrap;
+}
+@media screen and (max-width: 799px) {
+  .header input {
+    display: none;
+  }
+}
+#detailsbox {
+  display: none;
+  z-index: 1;
+  position: fixed;
+  top: 40px;
+  right: 20px;
+  background-color: #ffffff;
+  box-shadow: 0 1px 5px rgba(0,0,0,.3);
+  line-height: 24px;
+  padding: 1em;
+  text-align: left;
+}
+.header input {
+  background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' style='pointer-events:none;display:block;width:100%25;height:100%25;fill:%23757575'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61.0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat 4px center/20px 20px;
+  border: 1px solid #d1d2d3;
+  border-radius: 2px 0 0 2px;
+  padding: 0.25em;
+  padding-left: 28px;
+  margin-left: 1em;
+  font-family: 'Roboto', 'Noto', sans-serif;
+  font-size: 1em;
+  line-height: 24px;
+  color: #212121;
+}
+.downArrow {
+  border-top: .36em solid #ccc;
+  border-left: .36em solid transparent;
+  border-right: .36em solid transparent;
+  margin-bottom: .05em;
+  margin-left: .5em;
+  transition: border-top-color 200ms;
+}
+.menu-item {
+  height: 100%;
+  text-transform: uppercase;
+  font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+  position: relative;
+}
+.menu-item .menu-name:hover {
+  opacity: 0.75;
+}
+.menu-item .menu-name:hover .downArrow {
+  border-top-color: #666;
+}
+.menu-name {
+  height: 100%;
+  padding: 0 0.5em;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.menu-name a {
+  text-decoration: none;
+  color: #212121;
+}
+.submenu {
+  display: none;
+  z-index: 1;
+  margin-top: -4px;
+  min-width: 10em;
+  position: absolute;
+  left: 0px;
+  background-color: white;
+  box-shadow: 0 1px 5px rgba(0,0,0,.3);
+  font-size: 100%;
+  text-transform: none;
+}
+.menu-item, .submenu {
+  user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -webkit-user-select: none;
+}
+.submenu hr {
+  border: 0;
+  border-top: 2px solid #eee;
+}
+.submenu a {
+  display: block;
+  padding: .5em 1em;
+  text-decoration: none;
+}
+.submenu a:hover, .submenu a.active {
+  color: white;
+  background-color: #6b82d6;
+}
+.submenu a.disabled {
+  color: gray;
+  pointer-events: none;
+}
+.menu-check-mark {
+  position: absolute;
+  left: 2px;
+}
+.menu-delete-btn {
+  position: absolute;
+  right: 2px;
+}
+
+{{/* Used to disable events when a modal dialog is displayed */}}
+#dialog-overlay {
+  display: none;
+  position: fixed;
+  left: 0px;
+  top: 0px;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(1,1,1,0.1);
+}
+
+.dialog {
+  {{/* Displayed centered horizontally near the top */}}
+  display: none;
+  position: fixed;
+  margin: 0px;
+  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);
+}
+.dialog-header {
+  font-size: 120%;
+  border-bottom: 1px solid #CCCCCC;
+  width: 100%;
+  text-align: center;
+  background: #EEEEEE;
+  user-select: none;
+}
+.dialog-footer {
+  border-top: 1px solid #CCCCCC;
+  width: 100%;
+  text-align: right;
+  padding: 10px;
+}
+.dialog-error {
+  margin: 10px;
+  color: red;
+}
+.dialog input {
+  margin: 10px;
+  font-size: inherit;
+}
+.dialog button {
+  margin-left: 10px;
+  font-size: inherit;
+}
+#save-dialog, #delete-dialog {
+  width: 50%;
+  max-width: 20em;
+}
+#delete-prompt {
+  padding: 10px;
+}
+
+#content {
+  overflow-y: scroll;
+  padding: 1em;
+}
+#top {
+  overflow-y: scroll;
+}
+#graph {
+  overflow: hidden;
+}
+#graph svg {
+  width: 100%;
+  height: auto;
+  padding: 10px;
+}
+#content.source .filename {
+  margin-top: 0;
+  margin-bottom: 1em;
+  font-size: 120%;
+}
+#content.source pre {
+  margin-bottom: 3em;
+}
+table {
+  border-spacing: 0px;
+  width: 100%;
+  padding-bottom: 1em;
+  white-space: nowrap;
+}
+table thead {
+  font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+}
+table tr th {
+  position: sticky;
+  top: 0;
+  background-color: #ddd;
+  text-align: right;
+  padding: .3em .5em;
+}
+table tr td {
+  padding: .3em .5em;
+  text-align: right;
+}
+#top table tr th:nth-child(6),
+#top table tr th:nth-child(7),
+#top table tr td:nth-child(6),
+#top table tr td:nth-child(7) {
+  text-align: left;
+}
+#top table tr td:nth-child(6) {
+  width: 100%;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+}
+#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {
+  cursor: ns-resize;
+}
+.hilite {
+  background-color: #ebf5fb;
+  font-weight: bold;
+}
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.js b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.js
new file mode 100644 (file)
index 0000000..4fe3caa
--- /dev/null
@@ -0,0 +1,693 @@
+// Make svg pannable and zoomable.
+// Call clickHandler(t) if a click event is caught by the pan event handlers.
+function initPanAndZoom(svg, clickHandler) {
+  'use strict';
+
+  // Current mouse/touch handling mode
+  const IDLE = 0;
+  const MOUSEPAN = 1;
+  const TOUCHPAN = 2;
+  const TOUCHZOOM = 3;
+  let mode = IDLE;
+
+  // State needed to implement zooming.
+  let currentScale = 1.0;
+  const initWidth = svg.viewBox.baseVal.width;
+  const initHeight = svg.viewBox.baseVal.height;
+
+  // State needed to implement panning.
+  let panLastX = 0;      // Last event X coordinate
+  let panLastY = 0;      // Last event Y coordinate
+  let moved = false;     // Have we seen significant movement
+  let touchid = null;    // Current touch identifier
+
+  // State needed for pinch zooming
+  let touchid2 = null;     // Second id for pinch zooming
+  let initGap = 1.0;       // Starting gap between two touches
+  let initScale = 1.0;     // currentScale when pinch zoom started
+  let centerPoint = null;  // Center point for scaling
+
+  // Convert event coordinates to svg coordinates.
+  function toSvg(x, y) {
+    const p = svg.createSVGPoint();
+    p.x = x;
+    p.y = y;
+    let m = svg.getCTM();
+    if (m == null) m = svg.getScreenCTM(); // Firefox workaround.
+    return p.matrixTransform(m.inverse());
+  }
+
+  // Change the scaling for the svg to s, keeping the point denoted
+  // by u (in svg coordinates]) fixed at the same screen location.
+  function rescale(s, u) {
+    // Limit to a good range.
+    if (s < 0.2) s = 0.2;
+    if (s > 10.0) s = 10.0;
+
+    currentScale = s;
+
+    // svg.viewBox defines the visible portion of the user coordinate
+    // system.  So to magnify by s, divide the visible portion by s,
+    // which will then be stretched to fit the viewport.
+    const vb = svg.viewBox;
+    const w1 = vb.baseVal.width;
+    const w2 = initWidth / s;
+    const h1 = vb.baseVal.height;
+    const h2 = initHeight / s;
+    vb.baseVal.width = w2;
+    vb.baseVal.height = h2;
+
+    // We also want to adjust vb.baseVal.x so that u.x remains at same
+    // screen X coordinate.  In other words, want to change it from x1 to x2
+    // so that:
+    //     (u.x - x1) / w1 = (u.x - x2) / w2
+    // Simplifying that, we get
+    //     (u.x - x1) * (w2 / w1) = u.x - x2
+    //     x2 = u.x - (u.x - x1) * (w2 / w1)
+    vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1);
+    vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1);
+  }
+
+  function handleWheel(e) {
+    if (e.deltaY == 0) return;
+    // Change scale factor by 1.1 or 1/1.1
+    rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),
+            toSvg(e.offsetX, e.offsetY));
+  }
+
+  function setMode(m) {
+    mode = m;
+    touchid = null;
+    touchid2 = null;
+  }
+
+  function panStart(x, y) {
+    moved = false;
+    panLastX = x;
+    panLastY = y;
+  }
+
+  function panMove(x, y) {
+    let dx = x - panLastX;
+    let dy = y - panLastY;
+    if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return; // Ignore tiny moves
+
+    moved = true;
+    panLastX = x;
+    panLastY = y;
+
+    // Firefox workaround: get dimensions from parentNode.
+    const swidth = svg.clientWidth || svg.parentNode.clientWidth;
+    const sheight = svg.clientHeight || svg.parentNode.clientHeight;
+
+    // Convert deltas from screen space to svg space.
+    dx *= (svg.viewBox.baseVal.width / swidth);
+    dy *= (svg.viewBox.baseVal.height / sheight);
+
+    svg.viewBox.baseVal.x -= dx;
+    svg.viewBox.baseVal.y -= dy;
+  }
+
+  function handleScanStart(e) {
+    if (e.button != 0) return; // Do not catch right-clicks etc.
+    setMode(MOUSEPAN);
+    panStart(e.clientX, e.clientY);
+    e.preventDefault();
+    svg.addEventListener('mousemove', handleScanMove);
+  }
+
+  function handleScanMove(e) {
+    if (e.buttons == 0) {
+      // Missed an end event, perhaps because mouse moved outside window.
+      setMode(IDLE);
+      svg.removeEventListener('mousemove', handleScanMove);
+      return;
+    }
+    if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
+  }
+
+  function handleScanEnd(e) {
+    if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
+    setMode(IDLE);
+    svg.removeEventListener('mousemove', handleScanMove);
+    if (!moved) clickHandler(e.target);
+  }
+
+  // Find touch object with specified identifier.
+  function findTouch(tlist, id) {
+    for (const t of tlist) {
+      if (t.identifier == id) return t;
+    }
+    return null;
+  }
+
+  // Return distance between two touch points
+  function touchGap(t1, t2) {
+    const dx = t1.clientX - t2.clientX;
+    const dy = t1.clientY - t2.clientY;
+    return Math.hypot(dx, dy);
+  }
+
+  function handleTouchStart(e) {
+    if (mode == IDLE && e.changedTouches.length == 1) {
+      // Start touch based panning
+      const t = e.changedTouches[0];
+      setMode(TOUCHPAN);
+      touchid = t.identifier;
+      panStart(t.clientX, t.clientY);
+      e.preventDefault();
+    } else if (mode == TOUCHPAN && e.touches.length == 2) {
+      // Start pinch zooming
+      setMode(TOUCHZOOM);
+      const t1 = e.touches[0];
+      const t2 = e.touches[1];
+      touchid = t1.identifier;
+      touchid2 = t2.identifier;
+      initScale = currentScale;
+      initGap = touchGap(t1, t2);
+      centerPoint = toSvg((t1.clientX + t2.clientX) / 2,
+                          (t1.clientY + t2.clientY) / 2);
+      e.preventDefault();
+    }
+  }
+
+  function handleTouchMove(e) {
+    if (mode == TOUCHPAN) {
+      const t = findTouch(e.changedTouches, touchid);
+      if (t == null) return;
+      if (e.touches.length != 1) {
+        setMode(IDLE);
+        return;
+      }
+      panMove(t.clientX, t.clientY);
+      e.preventDefault();
+    } else if (mode == TOUCHZOOM) {
+      // Get two touches; new gap; rescale to ratio.
+      const t1 = findTouch(e.touches, touchid);
+      const t2 = findTouch(e.touches, touchid2);
+      if (t1 == null || t2 == null) return;
+      const gap = touchGap(t1, t2);
+      rescale(initScale * gap / initGap, centerPoint);
+      e.preventDefault();
+    }
+  }
+
+  function handleTouchEnd(e) {
+    if (mode == TOUCHPAN) {
+      const t = findTouch(e.changedTouches, touchid);
+      if (t == null) return;
+      panMove(t.clientX, t.clientY);
+      setMode(IDLE);
+      e.preventDefault();
+      if (!moved) clickHandler(t.target);
+    } else if (mode == TOUCHZOOM) {
+      setMode(IDLE);
+      e.preventDefault();
+    }
+  }
+
+  svg.addEventListener('mousedown', handleScanStart);
+  svg.addEventListener('mouseup', handleScanEnd);
+  svg.addEventListener('touchstart', handleTouchStart);
+  svg.addEventListener('touchmove', handleTouchMove);
+  svg.addEventListener('touchend', handleTouchEnd);
+  svg.addEventListener('wheel', handleWheel, true);
+}
+
+function initMenus() {
+  'use strict';
+
+  let activeMenu = null;
+  let activeMenuHdr = null;
+
+  function cancelActiveMenu() {
+    if (activeMenu == null) return;
+    activeMenu.style.display = 'none';
+    activeMenu = null;
+    activeMenuHdr = null;
+  }
+
+  // Set click handlers on every menu header.
+  for (const menu of document.getElementsByClassName('submenu')) {
+    const hdr = menu.parentElement;
+    if (hdr == null) return;
+    if (hdr.classList.contains('disabled')) return;
+    function showMenu(e) {
+      // menu is a child of hdr, so this event can fire for clicks
+      // inside menu. Ignore such clicks.
+      if (e.target.parentElement != hdr) return;
+      activeMenu = menu;
+      activeMenuHdr = hdr;
+      menu.style.display = 'block';
+    }
+    hdr.addEventListener('mousedown', showMenu);
+    hdr.addEventListener('touchstart', showMenu);
+  }
+
+  // If there is an active menu and a down event outside, retract the menu.
+  for (const t of ['mousedown', 'touchstart']) {
+    document.addEventListener(t, (e) => {
+      // Note: to avoid unnecessary flicker, if the down event is inside
+      // the active menu header, do not retract the menu.
+      if (activeMenuHdr != e.target.closest('.menu-item')) {
+        cancelActiveMenu();
+      }
+    }, { passive: true, capture: true });
+  }
+
+  // If there is an active menu and an up event inside, retract the menu.
+  document.addEventListener('mouseup', (e) => {
+    if (activeMenu == e.target.closest('.submenu')) {
+      cancelActiveMenu();
+    }
+  }, { passive: true, capture: true });
+}
+
+function sendURL(method, url, done) {
+  fetch(url.toString(), {method: method})
+      .then((response) => { done(response.ok); })
+      .catch((error) => { done(false); });
+}
+
+// Initialize handlers for saving/loading configurations.
+function initConfigManager() {
+  'use strict';
+
+  // Initialize various elements.
+  function elem(id) {
+    const result = document.getElementById(id);
+    if (!result) console.warn('element ' + id + ' not found');
+    return result;
+  }
+  const overlay = elem('dialog-overlay');
+  const saveDialog = elem('save-dialog');
+  const saveInput = elem('save-name');
+  const saveError = elem('save-error');
+  const delDialog = elem('delete-dialog');
+  const delPrompt = elem('delete-prompt');
+  const delError = elem('delete-error');
+
+  let currentDialog = null;
+  let currentDeleteTarget = null;
+
+  function showDialog(dialog) {
+    if (currentDialog != null) {
+      overlay.style.display = 'none';
+      currentDialog.style.display = 'none';
+    }
+    currentDialog = dialog;
+    if (dialog != null) {
+      overlay.style.display = 'block';
+      dialog.style.display = 'block';
+    }
+  }
+
+  function cancelDialog(e) {
+    showDialog(null);
+  }
+
+  // Show dialog for saving the current config.
+  function showSaveDialog(e) {
+    saveError.innerText = '';
+    showDialog(saveDialog);
+    saveInput.focus();
+  }
+
+  // Commit save config.
+  function commitSave(e) {
+    const name = saveInput.value;
+    const url = new URL(document.URL);
+    // Set path relative to existing path.
+    url.pathname = new URL('./saveconfig', document.URL).pathname;
+    url.searchParams.set('config', name);
+    saveError.innerText = '';
+    sendURL('POST', url, (ok) => {
+      if (!ok) {
+        saveError.innerText = 'Save failed';
+      } else {
+        showDialog(null);
+        location.reload();  // Reload to show updated config menu
+      }
+    });
+  }
+
+  function handleSaveInputKey(e) {
+    if (e.key === 'Enter') commitSave(e);
+  }
+
+  function deleteConfig(e, elem) {
+    e.preventDefault();
+    const config = elem.dataset.config;
+    delPrompt.innerText = 'Delete ' + config + '?';
+    currentDeleteTarget = elem;
+    showDialog(delDialog);
+  }
+
+  function commitDelete(e, elem) {
+    if (!currentDeleteTarget) return;
+    const config = currentDeleteTarget.dataset.config;
+    const url = new URL('./deleteconfig', document.URL);
+    url.searchParams.set('config', config);
+    delError.innerText = '';
+    sendURL('DELETE', url, (ok) => {
+      if (!ok) {
+        delError.innerText = 'Delete failed';
+        return;
+      }
+      showDialog(null);
+      // Remove menu entry for this config.
+      if (currentDeleteTarget && currentDeleteTarget.parentElement) {
+        currentDeleteTarget.parentElement.remove();
+      }
+    });
+  }
+
+  // Bind event on elem to fn.
+  function bind(event, elem, fn) {
+    if (elem == null) return;
+    elem.addEventListener(event, fn);
+    if (event == 'click') {
+      // Also enable via touch.
+      elem.addEventListener('touchstart', fn);
+    }
+  }
+
+  bind('click', elem('save-config'), showSaveDialog);
+  bind('click', elem('save-cancel'), cancelDialog);
+  bind('click', elem('save-confirm'), commitSave);
+  bind('keydown', saveInput, handleSaveInputKey);
+
+  bind('click', elem('delete-cancel'), cancelDialog);
+  bind('click', elem('delete-confirm'), commitDelete);
+
+  // Activate deletion button for all config entries in menu.
+  for (const del of Array.from(document.getElementsByClassName('menu-delete-btn'))) {
+    bind('click', del, (e) => {
+      deleteConfig(e, del);
+    });
+  }
+}
+
+function viewer(baseUrl, nodes) {
+  'use strict';
+
+  // Elements
+  const search = document.getElementById('search');
+  const graph0 = document.getElementById('graph0');
+  const svg = (graph0 == null ? null : graph0.parentElement);
+  const toptable = document.getElementById('toptable');
+
+  let regexpActive = false;
+  let selected = new Map();
+  let origFill = new Map();
+  let searchAlarm = null;
+  let buttonsEnabled = true;
+
+  function handleDetails(e) {
+    e.preventDefault();
+    const detailsText = document.getElementById('detailsbox');
+    if (detailsText != null) {
+      if (detailsText.style.display === 'block') {
+        detailsText.style.display = 'none';
+      } else {
+        detailsText.style.display = 'block';
+      }
+    }
+  }
+
+  function handleKey(e) {
+    if (e.keyCode != 13) return;
+    setHrefParams(window.location, function (params) {
+      params.set('f', search.value);
+    });
+    e.preventDefault();
+  }
+
+  function handleSearch() {
+    // Delay expensive processing so a flurry of key strokes is handled once.
+    if (searchAlarm != null) {
+      clearTimeout(searchAlarm);
+    }
+    searchAlarm = setTimeout(selectMatching, 300);
+
+    regexpActive = true;
+    updateButtons();
+  }
+
+  function selectMatching() {
+    searchAlarm = null;
+    let re = null;
+    if (search.value != '') {
+      try {
+        re = new RegExp(search.value);
+      } catch (e) {
+        // TODO: Display error state in search box
+        return;
+      }
+    }
+
+    function match(text) {
+      return re != null && re.test(text);
+    }
+
+    // drop currently selected items that do not match re.
+    selected.forEach(function(v, n) {
+      if (!match(nodes[n])) {
+        unselect(n, document.getElementById('node' + n));
+      }
+    })
+
+    // add matching items that are not currently selected.
+    if (nodes) {
+      for (let n = 0; n < nodes.length; n++) {
+        if (!selected.has(n) && match(nodes[n])) {
+          select(n, document.getElementById('node' + n));
+        }
+      }
+    }
+
+    updateButtons();
+  }
+
+  function toggleSvgSelect(elem) {
+    // Walk up to immediate child of graph0
+    while (elem != null && elem.parentElement != graph0) {
+      elem = elem.parentElement;
+    }
+    if (!elem) return;
+
+    // Disable regexp mode.
+    regexpActive = false;
+
+    const n = nodeId(elem);
+    if (n < 0) return;
+    if (selected.has(n)) {
+      unselect(n, elem);
+    } else {
+      select(n, elem);
+    }
+    updateButtons();
+  }
+
+  function unselect(n, elem) {
+    if (elem == null) return;
+    selected.delete(n);
+    setBackground(elem, false);
+  }
+
+  function select(n, elem) {
+    if (elem == null) return;
+    selected.set(n, true);
+    setBackground(elem, true);
+  }
+
+  function nodeId(elem) {
+    const id = elem.id;
+    if (!id) return -1;
+    if (!id.startsWith('node')) return -1;
+    const n = parseInt(id.slice(4), 10);
+    if (isNaN(n)) return -1;
+    if (n < 0 || n >= nodes.length) return -1;
+    return n;
+  }
+
+  function setBackground(elem, set) {
+    // Handle table row highlighting.
+    if (elem.nodeName == 'TR') {
+      elem.classList.toggle('hilite', set);
+      return;
+    }
+
+    // Handle svg element highlighting.
+    const p = findPolygon(elem);
+    if (p != null) {
+      if (set) {
+        origFill.set(p, p.style.fill);
+        p.style.fill = '#ccccff';
+      } else if (origFill.has(p)) {
+        p.style.fill = origFill.get(p);
+      }
+    }
+  }
+
+  function findPolygon(elem) {
+    if (elem.localName == 'polygon') return elem;
+    for (const c of elem.children) {
+      const p = findPolygon(c);
+      if (p != null) return p;
+    }
+    return null;
+  }
+
+  // convert a string to a regexp that matches that string.
+  function quotemeta(str) {
+    return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
+  }
+
+  function setSampleIndexLink(id) {
+    const elem = document.getElementById(id);
+    if (elem != null) {
+      setHrefParams(elem, function (params) {
+        params.set("si", id);
+      });
+    }
+  }
+
+  // Update id's href to reflect current selection whenever it is
+  // liable to be followed.
+  function makeSearchLinkDynamic(id) {
+    const elem = document.getElementById(id);
+    if (elem == null) return;
+
+    // Most links copy current selection into the 'f' parameter,
+    // but Refine menu links are different.
+    let param = 'f';
+    if (id == 'ignore') param = 'i';
+    if (id == 'hide') param = 'h';
+    if (id == 'show') param = 's';
+    if (id == 'show-from') param = 'sf';
+
+    // We update on mouseenter so middle-click/right-click work properly.
+    elem.addEventListener('mouseenter', updater);
+    elem.addEventListener('touchstart', updater);
+
+    function updater() {
+      // The selection can be in one of two modes: regexp-based or
+      // list-based.  Construct regular expression depending on mode.
+      let re = regexpActive
+        ? search.value
+        : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|');
+
+      setHrefParams(elem, function (params) {
+        if (re != '') {
+          // For focus/show/show-from, forget old parameter. For others, add to re.
+          if (param != 'f' && param != 's' && param != 'sf' && params.has(param)) {
+            const old = params.get(param);
+            if (old != '') {
+              re += '|' + old;
+            }
+          }
+          params.set(param, re);
+        } else {
+          params.delete(param);
+        }
+      });
+    }
+  }
+
+  function setHrefParams(elem, paramSetter) {
+    let url = new URL(elem.href);
+    url.hash = '';
+
+    // Copy params from this page's URL.
+    const params = url.searchParams;
+    for (const p of new URLSearchParams(window.location.search)) {
+      params.set(p[0], p[1]);
+    }
+
+    // Give the params to the setter to modify.
+    paramSetter(params);
+
+    elem.href = url.toString();
+  }
+
+  function handleTopClick(e) {
+    // Walk back until we find TR and then get the Name column (index 5)
+    let elem = e.target;
+    while (elem != null && elem.nodeName != 'TR') {
+      elem = elem.parentElement;
+    }
+    if (elem == null || elem.children.length < 6) return;
+
+    e.preventDefault();
+    const tr = elem;
+    const td = elem.children[5];
+    if (td.nodeName != 'TD') return;
+    const name = td.innerText;
+    const index = nodes.indexOf(name);
+    if (index < 0) return;
+
+    // Disable regexp mode.
+    regexpActive = false;
+
+    if (selected.has(index)) {
+      unselect(index, elem);
+    } else {
+      select(index, elem);
+    }
+    updateButtons();
+  }
+
+  function updateButtons() {
+    const enable = (search.value != '' || selected.size != 0);
+    if (buttonsEnabled == enable) return;
+    buttonsEnabled = enable;
+    for (const id of ['focus', 'ignore', 'hide', 'show', 'show-from']) {
+      const link = document.getElementById(id);
+      if (link != null) {
+        link.classList.toggle('disabled', !enable);
+      }
+    }
+  }
+
+  // Initialize button states
+  updateButtons();
+
+  // Setup event handlers
+  initMenus();
+  if (svg != null) {
+    initPanAndZoom(svg, toggleSvgSelect);
+  }
+  if (toptable != null) {
+    toptable.addEventListener('mousedown', handleTopClick);
+    toptable.addEventListener('touchstart', handleTopClick);
+  }
+
+  const ids = ['topbtn', 'graphbtn', 'flamegraph', 'peek', 'list', 'disasm',
+               'focus', 'ignore', 'hide', 'show', 'show-from'];
+  ids.forEach(makeSearchLinkDynamic);
+
+  const sampleIDs = [{{range .SampleTypes}}'{{.}}', {{end}}];
+  sampleIDs.forEach(setSampleIndexLink);
+
+  // Bind action to button with specified id.
+  function addAction(id, action) {
+    const btn = document.getElementById(id);
+    if (btn != null) {
+      btn.addEventListener('click', action);
+      btn.addEventListener('touchstart', action);
+    }
+  }
+
+  addAction('details', handleDetails);
+  initConfigManager();
+
+  search.addEventListener('input', handleSearch);
+  search.addEventListener('keydown', handleKey);
+
+  // Give initial focus to main container so it can be scrolled using keys.
+  const main = document.getElementById('bodycontainer');
+  if (main) {
+    main.focus();
+  }
+}
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/flamegraph.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/flamegraph.html
new file mode 100644 (file)
index 0000000..9866755
--- /dev/null
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>{{.Title}}</title>
+  {{template "css" .}}
+  <style type="text/css">{{template "d3flamegraphcss" .}}</style>
+  <style type="text/css">
+    .flamegraph-content {
+      width: 90%;
+      min-width: 80%;
+      margin-left: 5%;
+    }
+    .flamegraph-details {
+      height: 1.2em;
+      width: 90%;
+      min-width: 90%;
+      margin-left: 5%;
+      padding: 15px 0 35px;
+    }
+  </style>
+</head>
+<body>
+  {{template "header" .}}
+  <div id="bodycontainer">
+    <div id="flamegraphdetails" class="flamegraph-details"></div>
+    <div class="flamegraph-content">
+      <div id="chart"></div>
+    </div>
+  </div>
+  {{template "script" .}}
+  <script>viewer(new URL(window.location.href), {{.Nodes}});</script>
+  <script>{{template "d3flamegraphscript" .}}</script>
+  <script>
+    {{- /* Deserialize as JSON instead of a JS object literal because the browser's
+           JSON parser can handle larger payloads than its JS parser. */ -}}
+    var data = JSON.parse("{{.FlameGraph}}");
+
+    var width = document.getElementById('chart').clientWidth;
+
+    var flameGraph = flamegraph()
+      .width(width)
+      .cellHeight(18)
+      .minFrameSize(1)
+      .transitionDuration(750)
+      .inverted(true)
+      .sort(true)
+      .title('')
+      .tooltip(false)
+      .setDetailsElement(document.getElementById('flamegraphdetails'));
+
+    // <full name> (percentage, value)
+    flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')');
+
+    flameGraph.setColorHue('warm');
+
+    select('#chart')
+      .datum(data)
+      .call(flameGraph);
+
+    function clear() {
+      flameGraph.clear();
+    }
+
+    function resetZoom() {
+      flameGraph.resetZoom();
+    }
+
+    window.addEventListener('resize', function() {
+      var width = document.getElementById('chart').clientWidth;
+      var graphs = document.getElementsByClassName('d3-flame-graph');
+      if (graphs.length > 0) {
+        graphs[0].setAttribute('width', width);
+      }
+      flameGraph.width(width);
+      flameGraph.resetZoom();
+    }, true);
+
+    var search = document.getElementById('search');
+    var searchAlarm = null;
+
+    function selectMatching() {
+      searchAlarm = null;
+
+      if (search.value != '') {
+        flameGraph.search(search.value);
+      } else {
+        flameGraph.clear();
+      }
+    }
+
+    function handleSearch() {
+      // Delay expensive processing so a flurry of key strokes is handled once.
+      if (searchAlarm != null) {
+        clearTimeout(searchAlarm);
+      }
+      searchAlarm = setTimeout(selectMatching, 300);
+    }
+
+    search.addEventListener('input', handleSearch);
+  </script>
+</body>
+</html>
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html
new file mode 100644 (file)
index 0000000..a113549
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>{{.Title}}</title>
+  {{template "css" .}}
+</head>
+<body>
+  {{template "header" .}}
+  <div id="graph">
+    {{.HTMLBody}}
+  </div>
+  {{template "script" .}}
+  <script>viewer(new URL(window.location.href), {{.Nodes}});</script>
+</body>
+</html>
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html
new file mode 100644 (file)
index 0000000..66cabbb
--- /dev/null
@@ -0,0 +1,113 @@
+<div class="header">
+  <div class="title">
+    <h1><a href="./">pprof</a></h1>
+  </div>
+
+  <div id="view" class="menu-item">
+    <div class="menu-name">
+      View
+      <i class="downArrow"></i>
+    </div>
+    <div class="submenu">
+      <a title="{{.Help.top}}"  href="./top" id="topbtn">Top</a>
+      <a title="{{.Help.graph}}" href="./" id="graphbtn">Graph</a>
+      <a title="{{.Help.flamegraph}}" href="./flamegraph" id="flamegraph">Flame Graph</a>
+      <a title="{{.Help.peek}}" href="./peek" id="peek">Peek</a>
+      <a title="{{.Help.list}}" href="./source" id="list">Source</a>
+      <a title="{{.Help.disasm}}" href="./disasm" id="disasm">Disassemble</a>
+    </div>
+  </div>
+
+  {{$sampleLen := len .SampleTypes}}
+  {{if gt $sampleLen 1}}
+  <div id="sample" class="menu-item">
+    <div class="menu-name">
+      Sample
+      <i class="downArrow"></i>
+    </div>
+    <div class="submenu">
+      {{range .SampleTypes}}
+      <a href="?si={{.}}" id="{{.}}">{{.}}</a>
+      {{end}}
+    </div>
+  </div>
+  {{end}}
+
+  <div id="refine" class="menu-item">
+    <div class="menu-name">
+      Refine
+      <i class="downArrow"></i>
+    </div>
+    <div class="submenu">
+      <a title="{{.Help.focus}}" href="?" id="focus">Focus</a>
+      <a title="{{.Help.ignore}}" href="?" id="ignore">Ignore</a>
+      <a title="{{.Help.hide}}" href="?" id="hide">Hide</a>
+      <a title="{{.Help.show}}" href="?" id="show">Show</a>
+      <a title="{{.Help.show_from}}" href="?" id="show-from">Show from</a>
+      <hr>
+      <a title="{{.Help.reset}}" href="?">Reset</a>
+    </div>
+  </div>
+
+  <div id="config" class="menu-item">
+    <div class="menu-name">
+      Config
+      <i class="downArrow"></i>
+    </div>
+    <div class="submenu">
+      <a title="{{.Help.save_config}}" id="save-config">Save as ...</a>
+      <hr>
+      {{range .Configs}}
+      <a href="{{.URL}}">
+        {{if .Current}}<span class="menu-check-mark">✓</span>{{end}}
+        {{.Name}}
+        {{if .UserConfig}}<span class="menu-delete-btn" data-config={{.Name}}>🗙</span>{{end}}
+      </a>
+      {{end}}
+    </div>
+  </div>
+
+  <div id="download" class="menu-item">
+    <div class="menu-name">
+      <a href="./download">Download</a>
+    </div>
+  </div>
+
+  <div>
+    <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
+  </div>
+
+  <div class="description">
+    <a title="{{.Help.details}}" href="#" id="details">{{.Title}}</a>
+    <div id="detailsbox">
+      {{range .Legend}}<div>{{.}}</div>{{end}}
+    </div>
+  </div>
+</div>
+
+<div id="dialog-overlay"></div>
+
+<div class="dialog" id="save-dialog">
+  <div class="dialog-header">Save options as</div>
+  <datalist id="config-list">
+    {{range .Configs}}{{if .UserConfig}}<option value="{{.Name}}" />{{end}}{{end}}
+  </datalist>
+  <input id="save-name" type="text" list="config-list" placeholder="New config" />
+  <div class="dialog-footer">
+    <span class="dialog-error" id="save-error"></span>
+    <button id="save-cancel">Cancel</button>
+    <button id="save-confirm">Save</button>
+  </div>
+</div>
+
+<div class="dialog" id="delete-dialog">
+  <div class="dialog-header" id="delete-dialog-title">Delete config</div>
+  <div id="delete-prompt"></div>
+  <div class="dialog-footer">
+    <span class="dialog-error" id="delete-error"></span>
+    <button id="delete-cancel">Cancel</button>
+    <button id="delete-confirm">Delete</button>
+  </div>
+</div>
+
+<div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/plaintext.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/plaintext.html
new file mode 100644 (file)
index 0000000..9791cc7
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>{{.Title}}</title>
+  {{template "css" .}}
+</head>
+<body>
+  {{template "header" .}}
+  <div id="content">
+    <pre>
+      {{.TextBody}}
+    </pre>
+  </div>
+  {{template "script" .}}
+  <script>viewer(new URL(window.location.href), null);</script>
+</body>
+</html>
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html
new file mode 100644 (file)
index 0000000..3212bee
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>{{.Title}}</title>
+  {{template "css" .}}
+  {{template "weblistcss" .}}
+  {{template "weblistjs" .}}
+</head>
+<body>
+  {{template "header" .}}
+  <div id="content" class="source">
+    {{.HTMLBody}}
+  </div>
+  {{template "script" .}}
+  <script>viewer(new URL(window.location.href), null);</script>
+</body>
+</html>
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/top.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/top.html
new file mode 100644 (file)
index 0000000..86d9fcb
--- /dev/null
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>{{.Title}}</title>
+  {{template "css" .}}
+  <style type="text/css">
+  </style>
+</head>
+<body>
+  {{template "header" .}}
+  <div id="top">
+    <table id="toptable">
+      <thead>
+        <tr>
+          <th id="flathdr1">Flat</th>
+          <th id="flathdr2">Flat%</th>
+          <th>Sum%</th>
+          <th id="cumhdr1">Cum</th>
+          <th id="cumhdr2">Cum%</th>
+          <th id="namehdr">Name</th>
+          <th>Inlined?</th>
+        </tr>
+      </thead>
+      <tbody id="rows"></tbody>
+    </table>
+  </div>
+  {{template "script" .}}
+  <script>
+    function makeTopTable(total, entries) {
+      const rows = document.getElementById('rows');
+      if (rows == null) return;
+
+      // Store initial index in each entry so we have stable node ids for selection.
+      for (let i = 0; i < entries.length; i++) {
+        entries[i].Id = 'node' + i;
+      }
+
+      // Which column are we currently sorted by and in what order?
+      let currentColumn = '';
+      let descending = false;
+      sortBy('Flat');
+
+      function sortBy(column) {
+        // Update sort criteria
+        if (column == currentColumn) {
+          descending = !descending; // Reverse order
+        } else {
+          currentColumn = column;
+          descending = (column != 'Name');
+        }
+
+        // Sort according to current criteria.
+        function cmp(a, b) {
+          const av = a[currentColumn];
+          const bv = b[currentColumn];
+          if (av < bv) return -1;
+          if (av > bv) return +1;
+          return 0;
+        }
+        entries.sort(cmp);
+        if (descending) entries.reverse();
+
+        function addCell(tr, val) {
+          const td = document.createElement('td');
+          td.textContent = val;
+          tr.appendChild(td);
+        }
+
+        function percent(v) {
+          return (v * 100.0 / total).toFixed(2) + '%';
+        }
+
+        // Generate rows
+        const fragment = document.createDocumentFragment();
+        let sum = 0;
+        for (const row of entries) {
+          const tr = document.createElement('tr');
+          tr.id = row.Id;
+          sum += row.Flat;
+          addCell(tr, row.FlatFormat);
+          addCell(tr, percent(row.Flat));
+          addCell(tr, percent(sum));
+          addCell(tr, row.CumFormat);
+          addCell(tr, percent(row.Cum));
+          addCell(tr, row.Name);
+          addCell(tr, row.InlineLabel);
+          fragment.appendChild(tr);
+        }
+
+        rows.textContent = ''; // Remove old rows
+        rows.appendChild(fragment);
+      }
+
+      // Make different column headers trigger sorting.
+      function bindSort(id, column) {
+        const hdr = document.getElementById(id);
+        if (hdr == null) return;
+        const fn = function() { sortBy(column) };
+        hdr.addEventListener('click', fn);
+        hdr.addEventListener('touch', fn);
+      }
+      bindSort('flathdr1', 'Flat');
+      bindSort('flathdr2', 'Flat');
+      bindSort('cumhdr1', 'Cum');
+      bindSort('cumhdr2', 'Cum');
+      bindSort('namehdr', 'Name');
+    }
+
+    viewer(new URL(window.location.href), {{.Nodes}});
+    makeTopTable({{.Total}}, {{.Top}});
+  </script>
+</body>
+</html>
index c2cdfa455eeaf54e8063d7cfb1a0c5272ecdd727..c43d5999821f3aee234355994636d215117f2968 100644 (file)
@@ -113,12 +113,17 @@ func formatLabelValues(s *profile.Sample, k string, outputUnit string) []string
        values = append(values, s.Label[k]...)
        numLabels := s.NumLabel[k]
        numUnits := s.NumUnit[k]
-       if len(numLabels) != len(numUnits) {
+       if len(numLabels) != len(numUnits) && len(numUnits) != 0 {
                return values
        }
        for i, numLabel := range numLabels {
-               unit := numUnits[i]
-               values = append(values, measurement.ScaledLabel(numLabel, unit, outputUnit))
+               var value string
+               if len(numUnits) != 0 {
+                       value = measurement.ScaledLabel(numLabel, numUnits[i], outputUnit)
+               } else {
+                       value = measurement.ScaledLabel(numLabel, "", "")
+               }
+               values = append(values, value)
        }
        return values
 }
index 63df668321bf3b3cfd3671ca1e3dda79d6a64e5d..94f32e3755fc9bdcf400cc41f530d7a769315520 100644 (file)
 package driver
 
 import (
+       "embed"
+       "fmt"
        "html/template"
+       "os"
 
        "github.com/google/pprof/third_party/d3flamegraph"
 )
 
+//go:embed html
+var embeddedFiles embed.FS
+
 // addTemplates adds a set of template definitions to templates.
 func addTemplates(templates *template.Template) {
-       template.Must(templates.Parse(`{{define "d3flamegraphscript"}}` + d3flamegraph.JSSource + `{{end}}`))
-       template.Must(templates.Parse(`{{define "d3flamegraphcss"}}` + d3flamegraph.CSSSource + `{{end}}`))
-       template.Must(templates.Parse(`
-{{define "css"}}
-<style type="text/css">
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-}
-html, body {
-  height: 100%;
-}
-body {
-  font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
-  font-size: 13px;
-  line-height: 1.4;
-  display: flex;
-  flex-direction: column;
-}
-a {
-  color: #2a66d9;
-}
-.header {
-  display: flex;
-  align-items: center;
-  height: 44px;
-  min-height: 44px;
-  background-color: #eee;
-  color: #212121;
-  padding: 0 1rem;
-}
-.header > div {
-  margin: 0 0.125em;
-}
-.header .title h1 {
-  font-size: 1.75em;
-  margin-right: 1rem;
-  margin-bottom: 4px;
-}
-.header .title a {
-  color: #212121;
-  text-decoration: none;
-}
-.header .title a:hover {
-  text-decoration: underline;
-}
-.header .description {
-  width: 100%;
-  text-align: right;
-  white-space: nowrap;
-}
-@media screen and (max-width: 799px) {
-  .header input {
-    display: none;
-  }
-}
-#detailsbox {
-  display: none;
-  z-index: 1;
-  position: fixed;
-  top: 40px;
-  right: 20px;
-  background-color: #ffffff;
-  box-shadow: 0 1px 5px rgba(0,0,0,.3);
-  line-height: 24px;
-  padding: 1em;
-  text-align: left;
-}
-.header input {
-  background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' style='pointer-events:none;display:block;width:100%25;height:100%25;fill:%23757575'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61.0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat 4px center/20px 20px;
-  border: 1px solid #d1d2d3;
-  border-radius: 2px 0 0 2px;
-  padding: 0.25em;
-  padding-left: 28px;
-  margin-left: 1em;
-  font-family: 'Roboto', 'Noto', sans-serif;
-  font-size: 1em;
-  line-height: 24px;
-  color: #212121;
-}
-.downArrow {
-  border-top: .36em solid #ccc;
-  border-left: .36em solid transparent;
-  border-right: .36em solid transparent;
-  margin-bottom: .05em;
-  margin-left: .5em;
-  transition: border-top-color 200ms;
-}
-.menu-item {
-  height: 100%;
-  text-transform: uppercase;
-  font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
-  position: relative;
-}
-.menu-item .menu-name:hover {
-  opacity: 0.75;
-}
-.menu-item .menu-name:hover .downArrow {
-  border-top-color: #666;
-}
-.menu-name {
-  height: 100%;
-  padding: 0 0.5em;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.menu-name a {
-  text-decoration: none;
-  color: #212121;
-}
-.submenu {
-  display: none;
-  z-index: 1;
-  margin-top: -4px;
-  min-width: 10em;
-  position: absolute;
-  left: 0px;
-  background-color: white;
-  box-shadow: 0 1px 5px rgba(0,0,0,.3);
-  font-size: 100%;
-  text-transform: none;
-}
-.menu-item, .submenu {
-  user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
-  -webkit-user-select: none;
-}
-.submenu hr {
-  border: 0;
-  border-top: 2px solid #eee;
-}
-.submenu a {
-  display: block;
-  padding: .5em 1em;
-  text-decoration: none;
-}
-.submenu a:hover, .submenu a.active {
-  color: white;
-  background-color: #6b82d6;
-}
-.submenu a.disabled {
-  color: gray;
-  pointer-events: none;
-}
-.menu-check-mark {
-  position: absolute;
-  left: 2px;
-}
-.menu-delete-btn {
-  position: absolute;
-  right: 2px;
-}
-
-{{/* Used to disable events when a modal dialog is displayed */}}
-#dialog-overlay {
-  display: none;
-  position: fixed;
-  left: 0px;
-  top: 0px;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(1,1,1,0.1);
-}
-
-.dialog {
-  {{/* Displayed centered horizontally near the top */}}
-  display: none;
-  position: fixed;
-  margin: 0px;
-  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);
-}
-.dialog-header {
-  font-size: 120%;
-  border-bottom: 1px solid #CCCCCC;
-  width: 100%;
-  text-align: center;
-  background: #EEEEEE;
-  user-select: none;
-}
-.dialog-footer {
-  border-top: 1px solid #CCCCCC;
-  width: 100%;
-  text-align: right;
-  padding: 10px;
-}
-.dialog-error {
-  margin: 10px;
-  color: red;
-}
-.dialog input {
-  margin: 10px;
-  font-size: inherit;
-}
-.dialog button {
-  margin-left: 10px;
-  font-size: inherit;
-}
-#save-dialog, #delete-dialog {
-  width: 50%;
-  max-width: 20em;
-}
-#delete-prompt {
-  padding: 10px;
-}
-
-#content {
-  overflow-y: scroll;
-  padding: 1em;
-}
-#top {
-  overflow-y: scroll;
-}
-#graph {
-  overflow: hidden;
-}
-#graph svg {
-  width: 100%;
-  height: auto;
-  padding: 10px;
-}
-#content.source .filename {
-  margin-top: 0;
-  margin-bottom: 1em;
-  font-size: 120%;
-}
-#content.source pre {
-  margin-bottom: 3em;
-}
-table {
-  border-spacing: 0px;
-  width: 100%;
-  padding-bottom: 1em;
-  white-space: nowrap;
-}
-table thead {
-  font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
-}
-table tr th {
-  position: sticky;
-  top: 0;
-  background-color: #ddd;
-  text-align: right;
-  padding: .3em .5em;
-}
-table tr td {
-  padding: .3em .5em;
-  text-align: right;
-}
-#top table tr th:nth-child(6),
-#top table tr th:nth-child(7),
-#top table tr td:nth-child(6),
-#top table tr td:nth-child(7) {
-  text-align: left;
-}
-#top table tr td:nth-child(6) {
-  width: 100%;
-  text-overflow: ellipsis;
-  overflow: hidden;
-  white-space: nowrap;
-}
-#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {
-  cursor: ns-resize;
-}
-.hilite {
-  background-color: #ebf5fb;
-  font-weight: bold;
-}
-</style>
-{{end}}
-
-{{define "header"}}
-<div class="header">
-  <div class="title">
-    <h1><a href="./">pprof</a></h1>
-  </div>
-
-  <div id="view" class="menu-item">
-    <div class="menu-name">
-      View
-      <i class="downArrow"></i>
-    </div>
-    <div class="submenu">
-      <a title="{{.Help.top}}"  href="./top" id="topbtn">Top</a>
-      <a title="{{.Help.graph}}" href="./" id="graphbtn">Graph</a>
-      <a title="{{.Help.flamegraph}}" href="./flamegraph" id="flamegraph">Flame Graph</a>
-      <a title="{{.Help.peek}}" href="./peek" id="peek">Peek</a>
-      <a title="{{.Help.list}}" href="./source" id="list">Source</a>
-      <a title="{{.Help.disasm}}" href="./disasm" id="disasm">Disassemble</a>
-    </div>
-  </div>
-
-  {{$sampleLen := len .SampleTypes}}
-  {{if gt $sampleLen 1}}
-  <div id="sample" class="menu-item">
-    <div class="menu-name">
-      Sample
-      <i class="downArrow"></i>
-    </div>
-    <div class="submenu">
-      {{range .SampleTypes}}
-      <a href="?si={{.}}" id="{{.}}">{{.}}</a>
-      {{end}}
-    </div>
-  </div>
-  {{end}}
-
-  <div id="refine" class="menu-item">
-    <div class="menu-name">
-      Refine
-      <i class="downArrow"></i>
-    </div>
-    <div class="submenu">
-      <a title="{{.Help.focus}}" href="?" id="focus">Focus</a>
-      <a title="{{.Help.ignore}}" href="?" id="ignore">Ignore</a>
-      <a title="{{.Help.hide}}" href="?" id="hide">Hide</a>
-      <a title="{{.Help.show}}" href="?" id="show">Show</a>
-      <a title="{{.Help.show_from}}" href="?" id="show-from">Show from</a>
-      <hr>
-      <a title="{{.Help.reset}}" href="?">Reset</a>
-    </div>
-  </div>
-
-  <div id="config" class="menu-item">
-    <div class="menu-name">
-      Config
-      <i class="downArrow"></i>
-    </div>
-    <div class="submenu">
-      <a title="{{.Help.save_config}}" id="save-config">Save as ...</a>
-      <hr>
-      {{range .Configs}}
-        <a href="{{.URL}}">
-          {{if .Current}}<span class="menu-check-mark">✓</span>{{end}}
-          {{.Name}}
-          {{if .UserConfig}}<span class="menu-delete-btn" data-config={{.Name}}>🗙</span>{{end}}
-        </a>
-      {{end}}
-    </div>
-  </div>
-
-  <div id="download" class="menu-item">
-    <div class="menu-name">
-      <a href="./download">Download</a>
-    </div>
-  </div>
-
-  <div>
-    <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
-  </div>
-
-  <div class="description">
-    <a title="{{.Help.details}}" href="#" id="details">{{.Title}}</a>
-    <div id="detailsbox">
-      {{range .Legend}}<div>{{.}}</div>{{end}}
-    </div>
-  </div>
-</div>
-
-<div id="dialog-overlay"></div>
-
-<div class="dialog" id="save-dialog">
-  <div class="dialog-header">Save options as</div>
-  <datalist id="config-list">
-    {{range .Configs}}{{if .UserConfig}}<option value="{{.Name}}" />{{end}}{{end}}
-  </datalist>
-  <input id="save-name" type="text" list="config-list" placeholder="New config" />
-  <div class="dialog-footer">
-    <span class="dialog-error" id="save-error"></span>
-    <button id="save-cancel">Cancel</button>
-    <button id="save-confirm">Save</button>
-  </div>
-</div>
-
-<div class="dialog" id="delete-dialog">
-  <div class="dialog-header" id="delete-dialog-title">Delete config</div>
-  <div id="delete-prompt"></div>
-  <div class="dialog-footer">
-    <span class="dialog-error" id="delete-error"></span>
-    <button id="delete-cancel">Cancel</button>
-    <button id="delete-confirm">Delete</button>
-  </div>
-</div>
-
-<div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
-{{end}}
-
-{{define "graph" -}}
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>{{.Title}}</title>
-  {{template "css" .}}
-</head>
-<body>
-  {{template "header" .}}
-  <div id="graph">
-    {{.HTMLBody}}
-  </div>
-  {{template "script" .}}
-  <script>viewer(new URL(window.location.href), {{.Nodes}});</script>
-</body>
-</html>
-{{end}}
-
-{{define "script"}}
-<script>
-// Make svg pannable and zoomable.
-// Call clickHandler(t) if a click event is caught by the pan event handlers.
-function initPanAndZoom(svg, clickHandler) {
-  'use strict';
-
-  // Current mouse/touch handling mode
-  const IDLE = 0;
-  const MOUSEPAN = 1;
-  const TOUCHPAN = 2;
-  const TOUCHZOOM = 3;
-  let mode = IDLE;
-
-  // State needed to implement zooming.
-  let currentScale = 1.0;
-  const initWidth = svg.viewBox.baseVal.width;
-  const initHeight = svg.viewBox.baseVal.height;
-
-  // State needed to implement panning.
-  let panLastX = 0;      // Last event X coordinate
-  let panLastY = 0;      // Last event Y coordinate
-  let moved = false;     // Have we seen significant movement
-  let touchid = null;    // Current touch identifier
-
-  // State needed for pinch zooming
-  let touchid2 = null;     // Second id for pinch zooming
-  let initGap = 1.0;       // Starting gap between two touches
-  let initScale = 1.0;     // currentScale when pinch zoom started
-  let centerPoint = null;  // Center point for scaling
-
-  // Convert event coordinates to svg coordinates.
-  function toSvg(x, y) {
-    const p = svg.createSVGPoint();
-    p.x = x;
-    p.y = y;
-    let m = svg.getCTM();
-    if (m == null) m = svg.getScreenCTM(); // Firefox workaround.
-    return p.matrixTransform(m.inverse());
-  }
-
-  // Change the scaling for the svg to s, keeping the point denoted
-  // by u (in svg coordinates]) fixed at the same screen location.
-  function rescale(s, u) {
-    // Limit to a good range.
-    if (s < 0.2) s = 0.2;
-    if (s > 10.0) s = 10.0;
-
-    currentScale = s;
-
-    // svg.viewBox defines the visible portion of the user coordinate
-    // system.  So to magnify by s, divide the visible portion by s,
-    // which will then be stretched to fit the viewport.
-    const vb = svg.viewBox;
-    const w1 = vb.baseVal.width;
-    const w2 = initWidth / s;
-    const h1 = vb.baseVal.height;
-    const h2 = initHeight / s;
-    vb.baseVal.width = w2;
-    vb.baseVal.height = h2;
-
-    // We also want to adjust vb.baseVal.x so that u.x remains at same
-    // screen X coordinate.  In other words, want to change it from x1 to x2
-    // so that:
-    //     (u.x - x1) / w1 = (u.x - x2) / w2
-    // Simplifying that, we get
-    //     (u.x - x1) * (w2 / w1) = u.x - x2
-    //     x2 = u.x - (u.x - x1) * (w2 / w1)
-    vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1);
-    vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1);
-  }
-
-  function handleWheel(e) {
-    if (e.deltaY == 0) return;
-    // Change scale factor by 1.1 or 1/1.1
-    rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),
-            toSvg(e.offsetX, e.offsetY));
-  }
-
-  function setMode(m) {
-    mode = m;
-    touchid = null;
-    touchid2 = null;
-  }
-
-  function panStart(x, y) {
-    moved = false;
-    panLastX = x;
-    panLastY = y;
-  }
-
-  function panMove(x, y) {
-    let dx = x - panLastX;
-    let dy = y - panLastY;
-    if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return; // Ignore tiny moves
-
-    moved = true;
-    panLastX = x;
-    panLastY = y;
-
-    // Firefox workaround: get dimensions from parentNode.
-    const swidth = svg.clientWidth || svg.parentNode.clientWidth;
-    const sheight = svg.clientHeight || svg.parentNode.clientHeight;
-
-    // Convert deltas from screen space to svg space.
-    dx *= (svg.viewBox.baseVal.width / swidth);
-    dy *= (svg.viewBox.baseVal.height / sheight);
-
-    svg.viewBox.baseVal.x -= dx;
-    svg.viewBox.baseVal.y -= dy;
-  }
-
-  function handleScanStart(e) {
-    if (e.button != 0) return; // Do not catch right-clicks etc.
-    setMode(MOUSEPAN);
-    panStart(e.clientX, e.clientY);
-    e.preventDefault();
-    svg.addEventListener('mousemove', handleScanMove);
-  }
-
-  function handleScanMove(e) {
-    if (e.buttons == 0) {
-      // Missed an end event, perhaps because mouse moved outside window.
-      setMode(IDLE);
-      svg.removeEventListener('mousemove', handleScanMove);
-      return;
-    }
-    if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
-  }
-
-  function handleScanEnd(e) {
-    if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
-    setMode(IDLE);
-    svg.removeEventListener('mousemove', handleScanMove);
-    if (!moved) clickHandler(e.target);
-  }
-
-  // Find touch object with specified identifier.
-  function findTouch(tlist, id) {
-    for (const t of tlist) {
-      if (t.identifier == id) return t;
-    }
-    return null;
-  }
-
-  // Return distance between two touch points
-  function touchGap(t1, t2) {
-    const dx = t1.clientX - t2.clientX;
-    const dy = t1.clientY - t2.clientY;
-    return Math.hypot(dx, dy);
-  }
-
-  function handleTouchStart(e) {
-    if (mode == IDLE && e.changedTouches.length == 1) {
-      // Start touch based panning
-      const t = e.changedTouches[0];
-      setMode(TOUCHPAN);
-      touchid = t.identifier;
-      panStart(t.clientX, t.clientY);
-      e.preventDefault();
-    } else if (mode == TOUCHPAN && e.touches.length == 2) {
-      // Start pinch zooming
-      setMode(TOUCHZOOM);
-      const t1 = e.touches[0];
-      const t2 = e.touches[1];
-      touchid = t1.identifier;
-      touchid2 = t2.identifier;
-      initScale = currentScale;
-      initGap = touchGap(t1, t2);
-      centerPoint = toSvg((t1.clientX + t2.clientX) / 2,
-                          (t1.clientY + t2.clientY) / 2);
-      e.preventDefault();
-    }
-  }
-
-  function handleTouchMove(e) {
-    if (mode == TOUCHPAN) {
-      const t = findTouch(e.changedTouches, touchid);
-      if (t == null) return;
-      if (e.touches.length != 1) {
-        setMode(IDLE);
-        return;
-      }
-      panMove(t.clientX, t.clientY);
-      e.preventDefault();
-    } else if (mode == TOUCHZOOM) {
-      // Get two touches; new gap; rescale to ratio.
-      const t1 = findTouch(e.touches, touchid);
-      const t2 = findTouch(e.touches, touchid2);
-      if (t1 == null || t2 == null) return;
-      const gap = touchGap(t1, t2);
-      rescale(initScale * gap / initGap, centerPoint);
-      e.preventDefault();
-    }
-  }
-
-  function handleTouchEnd(e) {
-    if (mode == TOUCHPAN) {
-      const t = findTouch(e.changedTouches, touchid);
-      if (t == null) return;
-      panMove(t.clientX, t.clientY);
-      setMode(IDLE);
-      e.preventDefault();
-      if (!moved) clickHandler(t.target);
-    } else if (mode == TOUCHZOOM) {
-      setMode(IDLE);
-      e.preventDefault();
-    }
-  }
-
-  svg.addEventListener('mousedown', handleScanStart);
-  svg.addEventListener('mouseup', handleScanEnd);
-  svg.addEventListener('touchstart', handleTouchStart);
-  svg.addEventListener('touchmove', handleTouchMove);
-  svg.addEventListener('touchend', handleTouchEnd);
-  svg.addEventListener('wheel', handleWheel, true);
-}
-
-function initMenus() {
-  'use strict';
-
-  let activeMenu = null;
-  let activeMenuHdr = null;
-
-  function cancelActiveMenu() {
-    if (activeMenu == null) return;
-    activeMenu.style.display = 'none';
-    activeMenu = null;
-    activeMenuHdr = null;
-  }
-
-  // Set click handlers on every menu header.
-  for (const menu of document.getElementsByClassName('submenu')) {
-    const hdr = menu.parentElement;
-    if (hdr == null) return;
-    if (hdr.classList.contains('disabled')) return;
-    function showMenu(e) {
-      // menu is a child of hdr, so this event can fire for clicks
-      // inside menu. Ignore such clicks.
-      if (e.target.parentElement != hdr) return;
-      activeMenu = menu;
-      activeMenuHdr = hdr;
-      menu.style.display = 'block';
-    }
-    hdr.addEventListener('mousedown', showMenu);
-    hdr.addEventListener('touchstart', showMenu);
-  }
-
-  // If there is an active menu and a down event outside, retract the menu.
-  for (const t of ['mousedown', 'touchstart']) {
-    document.addEventListener(t, (e) => {
-      // Note: to avoid unnecessary flicker, if the down event is inside
-      // the active menu header, do not retract the menu.
-      if (activeMenuHdr != e.target.closest('.menu-item')) {
-        cancelActiveMenu();
-      }
-    }, { passive: true, capture: true });
-  }
-
-  // If there is an active menu and an up event inside, retract the menu.
-  document.addEventListener('mouseup', (e) => {
-    if (activeMenu == e.target.closest('.submenu')) {
-      cancelActiveMenu();
-    }
-  }, { passive: true, capture: true });
-}
-
-function sendURL(method, url, done) {
-  fetch(url.toString(), {method: method})
-      .then((response) => { done(response.ok); })
-      .catch((error) => { done(false); });
-}
-
-// Initialize handlers for saving/loading configurations.
-function initConfigManager() {
-  'use strict';
-
-  // Initialize various elements.
-  function elem(id) {
-    const result = document.getElementById(id);
-    if (!result) console.warn('element ' + id + ' not found');
-    return result;
-  }
-  const overlay = elem('dialog-overlay');
-  const saveDialog = elem('save-dialog');
-  const saveInput = elem('save-name');
-  const saveError = elem('save-error');
-  const delDialog = elem('delete-dialog');
-  const delPrompt = elem('delete-prompt');
-  const delError = elem('delete-error');
-
-  let currentDialog = null;
-  let currentDeleteTarget = null;
-
-  function showDialog(dialog) {
-    if (currentDialog != null) {
-      overlay.style.display = 'none';
-      currentDialog.style.display = 'none';
-    }
-    currentDialog = dialog;
-    if (dialog != null) {
-      overlay.style.display = 'block';
-      dialog.style.display = 'block';
-    }
-  }
-
-  function cancelDialog(e) {
-    showDialog(null);
-  }
-
-  // Show dialog for saving the current config.
-  function showSaveDialog(e) {
-    saveError.innerText = '';
-    showDialog(saveDialog);
-    saveInput.focus();
-  }
-
-  // Commit save config.
-  function commitSave(e) {
-    const name = saveInput.value;
-    const url = new URL(document.URL);
-    // Set path relative to existing path.
-    url.pathname = new URL('./saveconfig', document.URL).pathname;
-    url.searchParams.set('config', name);
-    saveError.innerText = '';
-    sendURL('POST', url, (ok) => {
-      if (!ok) {
-        saveError.innerText = 'Save failed';
-      } else {
-        showDialog(null);
-        location.reload();  // Reload to show updated config menu
-      }
-    });
-  }
-
-  function handleSaveInputKey(e) {
-    if (e.key === 'Enter') commitSave(e);
-  }
-
-  function deleteConfig(e, elem) {
-    e.preventDefault();
-    const config = elem.dataset.config;
-    delPrompt.innerText = 'Delete ' + config + '?';
-    currentDeleteTarget = elem;
-    showDialog(delDialog);
-  }
-
-  function commitDelete(e, elem) {
-    if (!currentDeleteTarget) return;
-    const config = currentDeleteTarget.dataset.config;
-    const url = new URL('./deleteconfig', document.URL);
-    url.searchParams.set('config', config);
-    delError.innerText = '';
-    sendURL('DELETE', url, (ok) => {
-      if (!ok) {
-        delError.innerText = 'Delete failed';
-        return;
-      }
-      showDialog(null);
-      // Remove menu entry for this config.
-      if (currentDeleteTarget && currentDeleteTarget.parentElement) {
-        currentDeleteTarget.parentElement.remove();
-      }
-    });
-  }
-
-  // Bind event on elem to fn.
-  function bind(event, elem, fn) {
-    if (elem == null) return;
-    elem.addEventListener(event, fn);
-    if (event == 'click') {
-      // Also enable via touch.
-      elem.addEventListener('touchstart', fn);
-    }
-  }
-
-  bind('click', elem('save-config'), showSaveDialog);
-  bind('click', elem('save-cancel'), cancelDialog);
-  bind('click', elem('save-confirm'), commitSave);
-  bind('keydown', saveInput, handleSaveInputKey);
-
-  bind('click', elem('delete-cancel'), cancelDialog);
-  bind('click', elem('delete-confirm'), commitDelete);
-
-  // Activate deletion button for all config entries in menu.
-  for (const del of Array.from(document.getElementsByClassName('menu-delete-btn'))) {
-    bind('click', del, (e) => {
-      deleteConfig(e, del);
-    });
-  }
-}
-
-function viewer(baseUrl, nodes) {
-  'use strict';
-
-  // Elements
-  const search = document.getElementById('search');
-  const graph0 = document.getElementById('graph0');
-  const svg = (graph0 == null ? null : graph0.parentElement);
-  const toptable = document.getElementById('toptable');
-
-  let regexpActive = false;
-  let selected = new Map();
-  let origFill = new Map();
-  let searchAlarm = null;
-  let buttonsEnabled = true;
-
-  function handleDetails(e) {
-    e.preventDefault();
-    const detailsText = document.getElementById('detailsbox');
-    if (detailsText != null) {
-      if (detailsText.style.display === 'block') {
-        detailsText.style.display = 'none';
-      } else {
-        detailsText.style.display = 'block';
-      }
-    }
-  }
-
-  function handleKey(e) {
-    if (e.keyCode != 13) return;
-    setHrefParams(window.location, function (params) {
-      params.set('f', search.value);
-    });
-    e.preventDefault();
-  }
-
-  function handleSearch() {
-    // Delay expensive processing so a flurry of key strokes is handled once.
-    if (searchAlarm != null) {
-      clearTimeout(searchAlarm);
-    }
-    searchAlarm = setTimeout(selectMatching, 300);
-
-    regexpActive = true;
-    updateButtons();
-  }
-
-  function selectMatching() {
-    searchAlarm = null;
-    let re = null;
-    if (search.value != '') {
-      try {
-        re = new RegExp(search.value);
-      } catch (e) {
-        // TODO: Display error state in search box
-        return;
-      }
-    }
-
-    function match(text) {
-      return re != null && re.test(text);
-    }
-
-    // drop currently selected items that do not match re.
-    selected.forEach(function(v, n) {
-      if (!match(nodes[n])) {
-        unselect(n, document.getElementById('node' + n));
-      }
-    })
-
-    // add matching items that are not currently selected.
-    if (nodes) {
-      for (let n = 0; n < nodes.length; n++) {
-        if (!selected.has(n) && match(nodes[n])) {
-          select(n, document.getElementById('node' + n));
-        }
-      }
-    }
-
-    updateButtons();
-  }
-
-  function toggleSvgSelect(elem) {
-    // Walk up to immediate child of graph0
-    while (elem != null && elem.parentElement != graph0) {
-      elem = elem.parentElement;
-    }
-    if (!elem) return;
-
-    // Disable regexp mode.
-    regexpActive = false;
-
-    const n = nodeId(elem);
-    if (n < 0) return;
-    if (selected.has(n)) {
-      unselect(n, elem);
-    } else {
-      select(n, elem);
-    }
-    updateButtons();
-  }
-
-  function unselect(n, elem) {
-    if (elem == null) return;
-    selected.delete(n);
-    setBackground(elem, false);
-  }
-
-  function select(n, elem) {
-    if (elem == null) return;
-    selected.set(n, true);
-    setBackground(elem, true);
-  }
-
-  function nodeId(elem) {
-    const id = elem.id;
-    if (!id) return -1;
-    if (!id.startsWith('node')) return -1;
-    const n = parseInt(id.slice(4), 10);
-    if (isNaN(n)) return -1;
-    if (n < 0 || n >= nodes.length) return -1;
-    return n;
-  }
-
-  function setBackground(elem, set) {
-    // Handle table row highlighting.
-    if (elem.nodeName == 'TR') {
-      elem.classList.toggle('hilite', set);
-      return;
-    }
-
-    // Handle svg element highlighting.
-    const p = findPolygon(elem);
-    if (p != null) {
-      if (set) {
-        origFill.set(p, p.style.fill);
-        p.style.fill = '#ccccff';
-      } else if (origFill.has(p)) {
-        p.style.fill = origFill.get(p);
-      }
-    }
-  }
-
-  function findPolygon(elem) {
-    if (elem.localName == 'polygon') return elem;
-    for (const c of elem.children) {
-      const p = findPolygon(c);
-      if (p != null) return p;
-    }
-    return null;
-  }
-
-  // convert a string to a regexp that matches that string.
-  function quotemeta(str) {
-    return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
-  }
-
-  function setSampleIndexLink(id) {
-    const elem = document.getElementById(id);
-    if (elem != null) {
-      setHrefParams(elem, function (params) {
-        params.set("si", id);
-      });
-    }
-  }
-
-  // Update id's href to reflect current selection whenever it is
-  // liable to be followed.
-  function makeSearchLinkDynamic(id) {
-    const elem = document.getElementById(id);
-    if (elem == null) return;
-
-    // Most links copy current selection into the 'f' parameter,
-    // but Refine menu links are different.
-    let param = 'f';
-    if (id == 'ignore') param = 'i';
-    if (id == 'hide') param = 'h';
-    if (id == 'show') param = 's';
-    if (id == 'show-from') param = 'sf';
-
-    // We update on mouseenter so middle-click/right-click work properly.
-    elem.addEventListener('mouseenter', updater);
-    elem.addEventListener('touchstart', updater);
-
-    function updater() {
-      // The selection can be in one of two modes: regexp-based or
-      // list-based.  Construct regular expression depending on mode.
-      let re = regexpActive
-        ? search.value
-        : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|');
-
-      setHrefParams(elem, function (params) {
-        if (re != '') {
-          // For focus/show/show-from, forget old parameter. For others, add to re.
-          if (param != 'f' && param != 's' && param != 'sf' && params.has(param)) {
-            const old = params.get(param);
-            if (old != '') {
-              re += '|' + old;
-            }
-          }
-          params.set(param, re);
-        } else {
-          params.delete(param);
-        }
-      });
-    }
-  }
-
-  function setHrefParams(elem, paramSetter) {
-    let url = new URL(elem.href);
-    url.hash = '';
-
-    // Copy params from this page's URL.
-    const params = url.searchParams;
-    for (const p of new URLSearchParams(window.location.search)) {
-      params.set(p[0], p[1]);
-    }
-
-    // Give the params to the setter to modify.
-    paramSetter(params);
-
-    elem.href = url.toString();
-  }
-
-  function handleTopClick(e) {
-    // Walk back until we find TR and then get the Name column (index 5)
-    let elem = e.target;
-    while (elem != null && elem.nodeName != 'TR') {
-      elem = elem.parentElement;
-    }
-    if (elem == null || elem.children.length < 6) return;
-
-    e.preventDefault();
-    const tr = elem;
-    const td = elem.children[5];
-    if (td.nodeName != 'TD') return;
-    const name = td.innerText;
-    const index = nodes.indexOf(name);
-    if (index < 0) return;
-
-    // Disable regexp mode.
-    regexpActive = false;
-
-    if (selected.has(index)) {
-      unselect(index, elem);
-    } else {
-      select(index, elem);
-    }
-    updateButtons();
-  }
-
-  function updateButtons() {
-    const enable = (search.value != '' || selected.size != 0);
-    if (buttonsEnabled == enable) return;
-    buttonsEnabled = enable;
-    for (const id of ['focus', 'ignore', 'hide', 'show', 'show-from']) {
-      const link = document.getElementById(id);
-      if (link != null) {
-        link.classList.toggle('disabled', !enable);
-      }
-    }
-  }
-
-  // Initialize button states
-  updateButtons();
-
-  // Setup event handlers
-  initMenus();
-  if (svg != null) {
-    initPanAndZoom(svg, toggleSvgSelect);
-  }
-  if (toptable != null) {
-    toptable.addEventListener('mousedown', handleTopClick);
-    toptable.addEventListener('touchstart', handleTopClick);
-  }
-
-  const ids = ['topbtn', 'graphbtn', 'flamegraph', 'peek', 'list', 'disasm',
-               'focus', 'ignore', 'hide', 'show', 'show-from'];
-  ids.forEach(makeSearchLinkDynamic);
-
-  const sampleIDs = [{{range .SampleTypes}}'{{.}}', {{end}}];
-  sampleIDs.forEach(setSampleIndexLink);
-
-  // Bind action to button with specified id.
-  function addAction(id, action) {
-    const btn = document.getElementById(id);
-    if (btn != null) {
-      btn.addEventListener('click', action);
-      btn.addEventListener('touchstart', action);
-    }
-  }
-
-  addAction('details', handleDetails);
-  initConfigManager();
-
-  search.addEventListener('input', handleSearch);
-  search.addEventListener('keydown', handleKey);
-
-  // Give initial focus to main container so it can be scrolled using keys.
-  const main = document.getElementById('bodycontainer');
-  if (main) {
-    main.focus();
-  }
-}
-</script>
-{{end}}
-
-{{define "top" -}}
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>{{.Title}}</title>
-  {{template "css" .}}
-  <style type="text/css">
-  </style>
-</head>
-<body>
-  {{template "header" .}}
-  <div id="top">
-    <table id="toptable">
-      <thead>
-        <tr>
-          <th id="flathdr1">Flat</th>
-          <th id="flathdr2">Flat%</th>
-          <th>Sum%</th>
-          <th id="cumhdr1">Cum</th>
-          <th id="cumhdr2">Cum%</th>
-          <th id="namehdr">Name</th>
-          <th>Inlined?</th>
-        </tr>
-      </thead>
-      <tbody id="rows"></tbody>
-    </table>
-  </div>
-  {{template "script" .}}
-  <script>
-    function makeTopTable(total, entries) {
-      const rows = document.getElementById('rows');
-      if (rows == null) return;
-
-      // Store initial index in each entry so we have stable node ids for selection.
-      for (let i = 0; i < entries.length; i++) {
-        entries[i].Id = 'node' + i;
-      }
-
-      // Which column are we currently sorted by and in what order?
-      let currentColumn = '';
-      let descending = false;
-      sortBy('Flat');
-
-      function sortBy(column) {
-        // Update sort criteria
-        if (column == currentColumn) {
-          descending = !descending; // Reverse order
-        } else {
-          currentColumn = column;
-          descending = (column != 'Name');
-        }
-
-        // Sort according to current criteria.
-        function cmp(a, b) {
-          const av = a[currentColumn];
-          const bv = b[currentColumn];
-          if (av < bv) return -1;
-          if (av > bv) return +1;
-          return 0;
-        }
-        entries.sort(cmp);
-        if (descending) entries.reverse();
-
-        function addCell(tr, val) {
-          const td = document.createElement('td');
-          td.textContent = val;
-          tr.appendChild(td);
-        }
-
-        function percent(v) {
-          return (v * 100.0 / total).toFixed(2) + '%';
-        }
-
-        // Generate rows
-        const fragment = document.createDocumentFragment();
-        let sum = 0;
-        for (const row of entries) {
-          const tr = document.createElement('tr');
-          tr.id = row.Id;
-          sum += row.Flat;
-          addCell(tr, row.FlatFormat);
-          addCell(tr, percent(row.Flat));
-          addCell(tr, percent(sum));
-          addCell(tr, row.CumFormat);
-          addCell(tr, percent(row.Cum));
-          addCell(tr, row.Name);
-          addCell(tr, row.InlineLabel);
-          fragment.appendChild(tr);
-        }
-
-        rows.textContent = ''; // Remove old rows
-        rows.appendChild(fragment);
-      }
-
-      // Make different column headers trigger sorting.
-      function bindSort(id, column) {
-        const hdr = document.getElementById(id);
-        if (hdr == null) return;
-        const fn = function() { sortBy(column) };
-        hdr.addEventListener('click', fn);
-        hdr.addEventListener('touch', fn);
-      }
-      bindSort('flathdr1', 'Flat');
-      bindSort('flathdr2', 'Flat');
-      bindSort('cumhdr1', 'Cum');
-      bindSort('cumhdr2', 'Cum');
-      bindSort('namehdr', 'Name');
-    }
-
-    viewer(new URL(window.location.href), {{.Nodes}});
-    makeTopTable({{.Total}}, {{.Top}});
-  </script>
-</body>
-</html>
-{{end}}
-
-{{define "sourcelisting" -}}
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>{{.Title}}</title>
-  {{template "css" .}}
-  {{template "weblistcss" .}}
-  {{template "weblistjs" .}}
-</head>
-<body>
-  {{template "header" .}}
-  <div id="content" class="source">
-    {{.HTMLBody}}
-  </div>
-  {{template "script" .}}
-  <script>viewer(new URL(window.location.href), null);</script>
-</body>
-</html>
-{{end}}
-
-{{define "plaintext" -}}
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>{{.Title}}</title>
-  {{template "css" .}}
-</head>
-<body>
-  {{template "header" .}}
-  <div id="content">
-    <pre>
-      {{.TextBody}}
-    </pre>
-  </div>
-  {{template "script" .}}
-  <script>viewer(new URL(window.location.href), null);</script>
-</body>
-</html>
-{{end}}
-
-{{define "flamegraph" -}}
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>{{.Title}}</title>
-  {{template "css" .}}
-  <style type="text/css">{{template "d3flamegraphcss" .}}</style>
-  <style type="text/css">
-    .flamegraph-content {
-      width: 90%;
-      min-width: 80%;
-      margin-left: 5%;
-    }
-    .flamegraph-details {
-      height: 1.2em;
-      width: 90%;
-      min-width: 90%;
-      margin-left: 5%;
-      padding: 15px 0 35px;
-    }
-  </style>
-</head>
-<body>
-  {{template "header" .}}
-  <div id="bodycontainer">
-    <div id="flamegraphdetails" class="flamegraph-details"></div>
-    <div class="flamegraph-content">
-      <div id="chart"></div>
-    </div>
-  </div>
-  {{template "script" .}}
-  <script>viewer(new URL(window.location.href), {{.Nodes}});</script>
-  <script>{{template "d3flamegraphscript" .}}</script>
-  <script>
-    var data = {{.FlameGraph}};
-
-    var width = document.getElementById('chart').clientWidth;
-
-    var flameGraph = flamegraph()
-      .width(width)
-      .cellHeight(18)
-      .minFrameSize(1)
-      .transitionDuration(750)
-      .inverted(true)
-      .sort(true)
-      .title('')
-      .tooltip(false)
-      .setDetailsElement(document.getElementById('flamegraphdetails'));
-
-    // <full name> (percentage, value)
-    flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')');
-
-    flameGraph.setColorHue('warm');
-
-    select('#chart')
-      .datum(data)
-      .call(flameGraph);
-
-    function clear() {
-      flameGraph.clear();
-    }
-
-    function resetZoom() {
-      flameGraph.resetZoom();
-    }
-
-    window.addEventListener('resize', function() {
-      var width = document.getElementById('chart').clientWidth;
-      var graphs = document.getElementsByClassName('d3-flame-graph');
-      if (graphs.length > 0) {
-        graphs[0].setAttribute('width', width);
-      }
-      flameGraph.width(width);
-      flameGraph.resetZoom();
-    }, true);
-
-    var search = document.getElementById('search');
-    var searchAlarm = null;
-
-    function selectMatching() {
-      searchAlarm = null;
-
-      if (search.value != '') {
-        flameGraph.search(search.value);
-      } else {
-        flameGraph.clear();
-      }
-    }
-
-    function handleSearch() {
-      // Delay expensive processing so a flurry of key strokes is handled once.
-      if (searchAlarm != null) {
-        clearTimeout(searchAlarm);
-      }
-      searchAlarm = setTimeout(selectMatching, 300);
-    }
-
-    search.addEventListener('input', handleSearch);
-  </script>
-</body>
-</html>
-{{end}}
-`))
+       // Load specified file.
+       loadFile := func(fname string) string {
+               data, err := embeddedFiles.ReadFile(fname)
+               if err != nil {
+                       fmt.Fprintf(os.Stderr, "internal/driver: embedded file %q not found\n",
+                               fname)
+                       os.Exit(1)
+               }
+               return string(data)
+       }
+       loadCSS := func(fname string) string {
+               return `<style type="text/css">` + "\n" + loadFile(fname) + `</style>` + "\n"
+       }
+       loadJS := func(fname string) string {
+               return `<script>` + "\n" + loadFile(fname) + `</script>` + "\n"
+       }
+
+       // Define a named template with specified contents.
+       def := func(name, contents string) {
+               sub := template.New(name)
+               template.Must(sub.Parse(contents))
+               template.Must(templates.AddParseTree(name, sub.Tree))
+       }
+
+       // Pre-packaged third-party files.
+       def("d3flamegraphscript", d3flamegraph.JSSource)
+       def("d3flamegraphcss", d3flamegraph.CSSSource)
+
+       // Embeded files.
+       def("css", loadCSS("html/common.css"))
+       def("header", loadFile("html/header.html"))
+       def("graph", loadFile("html/graph.html"))
+       def("script", loadJS("html/common.js"))
+       def("top", loadFile("html/top.html"))
+       def("sourcelisting", loadFile("html/source.html"))
+       def("plaintext", loadFile("html/plaintext.html"))
+       def("flamegraph", loadFile("html/flamegraph.html"))
 }
index 53325740a3ed8438fa5eb5d29681ab997e34da69..b5fcfbc3e463f98b1e33f1e7232b4ea9ccb539f3 100644 (file)
@@ -110,10 +110,15 @@ func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
                return false
        }
 
-       return v1.Unit == v2.Unit ||
-               (timeUnits.sniffUnit(v1.Unit) != nil && timeUnits.sniffUnit(v2.Unit) != nil) ||
-               (memoryUnits.sniffUnit(v1.Unit) != nil && memoryUnits.sniffUnit(v2.Unit) != nil) ||
-               (gcuUnits.sniffUnit(v1.Unit) != nil && gcuUnits.sniffUnit(v2.Unit) != nil)
+       if v1.Unit == v2.Unit {
+               return true
+       }
+       for _, ut := range unitTypes {
+               if ut.sniffUnit(v1.Unit) != nil && ut.sniffUnit(v2.Unit) != nil {
+                       return true
+               }
+       }
+       return false
 }
 
 // Scale a measurement from an unit to a different unit and returns
@@ -125,14 +130,10 @@ func Scale(value int64, fromUnit, toUnit string) (float64, string) {
                v, u := Scale(-value, fromUnit, toUnit)
                return -v, u
        }
-       if m, u, ok := memoryUnits.convertUnit(value, fromUnit, toUnit); ok {
-               return m, u
-       }
-       if t, u, ok := timeUnits.convertUnit(value, fromUnit, toUnit); ok {
-               return t, u
-       }
-       if g, u, ok := gcuUnits.convertUnit(value, fromUnit, toUnit); ok {
-               return g, u
+       for _, ut := range unitTypes {
+               if v, u, ok := ut.convertUnit(value, fromUnit, toUnit); ok {
+                       return v, u
+               }
        }
        // Skip non-interesting units.
        switch toUnit {
@@ -257,7 +258,7 @@ func (ut unitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (floa
        return v / toUnit.factor, toUnit.canonicalName, true
 }
 
-var memoryUnits = unitType{
+var unitTypes = []unitType{{
        units: []unit{
                {"B", []string{"b", "byte"}, 1},
                {"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)},
@@ -267,9 +268,7 @@ var memoryUnits = unitType{
                {"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)},
        },
        defaultUnit: unit{"B", []string{"b", "byte"}, 1},
-}
-
-var timeUnits = unitType{
+}, {
        units: []unit{
                {"ns", []string{"ns", "nanosecond"}, float64(time.Nanosecond)},
                {"us", []string{"μs", "us", "microsecond"}, float64(time.Microsecond)},
@@ -278,9 +277,7 @@ var timeUnits = unitType{
                {"hrs", []string{"hour", "hr"}, float64(time.Hour)},
        },
        defaultUnit: unit{"s", []string{}, float64(time.Second)},
-}
-
-var gcuUnits = unitType{
+}, {
        units: []unit{
                {"n*GCU", []string{"nanogcu"}, 1e-9},
                {"u*GCU", []string{"microgcu"}, 1e-6},
@@ -293,4 +290,4 @@ var gcuUnits = unitType{
                {"P*GCU", []string{"petagcu"}, 1e15},
        },
        defaultUnit: unit{"GCU", []string{}, 1.0},
-}
+}}
index 0c8f3bb5b71f45f6c9edc402c4a9fada3690b9f8..9ba9a77c924f82ce8799b866f996041a4728fa06 100644 (file)
@@ -295,11 +295,12 @@ func get64b(b []byte) (uint64, []byte) {
 //
 // The general format for profilez samples is a sequence of words in
 // binary format. The first words are a header with the following data:
-//   1st word -- 0
-//   2nd word -- 3
-//   3rd word -- 0 if a c++ application, 1 if a java application.
-//   4th word -- Sampling period (in microseconds).
-//   5th word -- Padding.
+//
+//     1st word -- 0
+//     2nd word -- 3
+//     3rd word -- 0 if a c++ application, 1 if a java application.
+//     4th word -- Sampling period (in microseconds).
+//     5th word -- Padding.
 func parseCPU(b []byte) (*Profile, error) {
        var parse func([]byte) (uint64, []byte)
        var n1, n2, n3, n4, n5 uint64
@@ -403,15 +404,18 @@ func cleanupDuplicateLocations(p *Profile) {
 //
 // profilez samples are a repeated sequence of stack frames of the
 // form:
-//    1st word -- The number of times this stack was encountered.
-//    2nd word -- The size of the stack (StackSize).
-//    3rd word -- The first address on the stack.
-//    ...
-//    StackSize + 2 -- The last address on the stack
+//
+//     1st word -- The number of times this stack was encountered.
+//     2nd word -- The size of the stack (StackSize).
+//     3rd word -- The first address on the stack.
+//     ...
+//     StackSize + 2 -- The last address on the stack
+//
 // The last stack trace is of the form:
-//   1st word -- 0
-//   2nd word -- 1
-//   3rd word -- 0
+//
+//     1st word -- 0
+//     2nd word -- 1
+//     3rd word -- 0
 //
 // Addresses from stack traces may point to the next instruction after
 // each call. Optionally adjust by -1 to land somewhere on the actual
index 94055d28461f2830b587fce76b3ff10fe9f3d430..eb84b6800784ed3a9420f0aafdfe65924aff8422 100644 (file)
@@ -25,7 +25,7 @@ A demonstration of building a custom D3 4.0 bundle using ES2015 modules and Roll
 
 ## Old version of d3-pprof
 
-A previous verison of d3-flame-graph bundled for pprof used Rollup instead of
+A previous version of d3-flame-graph bundled for pprof used Rollup instead of
 Webpack. This has now been migrated directly into this directory.
 
 The repository configuring Rollup was here:
index 7b9178f1bb09e0b2ad985a40a4758585f716506a..20d8a9982e27d8142d2e604e0f9a5bfecf3155ea 100644 (file)
@@ -1451,6 +1451,34 @@ func (ft *FixedType) goString(indent int, field string) string {
                ft.Base.goString(indent+2, "Base: "))
 }
 
+// BinaryFP is a binary floating-point type.
+type BinaryFP struct {
+       Bits int
+}
+
+func (bfp *BinaryFP) print(ps *printState) {
+       fmt.Fprintf(&ps.buf, "_Float%d", bfp.Bits)
+}
+
+func (bfp *BinaryFP) Traverse(fn func(AST) bool) {
+       fn(bfp)
+}
+
+func (bfp *BinaryFP) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+       if skip(bfp) {
+               return nil
+       }
+       return fn(bfp)
+}
+
+func (bfp *BinaryFP) GoString() string {
+       return bfp.goString(0, "")
+}
+
+func (bfp *BinaryFP) goString(indent int, field string) string {
+       return fmt.Sprintf("%*s%sBinaryFP: %d", indent, "", field, bfp.Bits)
+}
+
 // VectorType is a vector type.
 type VectorType struct {
        Dimension AST
@@ -2492,6 +2520,7 @@ func (u *Unary) print(ps *printState) {
        }
 
        if !u.Suffix {
+               isDelete := op != nil && (op.Name == "delete " || op.Name == "delete[] ")
                if op != nil && op.Name == "::" {
                        // Don't use parentheses after ::.
                        ps.print(expr)
@@ -2506,11 +2535,11 @@ func (u *Unary) print(ps *printState) {
                        ps.print(expr)
                        ps.writeByte(')')
                } else if ps.llvmStyle {
-                       if op == nil || op.Name != `operator"" ` {
+                       if op == nil || (op.Name != `operator"" ` && !isDelete) {
                                ps.writeByte('(')
                        }
                        ps.print(expr)
-                       if op == nil || op.Name != `operator"" ` {
+                       if op == nil || (op.Name != `operator"" ` && !isDelete) {
                                ps.writeByte(')')
                        }
                } else {
@@ -2653,6 +2682,9 @@ func (b *Binary) print(ps *printState) {
                case ".", "->":
                        skipBothParens = true
                        addSpaces = false
+               case "->*":
+                       skipParens = true
+                       addSpaces = false
                }
        }
 
@@ -3115,8 +3147,20 @@ type New struct {
 }
 
 func (n *New) print(ps *printState) {
-       // Op doesn't really matter for printing--we always print "new".
-       ps.writeString("new ")
+       if !ps.llvmStyle {
+               // Op doesn't really matter for printing--we always print "new".
+               ps.writeString("new ")
+       } else {
+               op, _ := n.Op.(*Operator)
+               if op != nil {
+                       ps.writeString(op.Name)
+                       if n.Place == nil {
+                               ps.writeByte(' ')
+                       }
+               } else {
+                       ps.print(n.Op)
+               }
+       }
        if n.Place != nil {
                parenthesize(ps, n.Place)
                ps.writeByte(' ')
index 9eec0aa3c83921792418b06ed6513d95ee2e6502..66ac7dde62a553f70519d8cd3141784f5bb1c9d6 100644 (file)
@@ -73,19 +73,26 @@ func ToString(name string, options ...Option) (string, error) {
 
        // Check for an old-style Rust mangled name.
        // It starts with _ZN and ends with "17h" followed by 16 hex digits
-       // followed by "E".
-       if strings.HasPrefix(name, "_ZN") && strings.HasSuffix(name, "E") && len(name) > 23 && name[len(name)-20:len(name)-17] == "17h" {
-               noRust := false
-               for _, o := range options {
-                       if o == NoRust {
-                               noRust = true
-                               break
+       // followed by "E" followed by an optional suffix starting with "."
+       // (which we ignore).
+       if strings.HasPrefix(name, "_ZN") {
+               rname := name
+               if pos := strings.LastIndex(rname, "E."); pos > 0 {
+                       rname = rname[:pos+1]
+               }
+               if strings.HasSuffix(rname, "E") && len(rname) > 23 && rname[len(rname)-20:len(rname)-17] == "17h" {
+                       noRust := false
+                       for _, o := range options {
+                               if o == NoRust {
+                                       noRust = true
+                                       break
+                               }
                        }
-               }
-               if !noRust {
-                       s, ok := oldRustToString(name, options)
-                       if ok {
-                               return s, nil
+                       if !noRust {
+                               s, ok := oldRustToString(rname, options)
+                               if ok {
+                                       return s, nil
+                               }
                        }
                }
        }
@@ -332,9 +339,11 @@ const (
        notForLocalName
 )
 
-// encoding ::= <(function) name> <bare-function-type>
-//              <(data) name>
-//              <special-name>
+// encoding parses:
+//
+//     encoding ::= <(function) name> <bare-function-type>
+//                  <(data) name>
+//                  <special-name>
 func (st *state) encoding(params bool, local forLocalNameType) AST {
        if len(st.str) < 1 {
                st.fail("expected encoding")
@@ -499,7 +508,9 @@ func isCDtorConversion(a AST) bool {
        }
 }
 
-// <tagged-name> ::= <name> B <source-name>
+// taggedName parses:
+//
+//     <tagged-name> ::= <name> B <source-name>
 func (st *state) taggedName(a AST) AST {
        for len(st.str) > 0 && st.str[0] == 'B' {
                st.advance(1)
@@ -509,16 +520,18 @@ func (st *state) taggedName(a AST) AST {
        return a
 }
 
-// <name> ::= <nested-name>
-//        ::= <unscoped-name>
-//        ::= <unscoped-template-name> <template-args>
-//        ::= <local-name>
+// name parses:
 //
-// <unscoped-name> ::= <unqualified-name>
-//                 ::= St <unqualified-name>
+//     <name> ::= <nested-name>
+//            ::= <unscoped-name>
+//            ::= <unscoped-template-name> <template-args>
+//            ::= <local-name>
 //
-// <unscoped-template-name> ::= <unscoped-name>
-//                          ::= <substitution>
+//     <unscoped-name> ::= <unqualified-name>
+//                     ::= St <unqualified-name>
+//
+//     <unscoped-template-name> ::= <unscoped-name>
+//                              ::= <substitution>
 func (st *state) name() AST {
        if len(st.str) < 1 {
                st.fail("expected name")
@@ -593,8 +606,10 @@ func (st *state) name() AST {
        }
 }
 
-// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix> <unqualified-name> E
-//               ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix> <template-args> E
+// nestedName parses:
+//
+//     <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix> <unqualified-name> E
+//                   ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix> <template-args> E
 func (st *state) nestedName() AST {
        st.checkChar('N')
        q := st.cvQualifiers()
@@ -610,19 +625,21 @@ func (st *state) nestedName() AST {
        return a
 }
 
-// <prefix> ::= <prefix> <unqualified-name>
-//          ::= <template-prefix> <template-args>
-//          ::= <template-param>
-//          ::= <decltype>
-//          ::=
-//          ::= <substitution>
+// prefix parses:
+//
+//     <prefix> ::= <prefix> <unqualified-name>
+//              ::= <template-prefix> <template-args>
+//              ::= <template-param>
+//              ::= <decltype>
+//              ::=
+//              ::= <substitution>
 //
-// <template-prefix> ::= <prefix> <(template) unqualified-name>
-//                   ::= <template-param>
-//                   ::= <substitution>
+//     <template-prefix> ::= <prefix> <(template) unqualified-name>
+//                       ::= <template-param>
+//                       ::= <substitution>
 //
-// <decltype> ::= Dt <expression> E
-//            ::= DT <expression> E
+//     <decltype> ::= Dt <expression> E
+//                ::= DT <expression> E
 func (st *state) prefix() AST {
        var a AST
 
@@ -777,12 +794,14 @@ func (st *state) prefix() AST {
        }
 }
 
-// <unqualified-name> ::= <operator-name>
-//                    ::= <ctor-dtor-name>
-//                    ::= <source-name>
-//                    ::= <local-source-name>
+// unqualifiedName parses:
 //
-//  <local-source-name>        ::= L <source-name> <discriminator>
+//     <unqualified-name> ::= <operator-name>
+//                        ::= <ctor-dtor-name>
+//                        ::= <source-name>
+//                        ::= <local-source-name>
+//
+//      <local-source-name>    ::= L <source-name> <discriminator>
 func (st *state) unqualifiedName() (r AST, isCast bool) {
        if len(st.str) < 1 {
                st.fail("expected unqualified name")
@@ -852,8 +871,10 @@ func (st *state) unqualifiedName() (r AST, isCast bool) {
        return a, isCast
 }
 
-// <source-name> ::= <(positive length) number> <identifier>
-// identifier ::= <(unqualified source code identifier)>
+// sourceName parses:
+//
+//     <source-name> ::= <(positive length) number> <identifier>
+//     identifier ::= <(unqualified source code identifier)>
 func (st *state) sourceName() AST {
        val := st.number()
        if val <= 0 {
@@ -880,7 +901,9 @@ func (st *state) sourceName() AST {
        return n
 }
 
-// number ::= [n] <(non-negative decimal integer)>
+// number parses:
+//
+//     number ::= [n] <(non-negative decimal integer)>
 func (st *state) number() int {
        neg := false
        if len(st.str) > 0 && st.str[0] == 'n' {
@@ -906,7 +929,9 @@ func (st *state) number() int {
        return val
 }
 
-// <seq-id> ::= <0-9A-Z>+
+// seqID parses:
+//
+//     <seq-id> ::= <0-9A-Z>+
 //
 // We expect this to be followed by an underscore.
 func (st *state) seqID(eofOK bool) int {
@@ -1030,9 +1055,11 @@ var operators = map[string]operator{
        "tw": {"throw ", 1},
 }
 
-// operator_name ::= many different two character encodings.
-//               ::= cv <type>
-//               ::= v <digit> <source-name>
+// operatorName parses:
+//
+//     operator_name ::= many different two character encodings.
+//                   ::= cv <type>
+//                   ::= v <digit> <source-name>
 //
 // We need to know whether we are in an expression because it affects
 // how we handle template parameters in the type of a cast operator.
@@ -1068,9 +1095,11 @@ func (st *state) operatorName(inExpression bool) (AST, int) {
        }
 }
 
-// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
-//              ::= Z <(function) encoding> E s [<discriminator>]
-//              ::= Z <(function) encoding> E d [<parameter> number>] _ <entity name>
+// localName parses:
+//
+//     <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
+//                  ::= Z <(function) encoding> E s [<discriminator>]
+//                  ::= Z <(function) encoding> E d [<parameter> number>] _ <entity name>
 func (st *state) localName() AST {
        st.checkChar('Z')
        fn := st.encoding(true, forLocalName)
@@ -1139,23 +1168,25 @@ func (st *state) javaResource() AST {
        return &Special{Prefix: "java resource ", Val: &Name{Name: final}}
 }
 
-// <special-name> ::= TV <type>
-//                ::= TT <type>
-//                ::= TI <type>
-//                ::= TS <type>
-//                ::= TA <template-arg>
-//                ::= GV <(object) name>
-//                ::= T <call-offset> <(base) encoding>
-//                ::= Tc <call-offset> <call-offset> <(base) encoding>
-// Also g++ extensions:
-//                ::= TC <type> <(offset) number> _ <(base) type>
-//                ::= TF <type>
-//                ::= TJ <type>
-//                ::= GR <name>
-//                ::= GA <encoding>
-//                ::= Gr <resource name>
-//                ::= GTt <encoding>
-//                ::= GTn <encoding>
+// specialName parses:
+//
+//     <special-name> ::= TV <type>
+//                    ::= TT <type>
+//                    ::= TI <type>
+//                    ::= TS <type>
+//                    ::= TA <template-arg>
+//                    ::= GV <(object) name>
+//                    ::= T <call-offset> <(base) encoding>
+//                    ::= Tc <call-offset> <call-offset> <(base) encoding>
+//     g++ extensions:
+//                    ::= TC <type> <(offset) number> _ <(base) type>
+//                    ::= TF <type>
+//                    ::= TJ <type>
+//                    ::= GR <name>
+//                    ::= GA <encoding>
+//                    ::= Gr <resource name>
+//                    ::= GTt <encoding>
+//                    ::= GTn <encoding>
 func (st *state) specialName() AST {
        if st.str[0] == 'T' {
                st.advance(1)
@@ -1268,12 +1299,14 @@ func (st *state) specialName() AST {
        }
 }
 
-// <call-offset> ::= h <nv-offset> _
-//               ::= v <v-offset> _
+// callOffset parses:
+//
+//     <call-offset> ::= h <nv-offset> _
+//                   ::= v <v-offset> _
 //
-// <nv-offset> ::= <(offset) number>
+//     <nv-offset> ::= <(offset) number>
 //
-// <v-offset> ::= <(offset) number> _ <(virtual offset) number>
+//     <v-offset> ::= <(offset) number> _ <(virtual offset) number>
 //
 // The c parameter, if not 0, is a character we just read which is the
 // start of the <call-offset>.
@@ -1331,24 +1364,26 @@ var builtinTypes = map[byte]string{
        'z': "...",
 }
 
-// <type> ::= <builtin-type>
-//        ::= <function-type>
-//        ::= <class-enum-type>
-//        ::= <array-type>
-//        ::= <pointer-to-member-type>
-//        ::= <template-param>
-//        ::= <template-template-param> <template-args>
-//        ::= <substitution>
-//        ::= <CV-qualifiers> <type>
-//        ::= P <type>
-//        ::= R <type>
-//        ::= O <type> (C++0x)
-//        ::= C <type>
-//        ::= G <type>
-//        ::= U <source-name> <type>
+// demangleType parses:
+//
+//     <type> ::= <builtin-type>
+//            ::= <function-type>
+//            ::= <class-enum-type>
+//            ::= <array-type>
+//            ::= <pointer-to-member-type>
+//            ::= <template-param>
+//            ::= <template-template-param> <template-args>
+//            ::= <substitution>
+//            ::= <CV-qualifiers> <type>
+//            ::= P <type>
+//            ::= R <type>
+//            ::= O <type> (C++0x)
+//            ::= C <type>
+//            ::= G <type>
+//            ::= U <source-name> <type>
 //
-// <builtin-type> ::= various one letter codes
-//                ::= u <source-name>
+//     <builtin-type> ::= various one letter codes
+//                    ::= u <source-name>
 func (st *state) demangleType(isCast bool) AST {
        if len(st.str) == 0 {
                st.fail("expected type")
@@ -1544,24 +1579,32 @@ func (st *state) demangleType(isCast bool) AST {
 
                case 'F':
                        accum := false
+                       bits := 0
                        if len(st.str) > 0 && isDigit(st.str[0]) {
                                accum = true
-                               // We don't care about the bits.
-                               _ = st.number()
+                               bits = st.number()
                        }
-                       base := st.demangleType(isCast)
-                       if len(st.str) > 0 && isDigit(st.str[0]) {
-                               // We don't care about the bits.
-                               st.number()
-                       }
-                       sat := false
-                       if len(st.str) > 0 {
-                               if st.str[0] == 's' {
-                                       sat = true
+                       if len(st.str) > 0 && st.str[0] == '_' {
+                               if bits == 0 {
+                                       st.fail("expected non-zero number of bits")
                                }
                                st.advance(1)
+                               ret = &BinaryFP{Bits: bits}
+                       } else {
+                               base := st.demangleType(isCast)
+                               if len(st.str) > 0 && isDigit(st.str[0]) {
+                                       // We don't care about the bits.
+                                       st.number()
+                               }
+                               sat := false
+                               if len(st.str) > 0 {
+                                       if st.str[0] == 's' {
+                                               sat = true
+                                       }
+                                       st.advance(1)
+                               }
+                               ret = &FixedType{Base: base, Accum: accum, Sat: sat}
                        }
-                       ret = &FixedType{Base: base, Accum: accum, Sat: sat}
 
                case 'v':
                        ret = st.vectorType(isCast)
@@ -1615,25 +1658,25 @@ func (st *state) demangleType(isCast bool) AST {
 // is if there is another set of template-args immediately after this
 // set.  That would look like this:
 //
-// <nested-name>
-// -> <template-prefix> <template-args>
-// -> <prefix> <template-unqualified-name> <template-args>
-// -> <unqualified-name> <template-unqualified-name> <template-args>
-// -> <source-name> <template-unqualified-name> <template-args>
-// -> <source-name> <operator-name> <template-args>
-// -> <source-name> cv <type> <template-args>
-// -> <source-name> cv <template-template-param> <template-args> <template-args>
+//     <nested-name>
+//     -> <template-prefix> <template-args>
+//     -> <prefix> <template-unqualified-name> <template-args>
+//     -> <unqualified-name> <template-unqualified-name> <template-args>
+//     -> <source-name> <template-unqualified-name> <template-args>
+//     -> <source-name> <operator-name> <template-args>
+//     -> <source-name> cv <type> <template-args>
+//     -> <source-name> cv <template-template-param> <template-args> <template-args>
 //
 // Otherwise, we have this derivation:
 //
-// <nested-name>
-// -> <template-prefix> <template-args>
-// -> <prefix> <template-unqualified-name> <template-args>
-// -> <unqualified-name> <template-unqualified-name> <template-args>
-// -> <source-name> <template-unqualified-name> <template-args>
-// -> <source-name> <operator-name> <template-args>
-// -> <source-name> cv <type> <template-args>
-// -> <source-name> cv <template-param> <template-args>
+//     <nested-name>
+//     -> <template-prefix> <template-args>
+//     -> <prefix> <template-unqualified-name> <template-args>
+//     -> <unqualified-name> <template-unqualified-name> <template-args>
+//     -> <source-name> <template-unqualified-name> <template-args>
+//     -> <source-name> <operator-name> <template-args>
+//     -> <source-name> cv <type> <template-args>
+//     -> <source-name> cv <template-param> <template-args>
 //
 // in which the template-args are actually part of the prefix.  For
 // the special case where this arises, demangleType is called with
@@ -1710,7 +1753,9 @@ var qualifiers = map[byte]string{
        'K': "const",
 }
 
-// <CV-qualifiers> ::= [r] [V] [K]
+// cvQualifiers parses:
+//
+//     <CV-qualifiers> ::= [r] [V] [K]
 func (st *state) cvQualifiers() AST {
        var q []AST
 qualLoop:
@@ -1758,8 +1803,10 @@ qualLoop:
        return &Qualifiers{Qualifiers: q}
 }
 
-// <ref-qualifier> ::= R
-//                 ::= O
+// refQualifier parses:
+//
+//     <ref-qualifier> ::= R
+//                     ::= O
 func (st *state) refQualifier() string {
        if len(st.str) > 0 {
                switch st.str[0] {
@@ -1774,7 +1821,9 @@ func (st *state) refQualifier() string {
        return ""
 }
 
-// <type>+
+// parmlist parses:
+//
+//     <type>+
 func (st *state) parmlist() []AST {
        var ret []AST
        for {
@@ -1809,7 +1858,9 @@ func (st *state) parmlist() []AST {
        return ret
 }
 
-// <function-type> ::= F [Y] <bare-function-type> [<ref-qualifier>] E
+// functionType parses:
+//
+//     <function-type> ::= F [Y] <bare-function-type> [<ref-qualifier>] E
 func (st *state) functionType() AST {
        st.checkChar('F')
        if len(st.str) > 0 && st.str[0] == 'Y' {
@@ -1828,7 +1879,9 @@ func (st *state) functionType() AST {
        return ret
 }
 
-// <bare-function-type> ::= [J]<type>+
+// bareFunctionType parses:
+//
+//     <bare-function-type> ::= [J]<type>+
 func (st *state) bareFunctionType(hasReturnType bool) AST {
        if len(st.str) > 0 && st.str[0] == 'J' {
                hasReturnType = true
@@ -1846,8 +1899,10 @@ func (st *state) bareFunctionType(hasReturnType bool) AST {
        }
 }
 
-// <array-type> ::= A <(positive dimension) number> _ <(element) type>
-//              ::= A [<(dimension) expression>] _ <(element) type>
+// arrayType parses:
+//
+//     <array-type> ::= A <(positive dimension) number> _ <(element) type>
+//                  ::= A [<(dimension) expression>] _ <(element) type>
 func (st *state) arrayType(isCast bool) AST {
        st.checkChar('A')
 
@@ -1887,8 +1942,10 @@ func (st *state) arrayType(isCast bool) AST {
        return arr
 }
 
-// <vector-type> ::= Dv <number> _ <type>
-//               ::= Dv _ <expression> _ <type>
+// vectorType parses:
+//
+//     <vector-type> ::= Dv <number> _ <type>
+//                   ::= Dv _ <expression> _ <type>
 func (st *state) vectorType(isCast bool) AST {
        if len(st.str) == 0 {
                st.fail("expected vector dimension")
@@ -1913,7 +1970,9 @@ func (st *state) vectorType(isCast bool) AST {
        return &VectorType{Dimension: dim, Base: t}
 }
 
-// <pointer-to-member-type> ::= M <(class) type> <(member) type>
+// pointerToMemberType parses:
+//
+//     <pointer-to-member-type> ::= M <(class) type> <(member) type>
 func (st *state) pointerToMemberType(isCast bool) AST {
        st.checkChar('M')
        cl := st.demangleType(false)
@@ -1939,7 +1998,9 @@ func (st *state) pointerToMemberType(isCast bool) AST {
        return &PtrMem{Class: cl, Member: mem}
 }
 
-// <non-negative number> _ */
+// compactNumber parses:
+//
+//     <non-negative number> _
 func (st *state) compactNumber() int {
        if len(st.str) == 0 {
                st.fail("missing index")
@@ -1958,10 +2019,12 @@ func (st *state) compactNumber() int {
        return n + 1
 }
 
-// <template-param> ::= T_
-//                  ::= T <(parameter-2 non-negative) number> _
-//                  ::= TL <level-1> __
-//                  ::= TL <level-1> _ <parameter-2 non-negative number> _
+// templateParam parses:
+//
+//     <template-param> ::= T_
+//                      ::= T <(parameter-2 non-negative) number> _
+//                      ::= TL <level-1> __
+//                      ::= TL <level-1> _ <parameter-2 non-negative number> _
 //
 // When a template parameter is a substitution candidate, any
 // reference to that substitution refers to the template parameter
@@ -2058,7 +2121,9 @@ func (st *state) clearTemplateArgs(args []AST) {
        }
 }
 
-// <template-args> ::= I <template-arg>+ E
+// templateArgs parses:
+//
+//     <template-args> ::= I <template-arg>+ E
 func (st *state) templateArgs() []AST {
        if len(st.str) == 0 || (st.str[0] != 'I' && st.str[0] != 'J') {
                panic("internal error")
@@ -2074,9 +2139,11 @@ func (st *state) templateArgs() []AST {
        return ret
 }
 
-// <template-arg> ::= <type>
-//                ::= X <expression> E
-//                ::= <expr-primary>
+// templateArg parses:
+//
+//     <template-arg> ::= <type>
+//                    ::= X <expression> E
+//                    ::= <expr-primary>
 func (st *state) templateArg() AST {
        if len(st.str) == 0 {
                st.fail("missing template argument")
@@ -2122,66 +2189,67 @@ func (st *state) exprList(stop byte) AST {
        return &ExprList{Exprs: exprs}
 }
 
-// <expression> ::= <(unary) operator-name> <expression>
-//              ::= <(binary) operator-name> <expression> <expression>
-//              ::= <(trinary) operator-name> <expression> <expression> <expression>
-//              ::= pp_ <expression>
-//              ::= mm_ <expression>
-//              ::= cl <expression>+ E
-//              ::= cl <expression>+ E
-//              ::= cv <type> <expression>
-//              ::= cv <type> _ <expression>* E
-//              ::= tl <type> <braced-expression>* E
-//              ::= il <braced-expression>* E
-//              ::= [gs] nw <expression>* _ <type> E
-//              ::= [gs] nw <expression>* _ <type> <initializer>
-//              ::= [gs] na <expression>* _ <type> E
-//              ::= [gs] na <expression>* _ <type> <initializer>
-//              ::= [gs] dl <expression>
-//              ::= [gs] da <expression>
-//              ::= dc <type> <expression>
-//              ::= sc <type> <expression>
-//              ::= cc <type> <expression>
-//              ::= mc <parameter type> <expr> [<offset number>] E
-//              ::= rc <type> <expression>
-//              ::= ti <type>
-//              ::= te <expression>
-//              ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E
-//              ::= st <type>
-//              ::= sz <expression>
-//              ::= at <type>
-//              ::= az <expression>
-//              ::= nx <expression>
-//              ::= <template-param>
-//              ::= <function-param>
-//              ::= dt <expression> <unresolved-name>
-//              ::= pt <expression> <unresolved-name>
-//              ::= ds <expression> <expression>
-//              ::= sZ <template-param>
-//              ::= sZ <function-param>
-//              ::= sP <template-arg>* E
-//              ::= sp <expression>
-//              ::= fl <binary operator-name> <expression>
-//              ::= fr <binary operator-name> <expression>
-//              ::= fL <binary operator-name> <expression> <expression>
-//              ::= fR <binary operator-name> <expression> <expression>
-//              ::= tw <expression>
-//              ::= tr
-//              ::= u <source-name> <template-arg>* E
-//              ::= <unresolved-name>
-//              ::= <expr-primary>
+// expression parses:
 //
-// <function-param> ::= fp <CV-qualifiers> _
-//                  ::= fp <CV-qualifiers> <number>
-//                  ::= fL <number> p <CV-qualifiers> _
-//                  ::= fL <number> p <CV-qualifiers> <number>
-//                  ::= fpT
+//     <expression> ::= <(unary) operator-name> <expression>
+//                  ::= <(binary) operator-name> <expression> <expression>
+//                  ::= <(trinary) operator-name> <expression> <expression> <expression>
+//                  ::= pp_ <expression>
+//                  ::= mm_ <expression>
+//                  ::= cl <expression>+ E
+//                  ::= cl <expression>+ E
+//                  ::= cv <type> <expression>
+//                  ::= cv <type> _ <expression>* E
+//                  ::= tl <type> <braced-expression>* E
+//                  ::= il <braced-expression>* E
+//                  ::= [gs] nw <expression>* _ <type> E
+//                  ::= [gs] nw <expression>* _ <type> <initializer>
+//                  ::= [gs] na <expression>* _ <type> E
+//                  ::= [gs] na <expression>* _ <type> <initializer>
+//                  ::= [gs] dl <expression>
+//                  ::= [gs] da <expression>
+//                  ::= dc <type> <expression>
+//                  ::= sc <type> <expression>
+//                  ::= cc <type> <expression>
+//                  ::= mc <parameter type> <expr> [<offset number>] E
+//                  ::= rc <type> <expression>
+//                  ::= ti <type>
+//                  ::= te <expression>
+//                  ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E
+//                  ::= st <type>
+//                  ::= sz <expression>
+//                  ::= at <type>
+//                  ::= az <expression>
+//                  ::= nx <expression>
+//                  ::= <template-param>
+//                  ::= <function-param>
+//                  ::= dt <expression> <unresolved-name>
+//                  ::= pt <expression> <unresolved-name>
+//                  ::= ds <expression> <expression>
+//                  ::= sZ <template-param>
+//                  ::= sZ <function-param>
+//                  ::= sP <template-arg>* E
+//                  ::= sp <expression>
+//                  ::= fl <binary operator-name> <expression>
+//                  ::= fr <binary operator-name> <expression>
+//                  ::= fL <binary operator-name> <expression> <expression>
+//                  ::= fR <binary operator-name> <expression> <expression>
+//                  ::= tw <expression>
+//                  ::= tr
+//                  ::= u <source-name> <template-arg>* E
+//                  ::= <unresolved-name>
+//                  ::= <expr-primary>
 //
-// <braced-expression> ::= <expression>
-//                     ::= di <field source-name> <braced-expression>
-//                     ::= dx <index expression> <braced-expression>
-//                     ::= dX <range begin expression> <range end expression> <braced-expression>
+//     <function-param> ::= fp <CV-qualifiers> _
+//                      ::= fp <CV-qualifiers> <number>
+//                      ::= fL <number> p <CV-qualifiers> _
+//                      ::= fL <number> p <CV-qualifiers> <number>
+//                      ::= fpT
 //
+//     <braced-expression> ::= <expression>
+//                         ::= di <field source-name> <braced-expression>
+//                         ::= dx <index expression> <braced-expression>
+//                         ::= dX <range begin expression> <range end expression> <braced-expression>
 func (st *state) expression() AST {
        if len(st.str) == 0 {
                st.fail("expected expression")
@@ -2426,8 +2494,10 @@ func (st *state) expression() AST {
        }
 }
 
-// <expression> ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E
-// <union-selector> ::= _ [<number>]
+// subobject parses:
+//
+//     <expression> ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E
+//     <union-selector> ::= _ [<number>]
 func (st *state) subobject() AST {
        typ := st.demangleType(false)
        expr := st.expression()
@@ -2462,10 +2532,12 @@ func (st *state) subobject() AST {
        }
 }
 
-// <unresolved-name> ::= [gs] <base-unresolved-name>
-//                   ::= sr <unresolved-type> <base-unresolved-name>
-//                   ::= srN <unresolved-type> <unresolved-qualifier-level>+ E <base-unresolved-name>
-//                   ::= [gs] sr <unresolved-qualifier-level>+ E <base-unresolved-name>
+// unresolvedName parses:
+//
+//     <unresolved-name> ::= [gs] <base-unresolved-name>
+//                       ::= sr <unresolved-type> <base-unresolved-name>
+//                       ::= srN <unresolved-type> <unresolved-qualifier-level>+ E <base-unresolved-name>
+//                       ::= [gs] sr <unresolved-qualifier-level>+ E <base-unresolved-name>
 func (st *state) unresolvedName() AST {
        if len(st.str) >= 2 && st.str[:2] == "gs" {
                st.advance(2)
@@ -2538,12 +2610,14 @@ func (st *state) unresolvedName() AST {
        }
 }
 
-// <base-unresolved-name> ::= <simple-id>
-//                        ::= on <operator-name>
-//                        ::= on <operator-name> <template-args>
-//                        ::= dn <destructor-name>
+// baseUnresolvedName parses:
+//
+//     <base-unresolved-name> ::= <simple-id>
+//                            ::= on <operator-name>
+//                            ::= on <operator-name> <template-args>
+//                            ::= dn <destructor-name>
 //
-//<simple-id> ::= <source-name> [ <template-args> ]
+//     <simple-id> ::= <source-name> [ <template-args> ]
 func (st *state) baseUnresolvedName() AST {
        var n AST
        if len(st.str) >= 2 && st.str[:2] == "on" {
@@ -2572,9 +2646,11 @@ func (st *state) baseUnresolvedName() AST {
        return n
 }
 
-// <expr-primary> ::= L <type> <(value) number> E
-//                ::= L <type> <(value) float> E
-//                ::= L <mangled-name> E
+// exprPrimary parses:
+//
+//     <expr-primary> ::= L <type> <(value) number> E
+//                    ::= L <type> <(value) float> E
+//                    ::= L <mangled-name> E
 func (st *state) exprPrimary() AST {
        st.checkChar('L')
        if len(st.str) == 0 {
@@ -2642,8 +2718,10 @@ func (st *state) exprPrimary() AST {
        return ret
 }
 
-// <discriminator> ::= _ <(non-negative) number> (when number < 10)
-//                     __ <(non-negative) number> _ (when number >= 10)
+// discriminator parses:
+//
+//     <discriminator> ::= _ <(non-negative) number> (when number < 10)
+//                         __ <(non-negative) number> _ (when number >= 10)
 func (st *state) discriminator(a AST) AST {
        if len(st.str) == 0 || st.str[0] != '_' {
                // clang can generate a discriminator at the end of
@@ -2679,8 +2757,10 @@ func (st *state) discriminator(a AST) AST {
        return a
 }
 
-// <closure-type-name> ::= Ul <lambda-sig> E [ <nonnegative number> ] _
-// <lambda-sig> ::= <parameter type>+
+// closureTypeName parses:
+//
+//     <closure-type-name> ::= Ul <lambda-sig> E [ <nonnegative number> ] _
+//     <lambda-sig> ::= <parameter type>+
 func (st *state) closureTypeName() AST {
        st.checkChar('U')
        st.checkChar('l')
@@ -2721,10 +2801,12 @@ func (st *state) closureTypeName() AST {
        return &Closure{TemplateArgs: templateArgs, Types: types, Num: num}
 }
 
-// <template-param-decl> ::= Ty                          # type parameter
-//                       ::= Tn <type>                   # non-type parameter
-//                       ::= Tt <template-param-decl>* E # template parameter
-//                       ::= Tp <template-param-decl>    # parameter pack
+// templateParamDecl parses:
+//
+//     <template-param-decl> ::= Ty                          # type parameter
+//                           ::= Tn <type>                   # non-type parameter
+//                           ::= Tt <template-param-decl>* E # template parameter
+//                           ::= Tp <template-param-decl>    # parameter pack
 //
 // Returns the new AST to include in the AST we are building and the
 // new AST to add to the list of template parameters.
@@ -2807,7 +2889,9 @@ func (st *state) templateParamDecl() (AST, AST) {
        }
 }
 
-// <unnamed-type-name> ::= Ut [ <nonnegative number> ] _
+// unnamedTypeName parses:
+//
+//     <unnamed-type-name> ::= Ut [ <nonnegative number> ] _
 func (st *state) unnamedTypeName() AST {
        st.checkChar('U')
        st.checkChar('t')
@@ -2821,9 +2905,9 @@ func (st *state) unnamedTypeName() AST {
 // but are added by GCC when cloning functions.
 func (st *state) cloneSuffix(a AST) AST {
        i := 0
-       if len(st.str) > 1 && st.str[0] == '.' && (isLower(st.str[1]) || st.str[1] == '_') {
+       if len(st.str) > 1 && st.str[0] == '.' && (isLower(st.str[1]) || isDigit(st.str[1]) || st.str[1] == '_') {
                i += 2
-               for len(st.str) > i && (isLower(st.str[i]) || st.str[i] == '_') {
+               for len(st.str) > i && (isLower(st.str[i]) || isDigit(st.str[i]) || st.str[i] == '_') {
                        i++
                }
        }
@@ -2903,15 +2987,17 @@ var verboseAST = map[byte]AST{
                                Args: []AST{&BuiltinType{Name: "char"}}}}},
 }
 
-// <substitution> ::= S <seq-id> _
-//                ::= S_
-//                ::= St
-//                ::= Sa
-//                ::= Sb
-//                ::= Ss
-//                ::= Si
-//                ::= So
-//                ::= Sd
+// substitution parses:
+//
+//     <substitution> ::= S <seq-id> _
+//                    ::= S_
+//                    ::= St
+//                    ::= Sa
+//                    ::= Sb
+//                    ::= Ss
+//                    ::= Si
+//                    ::= So
+//                    ::= Sd
 func (st *state) substitution(forPrefix bool) AST {
        st.checkChar('S')
        if len(st.str) == 0 {
index 140b631644250ab1caf8485679c0fdd3ca3a562a..39792189acc5c341541b7bd2c4fe1173a08683df 100644 (file)
@@ -47,10 +47,19 @@ func rustToString(name string, options []Option) (ret string, err error) {
        }
 
        if suffix != "" {
-               rst.skip = false
-               rst.writeString(" (")
-               rst.writeString(suffix)
-               rst.writeByte(')')
+               llvmStyle := false
+               for _, o := range options {
+                       if o == LLVMStyle {
+                               llvmStyle = true
+                               break
+                       }
+               }
+               if llvmStyle {
+                       rst.skip = false
+                       rst.writeString(" (")
+                       rst.writeString(suffix)
+                       rst.writeByte(')')
+               }
        }
 
        return rst.buf.String(), nil
@@ -110,8 +119,10 @@ func (rst *rustState) writeString(s string) {
        }
 }
 
-// <symbol-name> = "_R" [<decimal-number>] <path> [<instantiating-crate>]
-// <instantiating-crate> = <path>
+// symbolName parses:
+//
+//     <symbol-name> = "_R" [<decimal-number>] <path> [<instantiating-crate>]
+//     <instantiating-crate> = <path>
 //
 // We've already skipped the "_R".
 func (rst *rustState) symbolName() {
@@ -131,17 +142,19 @@ func (rst *rustState) symbolName() {
        }
 }
 
-// <path> = "C" <identifier>                    // crate root
-//        | "M" <impl-path> <type>              // <T> (inherent impl)
-//        | "X" <impl-path> <type> <path>       // <T as Trait> (trait impl)
-//        | "Y" <type> <path>                   // <T as Trait> (trait definition)
-//        | "N" <namespace> <path> <identifier> // ...::ident (nested path)
-//        | "I" <path> {<generic-arg>} "E"      // ...<T, U> (generic args)
-//        | <backref>
-// <namespace> = "C"      // closure
-//             | "S"      // shim
-//             | <A-Z>    // other special namespaces
-//             | <a-z>    // internal namespaces
+// path parses:
+//
+//     <path> = "C" <identifier>                    // crate root
+//            | "M" <impl-path> <type>              // <T> (inherent impl)
+//            | "X" <impl-path> <type> <path>       // <T as Trait> (trait impl)
+//            | "Y" <type> <path>                   // <T as Trait> (trait definition)
+//            | "N" <namespace> <path> <identifier> // ...::ident (nested path)
+//            | "I" <path> {<generic-arg>} "E"      // ...<T, U> (generic args)
+//            | <backref>
+//     <namespace> = "C"      // closure
+//                 | "S"      // shim
+//                 | <A-Z>    // other special namespaces
+//                 | <a-z>    // internal namespaces
 //
 // needsSeparator is true if we need to write out :: for a generic;
 // it is passed as false if we are in the middle of a type.
@@ -237,7 +250,9 @@ func (rst *rustState) path(needsSeparator bool) {
        }
 }
 
-// <impl-path> = [<disambiguator>] <path>
+// implPath parses:
+//
+//     <impl-path> = [<disambiguator>] <path>
 func (rst *rustState) implPath() {
        // This path is not part of the demangled string.
        hold := rst.skip
@@ -250,16 +265,20 @@ func (rst *rustState) implPath() {
        rst.path(false)
 }
 
-// <identifier> = [<disambiguator>] <undisambiguated-identifier>
-// Returns the disambiguator and the identifier.
+// identifier parses:
+//
+//     <identifier> = [<disambiguator>] <undisambiguated-identifier>
+//
+// It returns the disambiguator and the identifier.
 func (rst *rustState) identifier() (int64, string) {
        dis := rst.disambiguator()
-       ident := rst.undisambiguatedIdentifier()
+       ident, _ := rst.undisambiguatedIdentifier()
        return dis, ident
 }
 
-// <disambiguator> = "s" <base-62-number>
-// This is optional.
+// disambiguator parses an optional:
+//
+//     <disambiguator> = "s" <base-62-number>
 func (rst *rustState) disambiguator() int64 {
        if len(rst.str) == 0 || rst.str[0] != 's' {
                return 0
@@ -268,12 +287,14 @@ func (rst *rustState) disambiguator() int64 {
        return rst.base62Number() + 1
 }
 
-// <undisambiguated-identifier> = ["u"] <decimal-number> ["_"] <bytes>
-func (rst *rustState) undisambiguatedIdentifier() string {
-       punycode := false
+// undisambiguatedIdentifier parses:
+//
+//     <undisambiguated-identifier> = ["u"] <decimal-number> ["_"] <bytes>
+func (rst *rustState) undisambiguatedIdentifier() (id string, isPunycode bool) {
+       isPunycode = false
        if len(rst.str) > 0 && rst.str[0] == 'u' {
                rst.advance(1)
-               punycode = true
+               isPunycode = true
        }
 
        val := rst.decimalNumber()
@@ -285,7 +306,7 @@ func (rst *rustState) undisambiguatedIdentifier() string {
        if len(rst.str) < val {
                rst.fail("not enough characters for identifier")
        }
-       id := rst.str[:val]
+       id = rst.str[:val]
        rst.advance(val)
 
        for i := 0; i < len(id); i++ {
@@ -300,11 +321,11 @@ func (rst *rustState) undisambiguatedIdentifier() string {
                }
        }
 
-       if punycode {
+       if isPunycode {
                id = rst.expandPunycode(id)
        }
 
-       return id
+       return id, isPunycode
 }
 
 // expandPunycode decodes the Rust version of punycode.
@@ -320,14 +341,18 @@ func (rst *rustState) expandPunycode(s string) string {
                initialN    = 128
        )
 
+       var (
+               output   []rune
+               encoding string
+       )
        idx := strings.LastIndex(s, "_")
-       if idx < 0 {
-               rst.fail("missing underscore in punycode string")
+       if idx >= 0 {
+               output = []rune(s[:idx])
+               encoding = s[idx+1:]
+       } else {
+               encoding = s
        }
 
-       output := []rune(s[:idx])
-       encoding := s[idx+1:]
-
        i := 0
        n := initialN
        bias := initialBias
@@ -398,6 +423,8 @@ func (rst *rustState) expandPunycode(s string) string {
                n += i / (len(output) + 1)
                if n > utf8.MaxRune {
                        rst.fail("punycode rune overflow")
+               } else if !utf8.ValidRune(rune(n)) {
+                       rst.fail("punycode invalid code point")
                }
                i %= len(output) + 1
                output = append(output, 0)
@@ -409,10 +436,12 @@ func (rst *rustState) expandPunycode(s string) string {
        return string(output)
 }
 
-// <generic-arg> = <lifetime>
-//               | <type>
-//               | "K" <const> // forward-compat for const generics
-// <lifetime> = "L" <base-62-number>
+// genericArg parses:
+//
+//     <generic-arg> = <lifetime>
+//                   | <type>
+//                   | "K" <const> // forward-compat for const generics
+//     <lifetime> = "L" <base-62-number>
 func (rst *rustState) genericArg() {
        if len(rst.str) < 1 {
                rst.fail("expected generic-arg")
@@ -428,8 +457,9 @@ func (rst *rustState) genericArg() {
        }
 }
 
-// <binder> = "G" <base-62-number>
-// This is optional.
+// binder parses an optional:
+//
+//     <binder> = "G" <base-62-number>
 func (rst *rustState) binder() {
        if len(rst.str) < 1 || rst.str[0] != 'G' {
                return
@@ -454,18 +484,20 @@ func (rst *rustState) binder() {
        rst.writeString("> ")
 }
 
-// <type> = <basic-type>
-//        | <path>                      // named type
-//        | "A" <type> <const>          // [T; N]
-//        | "S" <type>                  // [T]
-//        | "T" {<type>} "E"            // (T1, T2, T3, ...)
-//        | "R" [<lifetime>] <type>     // &T
-//        | "Q" [<lifetime>] <type>     // &mut T
-//        | "P" <type>                  // *const T
-//        | "O" <type>                  // *mut T
-//        | "F" <fn-sig>                // fn(...) -> ...
-//        | "D" <dyn-bounds> <lifetime> // dyn Trait<Assoc = X> + Send + 'a
-//        | <backref>
+// demangleType parses:
+//
+//     <type> = <basic-type>
+//            | <path>                      // named type
+//            | "A" <type> <const>          // [T; N]
+//            | "S" <type>                  // [T]
+//            | "T" {<type>} "E"            // (T1, T2, T3, ...)
+//            | "R" [<lifetime>] <type>     // &T
+//            | "Q" [<lifetime>] <type>     // &mut T
+//            | "P" <type>                  // *const T
+//            | "O" <type>                  // *mut T
+//            | "F" <fn-sig>                // fn(...) -> ...
+//            | "D" <dyn-bounds> <lifetime> // dyn Trait<Assoc = X> + Send + 'a
+//            | <backref>
 func (rst *rustState) demangleType() {
        if len(rst.str) < 1 {
                rst.fail("expected type")
@@ -577,7 +609,9 @@ var rustBasicTypes = map[byte]string{
        'z': "!",
 }
 
-// <basic-type>
+// basicType parses:
+//
+//     <basic-type>
 func (rst *rustState) basicType() {
        if len(rst.str) < 1 {
                rst.fail("expected basic type")
@@ -590,9 +624,11 @@ func (rst *rustState) basicType() {
        rst.writeString(str)
 }
 
-// <fn-sig> = [<binder>] ["U"] ["K" <abi>] {<type>} "E" <type>
-// <abi> = "C"
-//       | <undisambiguated-identifier>
+// fnSig parses:
+//
+//     <fn-sig> = [<binder>] ["U"] ["K" <abi>] {<type>} "E" <type>
+//     <abi> = "C"
+//           | <undisambiguated-identifier>
 func (rst *rustState) fnSig() {
        rst.binder()
        if len(rst.str) > 0 && rst.str[0] == 'U' {
@@ -606,7 +642,10 @@ func (rst *rustState) fnSig() {
                        rst.writeString(`extern "C" `)
                } else {
                        rst.writeString(`extern "`)
-                       id := rst.undisambiguatedIdentifier()
+                       id, isPunycode := rst.undisambiguatedIdentifier()
+                       if isPunycode {
+                               rst.fail("punycode used in ABI string")
+                       }
                        id = strings.ReplaceAll(id, "_", "-")
                        rst.writeString(id)
                        rst.writeString(`" `)
@@ -632,7 +671,9 @@ func (rst *rustState) fnSig() {
        }
 }
 
-// <dyn-bounds> = [<binder>] {<dyn-trait>} "E"
+// dynBounds parses:
+//
+//     <dyn-bounds> = [<binder>] {<dyn-trait>} "E"
 func (rst *rustState) dynBounds() {
        rst.writeString("dyn ")
        rst.binder()
@@ -648,8 +689,10 @@ func (rst *rustState) dynBounds() {
        rst.checkChar('E')
 }
 
-// <dyn-trait> = <path> {<dyn-trait-assoc-binding>}
-// <dyn-trait-assoc-binding> = "p" <undisambiguated-identifier> <type>
+// dynTrait parses:
+//
+//     <dyn-trait> = <path> {<dyn-trait-assoc-binding>}
+//     <dyn-trait-assoc-binding> = "p" <undisambiguated-identifier> <type>
 func (rst *rustState) dynTrait() {
        started := rst.pathStartGenerics()
        for len(rst.str) > 0 && rst.str[0] == 'p' {
@@ -660,7 +703,8 @@ func (rst *rustState) dynTrait() {
                        rst.writeByte('<')
                        started = true
                }
-               rst.writeString(rst.undisambiguatedIdentifier())
+               id, _ := rst.undisambiguatedIdentifier()
+               rst.writeString(id)
                rst.writeString(" = ")
                rst.demangleType()
        }
@@ -722,10 +766,12 @@ func (rst *rustState) writeLifetime(lifetime int64) {
        }
 }
 
-// <const> = <type> <const-data>
-//         | "p" // placeholder, shown as _
-//         | <backref>
-// <const-data> = ["n"] {<hex-digit>} "_"
+// demangleConst parses:
+//
+//     <const> = <type> <const-data>
+//             | "p" // placeholder, shown as _
+//             | <backref>
+//     <const-data> = ["n"] {<hex-digit>} "_"
 func (rst *rustState) demangleConst() {
        if len(rst.str) < 1 {
                rst.fail("expected constant")
@@ -856,7 +902,9 @@ digitLoop:
        }
 }
 
-// <base-62-number> = {<0-9a-zA-Z>} "_"
+// base62Number parses:
+//
+//     <base-62-number> = {<0-9a-zA-Z>} "_"
 func (rst *rustState) base62Number() int64 {
        if len(rst.str) > 0 && rst.str[0] == '_' {
                rst.advance(1)
@@ -884,7 +932,9 @@ func (rst *rustState) base62Number() int64 {
        return 0
 }
 
-// <backref> = "B" <base-62-number>
+// backref parses:
+//
+//     <backref> = "B" <base-62-number>
 func (rst *rustState) backref(demangle func()) {
        backoff := rst.off
 
@@ -954,7 +1004,7 @@ func oldRustToString(name string, options []Option) (string, bool) {
        // followed by "E". We check that the 16 characters are all hex digits.
        // Also the hex digits must contain at least 5 distinct digits.
        seen := uint16(0)
-       for i := len(name) - 17; i < len(name) - 1; i++ {
+       for i := len(name) - 17; i < len(name)-1; i++ {
                digit, ok := hexDigit(name[i])
                if !ok {
                        return "", false
@@ -1012,7 +1062,7 @@ func oldRustToString(name string, options []Option) (string, bool) {
                for len(id) > 0 {
                        switch c := id[0]; c {
                        case '$':
-                               codes := map[string]byte {
+                               codes := map[string]byte{
                                        "SP": '@',
                                        "BP": '*',
                                        "RF": '&',
index 62da0c76bb6a6c9391fe33fd8566e9c2bcbed2c7..1280a9e70163c67d92d41039d940c2a46b99e321 100644 (file)
@@ -1,5 +1,5 @@
-# github.com/google/pprof v0.0.0-20220314021825-5bba342933ea
-## explicit; go 1.14
+# github.com/google/pprof v0.0.0-20220517023622-154dc81eb7b0
+## explicit; go 1.17
 github.com/google/pprof/driver
 github.com/google/pprof/internal/binutils
 github.com/google/pprof/internal/driver
@@ -14,7 +14,7 @@ github.com/google/pprof/internal/transport
 github.com/google/pprof/profile
 github.com/google/pprof/third_party/d3flamegraph
 github.com/google/pprof/third_party/svgpan
-# github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d
+# github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2
 ## explicit; go 1.12
 github.com/ianlancetaylor/demangle
 # golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15