Skip to content

fix: bound observation buffering during collection - #2336

Open
zeitlinger wants to merge 7 commits into
mainfrom
agent/bound-observation-buffer
Open

fix: bound observation buffering during collection#2336
zeitlinger wants to merge 7 commits into
mainfrom
agent/bound-observation-buffer

Conversation

@zeitlinger

@zeitlinger zeitlinger commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

  • bound the collection spin wait and observation buffer
  • coordinate collection through explicit buffer generations and phase transitions
  • replay buffered observations before surfacing a collection timeout
  • skip timed-out native-histogram maintenance rather than throwing from observe()
  • document the generation handoff and striped-count protocol

This is stacked on the focused #2282 fix so the two Buffer concerns can be reviewed separately. It replaces the #2287 portion of #2297.

Fixes #2287

Ongoing discussion

  • Should the five-second collection deadline remain an internal default, or become publicly configurable? See the earlier discussion.
  • The bounded buffer currently applies backpressure rather than dropping observations. The earlier review suggested drop-on-overflow; this version avoids silent data loss but can block an observer while the generation remains active. See the earlier discussion.
  • The generation/phase handoff is a substantial concurrency change and should be reviewed independently of the simple stripe-index fix.

The earlier concerns about an unbounded second wait and exceptions escaping from native-histogram maintenance are addressed by this implementation.

Validation

  • mise run lint:fix
  • mise run build
  • ./mvnw test -pl prometheus-metrics-core -Dcoverage.skip=true -Dcheckstyle.skip=true

Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ API changes detected — maintainer review required

This PR modifies the published API diff for the following module(s):

  • prometheus-metrics-core

Please review the changes in docs/apidiffs/current_vs_latest/ carefully before approving.

Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Base automatically changed from agent/fix-buffer-stripe-index to main July 31, 2026 12:47
@zeitlinger
zeitlinger marked this pull request as ready for review August 18, 2026 08:38

@jaydeluca jaydeluca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I think block merge:

  1. The ==>= change is hiding a real accounting bug. The phase
    gate in append() created a class of observation that increments its stripe after the
    collector's getAndAdd — so it is excluded from expectedCount — yet still increments count.
    main could not produce that, which is why == was exact there and hangs here. Normally >=
    absorbs it, but when maybeReset() calls count.reset() the offset absorbs only the snapshot,
    not that observation, so expectedCount stays permanently one too high. I reproduced a
    permanently failing collect() (details inline on Buffer.java).
  2. observeDirect() takes a lock on every observation in steady state. JMH from
    HistogramBenchmark, both trees built from source (-f 1 -wi 3 -i 5 -r 2s -w 2s):
    prometheusClassicSingleThread −38%, prometheusClassicPerThread −44% (uncontended!),
    prometheusClassic −72%, prometheusNative −73%. The exclusion is only needed while a
    collection is in flight.

Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

Benchmark run succeeded for 4d69da18904fe3104da063c3392e427eed18540d.

Prometheus Java Client Benchmarks

Run Information

  • Date: 2026-08-27T21:07:07Z
  • Commit: 4d69da1
  • JDK: 25.0.3 (OpenJDK 64-Bit Server VM)
  • Benchmark config: 3 fork(s), 3 warmup, 5 measurement, 4 threads
  • Hardware: AMD EPYC 9V74 80-Core Processor, 4 cores, 16 GB RAM
  • OS: Linux 6.17.0-1022-azure

Comparison with base

  • Head: 4d69da1
  • Base: 6f88666
  • Metric direction: Throughput scores are higher-is-better; positive Head vs base deltas indicate faster performance.
  • Note: Base and head are compared on the same runner within each topic.
Benchmark PR Base Head vs base Regression verdict
CounterBenchmark.codahaleIncNoLabels 47.20K 47.52K -0.7% within noise
CounterBenchmark.openTelemetryAdd 13.00K 12.89K +0.8% within noise
CounterBenchmark.openTelemetryInc 14.36K 15.28K -6.0% meaningful regression
CounterBenchmark.openTelemetryIncNoLabels 18.54K 18.33K +1.2% within noise
CounterBenchmark.prometheusAdd 51.23K 51.38K -0.3% within noise
CounterBenchmark.prometheusInc 64.49K 65.36K -1.3% within noise
CounterBenchmark.prometheusNoLabelsInc 56.46K 56.01K +0.8% within noise
CounterBenchmark.simpleclientAdd 6.17K 6.29K -2.0% within noise
CounterBenchmark.simpleclientInc 6.57K 6.56K +0.2% within noise
CounterBenchmark.simpleclientNoLabelsInc 6.45K 6.34K +1.8% within noise
HistogramBenchmark.openTelemetryClassic 785.97 824.21 -4.6% within noise
HistogramBenchmark.openTelemetryExponential 688.04 706.76 -2.6% within noise
HistogramBenchmark.prometheusClassic 2.27K 4.85K -53.3% meaningful regression
HistogramBenchmark.prometheusClassicPerThread 12.04K 14.02K -14.2% meaningful regression
HistogramBenchmark.prometheusClassicSingleThread 4.93K 5.81K -15.2% meaningful regression
HistogramBenchmark.prometheusNative 2.00K 2.90K -31.0% meaningful regression
HistogramBenchmark.simpleclient 4.52K 4.56K -0.8% within noise
HistogramTextFormatBenchmark.openMetricsWriteToNull 27.17K 27.36K -0.7% within noise
HistogramTextFormatBenchmark.prometheusWriteToNull 27.29K 27.33K -0.1% within noise
TextFormatUtilBenchmark.openMetricsWriteToByteArray 532.91K 531.01K +0.4% within noise
TextFormatUtilBenchmark.openMetricsWriteToNull 545.70K 537.78K +1.5% within noise
TextFormatUtilBenchmark.prometheusWriteToByteArray 562.93K 557.88K +0.9% within noise
TextFormatUtilBenchmark.prometheusWriteToNull 571.44K 570.17K +0.2% within noise

