Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / value / __init__.py
1 # Copyright 2014 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 The Value hierarchy provides a way of representing the values measurements
6 produce such that they can be merged across runs, grouped by page, and output
7 to different targets.
8
9 The core Value concept provides the basic functionality:
10 - association with a page, may be none
11 - naming and units
12 - importance tracking [whether a value will show up on a waterfall or output
13   file by default]
14 - other metadata, such as a description of what was measured
15 - default conversion to scalar and string
16 - merging properties
17
18 A page may actually run a few times during a single telemetry session.
19 Downstream consumers of test results typically want to group these runs
20 together, then compute summary statistics across runs. Value provides the
21 Merge* family of methods for this kind of aggregation.
22 """
23 import os
24
25 from telemetry.core import discover
26 from telemetry.core import util
27
28 # When combining a pair of Values togehter, it is sometimes ambiguous whether
29 # the values should be concatenated, or one should be picked as representative.
30 # The possible merging policies are listed here.
31 CONCATENATE = 'concatenate'
32 PICK_FIRST = 'pick-first'
33
34 # When converting a Value to its buildbot equivalent, the context in which the
35 # value is being interpreted actually affects the conversion. This is insane,
36 # but there you have it. There are three contexts in which Values are converted
37 # for use by buildbot, represented by these output-intent values.
38 PER_PAGE_RESULT_OUTPUT_CONTEXT = 'per-page-result-output-context'
39 COMPUTED_PER_PAGE_SUMMARY_OUTPUT_CONTEXT = 'merged-pages-result-output-context'
40 SUMMARY_RESULT_OUTPUT_CONTEXT = 'summary-result-output-context'
41
42 class Value(object):
43   """An abstract value produced by a telemetry page test.
44   """
45   def __init__(self, page, name, units, important, description):
46     """A generic Value object.
47
48     Args:
49       page: A Page object, may be given as None to indicate that the value
50           represents results for multiple pages.
51       name: A value name string, may contain a dot. Values from the same test
52           with the same prefix before the dot may be considered to belong to
53           the same chart.
54       units: A units string.
55       important: Whether the value is "important". Causes the value to appear
56           by default in downstream UIs.
57       description: A string explaining in human-understandable terms what this
58           value represents.
59     """
60     self.page = page
61     self.name = name
62     self.units = units
63     self.important = important
64     self.description = description
65
66   def IsMergableWith(self, that):
67     return (self.units == that.units and
68             type(self) == type(that) and
69             self.important == that.important)
70
71   @classmethod
72   def MergeLikeValuesFromSamePage(cls, values):
73     """Combines the provided list of values into a single compound value.
74
75     When a page runs multiple times, it may produce multiple values. This
76     function is given the same-named values across the multiple runs, and has
77     the responsibility of producing a single result.
78
79     It must return a single Value. If merging does not make sense, the
80     implementation must pick a representative value from one of the runs.
81
82     For instance, it may be given
83         [ScalarValue(page, 'a', 1), ScalarValue(page, 'a', 2)]
84     and it might produce
85         ListOfScalarValues(page, 'a', [1, 2])
86     """
87     raise NotImplementedError()
88
89   @classmethod
90   def MergeLikeValuesFromDifferentPages(cls, values,
91                                         group_by_name_suffix=False):
92     """Combines the provided values into a single compound value.
93
94     When a full pageset runs, a single value_name will usually end up getting
95     collected for multiple pages. For instance, we may end up with
96        [ScalarValue(page1, 'a',  1),
97         ScalarValue(page2, 'a',  2)]
98
99     This function takes in the values of the same name, but across multiple
100     pages, and produces a single summary result value. In this instance, it
101     could produce a ScalarValue(None, 'a', 1.5) to indicate averaging, or even
102     ListOfScalarValues(None, 'a', [1, 2]) if concatenated output was desired.
103
104     Some results are so specific to a page that they make no sense when
105     aggregated across pages. If merging values of this type across pages is
106     non-sensical, this method may return None.
107
108     If group_by_name_suffix is True, then x.z and y.z are considered to be the
109     same value and are grouped together. If false, then x.z and y.z are
110     considered different.
111     """
112     raise NotImplementedError()
113
114   def _IsImportantGivenOutputIntent(self, output_context):
115     if output_context == PER_PAGE_RESULT_OUTPUT_CONTEXT:
116       return False
117     elif output_context == COMPUTED_PER_PAGE_SUMMARY_OUTPUT_CONTEXT:
118       return self.important
119     elif output_context == SUMMARY_RESULT_OUTPUT_CONTEXT:
120       return self.important
121
122   def GetBuildbotDataType(self, output_context):
123     """Returns the buildbot's equivalent data_type.
124
125     This should be one of the values accepted by perf_tests_results_helper.py.
126     """
127     raise NotImplementedError()
128
129   def GetBuildbotValue(self):
130     """Returns the buildbot's equivalent value."""
131     raise NotImplementedError()
132
133   def GetChartAndTraceNameForPerPageResult(self):
134     chart_name, _ = _ConvertValueNameToChartAndTraceName(self.name)
135     trace_name = self.page.display_name
136     return chart_name, trace_name
137
138   @property
139   def name_suffix(self):
140     """Returns the string after a . in the name, or the full name otherwise."""
141     if '.' in self.name:
142       return self.name.split('.', 1)[1]
143     else:
144       return self.name
145
146   def GetChartAndTraceNameForComputedSummaryResult(
147       self, trace_tag):
148     chart_name, trace_name = (
149         _ConvertValueNameToChartAndTraceName(self.name))
150     if trace_tag:
151       return chart_name, trace_name + trace_tag
152     else:
153       return chart_name, trace_name
154
155   def GetRepresentativeNumber(self):
156     """Gets a single scalar value that best-represents this value.
157
158     Returns None if not possible.
159     """
160     raise NotImplementedError()
161
162   def GetRepresentativeString(self):
163     """Gets a string value that best-represents this value.
164
165     Returns None if not possible.
166     """
167     raise NotImplementedError()
168
169   @staticmethod
170   def GetJSONTypeName():
171     """Gets the typename for serialization to JSON using AsDict."""
172     raise NotImplementedError()
173
174   def AsDict(self):
175     """Pre-serializes a value to a dict for output as JSON."""
176     return self._AsDictImpl()
177
178   def _AsDictImpl(self):
179     d = {
180       'name': self.name,
181       'type': self.GetJSONTypeName(),
182       'units': self.units,
183       'important': self.important
184     }
185
186     if self.description:
187       d['description'] = self.description
188
189     if self.page:
190       d['page_id'] = self.page.id
191
192     return d
193
194   def AsDictWithoutBaseClassEntries(self):
195     full_dict = self.AsDict()
196     base_dict_keys = set(self._AsDictImpl().keys())
197
198     # Extracts only entries added by the subclass.
199     return dict([(k, v) for (k, v) in full_dict.iteritems()
200                   if k not in base_dict_keys])
201
202   @staticmethod
203   def FromDict(value_dict, page_dict):
204     """Produces a value from a value dict and a page dict.
205
206     Value dicts are produced by serialization to JSON, and must be accompanied
207     by a dict mapping page IDs to pages, also produced by serialization, in
208     order to be completely deserialized. If deserializing multiple values, use
209     ListOfValuesFromListOfDicts instead.
210
211     value_dict: a dictionary produced by AsDict() on a value subclass.
212     page_dict: a dictionary mapping IDs to page objects.
213     """
214     return Value.ListOfValuesFromListOfDicts([value_dict], page_dict)[0]
215
216   @staticmethod
217   def ListOfValuesFromListOfDicts(value_dicts, page_dict):
218     """Takes a list of value dicts to values.
219
220     Given a list of value dicts produced by AsDict, this method
221     deserializes the dicts given a dict mapping page IDs to pages.
222     This method performs memoization for deserializing a list of values
223     efficiently, where FromDict is meant to handle one-offs.
224
225     values: a list of value dicts produced by AsDict() on a value subclass.
226     page_dict: a dictionary mapping IDs to page objects.
227     """
228     value_dir = os.path.dirname(__file__)
229     value_classes = discover.DiscoverClasses(
230         value_dir, util.GetTelemetryDir(),
231         Value, index_by_class_name=True)
232
233     value_json_types = dict((value_classes[x].GetJSONTypeName(), x) for x in
234         value_classes)
235
236     values = []
237     for value_dict in value_dicts:
238       value_class = value_classes[value_json_types[value_dict['type']]]
239       assert 'FromDict' in value_class.__dict__, \
240              'Subclass doesn\'t override FromDict'
241       values.append(value_class.FromDict(value_dict, page_dict))
242
243     return values
244
245   @staticmethod
246   def GetConstructorKwArgs(value_dict, page_dict):
247     """Produces constructor arguments from a value dict and a page dict.
248
249     Takes a dict parsed from JSON and an index of pages and recovers the
250     keyword arguments to be passed to the constructor for deserializing the
251     dict.
252
253     value_dict: a dictionary produced by AsDict() on a value subclass.
254     page_dict: a dictionary mapping IDs to page objects.
255     """
256     d = {
257       'name': value_dict['name'],
258       'units': value_dict['units']
259     }
260
261     description = value_dict.get('description', None)
262     if description:
263       d['description'] = description
264     else:
265       d['description'] = None
266
267     page_id = value_dict.get('page_id', None)
268     if page_id:
269       d['page'] = page_dict[int(page_id)]
270     else:
271       d['page'] = None
272
273     d['important'] = False
274
275     return d
276
277 def ValueNameFromTraceAndChartName(trace_name, chart_name=None):
278   """Mangles a trace name plus optional chart name into a standard string.
279
280   A value might just be a bareword name, e.g. numPixels. In that case, its
281   chart may be None.
282
283   But, a value might also be intended for display with other values, in which
284   case the chart name indicates that grouping. So, you might have
285   screen.numPixels, screen.resolution, where chartName='screen'.
286   """
287   assert trace_name != 'url', 'The name url cannot be used'
288   if chart_name:
289     return '%s.%s' % (chart_name, trace_name)
290   else:
291     assert '.' not in trace_name, ('Trace names cannot contain "." with an '
292         'empty chart_name since this is used to delimit chart_name.trace_name.')
293     return trace_name
294
295 def _ConvertValueNameToChartAndTraceName(value_name):
296   """Converts a value_name into the equivalent chart-trace name pair.
297
298   Buildbot represents values by the measurement name and an optional trace name,
299   whereas telemetry represents values with a chart_name.trace_name convention,
300   where chart_name is optional. This convention is also used by chart_json.
301
302   This converts from the telemetry convention to the buildbot convention,
303   returning a 2-tuple (measurement_name, trace_name).
304   """
305   if '.' in value_name:
306     return value_name.split('.', 1)
307   else:
308     return value_name, value_name