1 <html> 2 <!-- 3 Copyright 2016 the V8 project authors. All rights reserved. Use of this source 4 code 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 function showPage(firstPage) { 352 var changeSelectedEntry = selectedEntry !== undefined 353 && selectedEntry.page === selectedPage; 354 selectedPage = firstPage; 355 selectedPage.sort(); 356 showPageInColumn(firstPage, 0); 357 // Show the other versions of this page in the following columns. 358 var pageVersions = versions.getPageVersions(firstPage); 359 var index = 1; 360 pageVersions.forEach((page) => { 361 if (page !== firstPage) { 362 showPageInColumn(page, index); 363 index++; 364 } 365 }); 366 if (changeSelectedEntry) { 367 showEntryDetail(selectedPage.getEntry(selectedEntry)); 368 } 369 showImpactList(selectedPage); 370 } 371 372 function showPageInColumn(page, columnIndex) { 373 page.sort(); 374 var showDiff = (baselineVersion === undefined && columnIndex !== 0) || 375 (baselineVersion !== undefined && page.version !== baselineVersion); 376 var diffStatus = (td, a, b) => {}; 377 if (showDiff) { 378 if (baselineVersion !== undefined) { 379 diffStatus = (td, a, b) => { 380 if (a == 0) return; 381 td.style.color = a < 0 ? '#FF0000' : '#00BB00'; 382 }; 383 } else { 384 diffStatus = (td, a, b) => { 385 if (a == b) return; 386 var color; 387 var ratio = a / b; 388 if (ratio > 1) { 389 ratio = Math.min(Math.round((ratio - 1) * 255 * 10), 200); 390 color = '#' + ratio.toString(16) + "0000"; 391 } else { 392 ratio = Math.min(Math.round((1 - ratio) * 255 * 10), 200); 393 color = '#00' + ratio.toString(16) + "00"; 394 } 395 td.style.color = color; 396 } 397 } 398 } 399 400 var column = $('column_' + columnIndex); 401 var select = $('select_' + columnIndex); 402 // Find the matching option 403 selectOption(select, (i, option) => { 404 return option.page == page 405 }); 406 var table = column.querySelector("table"); 407 var oldTbody = table.querySelector('tbody'); 408 var tbody = document.createElement('tbody'); 409 var referencePage = selectedPage; 410 page.forEachSorted(selectedPage, (parentEntry, entry, referenceEntry) => { 411 // Filter out entries that do not exist in the first column for the default 412 // view. 413 if (baselineVersion === undefined && referenceEntry && 414 referenceEntry.time == 0) { 415 return; 416 } 417 var tr = document.createElement('tr'); 418 tbody.appendChild(tr); 419 tr.entry = entry; 420 tr.parentEntry = parentEntry; 421 tr.className = parentEntry === undefined ? 'parent' : 'child'; 422 // Don't show entries that do not exist on the current page or if we 423 // compare against the current page 424 if (entry !== undefined && page.version !== baselineVersion) { 425 // If we show a diff, use the baselineVersion as the referenceEntry 426 if (baselineVersion !== undefined) { 427 var baselineEntry = baselineVersion.getEntry(entry); 428 if (baselineEntry !== undefined) referenceEntry = baselineEntry 429 } 430 if (!parentEntry) { 431 var node = td(tr, '<div class="toggle"></div>', 'position'); 432 node.firstChild.addEventListener('click', handleToggleGroup); 433 } else { 434 td(tr, entry.position == 0 ? '' : entry.position, 'position'); 435 } 436 addCodeSearchButton(entry, 437 td(tr, entry.name, 'name ' + entry.cssClass())); 438 439 diffStatus( 440 td(tr, ms(entry.time), 'value time'), 441 entry.time, referenceEntry.time); 442 diffStatus( 443 td(tr, percent(entry.timePercent), 'value time'), 444 entry.time, referenceEntry.time); 445 diffStatus( 446 td(tr, count(entry.count), 'value count'), 447 entry.count, referenceEntry.count); 448 } else if (baselineVersion !== undefined && referenceEntry 449 && page.version !== baselineVersion) { 450 // Show comparison of entry that does not exist on the current page. 451 tr.entry = new Entry(0, referenceEntry.name); 452 tr.entry.page = page; 453 td(tr, '-', 'position'); 454 td(tr, referenceEntry.name, 'name'); 455 diffStatus( 456 td(tr, ms(-referenceEntry.time), 'value time'), 457 -referenceEntry.time, 0); 458 diffStatus( 459 td(tr, percent(-referenceEntry.timePercent), 'value time'), 460 -referenceEntry.timePercent, 0); 461 diffStatus( 462 td(tr, count(-referenceEntry.count), 'value count'), 463 -referenceEntry.count, 0); 464 } else { 465 // Display empty entry / baseline entry 466 var showBaselineEntry = entry !== undefined; 467 if (showBaselineEntry) { 468 if (!parentEntry) { 469 var node = td(tr, '<div class="toggle"></div>', 'position'); 470 node.firstChild.addEventListener('click', handleToggleGroup); 471 } else { 472 td(tr, entry.position == 0 ? '' : entry.position, 'position'); 473 } 474 td(tr, entry.name, 'name'); 475 td(tr, ms(entry.time, false), 'value time'); 476 td(tr, percent(entry.timePercent, false), 'value time'); 477 td(tr, count(entry.count, false), 'value count'); 478 } else { 479 td(tr, '-', 'position'); 480 td(tr, '-', 'name'); 481 td(tr, '-', 'value time'); 482 td(tr, '-', 'value time'); 483 td(tr, '-', 'value count'); 484 } 485 } 486 }); 487 table.replaceChild(tbody, oldTbody); 488 var versionSelect = column.querySelector('select.version'); 489 selectOption(versionSelect, (index, option) => { 490 return option.version == page.version 491 }); 492 } 493 494 function selectEntry(entry, updateSelectedPage) { 495 if (updateSelectedPage) { 496 entry = selectedPage.version.getEntry(entry); 497 } 498 var rowIndex = 0; 499 var needsPageSwitch = updateSelectedPage && entry.page != selectedPage; 500 // If clicked in the detail row change the first column to that page. 501 if (needsPageSwitch) showPage(entry.page); 502 var childNodes = $('column_0').querySelector('.list tbody').childNodes; 503 for (var i = 0; i < childNodes.length; i++) { 504 if (childNodes[i].entry.name == entry.name) { 505 rowIndex = i; 506 break; 507 } 508 } 509 var firstEntry = childNodes[rowIndex].entry; 510 if (rowIndex) { 511 if (firstEntry.parent) showGroup(firstEntry.parent); 512 } 513 // Deselect all 514 $('view').querySelectorAll('.list tbody tr').forEach((tr) => { 515 toggleCssClass(tr, 'selected', false); 516 }); 517 // Select the entry row 518 $('view').querySelectorAll("tbody").forEach((body) => { 519 var row = body.childNodes[rowIndex]; 520 if (!row) return; 521 toggleCssClass(row, 'selected', row.entry && row.entry.name == 522 firstEntry.name); 523 }); 524 if (updateSelectedPage) { 525 entry = selectedEntry.page.version.getEntry(entry); 526 } 527 selectedEntry = entry; 528 showEntryDetail(entry); 529 } 530 531 function showEntryDetail(entry) { 532 showVersionDetails(entry); 533 showPageDetails(entry); 534 showImpactList(entry.page); 535 showGraphs(entry.page); 536 } 537 538 function showVersionDetails(entry) { 539 var table, tbody, entries; 540 table = $('detailView').querySelector('.versionDetailTable'); 541 tbody = document.createElement('tbody'); 542 if (entry !== undefined) { 543 $('detailView').querySelector('.versionDetail h3 span').textContent = 544 entry.name + ' in ' + entry.page.name; 545 entries = versions.getPageVersions(entry.page).map( 546 (page) => { 547 return page.get(entry.name) 548 }); 549 entries.sort((a, b) => { 550 return a.time - b.time 551 }); 552 entries.forEach((pageEntry) => { 553 if (pageEntry === undefined) return; 554 var tr = document.createElement('tr'); 555 if (pageEntry == entry) tr.className += 'selected'; 556 tr.entry = pageEntry; 557 var isBaselineEntry = pageEntry.page.version == baselineVersion; 558 td(tr, pageEntry.page.version.name, 'version'); 559 td(tr, ms(pageEntry.time, !isBaselineEntry), 'value time'); 560 td(tr, percent(pageEntry.timePercent, !isBaselineEntry), 'value time'); 561 td(tr, count(pageEntry.count, !isBaselineEntry), 'value count'); 562 tbody.appendChild(tr); 563 }); 564 } 565 table.replaceChild(tbody, table.querySelector('tbody')); 566 } 567 568 function showPageDetails(entry) { 569 var table, tbody, entries; 570 table = $('detailView').querySelector('.pageDetailTable'); 571 tbody = document.createElement('tbody'); 572 if (entry === undefined) { 573 table.replaceChild(tbody, table.querySelector('tbody')); 574 return; 575 } 576 var version = entry.page.version; 577 var showDiff = version !== baselineVersion; 578 $('detailView').querySelector('.pageDetail h3 span').textContent = 579 version.name; 580 entries = version.pages.map((page) => { 581 if (!page.enabled) return; 582 return page.get(entry.name) 583 }); 584 entries.sort((a, b) => { 585 var cmp = b.timePercent - a.timePercent; 586 if (cmp.toFixed(1) == 0) return b.time - a.time; 587 return cmp 588 }); 589 entries.forEach((pageEntry) => { 590 if (pageEntry === undefined) return; 591 var tr = document.createElement('tr'); 592 if (pageEntry === entry) tr.className += 'selected'; 593 tr.entry = pageEntry; 594 td(tr, pageEntry.page.name, 'name'); 595 td(tr, ms(pageEntry.time, showDiff), 'value time'); 596 td(tr, percent(pageEntry.timePercent, showDiff), 'value time'); 597 td(tr, percent(pageEntry.timePercentPerEntry, showDiff), 598 'value time hideNoDiff'); 599 td(tr, count(pageEntry.count, showDiff), 'value count'); 600 tbody.appendChild(tr); 601 }); 602 // show the total for all pages 603 var tds = table.querySelectorAll('tfoot td'); 604 tds[1].textContent = ms(entry.getTimeImpact(), showDiff); 605 // Only show the percentage total if we are in diff mode: 606 tds[2].textContent = percent(entry.getTimePercentImpact(), showDiff); 607 tds[3].textContent = ''; 608 tds[4].textContent = count(entry.getCountImpact(), showDiff); 609 table.replaceChild(tbody, table.querySelector('tbody')); 610 } 611 612 function showImpactList(page) { 613 var impactView = $('detailView').querySelector('.impactView'); 614 impactView.querySelector('h3 span').textContent = page.version.name; 615 616 var table = impactView.querySelector('table'); 617 var tbody = document.createElement('tbody'); 618 var version = page.version; 619 var entries = version.allEntries(); 620 if (selectedEntry !== undefined && selectedEntry.isGroup) { 621 impactView.querySelector('h3 span').textContent += " " + selectedEntry.name; 622 entries = entries.filter((entry) => { 623 return entry.name == selectedEntry.name || 624 (entry.parent && entry.parent.name == selectedEntry.name) 625 }); 626 } 627 var isCompareView = baselineVersion !== undefined; 628 entries = entries.filter((entry) => { 629 if (isCompareView) { 630 var impact = entry.getTimeImpact(); 631 return impact < -1 || 1 < impact 632 } 633 return entry.getTimePercentImpact() > 0.1; 634 }); 635 entries.sort((a, b) => { 636 var cmp = b.getTimePercentImpact() - a.getTimePercentImpact(); 637 if (isCompareView || cmp.toFixed(1) == 0) { 638 return b.getTimeImpact() - a.getTimeImpact(); 639 } 640 return cmp 641 }); 642 entries.forEach((entry) => { 643 var tr = document.createElement('tr'); 644 tr.entry = entry; 645 td(tr, entry.name, 'name'); 646 td(tr, ms(entry.getTimeImpact()), 'value time'); 647 var percentImpact = entry.getTimePercentImpact(); 648 td(tr, percentImpact > 1000 ? '-' : percent(percentImpact), 'value time'); 649 var topPages = entry.getPagesByPercentImpact().slice(0, 3) 650 .map((each) => { 651 return each.name + ' (' + percent(each.getEntry(entry).timePercent) + 652 ')' 653 }); 654 td(tr, topPages.join(', '), 'name'); 655 tbody.appendChild(tr); 656 }); 657 table.replaceChild(tbody, table.querySelector('tbody')); 658 } 659 660 function showGraphs(page) { 661 var groups = page.groups.slice(); 662 // Sort groups by the biggest impact 663 groups.sort((a, b) => { 664 return b.getTimeImpact() - a.getTimeImpact(); 665 }); 666 if (selectedGroup == undefined) { 667 selectedGroup = groups[0]; 668 } else { 669 groups = groups.filter(each => each.enabled && each.name != selectedGroup.name); 670 groups.unshift(selectedGroup); 671 } 672 showPageGraph(groups, page); 673 showVersionGraph(groups, page); 674 showPageVersionGraph(groups, page); 675 } 676 677 function getGraphDataTable(groups) { 678 var dataTable = new google.visualization.DataTable(); 679 dataTable.addColumn('string', 'Name'); 680 groups.forEach(group => { 681 var column = dataTable.addColumn('number', group.name.substring(6)); 682 dataTable.setColumnProperty(column, 'group', group); 683 }); 684 return dataTable; 685 } 686 687 var selectedGroup; 688 function showPageGraph(groups, page) { 689 var isDiffView = baselineVersion !== undefined; 690 var dataTable = getGraphDataTable(groups); 691 // Calculate the average row 692 var row = ['Average']; 693 groups.forEach((group) => { 694 if (isDiffView) { 695 row.push(group.isTotal ? 0 : group.getAverageTimeImpact()); 696 } else { 697 row.push(group.isTotal ? 0 : group.getTimeImpact()); 698 } 699 }); 700 dataTable.addRow(row); 701 // Sort the pages by the selected group. 702 var pages = page.version.pages.filter(page => page.enabled); 703 function sumDiff(page) { 704 var sum = 0; 705 groups.forEach(group => { 706 var value = group.getTimePercentImpact() - 707 page.getEntry(group).timePercent; 708 sum += value * value; 709 }); 710 return sum; 711 } 712 if (isDiffView) { 713 pages.sort((a, b) => { 714 return b.getEntry(selectedGroup).time- 715 a.getEntry(selectedGroup).time; 716 }); 717 } else { 718 pages.sort((a, b) => { 719 return b.getEntry(selectedGroup).timePercent - 720 a.getEntry(selectedGroup).timePercent; 721 }); 722 } 723 // Sort by sum of squared distance to the average. 724 // pages.sort((a, b) => { 725 // return a.distanceFromTotalPercent() - b.distanceFromTotalPercent(); 726 // }); 727 // Calculate the entries for the pages 728 pages.forEach((page) => { 729 row = [page.name]; 730 groups.forEach((group) => { 731 row.push(group.isTotal ? 0 : page.getEntry(group).time); 732 }); 733 var rowIndex = dataTable.addRow(row); 734 dataTable.setRowProperty(rowIndex, 'page', page); 735 }); 736 renderGraph('Pages for ' + page.version.name, groups, dataTable, 737 'pageGraph', isDiffView ? true : 'percent'); 738 } 739 740 function showVersionGraph(groups, page) { 741 var dataTable = getGraphDataTable(groups); 742 var row; 743 var vs = versions.versions.filter(version => version.enabled); 744 vs.sort((a, b) => { 745 return b.getEntry(selectedGroup).getTimeImpact() - 746 a.getEntry(selectedGroup).getTimeImpact(); 747 }); 748 // Calculate the entries for the versions 749 vs.forEach((version) => { 750 row = [version.name]; 751 groups.forEach((group) => { 752 row.push(group.isTotal ? 0 : version.getEntry(group).getTimeImpact()); 753 }); 754 var rowIndex = dataTable.addRow(row); 755 dataTable.setRowProperty(rowIndex, 'page', page); 756 }); 757 renderGraph('Versions Total Time over all Pages', groups, dataTable, 758 'versionGraph', true); 759 } 760 761 function showPageVersionGraph(groups, page) { 762 var dataTable = getGraphDataTable(groups); 763 var row; 764 var vs = versions.getPageVersions(page); 765 vs.sort((a, b) => { 766 return b.getEntry(selectedGroup).time - a.getEntry(selectedGroup).time; 767 }); 768 // Calculate the entries for the versions 769 vs.forEach((page) => { 770 row = [page.version.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('Versions for ' + page.name, groups, dataTable, 778 'pageVersionGraph', true); 779 } 780 781 function renderGraph(title, groups, dataTable, id, isStacked) { 782 var isDiffView = baselineVersion !== undefined; 783 var formatter = new google.visualization.NumberFormat({ 784 suffix: (isDiffView ? 'ms' : 'ms'), 785 negativeColor: 'red', 786 groupingSymbol: "'" 787 }); 788 for (var i = 1; i < dataTable.getNumberOfColumns(); i++) { 789 formatter.format(dataTable, i); 790 } 791 var height = 85 + 28 * dataTable.getNumberOfRows(); 792 var options = { 793 isStacked: isStacked, 794 height: height, 795 hAxis: { 796 minValue: 0, 797 }, 798 animation:{ 799 duration: 500, 800 easing: 'out', 801 }, 802 vAxis: { 803 }, 804 explorer: { 805 actions: ['dragToZoom', 'rightClickToReset'], 806 maxZoomIn: 0.01 807 }, 808 legend: {position:'top', textStyle:{fontSize: '16px'}}, 809 chartArea: {left:200, top:50, width:'98%', height:'80%'}, 810 colors: groups.map(each => each.color) 811 }; 812 var parentNode = $(id); 813 parentNode.querySelector('h2>span, h3>span').textContent = title; 814 var graphNode = parentNode.querySelector('.content'); 815 816 var chart = graphNode.chart; 817 if (chart === undefined) { 818 chart = graphNode.chart = new google.visualization.BarChart(graphNode); 819 } else { 820 google.visualization.events.removeAllListeners(chart); 821 } 822 google.visualization.events.addListener(chart, 'select', selectHandler); 823 function getChartEntry(selection) { 824 if (!selection) return undefined; 825 var column = selection.column; 826 if (column == undefined) return undefined; 827 var selectedGroup = dataTable.getColumnProperty(column, 'group'); 828 var row = selection.row; 829 if (row == null) return selectedGroup; 830 var page = dataTable.getRowProperty(row, 'page'); 831 if (!page) return selectedGroup; 832 return page.getEntry(selectedGroup); 833 } 834 function selectHandler() { 835 selectedGroup = getChartEntry(chart.getSelection()[0]) 836 if (!selectedGroup) return; 837 selectEntry(selectedGroup, true); 838 } 839 840 // Make our global tooltips work 841 google.visualization.events.addListener(chart, 'onmouseover', mouseOverHandler); 842 function mouseOverHandler(selection) { 843 graphNode.entry = getChartEntry(selection); 844 } 845 chart.draw(dataTable, options); 846 } 847 848 function showGroup(entry) { 849 toggleGroup(entry, true); 850 } 851 852 function toggleGroup(group, show) { 853 $('view').querySelectorAll(".child").forEach((tr) => { 854 var entry = tr.parentEntry; 855 if (!entry) return; 856 if (entry.name !== group.name) return; 857 toggleCssClass(tr, 'visible', show); 858 }); 859 } 860 861 function showPopover(entry) { 862 var popover = $('popover'); 863 popover.querySelector('td.name').textContent = entry.name; 864 popover.querySelector('td.page').textContent = entry.page.name; 865 setPopoverDetail(popover, entry, ''); 866 popover.querySelector('table').className = ""; 867 if (baselineVersion !== undefined) { 868 entry = baselineVersion.getEntry(entry); 869 setPopoverDetail(popover, entry, '.compare'); 870 popover.querySelector('table').className = "compare"; 871 } 872 } 873 874 function setPopoverDetail(popover, entry, prefix) { 875 var node = (name) => popover.querySelector(prefix + name); 876 if (entry == undefined) { 877 node('.version').textContent = baselineVersion.name; 878 node('.time').textContent = '-'; 879 node('.timeVariance').textContent = '-'; 880 node('.percent').textContent = '-'; 881 node('.percentPerEntry').textContent = '-'; 882 node('.percentVariance').textContent = '-'; 883 node('.count').textContent = '-'; 884 node('.countVariance').textContent = '-'; 885 node('.timeImpact').textContent = '-'; 886 node('.timePercentImpact').textContent = '-'; 887 } else { 888 node('.version').textContent = entry.page.version.name; 889 node('.time').textContent = ms(entry._time, false); 890 node('.timeVariance').textContent 891 = percent(entry.timeVariancePercent, false); 892 node('.percent').textContent = percent(entry.timePercent, false); 893 node('.percentPerEntry').textContent 894 = percent(entry.timePercentPerEntry, false); 895 node('.percentVariance').textContent 896 = percent(entry.timePercentVariancePercent, false); 897 node('.count').textContent = count(entry._count, false); 898 node('.countVariance').textContent 899 = percent(entry.timeVariancePercent, false); 900 node('.timeImpact').textContent 901 = ms(entry.getTimeImpact(false), false); 902 node('.timePercentImpact').textContent 903 = percent(entry.getTimeImpactVariancePercent(false), false); 904 } 905 } 906 </script> 907 <script type="text/javascript"> 908 "use strict" 909 // ========================================================================= 910 // Helpers 911 function $(id) { 912 return document.getElementById(id) 913 } 914 915 function removeAllChildren(node) { 916 while (node.firstChild) { 917 node.removeChild(node.firstChild); 918 } 919 } 920 921 function selectOption(select, match) { 922 var options = select.options; 923 for (var i = 0; i < options.length; i++) { 924 if (match(i, options[i])) { 925 select.selectedIndex = i; 926 return; 927 } 928 } 929 } 930 931 function addCodeSearchButton(entry, node) { 932 if (entry.isGroup) return; 933 var button = document.createElement("div"); 934 button.textContent = '?' 935 button.className = "codeSearch" 936 button.addEventListener('click', handleCodeSearch); 937 node.appendChild(button); 938 return node; 939 } 940 941 function td(tr, content, className) { 942 var td = document.createElement("td"); 943 if (content[0] == '<') { 944 td.innerHTML = content; 945 } else { 946 td.textContent = content; 947 } 948 td.className = className 949 tr.appendChild(td); 950 return td 951 } 952 953 function nodeIndex(node) { 954 var children = node.parentNode.childNodes, 955 i = 0; 956 for (; i < children.length; i++) { 957 if (children[i] == node) { 958 return i; 959 } 960 } 961 return -1; 962 } 963 964 function toggleCssClass(node, cssClass, toggleState) { 965 var index = -1; 966 var classes; 967 if (node.className != undefined) { 968 classes = node.className.split(' '); 969 index = classes.indexOf(cssClass); 970 } 971 if (index == -1) { 972 if (toggleState === false) return; 973 node.className += ' ' + cssClass; 974 return; 975 } 976 if (toggleState === true) return; 977 classes.splice(index, 1); 978 node.className = classes.join(' '); 979 } 980 981 function NameComparator(a, b) { 982 if (a.name > b.name) return 1; 983 if (a.name < b.name) return -1; 984 return 0 985 } 986 987 function diffSign(value, digits, unit, showDiff) { 988 if (showDiff === false || baselineVersion == undefined) { 989 if (value === undefined) return ''; 990 return value.toFixed(digits) + unit; 991 } 992 return (value >= 0 ? '+' : '') + value.toFixed(digits) + unit + ''; 993 } 994 995 function ms(value, showDiff) { 996 return diffSign(value, 1, 'ms', showDiff); 997 } 998 999 function count(value, showDiff) { 1000 return diffSign(value, 0, '#', showDiff); 1001 } 1002 1003 function percent(value, showDiff) { 1004 return diffSign(value, 1, '%', showDiff); 1005 } 1006 1007 </script> 1008 <script type="text/javascript"> 1009 "use strict" 1010 // ========================================================================= 1011 // EventHandlers 1012 function handleBodyLoad() { 1013 $('uploadInput').focus(); 1014 if (defaultData) { 1015 handleLoadJSON(defaultData); 1016 } else if (window.location.protocol !== 'file:') { 1017 tryLoadDefaultResults(); 1018 } 1019 } 1020 1021 function tryLoadDefaultResults() { 1022 // Try to load a results.json file adjacent to this day. 1023 var xhr = new XMLHttpRequest(); 1024 // The markers on the following line can be used to replace the url easily 1025 // with scripts. 1026 xhr.open('GET', /*results-url-start*/'results.json'/*results-url-end*/, true); 1027 xhr.onreadystatechange = function(e) { 1028 if(this.readyState !== XMLHttpRequest.DONE || this.status !== 200) return; 1029 handleLoadText(this.responseText); 1030 }; 1031 xhr.send(); 1032 } 1033 1034 function handleLoadFile() { 1035 var files = document.getElementById("uploadInput").files; 1036 var file = files[0]; 1037 var reader = new FileReader(); 1038 1039 reader.onload = function(evt) { 1040 handleLoadText(this.result); 1041 } 1042 reader.readAsText(file); 1043 } 1044 1045 function handleLoadText(text) { 1046 handleLoadJSON(JSON.parse(text)); 1047 } 1048 1049 function handleLoadJSON(json) { 1050 pages = new Pages(); 1051 versions = Versions.fromJSON(json); 1052 initialize() 1053 showPage(versions.versions[0].pages[0]); 1054 selectEntry(selectedPage.total); 1055 } 1056 1057 function handleToggleGroup(event) { 1058 var group = event.target.parentNode.parentNode.entry; 1059 toggleGroup(selectedPage.get(group.name)); 1060 } 1061 1062 function handleSelectPage(select, event) { 1063 var option = select.options[select.selectedIndex]; 1064 if (select.id == "select_0") { 1065 showPage(option.page); 1066 } else { 1067 var columnIndex = select.id.split('_')[1]; 1068 showPageInColumn(option.page, columnIndex); 1069 } 1070 } 1071 1072 function handleSelectVersion(select, event) { 1073 var option = select.options[select.selectedIndex]; 1074 var version = option.version; 1075 if (select.id == "selectVersion_0") { 1076 var page = version.get(selectedPage.name); 1077 showPage(page); 1078 } else { 1079 var columnIndex = select.id.split('_')[1]; 1080 var pageSelect = $('select_' + columnIndex); 1081 var page = pageSelect.options[pageSelect.selectedIndex].page; 1082 page = version.get(page.name); 1083 showPageInColumn(page, columnIndex); 1084 } 1085 } 1086 1087 function handleSelectDetailRow(table, event) { 1088 if (event.target.tagName != 'TD') return; 1089 var tr = event.target.parentNode; 1090 if (tr.tagName != 'TR') return; 1091 if (tr.entry === undefined) return; 1092 selectEntry(tr.entry, true); 1093 } 1094 1095 function handleSelectRow(table, event, fromDetail) { 1096 if (event.target.tagName != 'TD') return; 1097 var tr = event.target.parentNode; 1098 if (tr.tagName != 'TR') return; 1099 if (tr.entry === undefined) return; 1100 selectEntry(tr.entry, false); 1101 } 1102 1103 function handleSelectBaseline(select, event) { 1104 var option = select.options[select.selectedIndex]; 1105 baselineVersion = option.version; 1106 var showingDiff = baselineVersion !== undefined; 1107 var body = $('body'); 1108 toggleCssClass(body, 'diff', showingDiff); 1109 toggleCssClass(body, 'noDiff', !showingDiff); 1110 showPage(selectedPage); 1111 if (selectedEntry === undefined) return; 1112 selectEntry(selectedEntry, true); 1113 } 1114 1115 function findEntry(event) { 1116 var target = event.target; 1117 while (target.entry === undefined) { 1118 target = target.parentNode; 1119 if (!target) return undefined; 1120 } 1121 return target.entry; 1122 } 1123 1124 function handleUpdatePopover(event) { 1125 var popover = $('popover'); 1126 popover.style.left = event.pageX + 'px'; 1127 popover.style.top = event.pageY + 'px'; 1128 popover.style.display = 'none'; 1129 popover.style.display = event.shiftKey ? 'block' : 'none'; 1130 var entry = findEntry(event); 1131 if (entry === undefined) return; 1132 showPopover(entry); 1133 } 1134 1135 function handleToggleVersionOrPageEnable(event) { 1136 var item = this.item ; 1137 if (item === undefined) return; 1138 item .enabled = this.checked; 1139 initialize(); 1140 var page = selectedPage; 1141 if (page === undefined || !page.version.enabled) { 1142 page = versions.getEnabledPage(page.name); 1143 } 1144 if (!page.enabled) { 1145 page = page.getNextPage(); 1146 } 1147 showPage(page); 1148 } 1149 1150 function handleToggleContentVisibility(event) { 1151 var content = event.target.contentNode; 1152 toggleCssClass(content, 'hidden'); 1153 } 1154 1155 function handleCodeSearch(event) { 1156 var entry = findEntry(event); 1157 if (entry === undefined) return; 1158 var url = "https://cs.chromium.org/search/?sq=package:chromium&type=cs&q="; 1159 name = entry.name; 1160 if (name.startsWith("API_")) { 1161 name = name.substring(4); 1162 } 1163 url += encodeURIComponent(name) + "+file:src/v8/src"; 1164 window.open(url,'_blank'); 1165 } 1166 </script> 1167 <script type="text/javascript"> 1168 "use strict" 1169 // ========================================================================= 1170 class Versions { 1171 constructor() { 1172 this.versions = []; 1173 } 1174 add(version) { 1175 this.versions.push(version) 1176 } 1177 getPageVersions(page) { 1178 var result = []; 1179 this.versions.forEach((version) => { 1180 if (!version.enabled) return; 1181 var versionPage = version.get(page.name); 1182 if (versionPage !== undefined) result.push(versionPage); 1183 }); 1184 return result; 1185 } 1186 get length() { 1187 return this.versions.length 1188 } 1189 get(index) { 1190 return this.versions[index] 1191 }; 1192 forEach(f) { 1193 this.versions.forEach(f); 1194 } 1195 sort() { 1196 this.versions.sort(NameComparator); 1197 } 1198 getEnabledPage(name) { 1199 for (var i = 0; i < this.versions.length; i++) { 1200 var version = this.versions[i]; 1201 if (!version.enabled) continue; 1202 var page = version.get(name); 1203 if (page !== undefined) return page; 1204 } 1205 } 1206 } 1207 Versions.fromJSON = function(json) { 1208 var versions = new Versions(); 1209 for (var version in json) { 1210 versions.add(Version.fromJSON(version, json[version])); 1211 } 1212 versions.sort(); 1213 return versions; 1214 } 1215 1216 class Version { 1217 constructor(name) { 1218 this.name = name; 1219 this.enabled = true; 1220 this.pages = []; 1221 } 1222 add(page) { 1223 this.pages.push(page); 1224 } 1225 indexOf(name) { 1226 for (var i = 0; i < this.pages.length; i++) { 1227 if (this.pages[i].name == name) return i; 1228 } 1229 return -1; 1230 } 1231 getNextPage(page) { 1232 if (this.length == 0) return undefined; 1233 return this.pages[(this.indexOf(page.name) + 1) % this.length]; 1234 } 1235 get(name) { 1236 var index = this.indexOf(name); 1237 if (0 <= index) return this.pages[index]; 1238 return undefined 1239 } 1240 get length() { 1241 return this.pages.length 1242 } 1243 getEntry(entry) { 1244 if (entry === undefined) return undefined; 1245 var page = this.get(entry.page.name); 1246 if (page === undefined) return undefined; 1247 return page.get(entry.name); 1248 } 1249 forEachEntry(fun) { 1250 this.forEachPage((page) => { 1251 page.forEach(fun); 1252 }); 1253 } 1254 forEachPage(fun) { 1255 this.pages.forEach((page) => { 1256 if (!page.enabled) return; 1257 fun(page); 1258 }) 1259 } 1260 allEntries() { 1261 var map = new Map(); 1262 this.forEachEntry((group, entry) => { 1263 if (!map.has(entry.name)) map.set(entry.name, entry); 1264 }); 1265 return Array.from(map.values()); 1266 } 1267 getTotalValue(name, property) { 1268 if (name === undefined) name = this.pages[0].total.name; 1269 var sum = 0; 1270 this.forEachPage((page) => { 1271 var entry = page.get(name); 1272 if (entry !== undefined) sum += entry[property]; 1273 }); 1274 return sum; 1275 } 1276 getTotalTime(name, showDiff) { 1277 return this.getTotalValue(name, showDiff === false ? '_time' : 'time'); 1278 } 1279 getTotalTimePercent(name, showDiff) { 1280 if (baselineVersion === undefined || showDiff === false) { 1281 // Return the overall average percent of the given entry name. 1282 return this.getTotalValue(name, 'time') / 1283 this.getTotalTime('Group-Total') * 100; 1284 } 1285 // Otherwise return the difference to the sum of the baseline version. 1286 var baselineValue = baselineVersion.getTotalTime(name, false); 1287 var total = this.getTotalValue(name, '_time'); 1288 return (total / baselineValue - 1) * 100; 1289 } 1290 getTotalTimeVariance(name, showDiff) { 1291 // Calculate the overall error for a given entry name 1292 var sum = 0; 1293 this.forEachPage((page) => { 1294 var entry = page.get(name); 1295 if (entry === undefined) return; 1296 sum += entry.timeVariance * entry.timeVariance; 1297 }); 1298 return Math.sqrt(sum); 1299 } 1300 getTotalTimeVariancePercent(name, showDiff) { 1301 return this.getTotalTimeVariance(name, showDiff) / 1302 this.getTotalTime(name, showDiff) * 100; 1303 } 1304 getTotalCount(name, showDiff) { 1305 return this.getTotalValue(name, showDiff === false ? '_count' : 'count'); 1306 } 1307 getAverageTimeImpact(name, showDiff) { 1308 return this.getTotalTime(name, showDiff) / this.pages.length; 1309 } 1310 getPagesByPercentImpact(name) { 1311 var sortedPages = 1312 this.pages.filter((each) => { 1313 return each.get(name) !== undefined 1314 }); 1315 sortedPages.sort((a, b) => { 1316 return b.get(name).timePercent - a.get(name).timePercent; 1317 }); 1318 return sortedPages; 1319 } 1320 sort() { 1321 this.pages.sort(NameComparator) 1322 } 1323 } 1324 Version.fromJSON = function(name, data) { 1325 var version = new Version(name); 1326 for (var pageName in data) { 1327 version.add(PageVersion.fromJSON(version, pageName, data[pageName])); 1328 } 1329 version.sort(); 1330 return version; 1331 } 1332 1333 class Pages extends Map { 1334 get(name) { 1335 if (name.indexOf('www.') == 0) { 1336 name = name.substring(4); 1337 } 1338 if (!this.has(name)) { 1339 this.set(name, new Page(name)); 1340 } 1341 return super.get(name); 1342 } 1343 } 1344 1345 class Page { 1346 constructor(name) { 1347 this.name = name; 1348 this.enabled = true; 1349 this.versions = []; 1350 } 1351 add(page) { 1352 this.versions.push(page); 1353 } 1354 } 1355 1356 class PageVersion { 1357 constructor(version, page) { 1358 this.page = page; 1359 this.page.add(this); 1360 this.total = Group.groups.get('total').entry(); 1361 this.total.isTotal = true; 1362 this.unclassified = new UnclassifiedEntry(this) 1363 this.groups = [ 1364 this.total, 1365 Group.groups.get('ic').entry(), 1366 Group.groups.get('optimize').entry(), 1367 Group.groups.get('compile').entry(), 1368 Group.groups.get('parse').entry(), 1369 Group.groups.get('callback').entry(), 1370 Group.groups.get('api').entry(), 1371 Group.groups.get('gc').entry(), 1372 Group.groups.get('javascript').entry(), 1373 Group.groups.get('runtime').entry(), 1374 this.unclassified 1375 ]; 1376 this.entryDict = new Map(); 1377 this.groups.forEach((entry) => { 1378 entry.page = this; 1379 this.entryDict.set(entry.name, entry); 1380 }); 1381 this.version = version; 1382 } 1383 add(entry) { 1384 // Ignore accidentally added Group entries. 1385 if (entry.name.startsWith(GroupedEntry.prefix)) return; 1386 entry.page = this; 1387 this.entryDict.set(entry.name, entry); 1388 var added = false; 1389 this.groups.forEach((group) => { 1390 if (!added) added = group.add(entry); 1391 }); 1392 if (added) return; 1393 this.unclassified.push(entry); 1394 } 1395 get(name) { 1396 return this.entryDict.get(name) 1397 } 1398 getEntry(entry) { 1399 if (entry === undefined) return undefined; 1400 return this.get(entry.name); 1401 } 1402 get length() { 1403 return this.versions.length 1404 } 1405 get name() { return this.page.name } 1406 get enabled() { return this.page.enabled } 1407 forEachSorted(referencePage, func) { 1408 // Iterate over all the entries in the order they appear on the 1409 // reference page. 1410 referencePage.forEach((parent, referenceEntry) => { 1411 var entry; 1412 if (parent) parent = this.entryDict.get(parent.name); 1413 if (referenceEntry) entry = this.entryDict.get(referenceEntry.name); 1414 func(parent, entry, referenceEntry); 1415 }); 1416 } 1417 forEach(fun) { 1418 this.forEachGroup((group) => { 1419 fun(undefined, group); 1420 group.forEach((entry) => { 1421 fun(group, entry) 1422 }); 1423 }); 1424 } 1425 forEachGroup(fun) { 1426 this.groups.forEach(fun) 1427 } 1428 sort() { 1429 this.groups.sort((a, b) => { 1430 return b.time - a.time; 1431 }); 1432 this.groups.forEach((group) => { 1433 group.sort() 1434 }); 1435 } 1436 distanceFromTotalPercent() { 1437 var sum = 0; 1438 this.groups.forEach(group => { 1439 if (group == this.total) return; 1440 var value = group.getTimePercentImpact() - 1441 this.getEntry(group).timePercent; 1442 sum += value * value; 1443 }); 1444 return sum; 1445 } 1446 getNextPage() { 1447 return this.version.getNextPage(this); 1448 } 1449 } 1450 PageVersion.fromJSON = function(version, name, data) { 1451 var page = new PageVersion(version, pages.get(name)); 1452 for (var i = 0; i < data.length; i++) { 1453 page.add(Entry.fromJSON(i, data[data.length - i - 1])); 1454 } 1455 page.sort(); 1456 return page 1457 } 1458 1459 1460 class Entry { 1461 constructor(position, name, time, timeVariance, timeVariancePercent, 1462 count, 1463 countVariance, countVariancePercent) { 1464 this.position = position; 1465 this.name = name; 1466 this._time = time; 1467 this._timeVariance = timeVariance; 1468 this._timeVariancePercent = timeVariancePercent; 1469 this._count = count; 1470 this.countVariance = countVariance; 1471 this.countVariancePercent = countVariancePercent; 1472 this.page = undefined; 1473 this.parent = undefined; 1474 this.isTotal = false; 1475 } 1476 getCompareWithBaseline(value, property) { 1477 if (baselineVersion == undefined) return value; 1478 var baselineEntry = baselineVersion.getEntry(this); 1479 if (!baselineEntry) return value; 1480 if (baselineVersion === this.page.version) return value; 1481 return value - baselineEntry[property]; 1482 } 1483 cssClass() { 1484 return '' 1485 } 1486 get time() { 1487 return this.getCompareWithBaseline(this._time, '_time'); 1488 } 1489 get count() { 1490 return this.getCompareWithBaseline(this._count, '_count'); 1491 } 1492 get timePercent() { 1493 var value = this._time / this.page.total._time * 100; 1494 if (baselineVersion == undefined) return value; 1495 var baselineEntry = baselineVersion.getEntry(this); 1496 if (!baselineEntry) return value; 1497 if (baselineVersion === this.page.version) return value; 1498 return (this._time - baselineEntry._time) / this.page.total._time * 1499 100; 1500 } 1501 get timePercentPerEntry() { 1502 var value = this._time / this.page.total._time * 100; 1503 if (baselineVersion == undefined) return value; 1504 var baselineEntry = baselineVersion.getEntry(this); 1505 if (!baselineEntry) return value; 1506 if (baselineVersion === this.page.version) return value; 1507 return (this._time / baselineEntry._time - 1) * 100; 1508 } 1509 get timePercentVariancePercent() { 1510 // Get the absolute values for the percentages 1511 return this.timeVariance / this.page.total._time * 100; 1512 } 1513 getTimeImpact(showDiff) { 1514 return this.page.version.getTotalTime(this.name, showDiff); 1515 } 1516 getTimeImpactVariancePercent(showDiff) { 1517 return this.page.version.getTotalTimeVariancePercent(this.name, showDiff); 1518 } 1519 getTimePercentImpact(showDiff) { 1520 return this.page.version.getTotalTimePercent(this.name, showDiff); 1521 } 1522 getCountImpact(showDiff) { 1523 return this.page.version.getTotalCount(this.name, showDiff); 1524 } 1525 getAverageTimeImpact(showDiff) { 1526 return this.page.version.getAverageTimeImpact(this.name, showDiff); 1527 } 1528 getPagesByPercentImpact() { 1529 return this.page.version.getPagesByPercentImpact(this.name); 1530 } 1531 get isGroup() { 1532 return false 1533 } 1534 get timeVariance() { 1535 return this._timeVariance 1536 } 1537 get timeVariancePercent() { 1538 return this._timeVariancePercent 1539 } 1540 } 1541 Entry.fromJSON = function(position, data) { 1542 return new Entry(position, ...data); 1543 } 1544 1545 class Group { 1546 constructor(name, regexp, color) { 1547 this.name = name; 1548 this.regexp = regexp; 1549 this.color = color; 1550 this.enabled = true; 1551 } 1552 entry() { return new GroupedEntry(this) }; 1553 } 1554 Group.groups = new Map(); 1555 Group.add = function(name, group) { 1556 this.groups.set(name, group); 1557 } 1558 Group.add('total', new Group('Total', /.*Total.*/, '#BBB')); 1559 Group.add('ic', new Group('IC', /.*IC.*/, "#3366CC")); 1560 Group.add('optimize', new Group('Optimize', 1561 /StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/, "#DC3912")); 1562 Group.add('compile', new Group('Compile', /.*Compile.*/, "#FFAA00")); 1563 Group.add('parse', new Group('Parse', /.*Parse.*/, "#FF6600")); 1564 Group.add('callback', new Group('Callback', /.*Callback.*/, "#109618")); 1565 Group.add('api', new Group('API', /.*API.*/, "#990099")); 1566 Group.add('gc', new Group('GC', /GC|AllocateInTargetSpace/, "#0099C6")); 1567 Group.add('javascript', new Group('JavaScript', /JS_Execution/, "#DD4477")); 1568 Group.add('runtime', new Group('Runtime', /.*/, "#88BB00")); 1569 Group.add('unclassified', new Group('Unclassified', /.*/, "#000")); 1570 1571 class GroupedEntry extends Entry { 1572 constructor(group) { 1573 super(0, GroupedEntry.prefix + group.name, 0, 0, 0, 0, 0, 0); 1574 this.group = group; 1575 this.entries = []; 1576 } 1577 get regexp() { return this.group.regexp } 1578 get color() { return this.group.color } 1579 get enabled() { return this.group.enabled } 1580 add(entry) { 1581 if (!this.regexp.test(entry.name)) return false; 1582 this._time += entry.time; 1583 this._count += entry.count; 1584 // TODO: sum up variance 1585 this.entries.push(entry); 1586 entry.parent = this; 1587 return true; 1588 } 1589 forEach(fun) { 1590 if (baselineVersion === undefined) { 1591 this.entries.forEach(fun); 1592 return; 1593 } 1594 // If we have a baslineVersion to compare against show also all entries 1595 // from the other group. 1596 var tmpEntries = baselineVersion.getEntry(this) 1597 .entries.filter((entry) => { 1598 return this.page.get(entry.name) == undefined 1599 }); 1600 1601 // The compared entries are sorted by absolute impact. 1602 tmpEntries = tmpEntries.map((entry) => { 1603 var tmpEntry = new Entry(0, entry.name, 0, 0, 0, 0, 0, 0); 1604 tmpEntry.page = this.page; 1605 return tmpEntry; 1606 }); 1607 tmpEntries = tmpEntries.concat(this.entries); 1608 tmpEntries.sort((a, b) => { 1609 return a.time - b.time 1610 }); 1611 tmpEntries.forEach(fun); 1612 } 1613 sort() { 1614 this.entries.sort((a, b) => { 1615 return b.time - a.time; 1616 }); 1617 } 1618 cssClass() { 1619 if (this.page.total == this) return 'total'; 1620 return ''; 1621 } 1622 get isGroup() { 1623 return true 1624 } 1625 getVarianceForProperty(property) { 1626 var sum = 0; 1627 this.entries.forEach((entry) => { 1628 sum += entry[property + 'Variance'] * entry[property + 1629 'Variance']; 1630 }); 1631 return Math.sqrt(sum); 1632 } 1633 get timeVariancePercent() { 1634 if (this._time == 0) return 0; 1635 return this.getVarianceForProperty('time') / this._time * 100 1636 } 1637 get timeVariance() { 1638 return this.getVarianceForProperty('time') 1639 } 1640 } 1641 GroupedEntry.prefix = 'Group-'; 1642 1643 class UnclassifiedEntry extends GroupedEntry { 1644 constructor(page) { 1645 super(Group.groups.get('unclassified')); 1646 this.page = page; 1647 this._time = undefined; 1648 this._count = undefined; 1649 } 1650 add(entry) { 1651 this.entries.push(entry); 1652 entry.parent = this; 1653 return true; 1654 } 1655 forEachPageGroup(fun) { 1656 this.page.forEachGroup((group) => { 1657 if (group == this) return; 1658 if (group == this.page.total) return; 1659 fun(group); 1660 }); 1661 } 1662 get time() { 1663 if (this._time === undefined) { 1664 this._time = this.page.total._time; 1665 this.forEachPageGroup((group) => { 1666 this._time -= group._time; 1667 }); 1668 } 1669 return this.getCompareWithBaseline(this._time, '_time'); 1670 } 1671 get count() { 1672 if (this._count === undefined) { 1673 this._count = this.page.total._count; 1674 this.forEachPageGroup((group) => { 1675 this._count -= group._count; 1676 }); 1677 } 1678 return this.getCompareWithBaseline(this._count, '_count'); 1679 } 1680 } 1681 </script> 1682 </head> 1683 1684 <body id="body" onmousemove="handleUpdatePopover(event)" onload="handleBodyLoad()" class="noDiff"> 1685 <h1>Runtime Stats Komparator</h1> 1686 1687 <div id="results"> 1688 <div class="inline"> 1689 <h2>Data</h2> 1690 <form name="fileForm"> 1691 <p> 1692 <input id="uploadInput" type="file" name="files" onchange="handleLoadFile();" accept=".json"> 1693 </p> 1694 </form> 1695 </div> 1696 1697 <div class="inline hidden"> 1698 <h2>Result</h2> 1699 <div class="compareSelector inline"> 1700 Compare against: <select id="baseline" onchange="handleSelectBaseline(this, event)"></select><br/> 1701 <span style="color: #060">Green</span> the selected version above performs 1702 better on this measurement. 1703 </div> 1704 </div> 1705 1706 <div id="versionSelector" class="inline toggleContentVisibility"> 1707 <h2>Versions</h2> 1708 <div class="content hidden"> 1709 <ul></ul> 1710 </div> 1711 </div> 1712 1713 <div id="pageSelector" class="inline toggleContentVisibility"> 1714 <h2>Pages</h2> 1715 <div class="content hidden"> 1716 <ul></ul> 1717 </div> 1718 </div> 1719 1720 <div id="groupSelector" class="inline toggleContentVisibility"> 1721 <h2>Groups</h2> 1722 <div class="content hidden"> 1723 <ul></ul> 1724 </div> 1725 </div> 1726 1727 <div id="view"> 1728 </div> 1729 1730 <div id="detailView" class="hidden"> 1731 <div class="versionDetail inline toggleContentVisibility"> 1732 <h3><span></span></h3> 1733 <div class="content"> 1734 <table class="versionDetailTable" onclick="handleSelectDetailRow(this, event);"> 1735 <thead> 1736 <tr> 1737 <th class="version">Version </th> 1738 <th class="position">Pos. </th> 1739 <th class="value time">Time </th> 1740 <th class="value time">Percent </th> 1741 <th class="value count">Count </th> 1742 </tr> 1743 </thead> 1744 <tbody></tbody> 1745 </table> 1746 </div> 1747 </div> 1748 <div class="pageDetail inline toggleContentVisibility"> 1749 <h3>Page Comparison for <span></span></h3> 1750 <div class="content"> 1751 <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);"> 1752 <thead> 1753 <tr> 1754 <th class="page">Page </th> 1755 <th class="value time">Time </th> 1756 <th class="value time">Percent </th> 1757 <th class="value time hideNoDiff">%/Entry </th> 1758 <th class="value count">Count </th> 1759 </tr> 1760 </thead> 1761 <tfoot> 1762 <tr> 1763 <td class="page">Total:</td> 1764 <td class="value time"></td> 1765 <td class="value time"></td> 1766 <td class="value time hideNoDiff"></td> 1767 <td class="value count"></td> 1768 </tr> 1769 </tfoot> 1770 <tbody></tbody> 1771 </table> 1772 </div> 1773 </div> 1774 <div class="impactView inline toggleContentVisibility"> 1775 <h3>Impact list for <span></span></h3> 1776 <div class="content"> 1777 <table class="pageDetailTable" onclick="handleSelectDetailRow(this, event);"> 1778 <thead> 1779 <tr> 1780 <th class="page">Name </th> 1781 <th class="value time">Time </th> 1782 <th class="value time">Percent </th> 1783 <th class="">Top Pages</th> 1784 </tr> 1785 </thead> 1786 <tbody></tbody> 1787 </table> 1788 </div> 1789 </div> 1790 </div> 1791 <div id="pageVersionGraph" class="graph hidden toggleContentVisibility"> 1792 <h3><span></span></h3> 1793 <div class="content"></div> 1794 </div> 1795 <div id="pageGraph" class="graph hidden toggleContentVisibility"> 1796 <h3><span></span></h3> 1797 <div class="content"></div> 1798 </div> 1799 <div id="versionGraph" class="graph hidden toggleContentVisibility"> 1800 <h3><span></span></h3> 1801 <div class="content"></div> 1802 </div> 1803 1804 <div id="column" class="column"> 1805 <div class="header"> 1806 <select class="version" onchange="handleSelectVersion(this, event);"></select> 1807 <select class="pageVersion" onchange="handleSelectPage(this, event);"></select> 1808 </div> 1809 <table class="list" onclick="handleSelectRow(this, event);"> 1810 <thead> 1811 <tr> 1812 <th class="position">Pos. </th> 1813 <th class="name">Name </th> 1814 <th class="value time">Time </th> 1815 <th class="value time">Percent </th> 1816 <th class="value count">Count </th> 1817 </tr> 1818 </thead> 1819 <tbody></tbody> 1820 </table> 1821 </div> 1822 </div> 1823 1824 <div class="inline"> 1825 <h2>Usage</h2> 1826 <ol> 1827 <li>Install scipy, e.g. <code>sudo aptitude install python-scipy</code> 1828 <li>Build chrome.</li> 1829 <li>Check out a known working version of webpagereply: 1830 <pre>git -C $CHROME_DIR/third_party/webpagereplay checkout 7dbd94752d1cde5536ffc623a9e10a51721eff1d</pre> 1831 </li> 1832 <li>Run <code>callstats.py</code> with a web-page-replay archive: 1833 <pre>$V8_DIR/tools/callstats.py run \ 1834 --replay-bin=$CHROME_SRC/third_party/webpagereplay/replay.py \ 1835 --replay-wpr=$INPUT_DIR/top25.wpr \ 1836 --js-flags="" \ 1837 --with-chrome=$CHROME_SRC/out/Release/chrome \ 1838 --sites-file=$INPUT_DIR/top25.json</pre> 1839 </li> 1840 <li>Move results file to a subdirectory: <code>mkdir $VERSION_DIR; mv *.txt $VERSION_DIR</code></li> 1841 <li>Repeat from step 1 with a different configuration (e.g. <code>--js-flags="--nolazy"</code>).</li> 1842 <li>Create the final results file: <code>./callstats.py json $VERSION_DIR1 $VERSION_DIR2 > result.json</code></li> 1843 <li>Use <code>results.json</code> on this site.</code> 1844 </ol> 1845 </div> 1846 1847 <div id="popover"> 1848 <div class="popoverArrow"></div> 1849 <table> 1850 <tr> 1851 <td class="name" colspan="6"></td> 1852 </tr> 1853 <tr> 1854 <td>Page:</td> 1855 <td class="page name" colspan="6"></td> 1856 </tr> 1857 <tr> 1858 <td>Version:</td> 1859 <td class="version name" colspan="3"></td> 1860 <td class="compare version name" colspan="3"></td> 1861 </tr> 1862 <tr> 1863 <td>Time:</td> 1864 <td class="time"></td><td></td><td class="timeVariance"></td> 1865 <td class="compare time"></td><td class="compare"> </td><td class="compare timeVariance"></td> 1866 </tr> 1867 <tr> 1868 <td>Percent:</td> 1869 <td class="percent"></td><td></td><td class="percentVariance"></td> 1870 <td class="compare percent"></td><td class="compare"> </td><td class="compare percentVariance"></td> 1871 </tr> 1872 <tr> 1873 <td>Percent per Entry:</td> 1874 <td class="percentPerEntry"></td><td colspan=2></td> 1875 <td class="compare percentPerEntry"></td><td colspan=2></td> 1876 </tr> 1877 <tr> 1878 <td>Count:</td> 1879 <td class="count"></td><td></td><td class="countVariance"></td> 1880 <td class="compare count"></td><td class="compare"> </td><td class="compare countVariance"></td> 1881 </tr> 1882 <tr> 1883 <td>Overall Impact:</td> 1884 <td class="timeImpact"></td><td></td><td class="timePercentImpact"></td> 1885 <td class="compare timeImpact"></td><td class="compare"> </td><td class="compare timePercentImpact"></td> 1886 </tr> 1887 </table> 1888 </div> 1889 </body> 1890 </html> 1891