prometheus / client_python

Prometheus instrumentation library for Python applications

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CustomCollector shows duplicate metrics

LordKa0S opened this issue · comments

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.

Take the loop that contains yield out of the loop iterating through sources.