Issue 623854: Support unit annotations in ts_mon metrics
For go-implementation, please find the following CLs:
- https://codereview.chromium.org/2123853002
- https://codereview.chromium.org/2125943003
BUG=623854
Review-Url: https://codereview.chromium.org/2111473003
Cr-Mirrored-From: https://chromium.googlesource.com/infra/infra
Cr-Mirrored-Commit: 9bf7fbe68e4dc69611e78ee0b43c44c258c49d3b
diff --git a/ts_mon/__init__.py b/ts_mon/__init__.py
index 7c08de6..bda03e9 100644
--- a/ts_mon/__init__.py
+++ b/ts_mon/__init__.py
@@ -33,6 +33,7 @@
from infra_libs.ts_mon.common.metrics import FloatMetric
from infra_libs.ts_mon.common.metrics import GaugeMetric
from infra_libs.ts_mon.common.metrics import NonCumulativeDistributionMetric
+from infra_libs.ts_mon.common.metrics import MetricsDataUnits
from infra_libs.ts_mon.common.metrics import StringMetric
from infra_libs.ts_mon.common.targets import TaskTarget
diff --git a/ts_mon/common/metrics.py b/ts_mon/common/metrics.py
index 33a5437..a9ee417 100644
--- a/ts_mon/common/metrics.py
+++ b/ts_mon/common/metrics.py
@@ -32,6 +32,12 @@
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.
+ The unit of measurement for Metric data can be specified with MetricsDataUnits
+ when a Metric object is created:
+ e.g., MetricsDataUnits.SECONDS, MetricsDataUnits.BYTES, and etc..,
+ A full list of supported units can be found in the following protobuf file
+ : infra_libs/ts_mon/protos/metrics.proto
+
Do not directly instantiate an object of this class.
Use the concrete child classes instead:
* StringMetric for metrics with string value
@@ -44,7 +50,7 @@
See http://go/inframon-doc for help designing and using your metrics.
"""
- def __init__(self, name, fields=None, description=None):
+ def __init__(self, name, fields=None, description=None, units=None):
"""Create an instance of a Metric.
Args:
@@ -52,6 +58,9 @@
fields (dict): a set of key-value pairs to be set as default metric fields
description (string): help string for the metric. Should be enough to
know what the metric is about.
+ units (int): the unit used to measure data for given
+ metric. Please use the attributes of MetricDataUnit to find
+ valid integer values for this argument.
"""
self._name = name.lstrip('/')
self._start_time = None
@@ -61,6 +70,7 @@
self._fields = fields
self._normalized_fields = self._normalize_fields(self._fields)
self._description = description
+ self._units = units
interface.register(self)
@@ -98,6 +108,8 @@
metric_pb.name = self._name
if self._description is not None:
metric_pb.description = self._description
+ if self._units is not None:
+ metric_pb.units = self._units
self._populate_value(metric_pb, value, start_time)
self._populate_fields(metric_pb, fields)
@@ -250,9 +262,10 @@
class CounterMetric(NumericMetric):
"""A metric whose value type is a monotonically increasing integer."""
- def __init__(self, name, fields=None, start_time=None, description=None):
+ def __init__(self, name, fields=None, start_time=None, description=None,
+ units=None):
super(CounterMetric, self).__init__(
- name, fields=fields, description=description)
+ name, fields=fields, description=description, units=units)
self._start_time = start_time
def _populate_value(self, metric, value, start_time):
@@ -291,9 +304,10 @@
class CumulativeMetric(NumericMetric):
"""A metric whose value type is a monotonically increasing float."""
- def __init__(self, name, fields=None, start_time=None, description=None):
+ def __init__(self, name, fields=None, start_time=None, description=None,
+ units=None):
super(CumulativeMetric, self).__init__(
- name, fields=fields, description=description)
+ name, fields=fields, description=description, units=units)
self._start_time = start_time
def _populate_value(self, metric, value, start_time):
@@ -339,9 +353,9 @@
}
def __init__(self, name, is_cumulative=True, bucketer=None, fields=None,
- start_time=None, description=None):
+ start_time=None, description=None, units=None):
super(DistributionMetric, self).__init__(
- name, fields=fields, description=description)
+ name, fields=fields, description=description, units=units)
self._start_time = start_time
if bucketer is None:
@@ -435,13 +449,14 @@
"""A DistributionMetric with is_cumulative set to True."""
def __init__(self, name, bucketer=None, fields=None,
- description=None):
+ description=None, units=None):
super(CumulativeDistributionMetric, self).__init__(
name,
is_cumulative=True,
bucketer=bucketer,
fields=fields,
- description=description)
+ description=description,
+ units=units)
def is_cumulative(self):
return True
@@ -451,13 +466,28 @@
"""A DistributionMetric with is_cumulative set to False."""
def __init__(self, name, bucketer=None, fields=None,
- description=None):
+ description=None, units=None):
super(NonCumulativeDistributionMetric, self).__init__(
name,
is_cumulative=False,
bucketer=bucketer,
fields=fields,
- description=description)
+ description=description,
+ units=units)
def is_cumulative(self):
return False
+
+
+class MetaMetricsDataUnits(type):
+ """Metaclass to populate the enum values of metrics_pb2.MetricsData.Units."""
+ def __new__(mcs, name, bases, attrs):
+ attrs.update(metrics_pb2.MetricsData.Units.items())
+ return super(MetaMetricsDataUnits, mcs).__new__(mcs, name, bases, attrs)
+
+
+class MetricsDataUnits(object):
+ """An enumeration class for units of measurement for Metrics data.
+ See infra_libs/ts_mon/protos/metrics.proto for a full list of supported units.
+ """
+ __metaclass__ = MetaMetricsDataUnits
diff --git a/ts_mon/common/test/metrics_test.expected/MetricTest.test_serialize_with_units.json b/ts_mon/common/test/metrics_test.expected/MetricTest.test_serialize_with_units.json
new file mode 100644
index 0000000..15c7037
--- /dev/null
+++ b/ts_mon/common/test/metrics_test.expected/MetricTest.test_serialize_with_units.json
@@ -0,0 +1,26 @@
+[
+ "data {",
+ " name: \"test\"",
+ " metric_name_prefix: \"/chrome/infra/\"",
+ " network_device {",
+ " alertable: true",
+ " realm: \"ACQ_CHROME\"",
+ " metro: \"reg\"",
+ " role: \"role\"",
+ " hostname: \"host\"",
+ " hostgroup: \"net\"",
+ " }",
+ " fields {",
+ " name: \"bar\"",
+ " type: INT",
+ " int_value: 1",
+ " }",
+ " fields {",
+ " name: \"baz\"",
+ " type: BOOL",
+ " bool_value: false",
+ " }",
+ " gauge: 1",
+ " units: SECONDS",
+ "}"
+]
diff --git a/ts_mon/common/test/metrics_test.py b/ts_mon/common/test/metrics_test.py
index a09795d..e89ebc4 100644
--- a/ts_mon/common/test/metrics_test.py
+++ b/ts_mon/common/test/metrics_test.py
@@ -62,6 +62,15 @@
m.serialize_to(p, 1234, (('bar', 1), ('baz', False)), m.get(), t)
return str(p).splitlines()
+ def test_serialize_with_units(self):
+ t = targets.DeviceTarget('reg', 'role', 'net', 'host')
+ m = metrics.GaugeMetric('test', units=metrics.MetricsDataUnits.SECONDS)
+ m.set(1)
+ p = metrics_pb2.MetricsCollection()
+ m.serialize_to(p, 1234, (('bar', 1), ('baz', False)), m.get(), t)
+ self.assertEquals(p.data[0].units, metrics.MetricsDataUnits.SECONDS)
+ return str(p).splitlines()
+
def test_serialize_too_many_fields(self):
m = metrics.StringMetric('test', fields={'a': 1, 'b': 2, 'c': 3, 'd': 4})
m.set('val', fields={'e': 5, 'f': 6, 'g': 7})