Home | History | Annotate | Download | only in bench
      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 (at] 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.googlecode.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()
    280