Skip to content

Commit aad4633

Browse files
author
Alex Boten
committed
add histogram
1 parent ee2046e commit aad4633

File tree

2 files changed

+67
-66
lines changed

2 files changed

+67
-66
lines changed

exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -65,26 +65,15 @@
6565
import collections
6666
import logging
6767
import re
68-
from typing import Iterable, Optional, Sequence, Union
68+
from typing import Optional, Sequence, Tuple
6969

70-
from prometheus_client.core import (
71-
REGISTRY,
72-
CounterMetricFamily,
73-
GaugeMetricFamily,
74-
HistogramMetricFamily,
75-
)
70+
from prometheus_client import core
7671

7772
from opentelemetry.sdk._metrics.export import (
7873
MetricExporter,
7974
MetricExportResult,
8075
)
81-
from opentelemetry.sdk._metrics.point import (
82-
AggregationTemporality,
83-
Gauge,
84-
Histogram,
85-
Metric,
86-
Sum,
87-
)
76+
from opentelemetry.sdk._metrics.point import Gauge, Histogram, Metric, Sum
8877

8978
logger = logging.getLogger(__name__)
9079

@@ -99,14 +88,14 @@ class PrometheusMetricExporter(MetricExporter):
9988

10089
def __init__(self, prefix: str = ""):
10190
self._collector = CustomCollector(prefix)
102-
REGISTRY.register(self._collector)
91+
core.REGISTRY.register(self._collector)
10392

10493
def export(self, export_records: Sequence[Metric]) -> MetricExportResult:
10594
self._collector.add_metrics_data(export_records)
10695
return MetricExportResult.SUCCESS
10796

10897
def shutdown(self) -> None:
109-
REGISTRY.unregister(self._collector)
98+
core.REGISTRY.unregister(self._collector)
11099

111100

112101
class CustomCollector:
@@ -124,7 +113,7 @@ def __init__(self, prefix: str = ""):
124113
def add_metrics_data(self, export_records: Sequence[Metric]) -> None:
125114
self._metrics_to_export.append(export_records)
126115

