| # Copyright 2014 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 |
| |
| from py_utils import cloud_storage # pylint: disable=import-error |
| |
| from telemetry.core import util |
| from telemetry.internal.results import chart_json_output_formatter |
| from telemetry.internal.results import output_formatter |
| from telemetry import value as value_module |
| from telemetry.value import list_of_scalar_values |
| |
| |
| _TEMPLATE_HTML_PATH = os.path.join( |
| util.GetTelemetryDir(), 'support', 'html_output', 'results-template.html') |
| _JS_PLUGINS = [os.path.join('flot', 'jquery.flot.min.js'), |
| os.path.join('WebKit', 'PerformanceTests', 'resources', |
| 'jquery.tablesorter.min.js'), |
| os.path.join('WebKit', 'PerformanceTests', 'resources', |
| 'statistics.js')] |
| _UNIT_JSON = os.path.join( |
| util.GetTelemetryDir(), 'telemetry', 'value', 'unit-info.json') |
| |
| |
| def _DatetimeInEs5CompatibleFormat(dt): |
| return dt.strftime('%Y-%m-%dT%H:%M:%S.%f') |
| |
| |
| def _ShortDatetimeInEs5CompatibleFormat(dt): |
| return dt.strftime('%Y-%m-%d %H:%M:%S') |
| |
| |
| # TODO(eakuefner): rewrite template to use Telemetry JSON directly |
| class HtmlOutputFormatter(output_formatter.OutputFormatter): |
| def __init__(self, output_stream, metadata, reset_results, upload_results, |
| browser_type, results_label=None): |
| super(HtmlOutputFormatter, self).__init__(output_stream) |
| self._metadata = metadata |
| self._reset_results = reset_results |
| self._upload_results = upload_results |
| self._build_time = self._GetBuildTime() |
| self._existing_results = self._ReadExistingResults(output_stream) |
| if results_label: |
| self._results_label = results_label |
| else: |
| self._results_label = '%s (%s)' % ( |
| metadata.name, _ShortDatetimeInEs5CompatibleFormat(self._build_time)) |
| self._result = { |
| 'buildTime': _DatetimeInEs5CompatibleFormat(self._build_time), |
| 'label': self._results_label, |
| 'platform': browser_type, |
| 'tests': {} |
| } |
| |
| def _GetBuildTime(self): |
| return datetime.datetime.utcnow() |
| |
| def _GetHtmlTemplate(self): |
| with open(_TEMPLATE_HTML_PATH) as f: |
| return f.read() |
| |
| def _GetPlugins(self): |
| plugins = '' |
| for p in _JS_PLUGINS: |
| with open(os.path.join(util.GetTelemetryThirdPartyDir(), p)) as f: |
| plugins += f.read() |
| return plugins |
| |
| def _GetUnitJson(self): |
| with open(_UNIT_JSON) as f: |
| return f.read() |
| |
| def _ReadExistingResults(self, output_stream): |
| results_html = 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))[:512] |
| |
| def _SaveResults(self, results): |
| self._output_stream.seek(0) |
| self._output_stream.write(unicode(results, 'utf-8')) |
| self._output_stream.truncate() |
| |
| def _PrintPerfResult(self, measurement, trace, values, units, |
| result_type='default', std=None): |
| metric_name = measurement |
| if trace != measurement: |
| metric_name += '.' + trace |
| self._result['tests'].setdefault(self._test_name, {}) |
| self._result['tests'][self._test_name].setdefault('metrics', {}) |
| metric_data = { |
| 'current': values, |
| 'units': units, |
| 'important': result_type == 'default' |
| } |
| if std is not None: |
| metric_data['std'] = std |
| self._result['tests'][self._test_name]['metrics'][metric_name] = metric_data |
| |
| def _TranslateChartJson(self, chart_json_dict): |
| dummy_dict = dict() |
| |
| for chart_name, traces in chart_json_dict['charts'].iteritems(): |
| for trace_name, value_dict in traces.iteritems(): |
| # TODO(eakuefner): refactor summarization so we don't have to jump |
| # through hoops like this. |
| if 'page_id' in value_dict: |
| del value_dict['page_id'] |
| result_type = 'nondefault' |
| else: |
| result_type = 'default' |
| |
| # Note: we explicitly ignore TraceValues because Buildbot did. |
| if value_dict['type'] == 'trace': |
| continue |
| value = value_module.Value.FromDict(value_dict, dummy_dict) |
| |
| perf_value = value.GetBuildbotValue() |
| |
| if '@@' in chart_name: |
| chart_name_to_print = '%s-%s' % tuple(chart_name.split('@@')) |
| else: |
| chart_name_to_print = str(chart_name) |
| |
| if trace_name == 'summary': |
| trace_name = chart_name_to_print |
| |
| std = None |
| if isinstance(value, list_of_scalar_values.ListOfScalarValues): |
| std = value.std |
| |
| self._PrintPerfResult(chart_name_to_print, trace_name, perf_value, |
| value.units, result_type, std) |
| |
| @property |
| def _test_name(self): |
| return self._metadata.name |
| |
| def GetResults(self): |
| return self._result |
| |
| def GetCombinedResults(self): |
| all_results = list(self._existing_results) |
| all_results.append(self.GetResults()) |
| return all_results |
| |
| def Format(self, page_test_results): |
| chart_json_dict = chart_json_output_formatter.ResultsAsChartDict( |
| self._metadata, page_test_results.all_page_specific_values, |
| page_test_results.all_summary_values) |
| |
| self._TranslateChartJson(chart_json_dict) |
| self._PrintPerfResult('telemetry_page_measurement_results', 'num_failed', |
| [len(page_test_results.failures)], 'count', |
| 'unimportant') |
| |
| html = self._GetHtmlTemplate() |
| html = html.replace('%json_results%', json.dumps(self.GetCombinedResults())) |
| html = html.replace('%json_units%', self._GetUnitJson()) |
| html = html.replace('%plugins%', self._GetPlugins()) |
| self._SaveResults(html) |
| |
| if self._upload_results: |
| file_path = os.path.abspath(self._output_stream.name) |
| file_name = 'html-results/results-%s' % datetime.datetime.now().strftime( |
| '%Y-%m-%d_%H-%M-%S') |
| try: |
| cloud_storage.Insert(cloud_storage.PUBLIC_BUCKET, file_name, file_path) |
| print |
| print ('View online at ' |
| 'http://storage.googleapis.com/chromium-telemetry/%s' |
| % file_name) |
| except cloud_storage.PermissionError as e: |
| logging.error('Cannot upload profiling files to cloud storage due to ' |
| ' permission error: %s' % e.message) |
| print |
| print 'View result at file://%s' % os.path.abspath( |
| self._output_stream.name) |