Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(85)

Side by Side Diff: tools/telemetry/support/html_output/results-template.html

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

Powered by Google App Engine
This is Rietveld 408576698