Home | History | Annotate | Download | only in results
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import datetime
      6 import json
      7 import logging
      8 import os
      9 import re
     10 
     11 from catapult_base import cloud_storage  # pylint: disable=import-error
     12 
     13 from telemetry.core import util
     14 from telemetry.internal.results import chart_json_output_formatter
     15 from telemetry.internal.results import output_formatter
     16 from telemetry import value as value_module
     17 from telemetry.value import list_of_scalar_values
     18 
     19 
     20 _TEMPLATE_HTML_PATH = os.path.join(
     21     util.GetTelemetryDir(), 'support', 'html_output', 'results-template.html')
     22 _JS_PLUGINS = [os.path.join('flot', 'jquery.flot.min.js'),
     23                os.path.join('WebKit', 'PerformanceTests', 'resources',
     24                             'jquery.tablesorter.min.js'),
     25                os.path.join('WebKit', 'PerformanceTests', 'resources',
     26                             'statistics.js')]
     27 _UNIT_JSON = os.path.join(
     28     util.GetTelemetryDir(), 'telemetry', 'value', 'unit-info.json')
     29 
     30 
     31 def _DatetimeInEs5CompatibleFormat(dt):
     32   return dt.strftime('%Y-%m-%dT%H:%M:%S.%f')
     33 
     34 
     35 def _ShortDatetimeInEs5CompatibleFormat(dt):
     36   return dt.strftime('%Y-%m-%d %H:%M:%S')
     37 
     38 
     39 # TODO(eakuefner): rewrite template to use Telemetry JSON directly
     40 class HtmlOutputFormatter(output_formatter.OutputFormatter):
     41   def __init__(self, output_stream, metadata, reset_results, upload_results,
     42       browser_type, results_label=None):
     43     super(HtmlOutputFormatter, self).__init__(output_stream)
     44     self._metadata = metadata
     45     self._reset_results = reset_results
     46     self._upload_results = upload_results
     47     self._build_time = self._GetBuildTime()
     48     self._existing_results = self._ReadExistingResults(output_stream)
     49     if results_label:
     50       self._results_label = results_label
     51     else:
     52       self._results_label = '%s (%s)' % (
     53           metadata.name, _ShortDatetimeInEs5CompatibleFormat(self._build_time))
     54     self._result = {
     55         'buildTime': _DatetimeInEs5CompatibleFormat(self._build_time),
     56         'label': self._results_label,
     57         'platform': browser_type,
     58         'tests': {}
     59         }
     60 
     61   def _GetBuildTime(self):
     62     return datetime.datetime.utcnow()
     63 
     64   def _GetHtmlTemplate(self):
     65     with open(_TEMPLATE_HTML_PATH) as f:
     66       return f.read()
     67 
     68   def _GetPlugins(self):
     69     plugins = ''
     70     for p in _JS_PLUGINS:
     71       with open(os.path.join(util.GetTelemetryThirdPartyDir(), p)) as f:
     72         plugins += f.read()
     73     return plugins
     74 
     75   def _GetUnitJson(self):
     76     with open(_UNIT_JSON) as f:
     77       return f.read()
     78 
     79   def _ReadExistingResults(self, output_stream):
     80     results_html = output_stream.read()
     81     if self._reset_results or not results_html:
     82       return []
     83     m = re.search(
     84         '^<script id="results-json" type="application/json">(.*?)</script>$',
     85         results_html, re.MULTILINE | re.DOTALL)
     86     if not m:
     87       logging.warn('Failed to extract previous results from HTML output')
     88       return []
     89     return json.loads(m.group(1))[:512]
     90 
     91   def _SaveResults(self, results):
     92     self._output_stream.seek(0)
     93     self._output_stream.write(results)
     94     self._output_stream.truncate()
     95 
     96   def _PrintPerfResult(self, measurement, trace, values, units,
     97                        result_type='default', std=None):
     98     metric_name = measurement
     99     if trace != measurement:
    100       metric_name += '.' + trace
    101     self._result['tests'].setdefault(self._test_name, {})
    102     self._result['tests'][self._test_name].setdefault('metrics', {})
    103     metric_data = {
    104         'current': values,
    105         'units': units,
    106         'important': result_type == 'default'
    107         }
    108     if std is not None:
    109       metric_data['std'] = std
    110     self._result['tests'][self._test_name]['metrics'][metric_name] = metric_data
    111 
    112   def _TranslateChartJson(self, chart_json_dict):
    113     dummy_dict = dict()
    114 
    115     for chart_name, traces in chart_json_dict['charts'].iteritems():
    116       for trace_name, value_dict in traces.iteritems():
    117         # TODO(eakuefner): refactor summarization so we don't have to jump
    118         # through hoops like this.
    119         if 'page_id' in value_dict:
    120           del value_dict['page_id']
    121           result_type = 'nondefault'
    122         else:
    123           result_type = 'default'
    124 
    125         # Note: we explicitly ignore TraceValues because Buildbot did.
    126         if value_dict['type'] == 'trace':
    127           continue
    128         value = value_module.Value.FromDict(value_dict, dummy_dict)
    129 
    130         perf_value = value.GetBuildbotValue()
    131 
    132         if '@@' in chart_name:
    133           chart_name_to_print = '%s-%s' % tuple(chart_name.split('@@'))
    134         else:
    135           chart_name_to_print = str(chart_name)
    136 
    137         if trace_name == 'summary':
    138           trace_name = chart_name_to_print
    139 
    140         std = None
    141         if isinstance(value, list_of_scalar_values.ListOfScalarValues):
    142           std = value.std
    143 
    144         self._PrintPerfResult(chart_name_to_print, trace_name, perf_value,
    145                               value.units, result_type, std)
    146 
    147   @property
    148   def _test_name(self):
    149     return self._metadata.name
    150 
    151   def GetResults(self):
    152     return self._result
    153 
    154   def GetCombinedResults(self):
    155     all_results = list(self._existing_results)
    156     all_results.append(self.GetResults())
    157     return all_results
    158 
    159   def Format(self, page_test_results):
    160     chart_json_dict = chart_json_output_formatter.ResultsAsChartDict(
    161         self._metadata, page_test_results.all_page_specific_values,
    162         page_test_results.all_summary_values)
    163 
    164     self._TranslateChartJson(chart_json_dict)
    165     self._PrintPerfResult('telemetry_page_measurement_results', 'num_failed',
    166                           [len(page_test_results.failures)], 'count',
    167                           'unimportant')
    168 
    169     html = self._GetHtmlTemplate()
    170     html = html.replace('%json_results%', json.dumps(self.GetCombinedResults()))
    171     html = html.replace('%json_units%', self._GetUnitJson())
    172     html = html.replace('%plugins%', self._GetPlugins())
    173     self._SaveResults(html)
    174 
    175     if self._upload_results:
    176       file_path = os.path.abspath(self._output_stream.name)
    177       file_name = 'html-results/results-%s' % datetime.datetime.now().strftime(
    178           '%Y-%m-%d_%H-%M-%S')
    179       try:
    180         cloud_storage.Insert(cloud_storage.PUBLIC_BUCKET, file_name, file_path)
    181         print
    182         print ('View online at '
    183                'http://storage.googleapis.com/chromium-telemetry/%s'
    184                % file_name)
    185       except cloud_storage.PermissionError as e:
    186         logging.error('Cannot upload profiling files to cloud storage due to '
    187                       ' permission error: %s' % e.message)
    188     print
    189     print 'View result at file://%s' % os.path.abspath(
    190         self._output_stream.name)
    191