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 5import numbers 6import math 7 8from telemetry import value as value_module 9from telemetry.value import none_values 10from telemetry.value import summarizable 11 12 13def Variance(sample): 14 """ Compute the population variance. 15 16 Args: 17 sample: a list of numbers. 18 """ 19 k = len(sample) - 1 # Bessel correction 20 if k <= 0: 21 return 0 22 m = _Mean(sample) 23 return sum((x - m)**2 for x in sample)/k 24 25 26def StandardDeviation(sample): 27 """ Compute standard deviation for a list of numbers. 28 29 Args: 30 sample: a list of numbers. 31 """ 32 return math.sqrt(Variance(sample)) 33 34 35def PooledStandardDeviation(list_of_samples, list_of_variances=None): 36 """ Compute standard deviation for a list of samples. 37 38 See: https://en.wikipedia.org/wiki/Pooled_variance for the formula. 39 40 Args: 41 list_of_samples: a list of lists, each is a list of numbers. 42 list_of_variances: a list of numbers, the i-th element is the variance of 43 the i-th sample in list_of_samples. If this is None, we use 44 Variance(sample) to get the variance of the i-th sample. 45 """ 46 pooled_variance = 0.0 47 total_degrees_of_freedom = 0 48 for i in xrange(len(list_of_samples)): 49 l = list_of_samples[i] 50 k = len(l) - 1 # Bessel correction 51 if k <= 0: 52 continue 53 variance = list_of_variances[i] if list_of_variances else Variance(l) 54 pooled_variance += k * variance 55 total_degrees_of_freedom += k 56 if total_degrees_of_freedom: 57 return (pooled_variance/total_degrees_of_freedom) ** 0.5 58 return 0 59 60 61def _Mean(values): 62 return float(sum(values)) / len(values) if len(values) > 0 else 0.0 63 64 65class ListOfScalarValues(summarizable.SummarizableValue): 66 """ ListOfScalarValues represents a list of numbers. 67 68 By default, std is the standard deviation of all numbers in the list. Std can 69 also be specified in the constructor if the numbers are not from the same 70 population. 71 """ 72 def __init__(self, page, name, units, values, 73 important=True, description=None, 74 tir_label=None, none_value_reason=None, 75 std=None, same_page_merge_policy=value_module.CONCATENATE, 76 improvement_direction=None, grouping_keys=None): 77 super(ListOfScalarValues, self).__init__(page, name, units, important, 78 description, tir_label, 79 improvement_direction, 80 grouping_keys) 81 if values is not None: 82 assert isinstance(values, list) 83 assert len(values) > 0 84 assert all(isinstance(v, numbers.Number) for v in values) 85 assert std is None or isinstance(std, numbers.Number) 86 else: 87 assert std is None 88 none_values.ValidateNoneValueReason(values, none_value_reason) 89 self.values = values 90 self.none_value_reason = none_value_reason 91 self.same_page_merge_policy = same_page_merge_policy 92 if values is not None and std is None: 93 std = StandardDeviation(values) 94 assert std is None or std >= 0, ( 95 'standard deviation cannot be negative: %s' % std) 96 self._std = std 97 98 @property 99 def std(self): 100 return self._std 101 102 @property 103 def variance(self): 104 return self._std ** 2 105 106 def __repr__(self): 107 if self.page: 108 page_name = self.page.display_name 109 else: 110 page_name = 'None' 111 if self.same_page_merge_policy == value_module.CONCATENATE: 112 merge_policy = 'CONCATENATE' 113 else: 114 merge_policy = 'PICK_FIRST' 115 return ('ListOfScalarValues(%s, %s, %s, %s, ' 116 'important=%s, description=%s, tir_label=%s, std=%s, ' 117 'same_page_merge_policy=%s, improvement_direction=%s, ' 118 'grouping_keys=%s)') % ( 119 page_name, 120 self.name, 121 self.units, 122 repr(self.values), 123 self.important, 124 self.description, 125 self.tir_label, 126 self.std, 127 merge_policy, 128 self.improvement_direction, 129 self.grouping_keys) 130 131 def GetBuildbotDataType(self, output_context): 132 if self._IsImportantGivenOutputIntent(output_context): 133 return 'default' 134 return 'unimportant' 135 136 def GetBuildbotValue(self): 137 return self.values 138 139 def GetRepresentativeNumber(self): 140 return _Mean(self.values) 141 142 def GetRepresentativeString(self): 143 return repr(self.values) 144 145 def IsMergableWith(self, that): 146 return (super(ListOfScalarValues, self).IsMergableWith(that) and 147 self.same_page_merge_policy == that.same_page_merge_policy) 148 149 @staticmethod 150 def GetJSONTypeName(): 151 return 'list_of_scalar_values' 152 153 def AsDict(self): 154 d = super(ListOfScalarValues, self).AsDict() 155 d['values'] = self.values 156 d['std'] = self.std 157 158 if self.none_value_reason is not None: 159 d['none_value_reason'] = self.none_value_reason 160 161 return d 162 163 @staticmethod 164 def FromDict(value_dict, page_dict): 165 kwargs = value_module.Value.GetConstructorKwArgs(value_dict, page_dict) 166 kwargs['values'] = value_dict['values'] 167 kwargs['std'] = value_dict['std'] 168 169 if 'improvement_direction' in value_dict: 170 kwargs['improvement_direction'] = value_dict['improvement_direction'] 171 if 'none_value_reason' in value_dict: 172 kwargs['none_value_reason'] = value_dict['none_value_reason'] 173 174 return ListOfScalarValues(**kwargs) 175 176 @classmethod 177 def MergeLikeValuesFromSamePage(cls, values): 178 assert len(values) > 0 179 v0 = values[0] 180 181 if v0.same_page_merge_policy == value_module.PICK_FIRST: 182 return ListOfScalarValues( 183 v0.page, v0.name, v0.units, 184 values[0].values, 185 important=v0.important, 186 same_page_merge_policy=v0.same_page_merge_policy, 187 none_value_reason=v0.none_value_reason, 188 improvement_direction=v0.improvement_direction, 189 grouping_keys=v0.grouping_keys) 190 191 assert v0.same_page_merge_policy == value_module.CONCATENATE 192 return cls._MergeLikeValues(values, v0.page, v0.name, v0.tir_label, 193 v0.grouping_keys) 194 195 @classmethod 196 def MergeLikeValuesFromDifferentPages(cls, values): 197 assert len(values) > 0 198 v0 = values[0] 199 return cls._MergeLikeValues(values, None, v0.name, v0.tir_label, 200 v0.grouping_keys) 201 202 @classmethod 203 def _MergeLikeValues(cls, values, page, name, tir_label, grouping_keys): 204 v0 = values[0] 205 merged_values = [] 206 list_of_samples = [] 207 none_value_reason = None 208 pooled_std = None 209 for v in values: 210 if v.values is None: 211 merged_values = None 212 none_value_reason = none_values.MERGE_FAILURE_REASON 213 break 214 merged_values.extend(v.values) 215 list_of_samples.append(v.values) 216 if merged_values: 217 pooled_std = PooledStandardDeviation( 218 list_of_samples, list_of_variances=[v.variance for v in values]) 219 return ListOfScalarValues( 220 page, name, v0.units, 221 merged_values, 222 important=v0.important, 223 description=v0.description, 224 tir_label=tir_label, 225 same_page_merge_policy=v0.same_page_merge_policy, 226 std=pooled_std, 227 none_value_reason=none_value_reason, 228 improvement_direction=v0.improvement_direction, 229 grouping_keys=grouping_keys) 230