| # Copyright 2013 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | import re | 
 | import sys | 
 |  | 
 | import json | 
 | import logging | 
 | import math | 
 |  | 
 | import perf_result_data_type | 
 |  | 
 |  | 
 | # Mapping from result type to test output | 
 | RESULT_TYPES = {perf_result_data_type.UNIMPORTANT: 'RESULT ', | 
 |                 perf_result_data_type.DEFAULT: '*RESULT ', | 
 |                 perf_result_data_type.INFORMATIONAL: '', | 
 |                 perf_result_data_type.UNIMPORTANT_HISTOGRAM: 'HISTOGRAM ', | 
 |                 perf_result_data_type.HISTOGRAM: '*HISTOGRAM '} | 
 |  | 
 |  | 
 | def _EscapePerfResult(s): | 
 |   """Escapes |s| for use in a perf result.""" | 
 |   return re.sub('[\:|=/#&,]', '_', s) | 
 |  | 
 |  | 
 | def FlattenList(values): | 
 |   """Returns a simple list without sub-lists.""" | 
 |   ret = [] | 
 |   for entry in values: | 
 |     if isinstance(entry, list): | 
 |       ret.extend(FlattenList(entry)) | 
 |     else: | 
 |       ret.append(entry) | 
 |   return ret | 
 |  | 
 |  | 
 | def GeomMeanAndStdDevFromHistogram(histogram_json): | 
 |   histogram = json.loads(histogram_json) | 
 |   # Handle empty histograms gracefully. | 
 |   if not 'buckets' in histogram: | 
 |     return 0.0, 0.0 | 
 |   count = 0 | 
 |   sum_of_logs = 0 | 
 |   for bucket in histogram['buckets']: | 
 |     if 'high' in bucket: | 
 |       bucket['mean'] = (bucket['low'] + bucket['high']) / 2.0 | 
 |     else: | 
 |       bucket['mean'] = bucket['low'] | 
 |     if bucket['mean'] > 0: | 
 |       sum_of_logs += math.log(bucket['mean']) * bucket['count'] | 
 |       count += bucket['count'] | 
 |  | 
 |   if count == 0: | 
 |     return 0.0, 0.0 | 
 |  | 
 |   sum_of_squares = 0 | 
 |   geom_mean = math.exp(sum_of_logs / count) | 
 |   for bucket in histogram['buckets']: | 
 |     if bucket['mean'] > 0: | 
 |       sum_of_squares += (bucket['mean'] - geom_mean) ** 2 * bucket['count'] | 
 |   return geom_mean, math.sqrt(sum_of_squares / count) | 
 |  | 
 |  | 
 | def _ValueToString(v): | 
 |   # Special case for floats so we don't print using scientific notation. | 
 |   if isinstance(v, float): | 
 |     return '%f' % v | 
 |   else: | 
 |     return str(v) | 
 |  | 
 |  | 
 | def _MeanAndStdDevFromList(values): | 
 |   avg = None | 
 |   sd = None | 
 |   if len(values) > 1: | 
 |     try: | 
 |       value = '[%s]' % ','.join([_ValueToString(v) for v in values]) | 
 |       avg = sum([float(v) for v in values]) / len(values) | 
 |       sqdiffs = [(float(v) - avg) ** 2 for v in values] | 
 |       variance = sum(sqdiffs) / (len(values) - 1) | 
 |       sd = math.sqrt(variance) | 
 |     except ValueError: | 
 |       value = ', '.join(values) | 
 |   else: | 
 |     value = values[0] | 
 |   return value, avg, sd | 
 |  | 
 |  | 
 | def PrintPages(page_list): | 
 |   """Prints list of pages to stdout in the format required by perf tests.""" | 
 |   print 'Pages: [%s]' % ','.join([_EscapePerfResult(p) for p in page_list]) | 
 |  | 
 |  | 
 | def PrintPerfResult(measurement, trace, values, units, | 
 |                     result_type=perf_result_data_type.DEFAULT, | 
 |                     print_to_stdout=True): | 
 |   """Prints numerical data to stdout in the format required by perf tests. | 
 |  | 
 |   The string args may be empty but they must not contain any colons (:) or | 
 |   equals signs (=). | 
 |   This is parsed by the buildbot using: | 
 |   http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/slave/process_log_utils.py | 
 |  | 
 |   Args: | 
 |     measurement: A description of the quantity being measured, e.g. "vm_peak". | 
 |         On the dashboard, this maps to a particular graph. Mandatory. | 
 |     trace: A description of the particular data point, e.g. "reference". | 
 |         On the dashboard, this maps to a particular "line" in the graph. | 
 |         Mandatory. | 
 |     values: A list of numeric measured values. An N-dimensional list will be | 
 |         flattened and treated as a simple list. | 
 |     units: A description of the units of measure, e.g. "bytes". | 
 |     result_type: Accepts values of perf_result_data_type.ALL_TYPES. | 
 |     print_to_stdout: If True, prints the output in stdout instead of returning | 
 |         the output to caller. | 
 |  | 
 |     Returns: | 
 |       String of the formated perf result. | 
 |   """ | 
 |   assert perf_result_data_type.IsValidType(result_type), \ | 
 |          'result type: %s is invalid' % result_type | 
 |  | 
 |   trace_name = _EscapePerfResult(trace) | 
 |  | 
 |   if (result_type == perf_result_data_type.UNIMPORTANT or | 
 |       result_type == perf_result_data_type.DEFAULT or | 
 |       result_type == perf_result_data_type.INFORMATIONAL): | 
 |     assert isinstance(values, list) | 
 |     assert '/' not in measurement | 
 |     flattened_values = FlattenList(values) | 
 |     assert len(flattened_values) | 
 |     value, avg, sd = _MeanAndStdDevFromList(flattened_values) | 
 |     output = '%s%s: %s%s%s %s' % ( | 
 |         RESULT_TYPES[result_type], | 
 |         _EscapePerfResult(measurement), | 
 |         trace_name, | 
 |         # Do not show equal sign if the trace is empty. Usually it happens when | 
 |         # measurement is enough clear to describe the result. | 
 |         '= ' if trace_name else '', | 
 |         value, | 
 |         units) | 
 |   else: | 
 |     assert perf_result_data_type.IsHistogram(result_type) | 
 |     assert isinstance(values, list) | 
 |     # The histograms can only be printed individually, there's no computation | 
 |     # across different histograms. | 
 |     assert len(values) == 1 | 
 |     value = values[0] | 
 |     output = '%s%s: %s= %s %s' % ( | 
 |         RESULT_TYPES[result_type], | 
 |         _EscapePerfResult(measurement), | 
 |         trace_name, | 
 |         value, | 
 |         units) | 
 |     avg, sd = GeomMeanAndStdDevFromHistogram(value) | 
 |  | 
 |   if avg: | 
 |     output += '\nAvg %s: %f%s' % (measurement, avg, units) | 
 |   if sd: | 
 |     output += '\nSd  %s: %f%s' % (measurement, sd, units) | 
 |   if print_to_stdout: | 
 |     print output | 
 |     sys.stdout.flush() | 
 |   return output | 
 |  | 
 |  | 
 | def ReportPerfResult(chart_data, graph_title, trace_title, value, units, | 
 |                      improvement_direction='down', important=True): | 
 |   """Outputs test results in correct format. | 
 |  | 
 |   If chart_data is None, it outputs data in old format. If chart_data is a | 
 |   dictionary, formats in chartjson format. If any other format defaults to | 
 |   old format. | 
 |  | 
 |   Args: | 
 |     chart_data: A dictionary corresponding to perf results in the chartjson | 
 |         format. | 
 |     graph_title: A string containing the name of the chart to add the result | 
 |         to. | 
 |     trace_title: A string containing the name of the trace within the chart | 
 |         to add the result to. | 
 |     value: The value of the result being reported. | 
 |     units: The units of the value being reported. | 
 |     improvement_direction: A string denoting whether higher or lower is | 
 |         better for the result. Either 'up' or 'down'. | 
 |     important: A boolean denoting whether the result is important or not. | 
 |   """ | 
 |   if chart_data and isinstance(chart_data, dict): | 
 |     chart_data['charts'].setdefault(graph_title, {}) | 
 |     chart_data['charts'][graph_title][trace_title] = { | 
 |         'type': 'scalar', | 
 |         'value': value, | 
 |         'units': units, | 
 |         'improvement_direction': improvement_direction, | 
 |         'important': important | 
 |     } | 
 |   else: | 
 |     PrintPerfResult(graph_title, trace_title, [value], units) |