elastic / elastic/beats

[metricbeat][prometheus]: collector drops summary/histogram families that omit a `_sum` sample

Open
#52,865 2 comments 1 reaction 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.