Home | History | Annotate | Download | only in resources
      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <title>Blink Performance Test Results</title>
      5 <style type="text/css">
      6 
      7 section {
      8     background: white;
      9     padding: 10px;
     10     position: relative;
     11 }
     12 
     13 .time-plots {
     14     padding-left: 25px;
     15 }
     16 
     17 .time-plots > div {
     18     display: inline-block;
     19     width: 90px;
     20     height: 40px;
     21     margin-right: 10px;
     22 }
     23 
     24 section h1 {
     25     text-align: center;
     26     font-size: 1em;
     27 }
     28 
     29 section .tooltip {
     30     position: absolute;
     31     text-align: center;
     32     background: #ffcc66;
     33     border-radius: 5px;
     34     padding: 0px 5px;
     35 }
     36 
     37 body {
     38     padding: 0px;
     39     margin: 0px;
     40     font-family: sans-serif;
     41 }
     42 
     43 table {
     44     background: white;
     45     width: 100%;
     46 }
     47 
     48 table, td, th {
     49     border-collapse: collapse;
     50     padding: 5px;
     51 }
     52 
     53 tr.even {
     54     background: #f6f6f6;
     55 }
     56 
     57 table td {
     58     position: relative;
     59     font-family: monospace;
     60 }
     61 
     62 th, td {
     63     cursor: pointer;
     64     cursor: hand;
     65 }
     66 
     67 th {
     68     background: #e6eeee;
     69     background: -webkit-gradient(linear, left top, left bottom, from(rgb(244, 244, 244)), to(rgb(217, 217, 217)));
     70     border: 1px solid #ccc;
     71 }
     72 
     73 th:after {
     74     content: ' \25B8';
     75 }
     76 
     77 th.headerSortUp:after {
     78     content: ' \25BE';
     79 }
     80 
     81 th.headerSortDown:after {
     82     content: ' \25B4';
     83 }
     84 
     85 td.comparison, td.result {
     86     text-align: right;
     87 }
     88 
     89 td.better {
     90     color: #6c6;
     91 }
     92 
     93 td.worse {
     94     color: #c66;
     95 }
     96 
     97 td.missing {
     98     text-align: center;
     99 }
    100 
    101 .checkbox {
    102     display: inline-block;
    103     background: #eee;
    104     background: -webkit-gradient(linear, left bottom, left top, from(rgb(220, 220, 220)), to(rgb(200, 200, 200)));
    105     border: inset 1px #ddd;
    106     border-radius: 5px;
    107     margin: 10px;
    108     font-size: small;
    109     cursor: pointer;
    110     cursor: hand;
    111     -webkit-user-select: none;
    112     font-weight: bold;
    113 }
    114 
    115 .checkbox span {
    116     display: inline-block;
    117     line-height: 100%;
    118     padding: 5px 8px;
    119     border: outset 1px transparent;
    120 }
    121 
    122 .checkbox .checked {
    123     background: #e6eeee;
    124     background: -webkit-gradient(linear, left top, left bottom, from(rgb(255, 255, 255)), to(rgb(235, 235, 235)));
    125     border: outset 1px #eee;
    126     border-radius: 5px;
    127 }
    128 
    129 </style>
    130 </head>
    131 <body>
    132 <div style="padding: 0 10px;">
    133 Result <span id="time-memory" class="checkbox"><span class="checked">Time</span><span>Memory</span></span>
    134 Reference <span id="reference" class="checkbox"></span>
    135 </div>
    136 <table id="container"></table>
    137 <script>
    138 
    139 (function () {
    140     var jQuery = 'PerformanceTests/Dromaeo/resources/dromaeo/web/lib/jquery-1.6.4.js';
    141     var plugins = ['PerformanceTests/resources/jquery.flot.min.js', 'PerformanceTests/resources/jquery.tablesorter.min.js',
    142         'PerformanceTests/resources/statistics.js'];
    143     var localPath = '%AbsolutePathToWebKitTrunk%';
    144     var remotePath = 'https://svn.webkit.org/repository/webkit/trunk';
    145     var numberOfFailures = 0;
    146     var startedLoadingPlugins = false;
    147     var numberOfLoadedPlugins = 0;
    148 
    149     function loadScript(src, loaded, failed) {
    150         var script = document.createElement('script');
    151         script.async = true;
    152         script.src = src;
    153         script.onload = loaded;
    154         if (failed)
    155             script.onerror = failed;
    156         document.body.appendChild(script);
    157     }
    158 
    159     function loadPlugins(trunkPath) {
    160         for (var i = 0; i < plugins.length; i++)
    161             loadScript(trunkPath + '/' + plugins[i], loadedPlugin, createFailedToLoadPlugin(plugins[i]));
    162     }
    163 
    164     function loadedPlugin() {
    165         numberOfLoadedPlugins++;
    166         if (numberOfLoadedPlugins == plugins.length)
    167             setTimeout(init, 0);            
    168     }
    169 
    170     function createFailedToLoadPlugin(plugin) {
    171         return function () { alert("Failed to load " + plugin); }
    172     }
    173 
    174     function createLoadedJQuery(trunkPath) {
    175         return function () { loadPlugins(trunkPath); }
    176     }
    177 
    178     loadScript(localPath + '/' + jQuery,
    179         createLoadedJQuery(localPath),
    180         function () {
    181             loadScript(remotePath + '/' + jQuery,
    182                 createLoadedJQuery(remotePath),
    183                 function () { alert("Failed to load jQuery."); });
    184         });
    185 })();
    186 
    187 function TestResult(metric, values, associatedRun) {
    188     if (values[0] instanceof Array) {
    189         var flattenedValues = [];
    190         for (var i = 0; i < values.length; i++)
    191             flattenedValues = flattenedValues.concat(values[i]);
    192         values = flattenedValues;
    193     }
    194 
    195     this.test = function () { return metric; }
    196     this.values = function () { return values.map(function (value) { return metric.scalingFactor() * value; }); }
    197     this.unscaledMean = function () { return Statistics.sum(values) / values.length; }
    198     this.mean = function () { return metric.scalingFactor() * this.unscaledMean(); }
    199     this.min = function () { return metric.scalingFactor() * Statistics.min(values); }
    200     this.max = function () { return metric.scalingFactor() * Statistics.max(values); }
    201     this.confidenceIntervalDelta = function () {
    202         return metric.scalingFactor() * Statistics.confidenceIntervalDelta(0.95, values.length,
    203             Statistics.sum(values), Statistics.squareSum(values));
    204     }
    205     this.confidenceIntervalDeltaRatio = function () { return this.confidenceIntervalDelta() / this.mean(); }
    206     this.percentDifference = function(other) { return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean(); }
    207     this.isStatisticallySignificant = function (other) {
    208         var diff = Math.abs(other.mean() - this.mean());
    209         return diff > this.confidenceIntervalDelta() && diff > other.confidenceIntervalDelta();
    210     }
    211     this.run = function () { return associatedRun; }
    212 }
    213 
    214 function TestRun(entry) {
    215     this.description = function () { return entry['description']; }
    216     this.webkitRevision = function () { return entry['revisions']['blink']['revision']; }
    217     this.label = function () {
    218         var label = 'r' + this.webkitRevision();
    219         if (this.description())
    220             label += ' &dash; ' + this.description();
    221         return label;
    222     }
    223 }
    224 
    225 function PerfTestMetric(name, metric) {
    226     var testResults = [];
    227     var cachedUnit = null;
    228     var cachedScalingFactor = null;
    229     var unit = {'FrameRate': 'fps', 'Runs': 'runs/s', 'Time': 'ms', 'Malloc': 'bytes', 'JSHeap': 'bytes'}[metric];
    230 
    231     // We can't do this in TestResult because all results for each test need to share the same unit and the same scaling factor.
    232     function computeScalingFactorIfNeeded() {
    233         // FIXME: We shouldn't be adjusting units on every test result.
    234         // We can only do this on the first test.
    235         if (!testResults.length || cachedUnit)
    236             return;
    237 
    238         var mean = testResults[0].unscaledMean(); // FIXME: We should look at all values.
    239         var kilo = unit == 'bytes' ? 1024 : 1000;
    240         if (mean > 10 * kilo * kilo && unit != 'ms') {
    241             cachedScalingFactor = 1 / kilo / kilo;
    242             cachedUnit = 'M ' + unit;
    243         } else if (mean > 10 * kilo) {
    244             cachedScalingFactor = 1 / kilo;
    245             cachedUnit = unit == 'ms' ? 's' : ('K ' + unit);
    246         } else {
    247             cachedScalingFactor = 1;
    248             cachedUnit = unit;
    249         }
    250     }
    251 
    252     this.name = function () { return name + ':' + metric; }
    253     this.isMemoryTest = function () { return metric == 'JSHeap' || metric == 'Malloc'; }
    254     this.addResult = function (newResult) {
    255         testResults.push(newResult);
    256         cachedUnit = null;
    257         cachedScalingFactor = null;
    258     }
    259     this.results = function () { return testResults; }
    260     this.scalingFactor = function() {
    261         computeScalingFactorIfNeeded();
    262         return cachedScalingFactor;
    263     }
    264     this.unit = function () {
    265         computeScalingFactorIfNeeded();
    266         return cachedUnit;
    267     }
    268     this.smallerIsBetter = function () { return unit == 'ms' || unit == 'bytes'; }
    269 }
    270 
    271 var plotColor = 'rgb(230,50,50)';
    272 var subpointsPlotOptions = {
    273     lines: {show:true, lineWidth: 0},
    274     color: plotColor,
    275     points: {show: true, radius: 1},
    276     bars: {show: false}};
    277 
    278 var mainPlotOptions = {
    279     xaxis: {
    280         min: -0.5,
    281         tickSize: 1,
    282     },
    283     crosshair: { mode: 'y' },
    284     series: { shadowSize: 0 },
    285     bars: {show: true, align: 'center', barWidth: 0.5},
    286     lines: { show: false },
    287     points: { show: true },
    288     grid: {
    289         borderWidth: 1,
    290         borderColor: '#ccc',
    291         backgroundColor: '#fff',
    292         hoverable: true,
    293         autoHighlight: false,
    294     }
    295 };
    296 
    297 var timePlotOptions = {
    298     yaxis: { show: false },
    299     xaxis: { show: false },
    300     lines: { show: true },
    301     grid: { borderWidth: 1, borderColor: '#ccc' },
    302     colors: [ plotColor ]
    303 };
    304 
    305 function createPlot(container, test) {
    306     var section = $('<section><div class="plot"></div><div class="time-plots"></div>'
    307         + '<span class="tooltip"></span></section>');
    308     section.children('.plot').css({'width': (100 * test.results().length + 25) + 'px', 'height': '300px'});
    309     $(container).append(section);
    310 
    311     var plotContainer = section.children('.plot');
    312     var minIsZero = true;
    313     attachPlot(test, plotContainer, minIsZero);
    314 
    315     attachTimePlots(test, section.children('.time-plots'));
    316 
    317     var tooltip = section.children('.tooltip');
    318     plotContainer.bind('plothover', function (event, position, item) {
    319         if (item) {
    320             var postfix = item.series.id ? ' (' + item.series.id + ')' : '';
    321             tooltip.html(item.datapoint[1].toPrecision(4) + postfix);
    322             var sectionOffset = $(section).offset();
    323             tooltip.css({left: item.pageX - sectionOffset.left - tooltip.outerWidth() / 2, top: item.pageY - sectionOffset.top + 10});
    324             tooltip.fadeIn(200);
    325         } else
    326             tooltip.hide();
    327     });
    328     plotContainer.mouseout(function () {
    329         tooltip.hide();
    330     });
    331     plotContainer.click(function (event) {
    332         event.preventDefault();
    333         minIsZero = !minIsZero;
    334         attachPlot(test, plotContainer, minIsZero);
    335     });
    336 
    337     return section;
    338 }
    339 
    340 function attachTimePlots(test, container) {
    341     var results = test.results();
    342     var attachedPlot = false;
    343     for (var i = 0; i < results.length; i++) {
    344         container.append('
'
); 345 var values = results[i].values(); 346 if (!values) 347 continue; 348 attachedPlot = true; 349 350 $.plot(container.children().last(), [values.map(function (value, index) { return [index, value]; })], 351 $.extend(true, {}, timePlotOptions, {yaxis: {min: Math.min.apply(Math, values) * 0.9, max: Math.max.apply(Math, values) * 1.1}, 352 xaxis: {min: -0.5, max: values.length - 0.5}})); 353 } 354 if (!attachedPlot) 355 container.children().remove(); 356 } 357 358 function attachPlot(test, plotContainer, minIsZero) { 359 var results = test.results(); 360 361 var values = results.reduce(function (values, result, index) { 362 var newValues = result.values(); 363 return newValues ? values.concat(newValues.map(function (value) { return [index, value]; })) : values; 364 }, []); 365 366 var plotData = [$.extend(true, {}, subpointsPlotOptions, {data: values})]; 367 plotData.push({id: '&mu;', data: results.map(function (result, index) { return [index, result.mean()]; }), color: plotColor}); 368 369 var overallMax = Statistics.max(results.map(function (result, index) { return result.max(); })); 370 var overallMin = Statistics.min(results.map(function (result, index) { return result.min(); })); 371 var margin = (overallMax - overallMin) * 0.1; 372 var currentPlotOptions = $.extend(true, {}, mainPlotOptions, {yaxis: { 373 min: minIsZero ? 0 : overallMin - margin, 374 max: minIsZero ? overallMax * 1.1 : overallMax + margin}}); 375 376 currentPlotOptions.xaxis.max = results.length - 0.5; 377 currentPlotOptions.xaxis.ticks = results.map(function (result, index) { return [index, result.run().label()]; }); 378 379 $.plot(plotContainer, plotData, currentPlotOptions); 380 } 381 382 function toFixedWidthPrecision(value) { 383 var decimal = value.toFixed(2); 384 return decimal; 385 } 386 387 function formatPercentage(fraction) { 388 var percentage = fraction * 100; 389 return (fraction * 100).toFixed(2) + '%'; 390 } 391 392 function createTable(tests, runs, shouldIgnoreMemory, referenceIndex) { 393 $('#container').html('TestUnit' + runs.map(function (run, index) { 394 return 'referenceIndex ? 2 : 3) + '" class="{sorter: \'comparison\'}">' + run.label() + ''; 395 }).reduce(function (markup, cell) { return markup + cell; }, '') + ''); 396 397 var testNames = []; 398 for (testName in tests) 399 testNames.push(testName); 400 401 testNames.sort().map(function (testName) { 402 var test = tests[testName]; 403 if (test.isMemoryTest() != shouldIgnoreMemory) 404 createTableRow(runs, test, referenceIndex); 405 }); 406 407 $('#container').tablesorter({widgets: ['zebra']}); 408 } 409 410 function linearRegression(points) { 411 // Implement http://www.easycalculation.com/statistics/learn-correlation.php. 412 // x = magnitude 413 // y = iterations 414 var sumX = 0; 415 var sumY = 0; 416 var sumXSquared = 0; 417 var sumYSquared = 0; 418 var sumXTimesY = 0; 419 420 for (var i = 0; i < points.length; i++) { 421 var x = i; 422 var y = points[i]; 423 sumX += x; 424 sumY += y; 425 sumXSquared += x * x; 426 sumYSquared += y * y; 427 sumXTimesY += x * y; 428 } 429 430 var r = (points.length * sumXTimesY - sumX * sumY) / 431 Math.sqrt((points.length * sumXSquared - sumX * sumX) * 432 (points.length * sumYSquared - sumY * sumY)); 433 434 if (isNaN(r) || r == Math.Infinity) 435 r = 0; 436 437 var slope = (points.length * sumXTimesY - sumX * sumY) / (points.length * sumXSquared - sumX * sumX); 438 var intercept = sumY / points.length - slope * sumX / points.length; 439 return {slope: slope, intercept: intercept, rSquared: r * r}; 440 } 441 442 var warningSign = '<svg viewBox="0 0 100 100" style="width: 18px; height: 18px; vertical-align: bottom;" version="1.1">' 443 + '<polygon fill="red" points="50,10 90,80 10,80 50,10" stroke="red" stroke-width="10" stroke-linejoin="round" />' 444 + '<polygon fill="white" points="47,30 48,29, 50, 28.7, 52,29 53,30 50,60" stroke="white" stroke-width="10" stroke-linejoin="round" />' 445 + '<circle cx="50" cy="73" r="6" fill="white" />' 446 + '</svg>'; 447 448 function createTableRow(runs, test, referenceIndex) { 449 var tableRow = $('<tr><td class="test">' + test.name() + '</td><td class="unit">' + test.unit() + '</td></tr>'); 450 451 function markupForRun(result, referenceResult) { 452 var comparisonCell = ''; 453 var hiddenValue = ''; 454 var shouldCompare = result !== referenceResult; 455 if (shouldCompare && referenceResult) { 456 var percentDifference = referenceResult.percentDifference(result); 457 var better = test.smallerIsBetter() ? percentDifference < 0 : percentDifference > 0; 458 var comparison = ''; 459 var className = 'comparison'; 460 if (referenceResult.isStatisticallySignificant(result)) { 461 comparison = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse&nbsp;'); 462 className += better ? ' better' : ' worse'; 463 } 464 hiddenValue = '<span style="display: none">|' + comparison + '</span>'; 465 comparisonCell = '<td class="' + className + '">' + comparison + '</td>'; 466 } else if (shouldCompare) 467 comparisonCell = '<td class="comparison"></td>'; 468 469 var values = result.values(); 470 var warning = ''; 471 var regressionAnalysis = ''; 472 if (values && values.length > 3) { 473 regressionResult = linearRegression(values); 474 regressionAnalysis = 'slope=' + toFixedWidthPrecision(regressionResult.slope) 475 + ', R^2=' + toFixedWidthPrecision(regressionResult.rSquared); 476 if (regressionResult.rSquared > 0.6 && Math.abs(regressionResult.slope) > 0.01) { 477 warning = ' <span class="regression-warning" title="Detected a time dependency with ' + regressionAnalysis + '">' + warningSign + ' </span>'; 478 } 479 } 480 481 var statistics = '&sigma;=' + toFixedWidthPrecision(result.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result.min()) 482 + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysis; 483 484 // Tablesorter doesn't know about the second cell so put the comparison in the invisible element. 485 return '<td class="result" title="' + statistics + '">' + toFixedWidthPrecision(result.mean()) + hiddenValue 486 + '</td><td class="confidenceIntervalDelta" title="' + statistics + '">&plusmn; ' 487 + formatPercentage(result.confidenceIntervalDeltaRatio()) + warning + '</td>' + comparisonCell; 488 } 489 490 function markupForMissingRun(isReference) { 491 return '<td colspan="' + (isReference ? 2 : 3) + '" class="missing">Missing</td>'; 492 } 493 494 var runIndex = 0; 495 var results = test.results(); 496 var referenceResult = undefined; 497 var resultIndexMap = {}; 498 for (var i = 0; i < results.length; i++) { 499 while (runs[runIndex] !== results[i].run()) 500 runIndex++; 501 if (runIndex == referenceIndex) 502 referenceResult = results[i]; 503 resultIndexMap[runIndex] = i; 504 } 505 for (var i = 0; i < runs.length; i++) { 506 var resultIndex = resultIndexMap[i]; 507 if (resultIndex == undefined) 508 tableRow.append(markupForMissingRun(i == referenceIndex)); 509 else 510 tableRow.append(markupForRun(results[resultIndex], referenceResult)); 511 } 512 513 $('#container').children('tbody').last().append(tableRow); 514 515 tableRow.click(function (event) { 516 if (event.target != tableRow[0] && event.target.parentNode != tableRow[0]) 517 return; 518 519 event.preventDefault(); 520 521 var firstCell = tableRow.children('td').first(); 522 if (firstCell.children('section').length) { 523 firstCell.children('section').remove(); 524 tableRow.children('td').css({'padding-bottom': ''}); 525 } else { 526 var plot = createPlot(firstCell, test); 527 plot.css({'position': 'absolute', 'z-index': 2}); 528 var offset = tableRow.offset(); 529 offset.left += 1; 530 offset.top += tableRow.outerHeight(); 531 plot.offset(offset); 532 tableRow.children('td').css({'padding-bottom': plot.outerHeight() + 5}); 533 } 534 535 return false; 536 }); 537 } 538 539 function init() { 540 $.tablesorter.addParser({ 541 id: 'comparison', 542 is: function(s) { 543 return s.indexOf('|') >= 0; 544 }, 545 format: function(s) { 546 var parsed = parseFloat(s.substring(s.indexOf('|') + 1)); 547 return isNaN(parsed) ? 0 : parsed; 548 }, 549 type: 'numeric', 550 }); 551 552 var runs = []; 553 var metrics = {}; 554 $.each(JSON.parse(document.getElementById('json').textContent), function (index, entry) { 555 var run = new TestRun(entry); 556 runs.push(run); 557 558 function addTests(tests, parentFullName) { 559 for (var testName in tests) { 560 var fullTestName = parentFullName + '/' + testName; 561 var rawMetrics = tests[testName].metrics; 562 563 for (var metricName in rawMetrics) { 564 var fullMetricName = fullTestName + ':' + metricName; 565 var metric = metrics[fullMetricName]; 566 if (!metric) { 567 metric = new PerfTestMetric(fullTestName, metricName); 568 metrics[fullMetricName] = metric; 569 } 570 metric.addResult(new TestResult(metric, rawMetrics[metricName].current, run)); 571 } 572 573 if (tests[testName].tests) 574 addTests(tests[testName].tests, fullTestName); 575 } 576 } 577 578 addTests(entry.tests, ''); 579 }); 580 581 var shouldIgnoreMemory= true; 582 var referenceIndex = 0; 583 584 createTable(metrics, runs, shouldIgnoreMemory, referenceIndex); 585 586 $('#time-memory').bind('change', function (event, checkedElement) { 587 shouldIgnoreMemory = checkedElement.textContent == 'Time'; 588 createTable(metrics, runs, shouldIgnoreMemory, referenceIndex); 589 }); 590 591 runs.map(function (run, index) { 592 $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + '>' + run.label() + '</span>'); 593 }) 594 595 $('#reference').bind('change', function (event, checkedElement) { 596 referenceIndex = parseInt(checkedElement.getAttribute('value')); 597 createTable(metrics, runs, shouldIgnoreMemory, referenceIndex); 598 }); 599 600 $('.checkbox').each(function (index, checkbox) { 601 $(checkbox).children('span').click(function (event) { 602 if ($(this).hasClass('checked')) 603 return; 604 $(checkbox).children('span').removeClass('checked'); 605 $(this).addClass('checked'); 606 $(checkbox).trigger('change', $(this)); 607 }); 608 }); 609 } 610 611 </script> 612 <script id="json" type="application/json">%PeformanceTestsResultsJSON%</script> 613 </body> 614 </html> 615