| # Copyright 2013 The Chromium Authors |
| # 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) |