toolkit/components/telemetry/histogram_tools.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 4
michael@0 5 import json
michael@0 6 import math
michael@0 7 import re
michael@0 8
michael@0 9 from collections import OrderedDict
michael@0 10
michael@0 11 def table_dispatch(kind, table, body):
michael@0 12 """Call body with table[kind] if it exists. Raise an error otherwise."""
michael@0 13 if kind in table:
michael@0 14 return body(table[kind])
michael@0 15 else:
michael@0 16 raise BaseException, "don't know how to handle a histogram of kind %s" % kind
michael@0 17
michael@0 18 class DefinitionException(BaseException):
michael@0 19 pass
michael@0 20
michael@0 21 def check_numeric_limits(dmin, dmax, n_buckets):
michael@0 22 if type(dmin) != int:
michael@0 23 raise DefinitionException, "minimum is not a number"
michael@0 24 if type(dmax) != int:
michael@0 25 raise DefinitionException, "maximum is not a number"
michael@0 26 if type(n_buckets) != int:
michael@0 27 raise DefinitionException, "number of buckets is not a number"
michael@0 28
michael@0 29 def linear_buckets(dmin, dmax, n_buckets):
michael@0 30 check_numeric_limits(dmin, dmax, n_buckets)
michael@0 31 ret_array = [0] * n_buckets
michael@0 32 dmin = float(dmin)
michael@0 33 dmax = float(dmax)
michael@0 34 for i in range(1, n_buckets):
michael@0 35 linear_range = (dmin * (n_buckets - 1 - i) + dmax * (i - 1)) / (n_buckets - 2)
michael@0 36 ret_array[i] = int(linear_range + 0.5)
michael@0 37 return ret_array
michael@0 38
michael@0 39 def exponential_buckets(dmin, dmax, n_buckets):
michael@0 40 check_numeric_limits(dmin, dmax, n_buckets)
michael@0 41 log_max = math.log(dmax);
michael@0 42 bucket_index = 2;
michael@0 43 ret_array = [0] * n_buckets
michael@0 44 current = dmin
michael@0 45 ret_array[1] = current
michael@0 46 for bucket_index in range(2, n_buckets):
michael@0 47 log_current = math.log(current)
michael@0 48 log_ratio = (log_max - log_current) / (n_buckets - bucket_index)
michael@0 49 log_next = log_current + log_ratio
michael@0 50 next_value = int(math.floor(math.exp(log_next) + 0.5))
michael@0 51 if next_value > current:
michael@0 52 current = next_value
michael@0 53 else:
michael@0 54 current = current + 1
michael@0 55 ret_array[bucket_index] = current
michael@0 56 return ret_array
michael@0 57
michael@0 58 always_allowed_keys = ['kind', 'description', 'cpp_guard', 'expires_in_version']
michael@0 59
michael@0 60 class Histogram:
michael@0 61 """A class for representing a histogram definition."""
michael@0 62
michael@0 63 def __init__(self, name, definition):
michael@0 64 """Initialize a histogram named name with the given definition.
michael@0 65 definition is a dict-like object that must contain at least the keys:
michael@0 66
michael@0 67 - 'kind': The kind of histogram. Must be one of 'boolean', 'flag',
michael@0 68 'enumerated', 'linear', or 'exponential'.
michael@0 69 - 'description': A textual description of the histogram.
michael@0 70
michael@0 71 The key 'cpp_guard' is optional; if present, it denotes a preprocessor
michael@0 72 symbol that should guard C/C++ definitions associated with the histogram."""
michael@0 73 self.verify_attributes(name, definition)
michael@0 74 self._name = name
michael@0 75 self._description = definition['description']
michael@0 76 self._kind = definition['kind']
michael@0 77 self._cpp_guard = definition.get('cpp_guard')
michael@0 78 self._extended_statistics_ok = definition.get('extended_statistics_ok', False)
michael@0 79 self._expiration = definition.get('expires_in_version')
michael@0 80 self.compute_bucket_parameters(definition)
michael@0 81 table = { 'boolean': 'BOOLEAN',
michael@0 82 'flag': 'FLAG',
michael@0 83 'enumerated': 'LINEAR',
michael@0 84 'linear': 'LINEAR',
michael@0 85 'exponential': 'EXPONENTIAL' }
michael@0 86 table_dispatch(self.kind(), table,
michael@0 87 lambda k: self._set_nsITelemetry_kind(k))
michael@0 88
michael@0 89 def name(self):
michael@0 90 """Return the name of the histogram."""
michael@0 91 return self._name
michael@0 92
michael@0 93 def description(self):
michael@0 94 """Return the description of the histogram."""
michael@0 95 return self._description
michael@0 96
michael@0 97 def kind(self):
michael@0 98 """Return the kind of the histogram.
michael@0 99 Will be one of 'boolean', 'flag', 'enumerated', 'linear', or 'exponential'."""
michael@0 100 return self._kind
michael@0 101
michael@0 102 def expiration(self):
michael@0 103 """Return the expiration version of the histogram."""
michael@0 104 return self._expiration
michael@0 105
michael@0 106 def nsITelemetry_kind(self):
michael@0 107 """Return the nsITelemetry constant corresponding to the kind of
michael@0 108 the histogram."""
michael@0 109 return self._nsITelemetry_kind
michael@0 110
michael@0 111 def _set_nsITelemetry_kind(self, kind):
michael@0 112 self._nsITelemetry_kind = "nsITelemetry::HISTOGRAM_%s" % kind
michael@0 113
michael@0 114 def low(self):
michael@0 115 """Return the lower bound of the histogram. May be a string."""
michael@0 116 return self._low
michael@0 117
michael@0 118 def high(self):
michael@0 119 """Return the high bound of the histogram. May be a string."""
michael@0 120 return self._high
michael@0 121
michael@0 122 def n_buckets(self):
michael@0 123 """Return the number of buckets in the histogram. May be a string."""
michael@0 124 return self._n_buckets
michael@0 125
michael@0 126 def cpp_guard(self):
michael@0 127 """Return the preprocessor symbol that should guard C/C++ definitions
michael@0 128 associated with the histogram. Returns None if no guarding is necessary."""
michael@0 129 return self._cpp_guard
michael@0 130
michael@0 131 def extended_statistics_ok(self):
michael@0 132 """Return True if gathering extended statistics for this histogram
michael@0 133 is enabled."""
michael@0 134 return self._extended_statistics_ok
michael@0 135
michael@0 136 def ranges(self):
michael@0 137 """Return an array of lower bounds for each bucket in the histogram."""
michael@0 138 table = { 'boolean': linear_buckets,
michael@0 139 'flag': linear_buckets,
michael@0 140 'enumerated': linear_buckets,
michael@0 141 'linear': linear_buckets,
michael@0 142 'exponential': exponential_buckets }
michael@0 143 return table_dispatch(self.kind(), table,
michael@0 144 lambda p: p(self.low(), self.high(), self.n_buckets()))
michael@0 145
michael@0 146 def compute_bucket_parameters(self, definition):
michael@0 147 table = {
michael@0 148 'boolean': Histogram.boolean_flag_bucket_parameters,
michael@0 149 'flag': Histogram.boolean_flag_bucket_parameters,
michael@0 150 'enumerated': Histogram.enumerated_bucket_parameters,
michael@0 151 'linear': Histogram.linear_bucket_parameters,
michael@0 152 'exponential': Histogram.exponential_bucket_parameters
michael@0 153 }
michael@0 154 table_dispatch(self.kind(), table,
michael@0 155 lambda p: self.set_bucket_parameters(*p(definition)))
michael@0 156
michael@0 157 def verify_attributes(self, name, definition):
michael@0 158 global always_allowed_keys
michael@0 159 general_keys = always_allowed_keys + ['low', 'high', 'n_buckets']
michael@0 160
michael@0 161 table = {
michael@0 162 'boolean': always_allowed_keys,
michael@0 163 'flag': always_allowed_keys,
michael@0 164 'enumerated': always_allowed_keys + ['n_values'],
michael@0 165 'linear': general_keys,
michael@0 166 'exponential': general_keys + ['extended_statistics_ok']
michael@0 167 }
michael@0 168 table_dispatch(definition['kind'], table,
michael@0 169 lambda allowed_keys: Histogram.check_keys(name, definition, allowed_keys))
michael@0 170
michael@0 171 Histogram.check_expiration(name, definition)
michael@0 172
michael@0 173 @staticmethod
michael@0 174 def check_expiration(name, definition):
michael@0 175 expiration = definition.get('expires_in_version')
michael@0 176
michael@0 177 if not expiration:
michael@0 178 return
michael@0 179
michael@0 180 if re.match(r'^[1-9][0-9]*$', expiration):
michael@0 181 expiration = expiration + ".0a1"
michael@0 182 elif re.match(r'^[1-9][0-9]*\.0$', expiration):
michael@0 183 expiration = expiration + "a1"
michael@0 184
michael@0 185 definition['expires_in_version'] = expiration
michael@0 186
michael@0 187 @staticmethod
michael@0 188 def check_keys(name, definition, allowed_keys):
michael@0 189 for key in definition.iterkeys():
michael@0 190 if key not in allowed_keys:
michael@0 191 raise KeyError, '%s not permitted for %s' % (key, name)
michael@0 192
michael@0 193 def set_bucket_parameters(self, low, high, n_buckets):
michael@0 194 def try_to_coerce_to_number(v):
michael@0 195 try:
michael@0 196 return eval(v, {})
michael@0 197 except:
michael@0 198 return v
michael@0 199 self._low = try_to_coerce_to_number(low)
michael@0 200 self._high = try_to_coerce_to_number(high)
michael@0 201 self._n_buckets = try_to_coerce_to_number(n_buckets)
michael@0 202
michael@0 203 @staticmethod
michael@0 204 def boolean_flag_bucket_parameters(definition):
michael@0 205 return (1, 2, 3)
michael@0 206
michael@0 207 @staticmethod
michael@0 208 def linear_bucket_parameters(definition):
michael@0 209 return (definition.get('low', 1),
michael@0 210 definition['high'],
michael@0 211 definition['n_buckets'])
michael@0 212
michael@0 213 @staticmethod
michael@0 214 def enumerated_bucket_parameters(definition):
michael@0 215 n_values = definition['n_values']
michael@0 216 return (1, n_values, "%s+1" % n_values)
michael@0 217
michael@0 218 @staticmethod
michael@0 219 def exponential_bucket_parameters(definition):
michael@0 220 return (definition.get('low', 1),
michael@0 221 definition['high'],
michael@0 222 definition['n_buckets'])
michael@0 223
michael@0 224 def from_file(filename):
michael@0 225 """Return an iterator that provides a sequence of Histograms for
michael@0 226 the histograms defined in filename.
michael@0 227 """
michael@0 228 with open(filename, 'r') as f:
michael@0 229 histograms = json.load(f, object_pairs_hook=OrderedDict)
michael@0 230 for (name, definition) in histograms.iteritems():
michael@0 231 yield Histogram(name, definition)

mercurial