OLD | NEW |
(Empty) | |
| 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 |
| 7 Results from the platform_BootPerf test in the ChromiumOS autotest |
| 8 package are stored in as performance 'keyvals', that is, a mapping |
| 9 of names to numeric values. For each iteration of the test, one |
| 10 set of keyvals is recorded. |
| 11 |
| 12 This module currently tracks two kinds of keyval results, the boot |
| 13 time results, and the disk read results. These results are stored |
| 14 with keyval names such as 'seconds_kernel_to_login' and |
| 15 'rdbytes_kernel_to_login'. Additionally, some older versions of the |
| 16 test produced keyval names such as 'sectors_read_kernel_to_login'. |
| 17 These keyvals record an accumulated total measured from a fixed |
| 18 time in the past (kernel startup), e.g. 'seconds_kernel_to_login' |
| 19 records the total seconds from kernel startup to login screen |
| 20 ready. |
| 21 |
| 22 The boot time keyval names all start with the prefix |
| 23 'seconds_kernel_to_', and record time in seconds since kernel |
| 24 startup. |
| 25 |
| 26 The disk read keyval names all start with the prefix |
| 27 'rdbytes_kernel_to_', and record bytes read from the boot device |
| 28 since kernel startup. The obsolete disk keyvals start with the |
| 29 prefix 'sectors_read_kernel_to_' and record the same statistic |
| 30 measured in 512-byte sectors. |
| 31 |
| 32 Boot time and disk kevyal values have a consistent ordering |
| 33 across iterations. For instance, if in one iteration the value of |
| 34 'seconds_kernel_to_login' is greater than the value of |
| 35 'seconds_kernel_to_x_started', then it will be greater in *all* |
| 36 iterations. This property is a consequence of the underlying |
| 37 measurement procedure; it is not enforced by this module. |
| 38 |
| 39 """ |
| 40 |
| 41 import math |
| 42 |
| 43 |
| 44 def _ListStats(list_): |
| 45 # Utility function - calculate the average and (sample) standard |
| 46 # deviation of a list of numbers. Result is float, even if the |
| 47 # input list is full of int's |
| 48 sum_ = 0.0 |
| 49 sumsq = 0.0 |
| 50 for v in list_: |
| 51 sum_ += v |
| 52 sumsq += v * v |
| 53 n = len(list_) |
| 54 avg = sum_ / n |
| 55 var = (sumsq - sum_ * avg) / (n - 1) |
| 56 if var < 0.0: |
| 57 var = 0.0 |
| 58 dev = math.sqrt(var) |
| 59 return (avg, dev) |
| 60 |
| 61 |
| 62 def _DoCheck(dict_): |
| 63 # Utility function - check the that all keyvals occur the same |
| 64 # number of times. On success, return the number of occurrences; |
| 65 # on failure return None |
| 66 check = map(len, dict_.values()) |
| 67 if not check: |
| 68 return None |
| 69 for i in range(1, len(check)): |
| 70 if check[i] != check[i-1]: |
| 71 return None |
| 72 return check[0] |
| 73 |
| 74 |
| 75 def _KeyDelta(dict_, key0, key1): |
| 76 # Utility function - return a list of the vector difference between |
| 77 # two keyvals. |
| 78 return map(lambda a, b: b - a, dict_[key0], dict_[key1]) |
| 79 |
| 80 |
| 81 class TestResultSet(object): |
| 82 """A set of boot time and disk usage result statistics. |
| 83 |
| 84 Objects of this class consist of two sets of result statistics: |
| 85 the boot time statistics and the disk statistics. |
| 86 |
| 87 Class TestResultsSet does not interpret or store keyval mappings |
| 88 directly; iteration results are processed by attached _KeySet |
| 89 objects, one for boot time (`_timekeys`), one for disk read |
| 90 (`_diskkeys`). These attached _KeySet objects can be obtained |
| 91 with appropriate methods; various methods on these objects will |
| 92 calculate statistics on the results, and provide the raw data. |
| 93 |
| 94 """ |
| 95 |
| 96 def __init__(self, name): |
| 97 self.name = name |
| 98 self._timekeys = _TimeKeySet() |
| 99 self._diskkeys = _DiskKeySet() |
| 100 self._olddiskkeys = _OldDiskKeySet() |
| 101 |
| 102 def AddIterationResults(self, runkeys): |
| 103 """Add keyval results from a single iteration. |
| 104 |
| 105 A TestResultSet is constructed by repeatedly calling |
| 106 AddRunResults(), iteration by iteration. Iteration results are |
| 107 passed in as a dictionary mapping keyval attributes to values. |
| 108 When all iteration results have been added, FinalizeResults() |
| 109 makes the results available for analysis. |
| 110 |
| 111 """ |
| 112 |
| 113 self._timekeys.AddRunResults(runkeys) |
| 114 self._diskkeys.AddRunResults(runkeys) |
| 115 self._olddiskkeys.AddRunResults(runkeys) |
| 116 |
| 117 def FinalizeResults(self): |
| 118 """Make results available for analysis. |
| 119 |
| 120 A TestResultSet is constructed by repeatedly feeding it results, |
| 121 iteration by iteration. Iteration results are passed in as a |
| 122 dictionary mapping keyval attributes to values. When all iteration |
| 123 results have been added, FinalizeResults() makes the results |
| 124 available for analysis. |
| 125 |
| 126 """ |
| 127 |
| 128 self._timekeys.FinalizeResults() |
| 129 if not self._diskkeys.FinalizeResults(): |
| 130 self._olddiskkeys.FinalizeResults() |
| 131 self._diskkeys = self._olddiskkeys |
| 132 self._olddiskkeys = None |
| 133 |
| 134 def TimeKeySet(self): |
| 135 """Return the boot time statistics result set.""" |
| 136 return self._timekeys |
| 137 |
| 138 def DiskKeySet(self): |
| 139 """Return the disk read statistics result set.""" |
| 140 return self._diskkeys |
| 141 |
| 142 |
| 143 class _KeySet(object): |
| 144 """Container for a set of related statistics. |
| 145 |
| 146 _KeySet is an abstract superclass for containing collections of |
| 147 either boot time or disk read statistics. Statistics are stored |
| 148 as a dictionary (`_keyvals`) mapping keyval names to lists of |
| 149 values. |
| 150 |
| 151 The mapped keyval names are shortened by stripping the prefix |
| 152 that identifies the type of prefix (keyvals that don't start with |
| 153 the proper prefix are ignored). So, for example, with boot time |
| 154 keyvals, 'seconds_kernel_to_login' becomes 'login' (and |
| 155 'rdbytes_kernel_to_login' is ignored). |
| 156 |
| 157 A list of all valid keyval names is stored in the `markers` |
| 158 instance variable. The list is sorted by the natural ordering of |
| 159 the underlying values (see the module comments for more details). |
| 160 |
| 161 The list of values associated with a given keyval name are indexed |
| 162 in the order in which they were added. So, all values for a given |
| 163 iteration are stored at the same index. |
| 164 |
| 165 """ |
| 166 |
| 167 def __init__(self): |
| 168 self._keyvals = {} |
| 169 |
| 170 def AddRunResults(self, runkeys): |
| 171 """Add results for one iteration.""" |
| 172 |
| 173 for key, value in runkeys.iteritems(): |
| 174 if not key.startswith(self.PREFIX): |
| 175 continue |
| 176 shortkey = key[len(self.PREFIX):] |
| 177 keylist = self._keyvals.setdefault(shortkey, []) |
| 178 keylist.append(self._ConvertVal(value)) |
| 179 |
| 180 def FinalizeResults(self): |
| 181 """Finalize this object's results. |
| 182 |
| 183 This method makes available the `markers` and `num_iterations` |
| 184 instance variables. It also ensures that every keyval occurred |
| 185 in every iteration by requiring that all keyvals have the same |
| 186 number of data points. |
| 187 |
| 188 """ |
| 189 |
| 190 count = _DoCheck(self._keyvals) |
| 191 if count is None: |
| 192 self.num_iterations = 0 |
| 193 self.markers = [] |
| 194 return False |
| 195 self.num_iterations = count |
| 196 keylist = map(lambda k: (self._keyvals[k][0], k), |
| 197 self._keyvals.keys()) |
| 198 keylist.sort(key=lambda tp: tp[0]) |
| 199 self.markers = map(lambda tp: tp[1], keylist) |
| 200 return True |
| 201 |
| 202 def RawData(self, key): |
| 203 """Return the list of values for the given marker key.""" |
| 204 return self._keyvals[key] |
| 205 |
| 206 def DeltaData(self, key0, key1): |
| 207 """Return vector difference of the values of the given keys.""" |
| 208 return _KeyDelta(self._keyvals, key0, key1) |
| 209 |
| 210 def Statistics(self, key): |
| 211 """Return the average and standard deviation of the key's values.""" |
| 212 return _ListStats(self._keyvals[key]) |
| 213 |
| 214 def DeltaStatistics(self, key0, key1): |
| 215 """Return the average and standard deviation of the differences |
| 216 between two keys. |
| 217 |
| 218 """ |
| 219 |
| 220 return _ListStats(self.DeltaData(key0, key1)) |
| 221 |
| 222 |
| 223 class _TimeKeySet(_KeySet): |
| 224 """Concrete subclass of _KeySet for boot time statistics.""" |
| 225 |
| 226 # TIME_KEY_PREFIX = 'seconds_kernel_to_' |
| 227 PREFIX = 'seconds_kernel_to_' |
| 228 |
| 229 # Time-based keyvals are reported in seconds and get converted to |
| 230 # milliseconds |
| 231 TIME_SCALE = 1000 |
| 232 |
| 233 def _ConvertVal(self, value): |
| 234 # We use a "round to nearest int" formula here to make sure we |
| 235 # don't lose anything in the conversion from decimal. |
| 236 return int(self.TIME_SCALE * float(value) + 0.5) |
| 237 |
| 238 def PrintableStatistic(self, value): |
| 239 v = int(value + 0.5) |
| 240 return ("%d" % v, v) |
| 241 |
| 242 |
| 243 class _DiskKeySet(_KeySet): |
| 244 """Concrete subclass of _KeySet for disk read statistics.""" |
| 245 |
| 246 PREFIX = 'rdbytes_kernel_to_' |
| 247 |
| 248 # Disk read keyvals are reported in bytes and get converted to |
| 249 # MBytes (1 MByte = 1 million bytes, not 2**20) |
| 250 DISK_SCALE = 1.0e-6 |
| 251 |
| 252 def _ConvertVal(self, value): |
| 253 return self.DISK_SCALE * float(value) |
| 254 |
| 255 def PrintableStatistic(self, value): |
| 256 v = round(value, 1) |
| 257 return ("%.1fM" % v, v) |
| 258 |
| 259 |
| 260 class _OldDiskKeySet(_DiskKeySet): |
| 261 """Concrete subclass of _KeySet for the old-style disk read statistics.""" |
| 262 |
| 263 # Older versions of platform_BootPerf reported total sectors read |
| 264 # using names of the form sectors_read_kernel_to_* (instead of the |
| 265 # more recent rdbytes_kernel_to_*), but some of those names |
| 266 # exceeded the 30-character limit in the MySQL database schema. |
| 267 PREFIX = 'sectors_read_kernel_to_' |
| 268 |
| 269 # Old sytle disk read keyvals are reported in 512-byte sectors and |
| 270 # get converted to MBytes (1 MByte = 1 million bytes, not 2**20) |
| 271 SECTOR_SCALE = 512 * _DiskKeySet.DISK_SCALE |
| 272 |
| 273 def _ConvertVal(self, value): |
| 274 return self.SECTOR_SCALE * float(value) |
OLD | NEW |