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 11import bench_util 12import boto 13import math 14import optparse 15import os 16import re 17import shutil 18 19from oauth2_plugin import oauth2_plugin 20 21# The default platform to analyze. Used when OPTION_PLATFORM flag is not set. 22DEFAULT_PLATFORM = 'Nexus10_4-1_Float_Bench_32' 23 24# Template for gsutil uri. 25GOOGLE_STORAGE_URI_SCHEME = 'gs' 26URI_BUCKET = 'chromium-skia-gm' 27 28# Maximum number of rows of tiles to track for viewport covering. 29MAX_TILE_ROWS = 8 30 31# Constants for optparse. 32USAGE_STRING = 'USAGE: %s [options]' 33HOWTO_STRING = """ 34Note: to read bench data stored in Google Storage, you will need to set up the 35corresponding Python library. 36See http://developers.google.com/storage/docs/gspythonlibrary for details. 37""" 38HELP_STRING = """ 39For the given platform and revision number, find corresponding viewport and 40tile benchmarks for each available picture bench, and output visualization and 41analysis in HTML. By default it reads from Skia's Google Storage location where 42bot data are stored, but if --dir is given, will read from local directory 43instead. 44""" + HOWTO_STRING 45 46OPTION_DIR = '--dir' 47OPTION_DIR_SHORT = '-d' 48OPTION_REVISION = '--rev' 49OPTION_REVISION_SHORT = '-r' 50OPTION_PLATFORM = '--platform' 51OPTION_PLATFORM_SHORT = '-p' 52# Bench representation algorithm flag. 53OPTION_REPRESENTATION_ALG = '--algorithm' 54OPTION_REPRESENTATION_ALG_SHORT = '-a' 55 56# Bench representation algorithm. See trunk/bench/bench_util.py. 57REPRESENTATION_ALG = bench_util.ALGORITHM_25TH_PERCENTILE 58 59# Constants for bench file matching. 60GOOGLE_STORAGE_OBJECT_NAME_PREFIX = 'perfdata/Skia_' 61BENCH_FILE_PREFIX_TEMPLATE = 'bench_r%s_' 62TILING_FILE_NAME_INDICATOR = '_tile_' 63VIEWPORT_FILE_NAME_INDICATOR = '_viewport_' 64 65# Regular expression for matching format '<integer>x<integer>'. 66DIMENSIONS_RE = '(\d+)x(\d+)' 67 68# HTML and JS output templates. 69HTML_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.googlecode.com/svn/trunk/src/main/js/load.js"></script><script 74type="text/javascript"> systemsbiology.load("visualization", "1.0", 75{packages:["bioheatmap"]});</script><script type="text/javascript"> 76google.setOnLoadCallback(drawVisualization); function drawVisualization() { 77""" 78HTML_SUFFIX = '</body></html>' 79BAR_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') 82DRAW_OPTIONS = ('{passThroughBlack:false,useRowLabels:false,cellWidth:30,' 83 'cellHeight:30}') 84TABLE_OPTIONS = '{showRowNumber:true,firstRowNumber:" ",sort:"disable"}' 85 86def 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 130def 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 160def 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 183def 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 251def 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 278if '__main__' == __name__: 279 main() 280