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
)
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
)
-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=
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=
--- /dev/null
+* {
+ 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;
+}
--- /dev/null
+// 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();
+ }
+}
--- /dev/null
+<!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>
--- /dev/null
+<!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>
--- /dev/null
+<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>
--- /dev/null
+<!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>
--- /dev/null
+<!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>
--- /dev/null
+<!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>
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
}
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"))
}
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
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 {
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)},
{"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)},
{"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},
{"P*GCU", []string{"petagcu"}, 1e15},
},
defaultUnit: unit{"GCU", []string{}, 1.0},
-}
+}}
//
// 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
//
// 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
## 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:
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
}
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)
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 {
case ".", "->":
skipBothParens = true
addSpaces = false
+ case "->*":
+ skipParens = true
+ addSpaces = false
}
}
}
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(' ')
// 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
+ }
}
}
}
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")
}
}
-// <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)
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")
}
}
-// <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()
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
}
}
-// <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")
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 {
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' {
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 {
"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.
}
}
-// <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)
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)
}
}
-// <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>.
'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")
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)
// 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
'K': "const",
}
-// <CV-qualifiers> ::= [r] [V] [K]
+// cvQualifiers parses:
+//
+// <CV-qualifiers> ::= [r] [V] [K]
func (st *state) cvQualifiers() AST {
var q []AST
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] {
return ""
}
-// <type>+
+// parmlist parses:
+//
+// <type>+
func (st *state) parmlist() []AST {
var ret []AST
for {
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' {
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
}
}
-// <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')
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")
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)
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")
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
}
}
-// <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")
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")
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")
}
}
-// <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()
}
}
-// <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)
}
}
-// <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" {
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 {
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
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')
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.
}
}
-// <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')
// 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++
}
}
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 {
}
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
}
}
-// <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() {
}
}
-// <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.
}
}
-// <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
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
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()
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++ {
}
}
- if punycode {
+ if isPunycode {
id = rst.expandPunycode(id)
}
- return id
+ return id, isPunycode
}
// expandPunycode decodes the Rust version of punycode.
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
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)
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")
}
}
-// <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
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")
'z': "!",
}
-// <basic-type>
+// basicType parses:
+//
+// <basic-type>
func (rst *rustState) basicType() {
if len(rst.str) < 1 {
rst.fail("expected basic type")
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' {
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(`" `)
}
}
-// <dyn-bounds> = [<binder>] {<dyn-trait>} "E"
+// dynBounds parses:
+//
+// <dyn-bounds> = [<binder>] {<dyn-trait>} "E"
func (rst *rustState) dynBounds() {
rst.writeString("dyn ")
rst.binder()
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' {
rst.writeByte('<')
started = true
}
- rst.writeString(rst.undisambiguatedIdentifier())
+ id, _ := rst.undisambiguatedIdentifier()
+ rst.writeString(id)
rst.writeString(" = ")
rst.demangleType()
}
}
}
-// <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")
}
}
-// <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)
return 0
}
-// <backref> = "B" <base-62-number>
+// backref parses:
+//
+// <backref> = "B" <base-62-number>
func (rst *rustState) backref(demangle func()) {
backoff := rst.off
// 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
for len(id) > 0 {
switch c := id[0]; c {
case '$':
- codes := map[string]byte {
+ codes := map[string]byte{
"SP": '@',
"BP": '*',
"RF": '&',
-# 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
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