Home | History | Annotate | Download | only in html_output
      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <title>Telemetry Performance Test Results</title>
      5 <style type="text/css">
      6 
      7 section {
      8     background: white;
      9     padding: 10px;
     10     position: relative;
     11 }
     12 
     13 .collapsed:before {
     14     content: '\25B8\00A0';
     15 }
     16 
     17 .expanded:before {
     18     content: '\25BE\00A0';
     19 }
     20 
     21 .line-plots {
     22     padding-left: 25px;
     23 }
     24 
     25 .line-plots > div {
     26     display: inline-block;
     27     width: 90px;
     28     height: 40px;
     29     margin-right: 10px;
     30 }
     31 
     32 .lage-line-plots {
     33     padding-left: 25px;
     34 }
     35 
     36 .large-line-plots > div, .histogram-plots > div {
     37     display: inline-block;
     38     width: 400px;
     39     height: 200px;
     40     margin-right: 10px;
     41 }
     42 
     43 .large-line-plot-labels > div, .histogram-plot-labels > div {
     44     display: inline-block;
     45     width: 400px;
     46     height: 11px;
     47     margin-right: 10px;
     48     color: #545454;
     49     text-align: center;
     50     font-size: 11px;
     51 }
     52 
     53 .closeButton {
     54     background: #fff;
     55     border: 1px solid black;
     56     border-radius: 2px;
     57     display: inline-block;
     58     float: right;
     59     line-height: 11px;
     60     margin-top: 3px;
     61     width: 11px;
     62 }
     63 
     64 .closeButton:hover {
     65     background: #F09C9C;
     66 }
     67 
     68 .label {
     69     cursor: text;
     70 }
     71 
     72 .label:hover {
     73     background: #ffcc66;
     74 }
     75 
     76 section h1 {
     77     text-align: center;
     78     font-size: 1em;
     79 }
     80 
     81 section .tooltip {
     82     position: absolute;
     83     text-align: center;
     84     background: #ffcc66;
     85     border-radius: 5px;
     86     padding: 0px 5px;
     87 }
     88 
     89 body {
     90     padding: 0px;
     91     margin: 0px;
     92     font-family: sans-serif;
     93 }
     94 
     95 table {
     96     background: white;
     97     width: 100%;
     98 }
     99 
    100 table, td, th {
    101     border-collapse: collapse;
    102     padding: 5px;
    103     white-space: nowrap;
    104 }
    105 
    106 tr.even {
    107     background: #f6f6f6;
    108 }
    109 
    110 table td {
    111     position: relative;
    112     font-family: monospace;
    113 }
    114 
    115 th, td {
    116     cursor: pointer;
    117     cursor: hand;
    118 }
    119 
    120 th {
    121     background: #e6eeee;
    122     background: -webkit-gradient(linear, left top, left bottom, from(rgb(244, 244, 244)), to(rgb(217, 217, 217)));
    123     border: 1px solid #ccc;
    124 }
    125 
    126 th:after {
    127     content: ' \25B8';
    128 }
    129 
    130 th.headerSortUp:after {
    131     content: ' \25BE';
    132 }
    133 
    134 th.headerSortDown:after {
    135     content: ' \25B4';
    136 }
    137 
    138 td.comparison, td.result {
    139     text-align: right;
    140 }
    141 
    142 td.better {
    143     color: #6c6;
    144 }
    145 
    146 td.worse {
    147     color: #c66;
    148 }
    149 
    150 td.missing {
    151     color: #aaa;
    152     text-align: center;
    153 }
    154 
    155 .checkbox {
    156     display: inline-block;
    157     background: #eee;
    158     background: -webkit-gradient(linear, left bottom, left top, from(rgb(220, 220, 220)), to(rgb(200, 200, 200)));
    159     border: inset 1px #ddd;
    160     border-radius: 5px;
    161     margin: 10px;
    162     font-size: small;
    163     cursor: pointer;
    164     cursor: hand;
    165     -webkit-user-select: none;
    166     font-weight: bold;
    167 }
    168 
    169 .checkbox span {
    170     display: inline-block;
    171     line-height: 100%;
    172     padding: 5px 8px;
    173     border: outset 1px transparent;
    174 }
    175 
    176 .checkbox .checked {
    177     background: #e6eeee;
    178     background: -webkit-gradient(linear, left top, left bottom, from(rgb(255, 255, 255)), to(rgb(235, 235, 235)));
    179     border: outset 1px #eee;
    180     border-radius: 5px;
    181 }
    182 
    183 </style>
    184 </head>
    185 <body onload="init()">
    186 <div style="padding: 0 10px; white-space: nowrap;">
    187 Result <span id="time-memory" class="checkbox"><span class="checked">Time</span><span>Memory</span></span>
    188 Reference <span id="reference" class="checkbox"></span>
    189 Style <span id="scatter-line" class="checkbox"><span class="checked">Scatter</span><span>Line</span></span>
    190 <span class="checkbox"><span class="checked" id="undelete">Undelete</span></span>
    191 Run Telemetry with --reset-results to clear all runs
    192 </div>
    193 <table id="container"></table>
    194 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
    195 <script>
    196 %plugins%
    197 </script>
    198 <script>
    199 function TestResult(metric, values, associatedRun) {
    200     if (values[0] instanceof Array) {
    201         var flattenedValues = [];
    202         for (var i = 0; i < values.length; i++)
    203             flattenedValues = flattenedValues.concat(values[i]);
    204         values = flattenedValues;
    205     }
    206 
    207     if (jQuery.type(values[0]) === 'string') {
    208         try {
    209             var current = JSON.parse(values[0]);
    210             if (current.params.type === 'HISTOGRAM') {
    211                 this.histogramValues = current;
    212                 // Histogram results have no values (per se). Instead we calculate
    213                 // the values from the histogram bins.
    214                 var values = [];
    215                 var buckets = current.buckets
    216                 for (var i = 0; i < buckets.length; i++) {
    217                     var bucket = buckets[i];
    218                     var bucket_mean = (bucket.high + bucket.low) / 2;
    219                     for (var b = 0; b < bucket.count; b++) {
    220                         values.push(bucket_mean);
    221                     }
    222                 }
    223             }
    224         }
    225         catch (e) { /* ignore, assume not a JSON string */ }
    226     }
    227 
    228     this.test = function () { return metric; }
    229     this.values = function () { return values.map(function (value) { return metric.scalingFactor() * value; }); }
    230     this.unscaledMean = function () { return Statistics.sum(values) / values.length; }
    231     this.mean = function () { return metric.scalingFactor() * this.unscaledMean(); }
    232     this.min = function () { return metric.scalingFactor() * Statistics.min(values); }
    233     this.max = function () { return metric.scalingFactor() * Statistics.max(values); }
    234     this.confidenceIntervalDelta = function () {
    235         return metric.scalingFactor() * Statistics.confidenceIntervalDelta(0.95, values.length,
    236             Statistics.sum(values), Statistics.squareSum(values));
    237     }
    238     this.confidenceIntervalDeltaRatio = function () { return this.confidenceIntervalDelta() / this.mean(); }
    239     this.percentDifference = function(other) { return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean(); }
    240     this.isStatisticallySignificant = function (other) {
    241         var diff = Math.abs(other.mean() - this.mean());
    242         return diff > this.confidenceIntervalDelta() && diff > other.confidenceIntervalDelta();
    243     }
    244     this.run = function () { return associatedRun; }
    245 }
    246 
    247 function TestRun(entry) {
    248     this.id = function() { return entry['buildTime']; }
    249     this.revision = function () { return entry['revision']; }
    250     this.label = function () {
    251         if (labelKey in localStorage)
    252             return localStorage[labelKey];
    253         if (entry['label'])
    254             return entry['label'];
    255         return 'r' + this.revision();
    256     }
    257     this.setLabel = function(label) { localStorage[labelKey] = label; }
    258     this.isHidden = function() { return localStorage[hiddenKey]; }
    259     this.hide = function() { localStorage[hiddenKey] = true; }
    260     this.show = function() { localStorage.removeItem(hiddenKey); }
    261     this.description = function() {
    262         var label = this.label();
    263         if (label != 'r' + this.revision())
    264             label = ' ' + label;
    265         else
    266             label = '';
    267         return new Date(entry['buildTime']).toLocaleString() + '\n' + entry['platform'] + ' r' + entry['revision'] + label;
    268     }
    269 
    270     var labelKey = 'telemetry_label_' + this.id();
    271     var hiddenKey = 'telemetry_hide_' + this.id();
    272 }
    273 
    274 function PerfTestMetric(name, metric, unit, isImportant) {
    275     var testResults = [];
    276     var cachedUnit = null;
    277     var cachedScalingFactor = null;
    278 
    279     // We can't do this in TestResult because all results for each test need to share the same unit and the same scaling factor.
    280     function computeScalingFactorIfNeeded() {
    281         // FIXME: We shouldn't be adjusting units on every test result.
    282         // We can only do this on the first test.
    283         if (!testResults.length || cachedUnit)
    284             return;
    285 
    286         var mean = testResults[0].unscaledMean(); // FIXME: We should look at all values.
    287         var kilo = unit == 'bytes' ? 1024 : 1000;
    288         if (mean > 10 * kilo * kilo && unit != 'ms') {
    289             cachedScalingFactor = 1 / kilo / kilo;
    290             cachedUnit = 'M ' + unit;
    291         } else if (mean > 10 * kilo) {
    292             cachedScalingFactor = 1 / kilo;
    293             cachedUnit = unit == 'ms' ? 's' : ('K ' + unit);
    294         } else {
    295             cachedScalingFactor = 1;
    296             cachedUnit = unit;
    297         }
    298     }
    299 
    300     this.name = function () { return name + ':' + metric; }
    301     this.isImportant = isImportant;
    302     this.isMemoryTest = function () {
    303         return (unit == 'kb' ||
    304                 unit == 'KB' ||
    305                 unit == 'MB' ||
    306                 unit == 'bytes' ||
    307                 unit == 'count' ||
    308                 !metric.indexOf('V8.'));
    309     }
    310     this.addResult = function (newResult) {
    311         testResults.push(newResult);
    312         cachedUnit = null;
    313         cachedScalingFactor = null;
    314     }
    315     this.results = function () { return testResults; }
    316     this.scalingFactor = function() {
    317         computeScalingFactorIfNeeded();
    318         return cachedScalingFactor;
    319     }
    320     this.unit = function () {
    321         computeScalingFactorIfNeeded();
    322         return cachedUnit;
    323     }
    324     this.biggerIsBetter = function () {
    325         if (window.unitToBiggerIsBetter == undefined) {
    326             window.unitToBiggerIsBetter = {};
    327             var units = JSON.parse(document.getElementById('units-json').textContent);
    328             for (var u in units) {
    329                 if (units[u].improvement_direction == 'up') {
    330                     window.unitToBiggerIsBetter[u] = true;
    331                 }
    332             }
    333         }
    334         return window.unitToBiggerIsBetter[unit];
    335     }
    336 }
    337 
    338 function UndeleteManager() {
    339     var key = 'telemetry_undeleteIds'
    340     var undeleteIds = localStorage[key];
    341     if (undeleteIds) {
    342         undeleteIds = JSON.parse(undeleteIds);
    343     } else {
    344         undeleteIds = [];
    345     }
    346 
    347     this.ondelete = function(id) {
    348         undeleteIds.push(id);
    349         localStorage[key] = JSON.stringify(undeleteIds);
    350     }
    351     this.undeleteMostRecent = function() {
    352         if (!this.mostRecentlyDeletedId())
    353             return;
    354         undeleteIds.pop();
    355         localStorage[key] = JSON.stringify(undeleteIds);
    356     }
    357     this.mostRecentlyDeletedId = function() {
    358         if (!undeleteIds.length)
    359             return undefined;
    360         return undeleteIds[undeleteIds.length-1];
    361     }
    362 }
    363 var undeleteManager = new UndeleteManager();
    364 
    365 var plotColor = 'rgb(230,50,50)';
    366 var subpointsPlotOptions = {
    367     lines: {show:true, lineWidth: 0},
    368     color: plotColor,
    369     points: {show: true, radius: 1},
    370     bars: {show: false}};
    371 
    372 var mainPlotOptions = {
    373     xaxis: {
    374         min: -0.5,
    375         tickSize: 1,
    376     },
    377     crosshair: { mode: 'y' },
    378     series: { shadowSize: 0 },
    379     bars: {show: true, align: 'center', barWidth: 0.5},
    380     lines: { show: false },
    381     points: { show: true },
    382     grid: {
    383         borderWidth: 1,
    384         borderColor: '#ccc',
    385         backgroundColor: '#fff',
    386         hoverable: true,
    387         autoHighlight: false,
    388     }
    389 };
    390 
    391 var linePlotOptions = {
    392     yaxis: { show: false },
    393     xaxis: { show: false },
    394     lines: { show: true },
    395     grid: { borderWidth: 1, borderColor: '#ccc' },
    396     colors: [ plotColor ]
    397 };
    398 
    399 var largeLinePlotOptions = {
    400     xaxis: {
    401         show: true,
    402         tickDecimals: 0,
    403     },
    404     lines: { show: true },
    405     grid: { borderWidth: 1, borderColor: '#ccc' },
    406     colors: [ plotColor ]
    407 };
    408 
    409 var histogramPlotOptions = {
    410     bars: {show: true, fill: 1}
    411 };
    412 
    413 function createPlot(container, test, useLargeLinePlots) {
    414     if (test.results()[0].histogramValues) {
    415         var section = $('<section><div class="histogram-plots"></div>'
    416             + '<div class="histogram-plot-labels"></div>'
    417             + '<span class="tooltip"></span></section>');
    418         $(container).append(section);
    419         attachHistogramPlots(test, section.children('.histogram-plots'));
    420     }
    421     else if (useLargeLinePlots) {
    422         var section = $('<section><div class="large-line-plots"></div>'
    423             + '<div class="large-line-plot-labels"></div>'
    424             + '<span class="tooltip"></span></section>');
    425         $(container).append(section);
    426         attachLinePlots(test, section.children('.large-line-plots'), useLargeLinePlots);
    427         attachLinePlotLabels(test, section.children('.large-line-plot-labels'));
    428     } else {
    429         var section = $('<section><div class="plot"></div><div class="line-plots"></div>'
    430             + '<span class="tooltip"></span></section>');
    431         section.children('.plot').css({'width': (100 * test.results().length + 25) + 'px', 'height': '300px'});
    432         $(container).append(section);
    433 
    434         var plotContainer = section.children('.plot');
    435         var minIsZero = true;
    436         attachPlot(test, plotContainer, minIsZero);
    437 
    438         attachLinePlots(test, section.children('.line-plots'), useLargeLinePlots);
    439 
    440         var tooltip = section.children('.tooltip');
    441         plotContainer.bind('plothover', function (event, position, item) {
    442             if (item) {
    443                 var postfix = item.series.id ? ' (' + item.series.id + ')' : '';
    444                 tooltip.html(item.datapoint[1].toPrecision(4) + postfix);
    445                 var sectionOffset = $(section).offset();
    446                 tooltip.css({left: item.pageX - sectionOffset.left - tooltip.outerWidth() / 2, top: item.pageY - sectionOffset.top + 10});
    447                 tooltip.fadeIn(200);
    448             } else
    449                 tooltip.hide();
    450         });
    451         plotContainer.mouseout(function () {
    452             tooltip.hide();
    453         });
    454         plotContainer.click(function (event) {
    455             event.preventDefault();
    456             minIsZero = !minIsZero;
    457             attachPlot(test, plotContainer, minIsZero);
    458         });
    459     }
    460     return section;
    461 }
    462 
    463 function attachLinePlots(test, container, useLargeLinePlots) {
    464     var results = test.results();
    465     var attachedPlot = false;
    466 
    467     if (useLargeLinePlots) {
    468         var maximum = 0;
    469         for (var i = 0; i < results.length; i++) {
    470             var values = results[i].values();
    471             if (!values)
    472                 continue;
    473             var local_max = Math.max.apply(Math, values);
    474             if (local_max > maximum)
    475                 maximum = local_max;
    476         }
    477     }
    478 
    479     for (var i = 0; i < results.length; i++) {
    480         container.append('
'
); 481 var values = results[i].values(); 482 if (!values) 483 continue; 484 attachedPlot = true; 485 486 if (useLargeLinePlots) { 487 var options = $.extend(true, {}, largeLinePlotOptions, 488 {yaxis: {min: 0.0, max: maximum}, 489 xaxis: {min: 0.0, max: values.length - 1}, 490 points: {show: (values.length < 2) ? true : false}}); 491 } else { 492 var options = $.extend(true, {}, linePlotOptions, 493 {yaxis: {min: Math.min.apply(Math, values) * 0.9, max: Math.max.apply(Math, values) * 1.1}, 494 xaxis: {min: -0.5, max: values.length - 0.5}, 495 points: {show: (values.length < 2) ? true : false}}); 496 } 497 $.plot(container.children().last(), [values.map(function (value, index) { return [index, value]; })], options); 498 } 499 if (!attachedPlot) 500 container.children().remove(); 501 } 502 503 function attachHistogramPlots(test, container) { 504 var results = test.results(); 505 var attachedPlot = false; 506 507 for (var i = 0; i < results.length; i++) { 508 container.append('<div></div>'); 509 var histogram = results[i].histogramValues 510 if (!histogram) 511 continue; 512 attachedPlot = true; 513 514 var buckets = histogram.buckets 515 var bucket; 516 var max_count = 0; 517 for (var j = 0; j < buckets.length; j++) { 518 bucket = buckets[j]; 519 max_count = Math.max(max_count, bucket.count); 520 } 521 var xmax = bucket.high * 1.1; 522 var ymax = max_count * 1.1; 523 524 var options = $.extend(true, {}, histogramPlotOptions, 525 {yaxis: {min: 0.0, max: ymax}, 526 xaxis: {min: histogram.params.min, max: xmax}}); 527 var plot = $.plot(container.children().last(), [[]], options); 528 // Flot only supports fixed with bars and our histogram's buckets are 529 // variable width, so we need to do our own bar drawing. 530 var ctx = plot.getCanvas().getContext("2d"); 531 ctx.lineWidth="1"; 532 ctx.fillStyle = "rgba(255, 0, 0, 0.2)"; 533 ctx.strokeStyle="red"; 534 for (var j = 0; j < buckets.length; j++) { 535 bucket = buckets[j]; 536 var bl = plot.pointOffset({ x: bucket.low, y: 0}); 537 var tr = plot.pointOffset({ x: bucket.high, y: bucket.count}); 538 ctx.fillRect(bl.left, bl.top, tr.left - bl.left, tr.top - bl.top); 539 ctx.strokeRect(bl.left, bl.top, tr.left - bl.left, tr.top - bl.top); 540 } 541 } 542 if (!attachedPlot) 543 container.children().remove(); 544 } 545 546 function attachLinePlotLabels(test, container) { 547 var results = test.results(); 548 var attachedPlot = false; 549 for (var i = 0; i < results.length; i++) { 550 container.append('<div>' + results[i].run().label() + '</div>'); 551 } 552 } 553 554 function attachPlot(test, plotContainer, minIsZero) { 555 var results = test.results(); 556 557 var values = results.reduce(function (values, result, index) { 558 var newValues = result.values(); 559 return newValues ? values.concat(newValues.map(function (value) { return [index, value]; })) : values; 560 }, []); 561 562 var plotData = [$.extend(true, {}, subpointsPlotOptions, {data: values})]; 563 plotData.push({id: '&mu;', data: results.map(function (result, index) { return [index, result.mean()]; }), color: plotColor}); 564 565 var overallMax = Statistics.max(results.map(function (result, index) { return result.max(); })); 566 var overallMin = Statistics.min(results.map(function (result, index) { return result.min(); })); 567 var margin = (overallMax - overallMin) * 0.1; 568 var currentPlotOptions = $.extend(true, {}, mainPlotOptions, {yaxis: { 569 min: minIsZero ? 0 : overallMin - margin, 570 max: minIsZero ? overallMax * 1.1 : overallMax + margin}}); 571 572 currentPlotOptions.xaxis.max = results.length - 0.5; 573 currentPlotOptions.xaxis.ticks = results.map(function (result, index) { return [index, result.run().label()]; }); 574 575 $.plot(plotContainer, plotData, currentPlotOptions); 576 } 577 578 function toFixedWidthPrecision(value) { 579 var decimal = value.toFixed(2); 580 return decimal; 581 } 582 583 function formatPercentage(fraction) { 584 var percentage = fraction * 100; 585 return (fraction * 100).toFixed(2) + '%'; 586 } 587 588 function createTable(tests, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots) { 589 $('#container').html('<thead><tr><th class="headerSortDown">Test</th><th>Unit</th>' + runs.map(function (run, index) { 590 return '<th colspan="' + (index == referenceIndex ? 2 : 3) + '" class="{sorter: \'comparison\'}" id="' + run.id() + '" title="' + run.description() + '"><span class="label" title="Edit run label">' + run.label() + '</span><div class="closeButton" title="Delete run">x</div></th>'; 591 }).reduce(function (markup, cell) { return markup + cell; }, '') + '</tr></head><tbody></tbody>'); 592 593 var testNames = []; 594 for (testName in tests) 595 testNames.push(testName); 596 597 window.isFirstImportantRow = true; 598 599 testNames.sort().map(function (testName) { 600 var test = tests[testName]; 601 if (test.isMemoryTest() != shouldIgnoreMemory) 602 createTableRow(runs, test, referenceIndex, useLargeLinePlots); 603 }); 604 605 $('.closeButton').click(function(event) { 606 for (var i = 0; i < runs.length; i++) { 607 if (runs[i].id() == event.target.parentNode.id) { 608 runs[i].hide(); 609 undeleteManager.ondelete(runs[i].id()); 610 location.reload(); 611 break; 612 } 613 } 614 event.stopPropagation(); 615 }); 616 617 $('.label').click(function(event) { 618 for (var i = 0; i < runs.length; i++) { 619 if (runs[i].id() == event.target.parentNode.id) { 620 $(event.target).replaceWith('<input id="labelEditor" type="text" value="' + runs[i].label() + '">'); 621 $('#labelEditor').focusout(function() { 622 runs[i].setLabel(this.value); 623 location.reload(); 624 }); 625 $('#labelEditor').keypress(function(event) { 626 if (event.which == 13) { 627 runs[i].setLabel(this.value); 628 location.reload(); 629 } 630 }); 631 $('#labelEditor').click(function (event) { 632 event.stopPropagation(); 633 }); 634 $('#labelEditor').mousedown(function (event) { 635 event.stopPropagation(); 636 }); 637 $('#labelEditor').select(); 638 break; 639 } 640 } 641 event.stopPropagation(); 642 }); 643 644 $('#container').tablesorter({widgets: ['zebra']}); 645 } 646 647 function linearRegression(points) { 648 // Implement http://www.easycalculation.com/statistics/learn-correlation.php. 649 // x = magnitude 650 // y = iterations 651 var sumX = 0; 652 var sumY = 0; 653 var sumXSquared = 0; 654 var sumYSquared = 0; 655 var sumXTimesY = 0; 656 657 for (var i = 0; i < points.length; i++) { 658 var x = i; 659 var y = points[i]; 660 sumX += x; 661 sumY += y; 662 sumXSquared += x * x; 663 sumYSquared += y * y; 664 sumXTimesY += x * y; 665 } 666 667 var r = (points.length * sumXTimesY - sumX * sumY) / 668 Math.sqrt((points.length * sumXSquared - sumX * sumX) * 669 (points.length * sumYSquared - sumY * sumY)); 670 671 if (isNaN(r) || r == Math.Infinity) 672 r = 0; 673 674 var slope = (points.length * sumXTimesY - sumX * sumY) / (points.length * sumXSquared - sumX * sumX); 675 var intercept = sumY / points.length - slope * sumX / points.length; 676 return {slope: slope, intercept: intercept, rSquared: r * r}; 677 } 678 679 var warningSign = '' 680 + '' 681 + '' 682 + '' 683 + ''; 684 685 function createTableRow(runs, test, referenceIndex, useLargeLinePlots) { 686 var tableRow = $(' + (test.isImportant ? ' style="font-weight:bold"' : '') + '>' + test.name() + '' + test.unit() + ''); 687 688 function markupForRun(result, referenceResult) { 689 var comparisonCell = ''; 690 var hiddenValue = ''; 691 var shouldCompare = result !== referenceResult; 692 if (shouldCompare && referenceResult) { 693 var percentDifference = referenceResult.percentDifference(result); 694 var better = test.biggerIsBetter() ? percentDifference > 0 : percentDifference < 0; 695 var comparison = ''; 696 var className = 'comparison'; 697 if (referenceResult.isStatisticallySignificant(result)) { 698 comparison = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse&nbsp;'); 699 className += better ? ' better' : ' worse'; 700 } 701 hiddenValue = '|' + comparison + ''; 702 comparisonCell = 'className + '">' + comparison + ''; 703 } else if (shouldCompare) 704 comparisonCell = ''; 705 706 var values = result.values(); 707 var warning = ''; 708 var regressionAnalysis = ''; 709 if (result.histogramValues) { 710 // Don't calculate regression result for histograms. 711 } 712 else if (values && values.length > 3) { 713 regressionResult = linearRegression(values); 714 regressionAnalysis = 'slope=' + toFixedWidthPrecision(regressionResult.slope) 715 + ', R^2=' + toFixedWidthPrecision(regressionResult.rSquared); 716 if (regressionResult.rSquared > 0.6 && Math.abs(regressionResult.slope) > 0.01) { 717 warning = ' <span class="regression-warning" title="Detected a time dependency with ' + regressionAnalysis + '">' + warningSign + ' </span>'; 718 } 719 } 720 721 var statistics = '&sigma;=' + toFixedWidthPrecision(result.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result.min()) 722 + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysis; 723 724 // Tablesorter doesn't know about the second cell so put the comparison in the invisible element. 725 return '<td class="result" title="' + statistics + '">' + toFixedWidthPrecision(result.mean()) + hiddenValue 726 + '</td><td class="confidenceIntervalDelta" title="' + statistics + '">&plusmn; ' 727 + formatPercentage(result.confidenceIntervalDeltaRatio()) + warning + '</td>' + comparisonCell; 728 } 729 730 function markupForMissingRun(isReference) { 731 return '<td colspan="' + (isReference ? 2 : 3) + '" class="missing">Missing</td>'; 732 } 733 734 var runIndex = 0; 735 var results = test.results(); 736 var referenceResult = undefined; 737 var resultIndexMap = {}; 738 for (var i = 0; i < results.length; i++) { 739 while (runs[runIndex] !== results[i].run()) 740 runIndex++; 741 if (runIndex == referenceIndex) 742 referenceResult = results[i]; 743 resultIndexMap[runIndex] = i; 744 } 745 for (var i = 0; i < runs.length; i++) { 746 var resultIndex = resultIndexMap[i]; 747 if (resultIndex == undefined) 748 tableRow.append(markupForMissingRun(i == referenceIndex)); 749 else 750 tableRow.append(markupForRun(results[resultIndex], referenceResult)); 751 } 752 753 $('#container').children('tbody').last().append(tableRow); 754 755 function toggle() { 756 var firstCell = tableRow.children('td').first(); 757 if (firstCell.children('section').length) { 758 firstCell.children('section').remove(); 759 tableRow.children('td').css({'padding-bottom': ''}); 760 tableRow.children('td').first().addClass('collapsed'); 761 tableRow.children('td').first().removeClass('expanded'); 762 } else { 763 var plot = createPlot(firstCell, test, useLargeLinePlots); 764 plot.css({'position': 'absolute', 'z-index': 2}); 765 var offset = tableRow.offset(); 766 offset.left += 1; 767 offset.top += tableRow.outerHeight(); 768 plot.offset(offset); 769 tableRow.children('td').css({'padding-bottom': plot.outerHeight() + 5}); 770 tableRow.children('td').first().removeClass('collapsed'); 771 tableRow.children('td').first().addClass('expanded'); 772 } 773 774 return false; 775 }; 776 777 tableRow.click(function(event) { 778 if (event.target != tableRow[0] && event.target.parentNode != tableRow[0]) 779 return; 780 781 event.preventDefault(); 782 783 toggle(); 784 }); 785 786 if (test.isImportant && window.isFirstImportantRow) { 787 window.isFirstImportantRow = false; 788 toggle(); 789 } 790 } 791 792 function init() { 793 $.tablesorter.addParser({ 794 id: 'comparison', 795 is: function(s) { 796 return s.indexOf('|') >= 0; 797 }, 798 format: function(s) { 799 var parsed = parseFloat(s.substring(s.indexOf('|') + 1)); 800 return isNaN(parsed) ? 0 : parsed; 801 }, 802 type: 'numeric', 803 }); 804 805 var runs = []; 806 var metrics = {}; 807 var deletedRunsById = {}; 808 $.each(JSON.parse(document.getElementById('results-json').textContent), function (index, entry) { 809 var run = new TestRun(entry); 810 if (run.isHidden()) { 811 deletedRunsById[run.id()] = run; 812 return; 813 } 814 815 runs.push(run); 816 817 function addTests(tests) { 818 for (var testName in tests) { 819 var rawMetrics = tests[testName].metrics; 820 821 for (var metricName in rawMetrics) { 822 var fullMetricName = testName + ':' + metricName; 823 var metric = metrics[fullMetricName]; 824 if (!metric) { 825 metric = new PerfTestMetric(testName, metricName, rawMetrics[metricName].units, rawMetrics[metricName].important); 826 metrics[fullMetricName] = metric; 827 } 828 metric.addResult(new TestResult(metric, rawMetrics[metricName].current, run)); 829 } 830 } 831 } 832 833 addTests(entry.tests); 834 }); 835 836 var useLargeLinePlots = false; 837 var shouldIgnoreMemory= true; 838 var referenceIndex = 0; 839 840 createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); 841 842 $('#time-memory').bind('change', function (event, checkedElement) { 843 shouldIgnoreMemory = checkedElement.textContent == 'Time'; 844 createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); 845 }); 846 847 $('#scatter-line').bind('change', function (event, checkedElement) { 848 useLargeLinePlots = checkedElement.textContent == 'Line'; 849 createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); 850 }); 851 852 runs.map(function (run, index) { 853 $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + ' title="' + run.description() + '">' + run.label() + '</span>'); 854 }) 855 856 $('#reference').bind('change', function (event, checkedElement) { 857 referenceIndex = parseInt(checkedElement.getAttribute('value')); 858 createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); 859 }); 860 861 $('.checkbox').each(function (index, checkbox) { 862 $(checkbox).children('span').click(function (event) { 863 if ($(this).hasClass('checked')) 864 return; 865 $(checkbox).children('span').removeClass('checked'); 866 $(this).addClass('checked'); 867 $(checkbox).trigger('change', $(this)); 868 }); 869 }); 870 871 if (undeleteManager.mostRecentlyDeletedId()) { 872 $('#undelete').html('Undelete ' + deletedRunsById[undeleteManager.mostRecentlyDeletedId()].label()); 873 $('#undelete').attr('title', deletedRunsById[undeleteManager.mostRecentlyDeletedId()].description()); 874 $('#undelete').click(function (event) { 875 deletedRunsById[undeleteManager.mostRecentlyDeletedId()].show(); 876 undeleteManager.undeleteMostRecent(); 877 location.reload(); 878 }); 879 } else { 880 $('#undelete').hide(); 881 } 882 } 883 884 </script> 885 <script id="results-json" type="application/json">%json_results%</script> 886 <script id="units-json" type="application/json">%json_units%</script> 887 </body> 888 </html> 889