Skip to content

Commit ad778ff

Browse files
authored
Metrics view data conversion (census-instrumentation#469)
Add a utility function to convert ViewDatas to Metrics.
1 parent 99346e5 commit ad778ff

5 files changed

Lines changed: 223 additions & 32 deletions

File tree

opencensus/stats/measure_to_view_map.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,21 @@ def get_view(self, view_name, timestamp):
5555

5656
view_data_list = self._measure_to_view_data_list_map.get(
5757
view.measure.name)
58-
if view_data_list is not None:
59-
for view_data in view_data_list:
60-
if view_data.view.name == view_name:
61-
view_data_copy = copy.deepcopy(view_data)
62-
view_data_copy.end()
63-
return view_data_copy
58+
59+
if not view_data_list:
60+
return None
61+
62+
for view_data in view_data_list:
63+
if view_data.view.name == view_name:
64+
break
65+
else:
66+
return None
67+
68+
view_data_copy = copy.copy(view_data)
69+
tvdam_copy = copy.deepcopy(view_data.tag_value_aggregation_data_map)
70+
view_data_copy._tag_value_aggregation_data_map = tvdam_copy
71+
view_data_copy.end()
72+
return view_data_copy
6473

6574
def filter_exported_views(self, all_views):
6675
"""returns the subset of the given view that should be exported"""

opencensus/stats/metric_utils.py

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
Utilities to convert stats data models to metrics data models.
1616
"""
1717

18-
from opencensus.metrics import label_key
18+
from opencensus.metrics import label_value
19+
from opencensus.metrics.export import metric
1920
from opencensus.metrics.export import metric_descriptor
21+
from opencensus.metrics.export import time_series
2022
from opencensus.stats import aggregation as aggregation_module
2123
from opencensus.stats import measure as measure_module
2224

@@ -70,14 +72,58 @@ def get_metric_type(measure, aggregation):
7072
raise AssertionError # pragma: NO COVER
7173

7274

73-
def view_to_metric_descriptor(view):
74-
"""Get a MetricDescriptor for given view data.
75+
def is_gauge(md_type):
76+
"""Whether a given MetricDescriptorType value is a gauge.
7577
76-
:type view: (:class: '~opencensus.stats.view.View')
77-
:param view: the view data to for which to build a metric descriptor
78+
:type md_type: int
79+
:param md_type: A MetricDescriptorType enum value.
7880
"""
79-
return metric_descriptor.MetricDescriptor(
80-
view.name, view.description, view.measure.unit,
81-
get_metric_type(view.measure, view.aggregation),
82-
# TODO: add label key description
83-
[label_key.LabelKey(tk, "") for tk in view.columns])
81+
if md_type not in metric_descriptor.MetricDescriptorType:
82+
raise ValueError # pragma: NO COVER
83+
84+
return md_type in {
85+
metric_descriptor.MetricDescriptorType.GAUGE_INT64,
86+
metric_descriptor.MetricDescriptorType.GAUGE_DOUBLE,
87+
metric_descriptor.MetricDescriptorType.GAUGE_DISTRIBUTION
88+
}
89+
90+
91+
def get_label_values(tag_values):
92+
"""Convert an iterable of TagValues into a list of LabelValues.
93+
94+
:type tag_values: list(:class: `opencensus.tags.tag_value.TagValue`)
95+
:param tag_values: An iterable of TagValues to convert.
96+
97+
:rtype: list(:class: `opencensus.metrics.label_value.LabelValue`)
98+
:return: A list of LabelValues, converted from TagValues.
99+
"""
100+
return [label_value.LabelValue(tv) for tv in tag_values]
101+
102+
103+
def view_data_to_metric(view_data, timestamp):
104+
"""Convert a ViewData to a Metric at time `timestamp`.
105+
106+
:type view_data: :class: `opencensus.stats.view_data.ViewData`
107+
:param view_data: The ViewData to convert.
108+
109+
:type timestamp: :class: `datetime.datetime`
110+
:param timestamp: The time to set on the metric's point's aggregation,
111+
usually the current time.
112+
113+
:rtype: :class: `opencensus.metrics.export.metric.Metric`
114+
:return: A converted Metric.
115+
"""
116+
md = view_data.view.get_metric_descriptor()
117+
118+
# TODO: implement gauges
119+
if is_gauge(md.type):
120+
ts_start = None # pragma: NO COVER
121+
else:
122+
ts_start = view_data.start_time
123+
124+
ts_list = []
125+
for tag_vals, agg_data in view_data.tag_value_aggregation_data_map.items():
126+
label_values = get_label_values(tag_vals)
127+
point = agg_data.to_point(timestamp)
128+
ts_list.append(time_series.TimeSeries(label_values, [point], ts_start))
129+
return metric.Metric(md, ts_list)

opencensus/stats/view.py

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

1515

16+
import threading
17+
18+
from opencensus.metrics import label_key
19+
from opencensus.metrics.export import metric_descriptor
20+
from opencensus.stats import metric_utils
21+
22+
1623
class View(object):
1724
"""A view defines a specific aggregation and a set of tag keys
1825
@@ -33,13 +40,19 @@ class View(object):
3340
:param aggregation: the aggregation the view will support
3441
3542
"""
43+
3644
def __init__(self, name, description, columns, measure, aggregation):
3745
self._name = name
3846
self._description = description
3947
self._columns = columns
4048
self._measure = measure
4149
self._aggregation = aggregation
4250

51+
# Cache the converted MetricDescriptor here to avoid creating it each
52+
# time we convert a ViewData that realizes this View into a Metric.
53+
self._md_cache_lock = threading.Lock()
54+
self._metric_descriptor = None
55+
4356
@property
4457
def name(self):
4558
"""the name of the current view"""
@@ -64,3 +77,24 @@ def measure(self):
6477
def aggregation(self):
6578
"""the aggregation of the current view"""
6679
return self._aggregation
80+
81+
def get_metric_descriptor(self):
82+
"""Get a MetricDescriptor for this view.
83+
84+
Lazily creates a MetricDescriptor for metrics conversion.
85+
86+
:rtype: :class:
87+
`opencensus.metrics.export.metric_descriptor.MetricDescriptor`
88+
:return: A converted Metric.
89+
""" # noqa
90+
with self._md_cache_lock:
91+
if self._metric_descriptor is None:
92+
self._metric_descriptor = metric_descriptor.MetricDescriptor(
93+
self.name,
94+
self.description,
95+
self.measure.unit,
96+
metric_utils.get_metric_type(self.measure,
97+
self.aggregation),
98+
# TODO: add label key description
99+
[label_key.LabelKey(tk, "") for tk in self.columns])
100+
return self._metric_descriptor

tests/unit/stats/test_metric_utils.py

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,20 @@
1717
except ImportError:
1818
from unittest import mock
1919

20+
import datetime
2021
import unittest
2122

2223
from opencensus.metrics.export import metric_descriptor
24+
from opencensus.metrics.export import point
25+
from opencensus.metrics.export import value
2326
from opencensus.stats import aggregation
27+
from opencensus.stats import aggregation_data
2428
from opencensus.stats import measure
2529
from opencensus.stats import metric_utils
2630
from opencensus.stats import view
31+
from opencensus.stats import view_data
32+
from opencensus.tags import tag_key
33+
from opencensus.tags import tag_value
2734

2835

2936
class TestMetricUtils(unittest.TestCase):
@@ -83,20 +90,84 @@ def test_get_metric_type_bad_measure(self):
8390
with self.assertRaises(ValueError):
8491
metric_utils.get_metric_type(base_measure, agg_lv)
8592

86-
def test_view_to_metric_descriptor(self):
93+
def do_test_view_data_to_metric(self, aggregation_type, aggregation_class,
94+
value_type, metric_descriptor_type):
95+
"""Test that ViewDatas are converted correctly into Metrics.
96+
97+
This test doesn't check that the various aggregation data `to_point`
98+
methods handle the point conversion correctly, just that converted
99+
Point is included in the Metric, and the metric has the expected
100+
structure, descriptor, and labels.
101+
"""
102+
start_time = datetime.datetime(2019, 1, 25, 11, 12, 13)
103+
current_time = datetime.datetime(2019, 1, 25, 12, 13, 14)
104+
87105
mock_measure = mock.Mock(spec=measure.MeasureFloat)
88-
mock_agg = mock.Mock(spec=aggregation.SumAggregation)
89-
mock_agg.aggregation_type = aggregation.Type.SUM
90-
test_view = view.View("name", "description", ["tk1", "tk2"],
91-
mock_measure, mock_agg)
92-
93-
md = metric_utils.view_to_metric_descriptor(test_view)
94-
self.assertTrue(isinstance(md, metric_descriptor.MetricDescriptor))
95-
self.assertEqual(md.name, test_view.name)
96-
self.assertEqual(md.description, test_view.description)
97-
self.assertEqual(md.unit, test_view.measure.unit)
98-
self.assertEqual(
99-
md.type, metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE)
100-
self.assertTrue(
101-
all(lk.key == col
102-
for lk, col in zip(md.label_keys, test_view.columns)))
106+
mock_aggregation = mock.Mock(spec=aggregation_class)
107+
mock_aggregation.aggregation_type = aggregation_type
108+
109+
vv = view.View(
110+
name=mock.Mock(),
111+
description=mock.Mock(),
112+
columns=[tag_key.TagKey('k1'), tag_key.TagKey('k2')],
113+
measure=mock_measure,
114+
aggregation=mock_aggregation)
115+
116+
vd = mock.Mock(spec=view_data.ViewData)
117+
vd.view = vv
118+
vd.start_time = start_time
119+
120+
mock_point = mock.Mock(spec=point.Point)
121+
mock_point.value = mock.Mock(spec=value_type)
122+
123+
mock_agg = mock.Mock(spec=aggregation_data.SumAggregationDataFloat)
124+
mock_agg.to_point.return_value = mock_point
125+
126+
vd.tag_value_aggregation_data_map = {
127+
(tag_value.TagValue('v1'), tag_value.TagValue('v2')): mock_agg
128+
}
129+
130+
metric = metric_utils.view_data_to_metric(vd, current_time)
131+
mock_agg.to_point.assert_called_once_with(current_time)
132+
133+
self.assertEqual(metric.descriptor.name, vv.name)
134+
self.assertEqual(metric.descriptor.description, vv.description)
135+
self.assertEqual(metric.descriptor.unit, vv.measure.unit)
136+
self.assertEqual(metric.descriptor.type, metric_descriptor_type)
137+
self.assertListEqual(
138+
[lk.key for lk in metric.descriptor.label_keys],
139+
['k1', 'k2'])
140+
141+
self.assertEqual(len(metric.time_series), 1)
142+
[ts] = metric.time_series
143+
self.assertEqual(ts.start_timestamp, start_time)
144+
self.assertListEqual(
145+
[lv.value for lv in ts.label_values],
146+
['v1', 'v2'])
147+
self.assertEqual(len(ts.points), 1)
148+
[pt] = ts.points
149+
self.assertEqual(pt, mock_point)
150+
151+
def test_view_data_to_metric(self):
152+
args_list = [
153+
[
154+
aggregation.Type.SUM,
155+
aggregation.SumAggregation,
156+
value.ValueDouble,
157+
metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE
158+
],
159+
[
160+
aggregation.Type.COUNT,
161+
aggregation.CountAggregation,
162+
value.ValueLong,
163+
metric_descriptor.MetricDescriptorType.CUMULATIVE_INT64
164+
],
165+
[
166+
aggregation.Type.DISTRIBUTION,
167+
aggregation.DistributionAggregation,
168+
value.ValueDistribution,
169+
metric_descriptor.MetricDescriptorType.CUMULATIVE_DISTRIBUTION
170+
]
171+
]
172+
for args in args_list:
173+
self.do_test_view_data_to_metric(*args)

tests/unit/stats/test_view.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414

1515
import unittest
1616
import mock
17+
18+
from opencensus.metrics.export import metric_descriptor
19+
from opencensus.stats import aggregation
20+
from opencensus.stats import measure
21+
from opencensus.stats import view
1722
from opencensus.stats import view as view_module
1823

1924

@@ -37,3 +42,29 @@ def test_constructor(self):
3742
self.assertEqual(["testTagKey1", "testTagKey2"], view.columns)
3843
self.assertEqual(measure, view.measure)
3944
self.assertEqual(aggregation, view.aggregation)
45+
46+
def test_view_to_metric_descriptor(self):
47+
mock_measure = mock.Mock(spec=measure.MeasureFloat)
48+
mock_agg = mock.Mock(spec=aggregation.SumAggregation)
49+
mock_agg.aggregation_type = aggregation.Type.SUM
50+
test_view = view.View("name", "description", ["tk1", "tk2"],
51+
mock_measure, mock_agg)
52+
53+
self.assertIsNone(test_view._metric_descriptor)
54+
md = test_view.get_metric_descriptor()
55+
self.assertTrue(isinstance(md, metric_descriptor.MetricDescriptor))
56+
self.assertEqual(md.name, test_view.name)
57+
self.assertEqual(md.description, test_view.description)
58+
self.assertEqual(md.unit, test_view.measure.unit)
59+
self.assertEqual(
60+
md.type, metric_descriptor.MetricDescriptorType.CUMULATIVE_DOUBLE)
61+
self.assertTrue(
62+
all(lk.key == col
63+
for lk, col in zip(md.label_keys, test_view.columns)))
64+
65+
md_path = ('opencensus.metrics.export.metric_descriptor'
66+
'.MetricDescriptor')
67+
with mock.patch(md_path) as mock_md_cls:
68+
md2 = test_view.get_metric_descriptor()
69+
mock_md_cls.assert_not_called()
70+
self.assertEqual(md, md2)

0 commit comments

Comments
 (0)