CustomCollector shows duplicate metrics
LordKa0S opened this issue · comments
Kaustubh Badrike commented
Consider the below minimal reproduction:
from random import randint
from typing import Callable
from prometheus_client import CollectorRegistry, make_asgi_app
from prometheus_client.core import GaugeMetricFamily
from prometheus_client.registry import Collector
from uvicorn import run
def create_guage_metric_family(
name: str,
documentation: str,
):
"""
Creates a new Gauge metric with app defaults
"""
LABELNAMES = (
"foo",
"qux",
)
gauge = GaugeMetricFamily(
name=name,
documentation=documentation,
labels=LABELNAMES,
)
return gauge
def get_metric_type_1(source):
# process source.attr1
return randint(3, 97)
def get_metric_type_2(source):
# process source.attr2
return randint(4, 97)
class MyCollector(Collector):
def __init__(self) -> None:
super().__init__()
def collect(self):
type1_metric_getter_pairs: tuple[
tuple[GaugeMetricFamily, Callable[..., float]], ...
] = (
(
create_guage_metric_family(
name="my_metric1",
documentation="my_metric1", # noqa: E501
),
get_metric_type_1,
),
)
type2_metric_getter_pairs: tuple[
tuple[GaugeMetricFamily, Callable[..., float]], ...
] = (
(
create_guage_metric_family(
name="my_metric2",
documentation="my_metric2",
),
get_metric_type_2,
),
)
sources = (
{
"attr1": "source1attr1",
"attr2": "source1attr2",
},
{
"attr1": "source2attr1",
"attr2": "source2attr2",
},
)
for source in sources:
labels = tuple(source.values())
for guage_metric_family, getter in type1_metric_getter_pairs:
guage_metric_family.add_metric(
labels=labels,
value=getter(
source=source,
),
)
for guage_metric_family, getter in type2_metric_getter_pairs:
guage_metric_family.add_metric(
labels=labels,
value=getter(
source=source,
),
)
for guage_metric_family, _ in (
type1_metric_getter_pairs + type2_metric_getter_pairs
):
yield guage_metric_family
def main():
registry = CollectorRegistry()
registry.register(
collector=MyCollector(),
)
app = make_asgi_app(
registry=registry,
)
run(
app=app,
host="0.0.0.0",
port=8080,
)
if __name__ == "__main__":
main()
with the below requirements on python 3.11.7
prometheus-client==0.17.1
uvicorn==0.27.0
I expect yield
to be invoked 4 times for each collect
run, which I verified on a debugger.
However, the result is as follows, which seems to imply 6 values.
# HELP my_metric1 my_metric1
# TYPE my_metric1 gauge
my_metric1{foo="source1attr1",qux="source1attr2"} 97.0
# HELP my_metric2 my_metric2
# TYPE my_metric2 gauge
my_metric2{foo="source1attr1",qux="source1attr2"} 73.0
# HELP my_metric1 my_metric1
# TYPE my_metric1 gauge
my_metric1{foo="source1attr1",qux="source1attr2"} 97.0
my_metric1{foo="source2attr1",qux="source2attr2"} 91.0
# HELP my_metric2 my_metric2
# TYPE my_metric2 gauge
my_metric2{foo="source1attr1",qux="source1attr2"} 73.0
my_metric2{foo="source2attr1",qux="source2attr2"} 55.0
The get_metric_type_1
and get_metric_type_2
functions are correctly evaluated only once each per source
. In the result, the metrics for the first source
are present twice. I believe lines 1-6 should not exist in the result.
Kaustubh Badrike commented
Take the loop that contains yield
out of the loop iterating through sources
.