1# Copyright (c) 2010 The Chromium OS 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"""Classes and functions for managing platform_BootPerf results. 6 7Results from the platform_BootPerf test in the ChromiumOS autotest 8package are stored as performance 'keyvals', that is, a mapping 9of names to numeric values. For each iteration of the test, one 10set of keyvals is recorded. 11 12This module currently tracks four kinds of keyval results: the boot 13time results, the disk read results, the firmware time results, and 14reboot time results. These results are stored with keyval names 15such as 'seconds_kernel_to_login', 'rdbytes_kernel_to_login', and 16'seconds_power_on_to_kernel'. These keyvals record an accumulated 17total measured from a fixed time in the past, e.g. 18'seconds_kernel_to_login' records the total seconds from kernel 19startup to login screen ready. 20 21The boot time keyval names all start with the prefix 22'seconds_kernel_to_', and record time in seconds since kernel 23startup. 24 25The disk read keyval names all start with the prefix 26'rdbytes_kernel_to_', and record bytes read from the boot device 27since kernel startup. 28 29The firmware keyval names all start with the prefix 30'seconds_power_on_to_', and record time in seconds since CPU 31power on. 32 33The reboot keyval names are selected from a hard-coded list of 34keyvals that include both some boot time and some firmware time 35keyvals, plus specific keyvals keyed to record shutdown and reboot 36time. 37 38""" 39 40import math 41 42 43def _ListStats(list_): 44 """Return the mean and sample standard deviation of a list. 45 46 The returned result is float, even if the input list is full of 47 integers. 48 49 @param list_ The list over which to calculate. 50 51 """ 52 sum_ = 0.0 53 sumsq = 0.0 54 for v in list_: 55 sum_ += v 56 sumsq += v * v 57 n = len(list_) 58 avg = sum_ / n 59 var = (sumsq - sum_ * avg) / (n - 1) 60 if var < 0.0: 61 var = 0.0 62 dev = math.sqrt(var) 63 return (avg, dev) 64 65 66class TestResultSet(object): 67 """A set of boot time and disk usage result statistics. 68 69 Objects of this class consist of three sets of result statistics: 70 the boot time statistics, the disk statistics, and the firmware 71 time statistics. 72 73 Class TestResultsSet does not interpret or store keyval mappings 74 directly; iteration results are processed by attached _KeySet 75 objects, one for each of the three types of result keyval. The 76 _KeySet objects are kept in a dictionary; they can be obtained 77 by calling the KeySet with the name of the keyset desired. 78 Various methods on the KeySet objects will calculate statistics on 79 the results, and provide the raw data. 80 81 """ 82 83 # The names of the available KeySets, to be used as arguments to 84 # KeySet(). 85 BOOTTIME_KEYSET = "boot" 86 DISK_KEYSET = "disk" 87 FIRMWARE_KEYSET = "firmware" 88 REBOOT_KEYSET = "reboot" 89 AVAILABLE_KEYSETS = [ 90 BOOTTIME_KEYSET, DISK_KEYSET, FIRMWARE_KEYSET, REBOOT_KEYSET 91 ] 92 93 def __init__(self, name): 94 self.name = name 95 self._keysets = { 96 self.BOOTTIME_KEYSET : _TimeKeySet(), 97 self.DISK_KEYSET : _DiskKeySet(), 98 self.FIRMWARE_KEYSET : _FirmwareKeySet(), 99 self.REBOOT_KEYSET : _RebootKeySet(), 100 } 101 102 def AddIterationResults(self, runkeys): 103 """Add keyval results from a single iteration. 104 105 A TestResultSet is constructed by repeatedly calling 106 AddIterationResults(), iteration by iteration. Iteration 107 results are passed in as a dictionary mapping keyval attributes 108 to values. When all iteration results have been added, 109 FinalizeResults() makes the results available for analysis. 110 111 @param runkeys The dictionary of keyvals for the iteration. 112 113 """ 114 115 for keyset in self._keysets.itervalues(): 116 keyset.AddIterationResults(runkeys) 117 118 def FinalizeResults(self): 119 """Make results available for analysis. 120 121 A TestResultSet is constructed by repeatedly feeding it results, 122 iteration by iteration. Iteration results are passed in as a 123 dictionary mapping keyval attributes to values. When all iteration 124 results have been added, FinalizeResults() makes the results 125 available for analysis. 126 127 """ 128 129 for keyset in self._keysets.itervalues(): 130 keyset.FinalizeResults() 131 132 def KeySet(self, keytype): 133 """Return a selected keyset from the test results. 134 135 @param keytype Selector for the desired keyset. 136 137 """ 138 return self._keysets[keytype] 139 140 141class _KeySet(object): 142 """Container for a set of related statistics. 143 144 _KeySet is an abstract superclass for containing collections of 145 a related set of performance statistics. Statistics are stored 146 as a dictionary (`_keyvals`) mapping keyval names to lists of 147 values. The lists are indexed by the iteration number. 148 149 The mapped keyval names are shortened by stripping the prefix 150 that identifies the type of keyval (keyvals that don't start with 151 the proper prefix are ignored). So, for example, with boot time 152 keyvals, 'seconds_kernel_to_login' becomes 'login' (and 153 'rdbytes_kernel_to_login' is ignored). 154 155 A list of all valid keyval names is stored in the `markers` 156 instance variable. The list is sorted by the ordering of the 157 average of the corresponding values. Each iteration is required 158 to contain the same set of keyvals. This is enforced in 159 FinalizeResults() (see below). 160 161 """ 162 163 def __init__(self): 164 self._keyvals = {} 165 166 def _CheckCounts(self): 167 """Check the validity of the keyvals results dictionary. 168 169 Each keyval must have occurred the same number of times. When 170 this check succeeds, it returns the total number of occurrences; 171 on failure return `None`. 172 173 """ 174 check = map(len, self._keyvals.values()) 175 if not check: 176 return None 177 for i in range(1, len(check)): 178 if check[i] != check[i-1]: 179 return None 180 return check[0] 181 182 def AddIterationResults(self, runkeys): 183 """Add results for one iteration. 184 185 @param runkeys The dictionary of keyvals for the iteration. 186 """ 187 for key, value in runkeys.iteritems(): 188 if not key.startswith(self.PREFIX): 189 continue 190 shortkey = key[len(self.PREFIX):] 191 keylist = self._keyvals.setdefault(shortkey, []) 192 keylist.append(self._ConvertVal(value)) 193 194 def FinalizeResults(self): 195 """Finalize this object's results. 196 197 This method makes available the `markers` and `num_iterations` 198 instance variables. It also ensures that every keyval occurred 199 in every iteration by requiring that all keyvals have the same 200 number of data points. 201 202 """ 203 count = self._CheckCounts() 204 if count is None: 205 self.num_iterations = 0 206 self.markers = [] 207 return False 208 self.num_iterations = count 209 keylist = map(lambda k: (sum(self._keyvals[k]), k), 210 self._keyvals.keys()) 211 keylist.sort(key=lambda tp: tp[0]) 212 self.markers = map(lambda tp: tp[1], keylist) 213 return True 214 215 def RawData(self, key): 216 """Return the list of values for the given key. 217 218 @param key Key of the list of values to return. 219 220 """ 221 return self._keyvals[key] 222 223 def DeltaData(self, key0, key1): 224 """Return the vector difference between two keyvals lists. 225 226 @param key0 Key of the subtrahend vector. 227 @param key1 Key of the subtractor vector. 228 229 """ 230 return map(lambda a, b: b - a, 231 self._keyvals[key0], 232 self._keyvals[key1]) 233 234 def Statistics(self, key): 235 """Return the average and standard deviation for a key. 236 237 @param key 238 """ 239 return _ListStats(self._keyvals[key]) 240 241 def DeltaStatistics(self, key0, key1): 242 """Return the average and standard deviation between two keys. 243 244 Calculates the difference between each matching element in the 245 two key's lists, and returns the average and sample standard 246 deviation of the differences. 247 248 @param key0 Key of the subtrahend. 249 @param key1 Key of the subtractor. 250 251 """ 252 return _ListStats(self.DeltaData(key0, key1)) 253 254 255class _TimeKeySet(_KeySet): 256 """Concrete subclass of _KeySet for boot time statistics.""" 257 258 PREFIX = 'seconds_kernel_to_' 259 260 # Time-based keyvals are reported in seconds and get converted to 261 # milliseconds 262 TIME_SCALE = 1000 263 264 def _ConvertVal(self, value): 265 """Return a keyval value in its 'canonical' form. 266 267 For boot time values, the input is seconds as a float; the 268 canonical form is milliseconds as an integer. 269 270 @param value A time statistic in seconds. 271 272 """ 273 # We want to return the nearest exact integer here. round() 274 # returns a float, and int() truncates its results, so we have 275 # to combine them. 276 return int(round(self.TIME_SCALE * float(value))) 277 278 def PrintableStatistic(self, value): 279 """Return a keyval in its preferred form for printing. 280 281 The return value is a tuple of a string to be printed, and 282 value rounded to the precision to which it was printed. 283 284 Rationale: Some callers of this function total up intermediate 285 results. Returning the rounded value makes those totals more 286 robust against visible rounding anomalies. 287 288 @param value The value to be printed. 289 290 """ 291 v = int(round(value)) 292 return ("%d" % v, v) 293 294 295class _FirmwareKeySet(_TimeKeySet): 296 """Concrete subclass of _KeySet for firmware time statistics.""" 297 298 PREFIX = 'seconds_power_on_to_' 299 300 # Time-based keyvals are reported in seconds and get converted to 301 # milliseconds 302 TIME_SCALE = 1000 303 304 305class _RebootKeySet(_TimeKeySet): 306 """Concrete subclass of _KeySet for reboot time statistics.""" 307 308 PREFIX = '' 309 310 # Time-based keyvals are reported in seconds and get converted to 311 # milliseconds 312 TIME_SCALE = 1000 313 314 def AddIterationResults(self, runkeys): 315 """Add results for one iteration. 316 317 For _RebootKeySet, we cherry-pick and normalize a hard-coded 318 list of keyvals. 319 320 @param runkeys The dictionary of keyvals for the iteration. 321 """ 322 # The time values we report are calculated as the time from when 323 # shutdown was requested. However, the actual keyvals from the 324 # test are reported, variously, as "time from shutdown request", 325 # "time from power-on", and "time from kernel start". So, 326 # the values have to be normalized to a common time line. 327 # 328 # The keyvals below capture the time from shutdown request of 329 # the _end_ of a designated phase of reboot, as follows: 330 # shutdown - end of shutdown, start of firmware power-on 331 # sequence. 332 # firmware - end of firmware, transfer to kernel. 333 # startup - end of kernel initialization, Upstart's "startup" 334 # event. 335 # chrome_exec - session_manager initialization complete, 336 # Chrome starts running. 337 # login - Chrome completes initialization of the login screen. 338 # 339 shutdown = float(runkeys["seconds_shutdown_time"]) 340 firmware_time = float(runkeys["seconds_power_on_to_kernel"]) 341 startup = float(runkeys["seconds_kernel_to_startup"]) 342 chrome_exec = float(runkeys["seconds_kernel_to_chrome_exec"]) 343 reboot = float(runkeys["seconds_reboot_time"]) 344 newkeys = {} 345 newkeys["shutdown"] = shutdown 346 newkeys["firmware"] = shutdown + firmware_time 347 newkeys["startup"] = newkeys["firmware"] + startup 348 newkeys["chrome_exec"] = newkeys["firmware"] + chrome_exec 349 newkeys["login"] = reboot 350 super(_RebootKeySet, self).AddIterationResults(newkeys) 351 352 353class _DiskKeySet(_KeySet): 354 """Concrete subclass of _KeySet for disk read statistics.""" 355 356 PREFIX = 'rdbytes_kernel_to_' 357 358 # Disk read keyvals are reported in bytes and get converted to 359 # MBytes (1 MByte = 1 million bytes, not 2**20) 360 DISK_SCALE = 1.0e-6 361 362 def _ConvertVal(self, value): 363 """Return a keyval value in its 'canonical' form. 364 365 For disk statistics, the input is bytes as a float; the 366 canonical form is megabytes as a float. 367 368 @param value A disk data statistic in megabytes. 369 370 """ 371 return self.DISK_SCALE * float(value) 372 373 def PrintableStatistic(self, value): 374 """Return a keyval in its preferred form for printing. 375 376 The return value is a tuple of a string to be printed, and 377 value rounded to the precision to which it was printed. 378 379 Rationale: Some callers of this function total up intermediate 380 results. Returning the rounded value makes those totals more 381 robust against visible rounding anomalies. 382 383 @param value The value to be printed. 384 385 """ 386 v = round(value, 1) 387 return ("%.1fM" % v, v) 388