diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index dfa158b9..f50e48ff 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -1,3 +1,4 @@ +import collections import os from threading import Lock import time @@ -780,6 +781,10 @@ def __init__(self, raise ValueError(f'Overlapping labels for Enum metric: {name}') if not states: raise ValueError(f'No states provided for Enum metric: {name}') + # A repeated state is exposed as two samples with the same labels. + duplicates = sorted(s for s, count in collections.Counter(states).items() if count > 1) + if duplicates: + raise ValueError(f'Duplicate states for Enum metric {name}: {duplicates!r}') super().__init__( name=name, documentation=documentation, diff --git a/tests/test_core.py b/tests/test_core.py index 2c29f79a..3a0c9559 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -596,6 +596,12 @@ def test_overlapping_labels(self): with pytest.raises(ValueError): Enum('e', 'help', registry=None, labelnames=['e']) + def test_duplicate_states(self): + registry = CollectorRegistry() + with self.assertRaisesRegex(ValueError, r"^Duplicate states for Enum metric e: \['a'\]$"): + Enum('e', 'help', states=['a', 'b', 'a'], registry=registry) + self.assertEqual(list(registry.collect()), []) + def test_failed_init_does_not_pollute_registry(self): registry = CollectorRegistry() # A validation failure in __init__ must not leave a half-built collector