[metricbeat][prometheus]: collector drops summary/histogram families that omit a `_sum` sample
- Dominant language
- Go
- Stars
- 12.7k
- Forks
- 5k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 364
Description
> **Note:** This report captures the current findings. Details such as the
> proposed fix, the backport decision (including whether to target `8.19.x`), and
> the exact handling of edge cases (for example `_total` under a summary) are
> preliminary and may change based on our review and discussion.
## Summary
The Metricbeat Prometheus text parser (`metricbeat/helper/prometheus`) silently
drops any `summary` or `histogram` family that exposes a `_count` and/or
quantiles/buckets but does **not** emit a `_sum` sample. The whole family is
discarded before it reaches the collector, so those metrics never get indexed
and no error is logged.
A `_sum` is **optional** for a summary in the OpenMetrics / Prometheus text
format, so dropping the family when it is absent is stricter than the spec and
loses valid data.
This is a regression. The 7.17 line (and 8.0 through 8.6.x) collected these
metrics.
## Affected versions
- **Broken:** `8.7.0` and later (all `8.x` from `8.7.0` onward, and `9.x`).
- **Not affected:** `7.x` and `8.0` through `8.6.x`.
The behavior changed when the Prometheus parsing helper was rewritten from the
Prometheus `expfmt` decoder to a self-written `textparse`-based parser in
https://github.com/elastic/beats/pull/33865 (first released in `8.7.0`). The
`expfmt`-based parser used before `8.7.0` kept these families.
## Root cause
In `ParseMetricFamilies`, a `summary`/`histogram` family is only registered when
a sample name ending in `_sum` is seen. Otherwise the accumulated
`_count`/quantiles/buckets are never appended and the family is filtered out at
the end of parsing:
https://github.com/elastic/beats/blob/5dbdae9469e57d40a3466416768b64334c1a0677/metricbeat/helper/prometheus/textparse.go#L716
```go
case model.MetricTypeSummary:
lookupMetricName, metric = summaryMetricName(...)
metric.Label = labelPairs
if !isSum(metricName) {
// Avoid registering the metric multiple times.
continue
}
```
The histogram case has the identical `if !isSum(metricName) { continue }` gate.
When an exporter never emits `_sum`, the loop always `continue`s and the family
ends up with no metrics, so it is dropped.
## Steps to reproduce
1. Serve the following exposition from any endpoint (classic
`text/plain; version=0.0.4`):
```
# TYPE rpc_duration_seconds summary
rpc_duration_seconds{quantile="0.5"} 2
rpc_duration_seconds{quantile="0.95"} 5
rpc_duration_seconds_count 100
```
2. Scrape it with the `prometheus/collector` metricset.
### Expected
A `summary` family `rpc_duration_seconds` is indexed with its count and
quantiles (e.g. `prometheus.metrics.rpc_duration_seconds_count`).
### Actual
The `rpc_duration_seconds` family is missing entirely from the output on
`8.7.0` and later. There is no error or warning. The same scrape on `7.17` (or
`8.6.x`) indexes it.
`gauge`/`counter` families on the same endpoint are unaffected, which is why a
scrape can still produce some documents while the summary/histogram families
disappear.
## Why this is a spec bug (not just a 7.17 behavior diff)
Per the OpenMetrics specification, a summary's Sum is optional:
> "A Summary MetricPoint MAY consist of a Count, Sum, Created, and a set of
> quantiles."
> "If present, the MetricPoint's Sum Value Sample MetricName MUST have the suffix
> "_sum". If present, the MetricPoint's Count Value Sample MetricName MUST have
> the suffix "_count"."
Source: https://github.com/prometheus/OpenMetrics/blob/main/specification/OpenMetrics.md#summary
So a summary exposing only `_count` and quantiles (no `_sum`) is spec-valid and
must still be indexed. Requiring `_sum` to emit the family is stricter than the
format requires.
Note: some exporters additionally emit a `_total` sample **under** a `summary`
family. Per the spec, the valid summary suffixes are `_count`, `_sum`,
`_created`, and empty, while `_total` is a counter suffix. So that specific
sample is non-compliant and a correct parser may legitimately ignore it, but it
must not cause the valid `_count`/quantiles to be dropped.
## Proposed fix
In `metricbeat/helper/prometheus/textparse.go`:
1. Stop gating summary/histogram registration on `_sum`. Accumulate the samples
and register each `(family, labelset)` once at end-of-parse, so families that
omit `_sum` still surface with their `_count`/quantiles/buckets.
2. When flushing, skip malformed sub-series that only produced a sentinel
quantile/bucket (e.g. an unrecognized suffix, or a `_total` published under a
summary), matching the pre-existing behavior of not surfacing those.
3. Use a stable, quantile/`le`-free label set for the flushed family metric so
the per-sample `quantile`/`le` label does not leak onto the family.
Gauge histograms should keep their existing inline registration and be excluded
from the flush to avoid double-registration.
## Backport
The regression spans `8.7.0` through current. `8.7` through `8.18` are EOL, so
realistic backport targets are the active `8.19` maintenance branch and `main`
(`9.x`).
Note the restored output is not byte-for-byte identical to `7.17`. 7.17 was
lenient (for example it surfaced `_total` as its own field and substituted
`_sum = 0`); the fix indexes the spec-valid `_count`/quantiles rather than
reproducing 7.17 bug-for-bug.
## Related issues
- https://github.com/elastic/integrations/issues/9265 : same root cause, open.
- https://github.com/elastic/beats/issues/37376 : earlier report of the same
gap, closed without a fix.
>
Contributor guide
Research direction
Start in metricbeat/helper/prometheus/textparse.go at ParseMetricFamilies and the summary/histogram handling around the linked lines. Reproduce the no-_sum summary with the exposition shown in the issue, then trace registration and flushing for quantiles, buckets, and malformed suffixes. Done means spec-valid families without _sum are indexed, invalid sub-series remain ignored, and gauge histograms are not double-registered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, prometheus
- Domain
- observability
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100