1<html> 2<!-- 3Copyright 2016 the V8 project authors. All rights reserved. Use of this source 4code is governed by a BSD-style license that can be found in the LICENSE file. 5--> 6 7<head> 8 <meta charset="UTF-8"> 9 <style> 10 body { 11 font-family: arial; 12 } 13 14 table { 15 display: table; 16 border-spacing: 0px; 17 } 18 19 tr { 20 border-spacing: 0px; 21 padding: 10px; 22 } 23 24 td, 25 th { 26 padding: 3px 10px 3px 5px; 27 } 28 29 .inline { 30 display: inline-block; 31 vertical-align: top; 32 } 33 34 h2, 35 h3 { 36 margin-bottom: 0px; 37 } 38 39 .hidden { 40 display: none; 41 } 42 43 .view { 44 display: table; 45 } 46 47 .column { 48 display: table-cell; 49 border-right: 1px black dotted; 50 min-width: 200px; 51 } 52 53 .column .header { 54 padding: 0 10px 0 10px 55 } 56 57 #column { 58 display: none; 59 } 60 61 .list { 62 width: 100%; 63 } 64 65 select { 66 width: 100% 67 } 68 69 .list tbody { 70 cursor: pointer; 71 } 72 73 .list tr:nth-child(even) { 74 background-color: #EFEFEF; 75 } 76 77 .list tr:nth-child(even).selected { 78 background-color: #DDD; 79 } 80 81 .list tr.child { 82 display: none; 83 } 84 85 .list tr.child.visible { 86 display: table-row; 87 } 88 89 .list .child .name { 90 padding-left: 20px; 91 } 92 93 .list .parent td { 94 border-top: 1px solid #AAA; 95 } 96 97 .list .total { 98 font-weight: bold 99 } 100 101 .list tr.parent { 102 background-color: #FFF; 103 } 104 105 .list tr.parent.selected { 106 background-color: #DDD; 107 } 108 109 tr.selected { 110 background-color: #DDD; 111 } 112 113 .codeSearch { 114 display: block-inline; 115 float: right; 116 border-radius: 5px; 117 background-color: #EEE; 118 width: 1em; 119 text-align: center; 120 } 121 122 .list .position { 123 text-align: right; 124 display: none; 125 } 126 127 .list div.toggle { 128 cursor: pointer; 129 } 130 131 #column_0 .position { 132 display: table-cell; 133 } 134 135 #column_0 .name { 136 display: table-cell; 137 } 138 139 .list .name { 140 display: none; 141 white-space: nowrap; 142 } 143 144 .value { 145 text-align: right; 146 } 147 148 .selectedVersion { 149 font-weight: bold; 150 } 151 152 #baseline { 153 width: auto; 154 } 155 156 .compareSelector { 157 padding-bottom: 20px; 158 } 159 160 .pageDetailTable tbody { 161 cursor: pointer 162 } 163 164 .pageDetailTable tfoot td { 165 border-top: 1px grey solid; 166 } 167 168 #popover { 169 position: absolute; 170 transform: translateY(-50%) translateX(40px); 171 box-shadow: -2px 10px 44px -10px #000; 172 border-radius: 5px; 173 z-index: 1; 174 background-color: #FFF; 175 display: none; 176 white-space: nowrap; 177 } 178 179 #popover table { 180 position: relative; 181 z-index: 1; 182 text-align: right; 183 margin: 10px; 184 } 185 #popover td { 186 padding: 3px 0px 3px 5px; 187 white-space: nowrap; 188 } 189 190 .popoverArrow { 191 background-color: #FFF; 192 position: absolute; 193 width: 30px; 194 height: 30px; 195 transform: translateY(-50%)rotate(45deg); 196 top: 50%; 197 left: -10px; 198 z-index: 0; 199 } 200 201 #popover .name { 202 padding: 5px; 203 font-weight: bold; 204 text-align: center; 205 } 206 207 #popover table .compare { 208 display: none 209 } 210 211 #popover table.compare .compare { 212 display: table-cell; 213 } 214 215 #popover .compare .time, 216 #popover .compare .version { 217 padding-left: 10px; 218 } 219 .graph, 220 .graph .content { 221 width: 100%; 222 } 223 224 .diff .hideDiff { 225 display: none; 226 } 227 .noDiff .hideNoDiff { 228 display: none; 229 } 230 </style> 231 <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> 232 <script type="text/javascript"> 233 "use strict" 234 google.charts.load('current', {packages: ['corechart']}); 235 236 // Did anybody say monkeypatching? 237 if (!NodeList.prototype.forEach) { 238 NodeList.prototype.forEach = function(func) { 239 for (var i = 0; i < this.length; i++) { 240 func(this[i]); 241 } 242 } 243 } 244 245 var versions; 246 var pages; 247 var selectedPage; 248 var baselineVersion; 249 var selectedEntry; 250 251 // Marker to programatically replace the defaultData. 252 var defaultData = /*default-data-start*/undefined/*default-data-end*/; 253 254 function initialize() { 255 // Initialize the stats table and toggle lists. 256 var original = $("column"); 257 var view = document.createElement('div'); 258 view.id = 'view'; 259 var i = 0; 260 versions.forEach((version) => { 261 if (!version.enabled) return; 262 // add column 263 var column = original.cloneNode(true); 264 column.id = "column_" + i; 265 // Fill in all versions 266 var select = column.querySelector(".version"); 267 select.id = "selectVersion_" + i; 268 // add all select options 269 versions.forEach((version) => { 270 if (!version.enabled) return; 271 var option = document.createElement("option"); 272 option.textContent = version.name; 273 option.version = version; 274 select.appendChild(option); 275 }); 276 // Fill in all page versions 277 select = column.querySelector(".pageVersion"); 278 select.id = "select_" + i; 279 // add all pages 280 versions.forEach((version) => { 281 if (!version.enabled) return; 282 var optgroup = document.createElement("optgroup"); 283 optgroup.label = version.name; 284 optgroup.version = version; 285 version.forEachPage((page) => { 286 var option = document.createElement("option"); 287 option.textContent = page.name; 288 option.page = page; 289 optgroup.appendChild(option); 290 }); 291 select.appendChild(optgroup); 292 }); 293 view.appendChild(column); 294 i++; 295 }); 296 var oldView = $('view'); 297 oldView.parentNode.replaceChild(view, oldView); 298 299 var select = $('baseline'); 300 removeAllChildren(select); 301 select.appendChild(document.createElement('option')); 302 versions.forEach((version) => { 303 var option = document.createElement("option"); 304 option.textContent = version.name; 305 option.version = version; 306 select.appendChild(option); 307 }); 308 initializeToggleList(versions.versions, $('versionSelector')); 309 initializeToggleList(pages.values(), $('pageSelector')); 310 initializeToggleList(Group.groups.values(), $('groupSelector')); 311 initializeToggleContentVisibility(); 312 } 313 314 function initializeToggleList(items, node) { 315 var list = node.querySelector('ul'); 316 removeAllChildren(list); 317 items = Array.from(items); 318 items.sort(NameComparator); 319 items.forEach((item) => { 320 var li = document.createElement('li'); 321 var checkbox = document.createElement('input'); 322 checkbox.type = 'checkbox'; 323 checkbox.checked = item.enabled; 324 checkbox.item = item; 325 checkbox.addEventListener('click', handleToggleVersionOrPageEnable); 326 li.appendChild(checkbox); 327 li.appendChild(document.createTextNode(item.name)); 328 list.appendChild(li); 329 }); 330 $('results').querySelectorAll('#results > .hidden').forEach((node) => { 331 toggleCssClass(node, 'hidden', false); 332 }) 333 } 334 335 function initializeToggleContentVisibility() { 336 var nodes = document.querySelectorAll('.toggleContentVisibility'); 337 nodes.forEach((node) => { 338 var content = node.querySelector('.content'); 339 var header = node.querySelector('h1,h2,h3'); 340 if (content === undefined || header === undefined) return; 341 if (header.querySelector('input') != undefined) return; 342 var checkbox = document.createElement('input'); 343 checkbox.type = 'checkbox'; 344 checkbox.checked = content.className.indexOf('hidden') == -1; 345 checkbox.contentNode = content; 346 checkbox.addEventListener('click', handleToggleContentVisibility); 347 header.insertBefore(checkbox, header.childNodes[0]); 348 }); 349 } 350 351 window.addEventListener('popstate', (event) => { 352 popHistoryState(event.state); 353 }); 354 355 function popHistoryState(state) { 356 if (!state.version) return false; 357 if (!versions) return false; 358 var version = versions.getByName(state.version); 359 if (!version) return false; 360 var page = version.get(state.page); 361 if (!page) return false; 362 if (!state.entry) { 363 showPage(page); 364 } else { 365 var entry = page.get(state.entry); 366 if (!entry) { 367 showPage(page); 368 } else { 369 showEntry(entry); 370 } 371 } 372 return true; 373 } 374 375 function pushHistoryState() { 376 var selection = selectedEntry ? selectedEntry : selectedPage; 377 if (!selection) return; 378 var state = selection.urlParams(); 379 // Don't push a history state if it didn't change. 380 if (JSON.stringify(window.history.state) === JSON.stringify(state)) return; 381 var params = "?"; 382 for (var pairs of Object.entries(state)) { 383 params += encodeURIComponent(pairs[0]) + "=" 384 + encodeURIComponent(pairs[1]) + "&"; 385 } 386 window.history.pushState(state, selection.toString(), params); 387 } 388 389 function showPage(firstPage) { 390 var changeSelectedEntry = selectedEntry !== undefined 391 && selectedEntry.page === selectedPage; 392 pushHistoryState(); 393 selectedPage = firstPage; 394 selectedPage.sort(); 395 showPageInColumn(firstPage, 0); 396 // Show the other versions of this page in the following columns. 397 var pageVersions = versions.getPageVersions(firstPage); 398 var index = 1; 399 pageVersions.forEach((page) => { 400 if (page !== firstPage) { 401 showPageInColumn(page, index); 402 index++; 403 } 404 }); 405 if (changeSelectedEntry) { 406 showEntryDetail(selectedPage.getEntry(selectedEntry)); 407 } 408 showImpactList(selectedPage); 409 pushHistoryState(); 410 } 411 412 function showPageInColumn(page, columnIndex) { 413 page.sort(); 414 var showDiff = (baselineVersion === undefined && columnIndex !== 0) || 415 (baselineVersion !== undefined && page.version !== baselineVersion); 416 var diffStatus = (td, a, b) => {}; 417 if (showDiff) { 418 if (baselineVersion !== undefined) { 419 diffStatus = (td, a, b) => { 420 if (a == 0) return; 421 td.style.color = a < 0 ? '#FF0000' : '#00BB00'; 422 }; 423 } else { 424 diffStatus = (td, a, b) => { 425 if (a == b) return; 426 var color; 427 var ratio = a / b; 428 if (ratio > 1) { 429 ratio = Math.min(Math.round((ratio - 1) * 255 * 10), 200); 430 color = '#' + ratio.toString(16) + "0000"; 431 } else { 432 ratio = Math.min(Math.round((1 - ratio) * 255 * 10), 200); 433 color = '#00' + ratio.toString(16) + "00"; 434 } 435 td.style.color = color; 436 } 437 } 438 } 439 440 var column = $('column_' + columnIndex); 441 var select = $('select_' + columnIndex); 442 // Find the matching option 443 selectOption(select, (i, option) => { 444 return option.page == page 445 }); 446 var table = column.querySelector("table"); 447 var oldTbody = table.querySelector('tbody'); 448 var tbody = document.createElement('tbody'); 449 var referencePage = selectedPage; 450 page.forEachSorted(selectedPage, (parentEntry, entry, referenceEntry) => { 451 var tr = document.createElement('tr'); 452 tbody.appendChild(tr); 453 tr.entry = entry; 454 tr.parentEntry = parentEntry; 455 tr.className = parentEntry === undefined ? 'parent' : 'child'; 456 // Don't show entries that do not exist on the current page or if we 457 // compare against the current page 458 if (entry !== undefined && page.version !== baselineVersion) { 459 // If we show a diff, use the baselineVersion as the referenceEntry 460 if (baselineVersion !== undefined) { 461 var baselineEntry = baselineVersion.getEntry(entry); 462 if (baselineEntry !== undefined) referenceEntry = baselineEntry 463 } 464 if (!parentEntry) { 465 var node = td(tr, '<div class="toggle">►</div>', 'position'); 466 node.firstChild.addEventListener('click', handleToggleGroup); 467 } else { 468 td(tr, entry.position == 0 ? '' : entry.position, 'position'); 469 } 470 addCodeSearchButton(entry, 471 td(tr, entry.name, 'name ' + entry.cssClass())); 472 473 diffStatus( 474 td(tr, ms(entry.time), 'value time'), 475 entry.time, referenceEntry.time); 476 diffStatus( 477 td(tr, percent(entry.timePercent), 'value time'), 478 entry.time, referenceEntry.time); 479 diffStatus( 480 td(tr, count(entry.count), 'value count'), 481 entry.count, referenceEntry.count); 482 } else if (baselineVersion !== undefined && referenceEntry 483 && page.version !== baselineVersion) { 484 // Show comparison of entry that does not exist on the current page. 485 tr.entry = new Entry(0, referenceEntry.name); 486 tr.entry.page = page; 487 td(tr, '-', 'position'); 488 td(tr, referenceEntry.name, 'name'); 489 diffStatus( 490 td(tr, ms(-referenceEntry.time), 'value time'), 491 -referenceEntry.time, 0); 492 diffStatus( 493 td(tr, percent(-referenceEntry.timePercent), 'value time'), 494 -referenceEntry.timePercent, 0); 495 diffStatus( 496 td(tr, count(-referenceEntry.count), 'value count'), 497 -referenceEntry.count, 0); 498 } else { 499 // Display empty entry / baseline entry 500 var showBaselineEntry = entry !== undefined; 501 if (showBaselineEntry) { 502 if (!parentEntry) { 503 var node = td(tr, '<div class="toggle">►</div>', 'position'); 504 node.firstChild.addEventListener('click', handleToggleGroup); 505 } else { 506 td(tr, entry.position == 0 ? '' : entry.position, 'position'); 507 } 508 td(tr, entry.name, 'name'); 509 td(tr, ms(entry.time, false), 'value time'); 510 td(tr, percent(entry.timePercent, false), 'value time'); 511 td(tr, count(entry.count, false), 'value count'); 512 } else { 513 td(tr, '-', 'position'); 514 td(tr, referenceEntry.name, 'name'); 515 td(tr, '-', 'value time'); 516 td(tr, '-', 'value time'); 517 td(tr, '-', 'value count'); 518 } 519 } 520 }); 521 table.replaceChild(tbody, oldTbody); 522 var versionSelect = column.querySelector('select.version'); 523 selectOption(versionSelect, (index, option) => { 524 return option.version == page.version 525 }); 526 } 527 528 function showEntry(entry) { 529 selectedEntry = entry; 530 selectEntry(entry, true); 531 } 532 533 function selectEntry(entry, updateSelectedPage) { 534 if (updateSelectedPage) { 535 entry = selectedPage.version.getEntry(entry); 536 } 537 var rowIndex = 0; 538 var needsPageSwitch = updateSelectedPage && entry.page != selectedPage; 539 // If clicked in the detail row change the first column to that page. 540 if (needsPageSwitch) showPage(entry.page); 541 var childNodes = $('column_0').querySelector('.list tbody').childNodes; 542 for (var i = 0; i < childNodes.length; i++) { 543 if (childNodes[i].entry !== undefined && 544 childNodes[i].entry.name == entry.name) { 545 rowIndex = i; 546 break; 547 } 548 } 549 var firstEntry = childNodes[rowIndex].entry; 550 if (rowIndex) { 551 if (firstEntry.parent) showGroup(firstEntry.parent); 552 } 553 // Deselect all 554 $('view').querySelectorAll('.list tbody tr').forEach((tr) => { 555 toggleCssClass(tr, 'selected', false); 556 }); 557 // Select the entry row 558 $('view').querySelectorAll("tbody").forEach((body) => { 559 var row = body.childNodes[rowIndex]; 560 if (!row) return; 561 toggleCssClass(row, 'selected', row.entry && row.entry.name == 562 firstEntry.name); 563 }); 564 if (updateSelectedPage) { 565 entry = selectedEntry.page.version.getEntry(entry); 566 } 567 selectedEntry = entry; 568 showEntryDetail(entry); 569 } 570 571 function showEntryDetail(entry) { 572 showVersionDetails(entry); 573 showPageDetails(entry); 574 showImpactList(entry.page); 575 showGraphs(entry.page); 576 pushHistoryState(); 577 } 578 579 function showVersionDetails(entry) { 580 var table, tbody, entries; 581 table = $('detailView').querySelector('.versionDetailTable'); 582 tbody = document.createElement('tbody'); 583 if (entry !== undefined) { 584 $('detailView').querySelector('.versionDetail h3 span').textContent = 585 entry.name + ' in ' + entry.page.name; 586 entries = versions.getPageVersions(entry.page).map( 587 (page) => { 588 return page.get(entry.name) 589 }); 590 entries.sort((a, b) => { 591 return a.time - b.time 592 }); 593 entries.forEach((pageEntry) => { 594 if (pageEntry === undefined) return; 595 var tr = document.createElement('tr'); 596 if (pageEntry == entry) tr.className += 'selected'; 597 tr.entry = pageEntry; 598 var isBaselineEntry = pageEntry.page.version == baselineVersion; 599 td(tr, pageEntry.page.version.name, 'version'); 600 td(tr, ms(pageEntry.time, !isBaselineEntry), 'value time'); 601 td(tr, percent(pageEntry.timePercent, !isBaselineEntry), 'value time'); 602 td(tr, count(pageEntry.count, !isBaselineEntry), 'value count'); 603 tbody.appendChild(tr); 604 }); 605 } 606 table.replaceChild(tbody, table.querySelector('tbody')); 607 } 608 609 function showPageDetails(entry) { 610 var table, tbody, entries; 611 table = $('detailView').querySelector('.pageDetailTable'); 612 tbody = document.createElement('tbody'); 613 if (entry === undefined) { 614 table.replaceChild(tbody, table.querySelector('tbody')); 615 return; 616 } 617 var version = entry.page.version; 618 var showDiff = version !== baselineVersion; 619 $('detailView').querySelector('.pageDetail h3 span').textContent = 620 version.name; 621 entries = version.pages.map((page) => { 622 if (!page.enabled) return; 623 return page.get(entry.name) 624 }); 625 entries.sort((a, b) => { 626 var cmp = b.timePercent - a.timePercent; 627 if (cmp.toFixed(1) == 0) return b.time - a.time; 628 return cmp 629 }); 630 entries.forEach((pageEntry) => { 631 if (pageEntry === undefined) return; 632 var tr = document.createElement('tr'); 633 if (pageEntry === entry) tr.className += 'selected'; 634 tr.entry = pageEntry; 635 td(tr, pageEntry.page.name, 'name'); 636 td(tr, ms(pageEntry.time, showDiff), 'value time'); 637 td(tr, percent(pageEntry.timePercent, showDiff), 'value time'); 638 td(tr, percent(pageEntry.timePercentPerEntry, showDiff), 639 'value time hideNoDiff'); 640 td(tr, count(pageEntry.count, showDiff), 'value count'); 641 tbody.appendChild(tr); 642 }); 643 // show the total for all pages 644 var tds = table.querySelectorAll('tfoot td'); 645 tds[1].textContent = ms(entry.getTimeImpact(), showDiff); 646 // Only show the percentage total if we are in diff mode: 647 tds[2].textContent = percent(entry.getTimePercentImpact(), showDiff); 648 tds[3].textContent = ''; 649 tds[4].textContent = count(entry.getCountImpact(), showDiff); 650 table.replaceChild(tbody, table.querySelector('tbody')); 651 } 652 653 function showImpactList(page) { 654 var impactView = $('detailView').querySelector('.impactView'); 655 impactView.querySelector('h3 span').textContent = page.version.name; 656 657 var table = impactView.querySelector('table'); 658 var tbody = document.createElement('tbody'); 659 var version = page.version; 660 var entries = version.allEntries(); 661 if (selectedEntry !== undefined && selectedEntry.isGroup) { 662 impactView.querySelector('h3 span').textContent += " " + selectedEntry.name; 663 entries = entries.filter((entry) => { 664 return entry.name == selectedEntry.name || 665 (entry.parent && entry.parent.name == selectedEntry.name) 666 }); 667 } 668 var isCompareView = baselineVersion !== undefined; 669 entries = entries.filter((entry) => { 670 if (isCompareView) { 671 var impact = entry.getTimeImpact(); 672 return impact < -1 || 1 < impact 673 } 674 return entry.getTimePercentImpact() > 0.1; 675 }); 676 entries.sort((a, b) => { 677 var cmp = b.getTimePercentImpact() - a.getTimePercentImpact(); 678 if (isCompareView || cmp.toFixed(1) == 0) { 679 return b.getTimeImpact() - a.getTimeImpact(); 680 } 681 return cmp 682 }); 683 entries.forEach((entry) => { 684 var tr = document.createElement('tr'); 685 tr.entry = entry; 686 td(tr, entry.name, 'name'); 687 td(tr, ms(entry.getTimeImpact()), 'value time'); 688 var percentImpact = entry.getTimePercentImpact(); 689 td(tr, percentImpact > 1000 ? '-' : percent(percentImpact), 'value time'); 690 var topPages = entry.getPagesByPercentImpact().slice(0, 3) 691 .map((each) => { 692 return each.name + ' (' + percent(each.getEntry(entry).timePercent) + 693 ')' 694 }); 695 td(tr, topPages.join(', '), 'name'); 696 tbody.appendChild(tr); 697 }); 698 table.replaceChild(tbody, table.querySelector('tbody')); 699 } 700 701 function showGraphs(page) { 702 var groups = page.groups.slice(); 703 // Sort groups by the biggest impact 704 groups.sort((a, b) => { 705 return b.getTimeImpact() - a.getTimeImpact(); 706 }); 707 if (selectedGroup == undefined) { 708 selectedGroup = groups[0]; 709 } else { 710 groups = groups.filter(each => each.enabled && each.name != selectedGroup.name); 711 groups.unshift(selectedGroup); 712 } 713 showPageGraph(groups, page); 714 showVersionGraph(groups, page); 715 showPageVersionGraph(groups, page); 716 } 717 718 function getGraphDataTable(groups) { 719 var dataTable = new google.visualization.DataTable(); 720 dataTable.addColumn('string', 'Name'); 721 groups.forEach(group => { 722 var column = dataTable.addColumn('number', group.name.substring(6)); 723 dataTable.setColumnProperty(column, 'group', group); 724 }); 725 return dataTable; 726 } 727 728 var selectedGroup; 729 function showPageGraph(groups, page) { 730 var isDiffView = baselineVersion !== undefined; 731 var dataTable = getGraphDataTable(groups); 732 // Calculate the average row 733 var row = ['Average']; 734 groups.forEach((group) => { 735 if (isDiffView) { 736 row.push(group.isTotal ? 0 : group.getAverageTimeImpact()); 737 } else { 738 row.push(group.isTotal ? 0 : group.getTimeImpact()); 739 } 740 }); 741 dataTable.addRow(row); 742 // Sort the pages by the selected group. 743 var pages = page.version.pages.filter(page => page.enabled); 744 function sumDiff(page) { 745 var sum = 0; 746 groups.forEach(group => { 747 var value = group.getTimePercentImpact() - 748 page.getEntry(group).timePercent; 749 sum += value * value; 750 }); 751 return sum; 752 } 753 if (isDiffView) { 754 pages.sort((a, b) => { 755 return b.getEntry(selectedGroup).time- 756 a.getEntry(selectedGroup).time; 757 }); 758 } else { 759 pages.sort((a, b) => { 760 return b.getEntry(selectedGroup).timePercent - 761 a.getEntry(selectedGroup).timePercent; 762 }); 763 } 764 // Sort by sum of squared distance to the average. 765 // pages.sort((a, b) => { 766 // return a.distanceFromTotalPercent() - b.distanceFromTotalPercent(); 767 // }); 768 // Calculate the entries for the pages 769 pages.forEach((page) => { 770 row = [page.name]; 771 groups.forEach((group) => { 772 row.push(group.isTotal ? 0 : page.getEntry(group).time); 773 }); 774 var rowIndex = dataTable.addRow(row); 775 dataTable.setRowProperty(rowIndex, 'page', page); 776 }); 777 renderGraph('Pages for ' + page.version.name, groups, dataTable, 778 'pageGraph', isDiffView ? true : 'percent'); 779 } 780 781 function showVersionGraph(groups, page) { 782 var dataTable = getGraphDataTable(groups); 783 var row; 784 var vs = versions.versions.filter(version => version.enabled); 785 vs.sort((a, b) => { 786 return b.getEntry(selectedGroup).getTimeImpact() - 787 a.getEntry(selectedGroup).getTimeImpact(); 788 }); 789 // Calculate the entries for the versions 790 vs.forEach((version) => { 791 row = [version.name]; 792 groups.forEach((group) => { 793 row.push(group.isTotal ? 0 : version.getEntry(group).getTimeImpact()); 794 }); 795 var rowIndex = dataTable.addRow(row); 796 dataTable.setRowProperty(rowIndex, 'page', page); 797 }); 798 renderGraph('Versions Total Time over all Pages', groups, dataTable, 799 'versionGraph', true); 800 } 801 802 function showPageVersionGraph(groups, page) { 803 var dataTable = getGraphDataTable(groups); 804 var row; 805 var vs = versions.getPageVersions(page); 806 vs.sort((a, b) => { 807 return b.getEntry(selectedGroup).time - a.getEntry(selectedGroup).time; 808 }); 809 // Calculate the entries for the versions 810 vs.forEach((page) => { 811 row = [page.version.name]; 812 groups.forEach((group) => { 813 row.push(group.isTotal ? 0 : page.getEntry(group).time); 814 }); 815 var rowIndex = dataTable.addRow(row); 816 dataTable.setRowProperty(rowIndex, 'page', page); 817 }); 818 renderGraph('Versions for ' + page.name, groups, dataTable, 819 'pageVersionGraph', true); 820 } 821 822 function renderGraph(title, groups, dataTable, id, isStacked) { 823 var isDiffView = baselineVersion !== undefined; 824 var formatter = new google.visualization.NumberFormat({ 825 suffix: (isDiffView ? 'msΔ' : 'ms'), 826 negativeColor: 'red', 827 groupingSymbol: "'" 828 }); 829 for (var i = 1; i < dataTable.getNumberOfColumns(); i++) { 830 formatter.format(dataTable, i); 831 } 832 var height = 85 + 28 * dataTable.getNumberOfRows(); 833 var options = { 834 isStacked: isStacked, 835 height: height, 836 hAxis: { 837 minValue: 0, 838 }, 839 animation:{ 840 duration: 500, 841 easing: 'out', 842 }, 843 vAxis: { 844 }, 845 explorer: { 846 actions: ['dragToZoom', 'rightClickToReset'], 847 maxZoomIn: 0.01 848 }, 849 legend: {position:'top', textStyle:{fontSize: '16px'}}, 850 chartArea: {left:200, top:50, width:'98%', height:'80%'}, 851 colors: groups.map(each => each.color) 852 }; 853 var parentNode = $(id); 854 parentNode.querySelector('h2>span, h3>span').textContent = title; 855 var graphNode = parentNode.querySelector('.content'); 856 857 var chart = graphNode.chart; 858 if (chart === undefined) { 859 chart = graphNode.chart = new google.visualization.BarChart(graphNode); 860 } else { 861 google.visualization.events.removeAllListeners(chart); 862 } 863 google.visualization.events.addListener(chart, 'select', selectHandler); 864 function getChartEntry(selection) { 865 if (!selection) return undefined; 866 var column = selection.column; 867 if (column == undefined) return undefined; 868 var selectedGroup = dataTable.getColumnProperty(column, 'group'); 869 var row = selection.row; 870 if (row == null) return selectedGroup; 871 var page = dataTable.getRowProperty(row, 'page'); 872 if (!page) return selectedGroup; 873 return page.getEntry(selectedGroup); 874 } 875 function selectHandler() { 876 selectedGroup = getChartEntry(chart.getSelection()[0]) 877 if (!selectedGroup) return; 878 selectEntry(selectedGroup, true); 879 } 880 881 // Make our global tooltips work 882 google.visualization.events.addListener(chart, 'onmouseover', mouseOverHandler); 883 function mouseOverHandler(selection) { 884 graphNode.entry = getChartEntry(selection); 885 } 886 chart.draw(dataTable, options); 887 } 888 889 function showGroup(entry) { 890 toggleGroup(entry, true); 891 } 892 893 function toggleGroup(group, show) { 894 $('view').querySelectorAll(".child").forEach((tr) => { 895 var entry = tr.parentEntry; 896 if (!entry) return; 897 if (entry.name !== group.name) return; 898 toggleCssClass(tr, 'visible', show); 899 }); 900 } 901 902 function showPopover(entry) { 903 var popover = $('popover'); 904 popover.querySelector('td.name').textContent = entry.name; 905 popover.querySelector('td.page').textContent = entry.page.name; 906 setPopoverDetail(popover, entry, ''); 907 popover.querySelector('table').className = ""; 908 if (baselineVersion !== undefined) { 909 entry = baselineVersion.getEntry(entry); 910 setPopoverDetail(popover, entry, '.compare'); 911 popover.querySelector('table').className = "compare"; 912 } 913 } 914 915 function setPopoverDetail(popover, entry, prefix) { 916 var node = (name) => popover.querySelector(prefix + name); 917 if (entry == undefined) { 918 node('.version').textContent = baselineVersion.name; 919 node('.time').textContent = '-'; 920 node('.timeVariance').textContent = '-'; 921 node('.percent').textContent = '-'; 922 node('.percentPerEntry').textContent = '-'; 923 node('.percentVariance').textContent = '-'; 924 node('.count').textContent = '-'; 925 node('.countVariance').textContent = '-'; 926 node('.timeImpact').textContent = '-'; 927 node('.timePercentImpact').textContent = '-'; 928 } else { 929 node('.version').textContent = entry.page.version.name; 930 node('.time').textContent = ms(entry._time, false); 931 node('.timeVariance').textContent 932 = percent(entry.timeVariancePercent, false); 933 node('.percent').textContent = percent(entry.timePercent, false); 934 node('.percentPerEntry').textContent 935 = percent(entry.timePercentPerEntry, false); 936 node('.percentVariance').textContent 937 = percent(entry.timePercentVariancePercent, false); 938 node('.count').textContent = count(entry._count, false); 939 node('.countVariance').textContent 940 = percent(entry.timeVariancePercent, false); 941 node('.timeImpact').textContent 942 = ms(entry.getTimeImpact(false), false); 943 node('.timePercentImpact').textContent 944 = percent(entry.getTimeImpactVariancePercent(false), false); 945 } 946 } 947 </script> 948 <script type="text/javascript"> 949 "use strict" 950 // ========================================================================= 951 // Helpers 952 function $(id) { 953 return document.getElementById(id) 954 } 955 956 function removeAllChildren(node) { 957 while (node.firstChild) { 958 node.removeChild(node.firstChild); 959 } 960 } 961 962 function selectOption(select, match) { 963 var options = select.options; 964 for (var i = 0; i < options.length; i++) { 965 if (match(i, options[i])) { 966 select.selectedIndex = i; 967 return; 968 } 969 } 970 } 971 972 function addCodeSearchButton(entry, node) { 973 if (entry.isGroup) return; 974 var button = document.createElement("div"); 975 button.textContent = '?' 976 button.className = "codeSearch" 977 button.addEventListener('click', handleCodeSearch); 978 node.appendChild(button); 979 return node; 980 } 981 982 function td(tr, content, className) { 983 var td = document.createElement("td"); 984 if (content[0] == '<') { 985 td.innerHTML = content; 986 } else { 987 td.textContent = content; 988 } 989 td.className = className 990 tr.appendChild(td); 991 return td 992 } 993 994 function nodeIndex(node) { 995 var 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 1005 function toggleCssClass(node, cssClass, toggleState) { 1006 var index = -1; 1007 var classes; 1008 if (node.className != undefined) { 1009 classes = node.className.split(' '); 1010 index = classes.indexOf(cssClass); 1011 } 1012 if (index == -1) { 1013 if (toggleState === false) return; 1014 node.className += ' ' + cssClass; 1015 return; 1016 } 1017 if (toggleState === true) return; 1018 classes.splice(index, 1); 1019 node.className = classes.join(' '); 1020 } 1021 1022 function NameComparator(a, b) { 1023 if (a.name > b.name) return 1; 1024 if (a.name < b.name) return -1; 1025 return 0 1026 } 1027 1028 function diffSign(value, digits, unit, showDiff) { 1029 if (showDiff === false || baselineVersion == undefined) { 1030 if (value === undefined) return ''; 1031 return value.toFixed(digits) + unit; 1032 } 1033 return (value >= 0 ? '+' : '') + value.toFixed(digits) + unit + 'Δ'; 1034 } 1035 1036 function ms(value, showDiff) { 1037 return diffSign(value, 1, 'ms', showDiff); 1038 } 1039 1040 function count(value, showDiff) { 1041 return diffSign(value, 0, '#', showDiff); 1042 } 1043 1044 function percent(value, showDiff) { 1045 return diffSign(value, 1, '%', showDiff); 1046 } 1047 1048 </script> 1049 <script type="text/javascript"> 1050 "use strict" 1051 // ========================================================================= 1052 // EventHandlers 1053 function handleBodyLoad() { 1054 $('uploadInput').focus(); 1055 if (defaultData) { 1056 handleLoadJSON(defaultData); 1057 } else if (window.location.protocol !== 'file:') { 1058 tryLoadDefaultResults(); 1059 } 1060 } 1061 1062 function tryLoadDefaultResults() { 1063 // Try to load a results.json file adjacent to this day. 1064 var xhr = new XMLHttpRequest(); 1065 // The markers on the following line can be used to replace the url easily 1066 // with scripts. 1067 xhr.open('GET', /*results-url-start*/'results.json'/*results-url-end*/, true); 1068 xhr.onreadystatechange = function(e) { 1069 if(this.readyState !== XMLHttpRequest.DONE || this.status !== 200) return; 1070 handleLoadText(this.responseText); 1071 }; 1072 xhr.send(); 1073 } 1074 1075 function handleLoadFile() { 1076 var files = document.getElementById("uploadInput").files; 1077 var file = files[0]; 1078 var reader = new FileReader(); 1079 1080 reader.onload = function(evt) { 1081 handleLoadText(this.result); 1082 } 1083 reader.readAsText(file); 1084 } 1085 1086 function handleLoadText(text) { 1087 handleLoadJSON(JSON.parse(text)); 1088 } 1089 1090 function getStateFromParams() { 1091 var query = window.location.search.substr(1); 1092 var result = {}; 1093 query.split("&").forEach((part) => { 1094 var item = part.split("="); 1095 var key = decodeURIComponent(item[0]) 1096 result[key] = decodeURIComponent(item[1]); 1097 }); 1098 return result; 1099 } 1100 1101 function fixSinglePageJSON(json) { 1102 // Try to detect the single-version case, where we're missing the toplevel 1103 // version object. The incoming JSON is of the form: 1104 // {"Page 1": [... data points ... ], "Page 2": [...], ...} 1105 // Instead of the default multi-page JSON: 1106 // {"Version 1": { "Page 1": ..., ...}, "Version 2": {...}, ...} 1107 // In this case insert a single "Default" version as top-level entry. 1108 var firstProperty = (object) => { 1109 for (var key in object) return key; 1110 }; 1111 var maybePage = json[firstProperty(json)]; 1112 if (!Array.isArray(maybePage)) return json; 1113 return {"Default": json} 1114 } 1115 1116 function handleLoadJSON(json) { 1117 json = fixSinglePageJSON(json); 1118 var state = getStateFromParams(); 1119 pages = new Pages(); 1120 versions = Versions.fromJSON(json); 1121 initialize() 1122 showPage(versions.versions[0].pages[0]); 1123 if (!popHistoryState(state)) { 1124 selectEntry(selectedPage.total); 1125 } 1126 } 1127 1128 function handleToggleGroup(event) { 1129 var group = event.target.parentNode.parentNode.entry; 1130 toggleGroup(selectedPage.get(group.name)); 1131 } 1132 1133 function handleSelectPage(select, event) { 1134 var option = select.options[select.selectedIndex]; 1135 if (select.id == "select_0") { 1136 showPage(option.page); 1137 } else { 1138 var columnIndex = select.id.split('_')[1]; 1139 showPageInColumn(option.page, columnIndex); 1140 } 1141 } 1142 1143 function handleSelectVersion(select, event) { 1144 var option = select.options[select.selectedIndex]; 1145 var version = option.version; 1146 if (select.id == "selectVersion_0") { 1147 var page = version.get(selectedPage.name); 1148 showPage(page); 1149 } else { 1150 var columnIndex = select.id.split('_')[1]; 1151 var pageSelect = $('select_' + columnIndex); 1152 var page = pageSelect.options[pageSelect.selectedIndex].page; 1153 page = version.get(page.name); 1154 showPageInColumn(page, columnIndex); 1155 } 1156 } 1157 1158 function handleSelectDetailRow(table, event) { 1159 if (event.target.tagName != 'TD') return; 1160 var tr = event.target.parentNode; 1161 if (tr.tagName != 'TR') return; 1162 if (tr.entry === undefined) return; 1163 selectEntry(tr.entry, true); 1164 } 1165 1166 function handleSelectRow(table, event, fromDetail) { 1167 if (event.target.tagName != 'TD') return; 1168 var tr = event.target.parentNode; 1169 if (tr.tagName != 'TR') return; 1170 if (tr.entry === undefined) return; 1171 selectEntry(tr.entry, false); 1172 } 1173 1174 function handleSelectBaseline(select, event) { 1175 var option = select.options[select.selectedIndex]; 1176 baselineVersion = option.version; 1177 var showingDiff = baselineVersion !== undefined; 1178 var body = $('body'); 1179 toggleCssClass(body, 'diff', showingDiff); 1180 toggleCssClass(body, 'noDiff', !showingDiff); 1181 showPage(selectedPage); 1182 if (selectedEntry === undefined) return; 1183 selectEntry(selectedEntry, true); 1184 } 1185 1186 function findEntry(event) { 1187 var target = event.target; 1188 while (target.entry === undefined) { 1189 target = target.parentNode; 1190 if (!target) return undefined; 1191 } 1192 return target.entry; 1193 } 1194 1195 function handleUpdatePopover(event) { 1196 var popover = $('popover'); 1197 popover.style.left = event.pageX + 'px'; 1198 popover.style.top = event.pageY + 'px'; 1199 popover.style.display = 'none'; 1200 popover.style.display = event.shiftKey ? 'block' : 'none'; 1201 var entry = findEntry(event); 1202 if (entry === undefined) return; 1203 showPopover(entry); 1204 } 1205 1206 function handleToggleVersionOrPageEnable(event) { 1207 var item = this.item ; 1208 if (item === undefined) return; 1209 item .enabled = this.checked; 1210 initialize(); 1211 var page = selectedPage; 1212 if (page === undefined || !page.version.enabled) { 1213 page = versions.getEnabledPage(page.name); 1214 } 1215 if (!page.enabled) { 1216 page = page.getNextPage(); 1217 } 1218 showPage(page); 1219 } 1220 1221 function handleToggleContentVisibility(event) { 1222 var content = event.target.contentNode; 1223 toggleCssClass(content, 'hidden'); 1224 } 1225 1226 function handleCodeSearch(event) { 1227 var entry = findEntry(event); 1228 if (entry === undefined) return; 1229 var url = "https://cs.chromium.org/search/?sq=package:chromium&type=cs&q="; 1230 name = entry.name; 1231 if (name.startsWith("API_")) { 1232 name = name.substring(4); 1233 } 1234 url += encodeURIComponent(name) + "+file:src/v8/src"; 1235 window.open(url,'_blank'); 1236 } 1237 </script> 1238 <script type="text/javascript"> 1239 "use strict" 1240 // ========================================================================= 1241 class Versions { 1242 constructor() { 1243 this.versions = []; 1244 } 1245 add(version) { 1246 this.versions.push(version) 1247 } 1248 getPageVersions(page) { 1249 var result = []; 1250 this.versions.forEach((version) => { 1251 if (!version.enabled) return; 1252 var versionPage = version.get(page.name); 1253 if (versionPage !== undefined) result.push(versionPage); 1254 }); 1255 return result; 1256 } 1257 get length() { 1258 return this.versions.length 1259 } 1260 get(index) { 1261 return this.versions[index] 1262 } 1263 getByName(name) { 1264 return this.versions.find((each) => each.name == name); 1265 } 1266 forEach(f) { 1267 this.versions.forEach(f); 1268 } 1269 sort() { 1270 this.versions.sort(NameComparator); 1271 } 1272 getEnabledPage(name) { 1273 for (var i = 0; i < this.versions.length; i++) { 1274 var version = this.versions[i]; 1275 if (!version.enabled) continue; 1276 var page = version.get(name); 1277 if (page !== undefined) return page; 1278 } 1279 } 1280 } 1281 Versions.fromJSON = function(json) { 1282 var versions = new Versions(); 1283 for (var version in json) { 1284 versions.add(Version.fromJSON(version, json[version])); 1285 } 1286 versions.sort(); 1287 return versions; 1288 } 1289 1290 class Version { 1291 constructor(name) { 1292 this.name = name; 1293 this.enabled = true; 1294 this.pages = []; 1295 } 1296 add(page) { 1297 this.pages.push(page); 1298 } 1299 indexOf(name) { 1300 for (var i = 0; i < this.pages.length; i++) { 1301 if (this.pages[i].name == name) return i; 1302 } 1303 return -1; 1304 } 1305 getNextPage(page) { 1306 if (this.length == 0) return undefined; 1307 return this.pages[(this.indexOf(page.name) + 1) % this.length]; 1308 } 1309 get(name) { 1310 var index = this.indexOf(name); 1311 if (0 <= index) return this.pages[index]; 1312 return undefined 1313 } 1314 get length() { 1315 return this.pages.length 1316 } 1317 getEntry(entry) { 1318 if (entry === undefined) return undefined; 1319 var page = this.get(entry.page.name); 1320 if (page === undefined) return undefined; 1321 return page.get(entry.name); 1322 } 1323 forEachEntry(fun) { 1324 this.forEachPage((page) => { 1325 page.forEach(fun); 1326 }); 1327 } 1328 forEachPage(fun) { 1329 this.pages.forEach((page) => { 1330 if (!page.enabled) return; 1331 fun(page); 1332 }) 1333 } 1334 allEntries() { 1335 var map = new Map(); 1336 this.forEachEntry((group, entry) => { 1337 if (!map.has(entry.name)) map.set(entry.name, entry); 1338 }); 1339 return Array.from(map.values()); 1340 } 1341 getTotalValue(name, property) { 1342 if (name === undefined) name = this.pages[0].total.name; 1343 var sum = 0; 1344 this.forEachPage((page) => { 1345 var entry = page.get(name); 1346 if (entry !== undefined) sum += entry[property]; 1347 }); 1348 return sum; 1349 } 1350 getTotalTime(name, showDiff) { 1351 return this.getTotalValue(name, showDiff === false ? '_time' : 'time'); 1352 } 1353 getTotalTimePercent(name, showDiff) { 1354 if (baselineVersion === undefined || showDiff === false) { 1355 // Return the overall average percent of the given entry name. 1356 return this.getTotalValue(name, 'time') / 1357 this.getTotalTime('Group-Total') * 100; 1358 } 1359 // Otherwise return the difference to the sum of the baseline version. 1360 var baselineValue = baselineVersion.getTotalTime(name, false); 1361 var total = this.getTotalValue(name, '_time'); 1362 return (total / baselineValue - 1) * 100; 1363 } 1364 getTotalTimeVariance(name, showDiff) { 1365 // Calculate the overall error for a given entry name 1366 var sum = 0; 1367 this.forEachPage((page) => { 1368 var entry = page.get(name); 1369 if (entry === undefined) return; 1370 sum += entry.timeVariance * entry.timeVariance; 1371 }); 1372 return Math.sqrt(sum); 1373 } 1374 getTotalTimeVariancePercent(name, showDiff) { 1375 return this.getTotalTimeVariance(name, showDiff) / 1376 this.getTotalTime(name, showDiff) * 100; 1377 } 1378 getTotalCount(name, showDiff) { 1379 return this.getTotalValue(name, showDiff === false ? '_count' : 'count'); 1380 } 1381 getAverageTimeImpact(name, showDiff) { 1382 return this.getTotalTime(name, showDiff) / this.pages.length; 1383 } 1384 getPagesByPercentImpact(name) { 1385 var sortedPages = 1386 this.pages.filter((each) => { 1387 return each.get(name) !== undefined 1388 }); 1389 sortedPages.sort((a, b) => { 1390 return b.get(name).timePercent - a.get(name).timePercent; 1391 }); 1392 return sortedPages; 1393 } 1394 sort() { 1395 this.pages.sort(NameComparator) 1396 } 1397 } 1398 Version.fromJSON = function(name, data) { 1399 var version = new Version(name); 1400 for (var pageName in data) { 1401 version.add(PageVersion.fromJSON(version, pageName, data[pageName])); 1402 } 1403 version.sort(); 1404 return version; 1405 } 1406 1407 class Pages extends Map { 1408 get(name) { 1409 if (name.indexOf('www.') == 0) { 1410 name = name.substring(4); 1411 } 1412 if (!this.has(name)) { 1413 this.set(name, new Page(name)); 1414 } 1415 return super.get(name); 1416 } 1417 } 1418 1419 class Page { 1420 constructor(name) { 1421 this.name = name; 1422 this.enabled = true; 1423 this.versions = []; 1424 } 1425 add(page) { 1426 this.versions.push(page); 1427 } 1428 } 1429 1430 class PageVersion { 1431 constructor(version, page) { 1432 this.page = page; 1433 this.page.add(this); 1434 this.total = Group.groups.get('total').entry(); 1435 this.total.isTotal = true; 1436 this.unclassified = new UnclassifiedEntry(this) 1437 this.groups = [ 1438 this.total, 1439 Group.groups.get('ic').entry(), 1440 Group.groups.get('optimize').entry(), 1441 Group.groups.get('compile-background').entry(), 1442 Group.groups.get('compile').entry(), 1443 Group.groups.get('parse-background').entry(), 1444 Group.groups.get('parse').entry(), 1445 Group.groups.get('callback').entry(), 1446 Group.groups.get('api').entry(), 1447 Group.groups.get('gc').entry(), 1448 Group.groups.get('javascript').entry(), 1449 Group.groups.get('runtime').entry(), 1450 this.unclassified 1451 ]; 1452 this.entryDict = new Map(); 1453 this.groups.forEach((entry) => { 1454 entry.page = this; 1455 this.entryDict.set(entry.name, entry); 1456 }); 1457 this.version = version; 1458 } 1459 toString() { 1460 return this.version.name + ": " + this.name; 1461 } 1462 urlParams() { 1463 return { version: this.version.name, page: this.name}; 1464 } 1465 add(entry) { 1466 // Ignore accidentally added Group entries. 1467 if (entry.name.startsWith(GroupedEntry.prefix)) return; 1468 entry.page = this; 1469 this.entryDict.set(entry.name, entry); 1470 var added = false; 1471 this.groups.forEach((group) => { 1472 if (!added) added = group.add(entry); 1473 }); 1474 if (added) return; 1475 this.unclassified.push(entry); 1476 } 1477 get(name) { 1478 return this.entryDict.get(name) 1479 } 1480 getEntry(entry) { 1481 if (entry === undefined) return undefined; 1482 return this.get(entry.name); 1483 } 1484 get length() { 1485 return this.versions.length 1486 } 1487 get name() { return this.page.name } 1488 get enabled() { return this.page.enabled } 1489 forEachSorted(referencePage, func) { 1490 // Iterate over all the entries in the order they appear on the 1491 // reference page. 1492 referencePage.forEach((parent, referenceEntry) => { 1493 var entry; 1494 if (parent) parent = this.entryDict.get(parent.name); 1495 if (referenceEntry) entry = this.entryDict.get(referenceEntry.name); 1496 func(parent, entry, referenceEntry); 1497 }); 1498 } 1499 forEach(fun) { 1500 this.forEachGroup((group) => { 1501 fun(undefined, group); 1502 group.forEach((entry) => { 1503 fun(group, entry) 1504 }); 1505 }); 1506 } 1507 forEachGroup(fun) { 1508 this.groups.forEach(fun) 1509 } 1510 sort() { 1511 this.groups.sort((a, b) => { 1512 return b.time - a.time; 1513 }); 1514 this.groups.forEach((group) => { 1515 group.sort() 1516 }); 1517 } 1518 distanceFromTotalPercent() { 1519 var sum = 0; 1520 this.groups.forEach(group => { 1521 if (group == this.total) return; 1522 var value = group.getTimePercentImpact() - 1523 this.getEntry(group).timePercent; 1524 sum += value * value; 1525 }); 1526 return sum; 1527 } 1528 getNextPage() { 1529 return this.version.getNextPage(this); 1530 } 1531 } 1532 PageVersion.fromJSON = function(version, name, data) { 1533 var page = new PageVersion(version, pages.get(name)); 1534 for (var i = 0; i < data.length; i++) { 1535 page.add(Entry.fromJSON(i, data[data.length - i - 1])); 1536 } 1537 page.sort(); 1538 return page 1539 } 1540 1541 1542 class Entry { 1543 constructor(position, name, time, timeVariance, timeVariancePercent, 1544 count, 1545 countVariance, countVariancePercent) { 1546 this.position = position; 1547 this.name = name; 1548 this._time = time; 1549 this._timeVariance = timeVariance; 1550 this._timeVariancePercent = timeVariancePercent; 1551 this._count = count; 1552 this.countVariance = countVariance; 1553 this.countVariancePercent = countVariancePercent; 1554 this.page = undefined; 1555 this.parent = undefined; 1556 this.isTotal = false; 1557 } 1558 urlParams() { 1559 var params = this.page.urlParams(); 1560 params.entry = this.name; 1561 return params; 1562 } 1563 getCompareWithBaseline(value, property) { 1564 if (baselineVersion == undefined) return value; 1565 var baselineEntry = baselineVersion.getEntry(this); 1566 if (!baselineEntry) return value; 1567 if (baselineVersion === this.page.version) return value; 1568 return value - baselineEntry[property]; 1569 } 1570 cssClass() { 1571 return '' 1572 } 1573 get time() { 1574 return this.getCompareWithBaseline(this._time, '_time'); 1575 } 1576 get count() { 1577 return this.getCompareWithBaseline(this._count, '_count'); 1578 } 1579 get timePercent() { 1580 var value = this._time / this.page.total._time * 100; 1581 if (baselineVersion == undefined) return value; 1582 var baselineEntry = baselineVersion.getEntry(this); 1583 if (!baselineEntry) return value; 1584 if (baselineVersion === this.page.version) return value; 1585 return (this._time - baselineEntry._time) / this.page.total._time * 1586 100; 1587 } 1588 get timePercentPerEntry() { 1589 var value = this._time / this.page.total._time * 100; 1590 if (baselineVersion == undefined) return value; 1591 var baselineEntry = baselineVersion.getEntry(this); 1592 if (!baselineEntry) return value; 1593 if (baselineVersion === this.page.version) return value; 1594 return (this._time / baselineEntry._time - 1) * 100; 1595 } 1596 get timePercentVariancePercent() { 1597 // Get the absolute values for the percentages 1598 return this.timeVariance / this.page.total._time * 100; 1599 } 1600 getTimeImpact(showDiff) { 1601 return this.page.version.getTotalTime(this.name, showDiff); 1602 } 1603 getTimeImpactVariancePercent(showDiff) { 1604 return this.page.version.getTotalTimeVariancePercent(this.name, showDiff); 1605 } 1606 getTimePercentImpact(showDiff) { 1607 return this.page.version.getTotalTimePercent(this.name, showDiff); 1608 } 1609 getCountImpact(showDiff) { 1610 return this.page.version.getTotalCount(this.name, showDiff); 1611 } 1612 getAverageTimeImpact(showDiff) { 1613 return this.page.version.getAverageTimeImpact(this.name, showDiff); 1614 } 1615 getPagesByPercentImpact() { 1616 return this.page.version.getPagesByPercentImpact(this.name); 1617 } 1618 get isGroup() { 1619 return false 1620 } 1621 get timeVariance() { 1622 return this._timeVariance 1623 } 1624 get timeVariancePercent() { 1625 return this._timeVariancePercent 1626 } 1627 } 1628 Entry.fromJSON = function(position, data) { 1629 return new Entry(position, ...data); 1630 } 1631 1632 class Group { 1633 constructor(name, regexp, color) { 1634 this.name = name; 1635 this.regexp = regexp; 1636 this.color = color; 1637 this.enabled = true; 1638 } 1639 entry() { return new GroupedEntry(this) }; 1640 } 1641 Group.groups = new Map(); 1642 Group.add = function(name, group) { 1643 this.groups.set(name, group); 1644 return group; 1645 } 1646 Group.add('total', new Group('Total', /.*Total.*/, '#BBB')); 1647 Group.add('ic', new Group('IC', /.*IC_.*/, "#3366CC")); 1648 Group.add('optimize', new Group('Optimize', 1649 /StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/, "#DC3912")); 1650 Group.add('compile-background', new Group('Compile-Background', 1651 /(.*CompileBackground.*)/, "#b9a720")); 1652 Group.add('compile', new Group('Compile', 1653 /(^Compile.*)|(.*_Compile.*)/, "#FFAA00")); 1654 Group.add('parse-background', 1655 new Group('Parse-Background', /.*ParseBackground.*/, "#af744d")); 1656 Group.add('parse', new Group('Parse', /.*Parse.*/, "#FF6600")); 1657 Group.add('callback', new Group('Blink C++', /.*Callback.*/, "#109618")); 1658 Group.add('api', new Group('API', /.*API.*/, "#990099")); 1659 Group.add('gc', new Group('GC', /GC|AllocateInTargetSpace/, "#0099C6")); 1660 Group.add('javascript', new Group('JavaScript', /JS_Execution/, "#DD4477")); 1661 Group.add('runtime', new Group('V8 C++', /.*/, "#88BB00")); 1662 var group = 1663 Group.add('unclassified', new Group('Unclassified', /.*/, "#000")); 1664 group.enabled = false; 1665 1666 class GroupedEntry extends Entry { 1667 constructor(group) { 1668 super(0, GroupedEntry.prefix + group.name, 0, 0, 0, 0, 0, 0); 1669 this.group = group; 1670 this.entries = []; 1671 } 1672 get regexp() { return this.group.regexp } 1673 get color() { return this.group.color } 1674 get enabled() { return this.group.enabled } 1675 add(entry) { 1676 if (!this.regexp.test(entry.name)) return false; 1677 this._time += entry.time; 1678 this._count += entry.count; 1679 // TODO: sum up variance 1680 this.entries.push(entry); 1681 entry.parent = this; 1682 return true; 1683 } 1684 forEach(fun) { 1685 // Show also all entries which are in at least one version. 1686 var dummyEntryNames = new Set(); 1687 versions.forEach((version) => { 1688 var groupEntry = version.getEntry(this); 1689 if (groupEntry != this) { 1690 for (var entry of groupEntry.entries) { 1691 if (this.page.get(entry.name) == undefined) { 1692 dummyEntryNames.add(entry.name); 1693 } 1694 } 1695 } 1696 }); 1697 var tmpEntries = []; 1698 for (var name of dummyEntryNames) { 1699 var tmpEntry = new Entry(0, name, 0, 0, 0, 0, 0, 0); 1700 tmpEntry.page = this.page; 1701 tmpEntries.push(tmpEntry); 1702 }; 1703 1704 // Concatenate our real entries. 1705 tmpEntries = tmpEntries.concat(this.entries); 1706 1707 // The compared entries are sorted by absolute impact. 1708 tmpEntries.sort((a, b) => { 1709 return a.time - b.time 1710 }); 1711 tmpEntries.forEach(fun); 1712 } 1713 sort() { 1714 this.entries.sort((a, b) => { 1715 return b.time - a.time; 1716 }); 1717 } 1718 cssClass() { 1719 if (this.page.total == this) return 'total'; 1720 return ''; 1721 } 1722 get isGroup() { 1723 return true 1724 } 1725 getVarianceForProperty(property) { 1726 var sum = 0; 1727 this.entries.forEach((entry) => { 1728 sum += entry[property + 'Variance'] * entry[property + 1729 'Variance']; 1730 }); 1731 return Math.sqrt(sum); 1732 } 1733 get timeVariancePercent() { 1734 if (this._time == 0) return 0; 1735 return this.getVarianceForProperty('time') / this._time * 100 1736 } 1737 get timeVariance() { 1738 return this.getVarianceForProperty('time') 1739 } 1740 } 1741 GroupedEntry.prefix = 'Group-'; 1742 1743 class UnclassifiedEntry extends GroupedEntry { 1744 constructor(page) { 1745 super(Group.groups.get('unclassified')); 1746 this.page = page; 1747 this._time = undefined; 1748 this._count = undefined; 1749 } 1750 add(entry) { 1751 this.entries.push(entry); 1752 entry.parent = this; 1753 return true; 1754 } 1755 forEachPageGroup(fun) { 1756 this.page.forEachGroup((group) => { 1757 if (group == this) return; 1758 if (group == this.page.total) return; 1759 fun(group); 1760 }); 1761 } 1762 get time() { 1763 if (this._time === undefined) { 1764 this._time = this.page.total._time; 1765 this.forEachPageGroup((group) => { 1766 this._time -= group._time; 1767 }); 1768 } 1769 return this.getCompareWithBaseline(this._time, '_time'); 1770 } 1771 get count() { 1772 if (this._count === undefined) { 1773 this._count = this.page.total._count; 1774 this.forEachPageGroup((group) => { 1775 this._count -= group._count; 1776 }); 1777 } 1778 return this.getCompareWithBaseline(this._count, '_count'); 1779 } 1780 } 1781 </script> 1782</head> 1783 1784<body id="body" onmousemove="handleUpdatePopover(event)" onload="handleBodyLoad()" class="noDiff"> 1785 <h1>Runtime Stats Komparator</h1> 1786 1787 <div id="results"> 1788 <div class="inline"> 1789 <h2>Data</h2> 1790 <form name="fileForm"> 1791 <p> 1792 <input id="uploadInput" type="file" name="files" onchange="handleLoadFile();" accept=".json"> 1793 </p> 1794 </form> 1795 </div> 1796 1797 <div class="inline hidden"> 1798 <h2>Result</h2> 1799 <div class="compareSelector inline"> 1800 Compare against: <select id="baseline" onchange="handleSelectBaseline(this, event)"></select><br/> 1801 <span style="color: #060">Green</span> the selected version above performs 1802 better on this measurement. 1803 </div> 1804 </div> 1805 1806 <div id="versionSelector" class="inline toggleContentVisibility"> 1807 <h2>Versions</h2> 1808 <div class="content hidden"> 1809 <ul></ul> 1810 </div> 1811 </div> 1812 1813 <div id="pageSelector" class="inline toggleContentVisibility"> 1814 <h2>Pages</h2> 1815 <div class="content hidden"> 1816 <ul></ul> 1817 </div> 1818 </div> 1819 1820 <div id="groupSelector" class="inline toggleContentVisibility"> 1821 <h2>Groups</h2> 1822 <div class="content hidden"> 1823 <ul></ul> 1824 </div> 1825 </div> 1826 1827 <div id="view"> 1828 </div> 1829 1830 <div id="detailView" class="hidden"> 1831 <div class="versionDetail inline toggleContentVisibility"> 1832 <h3><span></span></h3> 1833 <div class="content"> 1834 <table class="versionDetailTable" onclick="handleSelectDetailRow(this, event);"> 1835 <thead> 1836 <tr> 1837 <th class="version">Version </th> 1838 <th class="position">Pos. </th> 1839 <th class="value time">Time▴ </th> 1840 <th class="value time">Percent </th> 1841 <th class="value count">Count </th> 1842 </tr> 1843 </thead> 1844 <tbody></tbody> 1845 </table> 1846 </div> 1847 </div> 1848 <div class="pageDetail inline toggleContentVisibility"> 1849 <h3>Page Comparison for <span></span></h3> 1850 <div class="content"> 1851 <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);"> 1852 <thead> 1853 <tr> 1854 <th class="page">Page </th> 1855 <th class="value time">Time </th> 1856 <th class="value time">Percent▾ </th> 1857 <th class="value time hideNoDiff">%/Entry </th> 1858 <th class="value count">Count </th> 1859 </tr> 1860 </thead> 1861 <tfoot> 1862 <tr> 1863 <td class="page">Total:</td> 1864 <td class="value time"></td> 1865 <td class="value time"></td> 1866 <td class="value time hideNoDiff"></td> 1867 <td class="value count"></td> 1868 </tr> 1869 </tfoot> 1870 <tbody></tbody> 1871 </table> 1872 </div> 1873 </div> 1874 <div class="impactView inline toggleContentVisibility"> 1875 <h3>Impact list for <span></span></h3> 1876 <div class="content"> 1877 <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);"> 1878 <thead> 1879 <tr> 1880 <th class="page">Name </th> 1881 <th class="value time">Time </th> 1882 <th class="value time">Percent▾ </th> 1883 <th class="">Top Pages</th> 1884 </tr> 1885 </thead> 1886 <tbody></tbody> 1887 </table> 1888 </div> 1889 </div> 1890 </div> 1891 <div id="pageVersionGraph" class="graph hidden toggleContentVisibility"> 1892 <h3><span></span></h3> 1893 <div class="content"></div> 1894 </div> 1895 <div id="pageGraph" class="graph hidden toggleContentVisibility"> 1896 <h3><span></span></h3> 1897 <div class="content"></div> 1898 </div> 1899 <div id="versionGraph" class="graph hidden toggleContentVisibility"> 1900 <h3><span></span></h3> 1901 <div class="content"></div> 1902 </div> 1903 1904 <div id="column" class="column"> 1905 <div class="header"> 1906 <select class="version" onchange="handleSelectVersion(this, event);"></select> 1907 <select class="pageVersion" onchange="handleSelectPage(this, event);"></select> 1908 </div> 1909 <table class="list" onclick="handleSelectRow(this, event);"> 1910 <thead> 1911 <tr> 1912 <th class="position">Pos. </th> 1913 <th class="name">Name </th> 1914 <th class="value time">Time </th> 1915 <th class="value time">Percent </th> 1916 <th class="value count">Count </th> 1917 </tr> 1918 </thead> 1919 <tbody></tbody> 1920 </table> 1921 </div> 1922 </div> 1923 1924 <div class="inline"> 1925 <h2>Usage</h2> 1926 <ol> 1927 <li>Install scipy, e.g. <code>sudo aptitude install python-scipy</code> 1928 <li>Build chrome.</li> 1929 <li>Check out a known working version of webpagereply: 1930 <pre>git -C $CHROME_DIR/third_party/webpagereplay checkout 7dbd94752d1cde5536ffc623a9e10a51721eff1d</pre> 1931 </li> 1932 <li>Run <code>callstats.py</code> with a web-page-replay archive: 1933 <pre>$V8_DIR/tools/callstats.py run \ 1934 --replay-bin=$CHROME_SRC/third_party/webpagereplay/replay.py \ 1935 --replay-wpr=$INPUT_DIR/top25.wpr \ 1936 --js-flags="" \ 1937 --with-chrome=$CHROME_SRC/out/Release/chrome \ 1938 --sites-file=$INPUT_DIR/top25.json</pre> 1939 </li> 1940 <li>Move results file to a subdirectory: <code>mkdir $VERSION_DIR; mv *.txt $VERSION_DIR</code></li> 1941 <li>Repeat from step 1 with a different configuration (e.g. <code>--js-flags="--nolazy"</code>).</li> 1942 <li>Create the final results file: <code>./callstats.py json $VERSION_DIR1 $VERSION_DIR2 > result.json</code></li> 1943 <li>Use <code>results.json</code> on this site.</code> 1944 </ol> 1945 </div> 1946 1947 <div id="popover"> 1948 <div class="popoverArrow"></div> 1949 <table> 1950 <tr> 1951 <td class="name" colspan="6"></td> 1952 </tr> 1953 <tr> 1954 <td>Page:</td> 1955 <td class="page name" colspan="6"></td> 1956 </tr> 1957 <tr> 1958 <td>Version:</td> 1959 <td class="version name" colspan="3"></td> 1960 <td class="compare version name" colspan="3"></td> 1961 </tr> 1962 <tr> 1963 <td>Time:</td> 1964 <td class="time"></td><td>±</td><td class="timeVariance"></td> 1965 <td class="compare time"></td><td class="compare"> ± </td><td class="compare timeVariance"></td> 1966 </tr> 1967 <tr> 1968 <td>Percent:</td> 1969 <td class="percent"></td><td>±</td><td class="percentVariance"></td> 1970 <td class="compare percent"></td><td class="compare"> ± </td><td class="compare percentVariance"></td> 1971 </tr> 1972 <tr> 1973 <td>Percent per Entry:</td> 1974 <td class="percentPerEntry"></td><td colspan=2></td> 1975 <td class="compare percentPerEntry"></td><td colspan=2></td> 1976 </tr> 1977 <tr> 1978 <td>Count:</td> 1979 <td class="count"></td><td>±</td><td class="countVariance"></td> 1980 <td class="compare count"></td><td class="compare"> ± </td><td class="compare countVariance"></td> 1981 </tr> 1982 <tr> 1983 <td>Overall Impact:</td> 1984 <td class="timeImpact"></td><td>±</td><td class="timePercentImpact"></td> 1985 <td class="compare timeImpact"></td><td class="compare"> ± </td><td class="compare timePercentImpact"></td> 1986 </tr> 1987 </table> 1988 </div> 1989</body> 1990</html> 1991