callstats.html 88 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712
  1. <!DOCTYPE html>
  2. <html>
  3. <!--
  4. Copyright 2016 the V8 project authors. All rights reserved. Use of this source
  5. code is governed by a BSD-style license that can be found in the LICENSE file.
  6. -->
  7. <head>
  8. <meta charset="utf-8">
  9. <title>V8 Runtime Call Stats Komparator</title>
  10. <link rel="stylesheet" type="text/css" href="system-analyzer/index.css">
  11. <style>
  12. body {
  13. font-family: arial;
  14. }
  15. .panel {
  16. display: none;
  17. }
  18. .loaded .panel {
  19. display: block;
  20. }
  21. .panel.alwaysVisible {
  22. display: inherit !important;
  23. }
  24. .error #inputs {
  25. background-color: var(--error-color);
  26. }
  27. table {
  28. display: table;
  29. border-spacing: 0px;
  30. }
  31. tr {
  32. border-spacing: 0px;
  33. padding: 10px;
  34. }
  35. td,
  36. th {
  37. padding: 3px 10px 3px 5px;
  38. }
  39. .inline {
  40. display: inline-block;
  41. vertical-align: middle;
  42. margin-right: 10px;
  43. }
  44. .hidden {
  45. display: none;
  46. }
  47. .view {
  48. display: table;
  49. }
  50. .panel-group {
  51. display: grid;
  52. align-content: center;
  53. grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
  54. grid-auto-flow: row dense;
  55. grid-gap: 10px;
  56. margin-top: 10px;
  57. }
  58. .column {
  59. display: table-cell;
  60. border-right: 1px black dotted;
  61. min-width: 200px;
  62. }
  63. .column .header {
  64. padding: 0 10px 0 10px
  65. }
  66. #column {
  67. display: none;
  68. }
  69. .list {
  70. width: 100%;
  71. }
  72. select {
  73. width: 100%
  74. }
  75. .list tbody {
  76. cursor: pointer;
  77. }
  78. .list tr:nth-child(even) {
  79. background-color: rgba(0.5, 0.5, 0.5, 0.1);
  80. }
  81. .list tr.child {
  82. display: none;
  83. }
  84. .list tr.child.visible {
  85. display: table-row;
  86. }
  87. .list .child .name {
  88. padding-left: 20px;
  89. }
  90. .list .parent td {
  91. border-top: 1px solid #AAA;
  92. }
  93. .list .total {
  94. font-weight: bold
  95. }
  96. .list tr.parent.selected,
  97. .list tr:nth-child(even).selected,
  98. tr.selected {
  99. background-color: rgba(0.5, 0.5, 0.5, 0.1);
  100. }
  101. .codeSearch {
  102. display: block-inline;
  103. float: right;
  104. border-radius: 5px;
  105. background-color: #333;
  106. width: 1em;
  107. text-align: center;
  108. }
  109. .list .position {
  110. text-align: right;
  111. display: none;
  112. }
  113. .list div.toggle {
  114. cursor: pointer;
  115. }
  116. #column_0 .position {
  117. display: table-cell;
  118. }
  119. #column_0 .name {
  120. display: table-cell;
  121. }
  122. .list .name {
  123. display: none;
  124. white-space: nowrap;
  125. }
  126. .value {
  127. text-align: right;
  128. }
  129. .selectedVersion {
  130. font-weight: bold;
  131. }
  132. #baseline {
  133. width: auto;
  134. }
  135. .pageDetailTable tbody {
  136. cursor: pointer
  137. }
  138. .pageDetailTable tfoot td {
  139. border-top: 1px grey solid;
  140. }
  141. #popover {
  142. position: absolute;
  143. transform: translateY(-50%) translateX(40px);
  144. box-shadow: -2px 10px 44px -10px #000;
  145. border-radius: 5px;
  146. z-index: 1;
  147. background-color: var(--surface-color);
  148. display: none;
  149. white-space: nowrap;
  150. }
  151. #popover table {
  152. position: relative;
  153. z-index: 1;
  154. text-align: right;
  155. margin: 10px;
  156. }
  157. #popover td {
  158. padding: 3px 0px 3px 5px;
  159. white-space: nowrap;
  160. }
  161. .popoverArrow {
  162. background-color: var(--surface-color);
  163. position: absolute;
  164. width: 30px;
  165. height: 30px;
  166. transform: translateY(-50%)rotate(45deg);
  167. top: 50%;
  168. left: -10px;
  169. z-index: 0;
  170. }
  171. #popover .name {
  172. padding: 5px;
  173. font-weight: bold;
  174. text-align: center;
  175. }
  176. #popover table .compare {
  177. display: none
  178. }
  179. #popover table.compare .compare {
  180. display: table-cell;
  181. }
  182. #popover .compare .time,
  183. #popover .compare .version {
  184. padding-left: 10px;
  185. }
  186. .diff .hideDiff {
  187. display: none;
  188. }
  189. .noDiff .hideNoDiff {
  190. display: none;
  191. }
  192. </style>
  193. <script src="https://www.gstatic.com/charts/loader.js"></script>
  194. <script>
  195. "use strict"
  196. google.charts.load('current', {
  197. packages: ['corechart']
  198. });
  199. // Did anybody say monkeypatching?
  200. if (!NodeList.prototype.forEach) {
  201. NodeList.prototype.forEach = function (func) {
  202. for (let i = 0; i < this.length; i++) {
  203. func(this[i]);
  204. }
  205. }
  206. }
  207. let versions;
  208. let pages;
  209. let selectedPage;
  210. let baselineVersion;
  211. let selectedEntry;
  212. let sortByLabel = false;
  213. // Marker to programatically replace the defaultData.
  214. let defaultData = /*default-data-start*/ undefined /*default-data-end*/;
  215. function initialize() {
  216. // Initialize the stats table and toggle lists.
  217. let original = $("column");
  218. let viewBody = $("view").querySelector('.panelBody');
  219. removeAllChildren(viewBody);
  220. let i = 0;
  221. versions.forEach((version) => {
  222. if (!version.enabled) return;
  223. // add column
  224. let column = original.cloneNode(true);
  225. column.id = "column_" + i;
  226. // Fill in all versions
  227. let select = column.querySelector(".version");
  228. select.id = "selectVersion_" + i;
  229. // add all select options
  230. versions.forEach((version) => {
  231. if (!version.enabled) return;
  232. let option = document.createElement("option");
  233. option.textContent = version.name;
  234. option.version = version;
  235. select.appendChild(option);
  236. });
  237. // Fill in all page versions
  238. select = column.querySelector(".pageVersion");
  239. select.id = "select_" + i;
  240. // add all pages
  241. versions.forEach((version) => {
  242. if (!version.enabled) return;
  243. let optgroup = document.createElement("optgroup");
  244. optgroup.label = version.name;
  245. optgroup.version = version;
  246. version.forEachPage((page) => {
  247. let option = document.createElement("option");
  248. option.textContent = page.name;
  249. option.page = page;
  250. optgroup.appendChild(option);
  251. });
  252. select.appendChild(optgroup);
  253. });
  254. viewBody.appendChild(column);
  255. i++;
  256. });
  257. let select = $('baseline');
  258. removeAllChildren(select);
  259. select.appendChild(document.createElement('option'));
  260. versions.forEach((version) => {
  261. let option = document.createElement("option");
  262. option.textContent = version.name;
  263. option.version = version;
  264. select.appendChild(option);
  265. });
  266. initializeToggleList(versions.versions, $('versionSelector'));
  267. initializeToggleList(pages.values(), $('pageSelector'));
  268. initializeToggleList(Group.groups.values(), $('groupSelector'));
  269. }
  270. function initializeToggleList(items, node) {
  271. let list = node.querySelector('ul');
  272. removeAllChildren(list);
  273. items = Array.from(items);
  274. items.sort(NameComparator);
  275. items.forEach((item) => {
  276. let li = document.createElement('li');
  277. let checkbox = document.createElement('input');
  278. checkbox.type = 'checkbox';
  279. checkbox.checked = item.enabled;
  280. checkbox.item = item;
  281. checkbox.addEventListener('click', handleToggleVersionOrPageEnable);
  282. li.appendChild(checkbox);
  283. li.appendChild(document.createTextNode(item.name));
  284. list.appendChild(li);
  285. });
  286. }
  287. window.addEventListener('popstate', (event) => {
  288. popHistoryState(event.state);
  289. });
  290. function popHistoryState(state) {
  291. if (!state.version) return false;
  292. if (!versions) return false;
  293. let version = versions.getByName(state.version);
  294. if (!version) return false;
  295. let page = version.get(state.page);
  296. if (!page) return false;
  297. if (!state.entry) {
  298. showEntry(page.total);
  299. } else {
  300. let entry = page.get(state.entry);
  301. if (!entry) {
  302. showEntry(page.total);
  303. } else {
  304. showEntry(entry);
  305. }
  306. }
  307. return true;
  308. }
  309. function pushHistoryState() {
  310. let selection = selectedEntry ? selectedEntry : selectedPage;
  311. if (!selection) return;
  312. let state = selection.urlParams();
  313. // Don't push a history state if it didn't change.
  314. if (JSON.stringify(window.history.state) === JSON.stringify(state)) return;
  315. let params = "?";
  316. for (let pairs of Object.entries(state)) {
  317. params += encodeURIComponent(pairs[0]) + "=" +
  318. encodeURIComponent(pairs[1]) + "&";
  319. }
  320. window.history.pushState(state, selection.toString(), params);
  321. }
  322. function showSelectedEntryInPage(page) {
  323. if (!selectedEntry) return showPage(page);
  324. let entry = page.get(selectedEntry.name);
  325. if (!entry) return showPage(page);
  326. selectEntry(entry);
  327. }
  328. function showPage(firstPage) {
  329. let changeSelectedEntry = selectedEntry !== undefined &&
  330. selectedEntry.page === selectedPage;
  331. selectedPage = firstPage;
  332. selectedPage.sort();
  333. showPageInColumn(firstPage, 0);
  334. // Show the other versions of this page in the following columns.
  335. let pageVersions = versions.getPageVersions(firstPage);
  336. let index = 1;
  337. pageVersions.forEach((page) => {
  338. if (page !== firstPage) {
  339. showPageInColumn(page, index);
  340. index++;
  341. }
  342. });
  343. if (changeSelectedEntry) {
  344. showEntryDetail(selectedPage.getEntry(selectedEntry));
  345. }
  346. showImpactList(selectedPage);
  347. pushHistoryState();
  348. }
  349. function clamp(value, min, max) {
  350. if (value < min) return min;
  351. if (value > max) return max;
  352. return value;
  353. }
  354. function diffColorFromRatio(ratio) {
  355. if (ratio == Infinity) {
  356. return '#ff0000';
  357. }
  358. if (ratio == -Infinity) {
  359. return '#00ff00';
  360. }
  361. if (ratio > 1) {
  362. // ratio > 1: #FFFFFF => #00FF00
  363. const red = clamp(((ratio - 1) * 255 * 10) | 0, 0, 255);
  364. const other = (255 - red).toString(16).padStart(2, '0');
  365. return `#ff${other}${other}`;
  366. }
  367. // ratio < 1: #FF0000 => #FFFFFF
  368. const green = clamp(((1 - ratio) * 255 * 10) | 0, 0, 255);
  369. const other = (255 - green).toString(16).padStart(2, '0');
  370. return `#${other}ff${other}`;
  371. }
  372. function showPageInColumn(page, columnIndex) {
  373. page.sort();
  374. let showDiff = columnIndex !== 0;
  375. if (baselineVersion) showDiff = page.version !== baselineVersion;
  376. let diffColor = (td, a, b) => { };
  377. if (showDiff) {
  378. if (baselineVersion) {
  379. diffColor = (td, diff, baseline) => {
  380. if (diff == 0) return;
  381. const ratio = (baseline + diff) / baseline;
  382. td.style.color = diffColorFromRatio(ratio);
  383. };
  384. } else {
  385. diffColor = (td, value, reference) => {
  386. if (value == reference) return;
  387. const ratio = value / reference;
  388. td.style.color = diffColorFromRatio(ratio);
  389. }
  390. }
  391. }
  392. let column = $('column_' + columnIndex);
  393. let select = $('select_' + columnIndex);
  394. // Find the matching option
  395. selectOption(select, (i, option) => {
  396. return option.page == page
  397. });
  398. let table = column.querySelector("table");
  399. let oldTbody = table.querySelector('tbody');
  400. let tbody = document.createElement('tbody');
  401. let referencePage = selectedPage;
  402. page.forEachSorted(selectedPage, (parentEntry, entry, referenceEntry) => {
  403. let tr = document.createElement('tr');
  404. tbody.appendChild(tr);
  405. tr.entry = entry;
  406. tr.parentEntry = parentEntry;
  407. tr.className = parentEntry === undefined ? 'parent' : 'child';
  408. // Don't show entries that do not exist on the current page or if we
  409. // compare against the current page
  410. if (entry !== undefined && page.version !== baselineVersion) {
  411. // If we show a diff, use the baselineVersion as the referenceEntry
  412. if (baselineVersion !== undefined) {
  413. let baselineEntry = baselineVersion.getEntry(entry);
  414. if (baselineEntry !== undefined) referenceEntry = baselineEntry
  415. }
  416. if (!parentEntry) {
  417. let node = td(tr, '<div class="toggle">►</div>', 'position');
  418. node.firstChild.addEventListener('click', handleToggleGroup);
  419. } else {
  420. td(tr, entry.position == 0 ? '' : entry.position, 'position');
  421. }
  422. addCodeSearchButton(entry,
  423. td(tr, entry.name, 'name ' + entry.cssClass()));
  424. diffColor(
  425. td(tr, ms(entry.time), 'value time'),
  426. entry.time, referenceEntry.time);
  427. diffColor(
  428. td(tr, percent(entry.timePercent), 'value time'),
  429. entry.time, referenceEntry.time);
  430. diffColor(
  431. td(tr, count(entry.count), 'value count'),
  432. entry.count, referenceEntry.count);
  433. } else if (baselineVersion !== undefined && referenceEntry &&
  434. page.version !== baselineVersion) {
  435. // Show comparison of entry that does not exist on the current page.
  436. tr.entry = new Entry(0, referenceEntry.name);
  437. tr.entry.page = page;
  438. td(tr, '-', 'position');
  439. td(tr, referenceEntry.name, 'name');
  440. diffColor(
  441. td(tr, ms(referenceEntry.time), 'value time'),
  442. referenceEntry.time, 0);
  443. diffColor(
  444. td(tr, percent(referenceEntry.timePercent), 'value time'),
  445. referenceEntry.timePercent, 0);
  446. diffColor(
  447. td(tr, count(referenceEntry.count), 'value count'),
  448. referenceEntry.count, 0);
  449. } else {
  450. // Display empty entry / baseline entry
  451. let showBaselineEntry = entry !== undefined;
  452. if (showBaselineEntry) {
  453. if (!parentEntry) {
  454. let node = td(tr, '<div class="toggle">►</div>', 'position');
  455. node.firstChild.addEventListener('click', handleToggleGroup);
  456. } else {
  457. td(tr, entry.position == 0 ? '' : entry.position, 'position');
  458. }
  459. td(tr, entry.name, 'name');
  460. td(tr, ms(entry.time, false), 'value time');
  461. td(tr, percent(entry.timePercent, false), 'value time');
  462. td(tr, count(entry.count, false), 'value count');
  463. } else {
  464. td(tr, '-', 'position');
  465. td(tr, referenceEntry.name, 'name');
  466. td(tr, '-', 'value time');
  467. td(tr, '-', 'value time');
  468. td(tr, '-', 'value count');
  469. }
  470. }
  471. });
  472. table.replaceChild(tbody, oldTbody);
  473. let versionSelect = column.querySelector('select.version');
  474. selectOption(versionSelect, (index, option) => {
  475. return option.version == page.version
  476. });
  477. }
  478. function showEntry(entry) {
  479. selectEntry(entry, true);
  480. }
  481. function selectEntry(entry, updateSelectedPage) {
  482. let needsPageSwitch = true;
  483. if (updateSelectedPage && selectedPage) {
  484. entry = selectedPage.version.getEntry(entry);
  485. needsPageSwitch = updateSelectedPage && entry.page != selectedPage;
  486. }
  487. let rowIndex = 0;
  488. // If clicked in the detail row change the first column to that page.
  489. if (needsPageSwitch) showPage(entry.page);
  490. let childNodes = $('column_0').querySelector('.list tbody').childNodes;
  491. for (let i = 0; i < childNodes.length; i++) {
  492. if (childNodes[i].entry !== undefined &&
  493. childNodes[i].entry.name == entry.name) {
  494. rowIndex = i;
  495. break;
  496. }
  497. }
  498. let firstEntry = childNodes[rowIndex].entry;
  499. if (rowIndex) {
  500. if (firstEntry.parent) showGroup(firstEntry.parent);
  501. }
  502. // Deselect all
  503. $('view').querySelectorAll('.list tbody tr').forEach((tr) => {
  504. toggleCssClass(tr, 'selected', false);
  505. });
  506. // Select the entry row
  507. $('view').querySelectorAll("tbody").forEach((body) => {
  508. let row = body.childNodes[rowIndex];
  509. if (!row) return;
  510. toggleCssClass(row, 'selected', row.entry && row.entry.name ==
  511. firstEntry.name);
  512. });
  513. if (updateSelectedPage && selectedEntry) {
  514. entry = selectedEntry.page.version.getEntry(entry);
  515. }
  516. if (entry !== selectedEntry) {
  517. selectedEntry = entry;
  518. showEntryDetail(entry);
  519. }
  520. }
  521. function showEntryDetail(entry) {
  522. showVersionDetails(entry);
  523. showPageDetails(entry);
  524. showImpactList(entry.page);
  525. showGraphs(entry.page);
  526. pushHistoryState();
  527. }
  528. function showVersionDetails(entry) {
  529. let table, tbody, entries;
  530. table = $('versionDetails').querySelector('.versionDetailTable');
  531. tbody = document.createElement('tbody');
  532. if (entry !== undefined) {
  533. $('versionDetails').querySelector('h2 span').textContent =
  534. entry.name + ' in ' + entry.page.name;
  535. entries = versions.getPageVersions(entry.page).map(
  536. (page) => {
  537. return page.get(entry.name)
  538. });
  539. entries.sort((a, b) => {
  540. return a.time - b.time
  541. });
  542. entries.forEach((pageEntry) => {
  543. if (pageEntry === undefined) return;
  544. let tr = document.createElement('tr');
  545. if (pageEntry == entry) tr.className += 'selected';
  546. tr.entry = pageEntry;
  547. let isBaselineEntry = pageEntry.page.version == baselineVersion;
  548. td(tr, pageEntry.page.version.name, 'version');
  549. td(tr, ms(pageEntry.time, !isBaselineEntry), 'value time');
  550. td(tr, percent(pageEntry.timePercent, !isBaselineEntry), 'value time');
  551. td(tr, count(pageEntry.count, !isBaselineEntry), 'value count');
  552. tbody.appendChild(tr);
  553. });
  554. }
  555. table.replaceChild(tbody, table.querySelector('tbody'));
  556. }
  557. function showPageDetails(entry) {
  558. let table, tbody, entries;
  559. table = $('pageDetail').querySelector('.pageDetailTable');
  560. tbody = document.createElement('tbody');
  561. if (entry === undefined) {
  562. table.replaceChild(tbody, table.querySelector('tbody'));
  563. return;
  564. }
  565. let version = entry.page.version;
  566. let showDiff = version !== baselineVersion;
  567. $('pageDetail').querySelector('h2 span').textContent =
  568. version.name;
  569. entries = version.pages.map((page) => {
  570. if (!page.enabled) return;
  571. return page.get(entry.name)
  572. });
  573. entries.sort((a, b) => {
  574. let cmp = b.timePercent - a.timePercent;
  575. if (cmp.toFixed(1) == 0) return b.time - a.time;
  576. return cmp
  577. });
  578. entries.forEach((pageEntry) => {
  579. if (pageEntry === undefined) return;
  580. let tr = document.createElement('tr');
  581. if (pageEntry === entry) tr.className += 'selected';
  582. tr.entry = pageEntry;
  583. td(tr, pageEntry.page.name, 'name');
  584. td(tr, ms(pageEntry.time, showDiff), 'value time');
  585. td(tr, percent(pageEntry.timePercent, showDiff), 'value time');
  586. td(tr, percent(pageEntry.timePercentPerEntry, showDiff),
  587. 'value time hideNoDiff');
  588. td(tr, count(pageEntry.count, showDiff), 'value count');
  589. tbody.appendChild(tr);
  590. });
  591. // show the total for all pages
  592. let tds = table.querySelectorAll('tfoot td');
  593. tds[1].textContent = ms(entry.getTimeImpact(), showDiff);
  594. // Only show the percentage total if we are in diff mode:
  595. tds[2].textContent = percent(entry.getTimePercentImpact(), showDiff);
  596. tds[3].textContent = '';
  597. tds[4].textContent = count(entry.getCountImpact(), showDiff);
  598. table.replaceChild(tbody, table.querySelector('tbody'));
  599. }
  600. function showImpactList(page) {
  601. let impactView = $('impactView');
  602. impactView.querySelector('h2 span').textContent = page.version.name;
  603. let table = impactView.querySelector('table');
  604. let tbody = document.createElement('tbody');
  605. let version = page.version;
  606. let entries = version.allEntries();
  607. if (selectedEntry !== undefined && selectedEntry.isGroup) {
  608. impactView.querySelector('h2 span').textContent += " " + selectedEntry.name;
  609. entries = entries.filter((entry) => {
  610. return entry.name == selectedEntry.name ||
  611. (entry.parent && entry.parent.name == selectedEntry.name)
  612. });
  613. }
  614. let isCompareView = baselineVersion !== undefined;
  615. entries = entries.filter((entry) => {
  616. if (isCompareView) {
  617. let impact = entry.getTimeImpact();
  618. return impact < -1 || 1 < impact
  619. }
  620. return entry.getTimePercentImpact() > 0.01;
  621. });
  622. entries = entries.slice(0, 50);
  623. entries.sort((a, b) => {
  624. let cmp = b.getTimePercentImpact() - a.getTimePercentImpact();
  625. if (isCompareView || cmp.toFixed(1) == 0) {
  626. return b.getTimeImpact() - a.getTimeImpact();
  627. }
  628. return cmp
  629. });
  630. entries.forEach((entry) => {
  631. let tr = document.createElement('tr');
  632. tr.entry = entry;
  633. td(tr, entry.name, 'name');
  634. td(tr, ms(entry.getTimeImpact()), 'value time');
  635. let percentImpact = entry.getTimePercentImpact();
  636. td(tr, percentImpact > 1000 ? '-' : percent(percentImpact), 'value time');
  637. let topPages = entry.getPagesByPercentImpact().slice(0, 3)
  638. .map((each) => {
  639. return each.name + ' (' + percent(each.getEntry(entry).timePercent) +
  640. ')'
  641. });
  642. td(tr, topPages.join(', '), 'name');
  643. tbody.appendChild(tr);
  644. });
  645. table.replaceChild(tbody, table.querySelector('tbody'));
  646. }
  647. function showGraphs(page) {
  648. let groups = page.groups.filter(each => each.enabled && !each.isTotal);
  649. // Sort groups by the biggest impact
  650. groups.sort((a, b) => b.getTimeImpact() - a.getTimeImpact());
  651. if (selectedGroup == undefined) {
  652. selectedGroup = groups[0];
  653. } else {
  654. groups = groups.filter(each => each.name != selectedGroup.name);
  655. if (!selectedGroup.isTotal && selectedGroup.enabled) {
  656. groups.unshift(selectedGroup);
  657. }
  658. }
  659. // Display graphs delayed for a snappier UI.
  660. setTimeout(() => {
  661. showPageVersionGraph(groups, page);
  662. showPageGraph(groups, page);
  663. showVersionGraph(groups, page);
  664. }, 10);
  665. }
  666. function getGraphDataTable(groups, page) {
  667. let dataTable = new google.visualization.DataTable();
  668. dataTable.addColumn('string', 'Name');
  669. groups.forEach(group => {
  670. let column = dataTable.addColumn('number', group.name.substring(6));
  671. dataTable.setColumnProperty(column, 'group', group);
  672. column = dataTable.addColumn({
  673. role: "annotation"
  674. });
  675. dataTable.setColumnProperty(column, 'group', group);
  676. });
  677. let column = dataTable.addColumn('number', 'Chart Total');
  678. dataTable.setColumnProperty(column, 'group', page.total);
  679. column = dataTable.addColumn({
  680. role: "annotation"
  681. });
  682. dataTable.setColumnProperty(column, 'group', page.total);
  683. return dataTable;
  684. }
  685. let selectedGroup;
  686. class ChartRow {
  687. static kSortFirstValueRelative(chartRow) {
  688. if (selectedGroup?.isTotal) return chartRow.total;
  689. return chartRow.data[0] / chartRow.total;
  690. }
  691. static kSortByFirstValue(chartRow) {
  692. if (selectedGroup?.isTotal) return chartRow.total;
  693. return chartRow.data[0];
  694. }
  695. constructor(linkedPage, label, sortValue_fn, data,
  696. excludeFromAverage = false) {
  697. this.linkedPage = linkedPage;
  698. this.label = label;
  699. if (!Array.isArray(data)) {
  700. throw new Error("Provide an Array for data");
  701. }
  702. this.data = data;
  703. this.total = 0;
  704. for (let i = 0; i < data.length; i++) this.total += data[i];
  705. this.sortValue = sortValue_fn(this);
  706. this.excludeFromAverage = excludeFromAverage;
  707. }
  708. forDataTable(maxRowsTotal) {
  709. // row = [label, entry1, annotation1, entry2, annotation2, ...]
  710. const rowData = [this.label];
  711. const kShowLabelLimit = 0.1;
  712. const kMinLabelWidth = 80;
  713. const chartWidth = window.innerWidth - 400;
  714. // Add value,label pairs
  715. for (let i = 0; i < this.data.length; i++) {
  716. const value = this.data[i];
  717. let label = '';
  718. // Only show labels for entries that are large enough..
  719. if (Math.abs(value / maxRowsTotal) * chartWidth > kMinLabelWidth) {
  720. label = ms(value);
  721. }
  722. rowData.push(value, label);
  723. }
  724. // Add the total row, with very small negative dummy entry for correct
  725. // placement of labels in diff view.
  726. rowData.push(this.total >= 0 ? 0 : -0.000000001, ms(this.total));
  727. return rowData;
  728. }
  729. }
  730. const collator = new Intl.Collator('en-UK');
  731. function setDataTableRows(dataTable, rows) {
  732. let skippedRows = 0;
  733. // Always sort by the selected entry (first column after the label)
  734. if (sortByLabel) {
  735. rows.sort((a, b) => collator.compare(a.label, b.label));
  736. } else {
  737. rows.sort((a, b) => b.sortValue - a.sortValue);
  738. }
  739. // Aggregate row data for Average/SUM chart entry:
  740. const aggregateData = rows[0].data.slice().fill(0);
  741. let maxTotal = 0;
  742. for (let i = 0; i < rows.length; i++) {
  743. const row = rows[i];
  744. let total = Math.abs(row.total);
  745. if (total > maxTotal) maxTotal = total;
  746. if (row.excludeFromAverage) {
  747. skippedRows++;
  748. continue
  749. }
  750. const chartRowData = row.data;
  751. for (let j = 0; j < chartRowData.length; j++) {
  752. aggregateData[j] += chartRowData[j];
  753. }
  754. }
  755. const length = rows.length - skippedRows;
  756. for (let i = 0; i < aggregateData.length; i++) {
  757. aggregateData[i] /= rows.length;
  758. }
  759. const averageRow = new ChartRow(undefined, 'Average',
  760. ChartRow.kSortByFirstValue, aggregateData);
  761. dataTable.addRow(averageRow.forDataTable());
  762. rows.forEach(chartRow => {
  763. let rowIndex = dataTable.addRow(chartRow.forDataTable(maxTotal));
  764. dataTable.setRowProperty(rowIndex, 'page', chartRow.linkedPage);
  765. });
  766. }
  767. function showPageVersionGraph(groups, page) {
  768. let dataTable = getGraphDataTable(groups, page);
  769. let vs = versions.getPageVersions(page);
  770. // Calculate the entries for the versions
  771. const rows = vs.map(page => new ChartRow(
  772. page, page.version.name, ChartRow.kSortByFirstValue,
  773. groups.map(group => page.getEntry(group).time),
  774. page.version === baselineVersion));
  775. renderGraph(`Versions for ${page.name}`, groups, dataTable, rows,
  776. 'pageVersionGraph', true);
  777. }
  778. function showPageGraph(groups, page) {
  779. let isDiffView = baselineVersion !== undefined;
  780. let dataTable = getGraphDataTable(groups, page);
  781. // Calculate the average row
  782. // Sort the pages by the selected group.
  783. let pages = page.version.pages.filter(page => page.enabled);
  784. // Calculate the entries for the pages
  785. const rows = pages.map(page => new ChartRow(
  786. page, page.name,
  787. isDiffView ?
  788. ChartRow.kSortByFirstValue : ChartRow.kSortFirstValueRelative,
  789. groups.map(group => page.getEntry(group).time)));
  790. renderGraph(`Pages for ${page.version.name}`, groups, dataTable, rows,
  791. 'pageGraph', isDiffView ? true : 'percent');
  792. }
  793. function showVersionGraph(groups, page) {
  794. let dataTable = getGraphDataTable(groups, page);
  795. let vs = versions.versions.filter(version => version.enabled);
  796. // Calculate the entries for the versions
  797. const rows = vs.map((version) => new ChartRow(
  798. version.get(page), version.name, ChartRow.kSortByFirstValue,
  799. groups.map(group => version.getEntry(group).getTimeImpact()),
  800. version === baselineVersion));
  801. renderGraph('Versions Total Time over all Pages', groups, dataTable, rows,
  802. 'versionGraph', true);
  803. }
  804. function renderGraph(title, groups, dataTable, rows, id, isStacked) {
  805. let isDiffView = baselineVersion !== undefined;
  806. setDataTableRows(dataTable, rows);
  807. let formatter = new google.visualization.NumberFormat({
  808. suffix: (isDiffView ? 'msΔ' : 'ms'),
  809. negativeColor: 'red',
  810. groupingSymbol: "'"
  811. });
  812. for (let i = 1; i < dataTable.getNumberOfColumns(); i++) {
  813. formatter.format(dataTable, i);
  814. }
  815. let height = 85 + 28 * dataTable.getNumberOfRows();
  816. let options = {
  817. isStacked: isStacked,
  818. height: height,
  819. hAxis: {
  820. minValue: 0,
  821. textStyle: {
  822. fontSize: 14
  823. }
  824. },
  825. vAxis: {
  826. textStyle: {
  827. fontSize: 14
  828. }
  829. },
  830. tooltip: {
  831. textStyle: {
  832. fontSize: 14
  833. }
  834. },
  835. annotations: {
  836. textStyle: {
  837. fontSize: 8
  838. }
  839. },
  840. explorer: {
  841. actions: ['dragToZoom', 'rightClickToReset'],
  842. maxZoomIn: 0.01
  843. },
  844. legend: {
  845. position: 'top',
  846. maxLines: 3,
  847. textStyle: {
  848. fontSize: 12
  849. }
  850. },
  851. chartArea: {
  852. left: 200,
  853. top: 50
  854. },
  855. colors: [
  856. ...groups.map(each => each.color),
  857. /* Chart Total */
  858. "#000000",
  859. ]
  860. };
  861. let parentNode = $(id);
  862. parentNode.querySelector('h2>span, h3>span').textContent = title;
  863. let graphNode = parentNode.querySelector('.panelBody');
  864. let chart = graphNode.chart;
  865. if (chart === undefined) {
  866. chart = graphNode.chart = new google.visualization.BarChart(graphNode);
  867. } else {
  868. google.visualization.events.removeAllListeners(chart);
  869. }
  870. google.visualization.events.addListener(chart, 'select', selectHandler);
  871. function getChartEntry(selection) {
  872. if (!selection) return undefined;
  873. let column = selection.column;
  874. if (column == undefined) return undefined;
  875. let selectedGroup = dataTable.getColumnProperty(column, 'group');
  876. let row = selection.row;
  877. if (row == null) return selectedGroup;
  878. let page = dataTable.getRowProperty(row, 'page');
  879. if (!page) return selectedGroup;
  880. return page.getEntry(selectedGroup);
  881. }
  882. function selectHandler(e) {
  883. const newSelectedGroup = getChartEntry(chart.getSelection()[0]);
  884. if (newSelectedGroup == selectedGroup) {
  885. sortByLabel = !sortByLabel;
  886. } else if (newSelectedGroup === undefined && selectedPage) {
  887. sortByLabel = true;
  888. return showGraphs(selectedPage);
  889. } else {
  890. sortByLabel = false;
  891. }
  892. selectedGroup = newSelectedGroup;
  893. selectEntry(selectedGroup, true);
  894. }
  895. // Make our global tooltips work
  896. google.visualization.events.addListener(chart, 'onmouseover', mouseOverHandler);
  897. function mouseOverHandler(selection) {
  898. const selectedGroup = getChartEntry(selection);
  899. graphNode.entry = selectedGroup;
  900. }
  901. chart.draw(dataTable, options);
  902. }
  903. function showGroup(entry) {
  904. toggleGroup(entry, true);
  905. }
  906. function toggleGroup(group, show) {
  907. $('view').querySelectorAll(".child").forEach((tr) => {
  908. let entry = tr.parentEntry;
  909. if (!entry) return;
  910. if (entry.name !== group.name) return;
  911. toggleCssClass(tr, 'visible', show);
  912. });
  913. }
  914. function showPopover(entry) {
  915. let popover = $('popover');
  916. popover.querySelector('td.name').textContent = entry.name;
  917. popover.querySelector('td.page').textContent = entry.page.name;
  918. setPopoverDetail(popover, entry, '');
  919. popover.querySelector('table').className = "";
  920. if (baselineVersion !== undefined) {
  921. entry = baselineVersion.getEntry(entry);
  922. setPopoverDetail(popover, entry, '.compare');
  923. popover.querySelector('table').className = "compare";
  924. }
  925. }
  926. function setPopoverDetail(popover, entry, prefix) {
  927. let node = (name) => popover.querySelector(prefix + name);
  928. if (entry == undefined) {
  929. node('.version').textContent = baselineVersion.name;
  930. node('.time').textContent = '-';
  931. node('.timeVariance').textContent = '-';
  932. node('.percent').textContent = '-';
  933. node('.percentPerEntry').textContent = '-';
  934. node('.percentVariance').textContent = '-';
  935. node('.count').textContent = '-';
  936. node('.countVariance').textContent = '-';
  937. node('.timeImpact').textContent = '-';
  938. node('.timePercentImpact').textContent = '-';
  939. } else {
  940. node('.version').textContent = entry.page.version.name;
  941. node('.time').textContent = ms(entry._time, false);
  942. node('.timeVariance').textContent = percent(entry.timeVariancePercent, false);
  943. node('.percent').textContent = percent(entry.timePercent, false);
  944. node('.percentPerEntry').textContent = percent(entry.timePercentPerEntry, false);
  945. node('.percentVariance').textContent = percent(entry.timePercentVariancePercent, false);
  946. node('.count').textContent = count(entry._count, false);
  947. node('.countVariance').textContent = percent(entry.timeVariancePercent, false);
  948. node('.timeImpact').textContent = ms(entry.getTimeImpact(false), false);
  949. node('.timePercentImpact').textContent = percent(entry.getTimeImpactVariancePercent(false), false);
  950. }
  951. }
  952. </script>
  953. <script>
  954. "use strict"
  955. // =========================================================================
  956. // Helpers
  957. function $(id) {
  958. return document.getElementById(id)
  959. }
  960. function removeAllChildren(node) {
  961. while (node.firstChild) {
  962. node.removeChild(node.firstChild);
  963. }
  964. }
  965. function selectOption(select, match) {
  966. let options = select.options;
  967. for (let i = 0; i < options.length; i++) {
  968. if (match(i, options[i])) {
  969. select.selectedIndex = i;
  970. return;
  971. }
  972. }
  973. }
  974. function addCodeSearchButton(entry, node) {
  975. if (entry.isGroup) return;
  976. let button = document.createElement("div");
  977. button.textContent = '?'
  978. button.className = "codeSearch"
  979. button.addEventListener('click', handleCodeSearch);
  980. node.appendChild(button);
  981. return node;
  982. }
  983. function td(tr, content, className) {
  984. let td = document.createElement("td");
  985. if (content[0] == '<') {
  986. td.innerHTML = content;
  987. } else {
  988. td.textContent = content;
  989. }
  990. td.className = className
  991. tr.appendChild(td);
  992. return td
  993. }
  994. function nodeIndex(node) {
  995. let children = node.parentNode.childNodes,
  996. i = 0;
  997. for (; i < children.length; i++) {
  998. if (children[i] == node) {
  999. return i;
  1000. }
  1001. }
  1002. return -1;
  1003. }
  1004. function toggleCssClass(node, cssClass, toggleState = true) {
  1005. let index = -1;
  1006. let classes;
  1007. if (node.className != undefined) {
  1008. classes = node.className.split(' ');
  1009. index = classes.indexOf(cssClass);
  1010. }
  1011. if (index == -1) {
  1012. if (toggleState === false) return;
  1013. node.className += ' ' + cssClass;
  1014. return;
  1015. }
  1016. if (toggleState === true) return;
  1017. classes.splice(index, 1);
  1018. node.className = classes.join(' ');
  1019. }
  1020. function NameComparator(a, b) {
  1021. if (a.name > b.name) return 1;
  1022. if (a.name < b.name) return -1;
  1023. return 0
  1024. }
  1025. function diffSign(value, digits, unit, showDiff) {
  1026. if (showDiff === false || baselineVersion == undefined) {
  1027. if (value === undefined) return '';
  1028. return value.toFixed(digits) + unit;
  1029. }
  1030. return (value >= 0 ? '+' : '') + value.toFixed(digits) + unit + 'Δ';
  1031. }
  1032. function ms(value, showDiff) {
  1033. return diffSign(value, 1, 'ms', showDiff);
  1034. }
  1035. function count(value, showDiff) {
  1036. return diffSign(value, 0, '#', showDiff);
  1037. }
  1038. function percent(value, showDiff) {
  1039. return diffSign(value, 1, '%', showDiff);
  1040. }
  1041. </script>
  1042. <script>
  1043. "use strict"
  1044. // =========================================================================
  1045. // EventHandlers
  1046. async function handleBodyLoad() {
  1047. $('uploadInput').focus();
  1048. if (tryLoadDefaultData() || await tryLoadFromURLParams() ||
  1049. await tryLoadDefaultResults()) {
  1050. displayResultsAfterLoading();
  1051. }
  1052. }
  1053. function tryLoadDefaultData() {
  1054. if (!defaultData) return false;
  1055. handleLoadJSON(defaultData);
  1056. return true;
  1057. }
  1058. async function tryLoadFromURLParams() {
  1059. let params = new URLSearchParams(document.location.search);
  1060. let hasFile = false;
  1061. params.forEach(async (value, key) => {
  1062. if (key !== 'file') return;
  1063. hasFile ||= await tryLoadFile(value, true);
  1064. });
  1065. return hasFile;
  1066. }
  1067. async function tryLoadDefaultResults() {
  1068. if (window.location.protocol === 'file:') return false;
  1069. // Try to load a results.json file adjacent to this day.
  1070. // The markers on the following line can be used to replace the url easily
  1071. // with scripts.
  1072. const url = /*results-url-start*/ 'results.json' /*results-url-end*/;
  1073. return tryLoadFile(url);
  1074. }
  1075. async function tryLoadFile(url, append = false) {
  1076. if (!url.startsWith('http')) {
  1077. // hack to get relative urls
  1078. let location = window.location;
  1079. let parts = location.pathname.split("/").slice(0, -1);
  1080. url = location.origin + parts.join('/') + '/' + url;
  1081. }
  1082. let response = await fetch(url);
  1083. if (!response.ok) return false;
  1084. let filename = url.split('/');
  1085. filename = filename[filename.length - 1];
  1086. handleLoadText(await response.text(), append, filename);
  1087. return true;
  1088. }
  1089. function handleAppendFiles() {
  1090. let files = document.getElementById("appendInput").files;
  1091. loadFiles(files, true);
  1092. }
  1093. function handleLoadFiles() {
  1094. let files = document.getElementById("uploadInput").files;
  1095. loadFiles(files, false)
  1096. }
  1097. async function loadFiles(files, append) {
  1098. for (let i = 0; i < files.length; i++) {
  1099. const file = files[i];
  1100. console.log(file.name);
  1101. let text = await new Promise((resolve, reject) => {
  1102. const reader = new FileReader();
  1103. reader.onload = () => resolve(reader.result)
  1104. reader.readAsText(file);
  1105. });
  1106. handleLoadText(text, append, file.name);
  1107. // Only the first file might clear existing data, all sequent files
  1108. // are always append.
  1109. append = true;
  1110. }
  1111. displayResultsAfterLoading();
  1112. toggleCssClass(document.body, "loaded");
  1113. }
  1114. function handleLoadText(text, append, fileName) {
  1115. if (fileName.endsWith('.json')) {
  1116. handleLoadJSON(JSON.parse(text), append, fileName);
  1117. } else if (fileName.endsWith('.csv') ||
  1118. fileName.endsWith('.output') || fileName.endsWith('.output.txt')) {
  1119. handleLoadCSV(text, append, fileName);
  1120. } else if (fileName.endsWith('.txt')) {
  1121. handleLoadTXT(text, append, fileName);
  1122. } else {
  1123. alert(`Unsupported file extension: "${fileName}"`);
  1124. }
  1125. }
  1126. function getStateFromParams() {
  1127. let query = window.location.search.substr(1);
  1128. let result = {};
  1129. query.split("&").forEach((part) => {
  1130. let item = part.split("=");
  1131. let key = decodeURIComponent(item[0])
  1132. result[key] = decodeURIComponent(item[1]);
  1133. });
  1134. return result;
  1135. }
  1136. function handleLoadJSON(json, append, fileName) {
  1137. json = fixClusterTelemetryResults(json);
  1138. json = fixTraceImportJSON(json);
  1139. json = fixSingleVersionJSON(json, fileName);
  1140. let isFirstLoad = pages === undefined;
  1141. if (append && !isFirstLoad) {
  1142. json = createUniqueVersions(json);
  1143. }
  1144. if (!append || isFirstLoad) {
  1145. pages = new Pages();
  1146. versions = Versions.fromJSON(json);
  1147. } else {
  1148. Versions.fromJSON(json).forEach(e => versions.add(e))
  1149. }
  1150. }
  1151. function handleLoadCSV(csv, append, fileName) {
  1152. let isFirstLoad = pages === undefined;
  1153. if (!append || isFirstLoad) {
  1154. pages = new Pages();
  1155. versions = new Versions();
  1156. }
  1157. const lines = csv.split(/\r?\n/);
  1158. // The first line contains only the field names.
  1159. const fields = new Map();
  1160. csvSplit(lines[0]).forEach((name, index) => {
  1161. fields.set(name, index);
  1162. });
  1163. if (fields.has('displayLabel') && fields.has('stories')) {
  1164. handleLoadResultCSV(fields, lines);
  1165. } else if (fields.has('page_name')) {
  1166. handleLoadClusterTelemetryCSV(fields, lines, fileName);
  1167. } else {
  1168. return alert("Unknown CSV format");
  1169. }
  1170. }
  1171. function csvSplit(line) {
  1172. let fields = [];
  1173. let index = 0;
  1174. while (index < line.length) {
  1175. let lastIndex = index;
  1176. if (line[lastIndex] == '"') {
  1177. index = line.indexOf('"', lastIndex + 1);
  1178. if (index < 0) index = line.length;
  1179. fields.push(line.substring(lastIndex + 1, index));
  1180. // Consume ','
  1181. index++;
  1182. } else {
  1183. index = line.indexOf(',', lastIndex);
  1184. if (index === -1) index = line.length;
  1185. fields.push(line.substring(lastIndex, index))
  1186. }
  1187. // Consume ','
  1188. index++;
  1189. }
  1190. return fields;
  1191. }
  1192. // Ignore the following categories as they are aggregated values and are
  1193. // created by callstats.html on the fly.
  1194. const import_skip_categories = new Set([
  1195. 'V8-Only', 'V8-Only-Main-Thread', 'Total-Main-Thread', 'Blink_Total'
  1196. ])
  1197. function handleLoadClusterTelemetryCSV(fields, lines, fileName) {
  1198. const rscFields = Array.from(fields.keys())
  1199. .filter(field => {
  1200. return field.endsWith(':duration (ms)') &&
  1201. !import_skip_categories.has(field.split(':')[0])
  1202. })
  1203. .map(field => {
  1204. let name = field.split(':')[0];
  1205. return [name, fields.get(field), fields.get(`${name}:count`)];
  1206. })
  1207. const page_name_i = fields.get('page_name');
  1208. const version = versions.getOrCreate(fileName);
  1209. for (let i = 1; i < lines.length; i++) {
  1210. const line = csvSplit(lines[i]);
  1211. if (line.length == 0) continue;
  1212. let page_name = line[page_name_i];
  1213. if (page_name === undefined) continue;
  1214. page_name = page_name.split(' ')[0];
  1215. const pageVersion = version.getOrCreate(page_name);
  1216. for (let [fieldName, duration_i, count_i] of rscFields) {
  1217. const duration = Number.parseFloat(line[duration_i]);
  1218. const count = Number.parseFloat(line[count_i]);
  1219. // Skip over entries without metrics (most likely crashes)
  1220. if (Number.isNaN(count) || Number.isNaN(duration)) {
  1221. console.warn(`BROKEN ${page_name}`, lines[i])
  1222. break;
  1223. }
  1224. pageVersion.add(new Entry(0, fieldName, duration, 0, 0, count, 0, 0))
  1225. }
  1226. }
  1227. }
  1228. function handleLoadResultCSV(fields, lines) {
  1229. const version_i = fields.get('displayLabel');
  1230. const page_i = fields.get('stories');
  1231. const category_i = fields.get('name');
  1232. const value_i = fields.get('avg');
  1233. const tempEntriesCache = new Map();
  1234. for (let i = 1; i < lines.length; i++) {
  1235. const line = csvSplit(lines[i]);
  1236. if (line.length == 0) continue;
  1237. const raw_category = line[category_i];
  1238. if (!raw_category.endsWith(':duration') &&
  1239. !raw_category.endsWith(':count')) {
  1240. continue;
  1241. }
  1242. let [category, type] = raw_category.split(':');
  1243. if (import_skip_categories.has(category)) continue;
  1244. const version = versions.getOrCreate(line[version_i]);
  1245. const pageVersion = version.getOrCreate(line[page_i]);
  1246. const value = Number.parseFloat(line[value_i]);
  1247. const entry = TempEntry.get(tempEntriesCache, pageVersion, category);
  1248. if (type == 'duration') {
  1249. entry.durations.push(value)
  1250. } else {
  1251. entry.counts.push(value)
  1252. }
  1253. }
  1254. tempEntriesCache.forEach((tempEntries, pageVersion) => {
  1255. tempEntries.forEach(tmpEntry => {
  1256. pageVersion.add(tmpEntry.toEntry())
  1257. })
  1258. });
  1259. }
  1260. class TempEntry {
  1261. constructor(category) {
  1262. this.category = category;
  1263. this.durations = [];
  1264. this.counts = [];
  1265. }
  1266. static get(cache, pageVersion, category) {
  1267. let tempEntries = cache.get(pageVersion);
  1268. if (tempEntries === undefined) {
  1269. tempEntries = new Map();
  1270. cache.set(pageVersion, tempEntries);
  1271. }
  1272. let tempEntry = tempEntries.get(category);
  1273. if (tempEntry === undefined) {
  1274. tempEntry = new TempEntry(category);
  1275. tempEntries.set(category, tempEntry);
  1276. }
  1277. return tempEntry;
  1278. }
  1279. toEntry() {
  1280. const [duration, durationStddev] = this.stats(this.durations);
  1281. const [count, countStddev] = this.stats(this.durations);
  1282. return new Entry(0, this.category,
  1283. duration, durationStddev, 0, count, countStddev, 0)
  1284. }
  1285. stats(values) {
  1286. let sum = 0;
  1287. for (let i = 0; i < values.length; i++) {
  1288. sum += values[i];
  1289. }
  1290. const avg = sum / values.length;
  1291. let stddevSquared = 0;
  1292. for (let i = 0; i < values.length; i++) {
  1293. const delta = values[i] - avg;
  1294. stddevSquared += delta * delta;
  1295. }
  1296. const stddev = Math.sqrt(stddevSquared / values.length);
  1297. return [avg, stddev];
  1298. }
  1299. }
  1300. function handleLoadTXT(txt, append, fileName) {
  1301. fileName = window.prompt('Version name:', fileName);
  1302. let isFirstLoad = pages === undefined;
  1303. // Load raw RCS output which contains a single page
  1304. if (!append || isFirstLoad) {
  1305. pages = new Pages();
  1306. versions = new Versions()
  1307. }
  1308. versions.add(Version.fromTXT(fileName, txt));
  1309. }
  1310. function displayResultsAfterLoading() {
  1311. const isFirstLoad = pages === undefined;
  1312. let state = getStateFromParams();
  1313. initialize()
  1314. if (isFirstLoad && !popHistoryState(state) && selectedPage) {
  1315. showEntry(selectedPage.total);
  1316. return;
  1317. }
  1318. const page = versions.versions[0].pages[0]
  1319. if (page == undefined) return;
  1320. showPage(page);
  1321. showEntry(page.total);
  1322. }
  1323. function fixClusterTelemetryResults(json) {
  1324. // Convert CT results to callstats compatible JSON
  1325. // Input:
  1326. // { VERSION_NAME: { PAGE: { METRIC: { "count": {XX}, "duration": {XX} }.. }}.. }
  1327. let firstEntry;
  1328. for (let key in json) {
  1329. firstEntry = json[key];
  1330. break;
  1331. }
  1332. // Return the original JSON if it is not a CT result.
  1333. if (firstEntry.pairs === undefined) return json;
  1334. // The results include already the group totals, remove them by filtering.
  1335. let groupNames = new Set(Array.from(Group.groups.values()).map(e => e.name));
  1336. let result = Object.create(null);
  1337. for (let file_name in json) {
  1338. let entries = [];
  1339. let file_data = json[file_name].pairs;
  1340. for (let name in file_data) {
  1341. if (name != "Total" && groupNames.has(name)) continue;
  1342. let entry = file_data[name];
  1343. let count = entry.count;
  1344. let time = entry.time;
  1345. entries.push([name, time, 0, 0, count, 0, 0]);
  1346. }
  1347. let domain = file_name.split("/").slice(-1)[0];
  1348. result[domain] = entries;
  1349. }
  1350. return {
  1351. __proto__: null,
  1352. ClusterTelemetry: result
  1353. };
  1354. }
  1355. function fixTraceImportJSON(json) {
  1356. // Fix json file that was created by converting a trace json output
  1357. if (!('telemetry-results' in json)) return json;
  1358. // { telemetry-results: { PAGE:[ { METRIC: [ COUNT TIME ], ... }, ... ]}}
  1359. let version_data = {
  1360. __proto__: null
  1361. };
  1362. json = json["telemetry-results"];
  1363. for (let page_name in json) {
  1364. if (page_name == "placeholder") continue;
  1365. let page_data = {
  1366. __proto__: null,
  1367. Total: {
  1368. duration: {
  1369. average: 0,
  1370. stddev: 0
  1371. },
  1372. count: {
  1373. average: 0,
  1374. stddev: 0
  1375. }
  1376. }
  1377. };
  1378. let page = json[page_name];
  1379. for (let slice of page) {
  1380. for (let metric_name in slice) {
  1381. if (metric_name == "Blink_V8") continue;
  1382. // sum up entries
  1383. if (!(metric_name in page_data)) {
  1384. page_data[metric_name] = {
  1385. duration: {
  1386. average: 0,
  1387. stddev: 0
  1388. },
  1389. count: {
  1390. average: 0,
  1391. stddev: 0
  1392. }
  1393. }
  1394. }
  1395. let [metric_count, metric_duration] = slice[metric_name]
  1396. let metric = page_data[metric_name];
  1397. const kMicroToMilli = 1 / 1000;
  1398. metric.duration.average += metric_duration * kMicroToMilli;
  1399. metric.count.average += metric_count;
  1400. if (metric_name.startsWith('Blink_')) continue;
  1401. let total = page_data['Total'];
  1402. total.duration.average += metric_duration * kMicroToMilli;
  1403. total.count.average += metric_count;
  1404. }
  1405. }
  1406. version_data[page_name] = page_data;
  1407. }
  1408. return version_data;
  1409. }
  1410. function fixSingleVersionJSON(json, name) {
  1411. // Try to detect the single-version case, where we're missing the toplevel
  1412. // version object. The incoming JSON is of the form:
  1413. // { PAGE: ... , PAGE_2: }
  1414. // Instead of the default multi-page JSON:
  1415. // {"Version 1": { "Page 1": ..., ...}, "Version 2": {...}, ...}
  1416. // In this case insert a single "Default" version as top-level entry.
  1417. const firstProperty = (object) => {
  1418. for (let key in object) return object[key];
  1419. };
  1420. const maybeMetrics = firstProperty(json);
  1421. const maybeMetric = firstProperty(maybeMetrics);
  1422. const tempName = name ? name : new Date().toISOString();
  1423. const getFileName =
  1424. () => window.prompt('Enter a name for the loaded file:', tempName);
  1425. if ('count' in maybeMetric && 'duration' in maybeMetric) {
  1426. return {
  1427. [getFileName()]: json
  1428. }
  1429. }
  1430. // Legacy fallback where the metrics are encoded as arrays:
  1431. // { PAGE: [[metric_name, ...], [...], ]}
  1432. // Also, make sure we don't have the versioned array-style:
  1433. // { VERSION: { PAGE: [[metric_name, ...], [...], ]}, ...}
  1434. const innerArray = firstProperty(maybeMetrics);
  1435. if (Array.isArray(maybeMetric) && !Array.isArray(innerArray)) {
  1436. return {
  1437. [getFileName()]: json
  1438. }
  1439. }
  1440. return json
  1441. }
  1442. let appendIndex = 0;
  1443. function createUniqueVersions(json) {
  1444. // Make sure all toplevel entries are unique names and added properly
  1445. appendIndex++;
  1446. let result = {
  1447. __proto__: null
  1448. }
  1449. for (let key in json) {
  1450. result[key + "_" + appendIndex] = json[key];
  1451. }
  1452. return result
  1453. }
  1454. function handleCopyToClipboard(event) {
  1455. const names = ["Group", ...versions.versions.map(e => e.name)];
  1456. let result = [names.join("\t")];
  1457. let groups = Array.from(Group.groups.values());
  1458. // Move the total group to the end.
  1459. groups.push(groups.shift())
  1460. groups.forEach(group => {
  1461. let row = [group.name];
  1462. versions.forEach(v => {
  1463. const time = v.pages[0].get("Group-" + group.name)?._time ?? 0;
  1464. row.push(time);
  1465. })
  1466. result.push(row.join("\t"));
  1467. });
  1468. result = result.join("\n");
  1469. navigator.clipboard.writeText(result);
  1470. }
  1471. function handleToggleGroup(event) {
  1472. let group = event.target.parentNode.parentNode.entry;
  1473. toggleGroup(selectedPage.get(group.name), 'toggle');
  1474. }
  1475. function handleSelectPage(select, event) {
  1476. let option = select.options[select.selectedIndex];
  1477. if (select.id == "select_0") {
  1478. showSelectedEntryInPage(option.page);
  1479. } else {
  1480. let columnIndex = select.id.split('_')[1];
  1481. showPageInColumn(option.page, columnIndex);
  1482. }
  1483. }
  1484. function handleSelectVersion(select, event) {
  1485. let option = select.options[select.selectedIndex];
  1486. let version = option.version;
  1487. if (select.id == "selectVersion_0") {
  1488. let page = version.get(selectedPage.name);
  1489. showSelectedEntryInPage(page);
  1490. } else {
  1491. let columnIndex = select.id.split('_')[1];
  1492. let pageSelect = $('select_' + columnIndex);
  1493. let page = pageSelect.options[pageSelect.selectedIndex].page;
  1494. page = version.get(page.name);
  1495. showPageInColumn(page, columnIndex);
  1496. }
  1497. }
  1498. function handleSelectDetailRow(table, event) {
  1499. if (event.target.tagName != 'TD') return;
  1500. let tr = event.target.parentNode;
  1501. if (tr.tagName != 'TR') return;
  1502. if (tr.entry === undefined) return;
  1503. selectEntry(tr.entry, true);
  1504. }
  1505. function handleSelectRow(table, event, fromDetail) {
  1506. if (event.target.tagName != 'TD') return;
  1507. let tr = event.target.parentNode;
  1508. if (tr.tagName != 'TR') return;
  1509. if (tr.entry === undefined) return;
  1510. selectEntry(tr.entry, false);
  1511. }
  1512. function handleSelectBaseline(select, event) {
  1513. let option = select.options[select.selectedIndex];
  1514. baselineVersion = option.version;
  1515. let showingDiff = baselineVersion !== undefined;
  1516. let body = $('body');
  1517. toggleCssClass(body, 'diff', showingDiff);
  1518. toggleCssClass(body, 'noDiff', !showingDiff);
  1519. showPage(selectedPage);
  1520. if (selectedEntry === undefined) return;
  1521. selectEntry(selectedEntry, true);
  1522. }
  1523. function findEntry(event) {
  1524. let target = event.target;
  1525. while (target.entry === undefined) {
  1526. target = target.parentNode;
  1527. if (!target) return undefined;
  1528. }
  1529. return target.entry;
  1530. }
  1531. function handleUpdatePopover(event) {
  1532. let popover = $('popover');
  1533. popover.style.left = event.pageX + 'px';
  1534. popover.style.top = event.pageY + 'px';
  1535. popover.style.display = 'none';
  1536. popover.style.display = event.shiftKey ? 'block' : 'none';
  1537. let entry = findEntry(event);
  1538. if (entry === undefined) return;
  1539. showPopover(entry);
  1540. }
  1541. function handleToggleVersionOrPageEnable(event) {
  1542. let item = this.item;
  1543. if (item === undefined) return;
  1544. item.enabled = this.checked;
  1545. initialize();
  1546. let page = selectedPage;
  1547. if (page === undefined || !page.version.enabled) {
  1548. page = versions.getEnabledPage(page.name);
  1549. }
  1550. if (!page.enabled) {
  1551. page = page.getNextPage();
  1552. }
  1553. showPage(page);
  1554. }
  1555. function handleCodeSearch(event) {
  1556. let entry = findEntry(event);
  1557. if (entry === undefined) return;
  1558. let url = "https://cs.chromium.org/search/?sq=package:chromium&type=cs&q=";
  1559. name = entry.name;
  1560. if (name.startsWith("API_")) {
  1561. name = name.substring(4);
  1562. }
  1563. url += encodeURIComponent(name) + "+file:src/v8/src";
  1564. window.open(url, '_blank');
  1565. }
  1566. </script>
  1567. <script>
  1568. "use strict"
  1569. // =========================================================================
  1570. class Versions {
  1571. constructor() {
  1572. this.versions = [];
  1573. }
  1574. add(version) {
  1575. this.versions.push(version);
  1576. return version;
  1577. }
  1578. getPageVersions(page) {
  1579. let result = [];
  1580. this.versions.forEach((version) => {
  1581. if (!version.enabled) return;
  1582. let versionPage = version.get(page.name);
  1583. if (versionPage !== undefined) result.push(versionPage);
  1584. });
  1585. return result;
  1586. }
  1587. get length() {
  1588. return this.versions.length
  1589. }
  1590. get(index) {
  1591. return this.versions[index]
  1592. }
  1593. getByName(name) {
  1594. return this.versions.find((each) => each.name == name);
  1595. }
  1596. getOrCreate(name) {
  1597. return this.getByName(name) ?? this.add(new Version(name));
  1598. }
  1599. forEach(f) {
  1600. this.versions.forEach(f);
  1601. }
  1602. sort() {
  1603. this.versions.sort(NameComparator);
  1604. }
  1605. getEnabledPage(name) {
  1606. for (let i = 0; i < this.versions.length; i++) {
  1607. let version = this.versions[i];
  1608. if (!version.enabled) continue;
  1609. let page = version.get(name);
  1610. if (page !== undefined) return page;
  1611. }
  1612. }
  1613. static fromJSON(json) {
  1614. let versions = new Versions();
  1615. for (let version in json) {
  1616. versions.add(Version.fromJSON(version, json[version]));
  1617. }
  1618. versions.sort();
  1619. return versions;
  1620. }
  1621. }
  1622. class Version {
  1623. constructor(name) {
  1624. this.name = name;
  1625. this.enabled = true;
  1626. this.pages = [];
  1627. }
  1628. add(page) {
  1629. this.pages.push(page);
  1630. return page;
  1631. }
  1632. indexOf(name) {
  1633. for (let i = 0; i < this.pages.length; i++) {
  1634. if (this.pages[i].name == name) return i;
  1635. }
  1636. return -1;
  1637. }
  1638. getNextPage(page) {
  1639. if (this.length == 0) return undefined;
  1640. return this.pages[(this.indexOf(page.name) + 1) % this.length];
  1641. }
  1642. get(name) {
  1643. let index = this.indexOf(name);
  1644. if (0 <= index) return this.pages[index];
  1645. return undefined;
  1646. }
  1647. getOrCreate(name) {
  1648. return this.get(name) ??
  1649. this.add(new PageVersion(this, pages.getOrCreate(name)));
  1650. }
  1651. get length() {
  1652. return this.pages.length;
  1653. }
  1654. getEntry(entry) {
  1655. if (entry === undefined) return undefined;
  1656. let page = this.get(entry.page.name);
  1657. if (page === undefined) return undefined;
  1658. return page.get(entry.name);
  1659. }
  1660. forEachEntry(fun) {
  1661. this.forEachPage((page) => {
  1662. page.forEach(fun);
  1663. });
  1664. }
  1665. forEachPage(fun) {
  1666. this.pages.forEach((page) => {
  1667. if (!page.enabled) return;
  1668. fun(page);
  1669. })
  1670. }
  1671. allEntries() {
  1672. let map = new Map();
  1673. this.forEachEntry((group, entry) => {
  1674. if (!map.has(entry.name)) map.set(entry.name, entry);
  1675. });
  1676. return Array.from(map.values());
  1677. }
  1678. getTotalValue(name, property) {
  1679. if (name === undefined) name = this.pages[0].total.name;
  1680. let sum = 0;
  1681. this.forEachPage((page) => {
  1682. let entry = page.get(name);
  1683. if (entry !== undefined) sum += entry[property];
  1684. });
  1685. return sum;
  1686. }
  1687. getTotalTime(name, showDiff) {
  1688. return this.getTotalValue(name, showDiff === false ? '_time' : 'time');
  1689. }
  1690. getTotalTimePercent(name, showDiff) {
  1691. if (baselineVersion === undefined || showDiff === false) {
  1692. // Return the overall average percent of the given entry name.
  1693. return this.getTotalValue(name, 'time') /
  1694. this.getTotalTime('Group-Total') * 100;
  1695. }
  1696. // Otherwise return the difference to the sum of the baseline version.
  1697. let baselineValue = baselineVersion.getTotalTime(name, false);
  1698. let total = this.getTotalValue(name, '_time');
  1699. return (total / baselineValue - 1) * 100;
  1700. }
  1701. getTotalTimeVariance(name, showDiff) {
  1702. // Calculate the overall error for a given entry name
  1703. let sum = 0;
  1704. this.forEachPage((page) => {
  1705. let entry = page.get(name);
  1706. if (entry === undefined) return;
  1707. sum += entry.timeVariance * entry.timeVariance;
  1708. });
  1709. return Math.sqrt(sum);
  1710. }
  1711. getTotalTimeVariancePercent(name, showDiff) {
  1712. return this.getTotalTimeVariance(name, showDiff) /
  1713. this.getTotalTime(name, showDiff) * 100;
  1714. }
  1715. getTotalCount(name, showDiff) {
  1716. return this.getTotalValue(name, showDiff === false ? '_count' : 'count');
  1717. }
  1718. getAverageTimeImpact(name, showDiff) {
  1719. return this.getTotalTime(name, showDiff) / this.pages.length;
  1720. }
  1721. getPagesByPercentImpact(name) {
  1722. let sortedPages =
  1723. this.pages.filter((each) => {
  1724. return each.get(name) !== undefined
  1725. });
  1726. sortedPages.sort((a, b) => {
  1727. return b.get(name).timePercent - a.get(name).timePercent;
  1728. });
  1729. return sortedPages;
  1730. }
  1731. sort() {
  1732. this.pages.sort(NameComparator)
  1733. }
  1734. static fromJSON(name, data) {
  1735. let version = new Version(name);
  1736. for (let pageName in data) {
  1737. version.add(PageVersion.fromJSON(version, pageName, data[pageName]));
  1738. }
  1739. version.sort();
  1740. return version;
  1741. }
  1742. static fromTXT(name, txt) {
  1743. let version = new Version(name);
  1744. let defaultName = "RAW DATA";
  1745. PageVersion.fromTXT(version, defaultName, txt)
  1746. .forEach(each => version.add(each));
  1747. return version;
  1748. }
  1749. }
  1750. class Pages extends Map {
  1751. get(name) {
  1752. if (name.indexOf('www.') == 0) {
  1753. name = name.substring(4);
  1754. }
  1755. if (!this.has(name)) {
  1756. this.set(name, new Page(name));
  1757. }
  1758. return super.get(name);
  1759. }
  1760. getOrCreate(name) {
  1761. return this.get(name);
  1762. }
  1763. }
  1764. class Page {
  1765. constructor(name) {
  1766. this.name = name;
  1767. this.enabled = true;
  1768. this.versions = [];
  1769. }
  1770. add(pageVersion) {
  1771. this.versions.push(pageVersion);
  1772. return pageVersion;
  1773. }
  1774. }
  1775. class PageVersion {
  1776. constructor(version, page) {
  1777. this.page = page;
  1778. this.page.add(this);
  1779. this.total = Group.groups.get('total').entry();
  1780. this.total.isTotal = true;
  1781. this.unclassified = new UnclassifiedEntry(this)
  1782. this.groups = [
  1783. this.total,
  1784. Group.groups.get('ic').entry(),
  1785. Group.groups.get('optimize-background').entry(),
  1786. Group.groups.get('optimize').entry(),
  1787. Group.groups.get('compile-background').entry(),
  1788. Group.groups.get('compile').entry(),
  1789. Group.groups.get('parse-background').entry(),
  1790. Group.groups.get('parse').entry(),
  1791. Group.groups.get('blink').entry(),
  1792. Group.groups.get('callback').entry(),
  1793. Group.groups.get('api').entry(),
  1794. Group.groups.get('gc-custom').entry(),
  1795. Group.groups.get('gc-background').entry(),
  1796. Group.groups.get('gc').entry(),
  1797. Group.groups.get('javascript').entry(),
  1798. Group.groups.get('websnapshot').entry(),
  1799. Group.groups.get('runtime').entry(),
  1800. this.unclassified
  1801. ];
  1802. this.entryDict = new Map();
  1803. this.groups.forEach((entry) => {
  1804. entry.page = this;
  1805. this.entryDict.set(entry.name, entry);
  1806. });
  1807. this.version = version;
  1808. }
  1809. toString() {
  1810. return this.version.name + ": " + this.name;
  1811. }
  1812. urlParams() {
  1813. return {
  1814. version: this.version.name,
  1815. page: this.name
  1816. };
  1817. }
  1818. add(entry) {
  1819. let existingEntry = this.entryDict.get(entry.name);
  1820. if (existingEntry !== undefined) {
  1821. // Duplicate entries happen when multiple runs are combined into a
  1822. // single file.
  1823. existingEntry.add(entry);
  1824. for (let i = 0; i < this.groups.length; i++) {
  1825. const group = this.groups[i];
  1826. if (group.addTimeAndCount(entry)) return;
  1827. }
  1828. } else {
  1829. // Ignore accidentally added Group entries.
  1830. if (entry.name.startsWith(GroupedEntry.prefix)) {
  1831. console.warn("Skipping accidentally added Group entry:", entry, this);
  1832. return;
  1833. }
  1834. entry.page = this;
  1835. this.entryDict.set(entry.name, entry);
  1836. for (let group of this.groups) {
  1837. if (group.add(entry)) return;
  1838. }
  1839. }
  1840. console.error("Should not get here", entry);
  1841. }
  1842. get(name) {
  1843. return this.entryDict.get(name)
  1844. }
  1845. getEntry(entry) {
  1846. if (entry === undefined) return undefined;
  1847. return this.get(entry.name);
  1848. }
  1849. get length() {
  1850. return this.versions.length
  1851. }
  1852. get name() {
  1853. return this.page.name
  1854. }
  1855. get enabled() {
  1856. return this.page.enabled
  1857. }
  1858. forEachSorted(referencePage, func) {
  1859. // Iterate over all the entries in the order they appear on the
  1860. // reference page.
  1861. referencePage.forEach((parent, referenceEntry) => {
  1862. let entry;
  1863. if (parent) parent = this.entryDict.get(parent.name);
  1864. if (referenceEntry) entry = this.entryDict.get(referenceEntry.name);
  1865. func(parent, entry, referenceEntry);
  1866. });
  1867. }
  1868. forEach(fun) {
  1869. this.forEachGroup((group) => {
  1870. fun(undefined, group);
  1871. group.forEach((entry) => {
  1872. fun(group, entry)
  1873. });
  1874. });
  1875. }
  1876. forEachGroup(fun) {
  1877. this.groups.forEach(fun)
  1878. }
  1879. sort() {
  1880. this.groups.sort((a, b) => {
  1881. return b.time - a.time;
  1882. });
  1883. this.groups.forEach((group) => {
  1884. group.sort()
  1885. });
  1886. }
  1887. distanceFromTotalPercent() {
  1888. let sum = 0;
  1889. this.groups.forEach(group => {
  1890. if (group == this.total) return;
  1891. let value = group.getTimePercentImpact() -
  1892. this.getEntry(group).timePercent;
  1893. sum += value * value;
  1894. });
  1895. return sum;
  1896. }
  1897. getNextPage() {
  1898. return this.version.getNextPage(this);
  1899. }
  1900. static fromJSON(version, name, data) {
  1901. let page = new PageVersion(version, pages.get(name));
  1902. // Distinguish between the legacy format which just uses Arrays,
  1903. // or the new object style.
  1904. if (Array.isArray(data)) {
  1905. for (let i = 0; i < data.length; i++) {
  1906. page.add(Entry.fromLegacyJSON(i, data[data.length - i - 1]));
  1907. }
  1908. } else {
  1909. let position = 0;
  1910. for (let metric_name in data) {
  1911. page.add(Entry.fromJSON(position, metric_name, data[metric_name]));
  1912. position++;
  1913. }
  1914. }
  1915. page.sort();
  1916. return page
  1917. }
  1918. static fromTXT(version, defaultName, txt) {
  1919. const kPageNameIdentifier = "== Page:";
  1920. const kCommentStart = "=="
  1921. const lines = txt.split('\n');
  1922. const split = / +/g
  1923. const result = [];
  1924. let pageVersion = undefined;
  1925. for (let i = 0; i < lines.length; i++) {
  1926. const line = lines[i];
  1927. // Skip header separators
  1928. if (line.startsWith(kCommentStart)) {
  1929. // Check for page names
  1930. if (line.startsWith(kPageNameIdentifier)) {
  1931. const name = line.split(kPageNameIdentifier)[1];
  1932. pageVersion = new PageVersion(version, pages.get(name));
  1933. result.push(pageVersion);
  1934. }
  1935. }
  1936. // Skip header lines.
  1937. if (lines[i + 1]?.startsWith(kCommentStart)) continue;
  1938. const split_line = line.trim().split(split)
  1939. if (split_line.length != 5) continue;
  1940. if (pageVersion === undefined) {
  1941. pageVersion = new PageVersion(version, pages.get(defaultName));
  1942. result.push(pageVersion);
  1943. }
  1944. const position = i - 2;
  1945. pageVersion.add(Entry.fromTXT(position, split_line));
  1946. }
  1947. return result;
  1948. }
  1949. }
  1950. class Entry {
  1951. constructor(position, name, time, timeVariance, timeVariancePercent,
  1952. count, countVariance, countVariancePercent) {
  1953. this.position = position;
  1954. this.name = name;
  1955. this._time = time;
  1956. this._timeVariance = timeVariance;
  1957. this._timeVariancePercent =
  1958. this._variancePercent(time, timeVariance, timeVariancePercent);
  1959. this._count = count;
  1960. this.countVariance = countVariance;
  1961. this.countVariancePercent =
  1962. this._variancePercent(count, countVariance, countVariancePercent);
  1963. this.page = undefined;
  1964. this.parent = undefined;
  1965. this.isTotal = false;
  1966. }
  1967. _variancePercent(value, valueVariance, valueVariancePercent) {
  1968. if (valueVariancePercent) return valueVariancePercent;
  1969. if (!valueVariance) return 0;
  1970. return valueVariance / value * 100;
  1971. }
  1972. add(entry) {
  1973. if (this.name !== entry.name) {
  1974. console.error("Should not combine entries with different names");
  1975. return;
  1976. }
  1977. this._time += entry._time;
  1978. this._count += entry._count;
  1979. }
  1980. urlParams() {
  1981. let params = this.page.urlParams();
  1982. params.entry = this.name;
  1983. return params;
  1984. }
  1985. getCompareWithBaseline(value, property) {
  1986. if (baselineVersion == undefined) return value;
  1987. let baselineEntry = baselineVersion.getEntry(this);
  1988. if (!baselineEntry) return value;
  1989. if (baselineVersion === this.page.version) return value;
  1990. return value - baselineEntry[property];
  1991. }
  1992. cssClass() {
  1993. return ''
  1994. }
  1995. get time() {
  1996. return this.getCompareWithBaseline(this._time, '_time');
  1997. }
  1998. get count() {
  1999. return this.getCompareWithBaseline(this._count, '_count');
  2000. }
  2001. get timePercent() {
  2002. let value = this._time / this.page.total._time * 100;
  2003. if (baselineVersion == undefined) return value;
  2004. let baselineEntry = baselineVersion.getEntry(this);
  2005. if (!baselineEntry) return value;
  2006. if (baselineVersion === this.page.version) return value;
  2007. return (this._time - baselineEntry._time) / this.page.total._time *
  2008. 100;
  2009. }
  2010. get timePercentPerEntry() {
  2011. let value = this._time / this.page.total._time * 100;
  2012. if (baselineVersion == undefined) return value;
  2013. let baselineEntry = baselineVersion.getEntry(this);
  2014. if (!baselineEntry) return value;
  2015. if (baselineVersion === this.page.version) return value;
  2016. return (this._time / baselineEntry._time - 1) * 100;
  2017. }
  2018. get timePercentVariancePercent() {
  2019. // Get the absolute values for the percentages
  2020. return this.timeVariance / this.page.total._time * 100;
  2021. }
  2022. getTimeImpact(showDiff) {
  2023. return this.page.version.getTotalTime(this.name, showDiff);
  2024. }
  2025. getTimeImpactVariancePercent(showDiff) {
  2026. return this.page.version.getTotalTimeVariancePercent(this.name, showDiff);
  2027. }
  2028. getTimePercentImpact(showDiff) {
  2029. return this.page.version.getTotalTimePercent(this.name, showDiff);
  2030. }
  2031. getCountImpact(showDiff) {
  2032. return this.page.version.getTotalCount(this.name, showDiff);
  2033. }
  2034. getAverageTimeImpact(showDiff) {
  2035. return this.page.version.getAverageTimeImpact(this.name, showDiff);
  2036. }
  2037. getPagesByPercentImpact() {
  2038. return this.page.version.getPagesByPercentImpact(this.name);
  2039. }
  2040. get isGroup() {
  2041. return false;
  2042. }
  2043. get timeVariance() {
  2044. return this._timeVariance;
  2045. }
  2046. get timeVariancePercent() {
  2047. return this._timeVariancePercent;
  2048. }
  2049. static fromLegacyJSON(position, data) {
  2050. return new Entry(position, ...data);
  2051. }
  2052. static fromJSON(position, name, data) {
  2053. let time = data.duration;
  2054. let count = data.count;
  2055. return new Entry(position, name, time.average, time.stddev, 0,
  2056. count.average, count.stddev, 0);
  2057. }
  2058. static fromTXT(position, splitLine) {
  2059. const name = splitLine[0];
  2060. let time = splitLine[1];
  2061. const msIndex = time.indexOf('m');
  2062. if (msIndex > 0) time = time.substring(0, msIndex);
  2063. const timePercent = splitLine[2];
  2064. const count = splitLine[3];
  2065. const countPercent = splitLine[4];
  2066. const timeDeviation = 0;
  2067. const countDeviation = 0;
  2068. const timeDeviationPercent = 0;
  2069. const countDeviationPercent = 0
  2070. return new Entry(position, name,
  2071. Number.parseFloat(time), timeDeviation, timeDeviationPercent,
  2072. Number.parseInt(count), countDeviation, countDeviationPercent)
  2073. }
  2074. }
  2075. class Group {
  2076. constructor(name, regexp, color, enabled = true, addsToTotal = true) {
  2077. this.name = name;
  2078. this.regexp = regexp;
  2079. this.color = color;
  2080. this.enabled = enabled;
  2081. this.addsToTotal = addsToTotal;
  2082. }
  2083. entry() {
  2084. return new GroupedEntry(this);
  2085. }
  2086. }
  2087. Group.groups = new Map();
  2088. Group.add = function (name, group) {
  2089. this.groups.set(name, group);
  2090. return group;
  2091. }
  2092. Group.add('total', new Group('Total', /.*Total.*/, '#BBB', true, false));
  2093. Group.add('ic', new Group('IC', /(.*IC_.*)|IC/, "#3366CC"));
  2094. Group.add('optimize-background', new Group('Optimize-Background',
  2095. /.*Optimize(d?-?)(Background|Concurrent).*/, "#702000"));
  2096. Group.add('optimize', new Group('Optimize',
  2097. /(StackGuard|Optimize|Deoptimize|Recompile).*/, "#DC3912"));
  2098. Group.add('compile-background', new Group('Compile-Background',
  2099. /(.*Compile-?Background.*)/, "#b08000"));
  2100. Group.add('compile', new Group('Compile',
  2101. /(^Compile.*)|(.*_Compile.*)/, "#FFAA00"));
  2102. Group.add('parse-background',
  2103. new Group('Parse-Background', /.*Parse-?Background.*/, "#c05000"));
  2104. Group.add('parse', new Group('Parse', /.*Parse.*/, "#FF6600"));
  2105. Group.add('callback',
  2106. new Group('Blink C++', /.*(Callback)|(Blink C\+\+).*/, "#109618"));
  2107. Group.add('api', new Group('API', /.*API.*/, "#990099"));
  2108. Group.add('gc-custom', new Group('GC-Custom', /GC_Custom_.*/, "#0099C6"));
  2109. Group.add('gc-background',
  2110. new Group(
  2111. 'GC-Background', /.*GC.*(BACKGROUND|Background).*/, "#00597c"));
  2112. Group.add('gc',
  2113. new Group('GC', /GC_.*|AllocateInTargetSpace|GC/, "#00799c"));
  2114. Group.add('javascript',
  2115. new Group('JavaScript', /JS_Execution|JavaScript/, "#DD4477"));
  2116. Group.add('websnapshot', new Group('WebSnapshot', /.*Web.*/, "#E8E11C"));
  2117. Group.add('runtime', new Group('V8 C++', /.*/, "#88BB00"));
  2118. Group.add('blink',
  2119. new Group('Blink RCS', /.*Blink_.*/, "#006600", false, false));
  2120. Group.add('unclassified', new Group('Unclassified', /.*/, "#000", false));
  2121. class GroupedEntry extends Entry {
  2122. constructor(group) {
  2123. super(0, GroupedEntry.prefix + group.name, 0, 0, 0, 0, 0, 0);
  2124. this.group = group;
  2125. this.entries = [];
  2126. this.missingEntries = null;
  2127. this.addsToTotal = group.addsToTotal;
  2128. }
  2129. get regexp() {
  2130. return this.group.regexp;
  2131. }
  2132. get color() {
  2133. return this.group.color;
  2134. }
  2135. get enabled() {
  2136. return this.group.enabled;
  2137. }
  2138. add(entry) {
  2139. if (!this.addTimeAndCount(entry)) return;
  2140. // TODO: sum up variance
  2141. this.entries.push(entry);
  2142. entry.parent = this;
  2143. return true;
  2144. }
  2145. addTimeAndCount(entry) {
  2146. if (!this.regexp.test(entry.name)) return false;
  2147. this._time += entry.time;
  2148. this._count += entry.count;
  2149. return true;
  2150. }
  2151. _initializeMissingEntries() {
  2152. let dummyEntryNames = new Set();
  2153. versions.forEach((version) => {
  2154. let page = version.getOrCreate(this.page.name);
  2155. let groupEntry = page.get(this.name);
  2156. if (groupEntry != this) {
  2157. for (let entry of groupEntry.entries) {
  2158. if (this.page.get(entry.name) == undefined) {
  2159. dummyEntryNames.add(entry.name);
  2160. }
  2161. }
  2162. }
  2163. });
  2164. this.missingEntries = [];
  2165. for (let name of dummyEntryNames) {
  2166. let tmpEntry = new Entry(0, name, 0, 0, 0, 0, 0, 0);
  2167. tmpEntry.page = this.page;
  2168. this.missingEntries.push(tmpEntry);
  2169. };
  2170. }
  2171. forEach(fun) {
  2172. // Show also all entries which are in at least one version.
  2173. // Concatenate our real entries.
  2174. if (this.missingEntries == null) {
  2175. this._initializeMissingEntries();
  2176. }
  2177. let tmpEntries = this.missingEntries.concat(this.entries);
  2178. // The compared entries are sorted by absolute impact.
  2179. tmpEntries.sort((a, b) => {
  2180. return b.time - a.time
  2181. });
  2182. tmpEntries.forEach(fun);
  2183. }
  2184. sort() {
  2185. this.entries.sort((a, b) => {
  2186. return b.time - a.time;
  2187. });
  2188. }
  2189. cssClass() {
  2190. if (this.page.total == this) return 'total';
  2191. return '';
  2192. }
  2193. get isGroup() {
  2194. return true
  2195. }
  2196. getVarianceForProperty(property) {
  2197. let sum = 0;
  2198. const key = property + 'Variance';
  2199. this.entries.forEach((entry) => {
  2200. const value = entry[key];
  2201. sum += value * value;
  2202. });
  2203. return Math.sqrt(sum);
  2204. }
  2205. get timeVariancePercent() {
  2206. if (this._time == 0) return 0;
  2207. return this.getVarianceForProperty('time') / this._time * 100
  2208. }
  2209. get timeVariance() {
  2210. return this.getVarianceForProperty('time')
  2211. }
  2212. }
  2213. GroupedEntry.prefix = 'Group-';
  2214. class UnclassifiedEntry extends GroupedEntry {
  2215. constructor(page) {
  2216. super(Group.groups.get('unclassified'));
  2217. this.page = page;
  2218. this._time = undefined;
  2219. this._count = undefined;
  2220. }
  2221. add(entry) {
  2222. console.log("Adding unclassified:", entry);
  2223. this.entries.push(entry);
  2224. entry.parent = this;
  2225. return true;
  2226. }
  2227. forEachPageGroup(fun) {
  2228. this.page.forEachGroup((group) => {
  2229. if (group == this) return;
  2230. if (group == this.page.total) return;
  2231. fun(group);
  2232. });
  2233. }
  2234. get time() {
  2235. if (this._time === undefined) {
  2236. this._time = this.page.total._time;
  2237. this.forEachPageGroup((group) => {
  2238. if (group.addsToTotal) this._time -= group._time;
  2239. });
  2240. }
  2241. return this.getCompareWithBaseline(this._time, '_time');
  2242. }
  2243. get count() {
  2244. if (this._count === undefined) {
  2245. this._count = this.page.total._count;
  2246. this.forEachPageGroup((group) => {
  2247. this._count -= group._count;
  2248. });
  2249. }
  2250. return this.getCompareWithBaseline(this._count, '_count');
  2251. }
  2252. }
  2253. </script>
  2254. </head>
  2255. <body id="body" onmousemove="handleUpdatePopover(event)" onload="handleBodyLoad()" class="noDiff">
  2256. <h1>Runtime Stats Komparator</h1>
  2257. <section id="inputs" class="panel alwaysVisible">
  2258. <input type="checkbox" id="inputsCheckbox" class="panelCloserInput">
  2259. <label class="panelCloserLabel" for="inputsCheckbox">▼</label>
  2260. <h2>Input/Output</h2>
  2261. <div class="panelBody">
  2262. <form name="fileForm" class="inline">
  2263. <p class="inline">
  2264. <label for="uploadInput">Load Files:</label>
  2265. <input id="uploadInput" type="file" name="files" onchange="handleLoadFiles();" multiple
  2266. accept=".json,.txt,.csv,.output">
  2267. </p>
  2268. <p class="inline">
  2269. <label for="appendInput">Append Files:</label>
  2270. <input id="appendInput" type="file" name="files" onchange="handleAppendFiles();" multiple
  2271. accept=".json,.txt,.csv,.output">
  2272. </p>
  2273. </form>
  2274. <p class="inline">
  2275. <button onclick="handleCopyToClipboard()">Copy Table to Clipboard</button>
  2276. </p>
  2277. </div>
  2278. </section>
  2279. <section class="panel">
  2280. <h2>Baseline Selector</h2>
  2281. <div class="panel-body">
  2282. Compare against baseline:&nbsp;<select id="baseline" onchange="handleSelectBaseline(this, event)"></select><br />
  2283. <span style="color: #060">Green</span> a selected version performs
  2284. better than the baseline.
  2285. </div>
  2286. </section>
  2287. <section class="panel-group">
  2288. <div id="versionSelector" class="panel">
  2289. <input type="checkbox" checked id="versionSelectorCheckbox" class="panelCloserInput">
  2290. <label class="panelCloserLabel" for="versionSelectorCheckbox">▼</label>
  2291. <h2>Selected Versions</h2>
  2292. <div class="panelBody">
  2293. <ul></ul>
  2294. </div>
  2295. </div>
  2296. <div id="pageSelector" class="panel">
  2297. <input type="checkbox" checked id="pageSelectorCheckbox" class="panelCloserInput">
  2298. <label class="panelCloserLabel" for="pageSelectorCheckbox">▼</label>
  2299. <h2>Selected Pages</h2>
  2300. <div class="panelBody">
  2301. <ul></ul>
  2302. </div>
  2303. </div>
  2304. <div id="groupSelector" class="panel">
  2305. <input type="checkbox" checked id="groupSelectorCheckbox" class="panelCloserInput">
  2306. <label class="panelCloserLabel" for="groupSelectorCheckbox">▼</label>
  2307. <h2>Selected RCS Groups</h2>
  2308. <div class="panelBody">
  2309. <ul></ul>
  2310. </div>
  2311. </div>
  2312. </section>
  2313. <section id="view" class="panel">
  2314. <input type="checkbox" id="tableViewCheckbox" class="panelCloserInput">
  2315. <label class="panelCloserLabel" for="tableViewCheckbox">▼</label>
  2316. <h2>RCS Table</h2>
  2317. <div class="panelBody"></div>
  2318. </section>
  2319. <section class="panel-group">
  2320. <div id="versionDetails" class="panel">
  2321. <input type="checkbox" checked id="versionDetailCheckbox" class="panelCloserInput">
  2322. <label class="panelCloserLabel" for="versionDetailCheckbox">▼</label>
  2323. <h2><span>Compare Page Versions</span></h2>
  2324. <div class="conten panelBody">
  2325. <table class="versionDetailTable" onclick="handleSelectDetailRow(this, event);">
  2326. <thead>
  2327. <tr>
  2328. <th class="version">Version&nbsp;</th>
  2329. <th class="position">Pos.&nbsp;</th>
  2330. <th class="value time">Time▴&nbsp;</th>
  2331. <th class="value time">Percent&nbsp;</th>
  2332. <th class="value count">Count&nbsp;</th>
  2333. </tr>
  2334. </thead>
  2335. <tbody></tbody>
  2336. </table>
  2337. </div>
  2338. </div>
  2339. <div id="pageDetail" class="panel">
  2340. <input type="checkbox" checked id="pageDetailCheckbox" class="panelCloserInput">
  2341. <label class="panelCloserLabel" for="pageDetailCheckbox">▼</label>
  2342. <h2>Page Comparison for <span></span></h2>
  2343. <div class="panelBody">
  2344. <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);">
  2345. <thead>
  2346. <tr>
  2347. <th class="page">Page&nbsp;</th>
  2348. <th class="value time">Time&nbsp;</th>
  2349. <th class="value time">Percent▾&nbsp;</th>
  2350. <th class="value time hideNoDiff">%/Entry&nbsp;</th>
  2351. <th class="value count">Count&nbsp;</th>
  2352. </tr>
  2353. </thead>
  2354. <tfoot>
  2355. <tr>
  2356. <td class="page">Total:</td>
  2357. <td class="value time"></td>
  2358. <td class="value time"></td>
  2359. <td class="value time hideNoDiff"></td>
  2360. <td class="value count"></td>
  2361. </tr>
  2362. </tfoot>
  2363. <tbody></tbody>
  2364. </table>
  2365. </div>
  2366. </div>
  2367. <div id="impactView" class="panel">
  2368. <input type="checkbox" checked id="impactViewCheckbox" class="panelCloserInput">
  2369. <label class="panelCloserLabel" for="impactViewCheckbox">▼</label>
  2370. <h2>Impact list for <span></span></h2>
  2371. <div class="panelBody">
  2372. <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);">
  2373. <thead>
  2374. <tr>
  2375. <th class="page">Name&nbsp;</th>
  2376. <th class="value time">Time&nbsp;</th>
  2377. <th class="value time">Percent▾&nbsp;</th>
  2378. <th class="">Top Pages</th>
  2379. </tr>
  2380. </thead>
  2381. <tbody></tbody>
  2382. </table>
  2383. </div>
  2384. </div>
  2385. </section>
  2386. <section id="pageVersionGraph" class="panel">
  2387. <input type="checkbox" id="pageVersionGraphCheckbox" class="panelCloserInput">
  2388. <label class="panelCloserLabel" for="pageVersionGraphCheckbox">▼</label>
  2389. <h2><span></span></h2>
  2390. <div class="panelBody"></div>
  2391. </section>
  2392. <section id="pageGraph" class="panel">
  2393. <input type="checkbox" id="pageGraphCheckbox" class="panelCloserInput">
  2394. <label class="panelCloserLabel" for="pageGraphCheckbox">▼</label>
  2395. <h2><span></span></h2>
  2396. <div class="panelBody"></div>
  2397. </section>
  2398. <section id="versionGraph" class="panel">
  2399. <input type="checkbox" id="versionGraphCheckbox" class="panelCloserInput">
  2400. <label class="panelCloserLabel" for="versionGraphCheckbox">▼</label>
  2401. <h2><span></span></h2>
  2402. <div class="panelBody"></div>
  2403. </section>
  2404. <div id="column" class="column">
  2405. <div class="header">
  2406. <select class="version" onchange="handleSelectVersion(this, event);"></select>
  2407. <select class="pageVersion" onchange="handleSelectPage(this, event);"></select>
  2408. </div>
  2409. <table class="list" onclick="handleSelectRow(this, event);">
  2410. <thead>
  2411. <tr>
  2412. <th class="position">Pos.&nbsp;</th>
  2413. <th class="name">Name&nbsp;</th>
  2414. <th class="value time">Time&nbsp;</th>
  2415. <th class="value time">Percent&nbsp;</th>
  2416. <th class="value count">Count&nbsp;</th>
  2417. </tr>
  2418. </thead>
  2419. <tbody></tbody>
  2420. </table>
  2421. </div>
  2422. <section class="panel alwaysVisible">
  2423. <h2>Instructions</h2>
  2424. <div class="panelBody">
  2425. <ol>
  2426. <li>Build chrome.</li>
  2427. </ol>
  2428. <h3>Telemetry benchmark</h3>
  2429. <ol>
  2430. <li>Run <code>v8.browsing</code> benchmarks:
  2431. <pre>$CHROMIUM_DIR/tools/perf/run_benchmark run v8.browsing_desktop \
  2432. --browser=exact --browser-executable=$CHROMIUM_DIR/out/release/chrome \
  2433. --story-filter='.*2020 ' \
  2434. --also-run-disabled-tests
  2435. </pre>
  2436. </li>
  2437. <li>Install <a href="https://stedolan.github.io/jq/">jq</a>.</li>
  2438. <li>Convert the telemetry JSON files to callstats JSON file:
  2439. <pre>
  2440. $V8_DIR/tools/callstats-from-telemetry.sh $CHROMIUM_DIR/tools/perf/artifacts/run_XXXX
  2441. </pre>
  2442. </li>
  2443. <li>Load the generated <code>out.json</code></li>
  2444. </ol>
  2445. <h3>Merged CSV from results.html</h3>
  2446. <ol>
  2447. <li>Open a results.html page for RCS-enabled benchmarks</li>
  2448. <li>Select "Export merged CSV" in the toolbar</li>
  2449. <li>Load the downloading .csv file normally in callstats.html</li>
  2450. </ol>
  2451. <h3>Aggregated raw txt output</h3>
  2452. <ol>
  2453. <li>Install scipy, e.g. <code>sudo aptitude install python-scipy</code>
  2454. <li>Check out a known working version of webpagereply:
  2455. <pre>git -C $CHROME_DIR/third_party/webpagereplay checkout 7dbd94752d1cde5536ffc623a9e10a51721eff1d</pre>
  2456. </li>
  2457. <li>Run <code>callstats.py</code> with a web-page-replay archive:
  2458. <pre>$V8_DIR/tools/callstats.py run \
  2459. --replay-bin=$CHROME_SRC/third_party/webpagereplay/replay.py \
  2460. --replay-wpr=$INPUT_DIR/top25.wpr \
  2461. --js-flags="" \
  2462. --with-chrome=$CHROME_SRC/out/Release/chrome \
  2463. --sites-file=$INPUT_DIR/top25.json</pre>
  2464. </li>
  2465. <li>Move results file to a subdirectory: <code>mkdir $VERSION_DIR; mv *.txt $VERSION_DIR</code></li>
  2466. <li>Repeat from step 1 with a different configuration (e.g. <code>--js-flags="--nolazy"</code>).</li>
  2467. <li>Create the final results file: <code>./callstats.py json $VERSION_DIR1 $VERSION_DIR2 > result.json</code>
  2468. </li>
  2469. <li>Use <code>results.json</code> on this site.</code>
  2470. </ol>
  2471. </div>
  2472. </section>
  2473. <div id="popover">
  2474. <div class="popoverArrow"></div>
  2475. <table>
  2476. <tr>
  2477. <td class="name" colspan="6"></td>
  2478. </tr>
  2479. <tr>
  2480. <td>Page:</td>
  2481. <td class="page name" colspan="6"></td>
  2482. </tr>
  2483. <tr>
  2484. <td>Version:</td>
  2485. <td class="version name" colspan="3"></td>
  2486. <td class="compare version name" colspan="3"></td>
  2487. </tr>
  2488. <tr>
  2489. <td>Time:</td>
  2490. <td class="time"></td>
  2491. <td>±</td>
  2492. <td class="timeVariance"></td>
  2493. <td class="compare time"></td>
  2494. <td class="compare"> ± </td>
  2495. <td class="compare timeVariance"></td>
  2496. </tr>
  2497. <tr>
  2498. <td>Percent:</td>
  2499. <td class="percent"></td>
  2500. <td>±</td>
  2501. <td class="percentVariance"></td>
  2502. <td class="compare percent"></td>
  2503. <td class="compare"> ± </td>
  2504. <td class="compare percentVariance"></td>
  2505. </tr>
  2506. <tr>
  2507. <td>Percent per Entry:</td>
  2508. <td class="percentPerEntry"></td>
  2509. <td colspan=2></td>
  2510. <td class="compare percentPerEntry"></td>
  2511. <td colspan=2></td>
  2512. </tr>
  2513. <tr>
  2514. <td>Count:</td>
  2515. <td class="count"></td>
  2516. <td>±</td>
  2517. <td class="countVariance"></td>
  2518. <td class="compare count"></td>
  2519. <td class="compare"> ± </td>
  2520. <td class="compare countVariance"></td>
  2521. </tr>
  2522. <tr>
  2523. <td>Overall Impact:</td>
  2524. <td class="timeImpact"></td>
  2525. <td>±</td>
  2526. <td class="timePercentImpact"></td>
  2527. <td class="compare timeImpact"></td>
  2528. <td class="compare"> ± </td>
  2529. <td class="compare timePercentImpact"></td>
  2530. </tr>
  2531. </table>
  2532. </div>
  2533. </body>
  2534. </html>