• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!DOCTYPE html>
2<html>
3<head>
4<title>Blink Performance Test Results</title>
5<style type="text/css">
6
7section {
8    background: white;
9    padding: 10px;
10    position: relative;
11}
12
13.time-plots {
14    padding-left: 25px;
15}
16
17.time-plots > div {
18    display: inline-block;
19    width: 90px;
20    height: 40px;
21    margin-right: 10px;
22}
23
24section h1 {
25    text-align: center;
26    font-size: 1em;
27}
28
29section .tooltip {
30    position: absolute;
31    text-align: center;
32    background: #ffcc66;
33    border-radius: 5px;
34    padding: 0px 5px;
35}
36
37body {
38    padding: 0px;
39    margin: 0px;
40    font-family: sans-serif;
41}
42
43table {
44    background: white;
45    width: 100%;
46}
47
48table, td, th {
49    border-collapse: collapse;
50    padding: 5px;
51}
52
53tr.even {
54    background: #f6f6f6;
55}
56
57table td {
58    position: relative;
59    font-family: monospace;
60}
61
62th, td {
63    cursor: pointer;
64    cursor: hand;
65}
66
67th {
68    background: #e6eeee;
69    background: -webkit-gradient(linear, left top, left bottom, from(rgb(244, 244, 244)), to(rgb(217, 217, 217)));
70    border: 1px solid #ccc;
71}
72
73th:after {
74    content: ' \25B8';
75}
76
77th.headerSortUp:after {
78    content: ' \25BE';
79}
80
81th.headerSortDown:after {
82    content: ' \25B4';
83}
84
85td.comparison, td.result {
86    text-align: right;
87}
88
89td.better {
90    color: #6c6;
91}
92
93td.worse {
94    color: #c66;
95}
96
97td.missing {
98    text-align: center;
99}
100
101.checkbox {
102    display: inline-block;
103    background: #eee;
104    background: -webkit-gradient(linear, left bottom, left top, from(rgb(220, 220, 220)), to(rgb(200, 200, 200)));
105    border: inset 1px #ddd;
106    border-radius: 5px;
107    margin: 10px;
108    font-size: small;
109    cursor: pointer;
110    cursor: hand;
111    -webkit-user-select: none;
112    font-weight: bold;
113}
114
115.checkbox span {
116    display: inline-block;
117    line-height: 100%;
118    padding: 5px 8px;
119    border: outset 1px transparent;
120}
121
122.checkbox .checked {
123    background: #e6eeee;
124    background: -webkit-gradient(linear, left top, left bottom, from(rgb(255, 255, 255)), to(rgb(235, 235, 235)));
125    border: outset 1px #eee;
126    border-radius: 5px;
127}
128
129</style>
130</head>
131<body>
132<div style="padding: 0 10px;">
133Result <span id="time-memory" class="checkbox"><span class="checked">Time</span><span>Memory</span></span>
134Reference <span id="reference" class="checkbox"></span>
135</div>
136<table id="container"></table>
137<script>
138
139(function () {
140    var jQuery = 'PerformanceTests/Dromaeo/resources/dromaeo/web/lib/jquery-1.6.4.js';
141    var plugins = ['PerformanceTests/resources/jquery.flot.min.js', 'PerformanceTests/resources/jquery.tablesorter.min.js',
142        'PerformanceTests/resources/statistics.js'];
143    var localPath = '%AbsolutePathToWebKitTrunk%';
144    var remotePath = 'https://svn.webkit.org/repository/webkit/trunk';
145    var numberOfFailures = 0;
146    var startedLoadingPlugins = false;
147    var numberOfLoadedPlugins = 0;
148
149    function loadScript(src, loaded, failed) {
150        var script = document.createElement('script');
151        script.async = true;
152        script.src = src;
153        script.onload = loaded;
154        if (failed)
155            script.onerror = failed;
156        document.body.appendChild(script);
157    }
158
159    function loadPlugins(trunkPath) {
160        for (var i = 0; i < plugins.length; i++)
161            loadScript(trunkPath + '/' + plugins[i], loadedPlugin, createFailedToLoadPlugin(plugins[i]));
162    }
163
164    function loadedPlugin() {
165        numberOfLoadedPlugins++;
166        if (numberOfLoadedPlugins == plugins.length)
167            setTimeout(init, 0);
168    }
169
170    function createFailedToLoadPlugin(plugin) {
171        return function () { alert("Failed to load " + plugin); }
172    }
173
174    function createLoadedJQuery(trunkPath) {
175        return function () { loadPlugins(trunkPath); }
176    }
177
178    loadScript(localPath + '/' + jQuery,
179        createLoadedJQuery(localPath),
180        function () {
181            loadScript(remotePath + '/' + jQuery,
182                createLoadedJQuery(remotePath),
183                function () { alert("Failed to load jQuery."); });
184        });
185})();
186
187function TestResult(metric, values, associatedRun) {
188    if (values[0] instanceof Array) {
189        var flattenedValues = [];
190        for (var i = 0; i < values.length; i++)
191            flattenedValues = flattenedValues.concat(values[i]);
192        values = flattenedValues;
193    }
194
195    this.test = function () { return metric; }
196    this.values = function () { return values.map(function (value) { return metric.scalingFactor() * value; }); }
197    this.unscaledMean = function () { return Statistics.sum(values) / values.length; }
198    this.mean = function () { return metric.scalingFactor() * this.unscaledMean(); }
199    this.min = function () { return metric.scalingFactor() * Statistics.min(values); }
200    this.max = function () { return metric.scalingFactor() * Statistics.max(values); }
201    this.confidenceIntervalDelta = function () {
202        return metric.scalingFactor() * Statistics.confidenceIntervalDelta(0.95, values.length,
203            Statistics.sum(values), Statistics.squareSum(values));
204    }
205    this.confidenceIntervalDeltaRatio = function () { return this.confidenceIntervalDelta() / this.mean(); }
206    this.percentDifference = function(other) { return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean(); }
207    this.isStatisticallySignificant = function (other) {
208        var diff = Math.abs(other.mean() - this.mean());
209        return diff > this.confidenceIntervalDelta() && diff > other.confidenceIntervalDelta();
210    }
211    this.run = function () { return associatedRun; }
212}
213
214function TestRun(entry) {
215    this.description = function () { return entry['description']; }
216    this.webkitRevision = function () { return entry['revisions']['blink']['revision']; }
217    this.label = function () {
218        var label = 'r' + this.webkitRevision();
219        if (this.description())
220            label += ' &dash; ' + this.description();
221        return label;
222    }
223}
224
225function PerfTestMetric(name, metric) {
226    var testResults = [];
227    var cachedUnit = null;
228    var cachedScalingFactor = null;
229    var unit = {'FrameRate': 'fps', 'Runs': 'runs/s', 'Time': 'ms', 'Malloc': 'bytes', 'JSHeap': 'bytes'}[metric];
230
231    // We can't do this in TestResult because all results for each test need to share the same unit and the same scaling factor.
232    function computeScalingFactorIfNeeded() {
233        // FIXME: We shouldn't be adjusting units on every test result.
234        // We can only do this on the first test.
235        if (!testResults.length || cachedUnit)
236            return;
237
238        var mean = testResults[0].unscaledMean(); // FIXME: We should look at all values.
239        var kilo = unit == 'bytes' ? 1024 : 1000;
240        if (mean > 10 * kilo * kilo && unit != 'ms') {
241            cachedScalingFactor = 1 / kilo / kilo;
242            cachedUnit = 'M ' + unit;
243        } else if (mean > 10 * kilo) {
244            cachedScalingFactor = 1 / kilo;
245            cachedUnit = unit == 'ms' ? 's' : ('K ' + unit);
246        } else {
247            cachedScalingFactor = 1;
248            cachedUnit = unit;
249        }
250    }
251
252    this.name = function () { return name + ':' + metric; }
253    this.isMemoryTest = function () { return metric == 'JSHeap' || metric == 'Malloc'; }
254    this.addResult = function (newResult) {
255        testResults.push(newResult);
256        cachedUnit = null;
257        cachedScalingFactor = null;
258    }
259    this.results = function () { return testResults; }
260    this.scalingFactor = function() {
261        computeScalingFactorIfNeeded();
262        return cachedScalingFactor;
263    }
264    this.unit = function () {
265        computeScalingFactorIfNeeded();
266        return cachedUnit;
267    }
268    this.smallerIsBetter = function () { return unit == 'ms' || unit == 'bytes'; }
269}
270
271var plotColor = 'rgb(230,50,50)';
272var subpointsPlotOptions = {
273    lines: {show:true, lineWidth: 0},
274    color: plotColor,
275    points: {show: true, radius: 1},
276    bars: {show: false}};
277
278var mainPlotOptions = {
279    xaxis: {
280        min: -0.5,
281        tickSize: 1,
282    },
283    crosshair: { mode: 'y' },
284    series: { shadowSize: 0 },
285    bars: {show: true, align: 'center', barWidth: 0.5},
286    lines: { show: false },
287    points: { show: true },
288    grid: {
289        borderWidth: 1,
290        borderColor: '#ccc',
291        backgroundColor: '#fff',
292        hoverable: true,
293        autoHighlight: false,
294    }
295};
296
297var timePlotOptions = {
298    yaxis: { show: false },
299    xaxis: { show: false },
300    lines: { show: true },
301    grid: { borderWidth: 1, borderColor: '#ccc' },
302    colors: [ plotColor ]
303};
304
305function createPlot(container, test) {
306    var section = $('<section><div class="plot"></div><div class="time-plots"></div>'
307        + '<span class="tooltip"></span></section>');
308    section.children('.plot').css({'width': (100 * test.results().length + 25) + 'px', 'height': '300px'});
309    $(container).append(section);
310
311    var plotContainer = section.children('.plot');
312    var minIsZero = true;
313    attachPlot(test, plotContainer, minIsZero);
314
315    attachTimePlots(test, section.children('.time-plots'));
316
317    var tooltip = section.children('.tooltip');
318    plotContainer.bind('plothover', function (event, position, item) {
319        if (item) {
320            var postfix = item.series.id ? ' (' + item.series.id + ')' : '';
321            tooltip.html(item.datapoint[1].toPrecision(4) + postfix);
322            var sectionOffset = $(section).offset();
323            tooltip.css({left: item.pageX - sectionOffset.left - tooltip.outerWidth() / 2, top: item.pageY - sectionOffset.top + 10});
324            tooltip.fadeIn(200);
325        } else
326            tooltip.hide();
327    });
328    plotContainer.mouseout(function () {
329        tooltip.hide();
330    });
331    plotContainer.click(function (event) {
332        event.preventDefault();
333        minIsZero = !minIsZero;
334        attachPlot(test, plotContainer, minIsZero);
335    });
336
337    return section;
338}
339
340function attachTimePlots(test, container) {
341    var results = test.results();
342    var attachedPlot = false;
343    for (var i = 0; i < results.length; i++) {
344        container.append('<div></div>');
345        var values = results[i].values();
346        if (!values)
347            continue;
348        attachedPlot = true;
349
350        $.plot(container.children().last(), [values.map(function (value, index) { return [index, value]; })],
351            $.extend(true, {}, timePlotOptions, {yaxis: {min: Math.min.apply(Math, values) * 0.9, max: Math.max.apply(Math, values) * 1.1},
352                xaxis: {min: -0.5, max: values.length - 0.5}}));
353    }
354    if (!attachedPlot)
355        container.children().remove();
356}
357
358function attachPlot(test, plotContainer, minIsZero) {
359    var results = test.results();
360
361    var values = results.reduce(function (values, result, index) {
362        var newValues = result.values();
363        return newValues ? values.concat(newValues.map(function (value) { return [index, value]; })) : values;
364    }, []);
365
366    var plotData = [$.extend(true, {}, subpointsPlotOptions, {data: values})];
367    plotData.push({id: '&mu;', data: results.map(function (result, index) { return [index, result.mean()]; }), color: plotColor});
368
369    var overallMax = Statistics.max(results.map(function (result, index) { return result.max(); }));
370    var overallMin = Statistics.min(results.map(function (result, index) { return result.min(); }));
371    var margin = (overallMax - overallMin) * 0.1;
372    var currentPlotOptions = $.extend(true, {}, mainPlotOptions, {yaxis: {
373        min: minIsZero ? 0 : overallMin - margin,
374        max: minIsZero ? overallMax * 1.1 : overallMax + margin}});
375
376    currentPlotOptions.xaxis.max = results.length - 0.5;
377    currentPlotOptions.xaxis.ticks = results.map(function (result, index) { return [index, result.run().label()]; });
378
379    $.plot(plotContainer, plotData, currentPlotOptions);
380}
381
382function toFixedWidthPrecision(value) {
383    var decimal = value.toFixed(2);
384    return decimal;
385}
386
387function formatPercentage(fraction) {
388    var percentage = fraction * 100;
389    return (fraction * 100).toFixed(2) + '%';
390}
391
392function createTable(tests, runs, shouldIgnoreMemory, referenceIndex) {
393    $('#container').html('<thead><tr><th>Test</th><th>Unit</th>' + runs.map(function (run, index) {
394        return '<th colspan="' + (index == referenceIndex ? 2 : 3) + '" class="{sorter: \'comparison\'}">' + run.label() + '</th>';
395    }).reduce(function (markup, cell) { return markup + cell; }, '') + '</tr></head><tbody></tbody>');
396
397    var testNames = [];
398    for (testName in tests)
399        testNames.push(testName);
400
401    testNames.sort().map(function (testName) {
402        var test = tests[testName];
403        if (test.isMemoryTest() != shouldIgnoreMemory)
404            createTableRow(runs, test, referenceIndex);
405    });
406
407    $('#container').tablesorter({widgets: ['zebra']});
408}
409
410function linearRegression(points) {
411    // Implement http://www.easycalculation.com/statistics/learn-correlation.php.
412    // x = magnitude
413    // y = iterations
414    var sumX = 0;
415    var sumY = 0;
416    var sumXSquared = 0;
417    var sumYSquared = 0;
418    var sumXTimesY = 0;
419
420    for (var i = 0; i < points.length; i++) {
421        var x = i;
422        var y = points[i];
423        sumX += x;
424        sumY += y;
425        sumXSquared += x * x;
426        sumYSquared += y * y;
427        sumXTimesY += x * y;
428    }
429
430    var r = (points.length * sumXTimesY - sumX * sumY) /
431        Math.sqrt((points.length * sumXSquared - sumX * sumX) *
432                  (points.length * sumYSquared - sumY * sumY));
433
434    if (isNaN(r) || r == Math.Infinity)
435        r = 0;
436
437    var slope = (points.length * sumXTimesY - sumX * sumY) / (points.length * sumXSquared - sumX * sumX);
438    var intercept = sumY / points.length - slope * sumX / points.length;
439    return {slope: slope, intercept: intercept, rSquared: r * r};
440}
441
442var warningSign = '<svg viewBox="0 0 100 100" style="width: 18px; height: 18px; vertical-align: bottom;" version="1.1">'
443    + '<polygon fill="red" points="50,10 90,80 10,80 50,10" stroke="red" stroke-width="10" stroke-linejoin="round" />'
444    + '<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" />'
445    + '<circle cx="50" cy="73" r="6" fill="white" />'
446    + '</svg>';
447
448function createTableRow(runs, test, referenceIndex) {
449    var tableRow = $('<tr><td class="test">' + test.name() + '</td><td class="unit">' + test.unit() + '</td></tr>');
450
451    function markupForRun(result, referenceResult) {
452        var comparisonCell = '';
453        var hiddenValue = '';
454        var shouldCompare = result !== referenceResult;
455        if (shouldCompare && referenceResult) {
456            var percentDifference = referenceResult.percentDifference(result);
457            var better = test.smallerIsBetter() ? percentDifference < 0 : percentDifference > 0;
458            var comparison = '';
459            var className = 'comparison';
460            if (referenceResult.isStatisticallySignificant(result)) {
461                comparison = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse&nbsp;');
462                className += better ? ' better' : ' worse';
463            }
464            hiddenValue = '<span style="display: none">|' + comparison + '</span>';
465            comparisonCell = '<td class="' + className + '">' + comparison + '</td>';
466        } else if (shouldCompare)
467            comparisonCell = '<td class="comparison"></td>';
468
469        var values = result.values();
470        var warning = '';
471        var regressionAnalysis = '';
472        if (values && values.length > 3) {
473            regressionResult = linearRegression(values);
474            regressionAnalysis = 'slope=' + toFixedWidthPrecision(regressionResult.slope)
475                + ', R^2=' + toFixedWidthPrecision(regressionResult.rSquared);
476            if (regressionResult.rSquared > 0.6 && Math.abs(regressionResult.slope) > 0.01) {
477                warning = ' <span class="regression-warning" title="Detected a time dependency with ' + regressionAnalysis + '">' + warningSign + ' </span>';
478            }
479        }
480
481        var statistics = '&sigma;=' + toFixedWidthPrecision(result.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result.min())
482            + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysis;
483
484        // Tablesorter doesn't know about the second cell so put the comparison in the invisible element.
485        return '<td class="result" title="' + statistics + '">' + toFixedWidthPrecision(result.mean()) + hiddenValue
486            + '</td><td class="confidenceIntervalDelta" title="' + statistics + '">&plusmn; '
487            + formatPercentage(result.confidenceIntervalDeltaRatio()) + warning + '</td>' + comparisonCell;
488    }
489
490    function markupForMissingRun(isReference) {
491        return '<td colspan="' + (isReference ? 2 : 3) + '" class="missing">Missing</td>';
492    }
493
494    var runIndex = 0;
495    var results = test.results();
496    var referenceResult = undefined;
497    var resultIndexMap = {};
498    for (var i = 0; i < results.length; i++) {
499        while (runs[runIndex] !== results[i].run())
500            runIndex++;
501        if (runIndex == referenceIndex)
502            referenceResult = results[i];
503        resultIndexMap[runIndex] = i;
504    }
505    for (var i = 0; i < runs.length; i++) {
506        var resultIndex = resultIndexMap[i];
507        if (resultIndex == undefined)
508            tableRow.append(markupForMissingRun(i == referenceIndex));
509        else
510            tableRow.append(markupForRun(results[resultIndex], referenceResult));
511    }
512
513    $('#container').children('tbody').last().append(tableRow);
514
515    tableRow.click(function (event) {
516        if (event.target != tableRow[0] && event.target.parentNode != tableRow[0])
517            return;
518
519        event.preventDefault();
520
521        var firstCell = tableRow.children('td').first();
522        if (firstCell.children('section').length) {
523            firstCell.children('section').remove();
524            tableRow.children('td').css({'padding-bottom': ''});
525        } else {
526            var plot = createPlot(firstCell, test);
527            plot.css({'position': 'absolute', 'z-index': 2});
528            var offset = tableRow.offset();
529            offset.left += 1;
530            offset.top += tableRow.outerHeight();
531            plot.offset(offset);
532            tableRow.children('td').css({'padding-bottom': plot.outerHeight() + 5});
533        }
534
535        return false;
536    });
537}
538
539function init() {
540    $.tablesorter.addParser({
541        id: 'comparison',
542        is: function(s) {
543            return s.indexOf('|') >= 0;
544        },
545        format: function(s) {
546            var parsed = parseFloat(s.substring(s.indexOf('|') + 1));
547            return isNaN(parsed) ? 0 : parsed;
548        },
549        type: 'numeric',
550    });
551
552    var runs = [];
553    var metrics = {};
554    $.each(JSON.parse(document.getElementById('json').textContent), function (index, entry) {
555        var run = new TestRun(entry);
556        runs.push(run);
557
558        function addTests(tests, parentFullName) {
559            for (var testName in tests) {
560                var fullTestName = parentFullName + '/' + testName;
561                var rawMetrics = tests[testName].metrics;
562
563                for (var metricName in rawMetrics) {
564                    var fullMetricName = fullTestName + ':' + metricName;
565                    var metric = metrics[fullMetricName];
566                    if (!metric) {
567                        metric = new PerfTestMetric(fullTestName, metricName);
568                        metrics[fullMetricName] = metric;
569                    }
570                    metric.addResult(new TestResult(metric, rawMetrics[metricName].current, run));
571                }
572
573                if (tests[testName].tests)
574                    addTests(tests[testName].tests, fullTestName);
575            }
576        }
577
578        addTests(entry.tests, '');
579    });
580
581    var shouldIgnoreMemory= true;
582    var referenceIndex = 0;
583
584    createTable(metrics, runs, shouldIgnoreMemory, referenceIndex);
585
586    $('#time-memory').bind('change', function (event, checkedElement) {
587        shouldIgnoreMemory = checkedElement.textContent == 'Time';
588        createTable(metrics, runs, shouldIgnoreMemory, referenceIndex);
589    });
590
591    runs.map(function (run, index) {
592        $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + '>' + run.label() + '</span>');
593    })
594
595    $('#reference').bind('change', function (event, checkedElement) {
596        referenceIndex = parseInt(checkedElement.getAttribute('value'));
597        createTable(metrics, runs, shouldIgnoreMemory, referenceIndex);
598    });
599
600    $('.checkbox').each(function (index, checkbox) {
601        $(checkbox).children('span').click(function (event) {
602            if ($(this).hasClass('checked'))
603                return;
604            $(checkbox).children('span').removeClass('checked');
605            $(this).addClass('checked');
606            $(checkbox).trigger('change', $(this));
607        });
608    });
609}
610
611</script>
612<script id="json" type="application/json">%PeformanceTestsResultsJSON%</script>
613</body>
614</html>
615