1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""This is a helper module to get and manipulate histogram data.
6
7The histogram data is the same data as is visible from "chrome://histograms".
8More information can be found at: chromium/src/base/metrics/histogram.h
9"""
10
11import collections
12import json
13import logging
14
15from telemetry.core import exceptions
16
17BROWSER_HISTOGRAM = 'browser_histogram'
18RENDERER_HISTOGRAM = 'renderer_histogram'
19
20
21def GetHistogramBucketsFromJson(histogram_json):
22  return GetHistogramBucketsFromRawValue(json.loads(histogram_json))
23
24
25def GetHistogramBucketsFromRawValue(raw_value):
26  buckets = raw_value.get('buckets', [])
27  if buckets:
28    # If there are values greater than the maximum allowable for the histogram,
29    # the highest bucket will have a 'low': maxvalue entry in the dict but no
30    # 'high' entry. Code often assumes the 'high' value will always be present,
31    # and uses it to get bucket mean. So default it to the same value as low.
32    buckets[-1].setdefault('high', buckets[-1]['low'])
33  return buckets
34
35
36def CustomizeBrowserOptions(options):
37  """Allows histogram collection."""
38  options.AppendExtraBrowserArgs(['--enable-stats-collection-bindings'])
39
40
41def SubtractHistogram(histogram_json, start_histogram_json):
42  """Subtracts a previous histogram from a histogram.
43
44  Both parameters and the returned result are json serializations.
45  """
46  start_histogram = json.loads(start_histogram_json)
47  start_histogram_buckets = GetHistogramBucketsFromRawValue(start_histogram)
48  # It's ok if the start histogram is empty (we had no data, maybe even no
49  # histogram at all, at the start of the test).
50  if not start_histogram_buckets:
51    return histogram_json
52
53  histogram = json.loads(histogram_json)
54  if ('pid' in start_histogram and 'pid' in histogram
55      and start_histogram['pid'] != histogram['pid']):
56    raise Exception(
57        'Trying to compare histograms from different processes (%d and %d)'
58        % (start_histogram['pid'], histogram['pid']))
59
60  start_histogram_bucket_counts = dict()
61  for b in start_histogram_buckets:
62    start_histogram_bucket_counts[b['low']] = b['count']
63
64  new_buckets = []
65  for b in GetHistogramBucketsFromRawValue(histogram):
66    new_bucket = b
67    low = b['low']
68    if low in start_histogram_bucket_counts:
69      new_bucket['count'] = b['count'] - start_histogram_bucket_counts[low]
70      if new_bucket['count'] < 0:
71        logging.error('Histogram subtraction error, starting histogram most '
72                      'probably invalid.')
73    if new_bucket['count']:
74      new_buckets.append(new_bucket)
75  histogram['buckets'] = new_buckets
76  histogram['count'] -= start_histogram['count']
77
78  return json.dumps(histogram)
79
80
81def AddHistograms(histogram_jsons):
82  """Adds histograms together. Used for aggregating data.
83
84  The parameter is a list of json serializations and the returned result is a
85  json serialization too.
86
87  Note that the histograms to be added together are typically from different
88  processes.
89  """
90
91  buckets = collections.defaultdict(int)
92  for histogram_json in histogram_jsons:
93    for b in GetHistogramBucketsFromJson(histogram_json):
94      key = (b['low'], b['high'])
95      buckets[key] += b['count']
96
97  buckets = [{'low': key[0], 'high': key[1], 'count': value}
98      for key, value in buckets.iteritems()]
99  buckets.sort(key=lambda h: h['low'])
100
101  result_histogram = {}
102  result_histogram['buckets'] = buckets
103  return json.dumps(result_histogram)
104
105
106def GetHistogram(histogram_type, histogram_name, tab):
107  """Get a json serialization of a histogram."""
108  assert histogram_type in [BROWSER_HISTOGRAM, RENDERER_HISTOGRAM]
109  function = 'getHistogram'
110  if histogram_type == BROWSER_HISTOGRAM:
111    function = 'getBrowserHistogram'
112  try:
113    histogram_json = tab.EvaluateJavaScript(
114        'statsCollectionController.%s("%s")' %
115        (function, histogram_name))
116  except exceptions.EvaluateException:
117    # Sometimes JavaScript flakily fails to execute: http://crbug.com/508431
118    histogram_json = None
119  if histogram_json:
120    return histogram_json
121  return None
122
123
124def GetHistogramCount(histogram_type, histogram_name, tab):
125  """Get the count of events for the given histograms."""
126  histogram_json = GetHistogram(histogram_type, histogram_name, tab)
127  histogram = json.loads(histogram_json)
128  if 'count' in histogram:
129    return histogram['count']
130  else:
131    return 0
132
133def GetHistogramSum(histogram_type, histogram_name, tab):
134  """Get the sum of events for the given histograms."""
135  histogram_json = GetHistogram(histogram_type, histogram_name, tab)
136  histogram = json.loads(histogram_json)
137  if 'sum' in histogram:
138    return histogram['sum']
139  else:
140    return 0
141