Results for PR head

CounterBenchmark

Benchmark Score Error Units
prometheusInc 64.49K ± 1.72K ops/s
prometheusNoLabelsInc 56.46K ± 650.21 ops/s
prometheusAdd 51.23K ± 126.97 ops/s
codahaleIncNoLabels 47.20K ± 2.19K ops/s
openTelemetryIncNoLabels 18.54K ± 34.46 ops/s
openTelemetryInc 14.36K ± 452.00 ops/s
openTelemetryAdd 13.00K ± 36.57 ops/s
simpleclientInc 6.57K ± 45.17 ops/s
simpleclientNoLabelsInc 6.45K ± 143.63 ops/s
simpleclientAdd 6.17K ± 178.23 ops/s

HistogramBenchmark

Benchmark Score Error Units
prometheusClassicPerThread 12.04K ± 71.27 ops/s
prometheusClassicSingleThread 4.93K ± 8.89 ops/s
simpleclient 4.52K ± 31.49 ops/s
prometheusClassic 2.27K ± 84.87 ops/s
prometheusNative 2.00K ± 31.01 ops/s
openTelemetryClassic 785.97 ± 67.68 ops/s
openTelemetryExponential 688.04 ± 42.11 ops/s

HistogramTextFormatBenchmark

Benchmark Score Error Units
prometheusWriteToNull 27.29K ± 436.88 ops/s
openMetricsWriteToNull 27.17K ± 244.04 ops/s

TextFormatUtilBenchmark

Benchmark Score Error Units
prometheusWriteToNull 571.44K ± 16.00K ops/s
prometheusWriteToByteArray 562.93K ± 7.45K ops/s
openMetricsWriteToNull 545.70K ± 11.19K ops/s
openMetricsWriteToByteArray 532.91K ± 5.00K ops/s

Raw Results

Benchmark                                            Mode  Cnt          Score        Error  Units
CounterBenchmark.codahaleIncNoLabels                thrpt   15      47196.671   ± 2189.610  ops/s
CounterBenchmark.openTelemetryAdd                   thrpt   15      12997.098     ± 36.566  ops/s
CounterBenchmark.openTelemetryInc                   thrpt   15      14364.208    ± 451.997  ops/s
CounterBenchmark.openTelemetryIncNoLabels           thrpt   15      18543.096     ± 34.456  ops/s
CounterBenchmark.prometheusAdd                      thrpt   15      51225.422    ± 126.971  ops/s
CounterBenchmark.prometheusInc                      thrpt   15      64491.209   ± 1717.486  ops/s
CounterBenchmark.prometheusNoLabelsInc              thrpt   15      56455.152    ± 650.207  ops/s
CounterBenchmark.simpleclientAdd                    thrpt   15       6169.579    ± 178.226  ops/s
CounterBenchmark.simpleclientInc                    thrpt   15       6568.868     ± 45.174  ops/s
CounterBenchmark.simpleclientNoLabelsInc            thrpt   15       6448.782    ± 143.629  ops/s
HistogramBenchmark.openTelemetryClassic             thrpt   15        785.967     ± 67.676  ops/s
HistogramBenchmark.openTelemetryExponential         thrpt   15        688.038     ± 42.110  ops/s
HistogramBenchmark.prometheusClassic                thrpt   15       2266.753     ± 84.866  ops/s
HistogramBenchmark.prometheusClassicPerThread       thrpt   15      12037.577     ± 71.266  ops/s
HistogramBenchmark.prometheusClassicSingleThread    thrpt   15       4925.646      ± 8.888  ops/s
HistogramBenchmark.prometheusNative                 thrpt   15       2002.398     ± 31.014  ops/s
HistogramBenchmark.simpleclient                     thrpt   15       4522.238     ± 31.493  ops/s
HistogramTextFormatBenchmark.openMetricsWriteToNull  thrpt   15      27166.413    ± 244.040  ops/s
HistogramTextFormatBenchmark.prometheusWriteToNull  thrpt   15      27293.562    ± 436.884  ops/s
TextFormatUtilBenchmark.openMetricsWriteToByteArray  thrpt   15     532907.007   ± 5001.506  ops/s
TextFormatUtilBenchmark.openMetricsWriteToNull      thrpt   15     545698.856  ± 11187.940  ops/s
TextFormatUtilBenchmark.prometheusWriteToByteArray  thrpt   15     562931.475   ± 7454.217  ops/s
TextFormatUtilBenchmark.prometheusWriteToNull       thrpt   15     571439.885  ± 15996.286  ops/s

Notes

  • Score = the JMH primary metric; throughput is higher-is-better and latency is lower-is-better.
  • Error = 99.9% confidence interval
  • Regression verdict requires comparable benchmark metadata, non-overlapping JMH confidence intervals, and a change of at least 5%; otherwise it is marked "within noise" or "inconclusive".
  • Scores for different benchmark methods are not ranked against one another; they may measure different workloads.

Benchmark Descriptions

Benchmark Description
CounterBenchmark Counter increment performance: Prometheus, OpenTelemetry, simpleclient, Codahale
HistogramBenchmark Histogram observation performance (classic vs native/exponential)
TextFormatUtilBenchmark Metric exposition format writing speed

Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

# Security Report: Unbounded Observation Buffer Growth During Stalled Scrape in prometheus/client_java

2 participants