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.
4import json
5
6from telemetry import value as value_module
7from telemetry import perf_tests_helper
8from telemetry.value import histogram_util
9
10class HistogramValueBucket(object):
11  def __init__(self, low, high, count=0):
12    self.low = low
13    self.high = high
14    self.count = count
15
16  def AsDict(self):
17    return {
18      'low': self.low,
19      'high': self.high,
20      'count': self.count
21    }
22
23  def ToJSONString(self):
24    return '{%s}' % ', '.join([
25      '"low": %i' % self.low,
26      '"high": %i' % self.high,
27      '"count": %i' % self.count])
28
29class HistogramValue(value_module.Value):
30  def __init__(self, page, name, units,
31               raw_value=None, raw_value_json=None, important=True,
32               description=None):
33    super(HistogramValue, self).__init__(page, name, units, important,
34                                         description)
35    if raw_value_json:
36      assert raw_value == None, \
37             'Don\'t specify both raw_value and raw_value_json'
38      raw_value = json.loads(raw_value_json)
39    if raw_value:
40      assert 'buckets' in raw_value
41      assert isinstance(raw_value['buckets'], list)
42      self.buckets = []
43      for bucket in raw_value['buckets']:
44        self.buckets.append(HistogramValueBucket(
45          low=bucket['low'],
46          high=bucket['high'],
47          count=bucket['count']))
48    else:
49      self.buckets = []
50
51  def __repr__(self):
52    if self.page:
53      page_name = self.page.url
54    else:
55      page_name = None
56    return ('HistogramValue(%s, %s, %s, raw_json_string="%s", '
57            'important=%s, description=%s') % (
58              page_name,
59              self.name, self.units,
60              self.ToJSONString(),
61              self.important,
62              self.description)
63
64  def GetBuildbotDataType(self, output_context):
65    if self._IsImportantGivenOutputIntent(output_context):
66      return 'histogram'
67    return 'unimportant-histogram'
68
69  def GetBuildbotValue(self):
70    # More buildbot insanity: perf_tests_results_helper requires the histogram
71    # to be an array of size one.
72    return [self.ToJSONString()]
73
74  def ToJSONString(self):
75    # This has to hand-JSONify the histogram to ensure the order of keys
76    # produced is stable across different systems.
77    #
78    # This is done because the buildbot unittests are string equality
79    # assertions. Thus, tests that contain histograms require stable
80    # stringification of the histogram.
81    #
82    # Sigh, buildbot, Y U gotta be that way.
83    return '{"buckets": [%s]}' % (
84      ', '.join([b.ToJSONString() for b in self.buckets]))
85
86  def GetRepresentativeNumber(self):
87    (mean, _) = perf_tests_helper.GeomMeanAndStdDevFromHistogram(
88        self.ToJSONString())
89    return mean
90
91  def GetRepresentativeString(self):
92    return self.GetBuildbotValue()
93
94  @staticmethod
95  def GetJSONTypeName():
96    return 'histogram'
97
98  def AsDict(self):
99    d = super(HistogramValue, self).AsDict()
100    d['buckets'] = [b.AsDict() for b in self.buckets]
101    return d
102
103  @staticmethod
104  def FromDict(value_dict, page_dict):
105    kwargs = value_module.Value.GetConstructorKwArgs(value_dict, page_dict)
106    kwargs['raw_value'] = value_dict
107
108    return HistogramValue(**kwargs)
109
110  @classmethod
111  def MergeLikeValuesFromSamePage(cls, values):
112    assert len(values) > 0
113    v0 = values[0]
114    return HistogramValue(
115        v0.page, v0.name, v0.units,
116        raw_value_json=histogram_util.AddHistograms(
117            [v.ToJSONString() for v in values]),
118        important=v0.important)
119
120  @classmethod
121  def MergeLikeValuesFromDifferentPages(cls, values,
122                                        group_by_name_suffix=False):
123    # Histograms cannot be merged across pages, at least for now. It should be
124    # theoretically possible, just requires more work. Instead, return None.
125    # This signals to the merging code that the data is unmergable and it will
126    # cope accordingly.
127    return None
128