Skip to content

Fix operator precedence allowing exemplars on any metric type#1188

Open
sean-kim05 wants to merge 1 commit into
prometheus:masterfrom
sean-kim05:fix/exemplar-metric-type-precedence
Open

Fix operator precedence allowing exemplars on any metric type#1188
sean-kim05 wants to merge 1 commit into
prometheus:masterfrom
sean-kim05:fix/exemplar-metric-type-precedence

Conversation

@sean-kim05

Copy link
Copy Markdown

_is_valid_exemplar_metric() in openmetrics/exposition.py guards histograms with:

if metric.type in ('histogram') and sample.name.endswith('_bucket') or sample.name == metric.name:

Since and binds tighter than or, this is evaluated as:

(metric.type in ('histogram') and sample.name.endswith('_bucket')) or (sample.name == metric.name)

so the trailing sample.name == metric.name clause runs for every metric type, not just histograms. As a result the OpenMetrics writer attaches exemplars to gauge/info/stateset/summary/untyped metrics whenever a sample name equals the metric name — output that this library's own OpenMetrics parser then rejects (only histogram/gaugehistogram buckets and counters can have exemplars).

Minimal repro:

from prometheus_client.core import Metric
from prometheus_client.registry import CollectorRegistry
from prometheus_client.openmetrics.exposition import generate_latest
from prometheus_client.samples import Exemplar

reg = CollectorRegistry()

class C:
    def collect(self):
        m = Metric("gg", "a gauge", "gauge")
        m.add_sample("gg", {}, 1.0, None, Exemplar({'a': 'b'}, 0.5))
        yield m

reg.register(C())
print(generate_latest(reg).decode())
# emits `gg 1.0 # {a="b"} 0.5` instead of raising ValueError

The fix groups the histogram condition so the same-name clause — which exists to allow native-histogram exemplars, whose sample name equals the metric name — only applies to histograms. I also changed the two metric.type in ('...') checks to ==; with a single string argument they were doing substring matching rather than the intended equality.

Added test_gauge_exemplar next to the existing untyped/non-bucket exemplar tests. It fails before the change (ValueError not raised) and passes after. Full test suite is green and flake8/isort/mypy pass.

@csmarchbanks

_is_valid_exemplar_metric() guarded histograms with

    if metric.type in ('histogram') and sample.name.endswith('_bucket') or sample.name == metric.name:

Because 'and' binds tighter than 'or', this parsed as

    (metric.type in ('histogram') and sample.name.endswith('_bucket')) or (sample.name == metric.name)

so the trailing 'sample.name == metric.name' clause fired for every
metric type. As a result the OpenMetrics writer emitted exemplars on
gauges, info, stateset, summary and untyped metrics whenever a sample
name equalled the metric name -- output that the library's own
OpenMetrics parser rejects (only histogram/gaugehistogram buckets,
counter _total, and native histograms may carry exemplars).

Group the histogram condition correctly so the same-name clause (which
exists to allow native-histogram exemplars) only applies to histograms.
While here, replace the 'metric.type in (...)' single-string membership
checks with '==' -- they were doing substring matching, not the
intended equality.

Add test_gauge_exemplar, which asserts a gauge sample carrying an
exemplar raises ValueError, matching the existing untyped/non-bucket
exemplar tests.

Signed-off-by: Sean Kim <skim8705@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant