Skip to content

Commit 99346e5

Browse files
authored
Metrics point conversion (census-instrumentation#467)
Add point conversion functions for stats aggregation data.
1 parent b144219 commit 99346e5

8 files changed

Lines changed: 290 additions & 112 deletions

File tree

opencensus/metrics/export/metric.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,6 @@
1313
# limitations under the License.
1414

1515
from opencensus.metrics.export import metric_descriptor
16-
from opencensus.metrics.export import value
17-
18-
19-
DESCRIPTOR_VALUE = {
20-
metric_descriptor.MetricDescriptorType.GAUGE_INT64:
21-
value.ValueLong,
22-
metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64:
23-
value.ValueLong,
24-
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE:
25-
value.ValueDouble,
26-
metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE:
27-
value.ValueDouble,
28-
metric_descriptor.MetricDescriptorType.GAUGE_DISTRIBUTION:
29-
value.ValueDistribution,
30-
metric_descriptor.MetricDescriptorType.CUMULATIVE_DISTRIBUTION:
31-
value.ValueDistribution,
32-
metric_descriptor.MetricDescriptorType.SUMMARY:
33-
value.ValueSummary,
34-
}
3516

3617

3718
class Metric(object):
@@ -71,9 +52,8 @@ def descriptor(self):
7152

7253
def _check_type(self):
7354
"""Check that point value types match the descriptor type."""
74-
check_type = DESCRIPTOR_VALUE.get(self.descriptor.type)
75-
if check_type is None:
76-
raise ValueError("Unknown metric descriptor type")
55+
check_type = metric_descriptor.MetricDescriptorType.to_type_class(
56+
self.descriptor.type)
7757
for ts in self.time_series:
7858
if not ts.check_points_type(check_type):
7959
raise ValueError("Invalid point value type")

opencensus/metrics/export/metric_descriptor.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414

1515
import six
1616

17-
from opencensus.metrics.export.value import ValueDistribution
18-
from opencensus.metrics.export.value import ValueSummary
17+
from opencensus.metrics.export import value
1918

2019

2120
class _MetricDescriptorTypeMeta(type):
@@ -81,19 +80,20 @@ class MetricDescriptorType(object):
8180
# is not recommended, since it cannot be aggregated.
8281
SUMMARY = 7
8382

83+
_type_map = {
84+
GAUGE_INT64: value.ValueLong,
85+
GAUGE_DOUBLE: value.ValueDouble,
86+
GAUGE_DISTRIBUTION: value.ValueDistribution,
87+
CUMULATIVE_INT64: value.ValueLong,
88+
CUMULATIVE_DOUBLE: value.ValueDouble,
89+
CUMULATIVE_DISTRIBUTION: value.ValueDistribution,
90+
SUMMARY: value.ValueSummary
91+
}
92+
8493
@classmethod
8594
def to_type_class(cls, metric_descriptor_type):
86-
type_map = {
87-
cls.GAUGE_INT64: int,
88-
cls.GAUGE_DOUBLE: float,
89-
cls.GAUGE_DISTRIBUTION: ValueDistribution,
90-
cls.CUMULATIVE_INT64: int,
91-
cls.CUMULATIVE_DOUBLE: float,
92-
cls.CUMULATIVE_DISTRIBUTION: ValueDistribution,
93-
cls.SUMMARY: ValueSummary
94-
}
9595
try:
96-
return type_map[metric_descriptor_type]
96+
return cls._type_map[metric_descriptor_type]
9797
except KeyError:
9898
raise ValueError("Unknown MetricDescriptorType value")
9999

@@ -119,8 +119,8 @@ class MetricDescriptor(object):
119119
format described by http://unitsofmeasure.org/ucum.html.
120120
121121
:type type_: int
122-
:param unit: The unit in which the metric value is reported. The
123-
MetricDescriptorType class enumerates valid options.
122+
:param type_: The type of metric. MetricDescriptorType enumerates the valid
123+
options.
124124
125125
:type label_keys: list(:class: '~opencensus.metrics.label_key.LabelKey')
126126
:param label_keys: The label keys associated with the metric descriptor.

opencensus/metrics/export/time_series.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from opencensus.metrics.export import metric_descriptor
16-
1715

1816
class TimeSeries(object):
1917
"""Time series data for a given metric and time interval.
@@ -60,15 +58,19 @@ def label_values(self):
6058
def points(self):
6159
return self._points
6260

63-
def check_points_type(self, type_):
64-
"""Check that each point's value is an instance `type_`.
61+
def check_points_type(self, type_class):
62+
"""Check that each point's value is an instance of `type_class`.
63+
64+
`type_class` should typically be a Value type, i.e. one that extends
65+
:class: `opencensus.metrics.export.value.Value`.
66+
67+
:type type_class: type
68+
:param type_class: Type to check against.
6569
66-
:type type_: type
67-
:param type_: Type to check against.
70+
:rtype: bool
71+
:return: Whether all points are instances of `type_class`.
6872
"""
69-
type_class = (
70-
metric_descriptor.MetricDescriptorType.to_type_class(type_))
7173
for point in self.points:
72-
if not isinstance(point.value.value, type_class):
74+
if not isinstance(point.value, type_class):
7375
return False
7476
return True

opencensus/metrics/export/value.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class Bucket(object):
149149
distribution does not have a histogram.
150150
"""
151151

152-
def __init__(self, count, exemplar):
152+
def __init__(self, count, exemplar=None):
153153
self._count = count
154154
self._exemplar = exemplar
155155

opencensus/stats/aggregation_data.py

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import copy
1516
import logging
1617

18+
from opencensus.metrics.export import point
19+
from opencensus.metrics.export import value
1720
from opencensus.stats import bucket_boundaries
1821

1922

@@ -36,6 +39,18 @@ def aggregation_data(self):
3639
"""The current aggregation data"""
3740
return self._aggregation_data
3841

42+
def to_point(self, timestamp):
43+
"""Get a Point conversion of this aggregation.
44+
45+
:type timestamp: :class: `datetime.datetime`
46+
:param timestamp: The time to report the point as having been recorded.
47+
48+
:rtype: :class: `opencensus.metrics.export.point.Point`
49+
:return: a Point with with this aggregation's value and appropriate
50+
value type.
51+
"""
52+
raise NotImplementedError # pragma: NO COVER
53+
3954

4055
class SumAggregationDataFloat(BaseAggregationData):
4156
"""Sum Aggregation Data is the aggregated data for the Sum aggregation
@@ -60,6 +75,18 @@ def sum_data(self):
6075
"""The current sum data"""
6176
return self._sum_data
6277

78+
def to_point(self, timestamp):
79+
"""Get a Point conversion of this aggregation.
80+
81+
:type timestamp: :class: `datetime.datetime`
82+
:param timestamp: The time to report the point as having been recorded.
83+
84+
:rtype: :class: `opencensus.metrics.export.point.Point`
85+
:return: a :class: `opencensus.metrics.export.value.ValueDouble`-valued
86+
Point with value equal to `sum_data`.
87+
"""
88+
return point.Point(value.ValueDouble(self.sum_data), timestamp)
89+
6390

6491
class CountAggregationData(BaseAggregationData):
6592
"""Count Aggregation Data is the count value of aggregated data
@@ -83,6 +110,18 @@ def count_data(self):
83110
"""The current count data"""
84111
return self._count_data
85112

113+
def to_point(self, timestamp):
114+
"""Get a Point conversion of this aggregation.
115+
116+
:type timestamp: :class: `datetime.datetime`
117+
:param timestamp: The time to report the point as having been recorded.
118+
119+
:rtype: :class: `opencensus.metrics.export.point.Point`
120+
:return: a :class: `opencensus.metrics.export.value.ValueLong`-valued
121+
Point with value equal to `count_data`.
122+
"""
123+
return point.Point(value.ValueLong(self.count_data), timestamp)
124+
86125

87126
class DistributionAggregationData(BaseAggregationData):
88127
"""Distribution Aggregation Data refers to the distribution stats of
@@ -123,34 +162,37 @@ def __init__(self,
123162
counts_per_bucket=None,
124163
bounds=None,
125164
exemplars=None):
165+
if bounds is None and exemplars is not None:
166+
raise ValueError
167+
if exemplars is not None and len(exemplars) != len(bounds) + 1:
168+
raise ValueError
169+
126170
super(DistributionAggregationData, self).__init__(mean_data)
127171
self._mean_data = mean_data
128172
self._count_data = count_data
129173
self._min = min_
130174
self._max = max_
131175
self._sum_of_sqd_deviations = sum_of_sqd_deviations
176+
132177
if bounds is None:
133178
bounds = []
179+
self._exemplars = None
134180
else:
135181
assert bounds == list(sorted(set(bounds)))
136182
assert all(bb > 0 for bb in bounds)
183+
if exemplars is None:
184+
self._exemplars = {ii: None for ii in range(len(bounds) + 1)}
185+
else:
186+
self._exemplars = {ii: ex for ii, ex in enumerate(exemplars)}
187+
self._bounds = (bucket_boundaries.BucketBoundaries(boundaries=bounds)
188+
.boundaries)
137189

138190
if counts_per_bucket is None:
139191
counts_per_bucket = [0 for ii in range(len(bounds) + 1)]
140192
else:
141193
assert all(cc >= 0 for cc in counts_per_bucket)
142194
assert len(counts_per_bucket) == len(bounds) + 1
143-
144195
self._counts_per_bucket = counts_per_bucket
145-
self._bounds = bucket_boundaries.BucketBoundaries(
146-
boundaries=bounds).boundaries
147-
bucket = 0
148-
for _ in self.bounds:
149-
bucket = bucket + 1
150-
151-
# If there is no histogram, do not record an exemplar
152-
self._exemplars = \
153-
{bucket: exemplars} if len(self._bounds) > 0 else None
154196

155197
@property
156198
def mean_data(self):
@@ -240,6 +282,43 @@ def increment_bucket_count(self, value):
240282
self._counts_per_bucket[last_bucket_index] += 1
241283
return last_bucket_index
242284

285+
def to_point(self, timestamp):
286+
"""Get a Point conversion of this aggregation.
287+
288+
This method creates a :class: `opencensus.metrics.export.point.Point`
289+
with a :class: `opencensus.metrics.export.value.ValueDistribution`
290+
value, and creates buckets and exemplars for that distribution from the
291+
appropriate classes in the `metrics` package.
292+
293+
:type timestamp: :class: `datetime.datetime`
294+
:param timestamp: The time to report the point as having been recorded.
295+
296+
:rtype: :class: `opencensus.metrics.export.point.Point`
297+
:return: a :class: `opencensus.metrics.export.value.ValueDistribution`
298+
-valued Point.
299+
"""
300+
buckets = [None] * len(self.counts_per_bucket)
301+
for ii, count in enumerate(self.counts_per_bucket):
302+
stat_ex = self.exemplars.get(ii, None)
303+
if stat_ex is not None:
304+
metric_ex = value.Exemplar(stat_ex.value, stat_ex.timestamp,
305+
copy.copy(stat_ex.attachments))
306+
buckets[ii] = value.Bucket(count, metric_ex)
307+
else:
308+
buckets[ii] = value.Bucket(count)
309+
310+
bucket_options = value.BucketOptions(value.Explicit(self.bounds))
311+
return point.Point(
312+
value.ValueDistribution(
313+
count=self.count_data,
314+
sum_=self.sum,
315+
sum_of_squared_deviation=self.sum_of_sqd_deviations,
316+
bucket_options=bucket_options,
317+
buckets=buckets
318+
),
319+
timestamp
320+
)
321+
243322

244323
class LastValueAggregationData(BaseAggregationData):
245324
"""
@@ -265,6 +344,18 @@ def value(self):
265344
"""The current value recorded"""
266345
return self._value
267346

347+
def to_point(self, timestamp):
348+
"""Get a Point conversion of this aggregation.
349+
350+
:type timestamp: :class: `datetime.datetime`
351+
:param timestamp: The time to report the point as having been recorded.
352+
353+
:rtype: :class: `opencensus.metrics.export.point.Point`
354+
:return: a :class: `opencensus.metrics.export.value.ValueDouble`-valued
355+
Point.
356+
"""
357+
return point.Point(value.ValueDouble(self.value), timestamp)
358+
268359

269360
class Exemplar(object):
270361
""" Exemplar represents an example point that may be used to annotate

tests/unit/metrics/export/test_metric_descriptor.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,48 +16,53 @@
1616

1717
import unittest
1818

19-
from opencensus.metrics.export.metric_descriptor import MetricDescriptor
20-
from opencensus.metrics.export.metric_descriptor import MetricDescriptorType
21-
from opencensus.metrics.label_key import LabelKey
19+
from opencensus.metrics import label_key
20+
from opencensus.metrics.export import metric_descriptor
21+
from opencensus.metrics.export import value
2222

2323
NAME = 'metric'
2424
DESCRIPTION = 'Metric description'
2525
UNIT = '0.738.[ft_i].[lbf_av]/s'
26-
LABEL_KEY1 = LabelKey('key1', 'key description one')
27-
LABEL_KEY2 = LabelKey('值', '测试用键')
26+
LABEL_KEY1 = label_key.LabelKey('key1', 'key description one')
27+
LABEL_KEY2 = label_key.LabelKey('值', '测试用键')
2828
LABEL_KEYS = (LABEL_KEY1, LABEL_KEY2)
2929

3030

3131
class TestMetricDescriptor(unittest.TestCase):
3232
def test_init(self):
33-
metric_descriptor = MetricDescriptor(NAME, DESCRIPTION, UNIT,
34-
MetricDescriptorType.GAUGE_DOUBLE,
35-
(LABEL_KEY1, LABEL_KEY2))
33+
md = metric_descriptor.MetricDescriptor(
34+
NAME, DESCRIPTION, UNIT,
35+
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE,
36+
(LABEL_KEY1, LABEL_KEY2))
3637

37-
self.assertEqual(metric_descriptor.name, NAME)
38-
self.assertEqual(metric_descriptor.description, DESCRIPTION)
39-
self.assertEqual(metric_descriptor.unit, UNIT)
40-
self.assertEqual(metric_descriptor.type,
41-
MetricDescriptorType.GAUGE_DOUBLE)
42-
self.assertEqual(metric_descriptor.label_keys, LABEL_KEYS)
38+
self.assertEqual(md.name, NAME)
39+
self.assertEqual(md.description, DESCRIPTION)
40+
self.assertEqual(md.unit, UNIT)
41+
self.assertEqual(md.type,
42+
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE)
43+
self.assertEqual(md.label_keys, LABEL_KEYS)
4344

4445
def test_bogus_type(self):
4546
with self.assertRaises(ValueError):
46-
MetricDescriptor(NAME, DESCRIPTION, UNIT, 0, (LABEL_KEY1, ))
47+
metric_descriptor.MetricDescriptor(NAME, DESCRIPTION, UNIT, 0,
48+
(LABEL_KEY1, ))
4749

4850
def test_null_label_keys(self):
4951
with self.assertRaises(ValueError):
50-
MetricDescriptor(NAME, DESCRIPTION, UNIT,
51-
MetricDescriptorType.GAUGE_DOUBLE, None)
52+
metric_descriptor.MetricDescriptor(
53+
NAME, DESCRIPTION, UNIT,
54+
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE, None)
5255

5356
def test_null_label_key_values(self):
5457
with self.assertRaises(ValueError):
55-
MetricDescriptor(NAME, DESCRIPTION, UNIT,
56-
MetricDescriptorType.GAUGE_DOUBLE, (None, ))
58+
metric_descriptor.MetricDescriptor(
59+
NAME, DESCRIPTION, UNIT,
60+
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE, (None, ))
5761

5862
def test_to_type_class(self):
5963
self.assertEqual(
60-
MetricDescriptorType.to_type_class(
61-
MetricDescriptorType.GAUGE_INT64), int)
64+
metric_descriptor.MetricDescriptorType.to_type_class(
65+
metric_descriptor.MetricDescriptorType.GAUGE_INT64),
66+
value.ValueLong)
6267
with self.assertRaises(ValueError):
63-
MetricDescriptorType.to_type_class(10)
68+
metric_descriptor.MetricDescriptorType.to_type_class(10)

0 commit comments

Comments
 (0)