From db889052877c96ccd0fcf4bc6a5856cd664fe924 Mon Sep 17 00:00:00 2001 From: Sai Sridhar Date: Sat, 30 May 2026 13:42:31 +0530 Subject: [PATCH] refactor(prometheus): simplify _CustomCollector by replacing deque with single MetricsData field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The callback → _receive_metrics → add_metrics_data → deque → yield chain always held at most one MetricsData per collect cycle (the callback triggers exactly one _receive_metrics call before collect drains the queue). Replace the deque[MetricsData] with an Optional[MetricsData] field and simplify collect() to consume it directly, removing the while-loop, popleft(), len() check, and the now-unused `deque` import. This fixes a latent bug where multiple items in the deque would have caused collect() to yield duplicate Prometheus metric families. Fixes #2500 --- .../exporter/prometheus/__init__.py | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py index 9b3d2efd34a..a28b98d4679 100644 --- a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py +++ b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py @@ -51,7 +51,6 @@ --- """ -from collections import deque from collections.abc import Iterable, Sequence from itertools import chain from json import dumps @@ -172,14 +171,14 @@ class _CustomCollector: def __init__(self, disable_target_info: bool = False, prefix: str = ""): self._callback = None - self._metrics_datas: deque[MetricsData] = deque() + self._metrics_data: MetricsData | None = None self._disable_target_info = disable_target_info self._target_info = None self._prefix = prefix def add_metrics_data(self, metrics_data: MetricsData) -> None: """Add metrics to Prometheus data""" - self._metrics_datas.append(metrics_data) + self._metrics_data = metrics_data def collect(self) -> Iterable[PrometheusMetric]: """Collect fetches the metrics from OpenTelemetry @@ -190,29 +189,29 @@ def collect(self) -> Iterable[PrometheusMetric]: if self._callback is not None: self._callback() + metrics_data = self._metrics_data + self._metrics_data = None + + if metrics_data is None: + return + metric_family_id_metric_family = {} - if len(self._metrics_datas): - if not self._disable_target_info: - if self._target_info is None: - attributes: Attributes = {} - for res in self._metrics_datas[0].resource_metrics: - attributes = {**attributes, **res.resource.attributes} + if not self._disable_target_info: + if self._target_info is None: + attributes: Attributes = {} + for res in metrics_data.resource_metrics: + attributes = {**attributes, **res.resource.attributes} - self._target_info = self._create_info_metric( - _TARGET_INFO_NAME, _TARGET_INFO_DESCRIPTION, attributes - ) - metric_family_id_metric_family[_TARGET_INFO_NAME] = ( - self._target_info + self._target_info = self._create_info_metric( + _TARGET_INFO_NAME, _TARGET_INFO_DESCRIPTION, attributes ) + metric_family_id_metric_family[_TARGET_INFO_NAME] = self._target_info - while self._metrics_datas: - self._translate_to_prometheus( - self._metrics_datas.popleft(), metric_family_id_metric_family - ) + self._translate_to_prometheus(metrics_data, metric_family_id_metric_family) - if metric_family_id_metric_family: - yield from metric_family_id_metric_family.values() + if metric_family_id_metric_family: + yield from metric_family_id_metric_family.values() # pylint: disable=too-many-locals,too-many-branches def _translate_to_prometheus(