Index: tools/telemetry/support/html_output/results-template.html |
diff --git a/tools/telemetry/support/html_output/results-template.html b/tools/telemetry/support/html_output/results-template.html |
deleted file mode 100644 |
index b27c08003bb1bff6b8e2dd0d3c54e9303c80f8db..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/support/html_output/results-template.html |
+++ /dev/null |
@@ -1,1488 +0,0 @@ |
-<!DOCTYPE html> |
-<html> |
-<head> |
-<title>Telemetry Performance Test Results</title> |
-<style type="text/css"> |
- |
-section { |
- background: white; |
- padding: 10px; |
- position: relative; |
-} |
- |
-.collapsed:before { |
- color: #ccc; |
- content: '\25B8\00A0'; |
-} |
- |
-.expanded:before { |
- color: #eee; |
- 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 { |
- display: inline-block; |
- background: #eee; |
- background: linear-gradient(rgb(220, 220, 220), rgb(255, 255, 255)); |
- border: inset 1px #ddd; |
- border-radius: 4px; |
- float: right; |
- font-size: small; |
- -webkit-user-select: none; |
- font-weight: bold; |
- padding: 1px 4px; |
-} |
- |
-.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; |
-} |
- |
-.highlight:hover { |
- color: #202020; |
- background: #e0e0e0; |
-} |
- |
-.nestedRow { |
- background: #f8f8f8; |
-} |
- |
-.importantNestedRow { |
- background: #e0e0e0; |
- font-weight: bold; |
-} |
- |
-table td { |
- position: relative; |
-} |
- |
-th, td { |
- cursor: pointer; |
- cursor: hand; |
-} |
- |
-th { |
- background: #e6eeee; |
- background: linear-gradient(rgb(244, 244, 244), rgb(217, 217, 217)); |
- border: 1px solid #ccc; |
-} |
- |
-th.sortUp:after { |
- content: ' \25BE'; |
-} |
- |
-th.sortDown:after { |
- content: ' \25B4'; |
-} |
- |
-td.comparison, td.result { |
- text-align: right; |
-} |
- |
-td.better { |
- color: #6c6; |
-} |
- |
-td.fadeOut { |
- opacity: 0.5; |
-} |
- |
-td.unknown { |
- color: #ccc; |
-} |
- |
-td.worse { |
- color: #c66; |
-} |
- |
-td.reference { |
- font-style: italic; |
- font-weight: bold; |
- color: #444; |
-} |
- |
-td.missing { |
- color: #ccc; |
- text-align: center; |
-} |
- |
-td.missingReference { |
- color: #ccc; |
- text-align: center; |
- font-style: italic; |
-} |
- |
-.checkbox { |
- display: inline-block; |
- background: #eee; |
- background: linear-gradient(rgb(220, 220, 220), 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: linear-gradient(rgb(255, 255, 255), rgb(235, 235, 235)); |
- border: outset 1px #eee; |
- border-radius: 5px; |
-} |
- |
-.openAllButton { |
- display: inline-block; |
- colour: #6c6 |
- background: #eee; |
- background: linear-gradient(rgb(220, 220, 220), rgb(255, 255, 255)); |
- border: inset 1px #ddd; |
- border-radius: 5px; |
- float: left; |
- font-size: small; |
- -webkit-user-select: none; |
- font-weight: bold; |
- padding: 1px 4px; |
-} |
- |
-.openAllButton:hover { |
- background: #60f060; |
-} |
- |
-.closeAllButton { |
- display: inline-block; |
- colour: #c66 |
- background: #eee; |
- background: linear-gradient(rgb(220, 220, 220),rgb(255, 255, 255)); |
- border: inset 1px #ddd; |
- border-radius: 5px; |
- float: left; |
- font-size: small; |
- -webkit-user-select: none; |
- font-weight: bold; |
- padding: 1px 4px; |
-} |
- |
-.closeAllButton:hover { |
- background: #f04040; |
-} |
- |
-</style> |
-</head> |
-<body onload="init()"> |
-<div style="padding: 0 10px; white-space: nowrap;"> |
-Result <span id="time-memory" class="checkbox"></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><br> |
-Run your test 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> |
- |
-var EXPANDED = true; |
-var COLLAPSED = false; |
-var SMALLEST_PERCENT_DISPLAYED = 0.01; |
-var INVISIBLE = false; |
-var VISIBLE = true; |
-var COMPARISON_SUFFIX = '_compare'; |
-var SORT_DOWN_CLASS = 'sortDown'; |
-var SORT_UP_CLASS = 'sortUp'; |
-var BETTER_CLASS = 'better'; |
-var WORSE_CLASS = 'worse'; |
-var UNKNOWN_CLASS = 'unknown' |
-// px Indentation for graphs |
-var GRAPH_INDENT = 64; |
-var PADDING_UNDER_GRAPH = 5; |
-// px Indentation for nested children left-margins |
-var INDENTATION = 40; |
- |
-function TestResult(metric, values, associatedRun, std, degreesOfFreedom) { |
- if (values) { |
- 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) { |
- console.error(e, e.stack); |
- } |
- } |
- } else { |
- values = []; |
- } |
- |
- 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() { |
- if (std !== undefined) { |
- return metric.scalingFactor() * Statistics.confidenceIntervalDeltaFromStd(0.95, values.length, |
- std, degreesOfFreedom); |
- } |
- 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) { |
- if (other === undefined) { |
- return undefined; |
- } |
- return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean(); |
- } |
- this.isStatisticallySignificant = function(other) { |
- if (other === undefined) { |
- return false; |
- } |
- 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'].replace(/[:.-]/g,''); } |
- this.label = function() { |
- if (labelKey in localStorage) |
- return localStorage[labelKey]; |
- return entry['label']; |
- } |
- 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() { |
- return new Date(entry['buildTime']).toLocaleString() + '\n' + entry['platform'] + ' ' + this.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 setUpSortClicks(runs) |
-{ |
- $('#nameColumn').click(sortByName); |
- |
- $('#unitColumn').click(sortByUnit); |
- |
- runs.forEach(function(run) { |
- $('#' + run.id()).click(sortByResult); |
- $('#' + run.id() + COMPARISON_SUFFIX).click(sortByReference); |
- }); |
-} |
- |
-function TestTypeSelector(tests) { |
- this.recognizers = { |
- 'Time': function(test) { return test.isMemoryTest(); }, |
- 'Memory': function(test) { return !test.isMemoryTest(); } |
- }; |
- this.testTypeNames = this.generateUsedTestTypeNames(tests); |
- // Default to selecting the first test-type name in the list. |
- this.testTypeName = this.testTypeNames[0]; |
-} |
- |
-TestTypeSelector.prototype = { |
- set testTypeName(testTypeName) { |
- this._testTypeName = testTypeName; |
- this.shouldShowTest = this.recognizers[testTypeName]; |
- }, |
- |
- generateUsedTestTypeNames: function(allTests) { |
- var testTypeNames = []; |
- |
- for (var recognizedTestName in this.recognizers) { |
- var recognizes = this.recognizers[recognizedTestName]; |
- for (var testName in allTests) { |
- var test = allTests[testName]; |
- if (recognizes(test)) { |
- testTypeNames.push(recognizedTestName); |
- break; |
- } |
- } |
- } |
- |
- if (testTypeNames.length === 0) { |
- // No test types we recognize, add 'No Results' with a dummy recognizer. |
- var noResults = 'No Results'; |
- this.recognizers[noResults] = function() { return false; }; |
- testTypeNames.push(noResults); |
- } else if (testTypeNames.length > 1) { |
- // We have more than one test type, so add 'All' with a recognizer that always succeeds. |
- var allResults = 'All'; |
- this.recognizers[allResults] = function() { return true; }; |
- testTypeNames.push(allResults); |
- } |
- |
- return testTypeNames; |
- }, |
- |
- buildButtonHTMLForUsedTestTypes: function() { |
- var selectedTestTypeName = this._testTypeName; |
- // Build spans for all recognised test names with the selected test highlighted. |
- return this.testTypeNames.map(function(testTypeName) { |
- var classAttribute = testTypeName === selectedTestTypeName ? ' class=checked' : ''; |
- return '<span' + classAttribute + '>' + testTypeName + '</span>'; |
- }).join(''); |
- } |
-}; |
- |
-var topLevelRows; |
-var allTableRows; |
- |
-function displayTable(tests, runs, testTypeSelector, referenceIndex, useLargeLinePlots) { |
- var resultHeaders = runs.map(function(run, index) { |
- var header = '<th id="' + run.id() + '" ' + |
- 'colspan=2 ' + |
- 'title="' + run.description() + '">' + |
- '<span class="label" ' + |
- 'title="Edit run label">' + |
- run.label() + |
- '</span>' + |
- '<div class="closeButton" ' + |
- 'title="Delete run">' + |
- '×' + |
- '</div>' + |
- '</th>'; |
- if (index !== referenceIndex) { |
- header += '<th id="' + run.id() + COMPARISON_SUFFIX + '" ' + |
- 'title="Sort by better/worse">' + |
- 'Δ' + |
- '</th>'; |
- } |
- return header; |
- }); |
- |
- resultHeaders = resultHeaders.join(''); |
- |
- htmlString = '<thead>' + |
- '<tr>' + |
- '<th id="nameColumn">' + |
- '<div class="openAllButton" ' + |
- 'title="Open all rows or graphs">' + |
- 'Open All' + |
- '</div>' + |
- '<div class="closeAllButton" ' + |
- 'title="Close all rows">' + |
- 'Close All' + |
- '</div>' + |
- 'Test' + |
- '</th>' + |
- '<th id="unitColumn">' + |
- 'Unit' + |
- '</th>' + |
- resultHeaders + |
- '</tr>' + |
- '</head>' + |
- '<tbody>' + |
- '</tbody>'; |
- |
- $('#container').html(htmlString); |
- |
- var testNames = []; |
- for (testName in tests) |
- testNames.push(testName); |
- |
- allTableRows = []; |
- testNames.forEach(function(testName) { |
- var test = tests[testName]; |
- if (testTypeSelector.shouldShowTest(test)) { |
- allTableRows.push(new TableRow(runs, test, referenceIndex, useLargeLinePlots)); |
- } |
- }); |
- |
- // Build a list of top level rows with attached children |
- topLevelRows = []; |
- allTableRows.forEach(function(row) { |
- // Add us to top level if we are a top-level row... |
- if (row.hasNoURL) { |
- topLevelRows.push(row); |
- // Add a duplicate child row that holds the graph for the parent |
- var graphHolder = new TableRow(runs, row.test, referenceIndex, useLargeLinePlots); |
- graphHolder.isImportant = true; |
- graphHolder.URL = 'Summary'; |
- graphHolder.hideRowData(); |
- allTableRows.push(graphHolder); |
- row.addNestedChild(graphHolder); |
- return; |
- } |
- |
- // ...or add us to our parent if we have one ... |
- for (var i = 0; i < allTableRows.length; i++) { |
- if (allTableRows[i].isParentOf(row)) { |
- allTableRows[i].addNestedChild(row); |
- return; |
- } |
- } |
- |
- // ...otherwise this result is orphaned, display it at top level with a graph |
- row.hasGraph = true; |
- topLevelRows.push(row); |
- }); |
- |
- buildTable(topLevelRows); |
- |
- $('.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(); |
- }); |
- |
- $('.closeAllButton').click(function(event) { |
- for (var i = 0; i < allTableRows.length; i++) { |
- allTableRows[i].closeRow(); |
- } |
- event.stopPropagation(); |
- }); |
- |
- $('.openAllButton').click(function(event) { |
- for (var i = 0; i < topLevelRows.length; i++) { |
- topLevelRows[i].openRow(); |
- } |
- event.stopPropagation(); |
- }); |
- |
- setUpSortClicks(runs); |
- |
- $('.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(); |
- }); |
-} |
- |
-function validForSorting(row) { |
- return ($.type(row.sortValue) === 'string') || !isNaN(row.sortValue); |
-} |
- |
-var sortDirection = 1; |
- |
-function sortRows(rows) { |
- rows.sort( |
- function(rowA,rowB) { |
- if (validForSorting(rowA) !== validForSorting(rowB)) { |
- // Sort valid values upwards when compared to invalid |
- if (validForSorting(rowA)) { |
- return -1; |
- } |
- if (validForSorting(rowB)) { |
- return 1; |
- } |
- } |
- |
- // Some rows always sort to the top |
- if (rowA.isImportant) { |
- return -1; |
- } |
- if (rowB.isImportant) { |
- return 1; |
- } |
- |
- if (rowA.sortValue === rowB.sortValue) { |
- // Sort identical values by name to keep the sort stable, |
- // always keep name alphabetical (even if a & b sort values |
- // are invalid) |
- return rowA.test.name() > rowB.test.name() ? 1 : -1; |
- } |
- |
- return rowA.sortValue > rowB.sortValue ? sortDirection : -sortDirection; |
- } ); |
- |
- // Sort the rows' children |
- rows.forEach(function(row) { |
- sortRows(row.children); |
- }); |
-} |
- |
-function buildTable(rows) { |
- rows.forEach(function(row) { |
- row.removeFromPage(); |
- }); |
- |
- sortRows(rows); |
- |
- rows.forEach(function(row) { |
- row.addToPage(); |
- }); |
-} |
- |
-var activeSortHeaderElement = undefined; |
-var columnSortDirection = {}; |
- |
-function determineColumnSortDirection(element) { |
- columnDirection = columnSortDirection[element.id]; |
- |
- if (columnDirection === undefined) { |
- // First time we've sorted this row, default to down |
- columnSortDirection[element.id] = SORT_DOWN_CLASS; |
- } else if (element === activeSortHeaderElement) { |
- // Clicking on same header again, swap direction |
- columnSortDirection[element.id] = (columnDirection === SORT_UP_CLASS) ? SORT_DOWN_CLASS : SORT_UP_CLASS; |
- } |
-} |
- |
-function updateSortDirection(element) { |
- // Remove old header's sort arrow |
- if (activeSortHeaderElement !== undefined) { |
- activeSortHeaderElement.classList.remove(columnSortDirection[activeSortHeaderElement.id]); |
- } |
- |
- determineColumnSortDirection(element); |
- |
- sortDirection = (columnSortDirection[element.id] === SORT_UP_CLASS) ? 1 : -1; |
- |
- // Add new header's sort arrow |
- element.classList.add(columnSortDirection[element.id]); |
- activeSortHeaderElement = element; |
-} |
- |
-function sortByName(event) { |
- updateSortDirection(event.toElement); |
- |
- allTableRows.forEach(function(row) { |
- row.prepareToSortByName(); |
- }); |
- |
- buildTable(topLevelRows); |
-} |
- |
-function sortByUnit(event) { |
- updateSortDirection(event.toElement); |
- |
- allTableRows.forEach(function(row) { |
- row.prepareToSortByUnit(); |
- }); |
- |
- buildTable(topLevelRows); |
-} |
- |
-function sortByResult(event) { |
- updateSortDirection(event.toElement); |
- |
- var runId = event.target.id; |
- |
- allTableRows.forEach(function(row) { |
- row.prepareToSortByTestResults(runId); |
- }); |
- |
- buildTable(topLevelRows); |
-} |
- |
-function sortByReference(event) { |
- updateSortDirection(event.toElement); |
- |
- // The element ID has _compare appended to allow us to set up a click event |
- // remove the _compare to return a useful Id |
- var runIdWithCompare = event.target.id; |
- var runId = runIdWithCompare.split('_')[0]; |
- |
- allTableRows.forEach(function(row) { |
- row.prepareToSortRelativeToReference(runId); |
- }); |
- |
- buildTable(topLevelRows); |
-} |
- |
-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 TableRow(runs, test, referenceIndex, useLargeLinePlots) { |
- this.runs = runs; |
- this.test = test; |
- this.referenceIndex = referenceIndex; |
- this.useLargeLinePlots = useLargeLinePlots; |
- this.children = []; |
- |
- this.tableRow = $('<tr class="highlight">' + |
- '<td class="test collapsed" >' + |
- this.test.name() + |
- '</td>' + |
- '<td class="unit">' + |
- this.test.unit() + |
- '</td>' + |
- '</tr>'); |
- |
- var runIndex = 0; |
- var results = this.test.results(); |
- var referenceResult = undefined; |
- |
- this.resultIndexMap = {}; |
- for (var i = 0; i < results.length; i++) { |
- while (this.runs[runIndex] !== results[i].run()) |
- runIndex++; |
- if (runIndex === this.referenceIndex) |
- referenceResult = results[i]; |
- this.resultIndexMap[runIndex] = i; |
- } |
- for (var i = 0; i < this.runs.length; i++) { |
- var resultIndex = this.resultIndexMap[i]; |
- if (resultIndex === undefined) |
- this.tableRow.append(this.markupForMissingRun(i == this.referenceIndex)); |
- else |
- this.tableRow.append(this.markupForRun(results[resultIndex], referenceResult)); |
- } |
- |
- // Use the test name (without URL) to bind parents and their children |
- var nameAndURL = this.test.name().split('.'); |
- var benchmarkName = nameAndURL.shift(); |
- this.testName = nameAndURL.shift(); |
- this.hasNoURL = (nameAndURL.length === 0); |
- |
- if (!this.hasNoURL) { |
- // Re-join the URL |
- this.URL = nameAndURL.join('.'); |
- } |
- |
- this.isImportant = false; |
- this.hasGraph = false; |
- this.currentIndentationClass = '' |
- this.indentLevel = 0; |
- this.setRowNestedState(COLLAPSED); |
- this.setVisibility(VISIBLE); |
- this.prepareToSortByName(); |
-} |
- |
-TableRow.prototype.hideRowData = function() { |
- data = this.tableRow.children('td'); |
- |
- for (index in data) { |
- if (index > 0) { |
- // Blank out everything except the test name |
- data[index].innerHTML = ''; |
- } |
- } |
-} |
- |
-TableRow.prototype.prepareToSortByTestResults = function(runId) { |
- var testResults = this.test.results(); |
- // Find the column in this row that matches the runId and prepare to |
- // sort by the mean of that test. |
- for (index in testResults) { |
- sourceId = testResults[index].run().id(); |
- if (runId === sourceId) { |
- this.sortValue = testResults[index].mean(); |
- return; |
- } |
- } |
- // This row doesn't have any results for the passed runId |
- this.sortValue = undefined; |
-} |
- |
-TableRow.prototype.prepareToSortRelativeToReference = function(runId) { |
- var testResults = this.test.results(); |
- |
- // Get index of test results that correspond to the reference column. |
- var remappedReferenceIndex = this.resultIndexMap[this.referenceIndex]; |
- |
- if (remappedReferenceIndex === undefined) { |
- // This test has no results in the reference run. |
- this.sortValue = undefined; |
- return; |
- } |
- |
- otherResults = testResults[remappedReferenceIndex]; |
- |
- // Find the column in this row that matches the runId and prepare to |
- // sort by the difference from the reference. |
- for (index in testResults) { |
- sourceId = testResults[index].run().id(); |
- if (runId === sourceId) { |
- this.sortValue = testResults[index].percentDifference(otherResults); |
- if (this.test.biggerIsBetter()) { |
- // For this test bigger is not better |
- this.sortValue = -this.sortValue; |
- } |
- return; |
- } |
- } |
- // This row doesn't have any results for the passed runId |
- this.sortValue = undefined; |
-} |
- |
-TableRow.prototype.prepareToSortByUnit = function() { |
- this.sortValue = this.test.unit().toLowerCase(); |
-} |
- |
-TableRow.prototype.prepareToSortByName = function() { |
- this.sortValue = this.test.name().toLowerCase(); |
-} |
- |
-TableRow.prototype.isParentOf = function(row) { |
- return this.hasNoURL && (this.testName === row.testName); |
-} |
- |
-TableRow.prototype.addNestedChild = function(child) { |
- this.children.push(child); |
- |
- // Indent child one step in from parent |
- child.indentLevel = this.indentLevel + INDENTATION; |
- child.hasGraph = true; |
- // Start child off as hidden (i.e. collapsed inside parent) |
- child.setVisibility(INVISIBLE); |
- child.updateIndentation(); |
- // Show URL in the title column |
- child.tableRow.children()[0].innerHTML = child.URL; |
- // Set up class to change background colour of nested rows |
- if (child.isImportant) { |
- child.tableRow.addClass('importantNestedRow'); |
- } else { |
- child.tableRow.addClass('nestedRow'); |
- } |
-} |
- |
-TableRow.prototype.setVisibility = function(visibility) { |
- this.visibility = visibility; |
- this.tableRow[0].style.display = (visibility === INVISIBLE) ? 'none' : ''; |
-} |
- |
-TableRow.prototype.setRowNestedState = function(newState) { |
- this.rowState = newState; |
- this.updateIndentation(); |
-} |
- |
-TableRow.prototype.updateIndentation = function() { |
- var element = this.tableRow.children('td').first(); |
- |
- element.removeClass(this.currentIndentationClass); |
- |
- this.currentIndentationClass = (this.rowState === COLLAPSED) ? 'collapsed' : 'expanded'; |
- |
- element[0].style.marginLeft = this.indentLevel.toString() + 'px'; |
- element[0].style.float = 'left'; |
- |
- element.addClass(this.currentIndentationClass); |
-} |
- |
-TableRow.prototype.addToPage = function() { |
- $('#container').children('tbody').last().append(this.tableRow); |
- |
- // Set up click callback |
- var owningObject = this; |
- this.tableRow.click(function(event) { |
- event.preventDefault(); |
- owningObject.toggle(); |
- }); |
- |
- // Add children to the page too |
- this.children.forEach(function(child) { |
- child.addToPage(); |
- }); |
-} |
- |
-TableRow.prototype.removeFromPage = function() { |
- // Remove children |
- this.children.forEach(function(child) { |
- child.removeFromPage(); |
- }); |
- // Remove us |
- this.tableRow.remove(); |
-} |
- |
- |
-TableRow.prototype.markupForRun = function(result, referenceResult) { |
- var comparisonCell = ''; |
- var shouldCompare = result !== referenceResult; |
- if (shouldCompare) { |
- var comparisonText = ''; |
- var className = ''; |
- |
- if (referenceResult) { |
- var percentDifference = referenceResult.percentDifference(result); |
- if (isNaN(percentDifference)) { |
- comparisonText = 'Unknown'; |
- className = UNKNOWN_CLASS; |
- } else if (Math.abs(percentDifference) < SMALLEST_PERCENT_DISPLAYED) { |
- comparisonText = 'Equal'; |
- // Show equal values in green |
- className = BETTER_CLASS; |
- } else { |
- var better = this.test.biggerIsBetter() ? percentDifference > 0 : percentDifference < 0; |
- comparisonText = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse'); |
- className = better ? BETTER_CLASS : WORSE_CLASS; |
- } |
- |
- if (!referenceResult.isStatisticallySignificant(result)) { |
- // Put result in brackets and fade if not statistically significant |
- className += ' fadeOut'; |
- comparisonText = '(' + comparisonText + ')'; |
- } |
- } |
- comparisonCell = '<td class="comparison ' + className + '">' + comparisonText + '</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 referenceClass = shouldCompare ? '' : 'reference'; |
- |
- var statistics = 'σ=' + toFixedWidthPrecision(result.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result.min()) |
- + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysis; |
- |
- var confidence; |
- if (isNaN(result.confidenceIntervalDeltaRatio())) { |
- // Don't bother showing +- Nan as it is meaningless |
- confidence = ''; |
- } else { |
- confidence = '± ' + formatPercentage(result.confidenceIntervalDeltaRatio()); |
- } |
- |
- return '<td class="result ' + referenceClass + '" title="' + statistics + '">' + toFixedWidthPrecision(result.mean()) |
- + '</td><td class="confidenceIntervalDelta ' + referenceClass + '" title="' + statistics + '">' + confidence + warning + '</td>' + comparisonCell; |
-} |
- |
-TableRow.prototype.markupForMissingRun = function(isReference) { |
- if (isReference) { |
- return '<td colspan=2 class="missingReference">Missing</td>'; |
- } |
- return '<td colspan=3 class="missing">Missing</td>'; |
-} |
- |
-TableRow.prototype.openRow = function() { |
- if (this.rowState === EXPANDED) { |
- // If we're already expanded, open our children instead |
- this.children.forEach(function(child) { |
- child.openRow(); |
- }); |
- return; |
- } |
- |
- this.setRowNestedState(EXPANDED); |
- |
- if (this.hasGraph) { |
- var firstCell = this.tableRow.children('td').first(); |
- var plot = createPlot(firstCell, this.test, this.useLargeLinePlots); |
- plot.css({'position': 'absolute', 'z-index': 2}); |
- var offset = this.tableRow.offset(); |
- offset.left += GRAPH_INDENT; |
- offset.top += this.tableRow.outerHeight(); |
- plot.offset(offset); |
- this.tableRow.children('td').css({'padding-bottom': plot.outerHeight() + PADDING_UNDER_GRAPH}); |
- } |
- |
- this.children.forEach(function(child) { |
- child.setVisibility(VISIBLE); |
- }); |
- |
- if (this.children.length === 1) { |
- // If we only have a single child... |
- var child = this.children[0]; |
- if (child.isImportant) { |
- // ... and it is important (i.e. the summary row) just open it when |
- // parent is opened to save needless clicking |
- child.openRow(); |
- } |
- } |
-} |
- |
-TableRow.prototype.closeRow = function() { |
- if (this.rowState === COLLAPSED) { |
- return; |
- } |
- |
- this.setRowNestedState(COLLAPSED); |
- |
- if (this.hasGraph) { |
- var firstCell = this.tableRow.children('td').first(); |
- firstCell.children('section').remove(); |
- this.tableRow.children('td').css({'padding-bottom': ''}); |
- } |
- |
- this.children.forEach(function(child) { |
- // Make children invisible, but leave their collapsed status alone |
- child.setVisibility(INVISIBLE); |
- }); |
-} |
- |
-TableRow.prototype.toggle = function() { |
- if (this.rowState === EXPANDED) { |
- this.closeRow(); |
- } else { |
- this.openRow(); |
- } |
- return false; |
-} |
- |
-function init() { |
- 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; |
- } |
- // std & degrees_of_freedom could be undefined |
- metric.addResult( |
- new TestResult(metric, rawMetrics[metricName].current, |
- run, rawMetrics[metricName]['std'], rawMetrics[metricName]['degrees_of_freedom'])); |
- } |
- } |
- } |
- |
- addTests(entry.tests); |
- }); |
- |
- var useLargeLinePlots = false; |
- var referenceIndex = 0; |
- |
- var testTypeSelector = new TestTypeSelector(metrics); |
- var buttonHTML = testTypeSelector.buildButtonHTMLForUsedTestTypes(); |
- $('#time-memory').append(buttonHTML); |
- |
- $('#scatter-line').bind('change', function(event, checkedElement) { |
- useLargeLinePlots = checkedElement.textContent == 'Line'; |
- displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePlots); |
- }); |
- |
- runs.map(function(run, index) { |
- $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + ' title="' + run.description() + '">' + run.label() + '</span>'); |
- }) |
- |
- $('#time-memory').bind('change', function(event, checkedElement) { |
- testTypeSelector.testTypeName = checkedElement.textContent; |
- displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePlots); |
- }); |
- |
- $('#reference').bind('change', function(event, checkedElement) { |
- referenceIndex = parseInt(checkedElement.getAttribute('value')); |
- displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePlots); |
- }); |
- |
- displayTable(metrics, runs, testTypeSelector, 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)); |
- }); |
- }); |
- |
- runToUndelete = deletedRunsById[undeleteManager.mostRecentlyDeletedId()]; |
- |
- if (runToUndelete) { |
- $('#undelete').html('Undelete ' + runToUndelete.label()); |
- $('#undelete').attr('title', runToUndelete.description()); |
- $('#undelete').click(function(event) { |
- runToUndelete.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> |