Home | History | Annotate | Download | only in bench
      1 '''
      2 Created on May 16, 2011
      3 
      4 @author: bungeman
      5 '''
      6 import bench_util
      7 import getopt
      8 import httplib
      9 import itertools
     10 import json
     11 import os
     12 import re
     13 import sys
     14 import urllib
     15 import urllib2
     16 import xml.sax.saxutils
     17 
     18 # Maximum expected number of characters we expect in an svn revision.
     19 MAX_SVN_REV_LENGTH = 5
     20 
     21 # Indices for getting elements from bench expectation files.
     22 # See bench_expectations_<builder>.txt for details.
     23 EXPECTED_IDX = -3
     24 LB_IDX = -2
     25 UB_IDX = -1
     26 
     27 # Indices of the tuple of dictionaries containing slower and faster alerts.
     28 SLOWER = 0
     29 FASTER = 1
     30 
     31 # URL prefix for the bench dashboard page. Showing recent 15 days of data.
     32 DASHBOARD_URL_PREFIX = 'http://go/skpdash/#15'
     33 
     34 def usage():
     35     """Prints simple usage information."""
     36 
     37     print '-a <representation_alg> bench representation algorithm to use. '
     38     print '   Defaults to "25th". See bench_util.py for details.'
     39     print '-b <builder> name of the builder whose bench data we are checking.'
     40     print '-d <dir> a directory containing bench_<revision>_<scalar> files.'
     41     print '-e <file> file containing expected bench builder values/ranges.'
     42     print '   Will raise exception if actual bench values are out of range.'
     43     print '   See bench_expectations_<builder>.txt for data format / examples.'
     44     print '-r <revision> the git commit hash or svn revision for checking '
     45     print '   bench values.'
     46 
     47 
     48 class Label:
     49     """The information in a label.
     50 
     51     (str, str, str, str, {str:str})"""
     52     def __init__(self, bench, config, time_type, settings):
     53         self.bench = bench
     54         self.config = config
     55         self.time_type = time_type
     56         self.settings = settings
     57 
     58     def __repr__(self):
     59         return "Label(%s, %s, %s, %s)" % (
     60                    str(self.bench),
     61                    str(self.config),
     62                    str(self.time_type),
     63                    str(self.settings),
     64                )
     65 
     66     def __str__(self):
     67         return "%s_%s_%s_%s" % (
     68                    str(self.bench),
     69                    str(self.config),
     70                    str(self.time_type),
     71                    str(self.settings),
     72                )
     73 
     74     def __eq__(self, other):
     75         return (self.bench == other.bench and
     76                 self.config == other.config and
     77                 self.time_type == other.time_type and
     78                 self.settings == other.settings)
     79 
     80     def __hash__(self):
     81         return (hash(self.bench) ^
     82                 hash(self.config) ^
     83                 hash(self.time_type) ^
     84                 hash(frozenset(self.settings.iteritems())))
     85 
     86 def create_bench_dict(revision_data_points):
     87     """Convert current revision data into a dictionary of line data.
     88 
     89     Args:
     90       revision_data_points: a list of bench data points
     91 
     92     Returns:
     93       a dictionary of this form:
     94           keys = Label objects
     95           values = the corresponding bench value
     96     """
     97     bench_dict = {}
     98     for point in revision_data_points:
     99         point_name = Label(point.bench,point.config,point.time_type,
    100                            point.settings)
    101         if point_name not in bench_dict:
    102             bench_dict[point_name] = point.time
    103         else:
    104             raise Exception('Duplicate expectation entry: ' + str(point_name))
    105 
    106     return bench_dict
    107 
    108 def read_expectations(expectations, filename):
    109     """Reads expectations data from file and put in expectations dict."""
    110     for expectation in open(filename).readlines():
    111         elements = expectation.strip().split(',')
    112         if not elements[0] or elements[0].startswith('#'):
    113             continue
    114         if len(elements) != 5:
    115             raise Exception("Invalid expectation line format: %s" %
    116                             expectation)
    117         bench_entry = elements[0] + ',' + elements[1]
    118         if bench_entry in expectations:
    119             raise Exception("Dup entries for bench expectation %s" %
    120                             bench_entry)
    121         # [<Bench_BmpConfig_TimeType>,<Platform-Alg>] -> (LB, UB, EXPECTED)
    122         expectations[bench_entry] = (float(elements[LB_IDX]),
    123                                      float(elements[UB_IDX]),
    124                                      float(elements[EXPECTED_IDX]))
    125 
    126 def check_expectations(lines, expectations, key_suffix):
    127     """Check if any bench results are outside of expected range.
    128 
    129     For each input line in lines, checks the expectations dictionary to see if
    130     the bench is out of the given range.
    131 
    132     Args:
    133       lines: dictionary mapping Label objects to the bench values.
    134       expectations: dictionary returned by read_expectations().
    135       key_suffix: string of <Platform>-<Alg> containing the bot platform and the
    136         bench representation algorithm.
    137 
    138     Returns:
    139       No return value.
    140 
    141     Raises:
    142       Exception containing bench data that are out of range, if any.
    143     """
    144     # The platform for this bot, to pass to the dashboard plot.
    145     platform = key_suffix[ : key_suffix.rfind('-')]
    146     # Tuple of dictionaries recording exceptions that are slower and faster,
    147     # respectively. Each dictionary maps off_ratio (ratio of actual to expected)
    148     # to a list of corresponding exception messages.
    149     exceptions = ({}, {})
    150     for line in lines:
    151         line_str = str(line)
    152         line_str = line_str[ : line_str.find('_{')]
    153         # Extracts bench and config from line_str, which is in the format
    154         # <bench-picture-name>.skp_<config>_
    155         bench, config = line_str.strip('_').split('.skp_')
    156         bench_platform_key = line_str + ',' + key_suffix
    157         if bench_platform_key not in expectations:
    158             continue
    159         this_bench_value = lines[line]
    160         this_min, this_max, this_expected = expectations[bench_platform_key]
    161         if this_bench_value < this_min or this_bench_value > this_max:
    162             off_ratio = this_bench_value / this_expected
    163             exception = 'Bench %s out of range [%s, %s] (%s vs %s, %s%%).' % (
    164                 bench_platform_key, this_min, this_max, this_bench_value,
    165                 this_expected, (off_ratio - 1) * 100)
    166             exception += '\n' + '~'.join([
    167                 DASHBOARD_URL_PREFIX, bench, platform, config])
    168             if off_ratio > 1:  # Bench is slower.
    169                 exceptions[SLOWER].setdefault(off_ratio, []).append(exception)
    170             else:
    171                 exceptions[FASTER].setdefault(off_ratio, []).append(exception)
    172     outputs = []
    173     for i in [SLOWER, FASTER]:
    174       if exceptions[i]:
    175           ratios = exceptions[i].keys()
    176           ratios.sort(reverse=True)
    177           li = []
    178           for ratio in ratios:
    179               li.extend(exceptions[i][ratio])
    180           header = '%s benches got slower (sorted by %% difference):' % len(li)
    181           if i == FASTER:
    182               header = header.replace('slower', 'faster')
    183           outputs.extend(['', header] + li)
    184 
    185     if outputs:
    186         # Directly raising Exception will have stderr outputs tied to the line
    187         # number of the script, so use sys.stderr.write() instead.
    188         # Add a trailing newline to supress new line checking errors.
    189         sys.stderr.write('\n'.join(['Exception:'] + outputs + ['\n']))
    190         exit(1)
    191 
    192 
    193 def main():
    194     """Parses command line and checks bench expectations."""
    195     try:
    196         opts, _ = getopt.getopt(sys.argv[1:],
    197                                 "a:b:d:e:r:",
    198                                 "default-setting=")
    199     except getopt.GetoptError, err:
    200         print str(err)
    201         usage()
    202         sys.exit(2)
    203 
    204     directory = None
    205     bench_expectations = {}
    206     rep = '25th'  # bench representation algorithm, default to 25th
    207     rev = None  # git commit hash or svn revision number
    208     bot = None
    209 
    210     try:
    211         for option, value in opts:
    212             if option == "-a":
    213                 rep = value
    214             elif option == "-b":
    215                 bot = value
    216             elif option == "-d":
    217                 directory = value
    218             elif option == "-e":
    219                 read_expectations(bench_expectations, value)
    220             elif option == "-r":
    221                 rev = value
    222             else:
    223                 usage()
    224                 assert False, "unhandled option"
    225     except ValueError:
    226         usage()
    227         sys.exit(2)
    228 
    229     if directory is None or bot is None or rev is None:
    230         usage()
    231         sys.exit(2)
    232 
    233     platform_and_alg = bot + '-' + rep
    234 
    235     data_points = bench_util.parse_skp_bench_data(directory, rev, rep)
    236 
    237     bench_dict = create_bench_dict(data_points)
    238 
    239     if bench_expectations:
    240         check_expectations(bench_dict, bench_expectations, platform_and_alg)
    241 
    242 
    243 if __name__ == "__main__":
    244     main()
    245