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 5import datetime 6import json 7import logging 8import os 9import re 10 11from telemetry.core import util 12from telemetry.results import buildbot_output_formatter 13from telemetry.util import cloud_storage 14 15util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'util') 16import lastchange # pylint: disable=F0401 17 18 19_TEMPLATE_HTML_PATH = os.path.join( 20 util.GetTelemetryDir(), 'support', 'html_output', 'results-template.html') 21_PLUGINS = [('third_party', 'flot', 'jquery.flot.min.js'), 22 ('third_party', 'WebKit', 'PerformanceTests', 'resources', 23 'jquery.tablesorter.min.js'), 24 ('third_party', 'WebKit', 'PerformanceTests', 'resources', 25 'statistics.js')] 26_UNIT_JSON = ('tools', 'perf', 'unit-info.json') 27 28 29# TODO(chrishenry): This should not really extend BuildbotOutputFormatter. 30# Leaving as-is now since we are going to move HtmlOutputFormatter to be 31# based on JSON anyway. 32class HtmlOutputFormatter(buildbot_output_formatter.BuildbotOutputFormatter): 33 def __init__(self, output_stream, metadata, reset_results, upload_results, 34 browser_type, results_label=None, trace_tag=''): 35 # Pass output_stream=None so that we blow up if 36 # BuildbotOutputFormatter ever use the output_stream. 37 super(HtmlOutputFormatter, self).__init__(None, trace_tag) 38 self._metadata = metadata 39 self._reset_results = reset_results 40 self._upload_results = upload_results 41 self._html_output_stream = output_stream 42 self._existing_results = self._ReadExistingResults(output_stream) 43 self._result = { 44 'buildTime': self._GetBuildTime(), 45 'revision': self._GetRevision(), 46 'label': results_label, 47 'platform': browser_type, 48 'tests': {} 49 } 50 51 def _GetBuildTime(self): 52 def _DatetimeInEs5CompatibleFormat(dt): 53 return dt.strftime('%Y-%m-%dT%H:%M:%S.%f') 54 return _DatetimeInEs5CompatibleFormat(datetime.datetime.utcnow()) 55 56 def _GetRevision(self): 57 return lastchange.FetchVersionInfo(None).revision 58 59 def _GetHtmlTemplate(self): 60 with open(_TEMPLATE_HTML_PATH) as f: 61 return f.read() 62 63 def _GetPlugins(self): 64 plugins = '' 65 for p in _PLUGINS: 66 with open(os.path.join(util.GetChromiumSrcDir(), *p)) as f: 67 plugins += f.read() 68 return plugins 69 70 def _GetUnitJson(self): 71 with open(os.path.join(util.GetChromiumSrcDir(), *_UNIT_JSON)) as f: 72 return f.read() 73 74 def _ReadExistingResults(self, output_stream): 75 results_html = output_stream.read() 76 if self._reset_results or not results_html: 77 return [] 78 m = re.search( 79 '^<script id="results-json" type="application/json">(.*?)</script>$', 80 results_html, re.MULTILINE | re.DOTALL) 81 if not m: 82 logging.warn('Failed to extract previous results from HTML output') 83 return [] 84 return json.loads(m.group(1))[:512] 85 86 def _SaveResults(self, results): 87 self._html_output_stream.seek(0) 88 self._html_output_stream.write(results) 89 self._html_output_stream.truncate() 90 91 def _PrintPerfResult(self, measurement, trace, values, units, 92 result_type='default'): 93 metric_name = measurement 94 if trace != measurement: 95 metric_name += '.' + trace 96 self._result['tests'].setdefault(self._test_name, {}) 97 self._result['tests'][self._test_name].setdefault('metrics', {}) 98 self._result['tests'][self._test_name]['metrics'][metric_name] = { 99 'current': values, 100 'units': units, 101 'important': result_type == 'default' 102 } 103 104 @property 105 def _test_name(self): 106 return self._metadata.name 107 108 def GetResults(self): 109 return self._result 110 111 def GetCombinedResults(self): 112 all_results = list(self._existing_results) 113 all_results.append(self.GetResults()) 114 return all_results 115 116 def Format(self, page_test_results): 117 super(HtmlOutputFormatter, self).Format(page_test_results) 118 119 html = self._GetHtmlTemplate() 120 html = html.replace('%json_results%', json.dumps(self.GetCombinedResults())) 121 html = html.replace('%json_units%', self._GetUnitJson()) 122 html = html.replace('%plugins%', self._GetPlugins()) 123 self._SaveResults(html) 124 125 if self._upload_results: 126 file_path = os.path.abspath(self._html_output_stream.name) 127 file_name = 'html-results/results-%s' % datetime.datetime.now().strftime( 128 '%Y-%m-%d_%H-%M-%S') 129 cloud_storage.Insert(cloud_storage.PUBLIC_BUCKET, file_name, file_path) 130 print 131 print ('View online at ' 132 'http://storage.googleapis.com/chromium-telemetry/%s' % file_name) 133 print 134 print 'View result at file://%s' % os.path.abspath( 135 self._html_output_stream.name) 136