12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 |
- // Copyright 2017 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package driver
-
- import "html/template"
-
- import "github.com/google/pprof/third_party/d3"
- import "github.com/google/pprof/third_party/d3flamegraph"
-
- // addTemplates adds a set of template definitions to templates.
- func addTemplates(templates *template.Template) {
- template.Must(templates.Parse(`{{define "d3script"}}` + d3.JSSource + `{{end}}`))
- template.Must(templates.Parse(`{{define "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;
- }
- .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:#757575'%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;
- }
- .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;
- }
-
- #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 {
- 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>
-
- <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>
- <hr>
- <a title="{{.Help.reset}}" href="?">Reset</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="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 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;
- window.location.href =
- updateUrl(new URL(window.location.href), 'f');
- e.preventDefault();
- }
-
- function handleSearch() {
- // Delay expensive processing so a flurry of key strokes is handled once.
- if (searchAlarm != null) {
- clearTimeout(searchAlarm);
- }
- 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.
- 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');
- }
-
- // Update id's href to reflect current selection whenever it is
- // liable to be followed.
- function makeLinkDynamic(id) {
- const elem = document.getElementById(id);
- if (elem == null) return;
-
- // 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';
-
- // We update on mouseenter so middle-click/right-click work properly.
- elem.addEventListener('mouseenter', updater);
- elem.addEventListener('touchstart', updater);
-
- function updater() {
- elem.href = updateUrl(new URL(elem.href), param);
- }
- }
-
- // Update URL to reflect current selection.
- function updateUrl(url, param) {
- url.hash = '';
-
- // The selection can be in one of two modes: regexp-based or
- // list-based. Construct regular expression depending on mode.
- let re = regexpActive
- ? search.value
- : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|');
-
- // 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]);
- }
-
- if (re != '') {
- // For focus/show, forget old parameter. For others, add to re.
- if (param != 'f' && param != 's' && params.has(param)) {
- const old = params.get(param);
- if (old != '') {
- re += '|' + old;
- }
- }
- params.set(param, re);
- } else {
- params.delete(param);
- }
-
- return url.toString();
- }
-
- function handleTopClick(e) {
- // Walk back until we find TR and then get the Name column (index 5)
- let elem = e.target;
- while (elem != null && elem.nodeName != 'TR') {
- elem = elem.parentElement;
- }
- 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']) {
- 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', 'peek', 'list', 'disasm',
- 'focus', 'ignore', 'hide', 'show'];
- ids.forEach(makeLinkDynamic);
-
- // Bind action to button with specified id.
- function addAction(id, action) {
- const btn = document.getElementById(id);
- if (btn != null) {
- btn.addEventListener('click', action);
- btn.addEventListener('touchstart', action);
- }
- }
-
- addAction('details', handleDetails);
-
- 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 "d3script" .}}</script>
- <script>{{template "d3flamegraphscript" .}}</script>
- <script>
- var data = {{.FlameGraph}};
-
- var width = document.getElementById('chart').clientWidth;
-
- var flameGraph = d3.flamegraph()
- .width(width)
- .cellHeight(18)
- .minFrameSize(1)
- .transitionDuration(750)
- .transitionEase(d3.easeCubic)
- .inverted(true)
- .title('')
- .tooltip(false)
- .details(document.getElementById('flamegraphdetails'));
-
- // <full name> (percentage, value)
- flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')');
-
- (function(flameGraph) {
- var oldColorMapper = flameGraph.color();
- function colorMapper(d) {
- // Hack to force default color mapper to use 'warm' color scheme by not passing libtype
- const { data, highlight } = d;
- return oldColorMapper({ data: { n: data.n }, highlight });
- }
-
- flameGraph.color(colorMapper);
- }(flameGraph));
-
- d3.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}}
- `))
- }
|