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