Skip to content

perf: scan metric name validity once per metric family - #2443

Draft
david-mollitor-db wants to merge 1 commit into
prometheus:mainfrom
david-mollitor-db:hoist-metric-name-validity
Draft

perf: scan metric name validity once per metric family#2443
david-mollitor-db wants to merge 1 commit into
prometheus:mainfrom
david-mollitor-db:hoist-metric-name-validity

Conversation

@david-mollitor-db

Copy link
Copy Markdown

Summary

The Prometheus, OpenMetrics, and OpenMetrics 2 text writers validate the metric
name with PrometheusNaming.isValidLegacyMetricName(name) on every data point
line
— once to choose bare-vs-braced metric syntax, and again inside
writeName(). The metric name is constant across all data points of a family,
so for a family with N series this re-scans the same string ~2N times per scrape.

This change scans the name once per family and threads the boolean result
down through writeNameAndLabels() / writeName() (a new
writeName(writer, name, nameType, boolean validLegacyName) overload trusts the
precomputed value). A suffix built from legacy characters (_total, _bucket,
_count, _created, …) never changes legacy validity, so the base-name result
applies to the suffixed names too.

No behavior change

Output is byte-identical — the full exposition-format test suite passes
unchanged, including the golden-output ExpositionFormatsTest across every
format and escaping scheme, OpenMetrics2TextFormatWriterTest, and
PrometheusNamingTest. This is a CPU-only optimization; per-scrape allocation is
unchanged.

Benchmark

A local high-cardinality scrape+serialize benchmark (one counter family, 100,000
series; JMH -f 1 -wi 4 -i 5, GC profiler):

ops/s gc.alloc.rate.norm
before 6.876 ± 0.176 50,921,114 B/op
after 7.346 ± 0.327 50,921,081 B/op

~+7% throughput on this path, allocation unchanged. The effect scales with the
number of series and the metric-name length.

Notes

The per-label-name validity check is a similar (and larger, O(N × labels))
per-scrape rescan and is a natural follow-up; this PR intentionally scopes to the
metric name only.

This pull request and its description were written by Isaac.

The Prometheus, OpenMetrics, and OpenMetrics 2 text writers call
PrometheusNaming.isValidLegacyMetricName(name) for every data point line - once
to choose bare-vs-braced metric syntax and again inside writeName(). The metric
name is constant across all data points of a family, so this re-scans the same
string ~2N times per scrape for a family with N series.

Scan the name once per family and pass the boolean result down through
writeNameAndLabels()/writeName(). Appending a suffix built from legacy characters
(_total, _bucket, _count, _created, ...) does not change legacy validity, so the
base-name result applies to the suffixed names too.

No behavior change: exposition output is byte-identical (the full
exposition-format test suite, including the golden-output ExpositionFormatsTest
across all formats and escaping schemes, passes unchanged). This is a CPU-only
optimization; per-scrape allocation is unchanged.

On a high-cardinality scrape+serialize benchmark (one counter family, 100000
series) throughput improved ~7% (6.9 -> 7.3 ops/s) with allocation unchanged.

Signed-off-by: David Mollitor <david.mollitor@databricks.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