summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authortonyg@chromium.org <tonyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-06 19:29:13 +0000
committertonyg@chromium.org <tonyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-06 19:29:13 +0000
commitf85d3e8910914e31dde814ba6e5de95b7cc08d3b (patch)
treecdd6c2b401b33d8aa5c588627ff94d959f3abd12 /tools
parent37ee0c790f69885f796e8a849f9d36e657f06056 (diff)
downloadchromium_src-f85d3e8910914e31dde814ba6e5de95b7cc08d3b.zip
chromium_src-f85d3e8910914e31dde814ba6e5de95b7cc08d3b.tar.gz
chromium_src-f85d3e8910914e31dde814ba6e5de95b7cc08d3b.tar.bz2
Revert "Revert 215542 "[Telemetry] Add HTML output and make it the default.""
This reverts commit c5a53d831e281f10c8ab56ea991ca91bc286c6eb. This is the same as the initial patch, except the bootstrap deps have been updated to fix cros. BUG=264695 TBR=kochi@chromium.org Review URL: https://codereview.chromium.org/22325006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@215946 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools')
-rw-r--r--tools/telemetry/support/html_output/results-template.html596
-rw-r--r--tools/telemetry/telemetry/core/browser_options.py5
-rw-r--r--tools/telemetry/telemetry/core/util.py8
-rw-r--r--tools/telemetry/telemetry/page/html_page_measurement_results.py107
-rw-r--r--tools/telemetry/telemetry/page/page_measurement.py13
-rw-r--r--tools/telemetry/telemetry/test.py13
-rw-r--r--tools/telemetry/telemetry/test_runner.py3
-rw-r--r--tools/telemetry_tools/bootstrap_deps11
8 files changed, 742 insertions, 14 deletions
diff --git a/tools/telemetry/support/html_output/results-template.html b/tools/telemetry/support/html_output/results-template.html
new file mode 100644
index 0000000..b0b84c7
--- /dev/null
+++ b/tools/telemetry/support/html_output/results-template.html
@@ -0,0 +1,596 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Telemetry Performance Test Results</title>
+<style type="text/css">
+
+section {
+ background: white;
+ padding: 10px;
+ position: relative;
+}
+
+.time-plots {
+ padding-left: 25px;
+}
+
+.time-plots > div {
+ display: inline-block;
+ width: 90px;
+ height: 40px;
+ margin-right: 10px;
+}
+
+section h1 {
+ text-align: center;
+ font-size: 1em;
+}
+
+section .tooltip {
+ position: absolute;
+ text-align: center;
+ background: #ffcc66;
+ border-radius: 5px;
+ padding: 0px 5px;
+}
+
+body {
+ padding: 0px;
+ margin: 0px;
+ font-family: sans-serif;
+}
+
+table {
+ background: white;
+ width: 100%;
+}
+
+table, td, th {
+ border-collapse: collapse;
+ padding: 5px;
+ white-space: nowrap;
+}
+
+tr.even {
+ background: #f6f6f6;
+}
+
+table td {
+ position: relative;
+ font-family: monospace;
+}
+
+th, td {
+ cursor: pointer;
+ cursor: hand;
+}
+
+th {
+ background: #e6eeee;
+ background: -webkit-gradient(linear, left top, left bottom, from(rgb(244, 244, 244)), to(rgb(217, 217, 217)));
+ border: 1px solid #ccc;
+}
+
+th:after {
+ content: ' \25B8';
+}
+
+th.headerSortUp:after {
+ content: ' \25BE';
+}
+
+th.headerSortDown:after {
+ content: ' \25B4';
+}
+
+td.comparison, td.result {
+ text-align: right;
+}
+
+td.better {
+ color: #6c6;
+}
+
+td.worse {
+ color: #c66;
+}
+
+td.missing {
+ text-align: center;
+}
+
+.checkbox {
+ display: inline-block;
+ background: #eee;
+ background: -webkit-gradient(linear, left bottom, left top, from(rgb(220, 220, 220)), to(rgb(200, 200, 200)));
+ border: inset 1px #ddd;
+ border-radius: 5px;
+ margin: 10px;
+ font-size: small;
+ cursor: pointer;
+ cursor: hand;
+ -webkit-user-select: none;
+ font-weight: bold;
+}
+
+.checkbox span {
+ display: inline-block;
+ line-height: 100%;
+ padding: 5px 8px;
+ border: outset 1px transparent;
+}
+
+.checkbox .checked {
+ background: #e6eeee;
+ background: -webkit-gradient(linear, left top, left bottom, from(rgb(255, 255, 255)), to(rgb(235, 235, 235)));
+ border: outset 1px #eee;
+ border-radius: 5px;
+}
+
+</style>
+</head>
+<body onload="init()">
+<div style="padding: 0 10px; white-space: nowrap;">
+Result <span id="time-memory" class="checkbox"><span class="checked">Time</span><span>Memory</span></span>
+Reference <span id="reference" class="checkbox"></span>
+Run Telemetry with --reset-html-results to clear all runs
+</div>
+<table id="container"></table>
+<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
+<script>
+%plugins%
+</script>
+<script>
+function TestResult(metric, values, associatedRun) {
+ if (values[0] instanceof Array) {
+ var flattenedValues = [];
+ for (var i = 0; i < values.length; i++)
+ flattenedValues = flattenedValues.concat(values[i]);
+ values = flattenedValues;
+ }
+
+ this.test = function () { return metric; }
+ this.values = function () { return values.map(function (value) { return metric.scalingFactor() * value; }); }
+ this.unscaledMean = function () { return Statistics.sum(values) / values.length; }
+ this.mean = function () { return metric.scalingFactor() * this.unscaledMean(); }
+ this.min = function () { return metric.scalingFactor() * Statistics.min(values); }
+ this.max = function () { return metric.scalingFactor() * Statistics.max(values); }
+ this.confidenceIntervalDelta = function () {
+ return metric.scalingFactor() * Statistics.confidenceIntervalDelta(0.95, values.length,
+ Statistics.sum(values), Statistics.squareSum(values));
+ }
+ this.confidenceIntervalDeltaRatio = function () { return this.confidenceIntervalDelta() / this.mean(); }
+ this.percentDifference = function(other) { return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean(); }
+ this.isStatisticallySignificant = function (other) {
+ var diff = Math.abs(other.mean() - this.mean());
+ return diff > this.confidenceIntervalDelta() && diff > other.confidenceIntervalDelta();
+ }
+ this.run = function () { return associatedRun; }
+}
+
+function TestRun(entry) {
+ this.description = function () { return entry['description']; }
+ this.revision = function () { return entry['revision']; }
+ this.label = function () {
+ var label = 'r' + this.revision();
+ if (this.description())
+ label += ' &dash; ' + this.description();
+ return label;
+ }
+}
+
+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');
+ }
+ 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 unit in units) {
+ if (units[unit].improvement_direction == 'up') {
+ window.unitToBiggerIsBetter[unit] = true;
+ }
+ }
+ }
+ return window.unitToBiggerIsBetter[unit];
+ }
+}
+
+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 timePlotOptions = {
+ yaxis: { show: false },
+ xaxis: { show: false },
+ lines: { show: true },
+ grid: { borderWidth: 1, borderColor: '#ccc' },
+ colors: [ plotColor ]
+};
+
+function createPlot(container, test) {
+ var section = $('<section><div class="plot"></div><div class="time-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);
+
+ attachTimePlots(test, section.children('.time-plots'));
+
+ 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 attachTimePlots(test, container) {
+ var results = test.results();
+ var attachedPlot = false;
+ for (var i = 0; i < results.length; i++) {
+ container.append('<div></div>');
+ var values = results[i].values();
+ if (!values)
+ continue;
+ attachedPlot = true;
+
+ $.plot(container.children().last(), [values.map(function (value, index) { return [index, value]; })],
+ $.extend(true, {}, timePlotOptions, {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}}));
+ }
+ if (!attachedPlot)
+ container.children().remove();
+}
+
+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: '&mu;', data: results.map(function (result, index) { return [index, result.mean()]; }), color: plotColor});
+
+ var overallMax = Statistics.max(results.map(function (result, index) { return result.max(); }));
+ var overallMin = Statistics.min(results.map(function (result, index) { return result.min(); }));
+ var margin = (overallMax - overallMin) * 0.1;
+ var currentPlotOptions = $.extend(true, {}, mainPlotOptions, {yaxis: {
+ min: minIsZero ? 0 : overallMin - margin,
+ max: minIsZero ? overallMax * 1.1 : overallMax + margin}});
+
+ currentPlotOptions.xaxis.max = results.length - 0.5;
+ currentPlotOptions.xaxis.ticks = results.map(function (result, index) { return [index, result.run().label()]; });
+
+ $.plot(plotContainer, plotData, currentPlotOptions);
+}
+
+function toFixedWidthPrecision(value) {
+ var decimal = value.toFixed(2);
+ return decimal;
+}
+
+function formatPercentage(fraction) {
+ var percentage = fraction * 100;
+ return (fraction * 100).toFixed(2) + '%';
+}
+
+function createTable(tests, runs, shouldIgnoreMemory, referenceIndex) {
+ $('#container').html('<thead><tr><th>Test</th><th>Unit</th>' + runs.map(function (run, index) {
+ return '<th colspan="' + (index == referenceIndex ? 2 : 3) + '" class="{sorter: \'comparison\'}">' + run.label() + '</th>';
+ }).reduce(function (markup, cell) { return markup + cell; }, '') + '</tr></head><tbody></tbody>');
+
+ var testNames = [];
+ for (testName in tests)
+ testNames.push(testName);
+
+ testNames.sort().map(function (testName) {
+ var test = tests[testName];
+ if (test.isMemoryTest() != shouldIgnoreMemory)
+ createTableRow(runs, test, referenceIndex);
+ });
+
+ $('#container').tablesorter({widgets: ['zebra']});
+}
+
+function linearRegression(points) {
+ // Implement http://www.easycalculation.com/statistics/learn-correlation.php.
+ // x = magnitude
+ // y = iterations
+ var sumX = 0;
+ var sumY = 0;
+ var sumXSquared = 0;
+ var sumYSquared = 0;
+ var sumXTimesY = 0;
+
+ for (var i = 0; i < points.length; i++) {
+ var x = i;
+ var y = points[i];
+ sumX += x;
+ sumY += y;
+ sumXSquared += x * x;
+ sumYSquared += y * y;
+ sumXTimesY += x * y;
+ }
+
+ var r = (points.length * sumXTimesY - sumX * sumY) /
+ Math.sqrt((points.length * sumXSquared - sumX * sumX) *
+ (points.length * sumYSquared - sumY * sumY));
+
+ if (isNaN(r) || r == Math.Infinity)
+ r = 0;
+
+ var slope = (points.length * sumXTimesY - sumX * sumY) / (points.length * sumXSquared - sumX * sumX);
+ var intercept = sumY / points.length - slope * sumX / points.length;
+ return {slope: slope, intercept: intercept, rSquared: r * r};
+}
+
+var warningSign = '<svg viewBox="0 0 100 100" style="width: 18px; height: 18px; vertical-align: bottom;" version="1.1">'
+ + '<polygon fill="red" points="50,10 90,80 10,80 50,10" stroke="red" stroke-width="10" stroke-linejoin="round" />'
+ + '<polygon fill="white" points="47,30 48,29, 50, 28.7, 52,29 53,30 50,60" stroke="white" stroke-width="10" stroke-linejoin="round" />'
+ + '<circle cx="50" cy="73" r="6" fill="white" />'
+ + '</svg>';
+
+function createTableRow(runs, test, referenceIndex) {
+ var tableRow = $('<tr><td class="test"' + (test.isImportant ? ' style="font-weight:bold"' : '') + '>' + test.name() + '</td><td class="unit">' + test.unit() + '</td></tr>');
+
+ function markupForRun(result, referenceResult) {
+ var comparisonCell = '';
+ var hiddenValue = '';
+ var shouldCompare = result !== referenceResult;
+ if (shouldCompare && referenceResult) {
+ var percentDifference = referenceResult.percentDifference(result);
+ var better = test.biggerIsBetter() ? percentDifference > 0 : percentDifference < 0;
+ var comparison = '';
+ var className = 'comparison';
+ if (referenceResult.isStatisticallySignificant(result)) {
+ comparison = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse&nbsp;');
+ className += better ? ' better' : ' worse';
+ }
+ hiddenValue = '<span style="display: none">|' + comparison + '</span>';
+ comparisonCell = '<td class="' + className + '">' + comparison + '</td>';
+ } else if (shouldCompare)
+ comparisonCell = '<td class="comparison"></td>';
+
+ var values = result.values();
+ var warning = '';
+ var regressionAnalysis = '';
+ if (values && values.length > 3) {
+ regressionResult = linearRegression(values);
+ regressionAnalysis = 'slope=' + toFixedWidthPrecision(regressionResult.slope)
+ + ', R^2=' + toFixedWidthPrecision(regressionResult.rSquared);
+ if (regressionResult.rSquared > 0.6 && Math.abs(regressionResult.slope) > 0.01) {
+ warning = ' <span class="regression-warning" title="Detected a time dependency with ' + regressionAnalysis + '">' + warningSign + ' </span>';
+ }
+ }
+
+ var statistics = '&sigma;=' + toFixedWidthPrecision(result.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result.min())
+ + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysis;
+
+ // Tablesorter doesn't know about the second cell so put the comparison in the invisible element.
+ return '<td class="result" title="' + statistics + '">' + toFixedWidthPrecision(result.mean()) + hiddenValue
+ + '</td><td class="confidenceIntervalDelta" title="' + statistics + '">&plusmn; '
+ + formatPercentage(result.confidenceIntervalDeltaRatio()) + warning + '</td>' + comparisonCell;
+ }
+
+ function markupForMissingRun(isReference) {
+ return '<td colspan="' + (isReference ? 2 : 3) + '" class="missing">Missing</td>';
+ }
+
+ var runIndex = 0;
+ var results = test.results();
+ var referenceResult = undefined;
+ var resultIndexMap = {};
+ for (var i = 0; i < results.length; i++) {
+ while (runs[runIndex] !== results[i].run())
+ runIndex++;
+ if (runIndex == referenceIndex)
+ referenceResult = results[i];
+ resultIndexMap[runIndex] = i;
+ }
+ for (var i = 0; i < runs.length; i++) {
+ var resultIndex = resultIndexMap[i];
+ if (resultIndex == undefined)
+ tableRow.append(markupForMissingRun(i == referenceIndex));
+ else
+ tableRow.append(markupForRun(results[resultIndex], referenceResult));
+ }
+
+ $('#container').children('tbody').last().append(tableRow);
+
+ function toggle() {
+ var firstCell = tableRow.children('td').first();
+ if (firstCell.children('section').length) {
+ firstCell.children('section').remove();
+ tableRow.children('td').css({'padding-bottom': ''});
+ } else {
+ var plot = createPlot(firstCell, test);
+ plot.css({'position': 'absolute', 'z-index': 2});
+ var offset = tableRow.offset();
+ offset.left += 1;
+ offset.top += tableRow.outerHeight();
+ plot.offset(offset);
+ tableRow.children('td').css({'padding-bottom': plot.outerHeight() + 5});
+ }
+
+ return false;
+ };
+
+ tableRow.click(function(event) {
+ if (event.target != tableRow[0] && event.target.parentNode != tableRow[0])
+ return;
+
+ event.preventDefault();
+
+ toggle();
+ });
+
+ if (test.isImportant) {
+ toggle();
+ }
+}
+
+function init() {
+ $.tablesorter.addParser({
+ id: 'comparison',
+ is: function(s) {
+ return s.indexOf('|') >= 0;
+ },
+ format: function(s) {
+ var parsed = parseFloat(s.substring(s.indexOf('|') + 1));
+ return isNaN(parsed) ? 0 : parsed;
+ },
+ type: 'numeric',
+ });
+
+ var runs = [];
+ var metrics = {};
+ $.each(JSON.parse(document.getElementById('results-json').textContent), function (index, entry) {
+ var run = new TestRun(entry);
+ runs.push(run);
+
+ function addTests(tests, parentFullName) {
+ for (var testName in tests) {
+ var fullTestName = parentFullName + '/' + testName;
+ var rawMetrics = tests[testName].metrics;
+
+ for (var metricName in rawMetrics) {
+ var fullMetricName = fullTestName + ':' + metricName;
+ var metric = metrics[fullMetricName];
+ if (!metric) {
+ metric = new PerfTestMetric(fullTestName, metricName, rawMetrics[metricName].units, rawMetrics[metricName].important);
+ metrics[fullMetricName] = metric;
+ }
+ metric.addResult(new TestResult(metric, rawMetrics[metricName].current, run));
+ }
+
+ if (tests[testName].tests)
+ addTests(tests[testName].tests, fullTestName);
+ }
+ }
+
+ addTests(entry.tests, '');
+ });
+
+ var shouldIgnoreMemory= true;
+ var referenceIndex = 0;
+
+ createTable(metrics, runs, shouldIgnoreMemory, referenceIndex);
+
+ $('#time-memory').bind('change', function (event, checkedElement) {
+ shouldIgnoreMemory = checkedElement.textContent == 'Time';
+ createTable(metrics, runs, shouldIgnoreMemory, referenceIndex);
+ });
+
+ runs.map(function (run, index) {
+ $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + '>' + run.label() + '</span>');
+ })
+
+ $('#reference').bind('change', function (event, checkedElement) {
+ referenceIndex = parseInt(checkedElement.getAttribute('value'));
+ createTable(metrics, runs, shouldIgnoreMemory, referenceIndex);
+ });
+
+ $('.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));
+ });
+ });
+}
+
+</script>
+<script id="results-json" type="application/json">%json_results%</script>
+<script id="units-json" type="application/json">%json_units%</script>
+</body>
+</html>
diff --git a/tools/telemetry/telemetry/core/browser_options.py b/tools/telemetry/telemetry/core/browser_options.py
index 983d146..2daf439 100644
--- a/tools/telemetry/telemetry/core/browser_options.py
+++ b/tools/telemetry/telemetry/core/browser_options.py
@@ -52,6 +52,7 @@ class BrowserOptions(optparse.Values):
self.no_proxy_server = False
self.repeat_options = repeat_options.RepeatOptions()
+ self.output_file = None
def Copy(self):
return copy.deepcopy(self)
@@ -205,6 +206,10 @@ class BrowserOptions(optparse.Values):
if self.profile_type == 'default':
self.dont_override_profile = True
+ if ((hasattr(self, 'output_format') and self.output_format == 'html') and
+ (not hasattr(self, 'output_file') or not self.output_file)):
+ self.output_file = os.path.join(util.GetBaseDir(), 'results.html')
+
# Parse repeat options
self.repeat_options.UpdateFromParseResults(self, parser)
diff --git a/tools/telemetry/telemetry/core/util.py b/tools/telemetry/telemetry/core/util.py
index 5b11640f..fddab1c 100644
--- a/tools/telemetry/telemetry/core/util.py
+++ b/tools/telemetry/telemetry/core/util.py
@@ -4,11 +4,19 @@
import inspect
import os
import socket
+import sys
import time
class TimeoutException(Exception):
pass
+def GetBaseDir():
+ main_module = sys.modules['__main__']
+ if hasattr(main_module, '__file__'):
+ return os.path.dirname(os.path.abspath(main_module.__file__))
+ else:
+ return os.getcwd()
+
def GetTelemetryDir():
return os.path.normpath(os.path.join(
__file__, os.pardir, os.pardir, os.pardir))
diff --git a/tools/telemetry/telemetry/page/html_page_measurement_results.py b/tools/telemetry/telemetry/page/html_page_measurement_results.py
new file mode 100644
index 0000000..9d07b82
--- /dev/null
+++ b/tools/telemetry/telemetry/page/html_page_measurement_results.py
@@ -0,0 +1,107 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import datetime
+import json
+import logging
+import os
+import re
+import sys
+
+from telemetry.core import util
+from telemetry.page import buildbot_page_measurement_results
+
+# Get build/util scripts into our path.
+sys.path.append(os.path.abspath(os.path.join(
+ util.GetChromiumSrcDir(), 'build', 'util')))
+import lastchange # pylint: disable=F0401
+
+
+_TEMPLATE_HTML_PATH = os.path.join(
+ util.GetTelemetryDir(), 'support', 'html_output', 'results-template.html')
+_PLUGINS = [('third_party', 'flot', 'jquery.flot.min.js'),
+ ('third_party', 'WebKit', 'PerformanceTests', 'resources',
+ 'jquery.tablesorter.min.js'),
+ ('third_party', 'WebKit', 'PerformanceTests', 'resources',
+ 'statistics.js')]
+_UNIT_JSON = ('tools', 'perf', 'unit-info.json')
+
+
+def _DatetimeInEs5CompatibleFormat(dt):
+ return dt.strftime('%Y-%m-%dT%H:%M:%S.%f')
+
+
+class HtmlPageMeasurementResults(
+ buildbot_page_measurement_results.BuildbotPageMeasurementResults):
+ def __init__(self, output_stream, test_name, reset_results, browser_type,
+ trace_tag=''):
+ super(HtmlPageMeasurementResults, self).__init__(trace_tag)
+
+ self._output_stream = output_stream
+ self._reset_results = reset_results
+ self._test_name = test_name
+ self._result_json = {
+ 'buildTime': _DatetimeInEs5CompatibleFormat(datetime.datetime.utcnow()),
+ 'revision': lastchange.FetchVersionInfo(None).revision,
+ 'platform': browser_type,
+ 'tests': {}
+ }
+
+ def _GetHtmlTemplate(self):
+ return open(_TEMPLATE_HTML_PATH, 'r').read()
+
+ def _GetPlugins(self):
+ plugins = ''
+ for p in _PLUGINS:
+ plugins += open(os.path.join(util.GetChromiumSrcDir(), *p), 'r').read()
+ return plugins
+
+ def _GetUnitJson(self):
+ return open(os.path.join(util.GetChromiumSrcDir(), *_UNIT_JSON), 'r').read()
+
+ def _GetResultsJson(self):
+ results_html = self._output_stream.read()
+ if self._reset_results or not results_html:
+ return []
+ m = re.search(
+ '^<script id="results-json" type="application/json">(.*?)</script>$',
+ results_html, re.MULTILINE | re.DOTALL)
+ if not m:
+ logging.warn('Failed to extract previous results from HTML output')
+ return []
+ return json.loads(m.group(1))
+
+ def _SaveResults(self, results):
+ self._output_stream.seek(0)
+ self._output_stream.write(results)
+
+ def _PrintPerfResult(self, measurement, trace, values, units,
+ result_type='default'):
+ super(HtmlPageMeasurementResults, self)._PrintPerfResult(
+ measurement, trace, values, units, result_type)
+
+ metric_name = measurement
+ if trace != measurement:
+ metric_name += '.' + trace
+ self._result_json['tests'].setdefault(self._test_name, {})
+ self._result_json['tests'][self._test_name].setdefault('metrics', {})
+ self._result_json['tests'][self._test_name]['metrics'][metric_name] = {
+ 'current': values,
+ 'units': units,
+ 'important': result_type == 'default'
+ }
+
+ def PrintSummary(self):
+ super(HtmlPageMeasurementResults, self).PrintSummary()
+
+ json_results = self._GetResultsJson()
+ json_results.append(self._result_json)
+ html = self._GetHtmlTemplate()
+ html = html.replace('%json_results%', json.dumps(json_results))
+ html = html.replace('%json_units%', self._GetUnitJson())
+ html = html.replace('%plugins%', self._GetPlugins())
+ self._SaveResults(html)
+
+ print
+ print 'View result at file://%s' % os.path.abspath(self._output_stream.name)
diff --git a/tools/telemetry/telemetry/page/page_measurement.py b/tools/telemetry/telemetry/page/page_measurement.py
index f3bb72d..be3bb51 100644
--- a/tools/telemetry/telemetry/page/page_measurement.py
+++ b/tools/telemetry/telemetry/page/page_measurement.py
@@ -7,6 +7,7 @@ import sys
from telemetry.page import block_page_measurement_results
from telemetry.page import buildbot_page_measurement_results
from telemetry.page import csv_page_measurement_results
+from telemetry.page import html_page_measurement_results
from telemetry.page import page_measurement_results
from telemetry.page import page_test
@@ -66,14 +67,18 @@ class PageMeasurement(page_test.PageTest):
parser.add_option('--output-trace-tag',
default='',
help='Append a tag to the key of each result trace.')
+ parser.add_option('--reset-html-results', action='store_true',
+ help='Delete all stored runs in HTML output')
@property
def output_format_choices(self):
- return ['buildbot', 'block', 'csv', 'none']
+ return ['html', 'buildbot', 'block', 'csv', 'none']
def PrepareResults(self, options):
if hasattr(options, 'output_file') and options.output_file:
- output_stream = open(os.path.expanduser(options.output_file), 'w')
+ output_file = os.path.expanduser(options.output_file)
+ open(output_file, 'a').close() # Create file if it doesn't exist.
+ output_stream = open(output_file, 'r+')
else:
output_stream = sys.stdout
if not hasattr(options, 'output_format'):
@@ -91,6 +96,10 @@ class PageMeasurement(page_test.PageTest):
elif options.output_format == 'buildbot':
return buildbot_page_measurement_results.BuildbotPageMeasurementResults(
trace_tag=options.output_trace_tag)
+ elif options.output_format == 'html':
+ return html_page_measurement_results.HtmlPageMeasurementResults(
+ output_stream, self.__class__.__name__, options.reset_html_results,
+ options.browser_type, trace_tag=options.output_trace_tag)
elif options.output_format == 'none':
return page_measurement_results.PageMeasurementResults(
trace_tag=options.output_trace_tag)
diff --git a/tools/telemetry/telemetry/test.py b/tools/telemetry/telemetry/test.py
index 1edf35b..df74a2a 100644
--- a/tools/telemetry/telemetry/test.py
+++ b/tools/telemetry/telemetry/test.py
@@ -2,23 +2,15 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
-import sys
from telemetry.core import repeat_options
+from telemetry.core import util
from telemetry.page import page_runner
from telemetry.page import page_set
from telemetry.page import page_test
from telemetry.page import test_expectations
-def GetBaseDir():
- main_module = sys.modules['__main__']
- if hasattr(main_module, '__file__'):
- return os.path.dirname(os.path.abspath(main_module.__file__))
- else:
- return os.getcwd()
-
-
class Test(object):
"""Base class for a Telemetry test or benchmark.
@@ -60,7 +52,8 @@ class Test(object):
page_set attribute. Override to generate a custom page set.
"""
assert hasattr(self, 'page_set'), 'This test has no "page_set" attribute.'
- return page_set.PageSet.FromFile(os.path.join(GetBaseDir(), self.page_set))
+ return page_set.PageSet.FromFile(
+ os.path.join(util.GetBaseDir(), self.page_set))
def CreateExpectations(self, ps): # pylint: disable=W0613
"""Get the expectations this test will run with.
diff --git a/tools/telemetry/telemetry/test_runner.py b/tools/telemetry/telemetry/test_runner.py
index 1345eaaa..ebae40c 100644
--- a/tools/telemetry/telemetry/test_runner.py
+++ b/tools/telemetry/telemetry/test_runner.py
@@ -17,6 +17,7 @@ import sys
from telemetry import test
from telemetry.core import browser_options
from telemetry.core import discover
+from telemetry.core import util
class Command(object):
@@ -120,7 +121,7 @@ def _GetScriptName():
def _GetTests():
# Lazy load and cache results.
if not hasattr(_GetTests, 'tests'):
- base_dir = test.GetBaseDir()
+ base_dir = util.GetBaseDir()
_GetTests.tests = discover.DiscoverClasses(base_dir, base_dir, test.Test,
index_by_class_name=True)
return _GetTests.tests
diff --git a/tools/telemetry_tools/bootstrap_deps b/tools/telemetry_tools/bootstrap_deps
index 887e7ce..8288da6 100644
--- a/tools/telemetry_tools/bootstrap_deps
+++ b/tools/telemetry_tools/bootstrap_deps
@@ -22,5 +22,14 @@ deps = {
"https://src.chromium.org/chrome/trunk/src/tools/crx_id",
"src/chrome/test/data/extensions/profiles":
"https://src.chromium.org/chrome/trunk/src/chrome/test/data/extensions/profiles",
+ "src/build/util":
+ "https://src.chromium.org/chrome/trunk/src/build/util",
+ "third_party/flot/jquery.flot.min.js":
+ "https://src.chromium.org/chrome/trunk/src/third_party/flot/jquery.flot.min.js",
+ "src/third_party/WebKit/PerformanceTests/resources/jquery.tablesorter.min.js":
+ "https://src.chromium.org/blink/trunk/PerformanceTests/resources/jquery.tablesorter.min.js",
+ "src/third_party/WebKit/PerformanceTests/resources/statistics.js":
+ "https://src.chromium.org/blink/trunk/PerformanceTests/resources/statistics.js",
+ "src/tools/perf/unit-info.json":
+ "https://src.chromium.org/chrome/trunk/src/tools/perf/unit-info.json"
}
-