Index: bootperf-bin/resultset.py |
diff --git a/bootperf-bin/resultset.py b/bootperf-bin/resultset.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a1cee30b791a807d8ce65ad711e58402ddac050e |
--- /dev/null |
+++ b/bootperf-bin/resultset.py |
@@ -0,0 +1,274 @@ |
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Classes and functions for managing platform_BootPerf results. |
+ |
+Results from the platform_BootPerf test in the ChromiumOS autotest |
+package are stored in as performance 'keyvals', that is, a mapping |
+of names to numeric values. For each iteration of the test, one |
+set of keyvals is recorded. |
+ |
+This module currently tracks two kinds of keyval results, the boot |
+time results, and the disk read results. These results are stored |
+with keyval names such as 'seconds_kernel_to_login' and |
+'rdbytes_kernel_to_login'. Additionally, some older versions of the |
+test produced keyval names such as 'sectors_read_kernel_to_login'. |
+These keyvals record an accumulated total measured from a fixed |
+time in the past (kernel startup), e.g. 'seconds_kernel_to_login' |
+records the total seconds from kernel startup to login screen |
+ready. |
+ |
+The boot time keyval names all start with the prefix |
+'seconds_kernel_to_', and record time in seconds since kernel |
+startup. |
+ |
+The disk read keyval names all start with the prefix |
+'rdbytes_kernel_to_', and record bytes read from the boot device |
+since kernel startup. The obsolete disk keyvals start with the |
+prefix 'sectors_read_kernel_to_' and record the same statistic |
+measured in 512-byte sectors. |
+ |
+Boot time and disk kevyal values have a consistent ordering |
+across iterations. For instance, if in one iteration the value of |
+'seconds_kernel_to_login' is greater than the value of |
+'seconds_kernel_to_x_started', then it will be greater in *all* |
+iterations. This property is a consequence of the underlying |
+measurement procedure; it is not enforced by this module. |
+ |
+""" |
+ |
+import math |
+ |
+ |
+def _ListStats(list_): |
+ # Utility function - calculate the average and (sample) standard |
+ # deviation of a list of numbers. Result is float, even if the |
+ # input list is full of int's |
+ sum_ = 0.0 |
+ sumsq = 0.0 |
+ for v in list_: |
+ sum_ += v |
+ sumsq += v * v |
+ n = len(list_) |
+ avg = sum_ / n |
+ var = (sumsq - sum_ * avg) / (n - 1) |
+ if var < 0.0: |
+ var = 0.0 |
+ dev = math.sqrt(var) |
+ return (avg, dev) |
+ |
+ |
+def _DoCheck(dict_): |
+ # Utility function - check the that all keyvals occur the same |
+ # number of times. On success, return the number of occurrences; |
+ # on failure return None |
+ check = map(len, dict_.values()) |
+ if not check: |
+ return None |
+ for i in range(1, len(check)): |
+ if check[i] != check[i-1]: |
+ return None |
+ return check[0] |
+ |
+ |
+def _KeyDelta(dict_, key0, key1): |
+ # Utility function - return a list of the vector difference between |
+ # two keyvals. |
+ return map(lambda a, b: b - a, dict_[key0], dict_[key1]) |
+ |
+ |
+class TestResultSet(object): |
+ """A set of boot time and disk usage result statistics. |
+ |
+ Objects of this class consist of two sets of result statistics: |
+ the boot time statistics and the disk statistics. |
+ |
+ Class TestResultsSet does not interpret or store keyval mappings |
+ directly; iteration results are processed by attached _KeySet |
+ objects, one for boot time (`_timekeys`), one for disk read |
+ (`_diskkeys`). These attached _KeySet objects can be obtained |
+ with appropriate methods; various methods on these objects will |
+ calculate statistics on the results, and provide the raw data. |
+ |
+ """ |
+ |
+ def __init__(self, name): |
+ self.name = name |
+ self._timekeys = _TimeKeySet() |
+ self._diskkeys = _DiskKeySet() |
+ self._olddiskkeys = _OldDiskKeySet() |
+ |
+ def AddIterationResults(self, runkeys): |
+ """Add keyval results from a single iteration. |
+ |
+ A TestResultSet is constructed by repeatedly calling |
+ AddRunResults(), iteration by iteration. Iteration results are |
+ passed in as a dictionary mapping keyval attributes to values. |
+ When all iteration results have been added, FinalizeResults() |
+ makes the results available for analysis. |
+ |
+ """ |
+ |
+ self._timekeys.AddRunResults(runkeys) |
+ self._diskkeys.AddRunResults(runkeys) |
+ self._olddiskkeys.AddRunResults(runkeys) |
+ |
+ def FinalizeResults(self): |
+ """Make results available for analysis. |
+ |
+ A TestResultSet is constructed by repeatedly feeding it results, |
+ iteration by iteration. Iteration results are passed in as a |
+ dictionary mapping keyval attributes to values. When all iteration |
+ results have been added, FinalizeResults() makes the results |
+ available for analysis. |
+ |
+ """ |
+ |
+ self._timekeys.FinalizeResults() |
+ if not self._diskkeys.FinalizeResults(): |
+ self._olddiskkeys.FinalizeResults() |
+ self._diskkeys = self._olddiskkeys |
+ self._olddiskkeys = None |
+ |
+ def TimeKeySet(self): |
+ """Return the boot time statistics result set.""" |
+ return self._timekeys |
+ |
+ def DiskKeySet(self): |
+ """Return the disk read statistics result set.""" |
+ return self._diskkeys |
+ |
+ |
+class _KeySet(object): |
+ """Container for a set of related statistics. |
+ |
+ _KeySet is an abstract superclass for containing collections of |
+ either boot time or disk read statistics. Statistics are stored |
+ as a dictionary (`_keyvals`) mapping keyval names to lists of |
+ values. |
+ |
+ The mapped keyval names are shortened by stripping the prefix |
+ that identifies the type of prefix (keyvals that don't start with |
+ the proper prefix are ignored). So, for example, with boot time |
+ keyvals, 'seconds_kernel_to_login' becomes 'login' (and |
+ 'rdbytes_kernel_to_login' is ignored). |
+ |
+ A list of all valid keyval names is stored in the `markers` |
+ instance variable. The list is sorted by the natural ordering of |
+ the underlying values (see the module comments for more details). |
+ |
+ The list of values associated with a given keyval name are indexed |
+ in the order in which they were added. So, all values for a given |
+ iteration are stored at the same index. |
+ |
+ """ |
+ |
+ def __init__(self): |
+ self._keyvals = {} |
+ |
+ def AddRunResults(self, runkeys): |
+ """Add results for one iteration.""" |
+ |
+ for key, value in runkeys.iteritems(): |
+ if not key.startswith(self.PREFIX): |
+ continue |
+ shortkey = key[len(self.PREFIX):] |
+ keylist = self._keyvals.setdefault(shortkey, []) |
+ keylist.append(self._ConvertVal(value)) |
+ |
+ def FinalizeResults(self): |
+ """Finalize this object's results. |
+ |
+ This method makes available the `markers` and `num_iterations` |
+ instance variables. It also ensures that every keyval occurred |
+ in every iteration by requiring that all keyvals have the same |
+ number of data points. |
+ |
+ """ |
+ |
+ count = _DoCheck(self._keyvals) |
+ if count is None: |
+ self.num_iterations = 0 |
+ self.markers = [] |
+ return False |
+ self.num_iterations = count |
+ keylist = map(lambda k: (self._keyvals[k][0], k), |
+ self._keyvals.keys()) |
+ keylist.sort(key=lambda tp: tp[0]) |
+ self.markers = map(lambda tp: tp[1], keylist) |
+ return True |
+ |
+ def RawData(self, key): |
+ """Return the list of values for the given marker key.""" |
+ return self._keyvals[key] |
+ |
+ def DeltaData(self, key0, key1): |
+ """Return vector difference of the values of the given keys.""" |
+ return _KeyDelta(self._keyvals, key0, key1) |
+ |
+ def Statistics(self, key): |
+ """Return the average and standard deviation of the key's values.""" |
+ return _ListStats(self._keyvals[key]) |
+ |
+ def DeltaStatistics(self, key0, key1): |
+ """Return the average and standard deviation of the differences |
+ between two keys. |
+ |
+ """ |
+ |
+ return _ListStats(self.DeltaData(key0, key1)) |
+ |
+ |
+class _TimeKeySet(_KeySet): |
+ """Concrete subclass of _KeySet for boot time statistics.""" |
+ |
+ # TIME_KEY_PREFIX = 'seconds_kernel_to_' |
+ PREFIX = 'seconds_kernel_to_' |
+ |
+ # Time-based keyvals are reported in seconds and get converted to |
+ # milliseconds |
+ TIME_SCALE = 1000 |
+ |
+ def _ConvertVal(self, value): |
+ # We use a "round to nearest int" formula here to make sure we |
+ # don't lose anything in the conversion from decimal. |
+ return int(self.TIME_SCALE * float(value) + 0.5) |
+ |
+ def PrintableStatistic(self, value): |
+ v = int(value + 0.5) |
+ return ("%d" % v, v) |
+ |
+ |
+class _DiskKeySet(_KeySet): |
+ """Concrete subclass of _KeySet for disk read statistics.""" |
+ |
+ PREFIX = 'rdbytes_kernel_to_' |
+ |
+ # Disk read keyvals are reported in bytes and get converted to |
+ # MBytes (1 MByte = 1 million bytes, not 2**20) |
+ DISK_SCALE = 1.0e-6 |
+ |
+ def _ConvertVal(self, value): |
+ return self.DISK_SCALE * float(value) |
+ |
+ def PrintableStatistic(self, value): |
+ v = round(value, 1) |
+ return ("%.1fM" % v, v) |
+ |
+ |
+class _OldDiskKeySet(_DiskKeySet): |
+ """Concrete subclass of _KeySet for the old-style disk read statistics.""" |
+ |
+ # Older versions of platform_BootPerf reported total sectors read |
+ # using names of the form sectors_read_kernel_to_* (instead of the |
+ # more recent rdbytes_kernel_to_*), but some of those names |
+ # exceeded the 30-character limit in the MySQL database schema. |
+ PREFIX = 'sectors_read_kernel_to_' |
+ |
+ # Old sytle disk read keyvals are reported in 512-byte sectors and |
+ # get converted to MBytes (1 MByte = 1 million bytes, not 2**20) |
+ SECTOR_SCALE = 512 * _DiskKeySet.DISK_SCALE |
+ |
+ def _ConvertVal(self, value): |
+ return self.SECTOR_SCALE * float(value) |