Skip to content

Commit ac0eb56

Browse files
committed
start work on prometheus exporter
1 parent ecd03bc commit ac0eb56

File tree

1 file changed

+50
-8
lines changed
  • exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus

1 file changed

+50
-8
lines changed

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

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
from json import dumps
6868
from logging import getLogger
6969
from os import environ
70-
from typing import Deque, Dict, Iterable, Sequence, Tuple, Union
70+
from typing import Deque, Dict, Iterable, Sequence, Tuple, Union, Optional
7171

7272
from prometheus_client import start_http_server
7373
from prometheus_client.core import (
@@ -107,6 +107,9 @@
107107
Sum,
108108
)
109109
from opentelemetry.util.types import Attributes
110+
from opentelemetry.sdk.metrics._internal import Exemplar
111+
from prometheus_client.samples import Exemplar as PrometheusExemplar
112+
110113

111114
_logger = getLogger(__name__)
112115

@@ -115,17 +118,29 @@
115118

116119

117120
def _convert_buckets(
118-
bucket_counts: Sequence[int], explicit_bounds: Sequence[float]
119-
) -> Sequence[Tuple[str, int]]:
121+
bucket_counts: Sequence[int], explicit_bounds: Sequence[float], exemplars: Sequence[Optional[PrometheusExemplar]] = None
122+
) -> Sequence[Tuple[str, int, Optional[Exemplar]]]:
120123
buckets = []
121124
total_count = 0
125+
previous_bound = float('-inf')
126+
122127
for upper_bound, count in zip(
123128
chain(explicit_bounds, ["+Inf"]),
124129
bucket_counts,
125130
):
126131
total_count += count
127-
buckets.append((f"{upper_bound}", total_count))
128-
132+
buckets.append((f"{upper_bound}", total_count, None))
133+
134+
# assigning exemplars to their corresponding values
135+
if exemplars:
136+
for i, (upper_bound, _, _) in enumerate(buckets):
137+
for exemplar in exemplars:
138+
if previous_bound <= exemplar.value < float(upper_bound):
139+
# Assign the exemplar to the current bucket if it's the first valid one found
140+
_, current_count, current_exemplar = buckets[i]
141+
if current_exemplar is None: # Only assign if no exemplar has been assigned yet
142+
buckets[i] = (upper_bound, current_count, exemplar)
143+
previous_bound = float(upper_bound)
129144
return buckets
130145

131146

@@ -251,6 +266,10 @@ def _translate_to_prometheus(
251266
for number_data_point in metric.data.data_points:
252267
label_keys = []
253268
label_values = []
269+
exemplars = [
270+
self._convert_exemplar(ex) for ex in number_data_point.exemplars
271+
]
272+
254273

255274
for key, value in sorted(number_data_point.attributes.items()):
256275
label_keys.append(sanitize_attribute(key))
@@ -276,6 +295,7 @@ def _translate_to_prometheus(
276295
number_data_point.explicit_bounds
277296
),
278297
"sum": number_data_point.sum,
298+
"exemplars": exemplars,
279299
}
280300
)
281301
else:
@@ -350,7 +370,7 @@ def _translate_to_prometheus(
350370
[pre_metric_family_id, HistogramMetricFamily.__name__]
351371
)
352372

353-
if (
373+
if (
354374
metric_family_id
355375
not in metric_family_id_metric_family.keys()
356376
):
@@ -359,15 +379,15 @@ def _translate_to_prometheus(
359379
name=metric_name,
360380
documentation=metric_description,
361381
labels=label_keys,
362-
unit=metric_unit,
382+
unit=metric_unit,
363383
)
364384
)
365385
metric_family_id_metric_family[
366386
metric_family_id
367387
].add_metric(
368388
labels=label_values,
369389
buckets=_convert_buckets(
370-
value["bucket_counts"], value["explicit_bounds"]
390+
value["bucket_counts"], value["explicit_bounds"], value["exemplars"]
371391
),
372392
sum_value=value["sum"],
373393
)
@@ -395,7 +415,29 @@ def _create_info_metric(
395415
info = InfoMetricFamily(name, description, labels=attributes)
396416
info.add_metric(labels=list(attributes.keys()), value=attributes)
397417
return info
418+
419+
def _convert_exemplar(self, exemplar_data: Exemplar) -> PrometheusExemplar:
420+
"""
421+
Converts the SDK exemplar into a Prometheus Exemplar, including proper time conversion.
398422
423+
Parameters:
424+
- value (float): The value associated with the exemplar.
425+
- exemplar_data (ExemplarData): An OpenTelemetry exemplar data object containing attributes and timing information.
426+
427+
Returns:
428+
- Exemplar: A Prometheus Exemplar object with correct labeling and timing.
429+
"""
430+
labels = {self._sanitize_label(key): str(value) for key, value in exemplar_data.filtered_attributes.items()}
431+
432+
# Add trace_id and span_id to labels only if they are valid and not None
433+
if exemplar_data.trace_id and exemplar_data.span_id:
434+
labels['trace_id'] = exemplar_data.trace_id
435+
labels['span_id'] = exemplar_data.span_id
436+
437+
# Convert time from nanoseconds to seconds
438+
timestamp_seconds = exemplar_data.time_unix_nano / 1e9
439+
prom_exemplar = PrometheusExemplar(labels, exemplar_data.value, timestamp_seconds)
440+
return prom_exemplar
399441

400442
class _AutoPrometheusMetricReader(PrometheusMetricReader):
401443
"""Thin wrapper around PrometheusMetricReader used for the opentelemetry_metrics_exporter entry point.

0 commit comments

Comments
 (0)