123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712 |
- <!DOCTYPE html>
- <html>
- <!--
- Copyright 2016 the V8 project authors. All rights reserved. Use of this source
- code is governed by a BSD-style license that can be found in the LICENSE file.
- -->
- <head>
- <meta charset="utf-8">
- <title>V8 Runtime Call Stats Komparator</title>
- <link rel="stylesheet" type="text/css" href="system-analyzer/index.css">
- <style>
- body {
- font-family: arial;
- }
- .panel {
- display: none;
- }
- .loaded .panel {
- display: block;
- }
- .panel.alwaysVisible {
- display: inherit !important;
- }
- .error #inputs {
- background-color: var(--error-color);
- }
- table {
- display: table;
- border-spacing: 0px;
- }
- tr {
- border-spacing: 0px;
- padding: 10px;
- }
- td,
- th {
- padding: 3px 10px 3px 5px;
- }
- .inline {
- display: inline-block;
- vertical-align: middle;
- margin-right: 10px;
- }
- .hidden {
- display: none;
- }
- .view {
- display: table;
- }
- .panel-group {
- display: grid;
- align-content: center;
- grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
- grid-auto-flow: row dense;
- grid-gap: 10px;
- margin-top: 10px;
- }
- .column {
- display: table-cell;
- border-right: 1px black dotted;
- min-width: 200px;
- }
- .column .header {
- padding: 0 10px 0 10px
- }
- #column {
- display: none;
- }
- .list {
- width: 100%;
- }
- select {
- width: 100%
- }
- .list tbody {
- cursor: pointer;
- }
- .list tr:nth-child(even) {
- background-color: rgba(0.5, 0.5, 0.5, 0.1);
- }
- .list tr.child {
- display: none;
- }
- .list tr.child.visible {
- display: table-row;
- }
- .list .child .name {
- padding-left: 20px;
- }
- .list .parent td {
- border-top: 1px solid #AAA;
- }
- .list .total {
- font-weight: bold
- }
- .list tr.parent.selected,
- .list tr:nth-child(even).selected,
- tr.selected {
- background-color: rgba(0.5, 0.5, 0.5, 0.1);
- }
- .codeSearch {
- display: block-inline;
- float: right;
- border-radius: 5px;
- background-color: #333;
- width: 1em;
- text-align: center;
- }
- .list .position {
- text-align: right;
- display: none;
- }
- .list div.toggle {
- cursor: pointer;
- }
- #column_0 .position {
- display: table-cell;
- }
- #column_0 .name {
- display: table-cell;
- }
- .list .name {
- display: none;
- white-space: nowrap;
- }
- .value {
- text-align: right;
- }
- .selectedVersion {
- font-weight: bold;
- }
- #baseline {
- width: auto;
- }
- .pageDetailTable tbody {
- cursor: pointer
- }
- .pageDetailTable tfoot td {
- border-top: 1px grey solid;
- }
- #popover {
- position: absolute;
- transform: translateY(-50%) translateX(40px);
- box-shadow: -2px 10px 44px -10px #000;
- border-radius: 5px;
- z-index: 1;
- background-color: var(--surface-color);
- display: none;
- white-space: nowrap;
- }
- #popover table {
- position: relative;
- z-index: 1;
- text-align: right;
- margin: 10px;
- }
- #popover td {
- padding: 3px 0px 3px 5px;
- white-space: nowrap;
- }
- .popoverArrow {
- background-color: var(--surface-color);
- position: absolute;
- width: 30px;
- height: 30px;
- transform: translateY(-50%)rotate(45deg);
- top: 50%;
- left: -10px;
- z-index: 0;
- }
- #popover .name {
- padding: 5px;
- font-weight: bold;
- text-align: center;
- }
- #popover table .compare {
- display: none
- }
- #popover table.compare .compare {
- display: table-cell;
- }
- #popover .compare .time,
- #popover .compare .version {
- padding-left: 10px;
- }
- .diff .hideDiff {
- display: none;
- }
- .noDiff .hideNoDiff {
- display: none;
- }
- </style>
- <script src="https://www.gstatic.com/charts/loader.js"></script>
- <script>
- "use strict"
- google.charts.load('current', {
- packages: ['corechart']
- });
- // Did anybody say monkeypatching?
- if (!NodeList.prototype.forEach) {
- NodeList.prototype.forEach = function (func) {
- for (let i = 0; i < this.length; i++) {
- func(this[i]);
- }
- }
- }
- let versions;
- let pages;
- let selectedPage;
- let baselineVersion;
- let selectedEntry;
- let sortByLabel = false;
- // Marker to programatically replace the defaultData.
- let defaultData = /*default-data-start*/ undefined /*default-data-end*/;
- function initialize() {
- // Initialize the stats table and toggle lists.
- let original = $("column");
- let viewBody = $("view").querySelector('.panelBody');
- removeAllChildren(viewBody);
- let i = 0;
- versions.forEach((version) => {
- if (!version.enabled) return;
- // add column
- let column = original.cloneNode(true);
- column.id = "column_" + i;
- // Fill in all versions
- let select = column.querySelector(".version");
- select.id = "selectVersion_" + i;
- // add all select options
- versions.forEach((version) => {
- if (!version.enabled) return;
- let option = document.createElement("option");
- option.textContent = version.name;
- option.version = version;
- select.appendChild(option);
- });
- // Fill in all page versions
- select = column.querySelector(".pageVersion");
- select.id = "select_" + i;
- // add all pages
- versions.forEach((version) => {
- if (!version.enabled) return;
- let optgroup = document.createElement("optgroup");
- optgroup.label = version.name;
- optgroup.version = version;
- version.forEachPage((page) => {
- let option = document.createElement("option");
- option.textContent = page.name;
- option.page = page;
- optgroup.appendChild(option);
- });
- select.appendChild(optgroup);
- });
- viewBody.appendChild(column);
- i++;
- });
- let select = $('baseline');
- removeAllChildren(select);
- select.appendChild(document.createElement('option'));
- versions.forEach((version) => {
- let option = document.createElement("option");
- option.textContent = version.name;
- option.version = version;
- select.appendChild(option);
- });
- initializeToggleList(versions.versions, $('versionSelector'));
- initializeToggleList(pages.values(), $('pageSelector'));
- initializeToggleList(Group.groups.values(), $('groupSelector'));
- }
- function initializeToggleList(items, node) {
- let list = node.querySelector('ul');
- removeAllChildren(list);
- items = Array.from(items);
- items.sort(NameComparator);
- items.forEach((item) => {
- let li = document.createElement('li');
- let checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.checked = item.enabled;
- checkbox.item = item;
- checkbox.addEventListener('click', handleToggleVersionOrPageEnable);
- li.appendChild(checkbox);
- li.appendChild(document.createTextNode(item.name));
- list.appendChild(li);
- });
- }
- window.addEventListener('popstate', (event) => {
- popHistoryState(event.state);
- });
- function popHistoryState(state) {
- if (!state.version) return false;
- if (!versions) return false;
- let version = versions.getByName(state.version);
- if (!version) return false;
- let page = version.get(state.page);
- if (!page) return false;
- if (!state.entry) {
- showEntry(page.total);
- } else {
- let entry = page.get(state.entry);
- if (!entry) {
- showEntry(page.total);
- } else {
- showEntry(entry);
- }
- }
- return true;
- }
- function pushHistoryState() {
- let selection = selectedEntry ? selectedEntry : selectedPage;
- if (!selection) return;
- let state = selection.urlParams();
- // Don't push a history state if it didn't change.
- if (JSON.stringify(window.history.state) === JSON.stringify(state)) return;
- let params = "?";
- for (let pairs of Object.entries(state)) {
- params += encodeURIComponent(pairs[0]) + "=" +
- encodeURIComponent(pairs[1]) + "&";
- }
- window.history.pushState(state, selection.toString(), params);
- }
- function showSelectedEntryInPage(page) {
- if (!selectedEntry) return showPage(page);
- let entry = page.get(selectedEntry.name);
- if (!entry) return showPage(page);
- selectEntry(entry);
- }
- function showPage(firstPage) {
- let changeSelectedEntry = selectedEntry !== undefined &&
- selectedEntry.page === selectedPage;
- selectedPage = firstPage;
- selectedPage.sort();
- showPageInColumn(firstPage, 0);
- // Show the other versions of this page in the following columns.
- let pageVersions = versions.getPageVersions(firstPage);
- let index = 1;
- pageVersions.forEach((page) => {
- if (page !== firstPage) {
- showPageInColumn(page, index);
- index++;
- }
- });
- if (changeSelectedEntry) {
- showEntryDetail(selectedPage.getEntry(selectedEntry));
- }
- showImpactList(selectedPage);
- pushHistoryState();
- }
- function clamp(value, min, max) {
- if (value < min) return min;
- if (value > max) return max;
- return value;
- }
- function diffColorFromRatio(ratio) {
- if (ratio == Infinity) {
- return '#ff0000';
- }
- if (ratio == -Infinity) {
- return '#00ff00';
- }
- if (ratio > 1) {
- // ratio > 1: #FFFFFF => #00FF00
- const red = clamp(((ratio - 1) * 255 * 10) | 0, 0, 255);
- const other = (255 - red).toString(16).padStart(2, '0');
- return `#ff${other}${other}`;
- }
- // ratio < 1: #FF0000 => #FFFFFF
- const green = clamp(((1 - ratio) * 255 * 10) | 0, 0, 255);
- const other = (255 - green).toString(16).padStart(2, '0');
- return `#${other}ff${other}`;
- }
- function showPageInColumn(page, columnIndex) {
- page.sort();
- let showDiff = columnIndex !== 0;
- if (baselineVersion) showDiff = page.version !== baselineVersion;
- let diffColor = (td, a, b) => { };
- if (showDiff) {
- if (baselineVersion) {
- diffColor = (td, diff, baseline) => {
- if (diff == 0) return;
- const ratio = (baseline + diff) / baseline;
- td.style.color = diffColorFromRatio(ratio);
- };
- } else {
- diffColor = (td, value, reference) => {
- if (value == reference) return;
- const ratio = value / reference;
- td.style.color = diffColorFromRatio(ratio);
- }
- }
- }
- let column = $('column_' + columnIndex);
- let select = $('select_' + columnIndex);
- // Find the matching option
- selectOption(select, (i, option) => {
- return option.page == page
- });
- let table = column.querySelector("table");
- let oldTbody = table.querySelector('tbody');
- let tbody = document.createElement('tbody');
- let referencePage = selectedPage;
- page.forEachSorted(selectedPage, (parentEntry, entry, referenceEntry) => {
- let tr = document.createElement('tr');
- tbody.appendChild(tr);
- tr.entry = entry;
- tr.parentEntry = parentEntry;
- tr.className = parentEntry === undefined ? 'parent' : 'child';
- // Don't show entries that do not exist on the current page or if we
- // compare against the current page
- if (entry !== undefined && page.version !== baselineVersion) {
- // If we show a diff, use the baselineVersion as the referenceEntry
- if (baselineVersion !== undefined) {
- let baselineEntry = baselineVersion.getEntry(entry);
- if (baselineEntry !== undefined) referenceEntry = baselineEntry
- }
- if (!parentEntry) {
- let node = td(tr, '<div class="toggle">►</div>', 'position');
- node.firstChild.addEventListener('click', handleToggleGroup);
- } else {
- td(tr, entry.position == 0 ? '' : entry.position, 'position');
- }
- addCodeSearchButton(entry,
- td(tr, entry.name, 'name ' + entry.cssClass()));
- diffColor(
- td(tr, ms(entry.time), 'value time'),
- entry.time, referenceEntry.time);
- diffColor(
- td(tr, percent(entry.timePercent), 'value time'),
- entry.time, referenceEntry.time);
- diffColor(
- td(tr, count(entry.count), 'value count'),
- entry.count, referenceEntry.count);
- } else if (baselineVersion !== undefined && referenceEntry &&
- page.version !== baselineVersion) {
- // Show comparison of entry that does not exist on the current page.
- tr.entry = new Entry(0, referenceEntry.name);
- tr.entry.page = page;
- td(tr, '-', 'position');
- td(tr, referenceEntry.name, 'name');
- diffColor(
- td(tr, ms(referenceEntry.time), 'value time'),
- referenceEntry.time, 0);
- diffColor(
- td(tr, percent(referenceEntry.timePercent), 'value time'),
- referenceEntry.timePercent, 0);
- diffColor(
- td(tr, count(referenceEntry.count), 'value count'),
- referenceEntry.count, 0);
- } else {
- // Display empty entry / baseline entry
- let showBaselineEntry = entry !== undefined;
- if (showBaselineEntry) {
- if (!parentEntry) {
- let node = td(tr, '<div class="toggle">►</div>', 'position');
- node.firstChild.addEventListener('click', handleToggleGroup);
- } else {
- td(tr, entry.position == 0 ? '' : entry.position, 'position');
- }
- td(tr, entry.name, 'name');
- td(tr, ms(entry.time, false), 'value time');
- td(tr, percent(entry.timePercent, false), 'value time');
- td(tr, count(entry.count, false), 'value count');
- } else {
- td(tr, '-', 'position');
- td(tr, referenceEntry.name, 'name');
- td(tr, '-', 'value time');
- td(tr, '-', 'value time');
- td(tr, '-', 'value count');
- }
- }
- });
- table.replaceChild(tbody, oldTbody);
- let versionSelect = column.querySelector('select.version');
- selectOption(versionSelect, (index, option) => {
- return option.version == page.version
- });
- }
- function showEntry(entry) {
- selectEntry(entry, true);
- }
- function selectEntry(entry, updateSelectedPage) {
- let needsPageSwitch = true;
- if (updateSelectedPage && selectedPage) {
- entry = selectedPage.version.getEntry(entry);
- needsPageSwitch = updateSelectedPage && entry.page != selectedPage;
- }
- let rowIndex = 0;
- // If clicked in the detail row change the first column to that page.
- if (needsPageSwitch) showPage(entry.page);
- let childNodes = $('column_0').querySelector('.list tbody').childNodes;
- for (let i = 0; i < childNodes.length; i++) {
- if (childNodes[i].entry !== undefined &&
- childNodes[i].entry.name == entry.name) {
- rowIndex = i;
- break;
- }
- }
- let firstEntry = childNodes[rowIndex].entry;
- if (rowIndex) {
- if (firstEntry.parent) showGroup(firstEntry.parent);
- }
- // Deselect all
- $('view').querySelectorAll('.list tbody tr').forEach((tr) => {
- toggleCssClass(tr, 'selected', false);
- });
- // Select the entry row
- $('view').querySelectorAll("tbody").forEach((body) => {
- let row = body.childNodes[rowIndex];
- if (!row) return;
- toggleCssClass(row, 'selected', row.entry && row.entry.name ==
- firstEntry.name);
- });
- if (updateSelectedPage && selectedEntry) {
- entry = selectedEntry.page.version.getEntry(entry);
- }
- if (entry !== selectedEntry) {
- selectedEntry = entry;
- showEntryDetail(entry);
- }
- }
- function showEntryDetail(entry) {
- showVersionDetails(entry);
- showPageDetails(entry);
- showImpactList(entry.page);
- showGraphs(entry.page);
- pushHistoryState();
- }
- function showVersionDetails(entry) {
- let table, tbody, entries;
- table = $('versionDetails').querySelector('.versionDetailTable');
- tbody = document.createElement('tbody');
- if (entry !== undefined) {
- $('versionDetails').querySelector('h2 span').textContent =
- entry.name + ' in ' + entry.page.name;
- entries = versions.getPageVersions(entry.page).map(
- (page) => {
- return page.get(entry.name)
- });
- entries.sort((a, b) => {
- return a.time - b.time
- });
- entries.forEach((pageEntry) => {
- if (pageEntry === undefined) return;
- let tr = document.createElement('tr');
- if (pageEntry == entry) tr.className += 'selected';
- tr.entry = pageEntry;
- let isBaselineEntry = pageEntry.page.version == baselineVersion;
- td(tr, pageEntry.page.version.name, 'version');
- td(tr, ms(pageEntry.time, !isBaselineEntry), 'value time');
- td(tr, percent(pageEntry.timePercent, !isBaselineEntry), 'value time');
- td(tr, count(pageEntry.count, !isBaselineEntry), 'value count');
- tbody.appendChild(tr);
- });
- }
- table.replaceChild(tbody, table.querySelector('tbody'));
- }
- function showPageDetails(entry) {
- let table, tbody, entries;
- table = $('pageDetail').querySelector('.pageDetailTable');
- tbody = document.createElement('tbody');
- if (entry === undefined) {
- table.replaceChild(tbody, table.querySelector('tbody'));
- return;
- }
- let version = entry.page.version;
- let showDiff = version !== baselineVersion;
- $('pageDetail').querySelector('h2 span').textContent =
- version.name;
- entries = version.pages.map((page) => {
- if (!page.enabled) return;
- return page.get(entry.name)
- });
- entries.sort((a, b) => {
- let cmp = b.timePercent - a.timePercent;
- if (cmp.toFixed(1) == 0) return b.time - a.time;
- return cmp
- });
- entries.forEach((pageEntry) => {
- if (pageEntry === undefined) return;
- let tr = document.createElement('tr');
- if (pageEntry === entry) tr.className += 'selected';
- tr.entry = pageEntry;
- td(tr, pageEntry.page.name, 'name');
- td(tr, ms(pageEntry.time, showDiff), 'value time');
- td(tr, percent(pageEntry.timePercent, showDiff), 'value time');
- td(tr, percent(pageEntry.timePercentPerEntry, showDiff),
- 'value time hideNoDiff');
- td(tr, count(pageEntry.count, showDiff), 'value count');
- tbody.appendChild(tr);
- });
- // show the total for all pages
- let tds = table.querySelectorAll('tfoot td');
- tds[1].textContent = ms(entry.getTimeImpact(), showDiff);
- // Only show the percentage total if we are in diff mode:
- tds[2].textContent = percent(entry.getTimePercentImpact(), showDiff);
- tds[3].textContent = '';
- tds[4].textContent = count(entry.getCountImpact(), showDiff);
- table.replaceChild(tbody, table.querySelector('tbody'));
- }
- function showImpactList(page) {
- let impactView = $('impactView');
- impactView.querySelector('h2 span').textContent = page.version.name;
- let table = impactView.querySelector('table');
- let tbody = document.createElement('tbody');
- let version = page.version;
- let entries = version.allEntries();
- if (selectedEntry !== undefined && selectedEntry.isGroup) {
- impactView.querySelector('h2 span').textContent += " " + selectedEntry.name;
- entries = entries.filter((entry) => {
- return entry.name == selectedEntry.name ||
- (entry.parent && entry.parent.name == selectedEntry.name)
- });
- }
- let isCompareView = baselineVersion !== undefined;
- entries = entries.filter((entry) => {
- if (isCompareView) {
- let impact = entry.getTimeImpact();
- return impact < -1 || 1 < impact
- }
- return entry.getTimePercentImpact() > 0.01;
- });
- entries = entries.slice(0, 50);
- entries.sort((a, b) => {
- let cmp = b.getTimePercentImpact() - a.getTimePercentImpact();
- if (isCompareView || cmp.toFixed(1) == 0) {
- return b.getTimeImpact() - a.getTimeImpact();
- }
- return cmp
- });
- entries.forEach((entry) => {
- let tr = document.createElement('tr');
- tr.entry = entry;
- td(tr, entry.name, 'name');
- td(tr, ms(entry.getTimeImpact()), 'value time');
- let percentImpact = entry.getTimePercentImpact();
- td(tr, percentImpact > 1000 ? '-' : percent(percentImpact), 'value time');
- let topPages = entry.getPagesByPercentImpact().slice(0, 3)
- .map((each) => {
- return each.name + ' (' + percent(each.getEntry(entry).timePercent) +
- ')'
- });
- td(tr, topPages.join(', '), 'name');
- tbody.appendChild(tr);
- });
- table.replaceChild(tbody, table.querySelector('tbody'));
- }
- function showGraphs(page) {
- let groups = page.groups.filter(each => each.enabled && !each.isTotal);
- // Sort groups by the biggest impact
- groups.sort((a, b) => b.getTimeImpact() - a.getTimeImpact());
- if (selectedGroup == undefined) {
- selectedGroup = groups[0];
- } else {
- groups = groups.filter(each => each.name != selectedGroup.name);
- if (!selectedGroup.isTotal && selectedGroup.enabled) {
- groups.unshift(selectedGroup);
- }
- }
- // Display graphs delayed for a snappier UI.
- setTimeout(() => {
- showPageVersionGraph(groups, page);
- showPageGraph(groups, page);
- showVersionGraph(groups, page);
- }, 10);
- }
- function getGraphDataTable(groups, page) {
- let dataTable = new google.visualization.DataTable();
- dataTable.addColumn('string', 'Name');
- groups.forEach(group => {
- let column = dataTable.addColumn('number', group.name.substring(6));
- dataTable.setColumnProperty(column, 'group', group);
- column = dataTable.addColumn({
- role: "annotation"
- });
- dataTable.setColumnProperty(column, 'group', group);
- });
- let column = dataTable.addColumn('number', 'Chart Total');
- dataTable.setColumnProperty(column, 'group', page.total);
- column = dataTable.addColumn({
- role: "annotation"
- });
- dataTable.setColumnProperty(column, 'group', page.total);
- return dataTable;
- }
- let selectedGroup;
- class ChartRow {
- static kSortFirstValueRelative(chartRow) {
- if (selectedGroup?.isTotal) return chartRow.total;
- return chartRow.data[0] / chartRow.total;
- }
- static kSortByFirstValue(chartRow) {
- if (selectedGroup?.isTotal) return chartRow.total;
- return chartRow.data[0];
- }
- constructor(linkedPage, label, sortValue_fn, data,
- excludeFromAverage = false) {
- this.linkedPage = linkedPage;
- this.label = label;
- if (!Array.isArray(data)) {
- throw new Error("Provide an Array for data");
- }
- this.data = data;
- this.total = 0;
- for (let i = 0; i < data.length; i++) this.total += data[i];
- this.sortValue = sortValue_fn(this);
- this.excludeFromAverage = excludeFromAverage;
- }
- forDataTable(maxRowsTotal) {
- // row = [label, entry1, annotation1, entry2, annotation2, ...]
- const rowData = [this.label];
- const kShowLabelLimit = 0.1;
- const kMinLabelWidth = 80;
- const chartWidth = window.innerWidth - 400;
- // Add value,label pairs
- for (let i = 0; i < this.data.length; i++) {
- const value = this.data[i];
- let label = '';
- // Only show labels for entries that are large enough..
- if (Math.abs(value / maxRowsTotal) * chartWidth > kMinLabelWidth) {
- label = ms(value);
- }
- rowData.push(value, label);
- }
- // Add the total row, with very small negative dummy entry for correct
- // placement of labels in diff view.
- rowData.push(this.total >= 0 ? 0 : -0.000000001, ms(this.total));
- return rowData;
- }
- }
- const collator = new Intl.Collator('en-UK');
- function setDataTableRows(dataTable, rows) {
- let skippedRows = 0;
- // Always sort by the selected entry (first column after the label)
- if (sortByLabel) {
- rows.sort((a, b) => collator.compare(a.label, b.label));
- } else {
- rows.sort((a, b) => b.sortValue - a.sortValue);
- }
- // Aggregate row data for Average/SUM chart entry:
- const aggregateData = rows[0].data.slice().fill(0);
- let maxTotal = 0;
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
- let total = Math.abs(row.total);
- if (total > maxTotal) maxTotal = total;
- if (row.excludeFromAverage) {
- skippedRows++;
- continue
- }
- const chartRowData = row.data;
- for (let j = 0; j < chartRowData.length; j++) {
- aggregateData[j] += chartRowData[j];
- }
- }
- const length = rows.length - skippedRows;
- for (let i = 0; i < aggregateData.length; i++) {
- aggregateData[i] /= rows.length;
- }
- const averageRow = new ChartRow(undefined, 'Average',
- ChartRow.kSortByFirstValue, aggregateData);
- dataTable.addRow(averageRow.forDataTable());
- rows.forEach(chartRow => {
- let rowIndex = dataTable.addRow(chartRow.forDataTable(maxTotal));
- dataTable.setRowProperty(rowIndex, 'page', chartRow.linkedPage);
- });
- }
- function showPageVersionGraph(groups, page) {
- let dataTable = getGraphDataTable(groups, page);
- let vs = versions.getPageVersions(page);
- // Calculate the entries for the versions
- const rows = vs.map(page => new ChartRow(
- page, page.version.name, ChartRow.kSortByFirstValue,
- groups.map(group => page.getEntry(group).time),
- page.version === baselineVersion));
- renderGraph(`Versions for ${page.name}`, groups, dataTable, rows,
- 'pageVersionGraph', true);
- }
- function showPageGraph(groups, page) {
- let isDiffView = baselineVersion !== undefined;
- let dataTable = getGraphDataTable(groups, page);
- // Calculate the average row
- // Sort the pages by the selected group.
- let pages = page.version.pages.filter(page => page.enabled);
- // Calculate the entries for the pages
- const rows = pages.map(page => new ChartRow(
- page, page.name,
- isDiffView ?
- ChartRow.kSortByFirstValue : ChartRow.kSortFirstValueRelative,
- groups.map(group => page.getEntry(group).time)));
- renderGraph(`Pages for ${page.version.name}`, groups, dataTable, rows,
- 'pageGraph', isDiffView ? true : 'percent');
- }
- function showVersionGraph(groups, page) {
- let dataTable = getGraphDataTable(groups, page);
- let vs = versions.versions.filter(version => version.enabled);
- // Calculate the entries for the versions
- const rows = vs.map((version) => new ChartRow(
- version.get(page), version.name, ChartRow.kSortByFirstValue,
- groups.map(group => version.getEntry(group).getTimeImpact()),
- version === baselineVersion));
- renderGraph('Versions Total Time over all Pages', groups, dataTable, rows,
- 'versionGraph', true);
- }
- function renderGraph(title, groups, dataTable, rows, id, isStacked) {
- let isDiffView = baselineVersion !== undefined;
- setDataTableRows(dataTable, rows);
- let formatter = new google.visualization.NumberFormat({
- suffix: (isDiffView ? 'msΔ' : 'ms'),
- negativeColor: 'red',
- groupingSymbol: "'"
- });
- for (let i = 1; i < dataTable.getNumberOfColumns(); i++) {
- formatter.format(dataTable, i);
- }
- let height = 85 + 28 * dataTable.getNumberOfRows();
- let options = {
- isStacked: isStacked,
- height: height,
- hAxis: {
- minValue: 0,
- textStyle: {
- fontSize: 14
- }
- },
- vAxis: {
- textStyle: {
- fontSize: 14
- }
- },
- tooltip: {
- textStyle: {
- fontSize: 14
- }
- },
- annotations: {
- textStyle: {
- fontSize: 8
- }
- },
- explorer: {
- actions: ['dragToZoom', 'rightClickToReset'],
- maxZoomIn: 0.01
- },
- legend: {
- position: 'top',
- maxLines: 3,
- textStyle: {
- fontSize: 12
- }
- },
- chartArea: {
- left: 200,
- top: 50
- },
- colors: [
- ...groups.map(each => each.color),
- /* Chart Total */
- "#000000",
- ]
- };
- let parentNode = $(id);
- parentNode.querySelector('h2>span, h3>span').textContent = title;
- let graphNode = parentNode.querySelector('.panelBody');
- let chart = graphNode.chart;
- if (chart === undefined) {
- chart = graphNode.chart = new google.visualization.BarChart(graphNode);
- } else {
- google.visualization.events.removeAllListeners(chart);
- }
- google.visualization.events.addListener(chart, 'select', selectHandler);
- function getChartEntry(selection) {
- if (!selection) return undefined;
- let column = selection.column;
- if (column == undefined) return undefined;
- let selectedGroup = dataTable.getColumnProperty(column, 'group');
- let row = selection.row;
- if (row == null) return selectedGroup;
- let page = dataTable.getRowProperty(row, 'page');
- if (!page) return selectedGroup;
- return page.getEntry(selectedGroup);
- }
- function selectHandler(e) {
- const newSelectedGroup = getChartEntry(chart.getSelection()[0]);
- if (newSelectedGroup == selectedGroup) {
- sortByLabel = !sortByLabel;
- } else if (newSelectedGroup === undefined && selectedPage) {
- sortByLabel = true;
- return showGraphs(selectedPage);
- } else {
- sortByLabel = false;
- }
- selectedGroup = newSelectedGroup;
- selectEntry(selectedGroup, true);
- }
- // Make our global tooltips work
- google.visualization.events.addListener(chart, 'onmouseover', mouseOverHandler);
- function mouseOverHandler(selection) {
- const selectedGroup = getChartEntry(selection);
- graphNode.entry = selectedGroup;
- }
- chart.draw(dataTable, options);
- }
- function showGroup(entry) {
- toggleGroup(entry, true);
- }
- function toggleGroup(group, show) {
- $('view').querySelectorAll(".child").forEach((tr) => {
- let entry = tr.parentEntry;
- if (!entry) return;
- if (entry.name !== group.name) return;
- toggleCssClass(tr, 'visible', show);
- });
- }
- function showPopover(entry) {
- let popover = $('popover');
- popover.querySelector('td.name').textContent = entry.name;
- popover.querySelector('td.page').textContent = entry.page.name;
- setPopoverDetail(popover, entry, '');
- popover.querySelector('table').className = "";
- if (baselineVersion !== undefined) {
- entry = baselineVersion.getEntry(entry);
- setPopoverDetail(popover, entry, '.compare');
- popover.querySelector('table').className = "compare";
- }
- }
- function setPopoverDetail(popover, entry, prefix) {
- let node = (name) => popover.querySelector(prefix + name);
- if (entry == undefined) {
- node('.version').textContent = baselineVersion.name;
- node('.time').textContent = '-';
- node('.timeVariance').textContent = '-';
- node('.percent').textContent = '-';
- node('.percentPerEntry').textContent = '-';
- node('.percentVariance').textContent = '-';
- node('.count').textContent = '-';
- node('.countVariance').textContent = '-';
- node('.timeImpact').textContent = '-';
- node('.timePercentImpact').textContent = '-';
- } else {
- node('.version').textContent = entry.page.version.name;
- node('.time').textContent = ms(entry._time, false);
- node('.timeVariance').textContent = percent(entry.timeVariancePercent, false);
- node('.percent').textContent = percent(entry.timePercent, false);
- node('.percentPerEntry').textContent = percent(entry.timePercentPerEntry, false);
- node('.percentVariance').textContent = percent(entry.timePercentVariancePercent, false);
- node('.count').textContent = count(entry._count, false);
- node('.countVariance').textContent = percent(entry.timeVariancePercent, false);
- node('.timeImpact').textContent = ms(entry.getTimeImpact(false), false);
- node('.timePercentImpact').textContent = percent(entry.getTimeImpactVariancePercent(false), false);
- }
- }
- </script>
- <script>
- "use strict"
- // =========================================================================
- // Helpers
- function $(id) {
- return document.getElementById(id)
- }
- function removeAllChildren(node) {
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- }
- function selectOption(select, match) {
- let options = select.options;
- for (let i = 0; i < options.length; i++) {
- if (match(i, options[i])) {
- select.selectedIndex = i;
- return;
- }
- }
- }
- function addCodeSearchButton(entry, node) {
- if (entry.isGroup) return;
- let button = document.createElement("div");
- button.textContent = '?'
- button.className = "codeSearch"
- button.addEventListener('click', handleCodeSearch);
- node.appendChild(button);
- return node;
- }
- function td(tr, content, className) {
- let td = document.createElement("td");
- if (content[0] == '<') {
- td.innerHTML = content;
- } else {
- td.textContent = content;
- }
- td.className = className
- tr.appendChild(td);
- return td
- }
- function nodeIndex(node) {
- let children = node.parentNode.childNodes,
- i = 0;
- for (; i < children.length; i++) {
- if (children[i] == node) {
- return i;
- }
- }
- return -1;
- }
- function toggleCssClass(node, cssClass, toggleState = true) {
- let index = -1;
- let classes;
- if (node.className != undefined) {
- classes = node.className.split(' ');
- index = classes.indexOf(cssClass);
- }
- if (index == -1) {
- if (toggleState === false) return;
- node.className += ' ' + cssClass;
- return;
- }
- if (toggleState === true) return;
- classes.splice(index, 1);
- node.className = classes.join(' ');
- }
- function NameComparator(a, b) {
- if (a.name > b.name) return 1;
- if (a.name < b.name) return -1;
- return 0
- }
- function diffSign(value, digits, unit, showDiff) {
- if (showDiff === false || baselineVersion == undefined) {
- if (value === undefined) return '';
- return value.toFixed(digits) + unit;
- }
- return (value >= 0 ? '+' : '') + value.toFixed(digits) + unit + 'Δ';
- }
- function ms(value, showDiff) {
- return diffSign(value, 1, 'ms', showDiff);
- }
- function count(value, showDiff) {
- return diffSign(value, 0, '#', showDiff);
- }
- function percent(value, showDiff) {
- return diffSign(value, 1, '%', showDiff);
- }
- </script>
- <script>
- "use strict"
- // =========================================================================
- // EventHandlers
- async function handleBodyLoad() {
- $('uploadInput').focus();
- if (tryLoadDefaultData() || await tryLoadFromURLParams() ||
- await tryLoadDefaultResults()) {
- displayResultsAfterLoading();
- }
- }
- function tryLoadDefaultData() {
- if (!defaultData) return false;
- handleLoadJSON(defaultData);
- return true;
- }
- async function tryLoadFromURLParams() {
- let params = new URLSearchParams(document.location.search);
- let hasFile = false;
- params.forEach(async (value, key) => {
- if (key !== 'file') return;
- hasFile ||= await tryLoadFile(value, true);
- });
- return hasFile;
- }
- async function tryLoadDefaultResults() {
- if (window.location.protocol === 'file:') return false;
- // Try to load a results.json file adjacent to this day.
- // The markers on the following line can be used to replace the url easily
- // with scripts.
- const url = /*results-url-start*/ 'results.json' /*results-url-end*/;
- return tryLoadFile(url);
- }
- async function tryLoadFile(url, append = false) {
- if (!url.startsWith('http')) {
- // hack to get relative urls
- let location = window.location;
- let parts = location.pathname.split("/").slice(0, -1);
- url = location.origin + parts.join('/') + '/' + url;
- }
- let response = await fetch(url);
- if (!response.ok) return false;
- let filename = url.split('/');
- filename = filename[filename.length - 1];
- handleLoadText(await response.text(), append, filename);
- return true;
- }
- function handleAppendFiles() {
- let files = document.getElementById("appendInput").files;
- loadFiles(files, true);
- }
- function handleLoadFiles() {
- let files = document.getElementById("uploadInput").files;
- loadFiles(files, false)
- }
- async function loadFiles(files, append) {
- for (let i = 0; i < files.length; i++) {
- const file = files[i];
- console.log(file.name);
- let text = await new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result)
- reader.readAsText(file);
- });
- handleLoadText(text, append, file.name);
- // Only the first file might clear existing data, all sequent files
- // are always append.
- append = true;
- }
- displayResultsAfterLoading();
- toggleCssClass(document.body, "loaded");
- }
- function handleLoadText(text, append, fileName) {
- if (fileName.endsWith('.json')) {
- handleLoadJSON(JSON.parse(text), append, fileName);
- } else if (fileName.endsWith('.csv') ||
- fileName.endsWith('.output') || fileName.endsWith('.output.txt')) {
- handleLoadCSV(text, append, fileName);
- } else if (fileName.endsWith('.txt')) {
- handleLoadTXT(text, append, fileName);
- } else {
- alert(`Unsupported file extension: "${fileName}"`);
- }
- }
- function getStateFromParams() {
- let query = window.location.search.substr(1);
- let result = {};
- query.split("&").forEach((part) => {
- let item = part.split("=");
- let key = decodeURIComponent(item[0])
- result[key] = decodeURIComponent(item[1]);
- });
- return result;
- }
- function handleLoadJSON(json, append, fileName) {
- json = fixClusterTelemetryResults(json);
- json = fixTraceImportJSON(json);
- json = fixSingleVersionJSON(json, fileName);
- let isFirstLoad = pages === undefined;
- if (append && !isFirstLoad) {
- json = createUniqueVersions(json);
- }
- if (!append || isFirstLoad) {
- pages = new Pages();
- versions = Versions.fromJSON(json);
- } else {
- Versions.fromJSON(json).forEach(e => versions.add(e))
- }
- }
- function handleLoadCSV(csv, append, fileName) {
- let isFirstLoad = pages === undefined;
- if (!append || isFirstLoad) {
- pages = new Pages();
- versions = new Versions();
- }
- const lines = csv.split(/\r?\n/);
- // The first line contains only the field names.
- const fields = new Map();
- csvSplit(lines[0]).forEach((name, index) => {
- fields.set(name, index);
- });
- if (fields.has('displayLabel') && fields.has('stories')) {
- handleLoadResultCSV(fields, lines);
- } else if (fields.has('page_name')) {
- handleLoadClusterTelemetryCSV(fields, lines, fileName);
- } else {
- return alert("Unknown CSV format");
- }
- }
- function csvSplit(line) {
- let fields = [];
- let index = 0;
- while (index < line.length) {
- let lastIndex = index;
- if (line[lastIndex] == '"') {
- index = line.indexOf('"', lastIndex + 1);
- if (index < 0) index = line.length;
- fields.push(line.substring(lastIndex + 1, index));
- // Consume ','
- index++;
- } else {
- index = line.indexOf(',', lastIndex);
- if (index === -1) index = line.length;
- fields.push(line.substring(lastIndex, index))
- }
- // Consume ','
- index++;
- }
- return fields;
- }
- // Ignore the following categories as they are aggregated values and are
- // created by callstats.html on the fly.
- const import_skip_categories = new Set([
- 'V8-Only', 'V8-Only-Main-Thread', 'Total-Main-Thread', 'Blink_Total'
- ])
- function handleLoadClusterTelemetryCSV(fields, lines, fileName) {
- const rscFields = Array.from(fields.keys())
- .filter(field => {
- return field.endsWith(':duration (ms)') &&
- !import_skip_categories.has(field.split(':')[0])
- })
- .map(field => {
- let name = field.split(':')[0];
- return [name, fields.get(field), fields.get(`${name}:count`)];
- })
- const page_name_i = fields.get('page_name');
- const version = versions.getOrCreate(fileName);
- for (let i = 1; i < lines.length; i++) {
- const line = csvSplit(lines[i]);
- if (line.length == 0) continue;
- let page_name = line[page_name_i];
- if (page_name === undefined) continue;
- page_name = page_name.split(' ')[0];
- const pageVersion = version.getOrCreate(page_name);
- for (let [fieldName, duration_i, count_i] of rscFields) {
- const duration = Number.parseFloat(line[duration_i]);
- const count = Number.parseFloat(line[count_i]);
- // Skip over entries without metrics (most likely crashes)
- if (Number.isNaN(count) || Number.isNaN(duration)) {
- console.warn(`BROKEN ${page_name}`, lines[i])
- break;
- }
- pageVersion.add(new Entry(0, fieldName, duration, 0, 0, count, 0, 0))
- }
- }
- }
- function handleLoadResultCSV(fields, lines) {
- const version_i = fields.get('displayLabel');
- const page_i = fields.get('stories');
- const category_i = fields.get('name');
- const value_i = fields.get('avg');
- const tempEntriesCache = new Map();
- for (let i = 1; i < lines.length; i++) {
- const line = csvSplit(lines[i]);
- if (line.length == 0) continue;
- const raw_category = line[category_i];
- if (!raw_category.endsWith(':duration') &&
- !raw_category.endsWith(':count')) {
- continue;
- }
- let [category, type] = raw_category.split(':');
- if (import_skip_categories.has(category)) continue;
- const version = versions.getOrCreate(line[version_i]);
- const pageVersion = version.getOrCreate(line[page_i]);
- const value = Number.parseFloat(line[value_i]);
- const entry = TempEntry.get(tempEntriesCache, pageVersion, category);
- if (type == 'duration') {
- entry.durations.push(value)
- } else {
- entry.counts.push(value)
- }
- }
- tempEntriesCache.forEach((tempEntries, pageVersion) => {
- tempEntries.forEach(tmpEntry => {
- pageVersion.add(tmpEntry.toEntry())
- })
- });
- }
- class TempEntry {
- constructor(category) {
- this.category = category;
- this.durations = [];
- this.counts = [];
- }
- static get(cache, pageVersion, category) {
- let tempEntries = cache.get(pageVersion);
- if (tempEntries === undefined) {
- tempEntries = new Map();
- cache.set(pageVersion, tempEntries);
- }
- let tempEntry = tempEntries.get(category);
- if (tempEntry === undefined) {
- tempEntry = new TempEntry(category);
- tempEntries.set(category, tempEntry);
- }
- return tempEntry;
- }
- toEntry() {
- const [duration, durationStddev] = this.stats(this.durations);
- const [count, countStddev] = this.stats(this.durations);
- return new Entry(0, this.category,
- duration, durationStddev, 0, count, countStddev, 0)
- }
- stats(values) {
- let sum = 0;
- for (let i = 0; i < values.length; i++) {
- sum += values[i];
- }
- const avg = sum / values.length;
- let stddevSquared = 0;
- for (let i = 0; i < values.length; i++) {
- const delta = values[i] - avg;
- stddevSquared += delta * delta;
- }
- const stddev = Math.sqrt(stddevSquared / values.length);
- return [avg, stddev];
- }
- }
- function handleLoadTXT(txt, append, fileName) {
- fileName = window.prompt('Version name:', fileName);
- let isFirstLoad = pages === undefined;
- // Load raw RCS output which contains a single page
- if (!append || isFirstLoad) {
- pages = new Pages();
- versions = new Versions()
- }
- versions.add(Version.fromTXT(fileName, txt));
- }
- function displayResultsAfterLoading() {
- const isFirstLoad = pages === undefined;
- let state = getStateFromParams();
- initialize()
- if (isFirstLoad && !popHistoryState(state) && selectedPage) {
- showEntry(selectedPage.total);
- return;
- }
- const page = versions.versions[0].pages[0]
- if (page == undefined) return;
- showPage(page);
- showEntry(page.total);
- }
- function fixClusterTelemetryResults(json) {
- // Convert CT results to callstats compatible JSON
- // Input:
- // { VERSION_NAME: { PAGE: { METRIC: { "count": {XX}, "duration": {XX} }.. }}.. }
- let firstEntry;
- for (let key in json) {
- firstEntry = json[key];
- break;
- }
- // Return the original JSON if it is not a CT result.
- if (firstEntry.pairs === undefined) return json;
- // The results include already the group totals, remove them by filtering.
- let groupNames = new Set(Array.from(Group.groups.values()).map(e => e.name));
- let result = Object.create(null);
- for (let file_name in json) {
- let entries = [];
- let file_data = json[file_name].pairs;
- for (let name in file_data) {
- if (name != "Total" && groupNames.has(name)) continue;
- let entry = file_data[name];
- let count = entry.count;
- let time = entry.time;
- entries.push([name, time, 0, 0, count, 0, 0]);
- }
- let domain = file_name.split("/").slice(-1)[0];
- result[domain] = entries;
- }
- return {
- __proto__: null,
- ClusterTelemetry: result
- };
- }
- function fixTraceImportJSON(json) {
- // Fix json file that was created by converting a trace json output
- if (!('telemetry-results' in json)) return json;
- // { telemetry-results: { PAGE:[ { METRIC: [ COUNT TIME ], ... }, ... ]}}
- let version_data = {
- __proto__: null
- };
- json = json["telemetry-results"];
- for (let page_name in json) {
- if (page_name == "placeholder") continue;
- let page_data = {
- __proto__: null,
- Total: {
- duration: {
- average: 0,
- stddev: 0
- },
- count: {
- average: 0,
- stddev: 0
- }
- }
- };
- let page = json[page_name];
- for (let slice of page) {
- for (let metric_name in slice) {
- if (metric_name == "Blink_V8") continue;
- // sum up entries
- if (!(metric_name in page_data)) {
- page_data[metric_name] = {
- duration: {
- average: 0,
- stddev: 0
- },
- count: {
- average: 0,
- stddev: 0
- }
- }
- }
- let [metric_count, metric_duration] = slice[metric_name]
- let metric = page_data[metric_name];
- const kMicroToMilli = 1 / 1000;
- metric.duration.average += metric_duration * kMicroToMilli;
- metric.count.average += metric_count;
- if (metric_name.startsWith('Blink_')) continue;
- let total = page_data['Total'];
- total.duration.average += metric_duration * kMicroToMilli;
- total.count.average += metric_count;
- }
- }
- version_data[page_name] = page_data;
- }
- return version_data;
- }
- function fixSingleVersionJSON(json, name) {
- // Try to detect the single-version case, where we're missing the toplevel
- // version object. The incoming JSON is of the form:
- // { PAGE: ... , PAGE_2: }
- // Instead of the default multi-page JSON:
- // {"Version 1": { "Page 1": ..., ...}, "Version 2": {...}, ...}
- // In this case insert a single "Default" version as top-level entry.
- const firstProperty = (object) => {
- for (let key in object) return object[key];
- };
- const maybeMetrics = firstProperty(json);
- const maybeMetric = firstProperty(maybeMetrics);
- const tempName = name ? name : new Date().toISOString();
- const getFileName =
- () => window.prompt('Enter a name for the loaded file:', tempName);
- if ('count' in maybeMetric && 'duration' in maybeMetric) {
- return {
- [getFileName()]: json
- }
- }
- // Legacy fallback where the metrics are encoded as arrays:
- // { PAGE: [[metric_name, ...], [...], ]}
- // Also, make sure we don't have the versioned array-style:
- // { VERSION: { PAGE: [[metric_name, ...], [...], ]}, ...}
- const innerArray = firstProperty(maybeMetrics);
- if (Array.isArray(maybeMetric) && !Array.isArray(innerArray)) {
- return {
- [getFileName()]: json
- }
- }
- return json
- }
- let appendIndex = 0;
- function createUniqueVersions(json) {
- // Make sure all toplevel entries are unique names and added properly
- appendIndex++;
- let result = {
- __proto__: null
- }
- for (let key in json) {
- result[key + "_" + appendIndex] = json[key];
- }
- return result
- }
- function handleCopyToClipboard(event) {
- const names = ["Group", ...versions.versions.map(e => e.name)];
- let result = [names.join("\t")];
- let groups = Array.from(Group.groups.values());
- // Move the total group to the end.
- groups.push(groups.shift())
- groups.forEach(group => {
- let row = [group.name];
- versions.forEach(v => {
- const time = v.pages[0].get("Group-" + group.name)?._time ?? 0;
- row.push(time);
- })
- result.push(row.join("\t"));
- });
- result = result.join("\n");
- navigator.clipboard.writeText(result);
- }
- function handleToggleGroup(event) {
- let group = event.target.parentNode.parentNode.entry;
- toggleGroup(selectedPage.get(group.name), 'toggle');
- }
- function handleSelectPage(select, event) {
- let option = select.options[select.selectedIndex];
- if (select.id == "select_0") {
- showSelectedEntryInPage(option.page);
- } else {
- let columnIndex = select.id.split('_')[1];
- showPageInColumn(option.page, columnIndex);
- }
- }
- function handleSelectVersion(select, event) {
- let option = select.options[select.selectedIndex];
- let version = option.version;
- if (select.id == "selectVersion_0") {
- let page = version.get(selectedPage.name);
- showSelectedEntryInPage(page);
- } else {
- let columnIndex = select.id.split('_')[1];
- let pageSelect = $('select_' + columnIndex);
- let page = pageSelect.options[pageSelect.selectedIndex].page;
- page = version.get(page.name);
- showPageInColumn(page, columnIndex);
- }
- }
- function handleSelectDetailRow(table, event) {
- if (event.target.tagName != 'TD') return;
- let tr = event.target.parentNode;
- if (tr.tagName != 'TR') return;
- if (tr.entry === undefined) return;
- selectEntry(tr.entry, true);
- }
- function handleSelectRow(table, event, fromDetail) {
- if (event.target.tagName != 'TD') return;
- let tr = event.target.parentNode;
- if (tr.tagName != 'TR') return;
- if (tr.entry === undefined) return;
- selectEntry(tr.entry, false);
- }
- function handleSelectBaseline(select, event) {
- let option = select.options[select.selectedIndex];
- baselineVersion = option.version;
- let showingDiff = baselineVersion !== undefined;
- let body = $('body');
- toggleCssClass(body, 'diff', showingDiff);
- toggleCssClass(body, 'noDiff', !showingDiff);
- showPage(selectedPage);
- if (selectedEntry === undefined) return;
- selectEntry(selectedEntry, true);
- }
- function findEntry(event) {
- let target = event.target;
- while (target.entry === undefined) {
- target = target.parentNode;
- if (!target) return undefined;
- }
- return target.entry;
- }
- function handleUpdatePopover(event) {
- let popover = $('popover');
- popover.style.left = event.pageX + 'px';
- popover.style.top = event.pageY + 'px';
- popover.style.display = 'none';
- popover.style.display = event.shiftKey ? 'block' : 'none';
- let entry = findEntry(event);
- if (entry === undefined) return;
- showPopover(entry);
- }
- function handleToggleVersionOrPageEnable(event) {
- let item = this.item;
- if (item === undefined) return;
- item.enabled = this.checked;
- initialize();
- let page = selectedPage;
- if (page === undefined || !page.version.enabled) {
- page = versions.getEnabledPage(page.name);
- }
- if (!page.enabled) {
- page = page.getNextPage();
- }
- showPage(page);
- }
- function handleCodeSearch(event) {
- let entry = findEntry(event);
- if (entry === undefined) return;
- let url = "https://cs.chromium.org/search/?sq=package:chromium&type=cs&q=";
- name = entry.name;
- if (name.startsWith("API_")) {
- name = name.substring(4);
- }
- url += encodeURIComponent(name) + "+file:src/v8/src";
- window.open(url, '_blank');
- }
- </script>
- <script>
- "use strict"
- // =========================================================================
- class Versions {
- constructor() {
- this.versions = [];
- }
- add(version) {
- this.versions.push(version);
- return version;
- }
- getPageVersions(page) {
- let result = [];
- this.versions.forEach((version) => {
- if (!version.enabled) return;
- let versionPage = version.get(page.name);
- if (versionPage !== undefined) result.push(versionPage);
- });
- return result;
- }
- get length() {
- return this.versions.length
- }
- get(index) {
- return this.versions[index]
- }
- getByName(name) {
- return this.versions.find((each) => each.name == name);
- }
- getOrCreate(name) {
- return this.getByName(name) ?? this.add(new Version(name));
- }
- forEach(f) {
- this.versions.forEach(f);
- }
- sort() {
- this.versions.sort(NameComparator);
- }
- getEnabledPage(name) {
- for (let i = 0; i < this.versions.length; i++) {
- let version = this.versions[i];
- if (!version.enabled) continue;
- let page = version.get(name);
- if (page !== undefined) return page;
- }
- }
- static fromJSON(json) {
- let versions = new Versions();
- for (let version in json) {
- versions.add(Version.fromJSON(version, json[version]));
- }
- versions.sort();
- return versions;
- }
- }
- class Version {
- constructor(name) {
- this.name = name;
- this.enabled = true;
- this.pages = [];
- }
- add(page) {
- this.pages.push(page);
- return page;
- }
- indexOf(name) {
- for (let i = 0; i < this.pages.length; i++) {
- if (this.pages[i].name == name) return i;
- }
- return -1;
- }
- getNextPage(page) {
- if (this.length == 0) return undefined;
- return this.pages[(this.indexOf(page.name) + 1) % this.length];
- }
- get(name) {
- let index = this.indexOf(name);
- if (0 <= index) return this.pages[index];
- return undefined;
- }
- getOrCreate(name) {
- return this.get(name) ??
- this.add(new PageVersion(this, pages.getOrCreate(name)));
- }
- get length() {
- return this.pages.length;
- }
- getEntry(entry) {
- if (entry === undefined) return undefined;
- let page = this.get(entry.page.name);
- if (page === undefined) return undefined;
- return page.get(entry.name);
- }
- forEachEntry(fun) {
- this.forEachPage((page) => {
- page.forEach(fun);
- });
- }
- forEachPage(fun) {
- this.pages.forEach((page) => {
- if (!page.enabled) return;
- fun(page);
- })
- }
- allEntries() {
- let map = new Map();
- this.forEachEntry((group, entry) => {
- if (!map.has(entry.name)) map.set(entry.name, entry);
- });
- return Array.from(map.values());
- }
- getTotalValue(name, property) {
- if (name === undefined) name = this.pages[0].total.name;
- let sum = 0;
- this.forEachPage((page) => {
- let entry = page.get(name);
- if (entry !== undefined) sum += entry[property];
- });
- return sum;
- }
- getTotalTime(name, showDiff) {
- return this.getTotalValue(name, showDiff === false ? '_time' : 'time');
- }
- getTotalTimePercent(name, showDiff) {
- if (baselineVersion === undefined || showDiff === false) {
- // Return the overall average percent of the given entry name.
- return this.getTotalValue(name, 'time') /
- this.getTotalTime('Group-Total') * 100;
- }
- // Otherwise return the difference to the sum of the baseline version.
- let baselineValue = baselineVersion.getTotalTime(name, false);
- let total = this.getTotalValue(name, '_time');
- return (total / baselineValue - 1) * 100;
- }
- getTotalTimeVariance(name, showDiff) {
- // Calculate the overall error for a given entry name
- let sum = 0;
- this.forEachPage((page) => {
- let entry = page.get(name);
- if (entry === undefined) return;
- sum += entry.timeVariance * entry.timeVariance;
- });
- return Math.sqrt(sum);
- }
- getTotalTimeVariancePercent(name, showDiff) {
- return this.getTotalTimeVariance(name, showDiff) /
- this.getTotalTime(name, showDiff) * 100;
- }
- getTotalCount(name, showDiff) {
- return this.getTotalValue(name, showDiff === false ? '_count' : 'count');
- }
- getAverageTimeImpact(name, showDiff) {
- return this.getTotalTime(name, showDiff) / this.pages.length;
- }
- getPagesByPercentImpact(name) {
- let sortedPages =
- this.pages.filter((each) => {
- return each.get(name) !== undefined
- });
- sortedPages.sort((a, b) => {
- return b.get(name).timePercent - a.get(name).timePercent;
- });
- return sortedPages;
- }
- sort() {
- this.pages.sort(NameComparator)
- }
- static fromJSON(name, data) {
- let version = new Version(name);
- for (let pageName in data) {
- version.add(PageVersion.fromJSON(version, pageName, data[pageName]));
- }
- version.sort();
- return version;
- }
- static fromTXT(name, txt) {
- let version = new Version(name);
- let defaultName = "RAW DATA";
- PageVersion.fromTXT(version, defaultName, txt)
- .forEach(each => version.add(each));
- return version;
- }
- }
- class Pages extends Map {
- get(name) {
- if (name.indexOf('www.') == 0) {
- name = name.substring(4);
- }
- if (!this.has(name)) {
- this.set(name, new Page(name));
- }
- return super.get(name);
- }
- getOrCreate(name) {
- return this.get(name);
- }
- }
- class Page {
- constructor(name) {
- this.name = name;
- this.enabled = true;
- this.versions = [];
- }
- add(pageVersion) {
- this.versions.push(pageVersion);
- return pageVersion;
- }
- }
- class PageVersion {
- constructor(version, page) {
- this.page = page;
- this.page.add(this);
- this.total = Group.groups.get('total').entry();
- this.total.isTotal = true;
- this.unclassified = new UnclassifiedEntry(this)
- this.groups = [
- this.total,
- Group.groups.get('ic').entry(),
- Group.groups.get('optimize-background').entry(),
- Group.groups.get('optimize').entry(),
- Group.groups.get('compile-background').entry(),
- Group.groups.get('compile').entry(),
- Group.groups.get('parse-background').entry(),
- Group.groups.get('parse').entry(),
- Group.groups.get('blink').entry(),
- Group.groups.get('callback').entry(),
- Group.groups.get('api').entry(),
- Group.groups.get('gc-custom').entry(),
- Group.groups.get('gc-background').entry(),
- Group.groups.get('gc').entry(),
- Group.groups.get('javascript').entry(),
- Group.groups.get('websnapshot').entry(),
- Group.groups.get('runtime').entry(),
- this.unclassified
- ];
- this.entryDict = new Map();
- this.groups.forEach((entry) => {
- entry.page = this;
- this.entryDict.set(entry.name, entry);
- });
- this.version = version;
- }
- toString() {
- return this.version.name + ": " + this.name;
- }
- urlParams() {
- return {
- version: this.version.name,
- page: this.name
- };
- }
- add(entry) {
- let existingEntry = this.entryDict.get(entry.name);
- if (existingEntry !== undefined) {
- // Duplicate entries happen when multiple runs are combined into a
- // single file.
- existingEntry.add(entry);
- for (let i = 0; i < this.groups.length; i++) {
- const group = this.groups[i];
- if (group.addTimeAndCount(entry)) return;
- }
- } else {
- // Ignore accidentally added Group entries.
- if (entry.name.startsWith(GroupedEntry.prefix)) {
- console.warn("Skipping accidentally added Group entry:", entry, this);
- return;
- }
- entry.page = this;
- this.entryDict.set(entry.name, entry);
- for (let group of this.groups) {
- if (group.add(entry)) return;
- }
- }
- console.error("Should not get here", entry);
- }
- get(name) {
- return this.entryDict.get(name)
- }
- getEntry(entry) {
- if (entry === undefined) return undefined;
- return this.get(entry.name);
- }
- get length() {
- return this.versions.length
- }
- get name() {
- return this.page.name
- }
- get enabled() {
- return this.page.enabled
- }
- forEachSorted(referencePage, func) {
- // Iterate over all the entries in the order they appear on the
- // reference page.
- referencePage.forEach((parent, referenceEntry) => {
- let entry;
- if (parent) parent = this.entryDict.get(parent.name);
- if (referenceEntry) entry = this.entryDict.get(referenceEntry.name);
- func(parent, entry, referenceEntry);
- });
- }
- forEach(fun) {
- this.forEachGroup((group) => {
- fun(undefined, group);
- group.forEach((entry) => {
- fun(group, entry)
- });
- });
- }
- forEachGroup(fun) {
- this.groups.forEach(fun)
- }
- sort() {
- this.groups.sort((a, b) => {
- return b.time - a.time;
- });
- this.groups.forEach((group) => {
- group.sort()
- });
- }
- distanceFromTotalPercent() {
- let sum = 0;
- this.groups.forEach(group => {
- if (group == this.total) return;
- let value = group.getTimePercentImpact() -
- this.getEntry(group).timePercent;
- sum += value * value;
- });
- return sum;
- }
- getNextPage() {
- return this.version.getNextPage(this);
- }
- static fromJSON(version, name, data) {
- let page = new PageVersion(version, pages.get(name));
- // Distinguish between the legacy format which just uses Arrays,
- // or the new object style.
- if (Array.isArray(data)) {
- for (let i = 0; i < data.length; i++) {
- page.add(Entry.fromLegacyJSON(i, data[data.length - i - 1]));
- }
- } else {
- let position = 0;
- for (let metric_name in data) {
- page.add(Entry.fromJSON(position, metric_name, data[metric_name]));
- position++;
- }
- }
- page.sort();
- return page
- }
- static fromTXT(version, defaultName, txt) {
- const kPageNameIdentifier = "== Page:";
- const kCommentStart = "=="
- const lines = txt.split('\n');
- const split = / +/g
- const result = [];
- let pageVersion = undefined;
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- // Skip header separators
- if (line.startsWith(kCommentStart)) {
- // Check for page names
- if (line.startsWith(kPageNameIdentifier)) {
- const name = line.split(kPageNameIdentifier)[1];
- pageVersion = new PageVersion(version, pages.get(name));
- result.push(pageVersion);
- }
- }
- // Skip header lines.
- if (lines[i + 1]?.startsWith(kCommentStart)) continue;
- const split_line = line.trim().split(split)
- if (split_line.length != 5) continue;
- if (pageVersion === undefined) {
- pageVersion = new PageVersion(version, pages.get(defaultName));
- result.push(pageVersion);
- }
- const position = i - 2;
- pageVersion.add(Entry.fromTXT(position, split_line));
- }
- return result;
- }
- }
- class Entry {
- constructor(position, name, time, timeVariance, timeVariancePercent,
- count, countVariance, countVariancePercent) {
- this.position = position;
- this.name = name;
- this._time = time;
- this._timeVariance = timeVariance;
- this._timeVariancePercent =
- this._variancePercent(time, timeVariance, timeVariancePercent);
- this._count = count;
- this.countVariance = countVariance;
- this.countVariancePercent =
- this._variancePercent(count, countVariance, countVariancePercent);
- this.page = undefined;
- this.parent = undefined;
- this.isTotal = false;
- }
- _variancePercent(value, valueVariance, valueVariancePercent) {
- if (valueVariancePercent) return valueVariancePercent;
- if (!valueVariance) return 0;
- return valueVariance / value * 100;
- }
- add(entry) {
- if (this.name !== entry.name) {
- console.error("Should not combine entries with different names");
- return;
- }
- this._time += entry._time;
- this._count += entry._count;
- }
- urlParams() {
- let params = this.page.urlParams();
- params.entry = this.name;
- return params;
- }
- getCompareWithBaseline(value, property) {
- if (baselineVersion == undefined) return value;
- let baselineEntry = baselineVersion.getEntry(this);
- if (!baselineEntry) return value;
- if (baselineVersion === this.page.version) return value;
- return value - baselineEntry[property];
- }
- cssClass() {
- return ''
- }
- get time() {
- return this.getCompareWithBaseline(this._time, '_time');
- }
- get count() {
- return this.getCompareWithBaseline(this._count, '_count');
- }
- get timePercent() {
- let value = this._time / this.page.total._time * 100;
- if (baselineVersion == undefined) return value;
- let baselineEntry = baselineVersion.getEntry(this);
- if (!baselineEntry) return value;
- if (baselineVersion === this.page.version) return value;
- return (this._time - baselineEntry._time) / this.page.total._time *
- 100;
- }
- get timePercentPerEntry() {
- let value = this._time / this.page.total._time * 100;
- if (baselineVersion == undefined) return value;
- let baselineEntry = baselineVersion.getEntry(this);
- if (!baselineEntry) return value;
- if (baselineVersion === this.page.version) return value;
- return (this._time / baselineEntry._time - 1) * 100;
- }
- get timePercentVariancePercent() {
- // Get the absolute values for the percentages
- return this.timeVariance / this.page.total._time * 100;
- }
- getTimeImpact(showDiff) {
- return this.page.version.getTotalTime(this.name, showDiff);
- }
- getTimeImpactVariancePercent(showDiff) {
- return this.page.version.getTotalTimeVariancePercent(this.name, showDiff);
- }
- getTimePercentImpact(showDiff) {
- return this.page.version.getTotalTimePercent(this.name, showDiff);
- }
- getCountImpact(showDiff) {
- return this.page.version.getTotalCount(this.name, showDiff);
- }
- getAverageTimeImpact(showDiff) {
- return this.page.version.getAverageTimeImpact(this.name, showDiff);
- }
- getPagesByPercentImpact() {
- return this.page.version.getPagesByPercentImpact(this.name);
- }
- get isGroup() {
- return false;
- }
- get timeVariance() {
- return this._timeVariance;
- }
- get timeVariancePercent() {
- return this._timeVariancePercent;
- }
- static fromLegacyJSON(position, data) {
- return new Entry(position, ...data);
- }
- static fromJSON(position, name, data) {
- let time = data.duration;
- let count = data.count;
- return new Entry(position, name, time.average, time.stddev, 0,
- count.average, count.stddev, 0);
- }
- static fromTXT(position, splitLine) {
- const name = splitLine[0];
- let time = splitLine[1];
- const msIndex = time.indexOf('m');
- if (msIndex > 0) time = time.substring(0, msIndex);
- const timePercent = splitLine[2];
- const count = splitLine[3];
- const countPercent = splitLine[4];
- const timeDeviation = 0;
- const countDeviation = 0;
- const timeDeviationPercent = 0;
- const countDeviationPercent = 0
- return new Entry(position, name,
- Number.parseFloat(time), timeDeviation, timeDeviationPercent,
- Number.parseInt(count), countDeviation, countDeviationPercent)
- }
- }
- class Group {
- constructor(name, regexp, color, enabled = true, addsToTotal = true) {
- this.name = name;
- this.regexp = regexp;
- this.color = color;
- this.enabled = enabled;
- this.addsToTotal = addsToTotal;
- }
- entry() {
- return new GroupedEntry(this);
- }
- }
- Group.groups = new Map();
- Group.add = function (name, group) {
- this.groups.set(name, group);
- return group;
- }
- Group.add('total', new Group('Total', /.*Total.*/, '#BBB', true, false));
- Group.add('ic', new Group('IC', /(.*IC_.*)|IC/, "#3366CC"));
- Group.add('optimize-background', new Group('Optimize-Background',
- /.*Optimize(d?-?)(Background|Concurrent).*/, "#702000"));
- Group.add('optimize', new Group('Optimize',
- /(StackGuard|Optimize|Deoptimize|Recompile).*/, "#DC3912"));
- Group.add('compile-background', new Group('Compile-Background',
- /(.*Compile-?Background.*)/, "#b08000"));
- Group.add('compile', new Group('Compile',
- /(^Compile.*)|(.*_Compile.*)/, "#FFAA00"));
- Group.add('parse-background',
- new Group('Parse-Background', /.*Parse-?Background.*/, "#c05000"));
- Group.add('parse', new Group('Parse', /.*Parse.*/, "#FF6600"));
- Group.add('callback',
- new Group('Blink C++', /.*(Callback)|(Blink C\+\+).*/, "#109618"));
- Group.add('api', new Group('API', /.*API.*/, "#990099"));
- Group.add('gc-custom', new Group('GC-Custom', /GC_Custom_.*/, "#0099C6"));
- Group.add('gc-background',
- new Group(
- 'GC-Background', /.*GC.*(BACKGROUND|Background).*/, "#00597c"));
- Group.add('gc',
- new Group('GC', /GC_.*|AllocateInTargetSpace|GC/, "#00799c"));
- Group.add('javascript',
- new Group('JavaScript', /JS_Execution|JavaScript/, "#DD4477"));
- Group.add('websnapshot', new Group('WebSnapshot', /.*Web.*/, "#E8E11C"));
- Group.add('runtime', new Group('V8 C++', /.*/, "#88BB00"));
- Group.add('blink',
- new Group('Blink RCS', /.*Blink_.*/, "#006600", false, false));
- Group.add('unclassified', new Group('Unclassified', /.*/, "#000", false));
- class GroupedEntry extends Entry {
- constructor(group) {
- super(0, GroupedEntry.prefix + group.name, 0, 0, 0, 0, 0, 0);
- this.group = group;
- this.entries = [];
- this.missingEntries = null;
- this.addsToTotal = group.addsToTotal;
- }
- get regexp() {
- return this.group.regexp;
- }
- get color() {
- return this.group.color;
- }
- get enabled() {
- return this.group.enabled;
- }
- add(entry) {
- if (!this.addTimeAndCount(entry)) return;
- // TODO: sum up variance
- this.entries.push(entry);
- entry.parent = this;
- return true;
- }
- addTimeAndCount(entry) {
- if (!this.regexp.test(entry.name)) return false;
- this._time += entry.time;
- this._count += entry.count;
- return true;
- }
- _initializeMissingEntries() {
- let dummyEntryNames = new Set();
- versions.forEach((version) => {
- let page = version.getOrCreate(this.page.name);
- let groupEntry = page.get(this.name);
- if (groupEntry != this) {
- for (let entry of groupEntry.entries) {
- if (this.page.get(entry.name) == undefined) {
- dummyEntryNames.add(entry.name);
- }
- }
- }
- });
- this.missingEntries = [];
- for (let name of dummyEntryNames) {
- let tmpEntry = new Entry(0, name, 0, 0, 0, 0, 0, 0);
- tmpEntry.page = this.page;
- this.missingEntries.push(tmpEntry);
- };
- }
- forEach(fun) {
- // Show also all entries which are in at least one version.
- // Concatenate our real entries.
- if (this.missingEntries == null) {
- this._initializeMissingEntries();
- }
- let tmpEntries = this.missingEntries.concat(this.entries);
- // The compared entries are sorted by absolute impact.
- tmpEntries.sort((a, b) => {
- return b.time - a.time
- });
- tmpEntries.forEach(fun);
- }
- sort() {
- this.entries.sort((a, b) => {
- return b.time - a.time;
- });
- }
- cssClass() {
- if (this.page.total == this) return 'total';
- return '';
- }
- get isGroup() {
- return true
- }
- getVarianceForProperty(property) {
- let sum = 0;
- const key = property + 'Variance';
- this.entries.forEach((entry) => {
- const value = entry[key];
- sum += value * value;
- });
- return Math.sqrt(sum);
- }
- get timeVariancePercent() {
- if (this._time == 0) return 0;
- return this.getVarianceForProperty('time') / this._time * 100
- }
- get timeVariance() {
- return this.getVarianceForProperty('time')
- }
- }
- GroupedEntry.prefix = 'Group-';
- class UnclassifiedEntry extends GroupedEntry {
- constructor(page) {
- super(Group.groups.get('unclassified'));
- this.page = page;
- this._time = undefined;
- this._count = undefined;
- }
- add(entry) {
- console.log("Adding unclassified:", entry);
- this.entries.push(entry);
- entry.parent = this;
- return true;
- }
- forEachPageGroup(fun) {
- this.page.forEachGroup((group) => {
- if (group == this) return;
- if (group == this.page.total) return;
- fun(group);
- });
- }
- get time() {
- if (this._time === undefined) {
- this._time = this.page.total._time;
- this.forEachPageGroup((group) => {
- if (group.addsToTotal) this._time -= group._time;
- });
- }
- return this.getCompareWithBaseline(this._time, '_time');
- }
- get count() {
- if (this._count === undefined) {
- this._count = this.page.total._count;
- this.forEachPageGroup((group) => {
- this._count -= group._count;
- });
- }
- return this.getCompareWithBaseline(this._count, '_count');
- }
- }
- </script>
- </head>
- <body id="body" onmousemove="handleUpdatePopover(event)" onload="handleBodyLoad()" class="noDiff">
- <h1>Runtime Stats Komparator</h1>
- <section id="inputs" class="panel alwaysVisible">
- <input type="checkbox" id="inputsCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="inputsCheckbox">▼</label>
- <h2>Input/Output</h2>
- <div class="panelBody">
- <form name="fileForm" class="inline">
- <p class="inline">
- <label for="uploadInput">Load Files:</label>
- <input id="uploadInput" type="file" name="files" onchange="handleLoadFiles();" multiple
- accept=".json,.txt,.csv,.output">
- </p>
- <p class="inline">
- <label for="appendInput">Append Files:</label>
- <input id="appendInput" type="file" name="files" onchange="handleAppendFiles();" multiple
- accept=".json,.txt,.csv,.output">
- </p>
- </form>
- <p class="inline">
- <button onclick="handleCopyToClipboard()">Copy Table to Clipboard</button>
- </p>
- </div>
- </section>
- <section class="panel">
- <h2>Baseline Selector</h2>
- <div class="panel-body">
- Compare against baseline: <select id="baseline" onchange="handleSelectBaseline(this, event)"></select><br />
- <span style="color: #060">Green</span> a selected version performs
- better than the baseline.
- </div>
- </section>
- <section class="panel-group">
- <div id="versionSelector" class="panel">
- <input type="checkbox" checked id="versionSelectorCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="versionSelectorCheckbox">▼</label>
- <h2>Selected Versions</h2>
- <div class="panelBody">
- <ul></ul>
- </div>
- </div>
- <div id="pageSelector" class="panel">
- <input type="checkbox" checked id="pageSelectorCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="pageSelectorCheckbox">▼</label>
- <h2>Selected Pages</h2>
- <div class="panelBody">
- <ul></ul>
- </div>
- </div>
- <div id="groupSelector" class="panel">
- <input type="checkbox" checked id="groupSelectorCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="groupSelectorCheckbox">▼</label>
- <h2>Selected RCS Groups</h2>
- <div class="panelBody">
- <ul></ul>
- </div>
- </div>
- </section>
- <section id="view" class="panel">
- <input type="checkbox" id="tableViewCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="tableViewCheckbox">▼</label>
- <h2>RCS Table</h2>
- <div class="panelBody"></div>
- </section>
- <section class="panel-group">
- <div id="versionDetails" class="panel">
- <input type="checkbox" checked id="versionDetailCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="versionDetailCheckbox">▼</label>
- <h2><span>Compare Page Versions</span></h2>
- <div class="conten panelBody">
- <table class="versionDetailTable" onclick="handleSelectDetailRow(this, event);">
- <thead>
- <tr>
- <th class="version">Version </th>
- <th class="position">Pos. </th>
- <th class="value time">Time▴ </th>
- <th class="value time">Percent </th>
- <th class="value count">Count </th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- <div id="pageDetail" class="panel">
- <input type="checkbox" checked id="pageDetailCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="pageDetailCheckbox">▼</label>
- <h2>Page Comparison for <span></span></h2>
- <div class="panelBody">
- <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);">
- <thead>
- <tr>
- <th class="page">Page </th>
- <th class="value time">Time </th>
- <th class="value time">Percent▾ </th>
- <th class="value time hideNoDiff">%/Entry </th>
- <th class="value count">Count </th>
- </tr>
- </thead>
- <tfoot>
- <tr>
- <td class="page">Total:</td>
- <td class="value time"></td>
- <td class="value time"></td>
- <td class="value time hideNoDiff"></td>
- <td class="value count"></td>
- </tr>
- </tfoot>
- <tbody></tbody>
- </table>
- </div>
- </div>
- <div id="impactView" class="panel">
- <input type="checkbox" checked id="impactViewCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="impactViewCheckbox">▼</label>
- <h2>Impact list for <span></span></h2>
- <div class="panelBody">
- <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);">
- <thead>
- <tr>
- <th class="page">Name </th>
- <th class="value time">Time </th>
- <th class="value time">Percent▾ </th>
- <th class="">Top Pages</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- </div>
- </section>
- <section id="pageVersionGraph" class="panel">
- <input type="checkbox" id="pageVersionGraphCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="pageVersionGraphCheckbox">▼</label>
- <h2><span></span></h2>
- <div class="panelBody"></div>
- </section>
- <section id="pageGraph" class="panel">
- <input type="checkbox" id="pageGraphCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="pageGraphCheckbox">▼</label>
- <h2><span></span></h2>
- <div class="panelBody"></div>
- </section>
- <section id="versionGraph" class="panel">
- <input type="checkbox" id="versionGraphCheckbox" class="panelCloserInput">
- <label class="panelCloserLabel" for="versionGraphCheckbox">▼</label>
- <h2><span></span></h2>
- <div class="panelBody"></div>
- </section>
- <div id="column" class="column">
- <div class="header">
- <select class="version" onchange="handleSelectVersion(this, event);"></select>
- <select class="pageVersion" onchange="handleSelectPage(this, event);"></select>
- </div>
- <table class="list" onclick="handleSelectRow(this, event);">
- <thead>
- <tr>
- <th class="position">Pos. </th>
- <th class="name">Name </th>
- <th class="value time">Time </th>
- <th class="value time">Percent </th>
- <th class="value count">Count </th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- <section class="panel alwaysVisible">
- <h2>Instructions</h2>
- <div class="panelBody">
- <ol>
- <li>Build chrome.</li>
- </ol>
- <h3>Telemetry benchmark</h3>
- <ol>
- <li>Run <code>v8.browsing</code> benchmarks:
- <pre>$CHROMIUM_DIR/tools/perf/run_benchmark run v8.browsing_desktop \
- --browser=exact --browser-executable=$CHROMIUM_DIR/out/release/chrome \
- --story-filter='.*2020 ' \
- --also-run-disabled-tests
- </pre>
- </li>
- <li>Install <a href="https://stedolan.github.io/jq/">jq</a>.</li>
- <li>Convert the telemetry JSON files to callstats JSON file:
- <pre>
- $V8_DIR/tools/callstats-from-telemetry.sh $CHROMIUM_DIR/tools/perf/artifacts/run_XXXX
- </pre>
- </li>
- <li>Load the generated <code>out.json</code></li>
- </ol>
- <h3>Merged CSV from results.html</h3>
- <ol>
- <li>Open a results.html page for RCS-enabled benchmarks</li>
- <li>Select "Export merged CSV" in the toolbar</li>
- <li>Load the downloading .csv file normally in callstats.html</li>
- </ol>
- <h3>Aggregated raw txt output</h3>
- <ol>
- <li>Install scipy, e.g. <code>sudo aptitude install python-scipy</code>
- <li>Check out a known working version of webpagereply:
- <pre>git -C $CHROME_DIR/third_party/webpagereplay checkout 7dbd94752d1cde5536ffc623a9e10a51721eff1d</pre>
- </li>
- <li>Run <code>callstats.py</code> with a web-page-replay archive:
- <pre>$V8_DIR/tools/callstats.py run \
- --replay-bin=$CHROME_SRC/third_party/webpagereplay/replay.py \
- --replay-wpr=$INPUT_DIR/top25.wpr \
- --js-flags="" \
- --with-chrome=$CHROME_SRC/out/Release/chrome \
- --sites-file=$INPUT_DIR/top25.json</pre>
- </li>
- <li>Move results file to a subdirectory: <code>mkdir $VERSION_DIR; mv *.txt $VERSION_DIR</code></li>
- <li>Repeat from step 1 with a different configuration (e.g. <code>--js-flags="--nolazy"</code>).</li>
- <li>Create the final results file: <code>./callstats.py json $VERSION_DIR1 $VERSION_DIR2 > result.json</code>
- </li>
- <li>Use <code>results.json</code> on this site.</code>
- </ol>
- </div>
- </section>
- <div id="popover">
- <div class="popoverArrow"></div>
- <table>
- <tr>
- <td class="name" colspan="6"></td>
- </tr>
- <tr>
- <td>Page:</td>
- <td class="page name" colspan="6"></td>
- </tr>
- <tr>
- <td>Version:</td>
- <td class="version name" colspan="3"></td>
- <td class="compare version name" colspan="3"></td>
- </tr>
- <tr>
- <td>Time:</td>
- <td class="time"></td>
- <td>±</td>
- <td class="timeVariance"></td>
- <td class="compare time"></td>
- <td class="compare"> ± </td>
- <td class="compare timeVariance"></td>
- </tr>
- <tr>
- <td>Percent:</td>
- <td class="percent"></td>
- <td>±</td>
- <td class="percentVariance"></td>
- <td class="compare percent"></td>
- <td class="compare"> ± </td>
- <td class="compare percentVariance"></td>
- </tr>
- <tr>
- <td>Percent per Entry:</td>
- <td class="percentPerEntry"></td>
- <td colspan=2></td>
- <td class="compare percentPerEntry"></td>
- <td colspan=2></td>
- </tr>
- <tr>
- <td>Count:</td>
- <td class="count"></td>
- <td>±</td>
- <td class="countVariance"></td>
- <td class="compare count"></td>
- <td class="compare"> ± </td>
- <td class="compare countVariance"></td>
- </tr>
- <tr>
- <td>Overall Impact:</td>
- <td class="timeImpact"></td>
- <td>±</td>
- <td class="timePercentImpact"></td>
- <td class="compare timeImpact"></td>
- <td class="compare"> ± </td>
- <td class="compare timePercentImpact"></td>
- </tr>
- </table>
- </div>
- </body>
- </html>
|