Skip to content

Commit 1373f7a

Browse files
committed
feat: exporting resources attributes on target_info (optional)
1 parent 4d0de54 commit 1373f7a

File tree

2 files changed

+82
-9
lines changed

2 files changed

+82
-9
lines changed

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
CounterMetricFamily,
7575
GaugeMetricFamily,
7676
HistogramMetricFamily,
77+
InfoMetricFamily,
7778
)
7879
from prometheus_client.core import Metric as PrometheusMetric
7980

@@ -97,6 +98,9 @@
9798

9899
_logger = getLogger(__name__)
99100

101+
TARGET_INFO_NAME = "target"
102+
TARGET_INFO_DESCRIPTION = "Target metadata"
103+
100104

101105
def _convert_buckets(
102106
bucket_counts: Sequence[int], explicit_bounds: Sequence[float]
@@ -116,8 +120,7 @@ def _convert_buckets(
116120
class PrometheusMetricReader(MetricReader):
117121
"""Prometheus metric exporter for OpenTelemetry."""
118122

119-
def __init__(self) -> None:
120-
123+
def __init__(self, disable_target_info: bool = False) -> None:
121124
super().__init__(
122125
preferred_temporality={
123126
Counter: AggregationTemporality.CUMULATIVE,
@@ -128,7 +131,7 @@ def __init__(self) -> None:
128131
ObservableGauge: AggregationTemporality.CUMULATIVE,
129132
}
130133
)
131-
self._collector = _CustomCollector()
134+
self._collector = _CustomCollector(disable_target_info)
132135
REGISTRY.register(self._collector)
133136
self._collector._callback = self.collect
134137

@@ -153,12 +156,14 @@ class _CustomCollector:
153156
https://github.com/prometheus/client_python#custom-collectors
154157
"""
155158

156-
def __init__(self):
159+
def __init__(self, disable_target_info: bool = False):
157160
self._callback = None
158161
self._metrics_datas = deque()
159162
self._non_letters_digits_underscore_re = compile(
160163
r"[^\w]", UNICODE | IGNORECASE
161164
)
165+
self._disable_target_info = disable_target_info
166+
self._target_info = None
162167

163168
def add_metrics_data(self, metrics_data: MetricsData) -> None:
164169
"""Add metrics to Prometheus data"""
@@ -175,6 +180,20 @@ def collect(self) -> None:
175180

176181
metric_family_id_metric_family = {}
177182

183+
if len(self._metrics_datas) != 0:
184+
if not self._disable_target_info:
185+
if self._target_info is None:
186+
attributes = {}
187+
for res in self._metrics_datas[0].resource_metrics:
188+
attributes = {**attributes, **res.resource.attributes}
189+
190+
self._target_info = self._create_info_metric(
191+
TARGET_INFO_NAME, TARGET_INFO_DESCRIPTION, attributes
192+
)
193+
metric_family_id_metric_family[
194+
TARGET_INFO_NAME
195+
] = self._target_info
196+
178197
while self._metrics_datas:
179198
self._translate_to_prometheus(
180199
self._metrics_datas.popleft(), metric_family_id_metric_family
@@ -327,3 +346,11 @@ def _check_value(self, value: Union[int, float, str, Sequence]) -> str:
327346
if not isinstance(value, str):
328347
return dumps(value, default=str)
329348
return str(value)
349+
350+
def _create_info_metric(
351+
self, name: str, description: str, attributes: Dict[str, str]
352+
) -> InfoMetricFamily:
353+
"""Create an Info Metric Family with list of attributes"""
354+
i = InfoMetricFamily(name, description, labels=attributes)
355+
i.add_metric(labels=list(attributes.keys()), value=attributes)
356+
return i

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

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
from unittest.mock import Mock, patch
1818

1919
from prometheus_client import generate_latest
20-
from prometheus_client.core import CounterMetricFamily, GaugeMetricFamily
20+
from prometheus_client.core import (
21+
CounterMetricFamily,
22+
GaugeMetricFamily,
23+
InfoMetricFamily,
24+
)
2125

2226
from opentelemetry.exporter.prometheus import (
2327
PrometheusMetricReader,
@@ -33,6 +37,7 @@
3337
ResourceMetrics,
3438
ScopeMetrics,
3539
)
40+
from opentelemetry.sdk.resources import Resource
3641
from opentelemetry.test.metrictestutil import (
3742
_generate_gauge,
3843
_generate_sum,
@@ -101,7 +106,7 @@ def test_histogram_to_prometheus(self):
101106
]
102107
)
103108

104-
collector = _CustomCollector()
109+
collector = _CustomCollector(disable_target_info=True)
105110
collector.add_metrics_data(metrics_data)
106111
result_bytes = generate_latest(collector)
107112
result = result_bytes.decode("utf-8")
@@ -146,7 +151,7 @@ def test_sum_to_prometheus(self):
146151
]
147152
)
148153

149-
collector = _CustomCollector()
154+
collector = _CustomCollector(disable_target_info=True)
150155
collector.add_metrics_data(metrics_data)
151156

152157
for prometheus_metric in collector.collect():
@@ -189,7 +194,7 @@ def test_gauge_to_prometheus(self):
189194
]
190195
)
191196

192-
collector = _CustomCollector()
197+
collector = _CustomCollector(disable_target_info=True)
193198
collector.add_metrics_data(metrics_data)
194199

195200
for prometheus_metric in collector.collect():
@@ -251,7 +256,7 @@ def test_list_labels(self):
251256
)
252257
]
253258
)
254-
collector = _CustomCollector()
259+
collector = _CustomCollector(disable_target_info=True)
255260
collector.add_metrics_data(metrics_data)
256261

257262
for prometheus_metric in collector.collect():
@@ -293,3 +298,44 @@ def test_multiple_collection_calls(self):
293298
result_2 = list(metric_reader._collector.collect())
294299
self.assertEqual(result_0, result_1)
295300
self.assertEqual(result_1, result_2)
301+
302+
def test_target_info_enabled_by_default(self):
303+
metric_reader = PrometheusMetricReader()
304+
provider = MeterProvider(
305+
metric_readers=[metric_reader], resource=Resource({"os": "Unix", "histo": 1})
306+
)
307+
meter = provider.get_meter("getting-started", "0.1.2")
308+
counter = meter.create_counter("counter")
309+
counter.add(1)
310+
result = list(metric_reader._collector.collect())
311+
312+
for prometheus_metric in result[:0]:
313+
self.assertEqual(type(prometheus_metric), InfoMetricFamily)
314+
self.assertEqual(prometheus_metric.name, "target")
315+
self.assertEqual(
316+
prometheus_metric.documentation, "Target metadata"
317+
)
318+
self.assertTrue(len(prometheus_metric.samples) == 1)
319+
self.assertEqual(prometheus_metric.samples[0].value, 1)
320+
self.assertTrue(len(prometheus_metric.samples[0].labels) == 2)
321+
self.assertEqual(prometheus_metric.samples[0].labels["os"], "Unix")
322+
self.assertEqual(prometheus_metric.samples[0].labels["histo"], "1")
323+
324+
def test_target_info_disabled(self):
325+
metric_reader = PrometheusMetricReader(disable_target_info=True)
326+
provider = MeterProvider(
327+
metric_readers=[metric_reader], resource=Resource({"os": "Unix", "histo": 1})
328+
)
329+
meter = provider.get_meter("getting-started", "0.1.2")
330+
counter = meter.create_counter("counter")
331+
counter.add(1)
332+
result = list(metric_reader._collector.collect())
333+
334+
for prometheus_metric in result:
335+
self.assertNotEquals(type(prometheus_metric), InfoMetricFamily)
336+
self.assertNotEquals(prometheus_metric.name, "target")
337+
self.assertNotEquals(
338+
prometheus_metric.documentation, "Target metadata"
339+
)
340+
self.assertNotIn("os", prometheus_metric.samples[0].labels)
341+
self.assertNotIn("histo", prometheus_metric.samples[0].labels)

0 commit comments

Comments
 (0)