Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 989470e

Browse files
authored
Fix Prometheus Stats Exporter. (#440)
* Rename a dict. Remove desribe. * Fix the creation of metric samples. * Fix tests. * Remove view signature. Fix build failures. * Minor fix * Fix system test. * More fixes. * Replace dict kwarg default * Sort imports * Nitfix for dict iterator
1 parent bdcb1fa commit 989470e

File tree

4 files changed

+169
-183
lines changed

4 files changed

+169
-183
lines changed

examples/stats/exporter/prometheus.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@
2828
from pprint import pprint
2929

3030
MiB = 1 << 20
31-
FRONTEND_KEY = tag_key_module.TagKey("my.org/keys/frontend")
31+
FRONTEND_KEY = tag_key_module.TagKey("myorg_keys_frontend")
3232
VIDEO_SIZE_MEASURE = measure_module.MeasureInt(
33-
"my.org/measures/video_size", "size of processed videos", "By")
34-
VIDEO_SIZE_VIEW_NAME = "my.org/views/video_size"
33+
"myorg_measures_video_size", "size of processed videos", "By")
34+
VIDEO_SIZE_VIEW_NAME = "myorg_views_video_size"
3535
VIDEO_SIZE_DISTRIBUTION = aggregation_module.DistributionAggregation(
3636
[0.0, 16.0 * MiB, 256.0 * MiB])
3737
VIDEO_SIZE_VIEW = view_module.View(
@@ -63,17 +63,18 @@ def main():
6363
measure_map.measure_int_put(VIDEO_SIZE_MEASURE, 25 * MiB)
6464
measure_map.record(tag_map)
6565

66-
# Use the line below to see the data on prometheus
67-
# while True:
68-
# pass
69-
7066
# Get aggregated stats and print it to console.
7167
view_data = view_manager.get_view(VIDEO_SIZE_VIEW_NAME)
7268
pprint(vars(view_data))
73-
for k, v in view_data._tag_value_aggregation_data_map.items():
69+
for k, v in view_data.tag_value_aggregation_data_map.items():
7470
pprint(k)
7571
pprint(vars(v))
7672

73+
# Prevent main from exiting to see the data on prometheus
74+
# localhost:8000/metrics
75+
while True:
76+
pass
77+
7778

7879
if __name__ == '__main__':
7980
main()

opencensus/stats/exporters/prometheus_exporter.py

Lines changed: 69 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414

1515
from prometheus_client import start_http_server
1616
from prometheus_client.core import CollectorRegistry
17-
from prometheus_client.core import GaugeMetricFamily
1817
from prometheus_client.core import CounterMetricFamily
19-
from prometheus_client.core import UntypedMetricFamily
18+
from prometheus_client.core import GaugeMetricFamily
2019
from prometheus_client.core import HistogramMetricFamily
2120
from prometheus_client.core import REGISTRY
22-
from opencensus.stats.exporters import base
23-
from opencensus.stats import aggregation_data as aggregation_data_module
21+
from prometheus_client.core import UntypedMetricFamily
22+
2423
from opencensus.common.transports import sync
24+
from opencensus.stats import aggregation_data as aggregation_data_module
25+
from opencensus.stats.exporters import base
2526

2627

2728
class Options(object):
@@ -82,10 +83,12 @@ def address(self):
8283
class Collector(object):
8384
""" Collector represents the Prometheus Collector object
8485
"""
85-
def __init__(self, options=Options(), view_data={}):
86+
def __init__(self, options=Options(), view_name_to_data_map=None):
87+
if view_name_to_data_map is None:
88+
view_name_to_data_map = {}
8689
self._options = options
8790
self._registry = options.registry
88-
self._view_data = view_data
91+
self._view_name_to_data_map = view_name_to_data_map
8992
self._registered_views = {}
9093

9194
@property
@@ -101,11 +104,11 @@ def registry(self):
101104
return self._registry
102105

103106
@property
104-
def view_data(self):
107+
def view_name_to_data_map(self):
105108
""" Map with all view data objects
106109
that will be sent to Prometheus
107110
"""
108-
return self._view_data
111+
return self._view_name_to_data_map
109112

110113
@property
111114
def registered_views(self):
@@ -117,51 +120,58 @@ def register_view(self, view):
117120
""" register_view will create the needed structure
118121
in order to be able to sent all data to Prometheus
119122
"""
120-
count = 0
121-
122-
signature = view_signature(self.options.namespace, view)
123+
v_name = get_view_name(self.options.namespace, view)
123124

124-
if signature not in self.registered_views:
125-
desc = {'name': view_name(self.options.namespace, view),
125+
if v_name not in self.registered_views:
126+
desc = {'name': v_name,
126127
'documentation': view.description,
127-
'labels': tag_keys_to_labels(view.columns)}
128-
self.registered_views[signature] = desc
129-
count += 1
128+
'labels': list(view.columns)}
129+
self.registered_views[v_name] = desc
130130
self.registry.register(self)
131131

132132
def add_view_data(self, view_data):
133133
""" Add view data object to be sent to server
134134
"""
135135
self.register_view(view_data.view)
136-
signature = view_signature(self.options.namespace, view_data.view)
137-
self.view_data[signature] = view_data
136+
v_name = get_view_name(self.options.namespace, view_data.view)
137+
self.view_name_to_data_map[v_name] = view_data
138138

139-
def to_metric(self, desc, view):
139+
# TODO: add start and end timestamp
140+
def to_metric(self, desc, tag_values, agg_data):
140141
""" to_metric translate the data that OpenCensus create
141142
to Prometheus format, using Prometheus Metric object
142143
143-
:type desc: str
144-
:param desc: The view descriptor
144+
:type desc: dict
145+
:param desc: The map that describes view definition
145146
146-
:type view: object of :class:
147-
`~opencensus.stats.view.View`
148-
:param object of opencensus.stats.view.View view:
149-
View object to translate
147+
:type tag_values: tuple of :class:
148+
`~opencensus.tags.tag_value.TagValue`
149+
:param object of opencensus.tags.tag_value.TagValue:
150+
TagValue object used as label values
151+
152+
:type agg_data: object of :class:
153+
`~opencensus.stats.aggregation_data.AggregationData`
154+
:param object of opencensus.stats.aggregation_data.AggregationData:
155+
Aggregated data that needs to be converted as Prometheus samples
150156
151157
:rtype: :class:`~prometheus_client.core.CounterMetricFamily` or
152158
:class:`~prometheus_client.core.HistogramMetricFamily` or
153159
:class:`~prometheus_client.core.UntypedMetricFamily` or
154160
:class:`~prometheus_client.core.GaugeMetricFamily`
155161
:returns: A Prometheus metric object
156162
"""
157-
agg_data = view.aggregation.aggregation_data
163+
metric_name = desc['name']
164+
metric_description = desc['documentation']
165+
label_keys = desc['labels']
158166

159167
if isinstance(agg_data, aggregation_data_module.CountAggregationData):
160-
labels = desc['labels'] if agg_data.count_data is None else None
161-
return CounterMetricFamily(name=desc['name'],
162-
documentation=desc['documentation'],
163-
value=float(agg_data.count_data),
164-
labels=labels)
168+
metric = CounterMetricFamily(name=metric_name,
169+
documentation=metric_description,
170+
labels=label_keys)
171+
metric.add_metric(labels=list(tag_values),
172+
value=agg_data.count_data)
173+
return metric
174+
165175
elif isinstance(agg_data,
166176
aggregation_data_module.DistributionAggregationData):
167177

@@ -171,28 +181,31 @@ def to_metric(self, desc, view):
171181
for ii, bound in enumerate(agg_data.bounds):
172182
cum_count += agg_data.counts_per_bucket[ii]
173183
points[str(bound)] = cum_count
174-
labels = desc['labels'] if points is None else None
175-
return HistogramMetricFamily(name=desc['name'],
176-
documentation=desc['documentation'],
177-
buckets=list(points.items()),
178-
sum_value=agg_data.sum,
179-
labels=labels)
184+
metric = HistogramMetricFamily(name=metric_name,
185+
documentation=metric_description,
186+
labels=label_keys)
187+
metric.add_metric(labels=list(tag_values),
188+
buckets=list(points.items()),
189+
sum_value=agg_data.sum,)
190+
return metric
180191

181192
elif isinstance(agg_data,
182193
aggregation_data_module.SumAggregationDataFloat):
183-
labels = desc['labels'] if agg_data.sum_data is None else None
184-
return UntypedMetricFamily(name=desc['name'],
185-
documentation=desc['documentation'],
186-
value=agg_data.sum_data,
187-
labels=labels)
194+
metric = UntypedMetricFamily(name=metric_name,
195+
documentation=metric_description,
196+
labels=label_keys)
197+
metric.add_metric(labels=list(tag_values),
198+
value=agg_data.sum_data)
199+
return metric
188200

189201
elif isinstance(agg_data,
190202
aggregation_data_module.LastValueAggregationData):
191-
labels = desc['labels'] if agg_data.value is None else None
192-
return GaugeMetricFamily(name=desc['name'],
193-
documentation=desc['documentation'],
194-
value=agg_data.value,
195-
labels=labels)
203+
metric = GaugeMetricFamily(name=metric_name,
204+
documentation=metric_description,
205+
labels=label_keys)
206+
metric.add_metric(labels=list(tag_values),
207+
value=agg_data.value)
208+
return metric
196209

197210
else:
198211
raise ValueError("unsupported aggregation type %s"
@@ -201,30 +214,16 @@ def to_metric(self, desc, view):
201214
def collect(self): # pragma: NO COVER
202215
"""Collect fetches the statistics from OpenCensus
203216
and delivers them as Prometheus Metrics.
204-
Collect is invoked everytime a prometheus.Gatherer is run
217+
Collect is invoked every time a prometheus.Gatherer is run
205218
for example when the HTTP endpoint is invoked by Prometheus.
206219
"""
207-
for v_data in list(self.view_data):
208-
signature = view_signature(self.options.namespace,
209-
self.view_data[v_data].view)
210-
desc = self.registered_views[signature]
211-
metric = self.to_metric(desc,
212-
self.view_data[v_data].view)
213-
yield metric
214-
215-
def describe(self):
216-
""" describe will be used by Prometheus Client
217-
to retrieve all registered views.
218-
"""
219-
registered = {}
220-
for sign in self.registered_views:
221-
registered[sign] = self.registered_views[sign]
222-
for v_data in list(self.view_data): # pragma: NO COVER
223-
if not isinstance(v_data, str):
224-
signature = view_signature(self.options.namespace, v_data.view)
225-
desc = self.registered_views[signature]
226-
metric = self.to_metric(desc,
227-
self.view_data[v_data].view)
220+
for v_name, view_data in self.view_name_to_data_map.items():
221+
if v_name not in self.registered_views:
222+
continue
223+
desc = self.registered_views[v_name]
224+
for tag_values in view_data.tag_value_aggregation_data_map:
225+
agg_data = view_data.tag_value_aggregation_data_map[tag_values]
226+
metric = self.to_metric(desc, tag_values, agg_data)
228227
yield metric
229228

230229

@@ -332,15 +331,6 @@ def new_stats_exporter(option):
332331
return exporter
333332

334333

335-
def tag_keys_to_labels(tag_keys):
336-
""" Translate Tag keys to labels
337-
"""
338-
labels = []
339-
for key in tag_keys:
340-
labels.append(key)
341-
return labels
342-
343-
344334
def new_collector(options):
345335
""" new_collector should be used
346336
to create instance of Collector class in order to
@@ -349,19 +339,10 @@ def new_collector(options):
349339
return Collector(options=options)
350340

351341

352-
def view_name(namespace, view):
342+
def get_view_name(namespace, view):
353343
""" create the name for the view
354344
"""
355345
name = ""
356346
if namespace != "":
357347
name = namespace + "_"
358348
return name + view.name
359-
360-
361-
def view_signature(namespace, view):
362-
""" create the signature for the view
363-
"""
364-
sign = view_name(namespace, view)
365-
for key in view.columns:
366-
sign += "-" + key
367-
return sign

tests/system/stats/prometheus/prometheus_stats_test.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,15 @@
3030
class TestPrometheusStats(unittest.TestCase):
3131
def test_prometheus_stats(self):
3232

33-
MiB = 1 << 20
34-
FRONTEND_KEY = tag_key_module.TagKey("my.org/keys/frontend")
35-
VIDEO_SIZE_MEASURE = measure_module.MeasureInt(
36-
"my.org/measures/video_size", "size of processed videos", "By")
37-
VIDEO_SIZE_VIEW_NAME = "my.org/views/video_size"
38-
VIDEO_SIZE_DISTRIBUTION = aggregation_module.CountAggregation(
39-
256.0 * MiB)
40-
VIDEO_SIZE_VIEW = view_module.View(
41-
VIDEO_SIZE_VIEW_NAME, "processed video size over time",
42-
[FRONTEND_KEY], VIDEO_SIZE_MEASURE, VIDEO_SIZE_DISTRIBUTION)
33+
method_key = tag_key_module.TagKey("method")
34+
request_count_measure = measure_module.MeasureInt(
35+
"request_count", "number of requests", "1")
36+
request_count_view_name = "request_count_view"
37+
count_agg = aggregation_module.CountAggregation()
38+
request_count_view = view_module.View(
39+
request_count_view_name,
40+
"number of requests broken down by methods",
41+
[method_key], request_count_measure, count_agg)
4342
stats = stats_module.Stats()
4443
view_manager = stats.view_manager
4544
stats_recorder = stats.stats_recorder
@@ -48,16 +47,24 @@ def test_prometheus_stats(self):
4847
prometheus.Options(namespace="opencensus", port=9303))
4948
view_manager.register_exporter(exporter)
5049

51-
view_manager.register_view(VIDEO_SIZE_VIEW)
50+
view_manager.register_view(request_count_view)
5251

5352
time.sleep(random.randint(1, 10) / 1000.0)
5453

55-
tag_value = tag_value_module.TagValue(str(random.randint(1, 10000)))
56-
tag_map = tag_map_module.TagMap()
57-
tag_map.insert(FRONTEND_KEY, tag_value)
58-
measure_map = stats_recorder.new_measurement_map()
59-
measure_map.measure_int_put(VIDEO_SIZE_MEASURE, 25 * MiB)
60-
measure_map.record(tag_map)
54+
method_value_1 = tag_value_module.TagValue("some method")
55+
tag_map_1 = tag_map_module.TagMap()
56+
tag_map_1.insert(method_key, method_value_1)
57+
measure_map_1 = stats_recorder.new_measurement_map()
58+
measure_map_1.measure_int_put(request_count_measure, 1)
59+
measure_map_1.record(tag_map_1)
60+
61+
method_value_2 = tag_value_module.TagValue("some other method")
62+
tag_map_2 = tag_map_module.TagMap()
63+
tag_map_2.insert(method_key, method_value_2)
64+
measure_map_2 = stats_recorder.new_measurement_map()
65+
measure_map_2.measure_int_put(request_count_measure, 1)
66+
measure_map_2.record(tag_map_2)
67+
measure_map_2.record(tag_map_2)
6168

6269
if sys.version_info > (3, 0):
6370
import urllib.request
@@ -67,7 +74,11 @@ def test_prometheus_stats(self):
6774
import urllib2
6875
contents = urllib2.urlopen("http://localhost:9303/metrics").read()
6976

70-
self.assertIn(b'# TYPE opencensus_my.org/views/video_size counter',
77+
self.assertIn(b'# TYPE opencensus_request_count_view counter',
78+
contents)
79+
self.assertIn(b'opencensus_request_count_view'
80+
b'{method="some method"} 1.0',
7181
contents)
72-
self.assertIn(b'opencensus_my.org/views/video_size 268435456.0',
82+
self.assertIn(b'opencensus_request_count_view'
83+
b'{method="some other method"} 2.0',
7384
contents)

0 commit comments

Comments
 (0)