| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Classes representing individual metrics that can be sent.""" | 5 """Classes representing individual metrics that can be sent.""" |
| 6 | 6 |
| 7 import copy | 7 import copy |
| 8 import re |
| 8 | 9 |
| 9 from infra_libs.ts_mon.protos.current import metrics_pb2 | 10 from infra_libs.ts_mon.protos.current import metrics_pb2 |
| 10 from infra_libs.ts_mon.protos.new import metrics_pb2 as new_metrics_pb2 | 11 from infra_libs.ts_mon.protos.new import metrics_pb2 as new_metrics_pb2 |
| 11 | 12 |
| 12 from infra_libs.ts_mon.common import distribution | 13 from infra_libs.ts_mon.common import distribution |
| 13 from infra_libs.ts_mon.common import errors | 14 from infra_libs.ts_mon.common import errors |
| 14 from infra_libs.ts_mon.common import interface | 15 from infra_libs.ts_mon.common import interface |
| 15 | 16 |
| 16 | 17 |
| 17 MICROSECONDS_PER_SECOND = 1000000 | 18 MICROSECONDS_PER_SECOND = 1000000 |
| 18 | 19 |
| 19 | 20 |
| 21 class Field(object): |
| 22 FIELD_NAME_PATTERN = re.compile(r'[A-Za-z_][A-Za-z0-9_]*') |
| 23 |
| 24 allowed_python_types = None |
| 25 v1_type = None |
| 26 v2_type = None |
| 27 v1_field = None |
| 28 v2_field = None |
| 29 |
| 30 def __init__(self, name): |
| 31 if not self.FIELD_NAME_PATTERN.match(name): |
| 32 raise errors.MetricDefinitionError( |
| 33 'Invalid metric field name "%s" - must match the regex "%s"' % ( |
| 34 name, self.FIELD_NAME_PATTERN.pattern)) |
| 35 |
| 36 self.name = name |
| 37 |
| 38 def validate_value(self, metric_name, value): |
| 39 if not isinstance(value, self.allowed_python_types): |
| 40 raise errors.MonitoringInvalidFieldTypeError( |
| 41 metric_name, self.name, value) |
| 42 |
| 43 def populate_proto_v1(self, proto, value): |
| 44 setattr(proto, self.v1_field, value) |
| 45 |
| 46 def populate_proto_v2(self, proto, value): |
| 47 setattr(proto, self.v2_field, value) |
| 48 |
| 49 |
| 50 class StringField(Field): |
| 51 allowed_python_types = basestring |
| 52 v1_type = metrics_pb2.MetricsField.STRING |
| 53 v2_type = new_metrics_pb2.MetricsDataSet.MetricFieldDescriptor.STRING |
| 54 v1_field = 'string_value' |
| 55 v2_field = 'string_value' |
| 56 |
| 57 |
| 58 class IntegerField(Field): |
| 59 allowed_python_types = (int, long) |
| 60 v1_type = metrics_pb2.MetricsField.INT |
| 61 v2_type = new_metrics_pb2.MetricsDataSet.MetricFieldDescriptor.INT64 |
| 62 v1_field = 'int_value' |
| 63 v2_field = 'int64_value' |
| 64 |
| 65 |
| 66 class BooleanField(Field): |
| 67 allowed_python_types = bool |
| 68 v1_type = metrics_pb2.MetricsField.BOOL |
| 69 v2_type = new_metrics_pb2.MetricsDataSet.MetricFieldDescriptor.BOOL |
| 70 v1_field = 'bool_value' |
| 71 v2_field = 'bool_value' |
| 72 |
| 73 |
| 20 class Metric(object): | 74 class Metric(object): |
| 21 """Abstract base class for a metric. | 75 """Abstract base class for a metric. |
| 22 | 76 |
| 23 A Metric is an attribute that may be monitored across many targets. Examples | 77 A Metric is an attribute that may be monitored across many targets. Examples |
| 24 include disk usage or the number of requests a server has received. A single | 78 include disk usage or the number of requests a server has received. A single |
| 25 process may keep track of many metrics. | 79 process may keep track of many metrics. |
| 26 | 80 |
| 27 Note that Metric objects may be initialized at any time (for example, at the | 81 Note that Metric objects may be initialized at any time (for example, at the |
| 28 top of a library), but cannot be sent until the underlying Monitor object | 82 top of a library), but cannot be sent until the underlying Monitor object |
| 29 has been set up (usually by the top-level process parsing the command line). | 83 has been set up (usually by the top-level process parsing the command line). |
| (...skipping 14 matching lines...) Expand all Loading... |
| 44 * StringMetric for metrics with string value | 98 * StringMetric for metrics with string value |
| 45 * BooleanMetric for metrics with boolean values | 99 * BooleanMetric for metrics with boolean values |
| 46 * CounterMetric for metrics with monotonically increasing integer values | 100 * CounterMetric for metrics with monotonically increasing integer values |
| 47 * GaugeMetric for metrics with arbitrarily varying integer values | 101 * GaugeMetric for metrics with arbitrarily varying integer values |
| 48 * CumulativeMetric for metrics with monotonically increasing float values | 102 * CumulativeMetric for metrics with monotonically increasing float values |
| 49 * FloatMetric for metrics with arbitrarily varying float values | 103 * FloatMetric for metrics with arbitrarily varying float values |
| 50 | 104 |
| 51 See http://go/inframon-doc for help designing and using your metrics. | 105 See http://go/inframon-doc for help designing and using your metrics. |
| 52 """ | 106 """ |
| 53 | 107 |
| 54 def __init__(self, name, fields=None, description=None, units=None): | 108 def __init__(self, name, description, field_spec, units=None): |
| 55 """Create an instance of a Metric. | 109 """Create an instance of a Metric. |
| 56 | 110 |
| 57 Args: | 111 Args: |
| 58 name (str): the file-like name of this metric | 112 name (str): the file-like name of this metric |
| 59 fields (dict): a set of key-value pairs to be set as default metric fields | |
| 60 description (string): help string for the metric. Should be enough to | 113 description (string): help string for the metric. Should be enough to |
| 61 know what the metric is about. | 114 know what the metric is about. |
| 115 field_spec (list): a list of Field subclasses to define the fields that |
| 116 are allowed on this metric. Pass a list of either |
| 117 StringField, IntegerField or BooleanField here. |
| 62 units (int): the unit used to measure data for given | 118 units (int): the unit used to measure data for given |
| 63 metric. Please use the attributes of MetricDataUnit to find | 119 metric. Please use the attributes of MetricDataUnit to find |
| 64 valid integer values for this argument. | 120 valid integer values for this argument. |
| 65 """ | 121 """ |
| 122 field_spec = field_spec or [] |
| 123 |
| 66 self._name = name.lstrip('/') | 124 self._name = name.lstrip('/') |
| 125 |
| 126 if not isinstance(description, basestring): |
| 127 raise errors.MetricDefinitionError('Metric description must be a string') |
| 128 if not description: |
| 129 raise errors.MetricDefinitionError('Metric must have a description') |
| 130 if (not isinstance(field_spec, (list, tuple)) or |
| 131 any(not isinstance(x, Field) for x in field_spec)): |
| 132 raise errors.MetricDefinitionError( |
| 133 'Metric constructor takes a list of Fields, or None') |
| 134 if len(field_spec) > 7: |
| 135 raise errors.MonitoringTooManyFieldsError(self._name, field_spec) |
| 136 |
| 67 self._start_time = None | 137 self._start_time = None |
| 68 fields = fields or {} | 138 self._field_spec = field_spec |
| 69 if len(fields) > 7: | 139 self._sorted_field_names = sorted(x.name for x in field_spec) |
| 70 raise errors.MonitoringTooManyFieldsError(self._name, fields) | |
| 71 self._fields = fields | |
| 72 self._normalized_fields = self._normalize_fields(self._fields) | |
| 73 self._description = description | 140 self._description = description |
| 74 self._units = units | 141 self._units = units |
| 75 | 142 |
| 76 interface.register(self) | 143 interface.register(self) |
| 77 | 144 |
| 78 @property | 145 @property |
| 79 def name(self): | 146 def name(self): |
| 80 return self._name | 147 return self._name |
| 81 | 148 |
| 82 @property | 149 @property |
| 83 def start_time(self): | 150 def start_time(self): |
| 84 return self._start_time | 151 return self._start_time |
| 85 | 152 |
| 86 def is_cumulative(self): | 153 def is_cumulative(self): |
| 87 raise NotImplementedError() | 154 raise NotImplementedError() |
| 88 | 155 |
| 89 def __eq__(self, other): | |
| 90 return (self.name == other.name and | |
| 91 self._fields == other._fields and | |
| 92 type(self) == type(other)) | |
| 93 | |
| 94 def unregister(self): | 156 def unregister(self): |
| 95 interface.unregister(self) | 157 interface.unregister(self) |
| 96 | 158 |
| 97 @staticmethod | 159 @staticmethod |
| 98 def _map_units_to_string(units): | 160 def _map_units_to_string(units): |
| 99 """Map MetricsDataUnits to the corresponding string according to: | 161 """Map MetricsDataUnits to the corresponding string according to: |
| 100 http://unitsofmeasure.org/ucum.html because that's what the new proto | 162 http://unitsofmeasure.org/ucum.html because that's what the new proto |
| 101 requires.""" | 163 requires.""" |
| 102 if units in _UNITS_TO_STRING: | 164 if units in _UNITS_TO_STRING: |
| 103 return _UNITS_TO_STRING[units] | 165 return _UNITS_TO_STRING[units] |
| 104 else: | 166 else: |
| 105 return '{unknown}' | 167 return '{unknown}' |
| 106 | 168 |
| 107 def _populate_data_set(self, data_set, fields): | 169 def _populate_data_set(self, data_set): |
| 108 """Populate MetricsDataSet.""" | 170 """Populate MetricsDataSet.""" |
| 109 data_set.metric_name = '%s%s' % (interface.state.metric_name_prefix, | 171 data_set.metric_name = '%s%s' % (interface.state.metric_name_prefix, |
| 110 self._name) | 172 self._name) |
| 111 data_set.description = self._description or '' | 173 data_set.description = self._description or '' |
| 112 data_set.annotations.unit = self._map_units_to_string(self._units) | 174 data_set.annotations.unit = self._map_units_to_string(self._units) |
| 113 | 175 |
| 114 if self.is_cumulative(): | 176 if self.is_cumulative(): |
| 115 data_set.stream_kind = new_metrics_pb2.CUMULATIVE | 177 data_set.stream_kind = new_metrics_pb2.CUMULATIVE |
| 116 else: | 178 else: |
| 117 data_set.stream_kind = new_metrics_pb2.GAUGE | 179 data_set.stream_kind = new_metrics_pb2.GAUGE |
| 118 | 180 |
| 119 self._populate_value_type(data_set) | 181 self._populate_value_type(data_set) |
| 120 self._populate_field_descriptors(data_set, fields) | 182 self._populate_field_descriptors(data_set) |
| 121 | 183 |
| 122 def _populate_data(self, data, start_time, end_time, fields, value): | 184 def _populate_data(self, data, start_time, end_time, fields, value): |
| 123 """Populate a new metrics_pb2.MetricsData. | 185 """Populate a new metrics_pb2.MetricsData. |
| 124 | 186 |
| 125 Args: | 187 Args: |
| 126 data_ (new_metrics_pb2.MetricsData): protocol buffer into | 188 data_ (new_metrics_pb2.MetricsData): protocol buffer into |
| 127 which to populate the current metric values. | 189 which to populate the current metric values. |
| 128 start_time (int): timestamp in microseconds since UNIX epoch. | 190 start_time (int): timestamp in microseconds since UNIX epoch. |
| 129 """ | 191 """ |
| 130 data.start_timestamp.seconds = int(start_time) | 192 data.start_timestamp.seconds = int(start_time) |
| 131 data.end_timestamp.seconds = int(end_time) | 193 data.end_timestamp.seconds = int(end_time) |
| 132 | 194 |
| 133 self._populate_fields_new(data, fields) | 195 self._populate_fields_new(data, fields) |
| 134 self._populate_value_new(data, value) | 196 self._populate_value_new(data, value) |
| 135 | 197 |
| 136 def serialize_to(self, metric_pb, start_time, fields, value, target): | 198 def serialize_to(self, metric_pb, start_time, fields, value, target): |
| 137 """Generate metrics_pb2.MetricsData messages for this metric. | 199 """Generate metrics_pb2.MetricsData messages for this metric. |
| 138 | 200 |
| 139 Args: | 201 Args: |
| 140 metric_pb (metrics_pb2.MetricsData): protocol buffer into which | 202 metric_pb (metrics_pb2.MetricsData): protocol buffer into which |
| 141 to serialize the current metric values. | 203 to serialize the current metric values. |
| 142 start_time (int): timestamp in microseconds since UNIX epoch. | 204 start_time (int): timestamp in microseconds since UNIX epoch. |
| 143 target (Target): a Target to use. | 205 target (Target): a Target to use. |
| 144 """ | 206 """ |
| 145 | 207 |
| 146 metric_pb.metric_name_prefix = interface.state.metric_name_prefix | 208 metric_pb.metric_name_prefix = interface.state.metric_name_prefix |
| 147 metric_pb.name = self._name | 209 metric_pb.name = self._name |
| 148 if self._description is not None: | 210 metric_pb.description = self._description |
| 149 metric_pb.description = self._description | |
| 150 if self._units is not None: | 211 if self._units is not None: |
| 151 metric_pb.units = self._units | 212 metric_pb.units = self._units |
| 152 | 213 |
| 153 self._populate_value(metric_pb, value, start_time) | 214 self._populate_value(metric_pb, value, start_time) |
| 154 self._populate_fields(metric_pb, fields) | 215 self._populate_fields(metric_pb, fields) |
| 155 | 216 |
| 156 target._populate_target_pb(metric_pb) | 217 target._populate_target_pb(metric_pb) |
| 157 | 218 |
| 158 def _populate_field_descriptors(self, data_set, fields): | 219 def _populate_field_descriptors(self, data_set): |
| 159 """Populate `field_descriptor` in MetricsDataSet. | 220 """Populate `field_descriptor` in MetricsDataSet. |
| 160 | 221 |
| 161 Args: | 222 Args: |
| 162 data_set (new_metrics_pb2.MetricsDataSet): a data set protobuf to | 223 data_set (new_metrics_pb2.MetricsDataSet): a data set protobuf to populate |
| 163 populate | 224 """ |
| 164 fields (list of (key, value) tuples): normalized metric fields | 225 for spec in self._field_spec: |
| 226 descriptor = data_set.field_descriptor.add() |
| 227 descriptor.name = spec.name |
| 228 descriptor.field_type = spec.v2_type |
| 165 | 229 |
| 166 Raises: | 230 def _populate_fields_new(self, data, field_values): |
| 167 MonitoringInvalidFieldTypeError: if a field has a value of unknown type | |
| 168 """ | |
| 169 field_type = new_metrics_pb2.MetricsDataSet.MetricFieldDescriptor | |
| 170 for key, value in fields: | |
| 171 descriptor = data_set.field_descriptor.add() | |
| 172 descriptor.name = key | |
| 173 if isinstance(value, basestring): | |
| 174 descriptor.field_type = field_type.STRING | |
| 175 elif isinstance(value, bool): | |
| 176 descriptor.field_type = field_type.BOOL | |
| 177 elif isinstance(value, int): | |
| 178 descriptor.field_type = field_type.INT64 | |
| 179 else: | |
| 180 raise errors.MonitoringInvalidFieldTypeError(self._name, key, value) | |
| 181 | |
| 182 def _populate_fields_new(self, data, fields): | |
| 183 """Fill in the fields attribute of a metric protocol buffer. | 231 """Fill in the fields attribute of a metric protocol buffer. |
| 184 | 232 |
| 185 Args: | 233 Args: |
| 186 metric (metrics_pb2.MetricsData): a metrics protobuf to populate | 234 metric (metrics_pb2.MetricsData): a metrics protobuf to populate |
| 187 fields (list of (key, value) tuples): normalized metric fields | 235 field_values (tuple): field values |
| 236 """ |
| 237 for spec, value in zip(self._field_spec, field_values): |
| 238 field = data.field.add() |
| 239 field.name = spec.name |
| 240 spec.populate_proto_v2(field, value) |
| 188 | 241 |
| 189 Raises: | 242 def _populate_fields(self, metric, field_values): |
| 190 MonitoringInvalidFieldTypeError: if a field has a value of unknown type | |
| 191 """ | |
| 192 for key, value in fields: | |
| 193 field = data.field.add() | |
| 194 field.name = key | |
| 195 if isinstance(value, basestring): | |
| 196 field.string_value = value | |
| 197 elif isinstance(value, bool): | |
| 198 field.bool_value = value | |
| 199 elif isinstance(value, int): | |
| 200 field.int64_value = value | |
| 201 else: | |
| 202 raise errors.MonitoringInvalidFieldTypeError(self._name, key, value) | |
| 203 | |
| 204 def _populate_fields(self, metric, fields): | |
| 205 """Fill in the fields attribute of a metric protocol buffer. | 243 """Fill in the fields attribute of a metric protocol buffer. |
| 206 | 244 |
| 207 Args: | 245 Args: |
| 208 metric (metrics_pb2.MetricsData): a metrics protobuf to populate | 246 metric (metrics_pb2.MetricsData): a metrics protobuf to populate |
| 209 fields (list of (key, value) tuples): normalized metric fields | 247 field_values (tuple): field values |
| 248 """ |
| 249 for spec, value in zip(self._field_spec, field_values): |
| 250 field = metric.fields.add() |
| 251 field.name = spec.name |
| 252 field.type = spec.v1_type |
| 253 spec.populate_proto_v1(field, value) |
| 254 |
| 255 def _validate_fields(self, fields): |
| 256 """Checks the correct number and types of field values were provided. |
| 257 |
| 258 Args: |
| 259 fields (dict): A dict of field values given by the user, or None. |
| 260 |
| 261 Returns: |
| 262 fields' values as a tuple, in the same order as the field_spec. |
| 210 | 263 |
| 211 Raises: | 264 Raises: |
| 212 MonitoringInvalidFieldTypeError: if a field has a value of unknown type | 265 WrongFieldsError: if you provide a different number of fields to those |
| 266 the metric was defined with. |
| 267 MonitoringInvalidFieldTypeError: if the field value was the wrong type for |
| 268 the field spec. |
| 213 """ | 269 """ |
| 214 for key, value in fields: | 270 fields = fields or {} |
| 215 field = metric.fields.add() | |
| 216 field.name = key | |
| 217 if isinstance(value, basestring): | |
| 218 field.type = metrics_pb2.MetricsField.STRING | |
| 219 field.string_value = value | |
| 220 elif isinstance(value, bool): | |
| 221 field.type = metrics_pb2.MetricsField.BOOL | |
| 222 field.bool_value = value | |
| 223 elif isinstance(value, int): | |
| 224 field.type = metrics_pb2.MetricsField.INT | |
| 225 field.int_value = value | |
| 226 else: | |
| 227 raise errors.MonitoringInvalidFieldTypeError(self._name, key, value) | |
| 228 | 271 |
| 229 def _normalize_fields(self, fields): | 272 if not isinstance(fields, dict): |
| 230 """Merges the fields with the default fields and returns something hashable. | 273 raise ValueError('fields should be a dict, got %r (%s)' % ( |
| 274 fields, type(fields))) |
| 231 | 275 |
| 232 Args: | 276 if sorted(fields) != self._sorted_field_names: |
| 233 fields (dict): A dict of fields passed by the user, or None. | 277 raise errors.WrongFieldsError( |
| 278 self.name, fields.keys(), self._sorted_field_names) |
| 234 | 279 |
| 235 Returns: | 280 for spec in self._field_spec: |
| 236 A tuple of (key, value) tuples, ordered by key. This whole tuple is used | 281 spec.validate_value(self.name, fields[spec.name]) |
| 237 as the key in the self._values dict to identify the cell for a value. | |
| 238 | 282 |
| 239 Raises: | 283 return tuple(fields[spec.name] for spec in self._field_spec) |
| 240 MonitoringTooManyFieldsError: if there are more than seven metric fields | |
| 241 """ | |
| 242 if fields is None: | |
| 243 return self._normalized_fields | |
| 244 | |
| 245 all_fields = copy.copy(self._fields) | |
| 246 all_fields.update(fields) | |
| 247 | |
| 248 if len(all_fields) > 7: | |
| 249 raise errors.MonitoringTooManyFieldsError(self._name, all_fields) | |
| 250 | |
| 251 return tuple(sorted(all_fields.iteritems())) | |
| 252 | 284 |
| 253 def _populate_value(self, metric, value, start_time): | 285 def _populate_value(self, metric, value, start_time): |
| 254 """Fill in the the data values of a metric protocol buffer. | 286 """Fill in the the data values of a metric protocol buffer. |
| 255 | 287 |
| 256 Args: | 288 Args: |
| 257 metric (metrics_pb2.MetricsData): a metrics protobuf to populate | 289 metric (metrics_pb2.MetricsData): a metrics protobuf to populate |
| 258 value (see concrete class): the value of the metric to be set | 290 value (see concrete class): the value of the metric to be set |
| 259 start_time (int): timestamp in microseconds since UNIX epoch. | 291 start_time (int): timestamp in microseconds since UNIX epoch. |
| 260 """ | 292 """ |
| 261 raise NotImplementedError() | 293 raise NotImplementedError() |
| (...skipping 17 matching lines...) Expand all Loading... |
| 279 raise NotImplementedError() | 311 raise NotImplementedError() |
| 280 | 312 |
| 281 def set(self, value, fields=None, target_fields=None): | 313 def set(self, value, fields=None, target_fields=None): |
| 282 """Set a new value for this metric. Results in sending a new value. | 314 """Set a new value for this metric. Results in sending a new value. |
| 283 | 315 |
| 284 The subclass should do appropriate type checking on value and then call | 316 The subclass should do appropriate type checking on value and then call |
| 285 self._set_and_send_value. | 317 self._set_and_send_value. |
| 286 | 318 |
| 287 Args: | 319 Args: |
| 288 value (see concrete class): the value of the metric to be set | 320 value (see concrete class): the value of the metric to be set |
| 289 fields (dict): additional metric fields to complement those on self | 321 fields (dict): metric field values |
| 290 target_fields (dict): overwrite some of the default target fields | 322 target_fields (dict): overwrite some of the default target fields |
| 291 """ | 323 """ |
| 292 raise NotImplementedError() | 324 raise NotImplementedError() |
| 293 | 325 |
| 294 def get(self, fields=None, target_fields=None): | 326 def get(self, fields=None, target_fields=None): |
| 295 """Returns the current value for this metric. | 327 """Returns the current value for this metric. |
| 296 | 328 |
| 297 Subclasses should never use this to get a value, modify it and set it again. | 329 Subclasses should never use this to get a value, modify it and set it again. |
| 298 Instead use _incr with a modify_fn. | 330 Instead use _incr with a modify_fn. |
| 299 """ | 331 """ |
| 300 return interface.state.store.get( | 332 return interface.state.store.get( |
| 301 self.name, self._normalize_fields(fields), target_fields) | 333 self.name, self._validate_fields(fields), target_fields) |
| 302 | 334 |
| 303 def get_all(self): | 335 def get_all(self): |
| 304 return interface.state.store.iter_field_values(self.name) | 336 return interface.state.store.iter_field_values(self.name) |
| 305 | 337 |
| 306 def reset(self): | 338 def reset(self): |
| 307 """Clears the values of this metric. Useful in unit tests. | 339 """Clears the values of this metric. Useful in unit tests. |
| 308 | 340 |
| 309 It might be easier to call ts_mon.reset_for_unittest() in your setUp() | 341 It might be easier to call ts_mon.reset_for_unittest() in your setUp() |
| 310 method instead of resetting every individual metric. | 342 method instead of resetting every individual metric. |
| 311 """ | 343 """ |
| 312 | 344 |
| 313 interface.state.store.reset_for_unittest(self.name) | 345 interface.state.store.reset_for_unittest(self.name) |
| 314 | 346 |
| 315 def _set(self, fields, target_fields, value, enforce_ge=False): | 347 def _set(self, fields, target_fields, value, enforce_ge=False): |
| 316 interface.state.store.set(self.name, self._normalize_fields(fields), | 348 interface.state.store.set( |
| 317 target_fields, value, enforce_ge=enforce_ge) | 349 self.name, self._validate_fields(fields), target_fields, |
| 350 value, enforce_ge=enforce_ge) |
| 318 | 351 |
| 319 def _incr(self, fields, target_fields, delta, modify_fn=None): | 352 def _incr(self, fields, target_fields, delta, modify_fn=None): |
| 320 interface.state.store.incr(self.name, self._normalize_fields(fields), | 353 interface.state.store.incr( |
| 321 target_fields, delta, modify_fn=modify_fn) | 354 self.name, self._validate_fields(fields), target_fields, |
| 355 delta, modify_fn=modify_fn) |
| 322 | 356 |
| 323 | 357 |
| 324 class StringMetric(Metric): | 358 class StringMetric(Metric): |
| 325 """A metric whose value type is a string.""" | 359 """A metric whose value type is a string.""" |
| 326 | 360 |
| 327 def _populate_value(self, metric, value, start_time): | 361 def _populate_value(self, metric, value, start_time): |
| 328 metric.string_value = value | 362 metric.string_value = value |
| 329 | 363 |
| 330 def _populate_value_new(self, data, value): | 364 def _populate_value_new(self, data, value): |
| 331 data.string_value = value | 365 data.string_value = value |
| (...skipping 26 matching lines...) Expand all Loading... |
| 358 if not isinstance(value, bool): | 392 if not isinstance(value, bool): |
| 359 raise errors.MonitoringInvalidValueTypeError(self._name, value) | 393 raise errors.MonitoringInvalidValueTypeError(self._name, value) |
| 360 self._set(fields, target_fields, value) | 394 self._set(fields, target_fields, value) |
| 361 | 395 |
| 362 def is_cumulative(self): | 396 def is_cumulative(self): |
| 363 return False | 397 return False |
| 364 | 398 |
| 365 | 399 |
| 366 class NumericMetric(Metric): # pylint: disable=abstract-method | 400 class NumericMetric(Metric): # pylint: disable=abstract-method |
| 367 """Abstract base class for numeric (int or float) metrics.""" | 401 """Abstract base class for numeric (int or float) metrics.""" |
| 368 # TODO(agable): Figure out if there's a way to send units with these metrics. | |
| 369 | 402 |
| 370 def increment(self, fields=None, target_fields=None): | 403 def increment(self, fields=None, target_fields=None): |
| 371 self._incr(fields, target_fields, 1) | 404 self._incr(fields, target_fields, 1) |
| 372 | 405 |
| 373 def increment_by(self, step, fields=None, target_fields=None): | 406 def increment_by(self, step, fields=None, target_fields=None): |
| 374 self._incr(fields, target_fields, step) | 407 self._incr(fields, target_fields, step) |
| 375 | 408 |
| 376 | 409 |
| 377 class CounterMetric(NumericMetric): | 410 class CounterMetric(NumericMetric): |
| 378 """A metric whose value type is a monotonically increasing integer.""" | 411 """A metric whose value type is a monotonically increasing integer.""" |
| 379 | 412 |
| 380 def __init__(self, name, fields=None, start_time=None, description=None, | 413 def __init__(self, name, description, field_spec=None, start_time=None, |
| 381 units=None): | 414 units=None): |
| 382 super(CounterMetric, self).__init__( | 415 super(CounterMetric, self).__init__( |
| 383 name, fields=fields, description=description, units=units) | 416 name, description, field_spec, units=units) |
| 384 self._start_time = start_time | 417 self._start_time = start_time |
| 385 | 418 |
| 386 def _populate_value(self, metric, value, start_time): | 419 def _populate_value(self, metric, value, start_time): |
| 387 metric.counter = value | 420 metric.counter = value |
| 388 metric.start_timestamp_us = int(start_time * MICROSECONDS_PER_SECOND) | 421 metric.start_timestamp_us = int(start_time * MICROSECONDS_PER_SECOND) |
| 389 | 422 |
| 390 def _populate_value_new(self, data, value): | 423 def _populate_value_new(self, data, value): |
| 391 data.int64_value = value | 424 data.int64_value = value |
| 392 | 425 |
| 393 def _populate_value_type(self, data_set): | 426 def _populate_value_type(self, data_set): |
| (...skipping 30 matching lines...) Expand all Loading... |
| 424 raise errors.MonitoringInvalidValueTypeError(self._name, value) | 457 raise errors.MonitoringInvalidValueTypeError(self._name, value) |
| 425 self._set(fields, target_fields, value) | 458 self._set(fields, target_fields, value) |
| 426 | 459 |
| 427 def is_cumulative(self): | 460 def is_cumulative(self): |
| 428 return False | 461 return False |
| 429 | 462 |
| 430 | 463 |
| 431 class CumulativeMetric(NumericMetric): | 464 class CumulativeMetric(NumericMetric): |
| 432 """A metric whose value type is a monotonically increasing float.""" | 465 """A metric whose value type is a monotonically increasing float.""" |
| 433 | 466 |
| 434 def __init__(self, name, fields=None, start_time=None, description=None, | 467 def __init__(self, name, description, field_spec=None, start_time=None, |
| 435 units=None): | 468 units=None): |
| 436 super(CumulativeMetric, self).__init__( | 469 super(CumulativeMetric, self).__init__( |
| 437 name, fields=fields, description=description, units=units) | 470 name, description, field_spec, units=units) |
| 438 self._start_time = start_time | 471 self._start_time = start_time |
| 439 | 472 |
| 440 def _populate_value(self, metric, value, start_time): | 473 def _populate_value(self, metric, value, start_time): |
| 441 metric.cumulative_double_value = value | 474 metric.cumulative_double_value = value |
| 442 metric.start_timestamp_us = int(start_time * MICROSECONDS_PER_SECOND) | 475 metric.start_timestamp_us = int(start_time * MICROSECONDS_PER_SECOND) |
| 443 | 476 |
| 444 def _populate_value_new(self, data, value): | 477 def _populate_value_new(self, data, value): |
| 445 data.double_value = value | 478 data.double_value = value |
| 446 | 479 |
| 447 def _populate_value_type(self, data_set): | 480 def _populate_value_type(self, data_set): |
| (...skipping 22 matching lines...) Expand all Loading... |
| 470 | 503 |
| 471 def set(self, value, fields=None, target_fields=None): | 504 def set(self, value, fields=None, target_fields=None): |
| 472 if not isinstance(value, (float, int)): | 505 if not isinstance(value, (float, int)): |
| 473 raise errors.MonitoringInvalidValueTypeError(self._name, value) | 506 raise errors.MonitoringInvalidValueTypeError(self._name, value) |
| 474 self._set(fields, target_fields, float(value)) | 507 self._set(fields, target_fields, float(value)) |
| 475 | 508 |
| 476 def is_cumulative(self): | 509 def is_cumulative(self): |
| 477 return False | 510 return False |
| 478 | 511 |
| 479 | 512 |
| 480 class DistributionMetric(Metric): | 513 class _DistributionMetricBase(Metric): |
| 481 """A metric that holds a distribution of values. | 514 """A metric that holds a distribution of values. |
| 482 | 515 |
| 483 By default buckets are chosen from a geometric progression, each bucket being | 516 By default buckets are chosen from a geometric progression, each bucket being |
| 484 approximately 1.59 times bigger than the last. In practice this is suitable | 517 approximately 1.59 times bigger than the last. In practice this is suitable |
| 485 for many kinds of data, but you may want to provide a FixedWidthBucketer or | 518 for many kinds of data, but you may want to provide a FixedWidthBucketer or |
| 486 GeometricBucketer with different parameters.""" | 519 GeometricBucketer with different parameters.""" |
| 487 | 520 |
| 488 CANONICAL_SPEC_TYPES = { | 521 CANONICAL_SPEC_TYPES = { |
| 489 2: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_2, | 522 2: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_2, |
| 490 10**0.2: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_10_P_0_2, | 523 10**0.2: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_10_P_0_2, |
| 491 10: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_10, | 524 10: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_10, |
| 492 } | 525 } |
| 493 | 526 |
| 494 def __init__(self, name, is_cumulative=True, bucketer=None, fields=None, | 527 def __init__(self, name, description, field_spec=None, is_cumulative=True, |
| 495 start_time=None, description=None, units=None): | 528 bucketer=None, start_time=None, units=None): |
| 496 super(DistributionMetric, self).__init__( | 529 super(_DistributionMetricBase, self).__init__( |
| 497 name, fields=fields, description=description, units=units) | 530 name, description, field_spec, units=units) |
| 498 self._start_time = start_time | 531 self._start_time = start_time |
| 499 | 532 |
| 500 if bucketer is None: | 533 if bucketer is None: |
| 501 bucketer = distribution.GeometricBucketer() | 534 bucketer = distribution.GeometricBucketer() |
| 502 | 535 |
| 503 self._is_cumulative = is_cumulative | 536 self._is_cumulative = is_cumulative |
| 504 self.bucketer = bucketer | 537 self.bucketer = bucketer |
| 505 | 538 |
| 506 def _populate_value(self, metric, value, start_time): | 539 def _populate_value(self, metric, value, start_time): |
| 507 pb = metric.distribution | 540 pb = metric.distribution |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 600 if self._is_cumulative: | 633 if self._is_cumulative: |
| 601 raise TypeError( | 634 raise TypeError( |
| 602 'Cannot set() a cumulative DistributionMetric (use add() instead)') | 635 'Cannot set() a cumulative DistributionMetric (use add() instead)') |
| 603 | 636 |
| 604 if not isinstance(value, distribution.Distribution): | 637 if not isinstance(value, distribution.Distribution): |
| 605 raise errors.MonitoringInvalidValueTypeError(self._name, value) | 638 raise errors.MonitoringInvalidValueTypeError(self._name, value) |
| 606 | 639 |
| 607 self._set(fields, target_fields, value) | 640 self._set(fields, target_fields, value) |
| 608 | 641 |
| 609 def is_cumulative(self): | 642 def is_cumulative(self): |
| 610 raise NotImplementedError() # Keep this class abstract. | 643 return self._is_cumulative |
| 611 | 644 |
| 612 | 645 |
| 613 class CumulativeDistributionMetric(DistributionMetric): | 646 class CumulativeDistributionMetric(_DistributionMetricBase): |
| 614 """A DistributionMetric with is_cumulative set to True.""" | 647 """A DistributionMetric with is_cumulative set to True.""" |
| 615 | 648 |
| 616 def __init__(self, name, bucketer=None, fields=None, | 649 def __init__(self, name, description, field_spec=None, bucketer=None, |
| 617 description=None, units=None): | 650 units=None): |
| 618 super(CumulativeDistributionMetric, self).__init__( | 651 super(CumulativeDistributionMetric, self).__init__( |
| 619 name, | 652 name, description, field_spec, |
| 620 is_cumulative=True, | 653 is_cumulative=True, |
| 621 bucketer=bucketer, | 654 bucketer=bucketer, |
| 622 fields=fields, | |
| 623 description=description, | |
| 624 units=units) | 655 units=units) |
| 625 | 656 |
| 626 def is_cumulative(self): | |
| 627 return True | |
| 628 | 657 |
| 629 | 658 class NonCumulativeDistributionMetric(_DistributionMetricBase): |
| 630 class NonCumulativeDistributionMetric(DistributionMetric): | |
| 631 """A DistributionMetric with is_cumulative set to False.""" | 659 """A DistributionMetric with is_cumulative set to False.""" |
| 632 | 660 |
| 633 def __init__(self, name, bucketer=None, fields=None, | 661 def __init__(self, name, description, field_spec=None, bucketer=None, |
| 634 description=None, units=None): | 662 units=None): |
| 635 super(NonCumulativeDistributionMetric, self).__init__( | 663 super(NonCumulativeDistributionMetric, self).__init__( |
| 636 name, | 664 name, description, field_spec, |
| 637 is_cumulative=False, | 665 is_cumulative=False, |
| 638 bucketer=bucketer, | 666 bucketer=bucketer, |
| 639 fields=fields, | |
| 640 description=description, | |
| 641 units=units) | 667 units=units) |
| 642 | 668 |
| 643 def is_cumulative(self): | |
| 644 return False | |
| 645 | |
| 646 | 669 |
| 647 class MetaMetricsDataUnits(type): | 670 class MetaMetricsDataUnits(type): |
| 648 """Metaclass to populate the enum values of metrics_pb2.MetricsData.Units.""" | 671 """Metaclass to populate the enum values of metrics_pb2.MetricsData.Units.""" |
| 649 def __new__(mcs, name, bases, attrs): | 672 def __new__(mcs, name, bases, attrs): |
| 650 attrs.update(metrics_pb2.MetricsData.Units.items()) | 673 attrs.update(metrics_pb2.MetricsData.Units.items()) |
| 651 return super(MetaMetricsDataUnits, mcs).__new__(mcs, name, bases, attrs) | 674 return super(MetaMetricsDataUnits, mcs).__new__(mcs, name, bases, attrs) |
| 652 | 675 |
| 653 | 676 |
| 654 class MetricsDataUnits(object): | 677 class MetricsDataUnits(object): |
| 655 """An enumeration class for units of measurement for Metrics data. | 678 """An enumeration class for units of measurement for Metrics data. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 668 MetricsDataUnits.KILOBYTES: 'kBy', | 691 MetricsDataUnits.KILOBYTES: 'kBy', |
| 669 MetricsDataUnits.MEGABYTES: 'MBy', | 692 MetricsDataUnits.MEGABYTES: 'MBy', |
| 670 MetricsDataUnits.GIGABYTES: 'GBy', | 693 MetricsDataUnits.GIGABYTES: 'GBy', |
| 671 MetricsDataUnits.KIBIBYTES: 'kiBy', | 694 MetricsDataUnits.KIBIBYTES: 'kiBy', |
| 672 MetricsDataUnits.MEBIBYTES: 'MiBy', | 695 MetricsDataUnits.MEBIBYTES: 'MiBy', |
| 673 MetricsDataUnits.GIBIBYTES: 'GiBy', | 696 MetricsDataUnits.GIBIBYTES: 'GiBy', |
| 674 MetricsDataUnits.AMPS: 'A', | 697 MetricsDataUnits.AMPS: 'A', |
| 675 MetricsDataUnits.MILLIAMPS : 'mA', | 698 MetricsDataUnits.MILLIAMPS : 'mA', |
| 676 MetricsDataUnits.DEGREES_CELSIUS: 'Cel' | 699 MetricsDataUnits.DEGREES_CELSIUS: 'Cel' |
| 677 } | 700 } |
| OLD | NEW |