From 54c7ee80abc9c6edefa52169ba8904deaf7e7491 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 30 Oct 2024 19:43:33 +0100 Subject: [PATCH] exporter/prometheus_remote_write: sort labels before exporting (#2940) --- CHANGELOG.md | 2 ++ .../prometheus_remote_write/__init__.py | 8 +++-- .../test_prometheus_remote_write_exporter.py | 34 +++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a34bae1f..47191002dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#2922](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2922)) - `opentelemetry-instrumentation-celery` Don't detach context without a None token ([#2927](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2927)) +- `opentelemetry-exporter-prometheus-remote-write`: sort labels before exporting + ([#2940](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2940)) - `opentelemetry-instrumentation-dbapi` sqlcommenter key values created from PostgreSQL, MySQL systems ([#2897](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2897)) diff --git a/exporter/opentelemetry-exporter-prometheus-remote-write/src/opentelemetry/exporter/prometheus_remote_write/__init__.py b/exporter/opentelemetry-exporter-prometheus-remote-write/src/opentelemetry/exporter/prometheus_remote_write/__init__.py index 78b8516a46..cfb1d9ea75 100644 --- a/exporter/opentelemetry-exporter-prometheus-remote-write/src/opentelemetry/exporter/prometheus_remote_write/__init__.py +++ b/exporter/opentelemetry-exporter-prometheus-remote-write/src/opentelemetry/exporter/prometheus_remote_write/__init__.py @@ -16,7 +16,7 @@ import re from collections import defaultdict from itertools import chain -from typing import Dict, Sequence +from typing import Dict, Mapping, Sequence import requests import snappy @@ -253,12 +253,14 @@ def _parse_metric( return self._convert_to_timeseries(sample_sets, resource_labels) def _convert_to_timeseries( - self, sample_sets: Sequence[tuple], resource_labels: Sequence + self, sample_sets: Mapping[tuple, Sequence], resource_labels: Sequence ) -> Sequence[TimeSeries]: timeseries = [] for labels, samples in sample_sets.items(): ts = TimeSeries() - for label_name, label_value in chain(resource_labels, labels): + for label_name, label_value in sorted( + chain(resource_labels, labels) + ): # Previous implementation did not str() the names... ts.labels.append(self._label(label_name, str(label_value))) for value, timestamp in samples: diff --git a/exporter/opentelemetry-exporter-prometheus-remote-write/tests/test_prometheus_remote_write_exporter.py b/exporter/opentelemetry-exporter-prometheus-remote-write/tests/test_prometheus_remote_write_exporter.py index 785c6bdc29..1c50344353 100644 --- a/exporter/opentelemetry-exporter-prometheus-remote-write/tests/test_prometheus_remote_write_exporter.py +++ b/exporter/opentelemetry-exporter-prometheus-remote-write/tests/test_prometheus_remote_write_exporter.py @@ -22,6 +22,8 @@ PrometheusRemoteWriteMetricsExporter, ) from opentelemetry.exporter.prometheus_remote_write.gen.types_pb2 import ( # pylint: disable=E0611 + Label, + Sample, TimeSeries, ) from opentelemetry.sdk.metrics.export import ( @@ -155,6 +157,38 @@ def test_parse_metric(metric, prom_rw): assert sample.value in values +def test_convert_to_timeseries(prom_rw): + resource_labels = (("service_name", "foo"), ("bool_value", True)) + sample_sets = { + (("foo", "bar"), ("baz", 42), ("__name__", "test_histogram_tu")): [ + (1, 1641946016139) + ], + (("baz", "42"), ("foo", "bar")): [(4, 1641946016139)], + } + timeseries = prom_rw._convert_to_timeseries(sample_sets, resource_labels) + assert timeseries == [ + TimeSeries( + labels=[ + Label(name="__name__", value="test_histogram_tu"), + Label(name="baz", value="42"), + Label(name="bool_value", value="True"), + Label(name="foo", value="bar"), + Label(name="service_name", value="foo"), + ], + samples=[Sample(value=1, timestamp=1641946016139)], + ), + TimeSeries( + labels=[ + Label(name="baz", value="42"), + Label(name="bool_value", value="True"), + Label(name="foo", value="bar"), + Label(name="service_name", value="foo"), + ], + samples=[Sample(value=4, timestamp=1641946016139)], + ), + ] + + class TestValidation(unittest.TestCase): # Test cases to ensure exporter parameter validation works as intended def test_valid_standard_param(self):