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