127-
def collect(self):
116+
def collect(self) -> None:
128117
"""Collect fetches the metrics from OpenTelemetry
129118
and delivers them as Prometheus Metrics.
130119
Collect is invoked every time a prometheus.Gatherer is run
@@ -139,49 +128,71 @@ def collect(self):
139128
if prometheus_metric is not None:
140129
yield prometheus_metric
141130

142-
def _translate_to_prometheus(self, export_record: Metric):
131+
def _convert_buckets(self, metric: Metric) -> Sequence[Tuple[str, int]]:
132+
buckets = []
133+
total_count = 0
134+
for i in range(0, len(metric.point.bucket_counts)):
135+
total_count += metric.point.bucket_counts[i]
136+
buckets.append(
137+
(
138+
f"{metric.point.explicit_bounds[i]}",
139+
total_count,
140+
)
141+
)
142+
return buckets
143+
144+
def _translate_to_prometheus(
145+
self, metric: Metric
146+
) -> Optional[core.Metric]:
143147
prometheus_metric = None
144148
label_values = []
145149
label_keys = []
146-
for key, value in export_record.attributes.items():
150+
for key, value in metric.attributes.items():
147151
label_keys.append(self._sanitize(key))
148152
label_values.append(str(value))
149153

150154
metric_name = ""
151155
if self._prefix != "":
152156
metric_name = self._prefix + "_"
153-
metric_name += self._sanitize(export_record.name)
154-
155-
description = export_record.description or ""
156-
if isinstance(export_record.point, Sum):
157-
prometheus_metric = CounterMetricFamily(
158-
name=metric_name, documentation=description, labels=label_keys
157+
metric_name += self._sanitize(metric.name)
158+
159+
description = metric.description or ""
160+
if isinstance(metric.point, Sum):
161+
prometheus_metric = core.CounterMetricFamily(
162+
name=metric_name,
163+
documentation=description,
164+
labels=label_keys,
165+
unit=metric.unit,
159166
)
160167
prometheus_metric.add_metric(
161-
labels=label_values, value=export_record.point.value
168+
labels=label_values, value=metric.point.value
162169
)
163-
elif isinstance(export_record.point, Gauge):
164-
prometheus_metric = GaugeMetricFamily(
165-
name=metric_name, documentation=description, labels=label_keys
170+
elif isinstance(metric.point, Gauge):
171+
prometheus_metric = core.GaugeMetricFamily(
172+
name=metric_name,
173+
documentation=description,
174+
labels=label_keys,
175+
unit=metric.unit,
166176
)
167177
prometheus_metric.add_metric(
168-
labels=label_values, value=export_record.point.value
178+
labels=label_values, value=metric.point.value
179+
)
180+
elif isinstance(metric.point, Histogram):
181+
value = metric.point.sum
182+
prometheus_metric = core.HistogramMetricFamily(
183+
name=metric_name,
184+
documentation=description,
185+
labels=label_keys,
186+
unit=metric.unit,
187+
)
188+
buckets = self._convert_buckets(metric)
189+
prometheus_metric.add_metric(
190+
labels=label_values, buckets=buckets, sum_value=value
169191
)
170-
# TODO: Add support for histograms when supported in OT
171-
# elif isinstance(export_record.point, Histogram):
172-
# value = export_record.point.sum
173-
# prometheus_metric = HistogramMetricFamily(
174-
# name=metric_name,
175-
# documentation=description,
176-
# labels=label_keys,
177-
# )
178-
# prometheus_metric.add_metric(labels=label_values, buckets=export_record.point.explicit_bounds, sum_value=value)
179192
# TODO: add support for Summary once implemented
180193
# elif isinstance(export_record.point, Summary):
181194
else:
182-
logger.warning(
183-
"Unsupported metric type. %s", type(export_record.point)
184-
)
195+
logger.warning("Unsupported metric type. %s", type(metric.point))
185196
return prometheus_metric
186197

187198
def _sanitize(self, key: str) -> str:

exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@
2525
)
2626
from opentelemetry.sdk._metrics import MeterProvider
2727
from opentelemetry.sdk._metrics.export import MetricExportResult
28-
from opentelemetry.sdk._metrics.point import (
29-
AggregationTemporality,
30-
Histogram,
31-
Metric,
32-
)
28+
from opentelemetry.sdk._metrics.point import AggregationTemporality, Histogram
3329
from opentelemetry.sdk.util import get_dict_as_key
3430
from opentelemetry.test.metrictestutil import (
3531
_generate_gauge,
@@ -41,16 +37,6 @@
4137

4238
class TestPrometheusMetricExporter(unittest.TestCase):
4339
def setUp(self):
44-
set_meter_provider(MeterProvider())
45-
self._meter = get_meter_provider().get_meter(__name__)
46-
self._test_metric = self._meter.create_counter(
47-
"testname",
48-
description="testdesc",
49-
unit="unit",
50-
)
51-
labels = {"environment": "staging"}
52-
self._labels_key = get_dict_as_key(labels)
53-
5440
self._mock_registry_register = mock.Mock()
5541
self._registry_register_patch = mock.patch(
5642
"prometheus_client.core.REGISTRY.register",
@@ -82,7 +68,6 @@ def test_export(self):
8268
self.assertEqual(len(exporter._collector._metrics_to_export), 1)
8369
self.assertIs(result, MetricExportResult.SUCCESS)
8470

85-
# # TODO: Add unit test for histogram
8671
def test_histogram_to_prometheus(self):
8772
record = _generate_metric(
8873
"test@name",
@@ -94,14 +79,15 @@ def test_histogram_to_prometheus(self):
9479
explicit_bounds=[123.0, 456.0],
9580
aggregation_temporality=AggregationTemporality.DELTA,
9681
),
82+
attributes={"histo": 1},
9783
)
9884

99-
# collector = CustomCollector("testprefix")
100-
# collector.add_metrics_data([record])
101-
# result_bytes = generate_latest(collector)
102-
# result = result_bytes.decode("utf-8")
103-
# self.assertIn("testprefix_test_name_count 2.0", result)
104-
# self.assertIn("testprefix_test_name_sum 579.0", result)
85+
collector = CustomCollector("testprefix")
86+
collector.add_metrics_data([record])
87+
result_bytes = generate_latest(collector)
88+
result = result_bytes.decode("utf-8")
89+
self.assertIn('testprefix_test_name_s_sum{histo="1"} 579.0', result)
90+
self.assertIn('testprefix_test_name_s_count{histo="1"} 2.0', result)
10591

10692
def test_sum_to_prometheus(self):
10793
labels = {"environment@": "staging", "os": "Windows"}
@@ -117,7 +103,9 @@ def test_sum_to_prometheus(self):
117103

118104
for prometheus_metric in collector.collect():
119105
self.assertEqual(type(prometheus_metric), CounterMetricFamily)
120-
self.assertEqual(prometheus_metric.name, "testprefix_test_sum")
106+
self.assertEqual(
107+
prometheus_metric.name, "testprefix_test_sum_testunit"
108+
)
121109
self.assertEqual(prometheus_metric.documentation, "testdesc")
122110
self.assertTrue(len(prometheus_metric.samples) == 1)
123111
self.assertEqual(prometheus_metric.samples[0].value, 123)
@@ -143,7 +131,9 @@ def test_gauge_to_prometheus(self):
143131

144132
for prometheus_metric in collector.collect():
145133
self.assertEqual(type(prometheus_metric), GaugeMetricFamily)
146-
self.assertEqual(prometheus_metric.name, "testprefix_test_gauge")
134+
self.assertEqual(
135+
prometheus_metric.name, "testprefix_test_gauge_testunit"
136+
)
147137
self.assertEqual(prometheus_metric.documentation, "testdesc")
148138
self.assertTrue(len(prometheus_metric.samples) == 1)
149139
self.assertEqual(prometheus_metric.samples[0].value, 123)

0 commit comments

Comments
 (0)