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