4 <title>Telemetry Performance Test Results
</title>
5 <style type=
"text/css">
15 content: '\25B8\00A0';
20 content: '\25BE\00A0';
28 display: inline-block
;
38 .large-line-plots > div, .histogram-plots > div {
39 display: inline-block
;
45 .large-line-plot-labels > div, .histogram-plot-labels > div {
46 display: inline-block
;
56 display: inline-block
;
58 background: linear-gradient
(rgb
(220, 220, 220), rgb
(255, 255, 255));
59 border: inset
1px #ddd;
63 -webkit-user-select: none
;
96 font-family: sans-serif
;
105 border-collapse: collapse
;
119 .importantNestedRow {
135 background: linear-gradient
(rgb
(244, 244, 244), rgb
(217, 217, 217));
136 border: 1px solid
#ccc;
147 td
.comparison
, td
.result
{
178 td
.missingReference
{
185 display: inline-block
;
187 background: linear-gradient
(rgb
(220, 220, 220), rgb
(200, 200, 200));
188 border: inset
1px #ddd;
194 -webkit-user-select: none
;
199 display: inline-block
;
202 border: outset
1px transparent
;
207 background: linear-gradient
(rgb
(255, 255, 255), rgb
(235, 235, 235));
208 border: outset
1px #eee;
213 display: inline-block
;
216 background: linear-gradient
(rgb
(220, 220, 220), rgb
(255, 255, 255));
217 border: inset
1px #ddd;
221 -webkit-user-select: none
;
226 .openAllButton:hover {
231 display: inline-block
;
234 background: linear-gradient
(rgb
(220, 220, 220),rgb
(255, 255, 255));
235 border: inset
1px #ddd;
239 -webkit-user-select: none
;
244 .closeAllButton:hover {
250 <body onload=
"init()">
251 <div style=
"padding: 0 10px; white-space: nowrap;">
252 Result
<span id=
"time-memory" class=
"checkbox"><span class=
"checked">Time
</span><span>Memory
</span></span>
253 Reference
<span id=
"reference" class=
"checkbox"></span>
254 Style
<span id=
"scatter-line" class=
"checkbox"><span class=
"checked">Scatter
</span><span>Line
</span></span>
255 <span class=
"checkbox"><span class=
"checked" id=
"undelete">Undelete
</span></span><br>
256 Run your test with --reset-results to clear all runs
258 <table id=
"container"></table>
259 <script src=
"https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
266 var COLLAPSED
= false;
267 var SMALLEST_PERCENT_DISPLAYED
= 0.01;
268 var INVISIBLE
= false;
270 var COMPARISON_SUFFIX
= '_compare';
271 var SORT_DOWN_CLASS
= 'sortDown';
272 var SORT_UP_CLASS
= 'sortUp';
273 var BETTER_CLASS
= 'better';
274 var WORSE_CLASS
= 'worse';
275 var UNKNOWN_CLASS
= 'unknown'
276 // px Indentation for graphs
277 var GRAPH_INDENT
= 64;
278 var PADDING_UNDER_GRAPH
= 5;
279 // px Indentation for nested children left-margins
280 var INDENTATION
= 40;
282 function TestResult(metric
, values
, associatedRun
, std
, degreesOfFreedom
) {
284 if (values
[0] instanceof Array
) {
285 var flattenedValues
= [];
286 for (var i
= 0; i
< values
.length
; i
++)
287 flattenedValues
= flattenedValues
.concat(values
[i
]);
288 values
= flattenedValues
;
291 if (jQuery
.type(values
[0]) === 'string') {
293 var current
= JSON
.parse(values
[0]);
294 if (current
.params
.type
=== 'HISTOGRAM') {
295 this.histogramValues
= current
;
296 // Histogram results have no values (per se). Instead we calculate
297 // the values from the histogram bins.
299 var buckets
= current
.buckets
300 for (var i
= 0; i
< buckets
.length
; i
++) {
301 var bucket
= buckets
[i
];
302 var bucket_mean
= (bucket
.high
+ bucket
.low
) / 2;
303 for (var b
= 0; b
< bucket
.count
; b
++) {
304 values
.push(bucket_mean
);
310 console
.error(e
, e
.stack
);
317 this.test = function () { return metric
; }
318 this.values = function () { return values
.map(function (value
) { return metric
.scalingFactor() * value
; }); }
319 this.unscaledMean = function () { return Statistics
.sum(values
) / values
.length
; }
320 this.mean = function () { return metric
.scalingFactor() * this.unscaledMean(); }
321 this.min = function () { return metric
.scalingFactor() * Statistics
.min(values
); }
322 this.max = function () { return metric
.scalingFactor() * Statistics
.max(values
); }
323 this.confidenceIntervalDelta = function () {
324 if (std
!== undefined) {
325 return metric
.scalingFactor() * Statistics
.confidenceIntervalDeltaFromStd(0.95, values
.length
,
326 std
, degreesOfFreedom
);
328 return metric
.scalingFactor() * Statistics
.confidenceIntervalDelta(0.95, values
.length
,
329 Statistics
.sum(values
), Statistics
.squareSum(values
));
331 this.confidenceIntervalDeltaRatio = function () { return this.confidenceIntervalDelta() / this.mean(); }
332 this.percentDifference = function(other
) {
333 if (other
=== undefined) {
336 return (other
.unscaledMean() - this.unscaledMean()) / this.unscaledMean();
338 this.isStatisticallySignificant = function (other
) {
339 if (other
=== undefined) {
342 var diff
= Math
.abs(other
.mean() - this.mean());
343 return diff
> this.confidenceIntervalDelta() && diff
> other
.confidenceIntervalDelta();
345 this.run = function () { return associatedRun
; }
348 function TestRun(entry
) {
349 this.id = function() { return entry
['buildTime'].replace(/[:.-]/g,''); }
350 this.label = function () {
351 if (labelKey
in localStorage
)
352 return localStorage
[labelKey
];
353 return entry
['label'];
355 this.setLabel = function(label
) { localStorage
[labelKey
] = label
; }
356 this.isHidden = function() { return localStorage
[hiddenKey
]; }
357 this.hide = function() { localStorage
[hiddenKey
] = true; }
358 this.show = function() { localStorage
.removeItem(hiddenKey
); }
359 this.description = function() {
360 return new Date(entry
['buildTime']).toLocaleString() + '\n' + entry
['platform'] + ' ' + this.label();
363 var labelKey
= 'telemetry_label_' + this.id();
364 var hiddenKey
= 'telemetry_hide_' + this.id();
367 function PerfTestMetric(name
, metric
, unit
, isImportant
) {
368 var testResults
= [];
369 var cachedUnit
= null;
370 var cachedScalingFactor
= null;
372 // We can't do this in TestResult because all results for each test need to share the same unit and the same scaling factor.
373 function computeScalingFactorIfNeeded() {
374 // FIXME: We shouldn't be adjusting units on every test result.
375 // We can only do this on the first test.
376 if (!testResults
.length
|| cachedUnit
)
379 var mean
= testResults
[0].unscaledMean(); // FIXME: We should look at all values.
380 var kilo
= unit
== 'bytes' ? 1024 : 1000;
381 if (mean
> 10 * kilo
* kilo
&& unit
!= 'ms') {
382 cachedScalingFactor
= 1 / kilo
/ kilo
;
383 cachedUnit
= 'M ' + unit
;
384 } else if (mean
> 10 * kilo
) {
385 cachedScalingFactor
= 1 / kilo
;
386 cachedUnit
= unit
== 'ms' ? 's' : ('K ' + unit
);
388 cachedScalingFactor
= 1;
393 this.name = function () { return name
+ ':' + metric
; }
394 this.isImportant
= isImportant
;
395 this.isMemoryTest = function () {
396 return (unit
== 'kb' ||
401 !metric
.indexOf('V8.'));
403 this.addResult = function (newResult
) {
404 testResults
.push(newResult
);
406 cachedScalingFactor
= null;
408 this.results = function () { return testResults
; }
409 this.scalingFactor = function() {
410 computeScalingFactorIfNeeded();
411 return cachedScalingFactor
;
413 this.unit = function () {
414 computeScalingFactorIfNeeded();
417 this.biggerIsBetter = function () {
418 if (window
.unitToBiggerIsBetter
== undefined) {
419 window
.unitToBiggerIsBetter
= {};
420 var units
= JSON
.parse(document
.getElementById('units-json').textContent
);
421 for (var u
in units
) {
422 if (units
[u
].improvement_direction
== 'up') {
423 window
.unitToBiggerIsBetter
[u
] = true;
427 return window
.unitToBiggerIsBetter
[unit
];
431 function UndeleteManager() {
432 var key
= 'telemetry_undeleteIds'
433 var undeleteIds
= localStorage
[key
];
435 undeleteIds
= JSON
.parse(undeleteIds
);
440 this.ondelete = function(id
) {
441 undeleteIds
.push(id
);
442 localStorage
[key
] = JSON
.stringify(undeleteIds
);
444 this.undeleteMostRecent = function() {
445 if (!this.mostRecentlyDeletedId())
448 localStorage
[key
] = JSON
.stringify(undeleteIds
);
450 this.mostRecentlyDeletedId = function() {
451 if (!undeleteIds
.length
)
453 return undeleteIds
[undeleteIds
.length
-1];
456 var undeleteManager
= new UndeleteManager();
458 var plotColor
= 'rgb(230,50,50)';
459 var subpointsPlotOptions
= {
460 lines
: {show
:true, lineWidth
: 0},
462 points
: {show
: true, radius
: 1},
463 bars
: {show
: false}};
465 var mainPlotOptions
= {
470 crosshair
: { mode
: 'y' },
471 series
: { shadowSize
: 0 },
472 bars
: {show
: true, align
: 'center', barWidth
: 0.5},
473 lines
: { show
: false },
474 points
: { show
: true },
478 backgroundColor
: '#fff',
480 autoHighlight
: false,
484 var linePlotOptions
= {
485 yaxis
: { show
: false },
486 xaxis
: { show
: false },
487 lines
: { show
: true },
488 grid
: { borderWidth
: 1, borderColor
: '#ccc' },
489 colors
: [ plotColor
]
492 var largeLinePlotOptions
= {
497 lines
: { show
: true },
498 grid
: { borderWidth
: 1, borderColor
: '#ccc' },
499 colors
: [ plotColor
]
502 var histogramPlotOptions
= {
503 bars
: {show
: true, fill
: 1}
506 function createPlot(container
, test
, useLargeLinePlots
) {
507 if (test
.results()[0].histogramValues
) {
508 var section
= $('<section><div class="histogram-plots"></div>'
509 + '<div class="histogram-plot-labels"></div>'
510 + '<span class="tooltip"></span></section>');
511 $(container
).append(section
);
512 attachHistogramPlots(test
, section
.children('.histogram-plots'));
514 else if (useLargeLinePlots
) {
515 var section
= $('<section><div class="large-line-plots"></div>'
516 + '<div class="large-line-plot-labels"></div>'
517 + '<span class="tooltip"></span></section>');
518 $(container
).append(section
);
519 attachLinePlots(test
, section
.children('.large-line-plots'), useLargeLinePlots
);
520 attachLinePlotLabels(test
, section
.children('.large-line-plot-labels'));
522 var section
= $('<section><div class="plot"></div><div class="line-plots"></div>'
523 + '<span class="tooltip"></span></section>');
524 section
.children('.plot').css({'width': (100 * test
.results().length
+ 25) + 'px', 'height': '300px'});
525 $(container
).append(section
);
527 var plotContainer
= section
.children('.plot');
528 var minIsZero
= true;
529 attachPlot(test
, plotContainer
, minIsZero
);
531 attachLinePlots(test
, section
.children('.line-plots'), useLargeLinePlots
);
533 var tooltip
= section
.children('.tooltip');
534 plotContainer
.bind('plothover', function (event
, position
, item
) {
536 var postfix
= item
.series
.id
? ' (' + item
.series
.id
+ ')' : '';
537 tooltip
.html(item
.datapoint
[1].toPrecision(4) + postfix
);
538 var sectionOffset
= $(section
).offset();
539 tooltip
.css({left
: item
.pageX
- sectionOffset
.left
- tooltip
.outerWidth() / 2, top
: item
.pageY
- sectionOffset
.top
+ 10});
544 plotContainer
.mouseout(function () {
547 plotContainer
.click(function (event
) {
548 event
.preventDefault();
549 minIsZero
= !minIsZero
;
550 attachPlot(test
, plotContainer
, minIsZero
);
556 function attachLinePlots(test
, container
, useLargeLinePlots
) {
557 var results
= test
.results();
558 var attachedPlot
= false;
560 if (useLargeLinePlots
) {
562 for (var i
= 0; i
< results
.length
; i
++) {
563 var values
= results
[i
].values();
566 var local_max
= Math
.max
.apply(Math
, values
);
567 if (local_max
> maximum
)
572 for (var i
= 0; i
< results
.length
; i
++) {
573 container
.append('<div></div>');
574 var values
= results
[i
].values();
579 if (useLargeLinePlots
) {
580 var options
= $.extend(true, {}, largeLinePlotOptions
,
581 {yaxis
: {min
: 0.0, max
: maximum
},
582 xaxis
: {min
: 0.0, max
: values
.length
- 1},
583 points
: {show
: (values
.length
< 2) ? true : false}});
585 var options
= $.extend(true, {}, linePlotOptions
,
586 {yaxis
: {min
: Math
.min
.apply(Math
, values
) * 0.9, max
: Math
.max
.apply(Math
, values
) * 1.1},
587 xaxis
: {min
: -0.5, max
: values
.length
- 0.5},
588 points
: {show
: (values
.length
< 2) ? true : false}});
590 $.plot(container
.children().last(), [values
.map(function (value
, index
) { return [index
, value
]; })], options
);
593 container
.children().remove();
596 function attachHistogramPlots(test
, container
) {
597 var results
= test
.results();
598 var attachedPlot
= false;
600 for (var i
= 0; i
< results
.length
; i
++) {
601 container
.append('<div></div>');
602 var histogram
= results
[i
].histogramValues
607 var buckets
= histogram
.buckets
610 for (var j
= 0; j
< buckets
.length
; j
++) {
612 max_count
= Math
.max(max_count
, bucket
.count
);
614 var xmax
= bucket
.high
* 1.1;
615 var ymax
= max_count
* 1.1;
617 var options
= $.extend(true, {}, histogramPlotOptions
,
618 {yaxis
: {min
: 0.0, max
: ymax
},
619 xaxis
: {min
: histogram
.params
.min
, max
: xmax
}});
620 var plot
= $.plot(container
.children().last(), [[]], options
);
621 // Flot only supports fixed with bars and our histogram's buckets are
622 // variable width, so we need to do our own bar drawing.
623 var ctx
= plot
.getCanvas().getContext("2d");
625 ctx
.fillStyle
= "rgba(255, 0, 0, 0.2)";
626 ctx
.strokeStyle
="red";
627 for (var j
= 0; j
< buckets
.length
; j
++) {
629 var bl
= plot
.pointOffset({ x
: bucket
.low
, y
: 0});
630 var tr
= plot
.pointOffset({ x
: bucket
.high
, y
: bucket
.count
});
631 ctx
.fillRect(bl
.left
, bl
.top
, tr
.left
- bl
.left
, tr
.top
- bl
.top
);
632 ctx
.strokeRect(bl
.left
, bl
.top
, tr
.left
- bl
.left
, tr
.top
- bl
.top
);
636 container
.children().remove();
639 function attachLinePlotLabels(test
, container
) {
640 var results
= test
.results();
641 var attachedPlot
= false;
642 for (var i
= 0; i
< results
.length
; i
++) {
643 container
.append('<div>' + results
[i
].run().label() + '</div>');
647 function attachPlot(test
, plotContainer
, minIsZero
) {
648 var results
= test
.results();
650 var values
= results
.reduce(function (values
, result
, index
) {
651 var newValues
= result
.values();
652 return newValues
? values
.concat(newValues
.map(function (value
) { return [index
, value
]; })) : values
;
655 var plotData
= [$.extend(true, {}, subpointsPlotOptions
, {data
: values
})];
656 plotData
.push({id
: 'μ', data
: results
.map(function (result
, index
) { return [index
, result
.mean()]; }), color
: plotColor
});
658 var overallMax
= Statistics
.max(results
.map(function (result
, index
) { return result
.max(); }));
659 var overallMin
= Statistics
.min(results
.map(function (result
, index
) { return result
.min(); }));
660 var margin
= (overallMax
- overallMin
) * 0.1;
661 var currentPlotOptions
= $.extend(true, {}, mainPlotOptions
, {yaxis
: {
662 min
: minIsZero
? 0 : overallMin
- margin
,
663 max
: minIsZero
? overallMax
* 1.1 : overallMax
+ margin
}});
665 currentPlotOptions
.xaxis
.max
= results
.length
- 0.5;
666 currentPlotOptions
.xaxis
.ticks
= results
.map(function (result
, index
) { return [index
, result
.run().label()]; });
668 $.plot(plotContainer
, plotData
, currentPlotOptions
);
671 function toFixedWidthPrecision(value
) {
672 var decimal = value
.toFixed(2);
676 function formatPercentage(fraction
) {
677 var percentage
= fraction
* 100;
678 return (fraction
* 100).toFixed(2) + '%';
681 function setUpSortClicks(runs
)
683 $('#nameColumn').click(sortByName
);
685 $('#unitColumn').click(sortByUnit
);
687 runs
.forEach(function(run
) {
688 $('#' + run
.id()).click(sortByResult
);
689 $('#' + run
.id() + COMPARISON_SUFFIX
).click(sortByReference
);
696 function createTable(tests
, runs
, shouldIgnoreMemory
, referenceIndex
, useLargeLinePlots
) {
697 var resultHeaders
= runs
.map(function (run
, index
) {
698 var header
= '<th id="' + run
.id() + '" ' +
700 'title="' + run
.description() + '">' +
701 '<span class="label" ' +
702 'title="Edit run label">' +
705 '<div class="closeButton" ' +
706 'title="Delete run">' +
710 if (index
!== referenceIndex
) {
711 header
+= '<th id="' + run
.id() + COMPARISON_SUFFIX
+ '" ' +
712 'title="Sort by better/worse">' +
719 resultHeaders
= resultHeaders
.join('');
721 htmlString
= '<thead>' +
723 '<th id="nameColumn">' +
724 '<div class="openAllButton" ' +
725 'title="Open all rows or graphs">' +
728 '<div class="closeAllButton" ' +
729 'title="Close all rows">' +
734 '<th id="unitColumn">' +
743 $('#container').html(htmlString
);
746 for (testName
in tests
)
747 testNames
.push(testName
);
750 testNames
.forEach(function(testName
) {
751 var test
= tests
[testName
];
752 if (test
.isMemoryTest() === shouldIgnoreMemory
) {
755 allTableRows
.push(new TableRow(runs
, test
, referenceIndex
, useLargeLinePlots
));
758 // Build a list of top level rows with attached children
760 allTableRows
.forEach(function(row
) {
761 // Add us to top level if we are a top-level row...
763 topLevelRows
.push(row
);
764 // Add a duplicate child row that holds the graph for the parent
765 var graphHolder
= new TableRow(runs
, row
.test
, referenceIndex
, useLargeLinePlots
);
766 graphHolder
.isImportant
= true;
767 graphHolder
.URL
= 'Summary';
768 graphHolder
.hideRowData();
769 allTableRows
.push(graphHolder
);
770 row
.addNestedChild(graphHolder
);
774 // ...or add us to our parent if we have one ...
775 for (var i
= 0; i
< allTableRows
.length
; i
++) {
776 if (allTableRows
[i
].isParentOf(row
)) {
777 allTableRows
[i
].addNestedChild(row
);
782 // ...otherwise this result is orphaned, display it at top level with a graph
784 topLevelRows
.push(row
);
787 buildTable(topLevelRows
);
789 $('.closeButton').click(function(event
) {
790 for (var i
= 0; i
< runs
.length
; i
++) {
791 if (runs
[i
].id() == event
.target
.parentNode
.id
) {
793 undeleteManager
.ondelete(runs
[i
].id());
798 event
.stopPropagation();
801 $('.closeAllButton').click(function(event
) {
802 for (var i
= 0; i
< allTableRows
.length
; i
++) {
803 allTableRows
[i
].closeRow();
805 event
.stopPropagation();
808 $('.openAllButton').click(function(event
) {
809 for (var i
= 0; i
< topLevelRows
.length
; i
++) {
810 topLevelRows
[i
].openRow();
812 event
.stopPropagation();
815 setUpSortClicks(runs
);
817 $('.label').click(function(event
) {
818 for (var i
= 0; i
< runs
.length
; i
++) {
819 if (runs
[i
].id() == event
.target
.parentNode
.id
) {
820 $(event
.target
).replaceWith('<input id="labelEditor" type="text" value="' + runs
[i
].label() + '">');
821 $('#labelEditor').focusout(function() {
822 runs
[i
].setLabel(this.value
);
825 $('#labelEditor').keypress(function(event
) {
826 if (event
.which
== 13) {
827 runs
[i
].setLabel(this.value
);
831 $('#labelEditor').click(function (event
) {
832 event
.stopPropagation();
834 $('#labelEditor').mousedown(function (event
) {
835 event
.stopPropagation();
837 $('#labelEditor').select();
841 event
.stopPropagation();
845 function validForSorting(row
) {
846 return ($.type(row
.sortValue
) === 'string') || !isNaN(row
.sortValue
);
849 var sortDirection
= 1;
851 function sortRows(rows
) {
853 function(rowA
,rowB
) {
854 if (validForSorting(rowA
) !== validForSorting(rowB
)) {
855 // Sort valid values upwards when compared to invalid
856 if (validForSorting(rowA
)) {
859 if (validForSorting(rowB
)) {
864 // Some rows always sort to the top
865 if (rowA
.isImportant
) {
868 if (rowB
.isImportant
) {
872 if (rowA
.sortValue
=== rowB
.sortValue
) {
873 // Sort identical values by name to keep the sort stable,
874 // always keep name alphabetical (even if a & b sort values
876 return rowA
.test
.name() > rowB
.test
.name() ? 1 : -1;
879 return rowA
.sortValue
> rowB
.sortValue
? sortDirection
: -sortDirection
;
882 // Sort the rows' children
883 rows
.forEach(function(row
) {
884 sortRows(row
.children
);
888 function buildTable(rows
) {
889 rows
.forEach(function(row
) {
890 row
.removeFromPage();
895 rows
.forEach(function(row
) {
900 var activeSortHeaderElement
= undefined;
901 var columnSortDirection
= {};
903 function determineColumnSortDirection(element
) {
904 columnDirection
= columnSortDirection
[element
.id
];
906 if (columnDirection
=== undefined) {
907 // First time we've sorted this row, default to down
908 columnSortDirection
[element
.id
] = SORT_DOWN_CLASS
;
909 } else if (element
=== activeSortHeaderElement
) {
910 // Clicking on same header again, swap direction
911 columnSortDirection
[element
.id
] = (columnDirection
=== SORT_UP_CLASS
) ? SORT_DOWN_CLASS
: SORT_UP_CLASS
;
915 function updateSortDirection(element
) {
916 // Remove old header's sort arrow
917 if (activeSortHeaderElement
!== undefined) {
918 activeSortHeaderElement
.classList
.remove(columnSortDirection
[activeSortHeaderElement
.id
]);
921 determineColumnSortDirection(element
);
923 sortDirection
= (columnSortDirection
[element
.id
] === SORT_UP_CLASS
) ? 1 : -1;
925 // Add new header's sort arrow
926 element
.classList
.add(columnSortDirection
[element
.id
]);
927 activeSortHeaderElement
= element
;
930 function sortByName(event
) {
931 updateSortDirection(event
.toElement
);
933 allTableRows
.forEach(function(row
) {
934 row
.prepareToSortByName();
937 buildTable(topLevelRows
);
940 function sortByUnit(event
) {
941 updateSortDirection(event
.toElement
);
943 allTableRows
.forEach(function(row
) {
944 row
.prepareToSortByUnit();
947 buildTable(topLevelRows
);
950 function sortByResult(event
) {
951 updateSortDirection(event
.toElement
);
953 var runId
= event
.target
.id
;
955 allTableRows
.forEach(function(row
) {
956 row
.prepareToSortByTestResults(runId
);
959 buildTable(topLevelRows
);
962 function sortByReference(event
) {
963 updateSortDirection(event
.toElement
);
965 // The element ID has _compare appended to allow us to set up a click event
966 // remove the _compare to return a useful Id
967 var runIdWithCompare
= event
.target
.id
;
968 var runId
= runIdWithCompare
.split('_')[0];
970 allTableRows
.forEach(function(row
) {
971 row
.prepareToSortRelativeToReference(runId
);
974 buildTable(topLevelRows
);
977 function linearRegression(points
) {
978 // Implement http://www.easycalculation.com/statistics/learn-correlation.php.
987 for (var i
= 0; i
< points
.length
; i
++) {
992 sumXSquared
+= x
* x
;
993 sumYSquared
+= y
* y
;
997 var r
= (points
.length
* sumXTimesY
- sumX
* sumY
) /
998 Math
.sqrt((points
.length
* sumXSquared
- sumX
* sumX
) *
999 (points
.length
* sumYSquared
- sumY
* sumY
));
1001 if (isNaN(r
) || r
== Math
.Infinity
)
1004 var slope
= (points
.length
* sumXTimesY
- sumX
* sumY
) / (points
.length
* sumXSquared
- sumX
* sumX
);
1005 var intercept
= sumY
/ points
.length
- slope
* sumX
/ points
.length
;
1006 return {slope
: slope
, intercept
: intercept
, rSquared
: r
* r
};
1009 var warningSign
= '<svg viewBox="0 0 100 100" style="width: 18px; height: 18px; vertical-align: bottom;" version="1.1">'
1010 + '<polygon fill="red" points="50,10 90,80 10,80 50,10" stroke="red" stroke-width="10" stroke-linejoin="round" />'
1011 + '<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" />'
1012 + '<circle cx="50" cy="73" r="6" fill="white" />'
1015 function TableRow(runs
, test
, referenceIndex
, useLargeLinePlots
) {
1018 this.referenceIndex
= referenceIndex
;
1019 this.useLargeLinePlots
= useLargeLinePlots
;
1022 this.tableRow
= $('<tr class="highlight">' +
1023 '<td class="test collapsed" >' +
1026 '<td class="unit">' +
1032 var results
= this.test
.results();
1033 var referenceResult
= undefined;
1035 this.resultIndexMap
= {};
1036 for (var i
= 0; i
< results
.length
; i
++) {
1037 while (this.runs
[runIndex
] !== results
[i
].run())
1039 if (runIndex
=== this.referenceIndex
)
1040 referenceResult
= results
[i
];
1041 this.resultIndexMap
[runIndex
] = i
;
1043 for (var i
= 0; i
< this.runs
.length
; i
++) {
1044 var resultIndex
= this.resultIndexMap
[i
];
1045 if (resultIndex
=== undefined)
1046 this.tableRow
.append(this.markupForMissingRun(i
== this.referenceIndex
));
1048 this.tableRow
.append(this.markupForRun(results
[resultIndex
], referenceResult
));
1051 // Use the test name (without URL) to bind parents and their children
1052 var nameAndURL
= this.test
.name().split('.');
1053 var benchmarkName
= nameAndURL
.shift();
1054 this.testName
= nameAndURL
.shift();
1055 this.hasNoURL
= (nameAndURL
.length
=== 0);
1057 if (!this.hasNoURL
) {
1059 this.URL
= nameAndURL
.join('.');
1062 this.isImportant
= false;
1063 this.hasGraph
= false;
1064 this.currentIndentationClass
= ''
1065 this.indentLevel
= 0;
1066 this.setRowNestedState(COLLAPSED
);
1067 this.setVisibility(VISIBLE
);
1068 this.prepareToSortByName();
1071 TableRow
.prototype.hideRowData = function() {
1072 data
= this.tableRow
.children('td');
1074 for (index
in data
) {
1076 // Blank out everything except the test name
1077 data
[index
].innerHTML
= '';
1082 TableRow
.prototype.prepareToSortByTestResults = function(runId
) {
1083 var testResults
= this.test
.results();
1084 // Find the column in this row that matches the runId and prepare to
1085 // sort by the mean of that test.
1086 for (index
in testResults
) {
1087 sourceId
= testResults
[index
].run().id();
1088 if (runId
=== sourceId
) {
1089 this.sortValue
= testResults
[index
].mean();
1093 // This row doesn't have any results for the passed runId
1094 this.sortValue
= undefined;
1097 TableRow
.prototype.prepareToSortRelativeToReference = function(runId
) {
1098 var testResults
= this.test
.results();
1100 // Get index of test results that correspond to the reference column.
1101 var remappedReferenceIndex
= this.resultIndexMap
[this.referenceIndex
];
1103 if (remappedReferenceIndex
=== undefined) {
1104 // This test has no results in the reference run.
1105 this.sortValue
= undefined;
1109 otherResults
= testResults
[remappedReferenceIndex
];
1111 // Find the column in this row that matches the runId and prepare to
1112 // sort by the difference from the reference.
1113 for (index
in testResults
) {
1114 sourceId
= testResults
[index
].run().id();
1115 if (runId
=== sourceId
) {
1116 this.sortValue
= testResults
[index
].percentDifference(otherResults
);
1117 if (this.test
.biggerIsBetter()) {
1118 // For this test bigger is not better
1119 this.sortValue
= -this.sortValue
;
1124 // This row doesn't have any results for the passed runId
1125 this.sortValue
= undefined;
1128 TableRow
.prototype.prepareToSortByUnit = function() {
1129 this.sortValue
= this.test
.unit().toLowerCase();
1132 TableRow
.prototype.prepareToSortByName = function() {
1133 this.sortValue
= this.test
.name().toLowerCase();
1136 TableRow
.prototype.isParentOf = function(row
) {
1137 return this.hasNoURL
&& (this.testName
=== row
.testName
);
1140 TableRow
.prototype.addNestedChild = function(child
) {
1141 this.children
.push(child
);
1143 // Indent child one step in from parent
1144 child
.indentLevel
= this.indentLevel
+ INDENTATION
;
1145 child
.hasGraph
= true;
1146 // Start child off as hidden (i.e. collapsed inside parent)
1147 child
.setVisibility(INVISIBLE
);
1148 child
.updateIndentation();
1149 // Show URL in the title column
1150 child
.tableRow
.children()[0].innerHTML
= child
.URL
;
1151 // Set up class to change background colour of nested rows
1152 if (child
.isImportant
) {
1153 child
.tableRow
.addClass('importantNestedRow');
1155 child
.tableRow
.addClass('nestedRow');
1159 TableRow
.prototype.setVisibility = function(visibility
) {
1160 this.visibility
= visibility
;
1161 this.tableRow
[0].style
.display
= (visibility
=== INVISIBLE
) ? 'none' : '';
1164 TableRow
.prototype.setRowNestedState = function(newState
) {
1165 this.rowState
= newState
;
1166 this.updateIndentation();
1169 TableRow
.prototype.updateIndentation = function() {
1170 var element
= this.tableRow
.children('td').first();
1172 element
.removeClass(this.currentIndentationClass
);
1174 this.currentIndentationClass
= (this.rowState
=== COLLAPSED
) ? 'collapsed' : 'expanded';
1176 element
[0].style
.marginLeft
= this.indentLevel
.toString() + 'px';
1177 element
[0].style
.float = 'left';
1179 element
.addClass(this.currentIndentationClass
);
1182 TableRow
.prototype.addToPage = function() {
1183 $('#container').children('tbody').last().append(this.tableRow
);
1185 // Set up click callback
1186 var owningObject
= this;
1187 this.tableRow
.click(function(event
) {
1188 event
.preventDefault();
1189 owningObject
.toggle();
1192 // Add children to the page too
1193 this.children
.forEach(function(child
) {
1198 TableRow
.prototype.removeFromPage = function() {
1200 this.children
.forEach(function(child
) {
1201 child
.removeFromPage();
1204 this.tableRow
.remove();
1208 TableRow
.prototype.markupForRun = function(result
, referenceResult
) {
1209 var comparisonCell
= '';
1210 var shouldCompare
= result
!== referenceResult
;
1211 if (shouldCompare
) {
1212 var comparisonText
= '';
1215 if (referenceResult
) {
1216 var percentDifference
= referenceResult
.percentDifference(result
);
1217 if (isNaN(percentDifference
)) {
1218 comparisonText
= 'Unknown';
1219 className
= UNKNOWN_CLASS
;
1220 } else if (Math
.abs(percentDifference
) < SMALLEST_PERCENT_DISPLAYED
) {
1221 comparisonText
= 'Equal';
1222 // Show equal values in green
1223 className
= BETTER_CLASS
;
1225 var better
= this.test
.biggerIsBetter() ? percentDifference
> 0 : percentDifference
< 0;
1226 comparisonText
= formatPercentage(Math
.abs(percentDifference
)) + (better
? ' Better' : ' Worse');
1227 className
= better
? BETTER_CLASS
: WORSE_CLASS
;
1230 if (!referenceResult
.isStatisticallySignificant(result
)) {
1231 // Put result in brackets and fade if not statistically significant
1232 className
+= ' fadeOut';
1233 comparisonText
= '(' + comparisonText
+ ')';
1236 comparisonCell
= '<td class="comparison ' + className
+ '">' + comparisonText
+ '</td>';
1239 var values
= result
.values();
1241 var regressionAnalysis
= '';
1242 if (result
.histogramValues
) {
1243 // Don't calculate regression result for histograms.
1244 } else if (values
&& values
.length
> 3) {
1245 regressionResult
= linearRegression(values
);
1246 regressionAnalysis
= 'slope=' + toFixedWidthPrecision(regressionResult
.slope
)
1247 + ', R^2=' + toFixedWidthPrecision(regressionResult
.rSquared
);
1248 if (regressionResult
.rSquared
> 0.6 && Math
.abs(regressionResult
.slope
) > 0.01) {
1249 warning
= ' <span class="regression-warning" title="Detected a time dependency with ' + regressionAnalysis
+ '">' + warningSign
+ ' </span>';
1253 var referenceClass
= shouldCompare
? '' : 'reference';
1255 var statistics
= 'σ=' + toFixedWidthPrecision(result
.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result
.min())
1256 + ', max=' + toFixedWidthPrecision(result
.max()) + '\n' + regressionAnalysis
;
1259 if (isNaN(result
.confidenceIntervalDeltaRatio())) {
1260 // Don't bother showing +- Nan as it is meaningless
1263 confidence
= '± ' + formatPercentage(result
.confidenceIntervalDeltaRatio());
1266 return '<td class="result ' + referenceClass
+ '" title="' + statistics
+ '">' + toFixedWidthPrecision(result
.mean())
1267 + '</td><td class="confidenceIntervalDelta ' + referenceClass
+ '" title="' + statistics
+ '">' + confidence
+ warning
+ '</td>' + comparisonCell
;
1270 TableRow
.prototype.markupForMissingRun = function(isReference
) {
1272 return '<td colspan=2 class="missingReference">Missing</td>';
1274 return '<td colspan=3 class="missing">Missing</td>';
1277 TableRow
.prototype.openRow = function() {
1278 if (this.rowState
=== EXPANDED
) {
1279 // If we're already expanded, open our children instead
1280 this.children
.forEach(function(child
) {
1286 this.setRowNestedState(EXPANDED
);
1288 if (this.hasGraph
) {
1289 var firstCell
= this.tableRow
.children('td').first();
1290 var plot
= createPlot(firstCell
, this.test
, this.useLargeLinePlots
);
1291 plot
.css({'position': 'absolute', 'z-index': 2});
1292 var offset
= this.tableRow
.offset();
1293 offset
.left
+= GRAPH_INDENT
;
1294 offset
.top
+= this.tableRow
.outerHeight();
1295 plot
.offset(offset
);
1296 this.tableRow
.children('td').css({'padding-bottom': plot
.outerHeight() + PADDING_UNDER_GRAPH
});
1299 this.children
.forEach(function(child
) {
1300 child
.setVisibility(VISIBLE
);
1303 if (this.children
.length
=== 1) {
1304 // If we only have a single child...
1305 var child
= this.children
[0];
1306 if (child
.isImportant
) {
1307 // ... and it is important (i.e. the summary row) just open it when
1308 // parent is opened to save needless clicking
1314 TableRow
.prototype.closeRow = function() {
1315 if (this.rowState
=== COLLAPSED
) {
1319 this.setRowNestedState(COLLAPSED
);
1321 if (this.hasGraph
) {
1322 var firstCell
= this.tableRow
.children('td').first();
1323 firstCell
.children('section').remove();
1324 this.tableRow
.children('td').css({'padding-bottom': ''});
1327 this.children
.forEach(function(child
) {
1328 // Make children invisible, but leave their collapsed status alone
1329 child
.setVisibility(INVISIBLE
);
1333 TableRow
.prototype.toggle = function() {
1334 if (this.rowState
=== EXPANDED
) {
1345 var deletedRunsById
= {};
1346 $.each(JSON
.parse(document
.getElementById('results-json').textContent
), function (index
, entry
) {
1347 var run
= new TestRun(entry
);
1348 if (run
.isHidden()) {
1349 deletedRunsById
[run
.id()] = run
;
1355 function addTests(tests
) {
1356 for (var testName
in tests
) {
1357 var rawMetrics
= tests
[testName
].metrics
;
1359 for (var metricName
in rawMetrics
) {
1360 var fullMetricName
= testName
+ ':' + metricName
;
1361 var metric
= metrics
[fullMetricName
];
1363 metric
= new PerfTestMetric(testName
, metricName
, rawMetrics
[metricName
].units
, rawMetrics
[metricName
].important
);
1364 metrics
[fullMetricName
] = metric
;
1366 // std & degrees_of_freedom could be undefined
1368 new TestResult(metric
, rawMetrics
[metricName
].current
,
1369 run
, rawMetrics
[metricName
]['std'], rawMetrics
[metricName
]['degrees_of_freedom']));
1374 addTests(entry
.tests
);
1377 var useLargeLinePlots
= false;
1378 var shouldIgnoreMemory
= true;
1379 var referenceIndex
= 0;
1381 createTable(metrics
, runs
, shouldIgnoreMemory
, referenceIndex
, useLargeLinePlots
);
1383 $('#time-memory').bind('change', function (event
, checkedElement
) {
1384 shouldIgnoreMemory
= checkedElement
.textContent
== 'Time';
1385 createTable(metrics
, runs
, shouldIgnoreMemory
, referenceIndex
, useLargeLinePlots
);
1388 $('#scatter-line').bind('change', function (event
, checkedElement
) {
1389 useLargeLinePlots
= checkedElement
.textContent
== 'Line';
1390 createTable(metrics
, runs
, shouldIgnoreMemory
, referenceIndex
, useLargeLinePlots
);
1393 runs
.map(function (run
, index
) {
1394 $('#reference').append('<span value="' + index
+ '"' + (index
== referenceIndex
? ' class="checked"' : '') + ' title="' + run
.description() + '">' + run
.label() + '</span>');
1397 $('#reference').bind('change', function (event
, checkedElement
) {
1398 referenceIndex
= parseInt(checkedElement
.getAttribute('value'));
1399 createTable(metrics
, runs
, shouldIgnoreMemory
, referenceIndex
, useLargeLinePlots
);
1402 $('.checkbox').each(function (index
, checkbox
) {
1403 $(checkbox
).children('span').click(function (event
) {
1404 if ($(this).hasClass('checked'))
1406 $(checkbox
).children('span').removeClass('checked');
1407 $(this).addClass('checked');
1408 $(checkbox
).trigger('change', $(this));
1412 runToUndelete
= deletedRunsById
[undeleteManager
.mostRecentlyDeletedId()];
1414 if (runToUndelete
) {
1415 $('#undelete').html('Undelete ' + runToUndelete
.label());
1416 $('#undelete').attr('title', runToUndelete
.description());
1417 $('#undelete').click(function (event
) {
1418 runToUndelete
.show();
1419 undeleteManager
.undeleteMostRecent();
1423 $('#undelete').hide();
1428 <script id=
"results-json" type=
"application/json">%json_results%
</script>
1429 <script id=
"units-json" type=
"application/json">%json_units%
</script>