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: 'μ', 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 ');
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 = 'σ=' + 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 + '">± '
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