Skip to content

Commit 20bd611

Browse files
authored
Exporter/Prometheus: Upgrade version and fix buckets and labels. (census-instrumentation#492)
- Upgrade to latest Prometheus client. - Use `UnknownMetricFamily` instead of `UntypedMetricFamily`, as the latter is now deprecated. - Use ordered list for histogram buckets. Previously we use unordered map and that led to unpredictable behavior. - Label keys and values must match when uploading metrics. - Update unit tests to test against more precise results.
1 parent 24e4bc2 commit 20bd611

File tree

5 files changed

+53
-33
lines changed

5 files changed

+53
-33
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changelog
2+
- Fix bugs in Prometheus exporter. Use ordered list for histogram buckets.
3+
Use `UnknownMetricFamily` for `SumData` instead of `UntypedMetricFamily`.
4+
Check if label keys and values match before exporting.
25

36
## Unreleased
47

opencensus/stats/exporters/prometheus_exporter.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from prometheus_client.core import GaugeMetricFamily
1919
from prometheus_client.core import HistogramMetricFamily
2020
from prometheus_client.core import REGISTRY
21-
from prometheus_client.core import UntypedMetricFamily
21+
from prometheus_client.core import UnknownMetricFamily
2222

2323
from opencensus.common.transports import sync
2424
from opencensus.stats import aggregation_data as aggregation_data_module
@@ -158,14 +158,15 @@ def to_metric(self, desc, tag_values, agg_data):
158158
159159
:rtype: :class:`~prometheus_client.core.CounterMetricFamily` or
160160
:class:`~prometheus_client.core.HistogramMetricFamily` or
161-
:class:`~prometheus_client.core.UntypedMetricFamily` or
161+
:class:`~prometheus_client.core.UnknownMetricFamily` or
162162
:class:`~prometheus_client.core.GaugeMetricFamily`
163163
:returns: A Prometheus metric object
164164
"""
165165
metric_name = desc['name']
166166
metric_description = desc['documentation']
167167
label_keys = desc['labels']
168168

169+
assert(len(tag_values) == len(label_keys))
169170
# Prometheus requires that all tag values be strings hence
170171
# the need to cast none to the empty string before exporting. See
171172
# https://github.com/census-instrumentation/opencensus-python/issues/480
@@ -183,26 +184,30 @@ def to_metric(self, desc, tag_values, agg_data):
183184
aggregation_data_module.DistributionAggregationData):
184185

185186
assert(agg_data.bounds == sorted(agg_data.bounds))
186-
points = {}
187-
cum_count = 0
187+
# buckets are a list of buckets. Each bucket is another list with
188+
# a pair of bucket name and value, or a triple of bucket name,
189+
# value, and exemplar. buckets need to be in order.
190+
buckets = []
191+
cum_count = 0 # Prometheus buckets expect cumulative count.
188192
for ii, bound in enumerate(agg_data.bounds):
189193
cum_count += agg_data.counts_per_bucket[ii]
190-
points[str(bound)] = cum_count
194+
bucket = [str(bound), cum_count]
195+
buckets.append(bucket)
191196
# Prometheus requires buckets to be sorted, and +Inf present.
192197
# In OpenCensus we don't have +Inf in the bucket bonds so need to
193198
# append it here.
194-
points["+Inf"] = agg_data.count_data
199+
buckets.append(["+Inf", agg_data.count_data])
195200
metric = HistogramMetricFamily(name=metric_name,
196201
documentation=metric_description,
197202
labels=label_keys)
198203
metric.add_metric(labels=tag_values,
199-
buckets=list(points.items()),
204+
buckets=buckets,
200205
sum_value=agg_data.sum,)
201206
return metric
202207

203208
elif isinstance(agg_data,
204209
aggregation_data_module.SumAggregationDataFloat):
205-
metric = UntypedMetricFamily(name=metric_name,
210+
metric = UnknownMetricFamily(name=metric_name,
206211
documentation=metric_description,
207212
labels=label_keys)
208213
metric.add_metric(labels=tag_values,

requirements-test.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ SQLAlchemy==1.1.14
1515
WebOb==1.7.3
1616
wrapt==1.10.11
1717
thrift==0.10.0
18-
prometheus_client==0.3.1
18+
prometheus_client==0.5.0

tests/system/stats/prometheus/prometheus_stats_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ def test_prometheus_stats(self):
7474
import urllib2
7575
contents = urllib2.urlopen("http://localhost:9303/metrics").read()
7676

77-
self.assertIn(b'# TYPE opencensus_request_count_view counter',
77+
self.assertIn(b'# TYPE opencensus_request_count_view_total counter',
7878
contents)
79-
self.assertIn(b'opencensus_request_count_view'
79+
self.assertIn(b'opencensus_request_count_view_total'
8080
b'{method="some method"} 1.0',
8181
contents)
82-
self.assertIn(b'opencensus_request_count_view'
82+
self.assertIn(b'opencensus_request_count_view_total'
8383
b'{method="some other method"} 2.0',
8484
contents)

tests/unit/stats/exporters/test_prometheus_stats.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
# limitations under the License.
1414

1515
from datetime import datetime
16+
import copy
1617
import mock
1718
import unittest
1819

20+
from prometheus_client.core import Sample
21+
1922
from opencensus.stats import aggregation as aggregation_module
2023
from opencensus.stats import measure as measure_module
2124
from opencensus.stats import stats as stats_module
@@ -26,7 +29,6 @@
2629
from opencensus.tags import tag_map as tag_map_module
2730
from opencensus.tags import tag_value as tag_value_module
2831

29-
3032
MiB = 1 << 20
3133
FRONTEND_KEY = tag_key_module.TagKey("myorg_keys_frontend")
3234
FRONTEND_KEY_FLOAT = tag_key_module.TagKey("myorg_keys_frontend_FLOAT")
@@ -138,7 +140,7 @@ def test_collector_to_metric_count(self):
138140
collector.register_view(view)
139141
desc = collector.registered_views[list(REGISTERED_VIEW)[0]]
140142
metric = collector.to_metric(
141-
desc=desc, tag_values=[], agg_data=agg.aggregation_data)
143+
desc=desc, tag_values=[None], agg_data=agg.aggregation_data)
142144

143145
self.assertEqual(desc['name'], metric.name)
144146
self.assertEqual(desc['documentation'], metric.documentation)
@@ -156,11 +158,11 @@ def test_collector_to_metric_sum(self):
156158
collector.register_view(view)
157159
desc = collector.registered_views[list(REGISTERED_VIEW)[0]]
158160
metric = collector.to_metric(
159-
desc=desc, tag_values=[], agg_data=agg.aggregation_data)
161+
desc=desc, tag_values=[None], agg_data=agg.aggregation_data)
160162

161163
self.assertEqual(desc['name'], metric.name)
162164
self.assertEqual(desc['documentation'], metric.documentation)
163-
self.assertEqual('untyped', metric.type)
165+
self.assertEqual('unknown', metric.type)
164166
self.assertEqual(1, len(metric.samples))
165167

166168
def test_collector_to_metric_last_value(self):
@@ -174,7 +176,7 @@ def test_collector_to_metric_last_value(self):
174176
collector.register_view(view)
175177
desc = collector.registered_views[list(REGISTERED_VIEW)[0]]
176178
metric = collector.to_metric(
177-
desc=desc, tag_values=[], agg_data=agg.aggregation_data)
179+
desc=desc, tag_values=[None], agg_data=agg.aggregation_data)
178180

179181
self.assertEqual(desc['name'], metric.name)
180182
self.assertEqual(desc['documentation'], metric.documentation)
@@ -187,15 +189,31 @@ def test_collector_to_metric_histogram(self):
187189
collector = prometheus.Collector(options=options)
188190
collector.register_view(VIDEO_SIZE_VIEW)
189191
desc = collector.registered_views[list(REGISTERED_VIEW)[0]]
192+
distribution = copy.deepcopy(VIDEO_SIZE_DISTRIBUTION.aggregation_data)
193+
distribution.add_sample(280.0 * MiB, None, None)
190194
metric = collector.to_metric(
191195
desc=desc,
192-
tag_values=[],
193-
agg_data=VIDEO_SIZE_DISTRIBUTION.aggregation_data)
196+
tag_values=[tag_value_module.TagValue("ios")],
197+
agg_data=distribution)
194198

195199
self.assertEqual(desc['name'], metric.name)
196200
self.assertEqual(desc['documentation'], metric.documentation)
197201
self.assertEqual('histogram', metric.type)
198-
self.assertEqual(5, len(metric.samples))
202+
expected_samples = [
203+
Sample(metric.name + '_bucket',
204+
{"myorg_keys_frontend": "ios", "le": str(16.0 * MiB)},
205+
0),
206+
Sample(metric.name + '_bucket',
207+
{"myorg_keys_frontend": "ios", "le": str(256.0 * MiB)},
208+
0),
209+
Sample(metric.name + '_bucket',
210+
{"myorg_keys_frontend": "ios", "le": "+Inf"},
211+
1),
212+
Sample(metric.name + '_count', {"myorg_keys_frontend": "ios"}, 1),
213+
Sample(metric.name + '_sum',
214+
{"myorg_keys_frontend": "ios"},
215+
280.0 * MiB)]
216+
self.assertEqual(expected_samples, metric.samples)
199217

200218
def test_collector_to_metric_invalid_dist(self):
201219
agg = mock.Mock()
@@ -211,7 +229,7 @@ def test_collector_to_metric_invalid_dist(self):
211229
with self.assertRaisesRegexp(
212230
ValueError,
213231
'unsupported aggregation type <class \'mock.mock.Mock\'>'):
214-
collector.to_metric(desc=desc, tag_values=[], agg_data=agg)
232+
collector.to_metric(desc=desc, tag_values=[None], agg_data=agg)
215233

216234
def test_collector_collect(self):
217235
agg = aggregation_module.LastValueAggregation(256)
@@ -222,23 +240,17 @@ def test_collector_collect(self):
222240
collector = prometheus.Collector(options=options)
223241
collector.register_view(view)
224242
desc = collector.registered_views['test2_new_view']
225-
collector.to_metric(
226-
desc=desc, tag_values=[], agg_data=agg.aggregation_data)
227-
228-
registry = mock.Mock()
229-
options = prometheus.Options("test1", 8001, "localhost", registry)
230-
collector = prometheus.Collector(options=options)
231-
collector.register_view(VIDEO_SIZE_VIEW)
232-
desc = collector.registered_views[list(REGISTERED_VIEW)[0]]
233243
metric = collector.to_metric(
234244
desc=desc,
235-
tag_values=[],
236-
agg_data=VIDEO_SIZE_DISTRIBUTION.aggregation_data)
245+
tag_values=[tag_value_module.TagValue("value")],
246+
agg_data=agg.aggregation_data)
237247

238248
self.assertEqual(desc['name'], metric.name)
239249
self.assertEqual(desc['documentation'], metric.documentation)
240-
self.assertEqual('histogram', metric.type)
241-
self.assertEqual(5, len(metric.samples))
250+
self.assertEqual('gauge', metric.type)
251+
expected_samples = [
252+
Sample(metric.name, {"myorg_keys_frontend": "value"}, 256)]
253+
self.assertEqual(expected_samples, metric.samples)
242254

243255
def test_collector_collect_with_none_label_value(self):
244256
agg = aggregation_module.LastValueAggregation(256)

0 commit comments

Comments
 (0)