Skip to content

goodhistogram: spell out the native histogram bounds table - #10

Open
angles-n-daemons wants to merge 1 commit into
mainfrom
bdillmann/portable-bounds-table
Open

goodhistogram: spell out the native histogram bounds table#10
angles-n-daemons wants to merge 1 commit into
mainfrom
bdillmann/portable-bounds-table

Conversation

@angles-n-daemons

Copy link
Copy Markdown
Contributor

nativeHistogramBounds was computed at init time with math.Pow:

bounds[j] = math.Ldexp(math.Pow(2, float64(j)/float64(bucketsPerGroup)), -1)

math.Pow is not bit-portable — Go ships architecture-specific
implementations — so the table comes out one ULP different on arm64 than on
amd64, and matches Prometheus's published table on neither. (math.Ldexp is
exact, so all of the drift originates in that one call.)

entry arm64 amd64 Prometheus
schema 1 [1] 0.7071067811865476 0.7071067811865476 0.7071067811865475
schema 2 [3] 0.8408964152537146 0.8408964152537145 0.8408964152537144

The magnitude is irrelevant; the identity is not. getLe derives the classic
Prometheus bucket upper bounds from this table, and those are exported as le
label values. Prometheus keys time series on the exact label string, so a
boundary that varies by architecture splits one bucket into two series across a
mixed-architecture deployment, and histogram_quantile/rate aggregations see
half the counts in each. It also makes any golden-file test of the exported
format architecture-dependent — which is how this surfaced.

Transcribe the table from client_golang instead, which spells the values out
as literals for exactly this reason. Boundaries are now bit-identical across
architectures and identical to Prometheus's own, so the classic buckets we
export line up with what Prometheus computes for the same native bucket.

The literal is typed [maxSchema + 1][]float64 rather than [][]float64 so the
compiler enforces that the table covers every schema.

TestNativeHistogramBounds locks this down: structural invariants, a loose
(few-ULP) sanity check against 2^(j / 2^s) / 2, and exact equality against the
canonical values at the entries where math.Pow disagrees.

Verified by exporting the boundaries for an identical config on darwin/arm64 and
linux/amd64 — byte-identical after this change, divergent before.

nativeHistogramBounds was computed at init time with math.Pow. math.Pow
is not bit-portable: it has architecture-specific implementations, so the
table came out one ULP different on arm64 than on amd64, and matched
Prometheus's published table on neither.

The boundaries are not an internal detail. getLe derives the classic
Prometheus bucket upper bounds from them, and those are exported as `le`
label values, so a boundary that varies by architecture splits a single
bucket into two time series across a mixed-architecture deployment, and
makes any golden-file test of the exported format architecture-dependent.

Transcribe the table from client_golang instead, which spells the values
out as literals for exactly this reason. Boundaries are now bit-identical
everywhere and identical to Prometheus's own, so the classic buckets we
export line up with what Prometheus computes for the same native bucket.

Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.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