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""" 5The Value hierarchy provides a way of representing the values measurements 6produce such that they can be merged across runs, grouped by page, and output 7to different targets. 8 9The 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- default conversion to scalar and string 15- merging properties 16 17A page may actually run a few times during a single telemetry session. 18Downstream consumers of test results typically want to group these runs 19together, then compute summary statistics across runs. Value provides the 20Merge* family of methods for this kind of aggregation. 21""" 22 23# When combining a pair of Values togehter, it is sometimes ambiguous whether 24# the values should be concatenated, or one should be picked as representative. 25# The possible merging policies are listed here. 26CONCATENATE = 'concatenate' 27PICK_FIRST = 'pick-first' 28 29# When converting a Value to its buildbot equivalent, the context in which the 30# value is being interpreted actually affects the conversion. This is insane, 31# but there you have it. There are three contexts in which Values are converted 32# for use by buildbot, represented by these output-intent values. 33PER_PAGE_RESULT_OUTPUT_CONTEXT = 'per-page-result-output-context' 34MERGED_PAGES_RESULT_OUTPUT_CONTEXT = 'merged-pages-result-output-context' 35SUMMARY_RESULT_OUTPUT_CONTEXT = 'summary-result-output-context' 36 37class Value(object): 38 """An abstract value produced by a telemetry page test. 39 """ 40 def __init__(self, page, name, units, important): 41 """A generic Value object. 42 43 Note: page may be given as None to indicate that the value represents 44 results multiple pages. 45 """ 46 self.page = page 47 self.name = name 48 self.units = units 49 self.important = important 50 51 def IsMergableWith(self, that): 52 return (self.units == that.units and 53 type(self) == type(that) and 54 self.important == that.important) 55 56 @classmethod 57 def MergeLikeValuesFromSamePage(cls, values): 58 """Combines the provided list of values into a single compound value. 59 60 When a page runs multiple times, it may produce multiple values. This 61 function is given the same-named values across the multiple runs, and has 62 the responsibility of producing a single result. 63 64 It must return a single Value. If merging does not make sense, the 65 implementation must pick a representative value from one of the runs. 66 67 For instance, it may be given 68 [ScalarValue(page, 'a', 1), ScalarValue(page, 'a', 2)] 69 and it might produce 70 ListOfScalarValues(page, 'a', [1, 2]) 71 """ 72 raise NotImplementedError() 73 74 @classmethod 75 def MergeLikeValuesFromDifferentPages(cls, values, 76 group_by_name_suffix=False): 77 """Combines the provided values into a single compound value. 78 79 When a full pageset runs, a single value_name will usually end up getting 80 collected for multiple pages. For instance, we may end up with 81 [ScalarValue(page1, 'a', 1), 82 ScalarValue(page2, 'a', 2)] 83 84 This function takes in the values of the same name, but across multiple 85 pages, and produces a single summary result value. In this instance, it 86 could produce a ScalarValue(None, 'a', 1.5) to indicate averaging, or even 87 ListOfScalarValues(None, 'a', [1, 2]) if concatenated output was desired. 88 89 Some results are so specific to a page that they make no sense when 90 aggregated across pages. If merging values of this type across pages is 91 non-sensical, this method may return None. 92 93 If group_by_name_suffix is True, then x.z and y.z are considered to be the 94 same value and are grouped together. If false, then x.z and y.z are 95 considered different. 96 """ 97 raise NotImplementedError() 98 99 def _IsImportantGivenOutputIntent(self, output_context): 100 if output_context == PER_PAGE_RESULT_OUTPUT_CONTEXT: 101 return False 102 elif output_context == MERGED_PAGES_RESULT_OUTPUT_CONTEXT: 103 return self.important 104 elif output_context == SUMMARY_RESULT_OUTPUT_CONTEXT: 105 return self.important 106 107 def GetBuildbotDataType(self, output_context): 108 """Returns the buildbot's equivalent data_type. 109 110 This should be one of the values accepted by perf_tests_results_helper.py. 111 """ 112 raise NotImplementedError() 113 114 def GetBuildbotValue(self): 115 """Returns the buildbot's equivalent value.""" 116 raise NotImplementedError() 117 118 def GetBuildbotMeasurementAndTraceNameForPerPageResult(self): 119 measurement, _ = ( 120 _ConvertValueNameToBuildbotChartAndTraceName(self.name)) 121 return measurement + '_by_url', self.page.display_name 122 123 @property 124 def name_suffix(self): 125 """Returns the string after a . in the name, or the full name otherwise.""" 126 if '.' in self.name: 127 return self.name.split('.', 1)[1] 128 else: 129 return self.name 130 131 def GetBuildbotMeasurementAndTraceNameForMergedPagesResult(self, trace_tag): 132 measurement, bb_trace_name = ( 133 _ConvertValueNameToBuildbotChartAndTraceName(self.name)) 134 if trace_tag: 135 return measurement, bb_trace_name + trace_tag 136 else: 137 return measurement, bb_trace_name 138 139 def GetRepresentativeNumber(self): 140 """Gets a single scalar value that best-represents this value. 141 142 Returns None if not possible. 143 """ 144 raise NotImplementedError() 145 146 def GetRepresentativeString(self): 147 """Gets a string value that best-represents this value. 148 149 Returns None if not possible. 150 """ 151 raise NotImplementedError() 152 153def ValueNameFromTraceAndChartName(trace_name, chart_name=None): 154 """Mangles a trace name plus optional chart name into a standard string. 155 156 A value might just be a bareword name, e.g. numPixels. In that case, its 157 chart may be None. 158 159 But, a value might also be intended for display with other values, in which 160 case the chart name indicates that grouping. So, you might have 161 screen.numPixels, screen.resolution, where chartName='screen'. 162 """ 163 assert trace_name != 'url', 'The name url cannot be used' 164 if chart_name: 165 return '%s.%s' % (chart_name, trace_name) 166 else: 167 return trace_name 168 169def _ConvertValueNameToBuildbotChartAndTraceName(value_name): 170 """Converts a value_name into the buildbot equivalent name pair. 171 172 Buildbot represents values by the measurement name and an optional trace name, 173 whereas telemetry represents values with a chart_name.trace_name convention, 174 where chart_name is optional. 175 176 This converts from the telemetry convention to the buildbot convention, 177 returning a 2-tuple (measurement_name, trace_name). 178 """ 179 if '.' in value_name: 180 return value_name.split('.', 1) 181 else: 182 return value_name, value_name 183