|  | # 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) |