| 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 |