OLD | NEW |
| (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 .collapsed:before { | |
14 color: #ccc; | |
15 content: '\25B8\00A0'; | |
16 } | |
17 | |
18 .expanded:before { | |
19 color: #eee; | |
20 content: '\25BE\00A0'; | |
21 } | |
22 | |
23 .line-plots { | |
24 padding-left: 25px; | |
25 } | |
26 | |
27 .line-plots > div { | |
28 display: inline-block; | |
29 width: 90px; | |
30 height: 40px; | |
31 margin-right: 10px; | |
32 } | |
33 | |
34 .lage-line-plots { | |
35 padding-left: 25px; | |
36 } | |
37 | |
38 .large-line-plots > div, .histogram-plots > div { | |
39 display: inline-block; | |
40 width: 400px; | |
41 height: 200px; | |
42 margin-right: 10px; | |
43 } | |
44 | |
45 .large-line-plot-labels > div, .histogram-plot-labels > div { | |
46 display: inline-block; | |
47 width: 400px; | |
48 height: 11px; | |
49 margin-right: 10px; | |
50 color: #545454; | |
51 text-align: center; | |
52 font-size: 11px; | |
53 } | |
54 | |
55 .closeButton { | |
56 display: inline-block; | |
57 background: #eee; | |
58 background: linear-gradient(rgb(220, 220, 220), rgb(255, 255, 255)); | |
59 border: inset 1px #ddd; | |
60 border-radius: 4px; | |
61 float: right; | |
62 font-size: small; | |
63 -webkit-user-select: none; | |
64 font-weight: bold; | |
65 padding: 1px 4px; | |
66 } | |
67 | |
68 .closeButton:hover { | |
69 background: #F09C9C; | |
70 } | |
71 | |
72 .label { | |
73 cursor: text; | |
74 } | |
75 | |
76 .label:hover { | |
77 background: #ffcc66; | |
78 } | |
79 | |
80 section h1 { | |
81 text-align: center; | |
82 font-size: 1em; | |
83 } | |
84 | |
85 section .tooltip { | |
86 position: absolute; | |
87 text-align: center; | |
88 background: #ffcc66; | |
89 border-radius: 5px; | |
90 padding: 0px 5px; | |
91 } | |
92 | |
93 body { | |
94 padding: 0px; | |
95 margin: 0px; | |
96 font-family: sans-serif; | |
97 } | |
98 | |
99 table { | |
100 background: white; | |
101 width: 100%; | |
102 } | |
103 | |
104 table, td, th { | |
105 border-collapse: collapse; | |
106 padding: 5px; | |
107 white-space: nowrap; | |
108 } | |
109 | |
110 .highlight:hover { | |
111 color: #202020; | |
112 background: #e0e0e0; | |
113 } | |
114 | |
115 .nestedRow { | |
116 background: #f8f8f8; | |
117 } | |
118 | |
119 .importantNestedRow { | |
120 background: #e0e0e0; | |
121 font-weight: bold; | |
122 } | |
123 | |
124 table td { | |
125 position: relative; | |
126 } | |
127 | |
128 th, td { | |
129 cursor: pointer; | |
130 cursor: hand; | |
131 } | |
132 | |
133 th { | |
134 background: #e6eeee; | |
135 background: linear-gradient(rgb(244, 244, 244), rgb(217, 217, 217)); | |
136 border: 1px solid #ccc; | |
137 } | |
138 | |
139 th.sortUp:after { | |
140 content: ' \25BE'; | |
141 } | |
142 | |
143 th.sortDown:after { | |
144 content: ' \25B4'; | |
145 } | |
146 | |
147 td.comparison, td.result { | |
148 text-align: right; | |
149 } | |
150 | |
151 td.better { | |
152 color: #6c6; | |
153 } | |
154 | |
155 td.fadeOut { | |
156 opacity: 0.5; | |
157 } | |
158 | |
159 td.unknown { | |
160 color: #ccc; | |
161 } | |
162 | |
163 td.worse { | |
164 color: #c66; | |
165 } | |
166 | |
167 td.reference { | |
168 font-style: italic; | |
169 font-weight: bold; | |
170 color: #444; | |
171 } | |
172 | |
173 td.missing { | |
174 color: #ccc; | |
175 text-align: center; | |
176 } | |
177 | |
178 td.missingReference { | |
179 color: #ccc; | |
180 text-align: center; | |
181 font-style: italic; | |
182 } | |
183 | |
184 .checkbox { | |
185 display: inline-block; | |
186 background: #eee; | |
187 background: linear-gradient(rgb(220, 220, 220), rgb(200, 200, 200)); | |
188 border: inset 1px #ddd; | |
189 border-radius: 5px; | |
190 margin: 10px; | |
191 font-size: small; | |
192 cursor: pointer; | |
193 cursor: hand; | |
194 -webkit-user-select: none; | |
195 font-weight: bold; | |
196 } | |
197 | |
198 .checkbox span { | |
199 display: inline-block; | |
200 line-height: 100%; | |
201 padding: 5px 8px; | |
202 border: outset 1px transparent; | |
203 } | |
204 | |
205 .checkbox .checked { | |
206 background: #e6eeee; | |
207 background: linear-gradient(rgb(255, 255, 255), rgb(235, 235, 235)); | |
208 border: outset 1px #eee; | |
209 border-radius: 5px; | |
210 } | |
211 | |
212 .openAllButton { | |
213 display: inline-block; | |
214 colour: #6c6 | |
215 background: #eee; | |
216 background: linear-gradient(rgb(220, 220, 220), rgb(255, 255, 255)); | |
217 border: inset 1px #ddd; | |
218 border-radius: 5px; | |
219 float: left; | |
220 font-size: small; | |
221 -webkit-user-select: none; | |
222 font-weight: bold; | |
223 padding: 1px 4px; | |
224 } | |
225 | |
226 .openAllButton:hover { | |
227 background: #60f060; | |
228 } | |
229 | |
230 .closeAllButton { | |
231 display: inline-block; | |
232 colour: #c66 | |
233 background: #eee; | |
234 background: linear-gradient(rgb(220, 220, 220),rgb(255, 255, 255)); | |
235 border: inset 1px #ddd; | |
236 border-radius: 5px; | |
237 float: left; | |
238 font-size: small; | |
239 -webkit-user-select: none; | |
240 font-weight: bold; | |
241 padding: 1px 4px; | |
242 } | |
243 | |
244 .closeAllButton:hover { | |
245 background: #f04040; | |
246 } | |
247 | |
248 </style> | |
249 </head> | |
250 <body onload="init()"> | |
251 <div style="padding: 0 10px; white-space: nowrap;"> | |
252 Result <span id="time-memory" class="checkbox"></span> | |
253 Reference <span id="reference" class="checkbox"></span> | |
254 Style <span id="scatter-line" class="checkbox"><span class="checked">Scatter</sp
an><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 | |
257 </div> | |
258 <table id="container"></table> | |
259 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"><
/script> | |
260 <script> | |
261 %plugins% | |
262 </script> | |
263 <script> | |
264 | |
265 var EXPANDED = true; | |
266 var COLLAPSED = false; | |
267 var SMALLEST_PERCENT_DISPLAYED = 0.01; | |
268 var INVISIBLE = false; | |
269 var VISIBLE = true; | |
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; | |
281 | |
282 function TestResult(metric, values, associatedRun, std, degreesOfFreedom) { | |
283 if (values) { | |
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; | |
289 } | |
290 | |
291 if (jQuery.type(values[0]) === 'string') { | |
292 try { | |
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 cal
culate | |
297 // the values from the histogram bins. | |
298 var values = []; | |
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); | |
305 } | |
306 } | |
307 } | |
308 } | |
309 catch (e) { | |
310 console.error(e, e.stack); | |
311 } | |
312 } | |
313 } else { | |
314 values = []; | |
315 } | |
316 | |
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.leng
th; } | |
320 this.mean = function() { return metric.scalingFactor() * this.unscaledMean()
; } | |
321 this.min = function() { return metric.scalingFactor() * Statistics.min(value
s); } | |
322 this.max = function() { return metric.scalingFactor() * Statistics.max(value
s); } | |
323 this.confidenceIntervalDelta = function() { | |
324 if (std !== undefined) { | |
325 return metric.scalingFactor() * Statistics.confidenceIntervalDeltaFr
omStd(0.95, values.length, | |
326 std, degreesOfFreedom); | |
327 } | |
328 return metric.scalingFactor() * Statistics.confidenceIntervalDelta(0.95,
values.length, | |
329 Statistics.sum(values), Statistics.squareSum(values)); | |
330 } | |
331 this.confidenceIntervalDeltaRatio = function() { return this.confidenceInter
valDelta() / this.mean(); } | |
332 this.percentDifference = function(other) { | |
333 if (other === undefined) { | |
334 return undefined; | |
335 } | |
336 return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean(
); | |
337 } | |
338 this.isStatisticallySignificant = function(other) { | |
339 if (other === undefined) { | |
340 return false; | |
341 } | |
342 var diff = Math.abs(other.mean() - this.mean()); | |
343 return diff > this.confidenceIntervalDelta() && diff > other.confidenceI
ntervalDelta(); | |
344 } | |
345 this.run = function() { return associatedRun; } | |
346 } | |
347 | |
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']; | |
354 } | |
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['pla
tform'] + ' ' + this.label(); | |
361 } | |
362 | |
363 var labelKey = 'telemetry_label_' + this.id(); | |
364 var hiddenKey = 'telemetry_hide_' + this.id(); | |
365 } | |
366 | |
367 function PerfTestMetric(name, metric, unit, isImportant) { | |
368 var testResults = []; | |
369 var cachedUnit = null; | |
370 var cachedScalingFactor = null; | |
371 | |
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) | |
377 return; | |
378 | |
379 var mean = testResults[0].unscaledMean(); // FIXME: We should look at al
l 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); | |
387 } else { | |
388 cachedScalingFactor = 1; | |
389 cachedUnit = unit; | |
390 } | |
391 } | |
392 | |
393 this.name = function() { return name + ':' + metric; } | |
394 this.isImportant = isImportant; | |
395 this.isMemoryTest = function() { | |
396 return (unit == 'kb' || | |
397 unit == 'KB' || | |
398 unit == 'MB' || | |
399 unit == 'bytes' || | |
400 unit == 'count' || | |
401 !metric.indexOf('V8.')); | |
402 } | |
403 this.addResult = function(newResult) { | |
404 testResults.push(newResult); | |
405 cachedUnit = null; | |
406 cachedScalingFactor = null; | |
407 } | |
408 this.results = function() { return testResults; } | |
409 this.scalingFactor = function() { | |
410 computeScalingFactorIfNeeded(); | |
411 return cachedScalingFactor; | |
412 } | |
413 this.unit = function() { | |
414 computeScalingFactorIfNeeded(); | |
415 return cachedUnit; | |
416 } | |
417 this.biggerIsBetter = function() { | |
418 if (window.unitToBiggerIsBetter == undefined) { | |
419 window.unitToBiggerIsBetter = {}; | |
420 var units = JSON.parse(document.getElementById('units-json').textCon
tent); | |
421 for (var u in units) { | |
422 if (units[u].improvement_direction == 'up') { | |
423 window.unitToBiggerIsBetter[u] = true; | |
424 } | |
425 } | |
426 } | |
427 return window.unitToBiggerIsBetter[unit]; | |
428 } | |
429 } | |
430 | |
431 function UndeleteManager() { | |
432 var key = 'telemetry_undeleteIds' | |
433 var undeleteIds = localStorage[key]; | |
434 if (undeleteIds) { | |
435 undeleteIds = JSON.parse(undeleteIds); | |
436 } else { | |
437 undeleteIds = []; | |
438 } | |
439 | |
440 this.ondelete = function(id) { | |
441 undeleteIds.push(id); | |
442 localStorage[key] = JSON.stringify(undeleteIds); | |
443 } | |
444 this.undeleteMostRecent = function() { | |
445 if (!this.mostRecentlyDeletedId()) | |
446 return; | |
447 undeleteIds.pop(); | |
448 localStorage[key] = JSON.stringify(undeleteIds); | |
449 } | |
450 this.mostRecentlyDeletedId = function() { | |
451 if (!undeleteIds.length) | |
452 return undefined; | |
453 return undeleteIds[undeleteIds.length-1]; | |
454 } | |
455 } | |
456 var undeleteManager = new UndeleteManager(); | |
457 | |
458 var plotColor = 'rgb(230,50,50)'; | |
459 var subpointsPlotOptions = { | |
460 lines: {show:true, lineWidth: 0}, | |
461 color: plotColor, | |
462 points: {show: true, radius: 1}, | |
463 bars: {show: false}}; | |
464 | |
465 var mainPlotOptions = { | |
466 xaxis: { | |
467 min: -0.5, | |
468 tickSize: 1, | |
469 }, | |
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 }, | |
475 grid: { | |
476 borderWidth: 1, | |
477 borderColor: '#ccc', | |
478 backgroundColor: '#fff', | |
479 hoverable: true, | |
480 autoHighlight: false, | |
481 } | |
482 }; | |
483 | |
484 var linePlotOptions = { | |
485 yaxis: { show: false }, | |
486 xaxis: { show: false }, | |
487 lines: { show: true }, | |
488 grid: { borderWidth: 1, borderColor: '#ccc' }, | |
489 colors: [ plotColor ] | |
490 }; | |
491 | |
492 var largeLinePlotOptions = { | |
493 xaxis: { | |
494 show: true, | |
495 tickDecimals: 0, | |
496 }, | |
497 lines: { show: true }, | |
498 grid: { borderWidth: 1, borderColor: '#ccc' }, | |
499 colors: [ plotColor ] | |
500 }; | |
501 | |
502 var histogramPlotOptions = { | |
503 bars: {show: true, fill: 1} | |
504 }; | |
505 | |
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')); | |
513 } | |
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'), useLargeLin
ePlots); | |
520 attachLinePlotLabels(test, section.children('.large-line-plot-labels')); | |
521 } else { | |
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 + 2
5) + 'px', 'height': '300px'}); | |
525 $(container).append(section); | |
526 | |
527 var plotContainer = section.children('.plot'); | |
528 var minIsZero = true; | |
529 attachPlot(test, plotContainer, minIsZero); | |
530 | |
531 attachLinePlots(test, section.children('.line-plots'), useLargeLinePlots
); | |
532 | |
533 var tooltip = section.children('.tooltip'); | |
534 plotContainer.bind('plothover', function(event, position, item) { | |
535 if (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.out
erWidth() / 2, top: item.pageY - sectionOffset.top + 10}); | |
540 tooltip.fadeIn(200); | |
541 } else | |
542 tooltip.hide(); | |
543 }); | |
544 plotContainer.mouseout(function() { | |
545 tooltip.hide(); | |
546 }); | |
547 plotContainer.click(function(event) { | |
548 event.preventDefault(); | |
549 minIsZero = !minIsZero; | |
550 attachPlot(test, plotContainer, minIsZero); | |
551 }); | |
552 } | |
553 return section; | |
554 } | |
555 | |
556 function attachLinePlots(test, container, useLargeLinePlots) { | |
557 var results = test.results(); | |
558 var attachedPlot = false; | |
559 | |
560 if (useLargeLinePlots) { | |
561 var maximum = 0; | |
562 for (var i = 0; i < results.length; i++) { | |
563 var values = results[i].values(); | |
564 if (!values) | |
565 continue; | |
566 var local_max = Math.max.apply(Math, values); | |
567 if (local_max > maximum) | |
568 maximum = local_max; | |
569 } | |
570 } | |
571 | |
572 for (var i = 0; i < results.length; i++) { | |
573 container.append('<div></div>'); | |
574 var values = results[i].values(); | |
575 if (!values) | |
576 continue; | |
577 attachedPlot = true; | |
578 | |
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 : fals
e}}); | |
584 } else { | |
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 : fals
e}}); | |
589 } | |
590 $.plot(container.children().last(), [values.map(function(value, index) {
return [index, value]; })], options); | |
591 } | |
592 if (!attachedPlot) | |
593 container.children().remove(); | |
594 } | |
595 | |
596 function attachHistogramPlots(test, container) { | |
597 var results = test.results(); | |
598 var attachedPlot = false; | |
599 | |
600 for (var i = 0; i < results.length; i++) { | |
601 container.append('<div></div>'); | |
602 var histogram = results[i].histogramValues | |
603 if (!histogram) | |
604 continue; | |
605 attachedPlot = true; | |
606 | |
607 var buckets = histogram.buckets | |
608 var bucket; | |
609 var max_count = 0; | |
610 for (var j = 0; j < buckets.length; j++) { | |
611 bucket = buckets[j]; | |
612 max_count = Math.max(max_count, bucket.count); | |
613 } | |
614 var xmax = bucket.high * 1.1; | |
615 var ymax = max_count * 1.1; | |
616 | |
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"); | |
624 ctx.lineWidth="1"; | |
625 ctx.fillStyle = "rgba(255, 0, 0, 0.2)"; | |
626 ctx.strokeStyle="red"; | |
627 for (var j = 0; j < buckets.length; j++) { | |
628 bucket = buckets[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); | |
633 } | |
634 } | |
635 if (!attachedPlot) | |
636 container.children().remove(); | |
637 } | |
638 | |
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>'); | |
644 } | |
645 } | |
646 | |
647 function attachPlot(test, plotContainer, minIsZero) { | |
648 var results = test.results(); | |
649 | |
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; | |
653 }, []); | |
654 | |
655 var plotData = [$.extend(true, {}, subpointsPlotOptions, {data: values})]; | |
656 plotData.push({id: 'μ', data: results.map(function(result, index) { retur
n [index, result.mean()]; }), color: plotColor}); | |
657 | |
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}}); | |
664 | |
665 currentPlotOptions.xaxis.max = results.length - 0.5; | |
666 currentPlotOptions.xaxis.ticks = results.map(function(result, index) { retur
n [index, result.run().label()]; }); | |
667 | |
668 $.plot(plotContainer, plotData, currentPlotOptions); | |
669 } | |
670 | |
671 function toFixedWidthPrecision(value) { | |
672 var decimal = value.toFixed(2); | |
673 return decimal; | |
674 } | |
675 | |
676 function formatPercentage(fraction) { | |
677 var percentage = fraction * 100; | |
678 return (fraction * 100).toFixed(2) + '%'; | |
679 } | |
680 | |
681 function setUpSortClicks(runs) | |
682 { | |
683 $('#nameColumn').click(sortByName); | |
684 | |
685 $('#unitColumn').click(sortByUnit); | |
686 | |
687 runs.forEach(function(run) { | |
688 $('#' + run.id()).click(sortByResult); | |
689 $('#' + run.id() + COMPARISON_SUFFIX).click(sortByReference); | |
690 }); | |
691 } | |
692 | |
693 function TestTypeSelector(tests) { | |
694 this.recognizers = { | |
695 'Time': function(test) { return test.isMemoryTest(); }, | |
696 'Memory': function(test) { return !test.isMemoryTest(); } | |
697 }; | |
698 this.testTypeNames = this.generateUsedTestTypeNames(tests); | |
699 // Default to selecting the first test-type name in the list. | |
700 this.testTypeName = this.testTypeNames[0]; | |
701 } | |
702 | |
703 TestTypeSelector.prototype = { | |
704 set testTypeName(testTypeName) { | |
705 this._testTypeName = testTypeName; | |
706 this.shouldShowTest = this.recognizers[testTypeName]; | |
707 }, | |
708 | |
709 generateUsedTestTypeNames: function(allTests) { | |
710 var testTypeNames = []; | |
711 | |
712 for (var recognizedTestName in this.recognizers) { | |
713 var recognizes = this.recognizers[recognizedTestName]; | |
714 for (var testName in allTests) { | |
715 var test = allTests[testName]; | |
716 if (recognizes(test)) { | |
717 testTypeNames.push(recognizedTestName); | |
718 break; | |
719 } | |
720 } | |
721 } | |
722 | |
723 if (testTypeNames.length === 0) { | |
724 // No test types we recognize, add 'No Results' with a dummy recogni
zer. | |
725 var noResults = 'No Results'; | |
726 this.recognizers[noResults] = function() { return false; }; | |
727 testTypeNames.push(noResults); | |
728 } else if (testTypeNames.length > 1) { | |
729 // We have more than one test type, so add 'All' with a recognizer t
hat always succeeds. | |
730 var allResults = 'All'; | |
731 this.recognizers[allResults] = function() { return true; }; | |
732 testTypeNames.push(allResults); | |
733 } | |
734 | |
735 return testTypeNames; | |
736 }, | |
737 | |
738 buildButtonHTMLForUsedTestTypes: function() { | |
739 var selectedTestTypeName = this._testTypeName; | |
740 // Build spans for all recognised test names with the selected test high
lighted. | |
741 return this.testTypeNames.map(function(testTypeName) { | |
742 var classAttribute = testTypeName === selectedTestTypeName ? ' class
=checked' : ''; | |
743 return '<span' + classAttribute + '>' + testTypeName + '</span>'; | |
744 }).join(''); | |
745 } | |
746 }; | |
747 | |
748 var topLevelRows; | |
749 var allTableRows; | |
750 | |
751 function displayTable(tests, runs, testTypeSelector, referenceIndex, useLargeLin
ePlots) { | |
752 var resultHeaders = runs.map(function(run, index) { | |
753 var header = '<th id="' + run.id() + '" ' + | |
754 'colspan=2 ' + | |
755 'title="' + run.description() + '">' + | |
756 '<span class="label" ' + | |
757 'title="Edit run label">' + | |
758 run.label() + | |
759 '</span>' + | |
760 '<div class="closeButton" ' + | |
761 'title="Delete run">' + | |
762 '×' + | |
763 '</div>' + | |
764 '</th>'; | |
765 if (index !== referenceIndex) { | |
766 header += '<th id="' + run.id() + COMPARISON_SUFFIX + '" ' + | |
767 'title="Sort by better/worse">' + | |
768 'Δ' + | |
769 '</th>'; | |
770 } | |
771 return header; | |
772 }); | |
773 | |
774 resultHeaders = resultHeaders.join(''); | |
775 | |
776 htmlString = '<thead>' + | |
777 '<tr>' + | |
778 '<th id="nameColumn">' + | |
779 '<div class="openAllButton" ' + | |
780 'title="Open all rows or graphs">' + | |
781 'Open All' + | |
782 '</div>' + | |
783 '<div class="closeAllButton" ' + | |
784 'title="Close all rows">' + | |
785 'Close All' + | |
786 '</div>' + | |
787 'Test' + | |
788 '</th>' + | |
789 '<th id="unitColumn">' + | |
790 'Unit' + | |
791 '</th>' + | |
792 resultHeaders + | |
793 '</tr>' + | |
794 '</head>' + | |
795 '<tbody>' + | |
796 '</tbody>'; | |
797 | |
798 $('#container').html(htmlString); | |
799 | |
800 var testNames = []; | |
801 for (testName in tests) | |
802 testNames.push(testName); | |
803 | |
804 allTableRows = []; | |
805 testNames.forEach(function(testName) { | |
806 var test = tests[testName]; | |
807 if (testTypeSelector.shouldShowTest(test)) { | |
808 allTableRows.push(new TableRow(runs, test, referenceIndex, useLargeL
inePlots)); | |
809 } | |
810 }); | |
811 | |
812 // Build a list of top level rows with attached children | |
813 topLevelRows = []; | |
814 allTableRows.forEach(function(row) { | |
815 // Add us to top level if we are a top-level row... | |
816 if (row.hasNoURL) { | |
817 topLevelRows.push(row); | |
818 // Add a duplicate child row that holds the graph for the parent | |
819 var graphHolder = new TableRow(runs, row.test, referenceIndex, useLa
rgeLinePlots); | |
820 graphHolder.isImportant = true; | |
821 graphHolder.URL = 'Summary'; | |
822 graphHolder.hideRowData(); | |
823 allTableRows.push(graphHolder); | |
824 row.addNestedChild(graphHolder); | |
825 return; | |
826 } | |
827 | |
828 // ...or add us to our parent if we have one ... | |
829 for (var i = 0; i < allTableRows.length; i++) { | |
830 if (allTableRows[i].isParentOf(row)) { | |
831 allTableRows[i].addNestedChild(row); | |
832 return; | |
833 } | |
834 } | |
835 | |
836 // ...otherwise this result is orphaned, display it at top level with a
graph | |
837 row.hasGraph = true; | |
838 topLevelRows.push(row); | |
839 }); | |
840 | |
841 buildTable(topLevelRows); | |
842 | |
843 $('.closeButton').click(function(event) { | |
844 for (var i = 0; i < runs.length; i++) { | |
845 if (runs[i].id() == event.target.parentNode.id) { | |
846 runs[i].hide(); | |
847 undeleteManager.ondelete(runs[i].id()); | |
848 location.reload(); | |
849 break; | |
850 } | |
851 } | |
852 event.stopPropagation(); | |
853 }); | |
854 | |
855 $('.closeAllButton').click(function(event) { | |
856 for (var i = 0; i < allTableRows.length; i++) { | |
857 allTableRows[i].closeRow(); | |
858 } | |
859 event.stopPropagation(); | |
860 }); | |
861 | |
862 $('.openAllButton').click(function(event) { | |
863 for (var i = 0; i < topLevelRows.length; i++) { | |
864 topLevelRows[i].openRow(); | |
865 } | |
866 event.stopPropagation(); | |
867 }); | |
868 | |
869 setUpSortClicks(runs); | |
870 | |
871 $('.label').click(function(event) { | |
872 for (var i = 0; i < runs.length; i++) { | |
873 if (runs[i].id() == event.target.parentNode.id) { | |
874 $(event.target).replaceWith('<input id="labelEditor" type="text"
value="' + runs[i].label() + '">'); | |
875 $('#labelEditor').focusout(function() { | |
876 runs[i].setLabel(this.value); | |
877 location.reload(); | |
878 }); | |
879 $('#labelEditor').keypress(function(event) { | |
880 if (event.which == 13) { | |
881 runs[i].setLabel(this.value); | |
882 location.reload(); | |
883 } | |
884 }); | |
885 $('#labelEditor').click(function(event) { | |
886 event.stopPropagation(); | |
887 }); | |
888 $('#labelEditor').mousedown(function(event) { | |
889 event.stopPropagation(); | |
890 }); | |
891 $('#labelEditor').select(); | |
892 break; | |
893 } | |
894 } | |
895 event.stopPropagation(); | |
896 }); | |
897 } | |
898 | |
899 function validForSorting(row) { | |
900 return ($.type(row.sortValue) === 'string') || !isNaN(row.sortValue); | |
901 } | |
902 | |
903 var sortDirection = 1; | |
904 | |
905 function sortRows(rows) { | |
906 rows.sort( | |
907 function(rowA,rowB) { | |
908 if (validForSorting(rowA) !== validForSorting(rowB)) { | |
909 // Sort valid values upwards when compared to invalid | |
910 if (validForSorting(rowA)) { | |
911 return -1; | |
912 } | |
913 if (validForSorting(rowB)) { | |
914 return 1; | |
915 } | |
916 } | |
917 | |
918 // Some rows always sort to the top | |
919 if (rowA.isImportant) { | |
920 return -1; | |
921 } | |
922 if (rowB.isImportant) { | |
923 return 1; | |
924 } | |
925 | |
926 if (rowA.sortValue === rowB.sortValue) { | |
927 // Sort identical values by name to keep the sort stable, | |
928 // always keep name alphabetical (even if a & b sort values | |
929 // are invalid) | |
930 return rowA.test.name() > rowB.test.name() ? 1 : -1; | |
931 } | |
932 | |
933 return rowA.sortValue > rowB.sortValue ? sortDirection : -sortDirect
ion; | |
934 } ); | |
935 | |
936 // Sort the rows' children | |
937 rows.forEach(function(row) { | |
938 sortRows(row.children); | |
939 }); | |
940 } | |
941 | |
942 function buildTable(rows) { | |
943 rows.forEach(function(row) { | |
944 row.removeFromPage(); | |
945 }); | |
946 | |
947 sortRows(rows); | |
948 | |
949 rows.forEach(function(row) { | |
950 row.addToPage(); | |
951 }); | |
952 } | |
953 | |
954 var activeSortHeaderElement = undefined; | |
955 var columnSortDirection = {}; | |
956 | |
957 function determineColumnSortDirection(element) { | |
958 columnDirection = columnSortDirection[element.id]; | |
959 | |
960 if (columnDirection === undefined) { | |
961 // First time we've sorted this row, default to down | |
962 columnSortDirection[element.id] = SORT_DOWN_CLASS; | |
963 } else if (element === activeSortHeaderElement) { | |
964 // Clicking on same header again, swap direction | |
965 columnSortDirection[element.id] = (columnDirection === SORT_UP_CLASS) ? SORT
_DOWN_CLASS : SORT_UP_CLASS; | |
966 } | |
967 } | |
968 | |
969 function updateSortDirection(element) { | |
970 // Remove old header's sort arrow | |
971 if (activeSortHeaderElement !== undefined) { | |
972 activeSortHeaderElement.classList.remove(columnSortDirection[activeSortH
eaderElement.id]); | |
973 } | |
974 | |
975 determineColumnSortDirection(element); | |
976 | |
977 sortDirection = (columnSortDirection[element.id] === SORT_UP_CLASS) ? 1 : -1
; | |
978 | |
979 // Add new header's sort arrow | |
980 element.classList.add(columnSortDirection[element.id]); | |
981 activeSortHeaderElement = element; | |
982 } | |
983 | |
984 function sortByName(event) { | |
985 updateSortDirection(event.toElement); | |
986 | |
987 allTableRows.forEach(function(row) { | |
988 row.prepareToSortByName(); | |
989 }); | |
990 | |
991 buildTable(topLevelRows); | |
992 } | |
993 | |
994 function sortByUnit(event) { | |
995 updateSortDirection(event.toElement); | |
996 | |
997 allTableRows.forEach(function(row) { | |
998 row.prepareToSortByUnit(); | |
999 }); | |
1000 | |
1001 buildTable(topLevelRows); | |
1002 } | |
1003 | |
1004 function sortByResult(event) { | |
1005 updateSortDirection(event.toElement); | |
1006 | |
1007 var runId = event.target.id; | |
1008 | |
1009 allTableRows.forEach(function(row) { | |
1010 row.prepareToSortByTestResults(runId); | |
1011 }); | |
1012 | |
1013 buildTable(topLevelRows); | |
1014 } | |
1015 | |
1016 function sortByReference(event) { | |
1017 updateSortDirection(event.toElement); | |
1018 | |
1019 // The element ID has _compare appended to allow us to set up a click event | |
1020 // remove the _compare to return a useful Id | |
1021 var runIdWithCompare = event.target.id; | |
1022 var runId = runIdWithCompare.split('_')[0]; | |
1023 | |
1024 allTableRows.forEach(function(row) { | |
1025 row.prepareToSortRelativeToReference(runId); | |
1026 }); | |
1027 | |
1028 buildTable(topLevelRows); | |
1029 } | |
1030 | |
1031 function linearRegression(points) { | |
1032 // Implement http://www.easycalculation.com/statistics/learn-correlation.php
. | |
1033 // x = magnitude | |
1034 // y = iterations | |
1035 var sumX = 0; | |
1036 var sumY = 0; | |
1037 var sumXSquared = 0; | |
1038 var sumYSquared = 0; | |
1039 var sumXTimesY = 0; | |
1040 | |
1041 for (var i = 0; i < points.length; i++) { | |
1042 var x = i; | |
1043 var y = points[i]; | |
1044 sumX += x; | |
1045 sumY += y; | |
1046 sumXSquared += x * x; | |
1047 sumYSquared += y * y; | |
1048 sumXTimesY += x * y; | |
1049 } | |
1050 | |
1051 var r = (points.length * sumXTimesY - sumX * sumY) / | |
1052 Math.sqrt((points.length * sumXSquared - sumX * sumX) * | |
1053 (points.length * sumYSquared - sumY * sumY)); | |
1054 | |
1055 if (isNaN(r) || r == Math.Infinity) | |
1056 r = 0; | |
1057 | |
1058 var slope = (points.length * sumXTimesY - sumX * sumY) / (points.length * su
mXSquared - sumX * sumX); | |
1059 var intercept = sumY / points.length - slope * sumX / points.length; | |
1060 return {slope: slope, intercept: intercept, rSquared: r * r}; | |
1061 } | |
1062 | |
1063 var warningSign = '<svg viewBox="0 0 100 100" style="width: 18px; height: 18px;
vertical-align: bottom;" version="1.1">' | |
1064 + '<polygon fill="red" points="50,10 90,80 10,80 50,10" stroke="red" stroke-
width="10" stroke-linejoin="round" />' | |
1065 + '<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" />' | |
1066 + '<circle cx="50" cy="73" r="6" fill="white" />' | |
1067 + '</svg>'; | |
1068 | |
1069 function TableRow(runs, test, referenceIndex, useLargeLinePlots) { | |
1070 this.runs = runs; | |
1071 this.test = test; | |
1072 this.referenceIndex = referenceIndex; | |
1073 this.useLargeLinePlots = useLargeLinePlots; | |
1074 this.children = []; | |
1075 | |
1076 this.tableRow = $('<tr class="highlight">' + | |
1077 '<td class="test collapsed" >' + | |
1078 this.test.name() + | |
1079 '</td>' + | |
1080 '<td class="unit">' + | |
1081 this.test.unit() + | |
1082 '</td>' + | |
1083 '</tr>'); | |
1084 | |
1085 var runIndex = 0; | |
1086 var results = this.test.results(); | |
1087 var referenceResult = undefined; | |
1088 | |
1089 this.resultIndexMap = {}; | |
1090 for (var i = 0; i < results.length; i++) { | |
1091 while (this.runs[runIndex] !== results[i].run()) | |
1092 runIndex++; | |
1093 if (runIndex === this.referenceIndex) | |
1094 referenceResult = results[i]; | |
1095 this.resultIndexMap[runIndex] = i; | |
1096 } | |
1097 for (var i = 0; i < this.runs.length; i++) { | |
1098 var resultIndex = this.resultIndexMap[i]; | |
1099 if (resultIndex === undefined) | |
1100 this.tableRow.append(this.markupForMissingRun(i == this.referenceInd
ex)); | |
1101 else | |
1102 this.tableRow.append(this.markupForRun(results[resultIndex], referen
ceResult)); | |
1103 } | |
1104 | |
1105 // Use the test name (without URL) to bind parents and their children | |
1106 var nameAndURL = this.test.name().split('.'); | |
1107 var benchmarkName = nameAndURL.shift(); | |
1108 this.testName = nameAndURL.shift(); | |
1109 this.hasNoURL = (nameAndURL.length === 0); | |
1110 | |
1111 if (!this.hasNoURL) { | |
1112 // Re-join the URL | |
1113 this.URL = nameAndURL.join('.'); | |
1114 } | |
1115 | |
1116 this.isImportant = false; | |
1117 this.hasGraph = false; | |
1118 this.currentIndentationClass = '' | |
1119 this.indentLevel = 0; | |
1120 this.setRowNestedState(COLLAPSED); | |
1121 this.setVisibility(VISIBLE); | |
1122 this.prepareToSortByName(); | |
1123 } | |
1124 | |
1125 TableRow.prototype.hideRowData = function() { | |
1126 data = this.tableRow.children('td'); | |
1127 | |
1128 for (index in data) { | |
1129 if (index > 0) { | |
1130 // Blank out everything except the test name | |
1131 data[index].innerHTML = ''; | |
1132 } | |
1133 } | |
1134 } | |
1135 | |
1136 TableRow.prototype.prepareToSortByTestResults = function(runId) { | |
1137 var testResults = this.test.results(); | |
1138 // Find the column in this row that matches the runId and prepare to | |
1139 // sort by the mean of that test. | |
1140 for (index in testResults) { | |
1141 sourceId = testResults[index].run().id(); | |
1142 if (runId === sourceId) { | |
1143 this.sortValue = testResults[index].mean(); | |
1144 return; | |
1145 } | |
1146 } | |
1147 // This row doesn't have any results for the passed runId | |
1148 this.sortValue = undefined; | |
1149 } | |
1150 | |
1151 TableRow.prototype.prepareToSortRelativeToReference = function(runId) { | |
1152 var testResults = this.test.results(); | |
1153 | |
1154 // Get index of test results that correspond to the reference column. | |
1155 var remappedReferenceIndex = this.resultIndexMap[this.referenceIndex]; | |
1156 | |
1157 if (remappedReferenceIndex === undefined) { | |
1158 // This test has no results in the reference run. | |
1159 this.sortValue = undefined; | |
1160 return; | |
1161 } | |
1162 | |
1163 otherResults = testResults[remappedReferenceIndex]; | |
1164 | |
1165 // Find the column in this row that matches the runId and prepare to | |
1166 // sort by the difference from the reference. | |
1167 for (index in testResults) { | |
1168 sourceId = testResults[index].run().id(); | |
1169 if (runId === sourceId) { | |
1170 this.sortValue = testResults[index].percentDifference(otherResults); | |
1171 if (this.test.biggerIsBetter()) { | |
1172 // For this test bigger is not better | |
1173 this.sortValue = -this.sortValue; | |
1174 } | |
1175 return; | |
1176 } | |
1177 } | |
1178 // This row doesn't have any results for the passed runId | |
1179 this.sortValue = undefined; | |
1180 } | |
1181 | |
1182 TableRow.prototype.prepareToSortByUnit = function() { | |
1183 this.sortValue = this.test.unit().toLowerCase(); | |
1184 } | |
1185 | |
1186 TableRow.prototype.prepareToSortByName = function() { | |
1187 this.sortValue = this.test.name().toLowerCase(); | |
1188 } | |
1189 | |
1190 TableRow.prototype.isParentOf = function(row) { | |
1191 return this.hasNoURL && (this.testName === row.testName); | |
1192 } | |
1193 | |
1194 TableRow.prototype.addNestedChild = function(child) { | |
1195 this.children.push(child); | |
1196 | |
1197 // Indent child one step in from parent | |
1198 child.indentLevel = this.indentLevel + INDENTATION; | |
1199 child.hasGraph = true; | |
1200 // Start child off as hidden (i.e. collapsed inside parent) | |
1201 child.setVisibility(INVISIBLE); | |
1202 child.updateIndentation(); | |
1203 // Show URL in the title column | |
1204 child.tableRow.children()[0].innerHTML = child.URL; | |
1205 // Set up class to change background colour of nested rows | |
1206 if (child.isImportant) { | |
1207 child.tableRow.addClass('importantNestedRow'); | |
1208 } else { | |
1209 child.tableRow.addClass('nestedRow'); | |
1210 } | |
1211 } | |
1212 | |
1213 TableRow.prototype.setVisibility = function(visibility) { | |
1214 this.visibility = visibility; | |
1215 this.tableRow[0].style.display = (visibility === INVISIBLE) ? 'none' : ''; | |
1216 } | |
1217 | |
1218 TableRow.prototype.setRowNestedState = function(newState) { | |
1219 this.rowState = newState; | |
1220 this.updateIndentation(); | |
1221 } | |
1222 | |
1223 TableRow.prototype.updateIndentation = function() { | |
1224 var element = this.tableRow.children('td').first(); | |
1225 | |
1226 element.removeClass(this.currentIndentationClass); | |
1227 | |
1228 this.currentIndentationClass = (this.rowState === COLLAPSED) ? 'collapsed' :
'expanded'; | |
1229 | |
1230 element[0].style.marginLeft = this.indentLevel.toString() + 'px'; | |
1231 element[0].style.float = 'left'; | |
1232 | |
1233 element.addClass(this.currentIndentationClass); | |
1234 } | |
1235 | |
1236 TableRow.prototype.addToPage = function() { | |
1237 $('#container').children('tbody').last().append(this.tableRow); | |
1238 | |
1239 // Set up click callback | |
1240 var owningObject = this; | |
1241 this.tableRow.click(function(event) { | |
1242 event.preventDefault(); | |
1243 owningObject.toggle(); | |
1244 }); | |
1245 | |
1246 // Add children to the page too | |
1247 this.children.forEach(function(child) { | |
1248 child.addToPage(); | |
1249 }); | |
1250 } | |
1251 | |
1252 TableRow.prototype.removeFromPage = function() { | |
1253 // Remove children | |
1254 this.children.forEach(function(child) { | |
1255 child.removeFromPage(); | |
1256 }); | |
1257 // Remove us | |
1258 this.tableRow.remove(); | |
1259 } | |
1260 | |
1261 | |
1262 TableRow.prototype.markupForRun = function(result, referenceResult) { | |
1263 var comparisonCell = ''; | |
1264 var shouldCompare = result !== referenceResult; | |
1265 if (shouldCompare) { | |
1266 var comparisonText = ''; | |
1267 var className = ''; | |
1268 | |
1269 if (referenceResult) { | |
1270 var percentDifference = referenceResult.percentDifference(result); | |
1271 if (isNaN(percentDifference)) { | |
1272 comparisonText = 'Unknown'; | |
1273 className = UNKNOWN_CLASS; | |
1274 } else if (Math.abs(percentDifference) < SMALLEST_PERCENT_DISPLAYED)
{ | |
1275 comparisonText = 'Equal'; | |
1276 // Show equal values in green | |
1277 className = BETTER_CLASS; | |
1278 } else { | |
1279 var better = this.test.biggerIsBetter() ? percentDifference > 0
: percentDifference < 0; | |
1280 comparisonText = formatPercentage(Math.abs(percentDifference)) +
(better ? ' Better' : ' Worse'); | |
1281 className = better ? BETTER_CLASS : WORSE_CLASS; | |
1282 } | |
1283 | |
1284 if (!referenceResult.isStatisticallySignificant(result)) { | |
1285 // Put result in brackets and fade if not statistically signific
ant | |
1286 className += ' fadeOut'; | |
1287 comparisonText = '(' + comparisonText + ')'; | |
1288 } | |
1289 } | |
1290 comparisonCell = '<td class="comparison ' + className + '">' + compariso
nText + '</td>'; | |
1291 } | |
1292 | |
1293 var values = result.values(); | |
1294 var warning = ''; | |
1295 var regressionAnalysis = ''; | |
1296 if (result.histogramValues) { | |
1297 // Don't calculate regression result for histograms. | |
1298 } else if (values && values.length > 3) { | |
1299 regressionResult = linearRegression(values); | |
1300 regressionAnalysis = 'slope=' + toFixedWidthPrecision(regressionResult.s
lope) | |
1301 + ', R^2=' + toFixedWidthPrecision(regressionResult.rSquared); | |
1302 if (regressionResult.rSquared > 0.6 && Math.abs(regressionResult.slope)
> 0.01) { | |
1303 warning = ' <span class="regression-warning" title="Detected a time
dependency with ' + regressionAnalysis + '">' + warningSign + ' </span>'; | |
1304 } | |
1305 } | |
1306 | |
1307 var referenceClass = shouldCompare ? '' : 'reference'; | |
1308 | |
1309 var statistics = 'σ=' + toFixedWidthPrecision(result.confidenceInterva
lDelta()) + ', min=' + toFixedWidthPrecision(result.min()) | |
1310 + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysi
s; | |
1311 | |
1312 var confidence; | |
1313 if (isNaN(result.confidenceIntervalDeltaRatio())) { | |
1314 // Don't bother showing +- Nan as it is meaningless | |
1315 confidence = ''; | |
1316 } else { | |
1317 confidence = '± ' + formatPercentage(result.confidenceIntervalDel
taRatio()); | |
1318 } | |
1319 | |
1320 return '<td class="result ' + referenceClass + '" title="' + statistics + '"
>' + toFixedWidthPrecision(result.mean()) | |
1321 + '</td><td class="confidenceIntervalDelta ' + referenceClass + '" title
="' + statistics + '">' + confidence + warning + '</td>' + comparisonCell; | |
1322 } | |
1323 | |
1324 TableRow.prototype.markupForMissingRun = function(isReference) { | |
1325 if (isReference) { | |
1326 return '<td colspan=2 class="missingReference">Missing</td>'; | |
1327 } | |
1328 return '<td colspan=3 class="missing">Missing</td>'; | |
1329 } | |
1330 | |
1331 TableRow.prototype.openRow = function() { | |
1332 if (this.rowState === EXPANDED) { | |
1333 // If we're already expanded, open our children instead | |
1334 this.children.forEach(function(child) { | |
1335 child.openRow(); | |
1336 }); | |
1337 return; | |
1338 } | |
1339 | |
1340 this.setRowNestedState(EXPANDED); | |
1341 | |
1342 if (this.hasGraph) { | |
1343 var firstCell = this.tableRow.children('td').first(); | |
1344 var plot = createPlot(firstCell, this.test, this.useLargeLinePlots); | |
1345 plot.css({'position': 'absolute', 'z-index': 2}); | |
1346 var offset = this.tableRow.offset(); | |
1347 offset.left += GRAPH_INDENT; | |
1348 offset.top += this.tableRow.outerHeight(); | |
1349 plot.offset(offset); | |
1350 this.tableRow.children('td').css({'padding-bottom': plot.outerHeight() +
PADDING_UNDER_GRAPH}); | |
1351 } | |
1352 | |
1353 this.children.forEach(function(child) { | |
1354 child.setVisibility(VISIBLE); | |
1355 }); | |
1356 | |
1357 if (this.children.length === 1) { | |
1358 // If we only have a single child... | |
1359 var child = this.children[0]; | |
1360 if (child.isImportant) { | |
1361 // ... and it is important (i.e. the summary row) just open it when | |
1362 // parent is opened to save needless clicking | |
1363 child.openRow(); | |
1364 } | |
1365 } | |
1366 } | |
1367 | |
1368 TableRow.prototype.closeRow = function() { | |
1369 if (this.rowState === COLLAPSED) { | |
1370 return; | |
1371 } | |
1372 | |
1373 this.setRowNestedState(COLLAPSED); | |
1374 | |
1375 if (this.hasGraph) { | |
1376 var firstCell = this.tableRow.children('td').first(); | |
1377 firstCell.children('section').remove(); | |
1378 this.tableRow.children('td').css({'padding-bottom': ''}); | |
1379 } | |
1380 | |
1381 this.children.forEach(function(child) { | |
1382 // Make children invisible, but leave their collapsed status alone | |
1383 child.setVisibility(INVISIBLE); | |
1384 }); | |
1385 } | |
1386 | |
1387 TableRow.prototype.toggle = function() { | |
1388 if (this.rowState === EXPANDED) { | |
1389 this.closeRow(); | |
1390 } else { | |
1391 this.openRow(); | |
1392 } | |
1393 return false; | |
1394 } | |
1395 | |
1396 function init() { | |
1397 var runs = []; | |
1398 var metrics = {}; | |
1399 var deletedRunsById = {}; | |
1400 $.each(JSON.parse(document.getElementById('results-json').textContent), func
tion(index, entry) { | |
1401 var run = new TestRun(entry); | |
1402 if (run.isHidden()) { | |
1403 deletedRunsById[run.id()] = run; | |
1404 return; | |
1405 } | |
1406 | |
1407 runs.push(run); | |
1408 | |
1409 function addTests(tests) { | |
1410 for (var testName in tests) { | |
1411 var rawMetrics = tests[testName].metrics; | |
1412 | |
1413 for (var metricName in rawMetrics) { | |
1414 var fullMetricName = testName + ':' + metricName; | |
1415 var metric = metrics[fullMetricName]; | |
1416 if (!metric) { | |
1417 metric = new PerfTestMetric(testName, metricName, rawMet
rics[metricName].units, rawMetrics[metricName].important); | |
1418 metrics[fullMetricName] = metric; | |
1419 } | |
1420 // std & degrees_of_freedom could be undefined | |
1421 metric.addResult( | |
1422 new TestResult(metric, rawMetrics[metricName].current, | |
1423 run, rawMetrics[metricName]['std'], rawMetrics[metri
cName]['degrees_of_freedom'])); | |
1424 } | |
1425 } | |
1426 } | |
1427 | |
1428 addTests(entry.tests); | |
1429 }); | |
1430 | |
1431 var useLargeLinePlots = false; | |
1432 var referenceIndex = 0; | |
1433 | |
1434 var testTypeSelector = new TestTypeSelector(metrics); | |
1435 var buttonHTML = testTypeSelector.buildButtonHTMLForUsedTestTypes(); | |
1436 $('#time-memory').append(buttonHTML); | |
1437 | |
1438 $('#scatter-line').bind('change', function(event, checkedElement) { | |
1439 useLargeLinePlots = checkedElement.textContent == 'Line'; | |
1440 displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLi
nePlots); | |
1441 }); | |
1442 | |
1443 runs.map(function(run, index) { | |
1444 $('#reference').append('<span value="' + index + '"' + (index == referen
ceIndex ? ' class="checked"' : '') + ' title="' + run.description() + '">' + run
.label() + '</span>'); | |
1445 }) | |
1446 | |
1447 $('#time-memory').bind('change', function(event, checkedElement) { | |
1448 testTypeSelector.testTypeName = checkedElement.textContent; | |
1449 displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLi
nePlots); | |
1450 }); | |
1451 | |
1452 $('#reference').bind('change', function(event, checkedElement) { | |
1453 referenceIndex = parseInt(checkedElement.getAttribute('value')); | |
1454 displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLi
nePlots); | |
1455 }); | |
1456 | |
1457 displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePl
ots); | |
1458 | |
1459 $('.checkbox').each(function(index, checkbox) { | |
1460 $(checkbox).children('span').click(function(event) { | |
1461 if ($(this).hasClass('checked')) | |
1462 return; | |
1463 $(checkbox).children('span').removeClass('checked'); | |
1464 $(this).addClass('checked'); | |
1465 $(checkbox).trigger('change', $(this)); | |
1466 }); | |
1467 }); | |
1468 | |
1469 runToUndelete = deletedRunsById[undeleteManager.mostRecentlyDeletedId()]; | |
1470 | |
1471 if (runToUndelete) { | |
1472 $('#undelete').html('Undelete ' + runToUndelete.label()); | |
1473 $('#undelete').attr('title', runToUndelete.description()); | |
1474 $('#undelete').click(function(event) { | |
1475 runToUndelete.show(); | |
1476 undeleteManager.undeleteMostRecent(); | |
1477 location.reload(); | |
1478 }); | |
1479 } else { | |
1480 $('#undelete').hide(); | |
1481 } | |
1482 } | |
1483 | |
1484 </script> | |
1485 <script id="results-json" type="application/json">%json_results%</script> | |
1486 <script id="units-json" type="application/json">%json_units%</script> | |
1487 </body> | |
1488 </html> | |
OLD | NEW |