OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be found | |
4 # in the LICENSE file. | |
5 | |
6 """ Analyze per-tile and viewport bench data, and output visualized results. | |
7 """ | |
8 | |
9 __author__ = 'bensong@google.com (Ben Chen)' | |
10 | |
11 import bench_util | |
12 import boto | |
13 import math | |
14 import optparse | |
15 import os | |
16 import re | |
17 import shutil | |
18 | |
19 from oauth2_plugin import oauth2_plugin | |
20 | |
21 # The default platform to analyze. Used when OPTION_PLATFORM flag is not set. | |
22 DEFAULT_PLATFORM = 'Nexus10_4-1_Float_Bench_32' | |
23 | |
24 # Template for gsutil uri. | |
25 GOOGLE_STORAGE_URI_SCHEME = 'gs' | |
26 URI_BUCKET = 'chromium-skia-gm' | |
27 | |
28 # Maximum number of rows of tiles to track for viewport covering. | |
29 MAX_TILE_ROWS = 8 | |
30 | |
31 # Constants for optparse. | |
32 USAGE_STRING = 'USAGE: %s [options]' | |
33 HOWTO_STRING = """ | |
34 Note: to read bench data stored in Google Storage, you will need to set up the | |
35 corresponding Python library. | |
36 See http://developers.google.com/storage/docs/gspythonlibrary for details. | |
37 """ | |
38 HELP_STRING = """ | |
39 For the given platform and revision number, find corresponding viewport and | |
40 tile benchmarks for each available picture bench, and output visualization and | |
41 analysis in HTML. By default it reads from Skia's Google Storage location where | |
42 bot data are stored, but if --dir is given, will read from local directory | |
43 instead. | |
44 """ + HOWTO_STRING | |
45 | |
46 OPTION_DIR = '--dir' | |
47 OPTION_DIR_SHORT = '-d' | |
48 OPTION_REVISION = '--rev' | |
49 OPTION_REVISION_SHORT = '-r' | |
50 OPTION_PLATFORM = '--platform' | |
51 OPTION_PLATFORM_SHORT = '-p' | |
52 # Bench representation algorithm flag. | |
53 OPTION_REPRESENTATION_ALG = '--algorithm' | |
54 OPTION_REPRESENTATION_ALG_SHORT = '-a' | |
55 | |
56 # Bench representation algorithm. See trunk/bench/bench_util.py. | |
57 REPRESENTATION_ALG = bench_util.ALGORITHM_25TH_PERCENTILE | |
58 | |
59 # Constants for bench file matching. | |
60 GOOGLE_STORAGE_OBJECT_NAME_PREFIX = 'perfdata/Skia_' | |
61 BENCH_FILE_PREFIX_TEMPLATE = 'bench_r%s_' | |
62 TILING_FILE_NAME_INDICATOR = '_tile_' | |
63 VIEWPORT_FILE_NAME_INDICATOR = '_viewport_' | |
64 | |
65 # Regular expression for matching format '<integer>x<integer>'. | |
66 DIMENSIONS_RE = '(\d+)x(\d+)' | |
67 | |
68 # HTML and JS output templates. | |
69 HTML_PREFIX = """ | |
70 <html><head><script type="text/javascript" src="https://www.google.com/jsapi"> | |
71 </script><script type="text/javascript">google.load("visualization", "1.1", | |
72 {packages:["table"]});google.load("prototype", "1.6");</script> | |
73 <script type="text/javascript" src="https://systemsbiology-visualizations.google
code.com/svn/trunk/src/main/js/load.js"></script><script | |
74 type="text/javascript"> systemsbiology.load("visualization", "1.0", | |
75 {packages:["bioheatmap"]});</script><script type="text/javascript"> | |
76 google.setOnLoadCallback(drawVisualization); function drawVisualization() { | |
77 """ | |
78 HTML_SUFFIX = '</body></html>' | |
79 BAR_CHART_TEMPLATE = ('<img src="https://chart.googleapis.com/chart?chxr=0,0,' | |
80 '300&chxt=x&chbh=15,0&chs=600x150&cht=bhg&chco=80C65A,224499,FF0000,0A8C8A,' | |
81 'EBB671,DE091A,000000,00ffff&chds=a&chdl=%s&chd=t:%s" /><br>\n') | |
82 DRAW_OPTIONS = ('{passThroughBlack:false,useRowLabels:false,cellWidth:30,' | |
83 'cellHeight:30}') | |
84 TABLE_OPTIONS = '{showRowNumber:true,firstRowNumber:" ",sort:"disable"}' | |
85 | |
86 def GetFiles(rev, bench_dir, platform): | |
87 """Reads in bench files of interest into a dictionary. | |
88 | |
89 If bench_dir is not empty, tries to read in local bench files; otherwise check | |
90 Google Storage. Filters files by revision (rev) and platform, and ignores | |
91 non-tile, non-viewport bench files. | |
92 Outputs dictionary [filename] -> [file content]. | |
93 """ | |
94 file_dic = {} | |
95 if not bench_dir: | |
96 uri = boto.storage_uri(URI_BUCKET, GOOGLE_STORAGE_URI_SCHEME) | |
97 # The boto API does not allow prefix/wildcard matching of Google Storage | |
98 # objects. And Google Storage has a flat structure instead of being | |
99 # organized in directories. Therefore, we have to scan all objects in the | |
100 # Google Storage bucket to find the files we need, which is slow. | |
101 # The option of implementing prefix matching as in gsutil seems to be | |
102 # overkill, but gsutil does not provide an API ready for use. If speed is a | |
103 # big concern, we suggest copying bot bench data from Google Storage using | |
104 # gsutil and use --log_dir for fast local data reading. | |
105 for obj in uri.get_bucket(): | |
106 # Filters out files of no interest. | |
107 if (not obj.name.startswith(GOOGLE_STORAGE_OBJECT_NAME_PREFIX) or | |
108 (obj.name.find(TILING_FILE_NAME_INDICATOR) < 0 and | |
109 obj.name.find(VIEWPORT_FILE_NAME_INDICATOR) < 0) or | |
110 obj.name.find(platform) < 0 or | |
111 obj.name.find(BENCH_FILE_PREFIX_TEMPLATE % rev) < 0): | |
112 continue | |
113 file_dic[ | |
114 obj.name[obj.name.rfind('/') + 1 : ]] = obj.get_contents_as_string() | |
115 else: | |
116 for f in os.listdir(bench_dir): | |
117 if (not os.path.isfile(os.path.join(bench_dir, f)) or | |
118 (f.find(TILING_FILE_NAME_INDICATOR) < 0 and | |
119 f.find(VIEWPORT_FILE_NAME_INDICATOR) < 0) or | |
120 not f.startswith(BENCH_FILE_PREFIX_TEMPLATE % rev)): | |
121 continue | |
122 file_dic[f] = open(os.path.join(bench_dir, f)).read() | |
123 | |
124 if not file_dic: | |
125 raise Exception('No bench file found in "%s" or Google Storage.' % | |
126 bench_dir) | |
127 | |
128 return file_dic | |
129 | |
130 def GetTileMatrix(layout, tile_size, values, viewport): | |
131 """For the given tile layout and per-tile bench values, returns a matrix of | |
132 bench values with tiles outside the given viewport set to 0. | |
133 | |
134 layout, tile_size and viewport are given in string of format <w>x<h>, where | |
135 <w> is viewport width or number of tile columns, and <h> is viewport height or | |
136 number of tile rows. We truncate tile rows to MAX_TILE_ROWS to adjust for very | |
137 long skp's. | |
138 | |
139 values: per-tile benches ordered row-by-row, starting from the top-left tile. | |
140 | |
141 Returns [sum, matrix] where sum is the total bench tile time that covers the | |
142 viewport, and matrix is used for visualizing the tiles. | |
143 """ | |
144 [tile_cols, tile_rows] = [int(i) for i in layout.split('x')] | |
145 [tile_x, tile_y] = [int(i) for i in tile_size.split('x')] | |
146 [viewport_x, viewport_y] = [int(i) for i in viewport.split('x')] | |
147 viewport_cols = int(math.ceil(viewport_x * 1.0 / tile_x)) | |
148 viewport_rows = int(math.ceil(viewport_y * 1.0 / tile_y)) | |
149 truncated_tile_rows = min(tile_rows, MAX_TILE_ROWS) | |
150 | |
151 viewport_tile_sum = 0 | |
152 matrix = [[0 for y in range(tile_cols)] for x in range(truncated_tile_rows)] | |
153 for y in range(min(viewport_cols, tile_cols)): | |
154 for x in range(min(truncated_tile_rows, viewport_rows)): | |
155 matrix[x][y] = values[x * tile_cols + y] | |
156 viewport_tile_sum += values[x * tile_cols + y] | |
157 | |
158 return [viewport_tile_sum, matrix] | |
159 | |
160 def GetTileVisCodes(suffix, matrix): | |
161 """Generates and returns strings of [js_codes, row1, row2] which are codes for | |
162 visualizing the benches from the given tile config and matrix data. | |
163 row1 is used for the first row of heatmaps; row2 is for corresponding tables. | |
164 suffix is only used to avoid name conflicts in the whole html output. | |
165 """ | |
166 this_js = 'var data_%s=new google.visualization.DataTable();' % suffix | |
167 for i in range(len(matrix[0])): | |
168 this_js += 'data_%s.addColumn("number","%s");' % (suffix, i) | |
169 this_js += 'data_%s.addRows(%s);' % (suffix, str(matrix)) | |
170 # Adds heatmap chart. | |
171 this_js += ('var heat_%s=new org.systemsbiology.visualization' % suffix + | |
172 '.BioHeatMap(document.getElementById("%s"));' % suffix + | |
173 'heat_%s.draw(data_%s,%s);' % (suffix, suffix, DRAW_OPTIONS)) | |
174 # Adds data table chart. | |
175 this_js += ('var table_%s=new google.visualization.Table(document.' % suffix + | |
176 'getElementById("t%s"));table_%s.draw(data_%s,%s);\n' % ( | |
177 suffix, suffix, suffix, TABLE_OPTIONS)) | |
178 table_row1 = '<td>%s<div id="%s"></div></td>' % (suffix, suffix) | |
179 table_row2 = '<td><div id="t%s"></div></td>' % suffix | |
180 | |
181 return [this_js, table_row1, table_row2] | |
182 | |
183 def OutputTileAnalysis(rev, representation_alg, bench_dir, platform): | |
184 """Reads skp bench data and outputs tile vs. viewport analysis for the given | |
185 platform. | |
186 | |
187 Ignores data with revisions other than rev. If bench_dir is not empty, read | |
188 from the local directory instead of Google Storage. | |
189 Uses the provided representation_alg for calculating bench representations. | |
190 | |
191 Returns (js_codes, body_codes): strings of js/html codes for stats and | |
192 visualization. | |
193 """ | |
194 js_codes = '' | |
195 body_codes = ('}</script></head><body>' | |
196 '<h3>PLATFORM: %s REVISION: %s</h3><br>' % (platform, rev)) | |
197 bench_dic = {} # [bench][config] -> [layout, [values]] | |
198 file_dic = GetFiles(rev, bench_dir, platform) | |
199 for f in file_dic: | |
200 for point in bench_util.parse('', file_dic[f].split('\n'), | |
201 representation_alg): | |
202 if point.time_type: # Ignores non-walltime time_type. | |
203 continue | |
204 bench = point.bench.replace('.skp', '') | |
205 config = point.config.replace('simple_', '') | |
206 components = config.split('_') | |
207 if components[0] == 'viewport': | |
208 bench_dic.setdefault(bench, {})[config] = [components[1], [point.time]] | |
209 else: # Stores per-tile benches. | |
210 bench_dic.setdefault(bench, {})[config] = [ | |
211 point.tile_layout, point.per_tile_values] | |
212 benches = bench_dic.keys() | |
213 benches.sort() | |
214 for bench in benches: | |
215 body_codes += '<h4>%s</h4><br><table><tr>' % bench | |
216 heat_plots = '' # For table row of heatmap plots. | |
217 table_plots = '' # For table row of data table plots. | |
218 # For bar plot legends and values in URL string. | |
219 legends = '' | |
220 values = '' | |
221 keys = bench_dic[bench].keys() | |
222 keys.sort() | |
223 if not keys[-1].startswith('viewport'): # No viewport to analyze; skip. | |
224 continue | |
225 else: | |
226 # Extracts viewport size, which for all viewport configs is the same. | |
227 viewport = bench_dic[bench][keys[-1]][0] | |
228 for config in keys: | |
229 [layout, value_li] = bench_dic[bench][config] | |
230 if config.startswith('tile_'): # For per-tile data, visualize tiles. | |
231 tile_size = config.split('_')[1] | |
232 if (not re.search(DIMENSIONS_RE, layout) or | |
233 not re.search(DIMENSIONS_RE, tile_size) or | |
234 not re.search(DIMENSIONS_RE, viewport)): | |
235 continue # Skip unrecognized formats. | |
236 [viewport_tile_sum, matrix] = GetTileMatrix( | |
237 layout, tile_size, value_li, viewport) | |
238 values += '%s|' % viewport_tile_sum | |
239 [this_js, row1, row2] = GetTileVisCodes(config + '_' + bench, matrix) | |
240 heat_plots += row1 | |
241 table_plots += row2 | |
242 js_codes += this_js | |
243 else: # For viewport data, there is only one element in value_li. | |
244 values += '%s|' % sum(value_li) | |
245 legends += '%s:%s|' % (config, sum(value_li)) | |
246 body_codes += (heat_plots + '</tr><tr>' + table_plots + '</tr></table>' + | |
247 '<br>' + BAR_CHART_TEMPLATE % (legends[:-1], values[:-1])) | |
248 | |
249 return (js_codes, body_codes) | |
250 | |
251 def main(): | |
252 """Parses flags and outputs expected Skia picture bench results.""" | |
253 parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING) | |
254 parser.add_option(OPTION_PLATFORM_SHORT, OPTION_PLATFORM, | |
255 dest='plat', default=DEFAULT_PLATFORM, | |
256 help='Platform to analyze. Set to DEFAULT_PLATFORM if not given.') | |
257 parser.add_option(OPTION_REVISION_SHORT, OPTION_REVISION, | |
258 dest='rev', | |
259 help='(Mandatory) revision number to analyze.') | |
260 parser.add_option(OPTION_DIR_SHORT, OPTION_DIR, | |
261 dest='log_dir', default='', | |
262 help=('(Optional) local directory where bench log files reside. If left ' | |
263 'empty (by default), will try to read from Google Storage.')) | |
264 parser.add_option(OPTION_REPRESENTATION_ALG_SHORT, OPTION_REPRESENTATION_ALG, | |
265 dest='alg', default=REPRESENTATION_ALG, | |
266 help=('Bench representation algorithm. ' | |
267 'Default to "%s".' % REPRESENTATION_ALG)) | |
268 (options, args) = parser.parse_args() | |
269 if not (options.rev and options.rev.isdigit()): | |
270 parser.error('Please provide correct mandatory flag %s' % OPTION_REVISION) | |
271 return | |
272 rev = int(options.rev) | |
273 (js_codes, body_codes) = OutputTileAnalysis( | |
274 rev, options.alg, options.log_dir, options.plat) | |
275 print HTML_PREFIX + js_codes + body_codes + HTML_SUFFIX | |
276 | |
277 | |
278 if '__main__' == __name__: | |
279 main() | |
OLD | NEW |