| Index: infra_libs/ts_mon/metrics.py | 
| diff --git a/infra_libs/ts_mon/metrics.py b/infra_libs/ts_mon/metrics.py | 
| deleted file mode 100644 | 
| index fa986e6e115441c8eaaf97f57c1577658d5e0337..0000000000000000000000000000000000000000 | 
| --- a/infra_libs/ts_mon/metrics.py | 
| +++ /dev/null | 
| @@ -1,434 +0,0 @@ | 
| -# Copyright 2015 The Chromium 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 representing individual metrics that can be sent.""" | 
| - | 
| - | 
| -import copy | 
| -import threading | 
| -import time | 
| - | 
| -from monacq.proto import metrics_pb2 | 
| - | 
| -from infra_libs.ts_mon import distribution | 
| -from infra_libs.ts_mon import errors | 
| -from infra_libs.ts_mon import interface | 
| - | 
| - | 
| -MICROSECONDS_PER_SECOND = 1000000 | 
| - | 
| - | 
| -class Metric(object): | 
| -  """Abstract base class for a metric. | 
| - | 
| -  A Metric is an attribute that may be monitored across many targets. Examples | 
| -  include disk usage or the number of requests a server has received. A single | 
| -  process may keep track of many metrics. | 
| - | 
| -  Note that Metric objects may be initialized at any time (for example, at the | 
| -  top of a library), but cannot be sent until the underlying Monitor object | 
| -  has been set up (usually by the top-level process parsing the command line). | 
| - | 
| -  A Metric can actually store multiple values that are identified by a set of | 
| -  fields (which are themselves key-value pairs).  Fields can be passed to the | 
| -  set() or increment() methods to modify a particular value, or passed to the | 
| -  constructor in which case they will be used as the defaults for this Metric. | 
| - | 
| -  Do not directly instantiate an object of this class. | 
| -  Use the concrete child classes instead: | 
| -  * StringMetric for metrics with string value | 
| -  * BooleanMetric for metrics with boolean values | 
| -  * CounterMetric for metrics with monotonically increasing integer values | 
| -  * GaugeMetric for metrics with arbitrarily varying integer values | 
| -  * CumulativeMetric for metrics with monotonically increasing float values | 
| -  * FloatMetric for metrics with arbitrarily varying float values | 
| -  """ | 
| - | 
| -  _initial_value = None | 
| - | 
| -  def __init__(self, name, target=None, fields=None): | 
| -    """Create an instance of a Metric. | 
| - | 
| -    Args: | 
| -      name (str): the file-like name of this metric | 
| -      fields (dict): a set of key-value pairs to be set as default metric fields | 
| -      target (Target): a Target to be used with this metric. This should be | 
| -                       specified only rarely; usually the library's default | 
| -                       Target will be used (set up by the top-level process). | 
| -    """ | 
| -    self._name = name.lstrip('/') | 
| -    self._values = {} | 
| -    self._target = target | 
| -    fields = fields or {} | 
| -    if len(fields) > 7: | 
| -      raise errors.MonitoringTooManyFieldsError(self._name, fields) | 
| -    self._fields = fields | 
| -    self._normalized_fields = self._normalize_fields(self._fields) | 
| -    self._thread_lock = threading.Lock() | 
| - | 
| -    interface.register(self) | 
| - | 
| -  def unregister(self): | 
| -    interface.unregister(self) | 
| - | 
| -  def serialize_to(self, collection_pb, default_target=None, loop_action=None): | 
| -    """Generate metrics_pb2.MetricsData messages for this metric. | 
| - | 
| -    Args: | 
| -      collection_pb (metrics_pb2.MetricsCollection): protocol buffer into which | 
| -        to add the current metric values. | 
| -      default_target (Target): a Target to use if self._target is not set. | 
| -      loop_action (function(metrics_pb2.MetricsCollection)): a function that we | 
| -        must call with the collection_pb every loop iteration. | 
| - | 
| -    Raises: | 
| -      MonitoringNoConfiguredTargetError: if neither self._target nor | 
| -                                         default_target is set | 
| -    """ | 
| - | 
| -    for fields, value in self._values.iteritems(): | 
| -      if callable(loop_action): | 
| -        loop_action(collection_pb) | 
| -      metric_pb = collection_pb.data.add() | 
| -      metric_pb.metric_name_prefix = '/chrome/infra/' | 
| -      metric_pb.name = self._name | 
| - | 
| -      self._populate_value(metric_pb, value) | 
| -      self._populate_fields(metric_pb, fields) | 
| - | 
| -      if self._target: | 
| -        self._target._populate_target_pb(metric_pb) | 
| -      elif default_target: | 
| -        default_target._populate_target_pb(metric_pb) | 
| -      else: | 
| -        raise errors.MonitoringNoConfiguredTargetError(self._name) | 
| - | 
| -  def _populate_fields(self, metric, fields): | 
| -    """Fill in the fields attribute of a metric protocol buffer. | 
| - | 
| -    Args: | 
| -      metric (metrics_pb2.MetricsData): a metrics protobuf to populate | 
| -      fields (list of (key, value) tuples): normalized metric fields | 
| - | 
| -    Raises: | 
| -      MonitoringInvalidFieldTypeError: if a field has a value of unknown type | 
| -    """ | 
| -    for key, value in fields: | 
| -      field = metric.fields.add() | 
| -      field.name = key | 
| -      if isinstance(value, basestring): | 
| -        field.type = metrics_pb2.MetricsField.STRING | 
| -        field.string_value = value | 
| -      elif isinstance(value, bool): | 
| -        field.type = metrics_pb2.MetricsField.BOOL | 
| -        field.bool_value = value | 
| -      elif isinstance(value, int): | 
| -        field.type = metrics_pb2.MetricsField.INT | 
| -        field.int_value = value | 
| -      else: | 
| -        raise errors.MonitoringInvalidFieldTypeError(self._name, key, value) | 
| - | 
| -  def _normalize_fields(self, fields): | 
| -    """Merges the fields with the default fields and returns something hashable. | 
| - | 
| -    Args: | 
| -      fields (dict): A dict of fields passed by the user, or None. | 
| - | 
| -    Returns: | 
| -      A tuple of (key, value) tuples, ordered by key.  This whole tuple is used | 
| -      as the key in the self._values dict to identify the cell for a value. | 
| - | 
| -    Raises: | 
| -      MonitoringTooManyFieldsError: if there are more than seven metric fields | 
| -    """ | 
| -    if fields is None: | 
| -      return self._normalized_fields | 
| - | 
| -    all_fields = copy.copy(self._fields) | 
| -    all_fields.update(fields) | 
| - | 
| -    if len(all_fields) > 7: | 
| -      raise errors.MonitoringTooManyFieldsError(self._name, all_fields) | 
| - | 
| -    return tuple(sorted(all_fields.iteritems())) | 
| - | 
| -  def _set_and_send_value(self, value, fields): | 
| -    """Called by subclasses to set a new value for this metric. | 
| - | 
| -    Args: | 
| -      value (see concrete class): the value of the metric to be set | 
| -      fields (dict): additional metric fields to complement those on self | 
| -    """ | 
| -    self._values[self._normalize_fields(fields)] = value | 
| -    interface.send(self) | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    """Fill in the the data values of a metric protocol buffer. | 
| - | 
| -    Args: | 
| -      metric (metrics_pb2.MetricsData): a metrics protobuf to populate | 
| -      value (see concrete class): the value of the metric to be set | 
| -    """ | 
| -    raise NotImplementedError() | 
| - | 
| -  def set(self, value, fields=None): | 
| -    """Set a new value for this metric. Results in sending a new value. | 
| - | 
| -    The subclass should do appropriate type checking on value and then call | 
| -    self._set_and_send_value. | 
| - | 
| -    Args: | 
| -      value (see concrete class): the value of the metric to be set | 
| -      fields (dict): additional metric fields to complement those on self | 
| -    """ | 
| -    raise NotImplementedError() | 
| - | 
| -  def get(self, fields=None): | 
| -    """Returns the current value for this metric.""" | 
| -    return self._values.get(self._normalize_fields(fields), self._initial_value) | 
| - | 
| -  def reset(self): | 
| -    """Resets the current values for this metric to 0.  Useful for tests.""" | 
| -    self._values = {} | 
| - | 
| - | 
| -class StringMetric(Metric): | 
| -  """A metric whose value type is a string.""" | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    metric.string_value = value | 
| - | 
| -  def set(self, value, fields=None): | 
| -    if not isinstance(value, basestring): | 
| -      raise errors.MonitoringInvalidValueTypeError(self._name, value) | 
| -    self._set_and_send_value(value, fields) | 
| - | 
| - | 
| -class BooleanMetric(Metric): | 
| -  """A metric whose value type is a boolean.""" | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    metric.boolean_value = value | 
| - | 
| -  def set(self, value, fields=None): | 
| -    if not isinstance(value, bool): | 
| -      raise errors.MonitoringInvalidValueTypeError(self._name, value) | 
| -    self._set_and_send_value(value, fields) | 
| - | 
| -  def toggle(self, fields=None): | 
| -    self.set(not self.get(fields), fields) | 
| - | 
| - | 
| -class NumericMetric(Metric):  # pylint: disable=abstract-method | 
| -  """Abstract base class for numeric (int or float) metrics.""" | 
| -  #TODO(agable): Figure out if there's a way to send units with these metrics. | 
| - | 
| -  def increment(self, fields=None): | 
| -    self.increment_by(1, fields) | 
| - | 
| -  def increment_by(self, step, fields=None): | 
| -    if self.get(fields) is None: | 
| -      raise errors.MonitoringIncrementUnsetValueError(self._name) | 
| -    with self._thread_lock: | 
| -      self.set(self.get(fields) + step, fields) | 
| - | 
| - | 
| -class CounterMetric(NumericMetric): | 
| -  """A metric whose value type is a monotonically increasing integer.""" | 
| - | 
| -  _initial_value = 0 | 
| - | 
| -  def __init__( | 
| -      self, name, target=None, fields=None, start_time=None, time_fn=time.time): | 
| -    super(CounterMetric, self).__init__(name, target=target, fields=fields) | 
| -    self._start_time = start_time or int(time_fn() * MICROSECONDS_PER_SECOND) | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    metric.counter = value | 
| -    metric.start_timestamp_us = self._start_time | 
| - | 
| -  def set(self, value, fields=None): | 
| -    if not isinstance(value, (int, long)): | 
| -      raise errors.MonitoringInvalidValueTypeError(self._name, value) | 
| -    if value < self.get(fields): | 
| -      raise errors.MonitoringDecreasingValueError( | 
| -          self._name, self.get(fields), value) | 
| -    self._set_and_send_value(value, fields) | 
| - | 
| - | 
| -class GaugeMetric(NumericMetric): | 
| -  """A metric whose value type is an integer.""" | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    metric.gauge = value | 
| - | 
| -  def set(self, value, fields=None): | 
| -    if not isinstance(value, (int, long)): | 
| -      raise errors.MonitoringInvalidValueTypeError(self._name, value) | 
| -    self._set_and_send_value(value, fields) | 
| - | 
| - | 
| -class CumulativeMetric(NumericMetric): | 
| -  """A metric whose value type is a monotonically increasing float.""" | 
| - | 
| -  _initial_value = 0.0 | 
| - | 
| -  def __init__( | 
| -      self, name, target=None, fields=None, start_time=None, time_fn=time.time): | 
| -    super(CumulativeMetric, self).__init__(name, target=target, fields=fields) | 
| -    self._start_time = start_time or int(time_fn() * MICROSECONDS_PER_SECOND) | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    metric.cumulative_double_value = value | 
| -    metric.start_timestamp_us = self._start_time | 
| - | 
| -  def set(self, value, fields=None): | 
| -    if not isinstance(value, (float, int)): | 
| -      raise errors.MonitoringInvalidValueTypeError(self._name, value) | 
| -    if value < self.get(fields): | 
| -      raise errors.MonitoringDecreasingValueError( | 
| -          self._name, self.get(fields), value) | 
| -    self._set_and_send_value(float(value), fields) | 
| - | 
| - | 
| -class FloatMetric(NumericMetric): | 
| -  """A metric whose value type is a float.""" | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    metric.noncumulative_double_value = value | 
| - | 
| -  def set(self, value, fields=None): | 
| -    if not isinstance(value, (float, int)): | 
| -      raise errors.MonitoringInvalidValueTypeError(self._name, value) | 
| -    self._set_and_send_value(float(value), fields) | 
| - | 
| - | 
| -class DistributionMetric(Metric): | 
| -  """A metric that holds a distribution of values. | 
| - | 
| -  By default buckets are chosen from a geometric progression, each bucket being | 
| -  approximately 1.59 times bigger than the last.  In practice this is suitable | 
| -  for many kinds of data, but you may want to provide a FixedWidthBucketer or | 
| -  GeometricBucketer with different parameters.""" | 
| - | 
| -  CANONICAL_SPEC_TYPES = { | 
| -      2: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_2, | 
| -      10**0.2: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_10_P_0_2, | 
| -      10: metrics_pb2.PrecomputedDistribution.CANONICAL_POWERS_OF_10, | 
| -  } | 
| - | 
| -  def __init__(self, name, is_cumulative=True, bucketer=None, target=None, | 
| -               fields=None, start_time=None, time_fn=time.time): | 
| -    super(DistributionMetric, self).__init__(name, target, fields) | 
| -    self._start_time = start_time or int(time_fn() * MICROSECONDS_PER_SECOND) | 
| - | 
| -    if bucketer is None: | 
| -      bucketer = distribution.GeometricBucketer() | 
| - | 
| -    self.is_cumulative = is_cumulative | 
| -    self.bucketer = bucketer | 
| - | 
| -  def _populate_value(self, metric, value): | 
| -    pb = metric.distribution | 
| - | 
| -    pb.is_cumulative = self.is_cumulative | 
| -    metric.start_timestamp_us = self._start_time | 
| - | 
| -    # Copy the bucketer params. | 
| -    if (value.bucketer.width == 0 and | 
| -        value.bucketer.growth_factor in self.CANONICAL_SPEC_TYPES): | 
| -      pb.spec_type = self.CANONICAL_SPEC_TYPES[value.bucketer.growth_factor] | 
| -    else: | 
| -      pb.spec_type = metrics_pb2.PrecomputedDistribution.CUSTOM_PARAMETERIZED | 
| -      pb.width = value.bucketer.width | 
| -      pb.growth_factor = value.bucketer.growth_factor | 
| -      pb.num_buckets = value.bucketer.num_finite_buckets | 
| - | 
| -    # Copy the distribution bucket values.  Only include the finite buckets, not | 
| -    # the overflow buckets on each end. | 
| -    pb.bucket.extend(self._running_zero_generator( | 
| -        value.buckets.get(i, 0) for i in | 
| -        xrange(1, value.bucketer.total_buckets - 1))) | 
| - | 
| -    # Add the overflow buckets if present. | 
| -    if value.bucketer.underflow_bucket in value.buckets: | 
| -      pb.underflow = value.buckets[value.bucketer.underflow_bucket] | 
| -    if value.bucketer.overflow_bucket in value.buckets: | 
| -      pb.overflow = value.buckets[value.bucketer.overflow_bucket] | 
| - | 
| -    if value.count != 0: | 
| -      pb.mean = float(value.sum) / value.count | 
| - | 
| -  @staticmethod | 
| -  def _running_zero_generator(iterable): | 
| -    """Compresses sequences of zeroes in the iterable into negative zero counts. | 
| - | 
| -    For example an input of [1, 0, 0, 0, 2] is converted to [1, -3, 2]. | 
| -    """ | 
| - | 
| -    count = 0 | 
| - | 
| -    for value in iterable: | 
| -      if value == 0: | 
| -        count += 1 | 
| -      else: | 
| -        if count != 0: | 
| -          yield -count | 
| -          count = 0 | 
| -        yield value | 
| - | 
| -  def add(self, value, fields=None): | 
| -    with self._thread_lock: | 
| -      dist = self.get(fields) | 
| -      if dist is None: | 
| -        dist = distribution.Distribution(self.bucketer) | 
| - | 
| -      dist.add(value) | 
| -      self._set_and_send_value(dist, fields) | 
| - | 
| -  def set(self, value, fields=None): | 
| -    """Replaces the distribution with the given fields with another one. | 
| - | 
| -    This only makes sense on non-cumulative DistributionMetrics. | 
| - | 
| -    Args: | 
| -      value: A infra_libs.ts_mon.Distribution. | 
| -    """ | 
| - | 
| -    if self.is_cumulative: | 
| -      raise TypeError( | 
| -          'Cannot set() a cumulative DistributionMetric (use add() instead)') | 
| - | 
| -    if not isinstance(value, distribution.Distribution): | 
| -      raise errors.MonitoringInvalidValueTypeError(self._name, value) | 
| - | 
| -    self._set_and_send_value(value, fields) | 
| - | 
| - | 
| -class CumulativeDistributionMetric(DistributionMetric): | 
| -  """A DistributionMetric with is_cumulative set to True.""" | 
| - | 
| -  def __init__( | 
| -      self, name, bucketer=None, target=None, fields=None, time_fn=time.time): | 
| -    super(CumulativeDistributionMetric, self).__init__( | 
| -        name, | 
| -        is_cumulative=True, | 
| -        bucketer=bucketer, | 
| -        target=target, | 
| -        fields=fields, | 
| -        time_fn=time_fn) | 
| - | 
| - | 
| -class NonCumulativeDistributionMetric(DistributionMetric): | 
| -  """A DistributionMetric with is_cumulative set to False.""" | 
| - | 
| -  def __init__( | 
| -      self, name, bucketer=None, target=None, fields=None, time_fn=time.time): | 
| -    super(NonCumulativeDistributionMetric, self).__init__( | 
| -        name, | 
| -        is_cumulative=False, | 
| -        bucketer=bucketer, | 
| -        target=target, | 
| -        fields=fields, | 
| -        time_fn=time_fn) | 
|  |