Nav apraksta

webhtml.go 30KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138
  1. // Copyright 2017 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package driver
  15. import "html/template"
  16. import "github.com/google/pprof/third_party/d3"
  17. import "github.com/google/pprof/third_party/d3tip"
  18. import "github.com/google/pprof/third_party/d3flamegraph"
  19. // addTemplates adds a set of template definitions to templates.
  20. func addTemplates(templates *template.Template) {
  21. template.Must(templates.Parse(`{{define "d3script"}}` + d3.JSSource + `{{end}}`))
  22. template.Must(templates.Parse(`{{define "d3tipscript"}}` + d3tip.JSSource + `{{end}}`))
  23. template.Must(templates.Parse(`{{define "d3flamegraphscript"}}` + d3flamegraph.JSSource + `{{end}}`))
  24. template.Must(templates.Parse(`{{define "d3flamegraphcss"}}` + d3flamegraph.CSSSource + `{{end}}`))
  25. template.Must(templates.Parse(`
  26. {{define "css"}}
  27. <style type="text/css">
  28. * {
  29. margin: 0;
  30. padding: 0;
  31. box-sizing: border-box;
  32. }
  33. html, body {
  34. height: 100%;
  35. }
  36. body {
  37. font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  38. font-size: 13px;
  39. line-height: 1.4;
  40. display: flex;
  41. flex-direction: column;
  42. }
  43. a {
  44. color: #2a66d9;
  45. }
  46. .header {
  47. display: flex;
  48. align-items: center;
  49. height: 44px;
  50. min-height: 44px;
  51. background-color: #eee;
  52. color: #212121;
  53. padding: 0 1rem;
  54. }
  55. .header > div {
  56. margin: 0 0.125em;
  57. }
  58. .header .title h1 {
  59. font-size: 1.75em;
  60. margin-right: 1rem;
  61. }
  62. .header .title a {
  63. color: #212121;
  64. text-decoration: none;
  65. }
  66. .header .title a:hover {
  67. text-decoration: underline;
  68. }
  69. .header .description {
  70. width: 100%;
  71. text-align: right;
  72. white-space: nowrap;
  73. }
  74. @media screen and (max-width: 799px) {
  75. .header input {
  76. display: none;
  77. }
  78. }
  79. #detailsbox {
  80. display: none;
  81. z-index: 1;
  82. position: fixed;
  83. top: 40px;
  84. right: 20px;
  85. background-color: #ffffff;
  86. box-shadow: 0 1px 5px rgba(0,0,0,.3);
  87. line-height: 24px;
  88. padding: 1em;
  89. text-align: left;
  90. }
  91. .header input {
  92. 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;
  93. border: 1px solid #d1d2d3;
  94. border-radius: 2px 0 0 2px;
  95. padding: 0.25em;
  96. padding-left: 28px;
  97. margin-left: 1em;
  98. font-family: 'Roboto', 'Noto', sans-serif;
  99. font-size: 1em;
  100. line-height: 24px;
  101. color: #212121;
  102. }
  103. .downArrow {
  104. border-top: .36em solid #ccc;
  105. border-left: .36em solid transparent;
  106. border-right: .36em solid transparent;
  107. margin-bottom: .05em;
  108. margin-left: .5em;
  109. transition: border-top-color 200ms;
  110. }
  111. .menu-item {
  112. height: 100%;
  113. text-transform: uppercase;
  114. font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  115. position: relative;
  116. }
  117. .menu-item.disabled {
  118. opacity: 0.5;
  119. }
  120. .menu-item .menu-name:hover {
  121. opacity: 0.75;
  122. }
  123. .menu-item .menu-name:hover .downArrow {
  124. border-top-color: #666;
  125. }
  126. .menu-item.disabled .menu-name:hover {
  127. opacity: 1;
  128. }
  129. .menu-item.disabled .menu-name:hover .downArrow {
  130. border-top-color: #ccc;
  131. }
  132. .menu-name {
  133. height: 100%;
  134. padding: 0 0.5em;
  135. display: flex;
  136. align-items: center;
  137. justify-content: center;
  138. }
  139. .submenu {
  140. display: none;
  141. z-index: 1;
  142. margin-top: -4px;
  143. min-width: 10em;
  144. position: absolute;
  145. left: 0px;
  146. background-color: white;
  147. box-shadow: 0 1px 5px rgba(0,0,0,.3);
  148. font-size: 100%;
  149. text-transform: none;
  150. }
  151. .menu-item, .submenu {
  152. user-select: none;
  153. -moz-user-select: none;
  154. -ms-user-select: none;
  155. -webkit-user-select: none;
  156. }
  157. .submenu hr {
  158. border: 0;
  159. border-top: 2px solid #eee;
  160. }
  161. .submenu a {
  162. display: block;
  163. padding: .5em 1em;
  164. text-decoration: none;
  165. }
  166. .submenu a:hover, .submenu a.active {
  167. color: white;
  168. background-color: #6b82d6;
  169. }
  170. .submenu a.disabled {
  171. color: gray;
  172. pointer-events: none;
  173. }
  174. #content {
  175. overflow-y: scroll;
  176. padding: 1em;
  177. }
  178. #top {
  179. overflow-y: scroll;
  180. }
  181. #graph {
  182. overflow: hidden;
  183. }
  184. #graph svg {
  185. width: 100%;
  186. height: auto;
  187. padding: 10px;
  188. }
  189. #content.source .filename {
  190. margin-top: 0;
  191. margin-bottom: 1em;
  192. font-size: 120%;
  193. }
  194. #content.source pre {
  195. margin-bottom: 3em;
  196. }
  197. table {
  198. border-spacing: 0px;
  199. width: 100%;
  200. padding-bottom: 1em;
  201. white-space: nowrap;
  202. }
  203. table thead {
  204. font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  205. }
  206. table tr th {
  207. background-color: #ddd;
  208. text-align: right;
  209. padding: .3em .5em;
  210. }
  211. table tr td {
  212. padding: .3em .5em;
  213. text-align: right;
  214. }
  215. #top table tr th:nth-child(6),
  216. #top table tr th:nth-child(7),
  217. #top table tr td:nth-child(6),
  218. #top table tr td:nth-child(7) {
  219. text-align: left;
  220. }
  221. #top table tr td:nth-child(6) {
  222. width: 100%;
  223. text-overflow: ellipsis;
  224. overflow: hidden;
  225. white-space: nowrap;
  226. }
  227. #flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {
  228. cursor: ns-resize;
  229. }
  230. .hilite {
  231. background-color: #ebf5fb;
  232. font-weight: bold;
  233. }
  234. </style>
  235. {{end}}
  236. {{define "header"}}
  237. <div class="header">
  238. <div class="title">
  239. <h1><a href="/">pprof</a></h1>
  240. </div>
  241. <div id="view" class="menu-item">
  242. <div class="menu-name">
  243. View
  244. <i class="downArrow"></i>
  245. </div>
  246. <div class="submenu">
  247. <a title="{{.Help.top}}" href="/top" id="topbtn">Top</a>
  248. <a title="{{.Help.graph}}" href="/" id="graphbtn">Graph</a>
  249. <a title="{{.Help.flamegraph}}" href="/flamegraph" id="flamegraph">Flame Graph</a>
  250. <a title="{{.Help.peek}}" href="/peek" id="peek">Peek</a>
  251. <a title="{{.Help.list}}" href="/source" id="list">Source</a>
  252. <a title="{{.Help.disasm}}" href="/disasm" id="disasm">Disassemble</a>
  253. </div>
  254. </div>
  255. <div id="refine" class="menu-item disabled">
  256. <div class="menu-name">
  257. Refine
  258. <i class="downArrow"></i>
  259. </div>
  260. <div class="submenu">
  261. <a title="{{.Help.focus}}" href="{{.BaseURL}}" id="focus">Focus</a>
  262. <a title="{{.Help.ignore}}" href="{{.BaseURL}}" id="ignore">Ignore</a>
  263. <a title="{{.Help.hide}}" href="{{.BaseURL}}" id="hide">Hide</a>
  264. <a title="{{.Help.show}}" href="{{.BaseURL}}" id="show">Show</a>
  265. <hr>
  266. <a title="{{.Help.reset}}" href="{{.BaseURL}}">Reset</a>
  267. </div>
  268. </div>
  269. <div>
  270. <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
  271. </div>
  272. <div class="description">
  273. <a title="{{.Help.details}}" href="#" id="details">{{.Title}}</a>
  274. <div id="detailsbox">
  275. {{range .Legend}}<div>{{.}}</div>{{end}}
  276. </div>
  277. </div>
  278. </div>
  279. <div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
  280. {{end}}
  281. {{define "graph" -}}
  282. <!DOCTYPE html>
  283. <html>
  284. <head>
  285. <meta charset="utf-8">
  286. <title>{{.Title}}</title>
  287. {{template "css" .}}
  288. </head>
  289. <body>
  290. {{template "header" .}}
  291. <div id="graph">
  292. {{.HTMLBody}}
  293. </div>
  294. {{template "script" .}}
  295. <script>viewer({{.BaseURL}}, {{.Nodes}});</script>
  296. </body>
  297. </html>
  298. {{end}}
  299. {{define "script"}}
  300. <script>
  301. // Make svg pannable and zoomable.
  302. // Call clickHandler(t) if a click event is caught by the pan event handlers.
  303. function initPanAndZoom(svg, clickHandler) {
  304. 'use strict';
  305. // Current mouse/touch handling mode
  306. const IDLE = 0;
  307. const MOUSEPAN = 1;
  308. const TOUCHPAN = 2;
  309. const TOUCHZOOM = 3;
  310. let mode = IDLE;
  311. // State needed to implement zooming.
  312. let currentScale = 1.0;
  313. const initWidth = svg.viewBox.baseVal.width;
  314. const initHeight = svg.viewBox.baseVal.height;
  315. // State needed to implement panning.
  316. let panLastX = 0; // Last event X coordinate
  317. let panLastY = 0; // Last event Y coordinate
  318. let moved = false; // Have we seen significant movement
  319. let touchid = null; // Current touch identifier
  320. // State needed for pinch zooming
  321. let touchid2 = null; // Second id for pinch zooming
  322. let initGap = 1.0; // Starting gap between two touches
  323. let initScale = 1.0; // currentScale when pinch zoom started
  324. let centerPoint = null; // Center point for scaling
  325. // Convert event coordinates to svg coordinates.
  326. function toSvg(x, y) {
  327. const p = svg.createSVGPoint();
  328. p.x = x;
  329. p.y = y;
  330. let m = svg.getCTM();
  331. if (m == null) m = svg.getScreenCTM(); // Firefox workaround.
  332. return p.matrixTransform(m.inverse());
  333. }
  334. // Change the scaling for the svg to s, keeping the point denoted
  335. // by u (in svg coordinates]) fixed at the same screen location.
  336. function rescale(s, u) {
  337. // Limit to a good range.
  338. if (s < 0.2) s = 0.2;
  339. if (s > 10.0) s = 10.0;
  340. currentScale = s;
  341. // svg.viewBox defines the visible portion of the user coordinate
  342. // system. So to magnify by s, divide the visible portion by s,
  343. // which will then be stretched to fit the viewport.
  344. const vb = svg.viewBox;
  345. const w1 = vb.baseVal.width;
  346. const w2 = initWidth / s;
  347. const h1 = vb.baseVal.height;
  348. const h2 = initHeight / s;
  349. vb.baseVal.width = w2;
  350. vb.baseVal.height = h2;
  351. // We also want to adjust vb.baseVal.x so that u.x remains at same
  352. // screen X coordinate. In other words, want to change it from x1 to x2
  353. // so that:
  354. // (u.x - x1) / w1 = (u.x - x2) / w2
  355. // Simplifying that, we get
  356. // (u.x - x1) * (w2 / w1) = u.x - x2
  357. // x2 = u.x - (u.x - x1) * (w2 / w1)
  358. vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1);
  359. vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1);
  360. }
  361. function handleWheel(e) {
  362. if (e.deltaY == 0) return;
  363. // Change scale factor by 1.1 or 1/1.1
  364. rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),
  365. toSvg(e.offsetX, e.offsetY));
  366. }
  367. function setMode(m) {
  368. mode = m;
  369. touchid = null;
  370. touchid2 = null;
  371. }
  372. function panStart(x, y) {
  373. moved = false;
  374. panLastX = x;
  375. panLastY = y;
  376. }
  377. function panMove(x, y) {
  378. let dx = x - panLastX;
  379. let dy = y - panLastY;
  380. if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return; // Ignore tiny moves
  381. moved = true;
  382. panLastX = x;
  383. panLastY = y;
  384. // Firefox workaround: get dimensions from parentNode.
  385. const swidth = svg.clientWidth || svg.parentNode.clientWidth;
  386. const sheight = svg.clientHeight || svg.parentNode.clientHeight;
  387. // Convert deltas from screen space to svg space.
  388. dx *= (svg.viewBox.baseVal.width / swidth);
  389. dy *= (svg.viewBox.baseVal.height / sheight);
  390. svg.viewBox.baseVal.x -= dx;
  391. svg.viewBox.baseVal.y -= dy;
  392. }
  393. function handleScanStart(e) {
  394. if (e.button != 0) return; // Do not catch right-clicks etc.
  395. setMode(MOUSEPAN);
  396. panStart(e.clientX, e.clientY);
  397. e.preventDefault();
  398. svg.addEventListener('mousemove', handleScanMove);
  399. }
  400. function handleScanMove(e) {
  401. if (e.buttons == 0) {
  402. // Missed an end event, perhaps because mouse moved outside window.
  403. setMode(IDLE);
  404. svg.removeEventListener('mousemove', handleScanMove);
  405. return;
  406. }
  407. if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
  408. }
  409. function handleScanEnd(e) {
  410. if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);
  411. setMode(IDLE);
  412. svg.removeEventListener('mousemove', handleScanMove);
  413. if (!moved) clickHandler(e.target);
  414. }
  415. // Find touch object with specified identifier.
  416. function findTouch(tlist, id) {
  417. for (const t of tlist) {
  418. if (t.identifier == id) return t;
  419. }
  420. return null;
  421. }
  422. // Return distance between two touch points
  423. function touchGap(t1, t2) {
  424. const dx = t1.clientX - t2.clientX;
  425. const dy = t1.clientY - t2.clientY;
  426. return Math.hypot(dx, dy);
  427. }
  428. function handleTouchStart(e) {
  429. if (mode == IDLE && e.changedTouches.length == 1) {
  430. // Start touch based panning
  431. const t = e.changedTouches[0];
  432. setMode(TOUCHPAN);
  433. touchid = t.identifier;
  434. panStart(t.clientX, t.clientY);
  435. e.preventDefault();
  436. } else if (mode == TOUCHPAN && e.touches.length == 2) {
  437. // Start pinch zooming
  438. setMode(TOUCHZOOM);
  439. const t1 = e.touches[0];
  440. const t2 = e.touches[1];
  441. touchid = t1.identifier;
  442. touchid2 = t2.identifier;
  443. initScale = currentScale;
  444. initGap = touchGap(t1, t2);
  445. centerPoint = toSvg((t1.clientX + t2.clientX) / 2,
  446. (t1.clientY + t2.clientY) / 2);
  447. e.preventDefault();
  448. }
  449. }
  450. function handleTouchMove(e) {
  451. if (mode == TOUCHPAN) {
  452. const t = findTouch(e.changedTouches, touchid);
  453. if (t == null) return;
  454. if (e.touches.length != 1) {
  455. setMode(IDLE);
  456. return;
  457. }
  458. panMove(t.clientX, t.clientY);
  459. e.preventDefault();
  460. } else if (mode == TOUCHZOOM) {
  461. // Get two touches; new gap; rescale to ratio.
  462. const t1 = findTouch(e.touches, touchid);
  463. const t2 = findTouch(e.touches, touchid2);
  464. if (t1 == null || t2 == null) return;
  465. const gap = touchGap(t1, t2);
  466. rescale(initScale * gap / initGap, centerPoint);
  467. e.preventDefault();
  468. }
  469. }
  470. function handleTouchEnd(e) {
  471. if (mode == TOUCHPAN) {
  472. const t = findTouch(e.changedTouches, touchid);
  473. if (t == null) return;
  474. panMove(t.clientX, t.clientY);
  475. setMode(IDLE);
  476. e.preventDefault();
  477. if (!moved) clickHandler(t.target);
  478. } else if (mode == TOUCHZOOM) {
  479. setMode(IDLE);
  480. e.preventDefault();
  481. }
  482. }
  483. svg.addEventListener('mousedown', handleScanStart);
  484. svg.addEventListener('mouseup', handleScanEnd);
  485. svg.addEventListener('touchstart', handleTouchStart);
  486. svg.addEventListener('touchmove', handleTouchMove);
  487. svg.addEventListener('touchend', handleTouchEnd);
  488. svg.addEventListener('wheel', handleWheel, true);
  489. }
  490. function initMenus() {
  491. 'use strict';
  492. let activeMenu = null;
  493. let activeMenuHdr = null;
  494. function cancelActiveMenu() {
  495. if (activeMenu == null) return;
  496. activeMenu.style.display = 'none';
  497. activeMenu = null;
  498. activeMenuHdr = null;
  499. }
  500. // Set click handlers on every menu header.
  501. for (const menu of document.getElementsByClassName('submenu')) {
  502. const hdr = menu.parentElement;
  503. if (hdr == null) return;
  504. if (hdr.classList.contains('disabled')) return;
  505. function showMenu(e) {
  506. // menu is a child of hdr, so this event can fire for clicks
  507. // inside menu. Ignore such clicks.
  508. if (e.target.parentElement != hdr) return;
  509. activeMenu = menu;
  510. activeMenuHdr = hdr;
  511. menu.style.display = 'block';
  512. }
  513. hdr.addEventListener('mousedown', showMenu);
  514. hdr.addEventListener('touchstart', showMenu);
  515. }
  516. // If there is an active menu and a down event outside, retract the menu.
  517. for (const t of ['mousedown', 'touchstart']) {
  518. document.addEventListener(t, (e) => {
  519. // Note: to avoid unnecessary flicker, if the down event is inside
  520. // the active menu header, do not retract the menu.
  521. if (activeMenuHdr != e.target.closest('.menu-item')) {
  522. cancelActiveMenu();
  523. }
  524. }, { passive: true, capture: true });
  525. }
  526. // If there is an active menu and an up event inside, retract the menu.
  527. document.addEventListener('mouseup', (e) => {
  528. if (activeMenu == e.target.closest('.submenu')) {
  529. cancelActiveMenu();
  530. }
  531. }, { passive: true, capture: true });
  532. }
  533. function viewer(baseUrl, nodes) {
  534. 'use strict';
  535. // Elements
  536. const search = document.getElementById('search');
  537. const graph0 = document.getElementById('graph0');
  538. const svg = (graph0 == null ? null : graph0.parentElement);
  539. const toptable = document.getElementById('toptable');
  540. let regexpActive = false;
  541. let selected = new Map();
  542. let origFill = new Map();
  543. let searchAlarm = null;
  544. let buttonsEnabled = true;
  545. function handleDetails(e) {
  546. e.preventDefault();
  547. const detailsText = document.getElementById('detailsbox');
  548. if (detailsText != null) {
  549. if (detailsText.style.display === 'block') {
  550. detailsText.style.display = 'none';
  551. } else {
  552. detailsText.style.display = 'block';
  553. }
  554. }
  555. }
  556. function handleKey(e) {
  557. if (e.keyCode != 13) return;
  558. window.location.href =
  559. updateUrl(new URL({{.BaseURL}}, window.location.href), 'f');
  560. e.preventDefault();
  561. }
  562. function handleSearch() {
  563. // Delay expensive processing so a flurry of key strokes is handled once.
  564. if (searchAlarm != null) {
  565. clearTimeout(searchAlarm);
  566. }
  567. searchAlarm = setTimeout(selectMatching, 300);
  568. regexpActive = true;
  569. updateButtons();
  570. }
  571. function selectMatching() {
  572. searchAlarm = null;
  573. let re = null;
  574. if (search.value != '') {
  575. try {
  576. re = new RegExp(search.value);
  577. } catch (e) {
  578. // TODO: Display error state in search box
  579. return;
  580. }
  581. }
  582. function match(text) {
  583. return re != null && re.test(text);
  584. }
  585. // drop currently selected items that do not match re.
  586. selected.forEach(function(v, n) {
  587. if (!match(nodes[n])) {
  588. unselect(n, document.getElementById('node' + n));
  589. }
  590. })
  591. // add matching items that are not currently selected.
  592. for (let n = 0; n < nodes.length; n++) {
  593. if (!selected.has(n) && match(nodes[n])) {
  594. select(n, document.getElementById('node' + n));
  595. }
  596. }
  597. updateButtons();
  598. }
  599. function toggleSvgSelect(elem) {
  600. // Walk up to immediate child of graph0
  601. while (elem != null && elem.parentElement != graph0) {
  602. elem = elem.parentElement;
  603. }
  604. if (!elem) return;
  605. // Disable regexp mode.
  606. regexpActive = false;
  607. const n = nodeId(elem);
  608. if (n < 0) return;
  609. if (selected.has(n)) {
  610. unselect(n, elem);
  611. } else {
  612. select(n, elem);
  613. }
  614. updateButtons();
  615. }
  616. function unselect(n, elem) {
  617. if (elem == null) return;
  618. selected.delete(n);
  619. setBackground(elem, false);
  620. }
  621. function select(n, elem) {
  622. if (elem == null) return;
  623. selected.set(n, true);
  624. setBackground(elem, true);
  625. }
  626. function nodeId(elem) {
  627. const id = elem.id;
  628. if (!id) return -1;
  629. if (!id.startsWith('node')) return -1;
  630. const n = parseInt(id.slice(4), 10);
  631. if (isNaN(n)) return -1;
  632. if (n < 0 || n >= nodes.length) return -1;
  633. return n;
  634. }
  635. function setBackground(elem, set) {
  636. // Handle table row highlighting.
  637. if (elem.nodeName == 'TR') {
  638. elem.classList.toggle('hilite', set);
  639. return;
  640. }
  641. // Handle svg element highlighting.
  642. const p = findPolygon(elem);
  643. if (p != null) {
  644. if (set) {
  645. origFill.set(p, p.style.fill);
  646. p.style.fill = '#ccccff';
  647. } else if (origFill.has(p)) {
  648. p.style.fill = origFill.get(p);
  649. }
  650. }
  651. }
  652. function findPolygon(elem) {
  653. if (elem.localName == 'polygon') return elem;
  654. for (const c of elem.children) {
  655. const p = findPolygon(c);
  656. if (p != null) return p;
  657. }
  658. return null;
  659. }
  660. // convert a string to a regexp that matches that string.
  661. function quotemeta(str) {
  662. return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
  663. }
  664. // Update id's href to reflect current selection whenever it is
  665. // liable to be followed.
  666. function makeLinkDynamic(id) {
  667. const elem = document.getElementById(id);
  668. if (elem == null) return;
  669. // Most links copy current selection into the 'f' parameter,
  670. // but Refine menu links are different.
  671. let param = 'f';
  672. if (id == 'ignore') param = 'i';
  673. if (id == 'hide') param = 'h';
  674. if (id == 'show') param = 's';
  675. // We update on mouseenter so middle-click/right-click work properly.
  676. elem.addEventListener('mouseenter', updater);
  677. elem.addEventListener('touchstart', updater);
  678. function updater() {
  679. elem.href = updateUrl(new URL(elem.href), param);
  680. }
  681. }
  682. // Update URL to reflect current selection.
  683. function updateUrl(url, param) {
  684. url.hash = '';
  685. // The selection can be in one of two modes: regexp-based or
  686. // list-based. Construct regular expression depending on mode.
  687. let re = regexpActive
  688. ? search.value
  689. : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|');
  690. // Copy params from this page's URL.
  691. const params = url.searchParams;
  692. for (const p of new URLSearchParams(window.location.search)) {
  693. params.set(p[0], p[1]);
  694. }
  695. if (re != '') {
  696. // For focus/show, forget old parameter. For others, add to re.
  697. if (param != 'f' && param != 's' && params.has(param)) {
  698. const old = params.get(param);
  699. if (old != '') {
  700. re += '|' + old;
  701. }
  702. }
  703. params.set(param, re);
  704. } else {
  705. params.delete(param);
  706. }
  707. return url.toString();
  708. }
  709. function handleTopClick(e) {
  710. // Walk back until we find TR and then get the Name column (index 5)
  711. let elem = e.target;
  712. while (elem != null && elem.nodeName != 'TR') {
  713. elem = elem.parentElement;
  714. }
  715. if (elem == null || elem.children.length < 6) return;
  716. e.preventDefault();
  717. const tr = elem;
  718. const td = elem.children[5];
  719. if (td.nodeName != 'TD') return;
  720. const name = td.innerText;
  721. const index = nodes.indexOf(name);
  722. if (index < 0) return;
  723. // Disable regexp mode.
  724. regexpActive = false;
  725. if (selected.has(index)) {
  726. unselect(index, elem);
  727. } else {
  728. select(index, elem);
  729. }
  730. updateButtons();
  731. }
  732. function updateButtons() {
  733. const enable = (search.value != '' || selected.size != 0);
  734. if (buttonsEnabled == enable) return;
  735. buttonsEnabled = enable;
  736. for (const id of ['focus', 'ignore', 'hide', 'show']) {
  737. const link = document.getElementById(id);
  738. if (link != null) {
  739. link.classList.toggle('disabled', !enable);
  740. }
  741. }
  742. if (document.getElementById('graph') !== null) {
  743. document.getElementById('refine').classList.remove('disabled');
  744. }
  745. }
  746. // Initialize button states
  747. updateButtons();
  748. // Setup event handlers
  749. initMenus();
  750. if (svg != null) {
  751. initPanAndZoom(svg, toggleSvgSelect);
  752. }
  753. if (toptable != null) {
  754. toptable.addEventListener('mousedown', handleTopClick);
  755. toptable.addEventListener('touchstart', handleTopClick);
  756. }
  757. const ids = ['topbtn', 'graphbtn', 'peek', 'list', 'disasm',
  758. 'focus', 'ignore', 'hide', 'show'];
  759. ids.forEach(makeLinkDynamic);
  760. // Bind action to button with specified id.
  761. function addAction(id, action) {
  762. const btn = document.getElementById(id);
  763. if (btn != null) {
  764. btn.addEventListener('click', action);
  765. btn.addEventListener('touchstart', action);
  766. }
  767. }
  768. addAction('details', handleDetails);
  769. search.addEventListener('input', handleSearch);
  770. search.addEventListener('keydown', handleKey);
  771. // Give initial focus to main container so it can be scrolled using keys.
  772. const main = document.getElementById('bodycontainer');
  773. if (main) {
  774. main.focus();
  775. }
  776. }
  777. </script>
  778. {{end}}
  779. {{define "top" -}}
  780. <!DOCTYPE html>
  781. <html>
  782. <head>
  783. <meta charset="utf-8">
  784. <title>{{.Title}}</title>
  785. {{template "css" .}}
  786. <style type="text/css">
  787. </style>
  788. </head>
  789. <body>
  790. {{template "header" .}}
  791. <div id="top">
  792. <table id="toptable">
  793. <thead>
  794. <tr>
  795. <th id="flathdr1">Flat</th>
  796. <th id="flathdr2">Flat%</th>
  797. <th>Sum%</th>
  798. <th id="cumhdr1">Cum</th>
  799. <th id="cumhdr2">Cum%</th>
  800. <th id="namehdr">Name</th>
  801. <th>Inlined?</th>
  802. </tr>
  803. </thead>
  804. <tbody id="rows"></tbody>
  805. </table>
  806. </div>
  807. {{template "script" .}}
  808. <script>
  809. function makeTopTable(total, entries) {
  810. const rows = document.getElementById('rows');
  811. if (rows == null) return;
  812. // Store initial index in each entry so we have stable node ids for selection.
  813. for (let i = 0; i < entries.length; i++) {
  814. entries[i].Id = 'node' + i;
  815. }
  816. // Which column are we currently sorted by and in what order?
  817. let currentColumn = '';
  818. let descending = false;
  819. sortBy('Flat');
  820. function sortBy(column) {
  821. // Update sort criteria
  822. if (column == currentColumn) {
  823. descending = !descending; // Reverse order
  824. } else {
  825. currentColumn = column;
  826. descending = (column != 'Name');
  827. }
  828. // Sort according to current criteria.
  829. function cmp(a, b) {
  830. const av = a[currentColumn];
  831. const bv = b[currentColumn];
  832. if (av < bv) return -1;
  833. if (av > bv) return +1;
  834. return 0;
  835. }
  836. entries.sort(cmp);
  837. if (descending) entries.reverse();
  838. function addCell(tr, val) {
  839. const td = document.createElement('td');
  840. td.textContent = val;
  841. tr.appendChild(td);
  842. }
  843. function percent(v) {
  844. return (v * 100.0 / total).toFixed(2) + '%';
  845. }
  846. // Generate rows
  847. const fragment = document.createDocumentFragment();
  848. let sum = 0;
  849. for (const row of entries) {
  850. const tr = document.createElement('tr');
  851. tr.id = row.Id;
  852. sum += row.Flat;
  853. addCell(tr, row.FlatFormat);
  854. addCell(tr, percent(row.Flat));
  855. addCell(tr, percent(sum));
  856. addCell(tr, row.CumFormat);
  857. addCell(tr, percent(row.Cum));
  858. addCell(tr, row.Name);
  859. addCell(tr, row.InlineLabel);
  860. fragment.appendChild(tr);
  861. }
  862. rows.textContent = ''; // Remove old rows
  863. rows.appendChild(fragment);
  864. }
  865. // Make different column headers trigger sorting.
  866. function bindSort(id, column) {
  867. const hdr = document.getElementById(id);
  868. if (hdr == null) return;
  869. const fn = function() { sortBy(column) };
  870. hdr.addEventListener('click', fn);
  871. hdr.addEventListener('touch', fn);
  872. }
  873. bindSort('flathdr1', 'Flat');
  874. bindSort('flathdr2', 'Flat');
  875. bindSort('cumhdr1', 'Cum');
  876. bindSort('cumhdr2', 'Cum');
  877. bindSort('namehdr', 'Name');
  878. }
  879. viewer({{.BaseURL}}, {{.Nodes}});
  880. makeTopTable({{.Total}}, {{.Top}});
  881. </script>
  882. </body>
  883. </html>
  884. {{end}}
  885. {{define "sourcelisting" -}}
  886. <!DOCTYPE html>
  887. <html>
  888. <head>
  889. <meta charset="utf-8">
  890. <title>{{.Title}}</title>
  891. {{template "css" .}}
  892. {{template "weblistcss" .}}
  893. {{template "weblistjs" .}}
  894. </head>
  895. <body>
  896. {{template "header" .}}
  897. <div id="content" class="source">
  898. {{.HTMLBody}}
  899. </div>
  900. {{template "script" .}}
  901. <script>viewer({{.BaseURL}}, null);</script>
  902. </body>
  903. </html>
  904. {{end}}
  905. {{define "plaintext" -}}
  906. <!DOCTYPE html>
  907. <html>
  908. <head>
  909. <meta charset="utf-8">
  910. <title>{{.Title}}</title>
  911. {{template "css" .}}
  912. </head>
  913. <body>
  914. {{template "header" .}}
  915. <div id="content">
  916. <pre>
  917. {{.TextBody}}
  918. </pre>
  919. </div>
  920. {{template "script" .}}
  921. <script>viewer({{.BaseURL}}, null);</script>
  922. </body>
  923. </html>
  924. {{end}}
  925. {{define "flamegraph" -}}
  926. <!DOCTYPE html>
  927. <html>
  928. <head>
  929. <meta charset="utf-8">
  930. <title>{{.Title}}</title>
  931. {{template "css" .}}
  932. <style type="text/css">{{template "d3flamegraphcss" .}}</style>
  933. <style type="text/css">
  934. .flamegraph-content {
  935. width: 90%;
  936. min-width: 80%;
  937. margin-left: 5%;
  938. }
  939. .flamegraph-details {
  940. height: 1.2em;
  941. width: 90%;
  942. min-width: 90%;
  943. margin-left: 5%;
  944. padding-bottom: 41px;
  945. }
  946. </style>
  947. </head>
  948. <body>
  949. {{template "header" .}}
  950. <div id="bodycontainer">
  951. <div class="flamegraph-content">
  952. <div id="chart"></div>
  953. </div>
  954. <div id="flamegraphdetails" class="flamegraph-details"></div>
  955. </div>
  956. {{template "script" .}}
  957. <script>viewer({{.BaseURL}}, {{.Nodes}});</script>
  958. <script>{{template "d3script" .}}</script>
  959. <script>{{template "d3tipscript" .}}</script>
  960. <script>{{template "d3flamegraphscript" .}}</script>
  961. <script type="text/javascript">
  962. var data = {{.FlameGraph}};
  963. var label = function(d) {
  964. return d.data.n + ' (' + d.data.p + ', ' + d.data.l + ')';
  965. };
  966. var width = document.getElementById('chart').clientWidth;
  967. var flameGraph = d3.flameGraph()
  968. .width(width)
  969. .cellHeight(18)
  970. .minFrameSize(1)
  971. .transitionDuration(750)
  972. .transitionEase(d3.easeCubic)
  973. .sort(true)
  974. .title('')
  975. .label(label)
  976. .details(document.getElementById('flamegraphdetails'));
  977. var tip = d3.tip()
  978. .direction('s')
  979. .offset([8, 0])
  980. .attr('class', 'd3-flame-graph-tip')
  981. .html(function(d) { return 'name: ' + d.data.n + ', value: ' + d.data.l; });
  982. flameGraph.tooltip(tip);
  983. d3.select('#chart')
  984. .datum(data)
  985. .call(flameGraph);
  986. function clear() {
  987. flameGraph.clear();
  988. }
  989. function resetZoom() {
  990. flameGraph.resetZoom();
  991. }
  992. window.addEventListener('resize', function() {
  993. var width = document.getElementById('chart').clientWidth;
  994. var graphs = document.getElementsByClassName('d3-flame-graph');
  995. if (graphs.length > 0) {
  996. graphs[0].setAttribute('width', width);
  997. }
  998. flameGraph.width(width);
  999. flameGraph.resetZoom();
  1000. }, true);
  1001. var searchbox = document.getElementById('searchbox');
  1002. var searchAlarm = null;
  1003. function selectMatching() {
  1004. searchAlarm = null;
  1005. if (searchbox.value != '') {
  1006. flameGraph.search(searchbox.value);
  1007. } else {
  1008. flameGraph.clear();
  1009. }
  1010. }
  1011. function handleSearch() {
  1012. // Delay expensive processing so a flurry of key strokes is handled once.
  1013. if (searchAlarm != null) {
  1014. clearTimeout(searchAlarm);
  1015. }
  1016. searchAlarm = setTimeout(selectMatching, 300);
  1017. }
  1018. searchbox.addEventListener('input', handleSearch);
  1019. </script>
  1020. </body>
  1021. </html>
  1022. {{end}}
  1023. `))
  1024. }