| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Telemetry Performance Test Results</title> |
| <style type="text/css"> |
| |
| section { |
| background: white; |
| padding: 10px; |
| position: relative; |
| } |
| |
| .collapsed:before { |
| content: '\25B8\00A0'; |
| } |
| |
| .expanded:before { |
| content: '\25BE\00A0'; |
| } |
| |
| .line-plots { |
| padding-left: 25px; |
| } |
| |
| .line-plots > div { |
| display: inline-block; |
| width: 90px; |
| height: 40px; |
| margin-right: 10px; |
| } |
| |
| .lage-line-plots { |
| padding-left: 25px; |
| } |
| |
| .large-line-plots > div, .histogram-plots > div { |
| display: inline-block; |
| width: 400px; |
| height: 200px; |
| margin-right: 10px; |
| } |
| |
| .large-line-plot-labels > div, .histogram-plot-labels > div { |
| display: inline-block; |
| width: 400px; |
| height: 11px; |
| margin-right: 10px; |
| color: #545454; |
| text-align: center; |
| font-size: 11px; |
| } |
| |
| .closeButton { |
| background: #fff; |
| border: 1px solid black; |
| border-radius: 2px; |
| display: inline-block; |
| float: right; |
| line-height: 11px; |
| margin-top: 3px; |
| width: 11px; |
| } |
| |
| .closeButton:hover { |
| background: #F09C9C; |
| } |
| |
| .label { |
| cursor: text; |
| } |
| |
| .label:hover { |
| background: #ffcc66; |
| } |
| |
| section h1 { |
| text-align: center; |
| font-size: 1em; |
| } |
| |
| section .tooltip { |
| position: absolute; |
| text-align: center; |
| background: #ffcc66; |
| border-radius: 5px; |
| padding: 0px 5px; |
| } |
| |
| body { |
| padding: 0px; |
| margin: 0px; |
| font-family: sans-serif; |
| } |
| |
| table { |
| background: white; |
| width: 100%; |
| } |
| |
| table, td, th { |
| border-collapse: collapse; |
| padding: 5px; |
| white-space: nowrap; |
| } |
| |
| tr.even { |
| background: #f6f6f6; |
| } |
| |
| table td { |
| position: relative; |
| font-family: monospace; |
| } |
| |
| th, td { |
| cursor: pointer; |
| cursor: hand; |
| } |
| |
| th { |
| background: #e6eeee; |
| background: -webkit-gradient(linear, left top, left bottom, from(rgb(244, 244, 244)), to(rgb(217, 217, 217))); |
| border: 1px solid #ccc; |
| } |
| |
| th:after { |
| content: ' \25B8'; |
| } |
| |
| th.headerSortUp:after { |
| content: ' \25BE'; |
| } |
| |
| th.headerSortDown:after { |
| content: ' \25B4'; |
| } |
| |
| td.comparison, td.result { |
| text-align: right; |
| } |
| |
| td.better { |
| color: #6c6; |
| } |
| |
| td.worse { |
| color: #c66; |
| } |
| |
| td.missing { |
| color: #aaa; |
| text-align: center; |
| } |
| |
| .checkbox { |
| display: inline-block; |
| background: #eee; |
| background: -webkit-gradient(linear, left bottom, left top, from(rgb(220, 220, 220)), to(rgb(200, 200, 200))); |
| border: inset 1px #ddd; |
| border-radius: 5px; |
| margin: 10px; |
| font-size: small; |
| cursor: pointer; |
| cursor: hand; |
| -webkit-user-select: none; |
| font-weight: bold; |
| } |
| |
| .checkbox span { |
| display: inline-block; |
| line-height: 100%; |
| padding: 5px 8px; |
| border: outset 1px transparent; |
| } |
| |
| .checkbox .checked { |
| background: #e6eeee; |
| background: -webkit-gradient(linear, left top, left bottom, from(rgb(255, 255, 255)), to(rgb(235, 235, 235))); |
| border: outset 1px #eee; |
| border-radius: 5px; |
| } |
| |
| </style> |
| </head> |
| <body onload="init()"> |
| <div style="padding: 0 10px; white-space: nowrap;"> |
| Result <span id="time-memory" class="checkbox"><span class="checked">Time</span><span>Memory</span></span> |
| Reference <span id="reference" class="checkbox"></span> |
| Style <span id="scatter-line" class="checkbox"><span class="checked">Scatter</span><span>Line</span></span> |
| <span class="checkbox"><span class="checked" id="undelete">Undelete</span></span> |
| Run Telemetry with --reset-results to clear all runs |
| </div> |
| <table id="container"></table> |
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> |
| <script> |
| %plugins% |
| </script> |
| <script> |
| function TestResult(metric, values, associatedRun) { |
| if (values[0] instanceof Array) { |
| var flattenedValues = []; |
| for (var i = 0; i < values.length; i++) |
| flattenedValues = flattenedValues.concat(values[i]); |
| values = flattenedValues; |
| } |
| |
| if (jQuery.type(values[0]) === 'string') { |
| try { |
| var current = JSON.parse(values[0]); |
| if (current.params.type === 'HISTOGRAM') { |
| this.histogramValues = current; |
| // Histogram results have no values (per se). Instead we calculate |
| // the values from the histogram bins. |
| var values = []; |
| var buckets = current.buckets |
| for (var i = 0; i < buckets.length; i++) { |
| var bucket = buckets[i]; |
| var bucket_mean = (bucket.high + bucket.low) / 2; |
| for (var b = 0; b < bucket.count; b++) { |
| values.push(bucket_mean); |
| } |
| } |
| } |
| } |
| catch (e) { /* ignore, assume not a JSON string */ } |
| } |
| |
| this.test = function () { return metric; } |
| this.values = function () { return values.map(function (value) { return metric.scalingFactor() * value; }); } |
| this.unscaledMean = function () { return Statistics.sum(values) / values.length; } |
| this.mean = function () { return metric.scalingFactor() * this.unscaledMean(); } |
| this.min = function () { return metric.scalingFactor() * Statistics.min(values); } |
| this.max = function () { return metric.scalingFactor() * Statistics.max(values); } |
| this.confidenceIntervalDelta = function () { |
| return metric.scalingFactor() * Statistics.confidenceIntervalDelta(0.95, values.length, |
| Statistics.sum(values), Statistics.squareSum(values)); |
| } |
| this.confidenceIntervalDeltaRatio = function () { return this.confidenceIntervalDelta() / this.mean(); } |
| this.percentDifference = function(other) { return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean(); } |
| this.isStatisticallySignificant = function (other) { |
| var diff = Math.abs(other.mean() - this.mean()); |
| return diff > this.confidenceIntervalDelta() && diff > other.confidenceIntervalDelta(); |
| } |
| this.run = function () { return associatedRun; } |
| } |
| |
| function TestRun(entry) { |
| this.id = function() { return entry['buildTime']; } |
| this.revision = function () { return entry['revision']; } |
| this.label = function () { |
| if (labelKey in localStorage) |
| return localStorage[labelKey]; |
| if (entry['label']) |
| return entry['label']; |
| return 'r' + this.revision(); |
| } |
| this.setLabel = function(label) { localStorage[labelKey] = label; } |
| this.isHidden = function() { return localStorage[hiddenKey]; } |
| this.hide = function() { localStorage[hiddenKey] = true; } |
| this.show = function() { localStorage.removeItem(hiddenKey); } |
| this.description = function() { |
| var label = this.label(); |
| if (label != 'r' + this.revision()) |
| label = ' ' + label; |
| else |
| label = ''; |
| return new Date(entry['buildTime']).toLocaleString() + '\n' + entry['platform'] + ' r' + entry['revision'] + label; |
| } |
| |
| var labelKey = 'telemetry_label_' + this.id(); |
| var hiddenKey = 'telemetry_hide_' + this.id(); |
| } |
| |
| function PerfTestMetric(name, metric, unit, isImportant) { |
| var testResults = []; |
| var cachedUnit = null; |
| var cachedScalingFactor = null; |
| |
| // We can't do this in TestResult because all results for each test need to share the same unit and the same scaling factor. |
| function computeScalingFactorIfNeeded() { |
| // FIXME: We shouldn't be adjusting units on every test result. |
| // We can only do this on the first test. |
| if (!testResults.length || cachedUnit) |
| return; |
| |
| var mean = testResults[0].unscaledMean(); // FIXME: We should look at all values. |
| var kilo = unit == 'bytes' ? 1024 : 1000; |
| if (mean > 10 * kilo * kilo && unit != 'ms') { |
| cachedScalingFactor = 1 / kilo / kilo; |
| cachedUnit = 'M ' + unit; |
| } else if (mean > 10 * kilo) { |
| cachedScalingFactor = 1 / kilo; |
| cachedUnit = unit == 'ms' ? 's' : ('K ' + unit); |
| } else { |
| cachedScalingFactor = 1; |
| cachedUnit = unit; |
| } |
| } |
| |
| this.name = function () { return name + ':' + metric; } |
| this.isImportant = isImportant; |
| this.isMemoryTest = function () { |
| return (unit == 'kb' || |
| unit == 'KB' || |
| unit == 'MB' || |
| unit == 'bytes' || |
| unit == 'count' || |
| !metric.indexOf('V8.')); |
| } |
| this.addResult = function (newResult) { |
| testResults.push(newResult); |
| cachedUnit = null; |
| cachedScalingFactor = null; |
| } |
| this.results = function () { return testResults; } |
| this.scalingFactor = function() { |
| computeScalingFactorIfNeeded(); |
| return cachedScalingFactor; |
| } |
| this.unit = function () { |
| computeScalingFactorIfNeeded(); |
| return cachedUnit; |
| } |
| this.biggerIsBetter = function () { |
| if (window.unitToBiggerIsBetter == undefined) { |
| window.unitToBiggerIsBetter = {}; |
| var units = JSON.parse(document.getElementById('units-json').textContent); |
| for (var u in units) { |
| if (units[u].improvement_direction == 'up') { |
| window.unitToBiggerIsBetter[u] = true; |
| } |
| } |
| } |
| return window.unitToBiggerIsBetter[unit]; |
| } |
| } |
| |
| function UndeleteManager() { |
| var key = 'telemetry_undeleteIds' |
| var undeleteIds = localStorage[key]; |
| if (undeleteIds) { |
| undeleteIds = JSON.parse(undeleteIds); |
| } else { |
| undeleteIds = []; |
| } |
| |
| this.ondelete = function(id) { |
| undeleteIds.push(id); |
| localStorage[key] = JSON.stringify(undeleteIds); |
| } |
| this.undeleteMostRecent = function() { |
| if (!this.mostRecentlyDeletedId()) |
| return; |
| undeleteIds.pop(); |
| localStorage[key] = JSON.stringify(undeleteIds); |
| } |
| this.mostRecentlyDeletedId = function() { |
| if (!undeleteIds.length) |
| return undefined; |
| return undeleteIds[undeleteIds.length-1]; |
| } |
| } |
| var undeleteManager = new UndeleteManager(); |
| |
| var plotColor = 'rgb(230,50,50)'; |
| var subpointsPlotOptions = { |
| lines: {show:true, lineWidth: 0}, |
| color: plotColor, |
| points: {show: true, radius: 1}, |
| bars: {show: false}}; |
| |
| var mainPlotOptions = { |
| xaxis: { |
| min: -0.5, |
| tickSize: 1, |
| }, |
| crosshair: { mode: 'y' }, |
| series: { shadowSize: 0 }, |
| bars: {show: true, align: 'center', barWidth: 0.5}, |
| lines: { show: false }, |
| points: { show: true }, |
| grid: { |
| borderWidth: 1, |
| borderColor: '#ccc', |
| backgroundColor: '#fff', |
| hoverable: true, |
| autoHighlight: false, |
| } |
| }; |
| |
| var linePlotOptions = { |
| yaxis: { show: false }, |
| xaxis: { show: false }, |
| lines: { show: true }, |
| grid: { borderWidth: 1, borderColor: '#ccc' }, |
| colors: [ plotColor ] |
| }; |
| |
| var largeLinePlotOptions = { |
| xaxis: { |
| show: true, |
| tickDecimals: 0, |
| }, |
| lines: { show: true }, |
| grid: { borderWidth: 1, borderColor: '#ccc' }, |
| colors: [ plotColor ] |
| }; |
| |
| var histogramPlotOptions = { |
| bars: {show: true, fill: 1} |
| }; |
| |
| function createPlot(container, test, useLargeLinePlots) { |
| if (test.results()[0].histogramValues) { |
| var section = $('<section><div class="histogram-plots"></div>' |
| + '<div class="histogram-plot-labels"></div>' |
| + '<span class="tooltip"></span></section>'); |
| $(container).append(section); |
| attachHistogramPlots(test, section.children('.histogram-plots')); |
| } |
| else if (useLargeLinePlots) { |
| var section = $('<section><div class="large-line-plots"></div>' |
| + '<div class="large-line-plot-labels"></div>' |
| + '<span class="tooltip"></span></section>'); |
| $(container).append(section); |
| attachLinePlots(test, section.children('.large-line-plots'), useLargeLinePlots); |
| attachLinePlotLabels(test, section.children('.large-line-plot-labels')); |
| } else { |
| var section = $('<section><div class="plot"></div><div class="line-plots"></div>' |
| + '<span class="tooltip"></span></section>'); |
| section.children('.plot').css({'width': (100 * test.results().length + 25) + 'px', 'height': '300px'}); |
| $(container).append(section); |
| |
| var plotContainer = section.children('.plot'); |
| var minIsZero = true; |
| attachPlot(test, plotContainer, minIsZero); |
| |
| attachLinePlots(test, section.children('.line-plots'), useLargeLinePlots); |
| |
| var tooltip = section.children('.tooltip'); |
| plotContainer.bind('plothover', function (event, position, item) { |
| if (item) { |
| var postfix = item.series.id ? ' (' + item.series.id + ')' : ''; |
| tooltip.html(item.datapoint[1].toPrecision(4) + postfix); |
| var sectionOffset = $(section).offset(); |
| tooltip.css({left: item.pageX - sectionOffset.left - tooltip.outerWidth() / 2, top: item.pageY - sectionOffset.top + 10}); |
| tooltip.fadeIn(200); |
| } else |
| tooltip.hide(); |
| }); |
| plotContainer.mouseout(function () { |
| tooltip.hide(); |
| }); |
| plotContainer.click(function (event) { |
| event.preventDefault(); |
| minIsZero = !minIsZero; |
| attachPlot(test, plotContainer, minIsZero); |
| }); |
| } |
| return section; |
| } |
| |
| function attachLinePlots(test, container, useLargeLinePlots) { |
| var results = test.results(); |
| var attachedPlot = false; |
| |
| if (useLargeLinePlots) { |
| var maximum = 0; |
| for (var i = 0; i < results.length; i++) { |
| var values = results[i].values(); |
| if (!values) |
| continue; |
| var local_max = Math.max.apply(Math, values); |
| if (local_max > maximum) |
| maximum = local_max; |
| } |
| } |
| |
| for (var i = 0; i < results.length; i++) { |
| container.append('<div></div>'); |
| var values = results[i].values(); |
| if (!values) |
| continue; |
| attachedPlot = true; |
| |
| if (useLargeLinePlots) { |
| var options = $.extend(true, {}, largeLinePlotOptions, |
| {yaxis: {min: 0.0, max: maximum}, |
| xaxis: {min: 0.0, max: values.length - 1}, |
| points: {show: (values.length < 2) ? true : false}}); |
| } else { |
| var options = $.extend(true, {}, linePlotOptions, |
| {yaxis: {min: Math.min.apply(Math, values) * 0.9, max: Math.max.apply(Math, values) * 1.1}, |
| xaxis: {min: -0.5, max: values.length - 0.5}, |
| points: {show: (values.length < 2) ? true : false}}); |
| } |
| $.plot(container.children().last(), [values.map(function (value, index) { return [index, value]; })], options); |
| } |
| if (!attachedPlot) |
| container.children().remove(); |
| } |
| |
| function attachHistogramPlots(test, container) { |
| var results = test.results(); |
| var attachedPlot = false; |
| |
| for (var i = 0; i < results.length; i++) { |
| container.append('<div></div>'); |
| var histogram = results[i].histogramValues |
| if (!histogram) |
| continue; |
| attachedPlot = true; |
| |
| var buckets = histogram.buckets |
| var bucket; |
| var max_count = 0; |
| for (var j = 0; j < buckets.length; j++) { |
| bucket = buckets[j]; |
| max_count = Math.max(max_count, bucket.count); |
| } |
| var xmax = bucket.high * 1.1; |
| var ymax = max_count * 1.1; |
| |
| var options = $.extend(true, {}, histogramPlotOptions, |
| {yaxis: {min: 0.0, max: ymax}, |
| xaxis: {min: histogram.params.min, max: xmax}}); |
| var plot = $.plot(container.children().last(), [[]], options); |
| // Flot only supports fixed with bars and our histogram's buckets are |
| // variable width, so we need to do our own bar drawing. |
| var ctx = plot.getCanvas().getContext("2d"); |
| ctx.lineWidth="1"; |
| ctx.fillStyle = "rgba(255, 0, 0, 0.2)"; |
| ctx.strokeStyle="red"; |
| for (var j = 0; j < buckets.length; j++) { |
| bucket = buckets[j]; |
| var bl = plot.pointOffset({ x: bucket.low, y: 0}); |
| var tr = plot.pointOffset({ x: bucket.high, y: bucket.count}); |
| ctx.fillRect(bl.left, bl.top, tr.left - bl.left, tr.top - bl.top); |
| ctx.strokeRect(bl.left, bl.top, tr.left - bl.left, tr.top - bl.top); |
| } |
| } |
| if (!attachedPlot) |
| container.children().remove(); |
| } |
| |
| function attachLinePlotLabels(test, container) { |
| var results = test.results(); |
| var attachedPlot = false; |
| for (var i = 0; i < results.length; i++) { |
| container.append('<div>' + results[i].run().label() + '</div>'); |
| } |
| } |
| |
| function attachPlot(test, plotContainer, minIsZero) { |
| var results = test.results(); |
| |
| var values = results.reduce(function (values, result, index) { |
| var newValues = result.values(); |
| return newValues ? values.concat(newValues.map(function (value) { return [index, value]; })) : values; |
| }, []); |
| |
| var plotData = [$.extend(true, {}, subpointsPlotOptions, {data: values})]; |
| plotData.push({id: 'μ', data: results.map(function (result, index) { return [index, result.mean()]; }), color: plotColor}); |
| |
| var overallMax = Statistics.max(results.map(function (result, index) { return result.max(); })); |
| var overallMin = Statistics.min(results.map(function (result, index) { return result.min(); })); |
| var margin = (overallMax - overallMin) * 0.1; |
| var currentPlotOptions = $.extend(true, {}, mainPlotOptions, {yaxis: { |
| min: minIsZero ? 0 : overallMin - margin, |
| max: minIsZero ? overallMax * 1.1 : overallMax + margin}}); |
| |
| currentPlotOptions.xaxis.max = results.length - 0.5; |
| currentPlotOptions.xaxis.ticks = results.map(function (result, index) { return [index, result.run().label()]; }); |
| |
| $.plot(plotContainer, plotData, currentPlotOptions); |
| } |
| |
| function toFixedWidthPrecision(value) { |
| var decimal = value.toFixed(2); |
| return decimal; |
| } |
| |
| function formatPercentage(fraction) { |
| var percentage = fraction * 100; |
| return (fraction * 100).toFixed(2) + '%'; |
| } |
| |
| function createTable(tests, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots) { |
| $('#container').html('<thead><tr><th class="headerSortDown">Test</th><th>Unit</th>' + runs.map(function (run, index) { |
| return '<th colspan="' + (index == referenceIndex ? 2 : 3) + '" class="{sorter: \'comparison\'}" id="' + run.id() + '" title="' + run.description() + '"><span class="label" title="Edit run label">' + run.label() + '</span><div class="closeButton" title="Delete run">x</div></th>'; |
| }).reduce(function (markup, cell) { return markup + cell; }, '') + '</tr></head><tbody></tbody>'); |
| |
| var testNames = []; |
| for (testName in tests) |
| testNames.push(testName); |
| |
| window.isFirstImportantRow = true; |
| |
| testNames.sort().map(function (testName) { |
| var test = tests[testName]; |
| if (test.isMemoryTest() != shouldIgnoreMemory) |
| createTableRow(runs, test, referenceIndex, useLargeLinePlots); |
| }); |
| |
| $('.closeButton').click(function(event) { |
| for (var i = 0; i < runs.length; i++) { |
| if (runs[i].id() == event.target.parentNode.id) { |
| runs[i].hide(); |
| undeleteManager.ondelete(runs[i].id()); |
| location.reload(); |
| break; |
| } |
| } |
| event.stopPropagation(); |
| }); |
| |
| $('.label').click(function(event) { |
| for (var i = 0; i < runs.length; i++) { |
| if (runs[i].id() == event.target.parentNode.id) { |
| $(event.target).replaceWith('<input id="labelEditor" type="text" value="' + runs[i].label() + '">'); |
| $('#labelEditor').focusout(function() { |
| runs[i].setLabel(this.value); |
| location.reload(); |
| }); |
| $('#labelEditor').keypress(function(event) { |
| if (event.which == 13) { |
| runs[i].setLabel(this.value); |
| location.reload(); |
| } |
| }); |
| $('#labelEditor').click(function (event) { |
| event.stopPropagation(); |
| }); |
| $('#labelEditor').mousedown(function (event) { |
| event.stopPropagation(); |
| }); |
| $('#labelEditor').select(); |
| break; |
| } |
| } |
| event.stopPropagation(); |
| }); |
| |
| $('#container').tablesorter({widgets: ['zebra']}); |
| } |
| |
| function linearRegression(points) { |
| // Implement http://www.easycalculation.com/statistics/learn-correlation.php. |
| // x = magnitude |
| // y = iterations |
| var sumX = 0; |
| var sumY = 0; |
| var sumXSquared = 0; |
| var sumYSquared = 0; |
| var sumXTimesY = 0; |
| |
| for (var i = 0; i < points.length; i++) { |
| var x = i; |
| var y = points[i]; |
| sumX += x; |
| sumY += y; |
| sumXSquared += x * x; |
| sumYSquared += y * y; |
| sumXTimesY += x * y; |
| } |
| |
| var r = (points.length * sumXTimesY - sumX * sumY) / |
| Math.sqrt((points.length * sumXSquared - sumX * sumX) * |
| (points.length * sumYSquared - sumY * sumY)); |
| |
| if (isNaN(r) || r == Math.Infinity) |
| r = 0; |
| |
| var slope = (points.length * sumXTimesY - sumX * sumY) / (points.length * sumXSquared - sumX * sumX); |
| var intercept = sumY / points.length - slope * sumX / points.length; |
| return {slope: slope, intercept: intercept, rSquared: r * r}; |
| } |
| |
| var warningSign = '<svg viewBox="0 0 100 100" style="width: 18px; height: 18px; vertical-align: bottom;" version="1.1">' |
| + '<polygon fill="red" points="50,10 90,80 10,80 50,10" stroke="red" stroke-width="10" stroke-linejoin="round" />' |
| + '<polygon fill="white" points="47,30 48,29, 50, 28.7, 52,29 53,30 50,60" stroke="white" stroke-width="10" stroke-linejoin="round" />' |
| + '<circle cx="50" cy="73" r="6" fill="white" />' |
| + '</svg>'; |
| |
| function createTableRow(runs, test, referenceIndex, useLargeLinePlots) { |
| var tableRow = $('<tr><td class="test collapsed"' + (test.isImportant ? ' style="font-weight:bold"' : '') + '>' + test.name() + '</td><td class="unit">' + test.unit() + '</td></tr>'); |
| |
| function markupForRun(result, referenceResult) { |
| var comparisonCell = ''; |
| var hiddenValue = ''; |
| var shouldCompare = result !== referenceResult; |
| if (shouldCompare && referenceResult) { |
| var percentDifference = referenceResult.percentDifference(result); |
| var better = test.biggerIsBetter() ? percentDifference > 0 : percentDifference < 0; |
| var comparison = ''; |
| var className = 'comparison'; |
| if (referenceResult.isStatisticallySignificant(result)) { |
| comparison = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse '); |
| className += better ? ' better' : ' worse'; |
| } |
| hiddenValue = '<span style="display: none">|' + comparison + '</span>'; |
| comparisonCell = '<td class="' + className + '">' + comparison + '</td>'; |
| } else if (shouldCompare) |
| comparisonCell = '<td class="comparison"></td>'; |
| |
| var values = result.values(); |
| var warning = ''; |
| var regressionAnalysis = ''; |
| if (result.histogramValues) { |
| // Don't calculate regression result for histograms. |
| } |
| else if (values && values.length > 3) { |
| regressionResult = linearRegression(values); |
| regressionAnalysis = 'slope=' + toFixedWidthPrecision(regressionResult.slope) |
| + ', R^2=' + toFixedWidthPrecision(regressionResult.rSquared); |
| if (regressionResult.rSquared > 0.6 && Math.abs(regressionResult.slope) > 0.01) { |
| warning = ' <span class="regression-warning" title="Detected a time dependency with ' + regressionAnalysis + '">' + warningSign + ' </span>'; |
| } |
| } |
| |
| var statistics = 'σ=' + toFixedWidthPrecision(result.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result.min()) |
| + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysis; |
| |
| // Tablesorter doesn't know about the second cell so put the comparison in the invisible element. |
| return '<td class="result" title="' + statistics + '">' + toFixedWidthPrecision(result.mean()) + hiddenValue |
| + '</td><td class="confidenceIntervalDelta" title="' + statistics + '">± ' |
| + formatPercentage(result.confidenceIntervalDeltaRatio()) + warning + '</td>' + comparisonCell; |
| } |
| |
| function markupForMissingRun(isReference) { |
| return '<td colspan="' + (isReference ? 2 : 3) + '" class="missing">Missing</td>'; |
| } |
| |
| var runIndex = 0; |
| var results = test.results(); |
| var referenceResult = undefined; |
| var resultIndexMap = {}; |
| for (var i = 0; i < results.length; i++) { |
| while (runs[runIndex] !== results[i].run()) |
| runIndex++; |
| if (runIndex == referenceIndex) |
| referenceResult = results[i]; |
| resultIndexMap[runIndex] = i; |
| } |
| for (var i = 0; i < runs.length; i++) { |
| var resultIndex = resultIndexMap[i]; |
| if (resultIndex == undefined) |
| tableRow.append(markupForMissingRun(i == referenceIndex)); |
| else |
| tableRow.append(markupForRun(results[resultIndex], referenceResult)); |
| } |
| |
| $('#container').children('tbody').last().append(tableRow); |
| |
| function toggle() { |
| var firstCell = tableRow.children('td').first(); |
| if (firstCell.children('section').length) { |
| firstCell.children('section').remove(); |
| tableRow.children('td').css({'padding-bottom': ''}); |
| tableRow.children('td').first().addClass('collapsed'); |
| tableRow.children('td').first().removeClass('expanded'); |
| } else { |
| var plot = createPlot(firstCell, test, useLargeLinePlots); |
| plot.css({'position': 'absolute', 'z-index': 2}); |
| var offset = tableRow.offset(); |
| offset.left += 1; |
| offset.top += tableRow.outerHeight(); |
| plot.offset(offset); |
| tableRow.children('td').css({'padding-bottom': plot.outerHeight() + 5}); |
| tableRow.children('td').first().removeClass('collapsed'); |
| tableRow.children('td').first().addClass('expanded'); |
| } |
| |
| return false; |
| }; |
| |
| tableRow.click(function(event) { |
| if (event.target != tableRow[0] && event.target.parentNode != tableRow[0]) |
| return; |
| |
| event.preventDefault(); |
| |
| toggle(); |
| }); |
| |
| if (test.isImportant && window.isFirstImportantRow) { |
| window.isFirstImportantRow = false; |
| toggle(); |
| } |
| } |
| |
| function init() { |
| $.tablesorter.addParser({ |
| id: 'comparison', |
| is: function(s) { |
| return s.indexOf('|') >= 0; |
| }, |
| format: function(s) { |
| var parsed = parseFloat(s.substring(s.indexOf('|') + 1)); |
| return isNaN(parsed) ? 0 : parsed; |
| }, |
| type: 'numeric', |
| }); |
| |
| var runs = []; |
| var metrics = {}; |
| var deletedRunsById = {}; |
| $.each(JSON.parse(document.getElementById('results-json').textContent), function (index, entry) { |
| var run = new TestRun(entry); |
| if (run.isHidden()) { |
| deletedRunsById[run.id()] = run; |
| return; |
| } |
| |
| runs.push(run); |
| |
| function addTests(tests) { |
| for (var testName in tests) { |
| var rawMetrics = tests[testName].metrics; |
| |
| for (var metricName in rawMetrics) { |
| var fullMetricName = testName + ':' + metricName; |
| var metric = metrics[fullMetricName]; |
| if (!metric) { |
| metric = new PerfTestMetric(testName, metricName, rawMetrics[metricName].units, rawMetrics[metricName].important); |
| metrics[fullMetricName] = metric; |
| } |
| metric.addResult(new TestResult(metric, rawMetrics[metricName].current, run)); |
| } |
| } |
| } |
| |
| addTests(entry.tests); |
| }); |
| |
| var useLargeLinePlots = false; |
| var shouldIgnoreMemory= true; |
| var referenceIndex = 0; |
| |
| createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); |
| |
| $('#time-memory').bind('change', function (event, checkedElement) { |
| shouldIgnoreMemory = checkedElement.textContent == 'Time'; |
| createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); |
| }); |
| |
| $('#scatter-line').bind('change', function (event, checkedElement) { |
| useLargeLinePlots = checkedElement.textContent == 'Line'; |
| createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); |
| }); |
| |
| runs.map(function (run, index) { |
| $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + ' title="' + run.description() + '">' + run.label() + '</span>'); |
| }) |
| |
| $('#reference').bind('change', function (event, checkedElement) { |
| referenceIndex = parseInt(checkedElement.getAttribute('value')); |
| createTable(metrics, runs, shouldIgnoreMemory, referenceIndex, useLargeLinePlots); |
| }); |
| |
| $('.checkbox').each(function (index, checkbox) { |
| $(checkbox).children('span').click(function (event) { |
| if ($(this).hasClass('checked')) |
| return; |
| $(checkbox).children('span').removeClass('checked'); |
| $(this).addClass('checked'); |
| $(checkbox).trigger('change', $(this)); |
| }); |
| }); |
| |
| if (undeleteManager.mostRecentlyDeletedId()) { |
| $('#undelete').html('Undelete ' + deletedRunsById[undeleteManager.mostRecentlyDeletedId()].label()); |
| $('#undelete').attr('title', deletedRunsById[undeleteManager.mostRecentlyDeletedId()].description()); |
| $('#undelete').click(function (event) { |
| deletedRunsById[undeleteManager.mostRecentlyDeletedId()].show(); |
| undeleteManager.undeleteMostRecent(); |
| location.reload(); |
| }); |
| } else { |
| $('#undelete').hide(); |
| } |
| } |
| |
| </script> |
| <script id="results-json" type="application/json">%json_results%</script> |
| <script id="units-json" type="application/json">%json_units%</script> |
| </body> |
| </html> |