goodhistogram: add Prometheus adapter layer#2
Conversation
79f22bd to
83a0ed6
Compare
718efb4 to
06b75d4
Compare
|
Buddy, we might need to split this into two changes which build off each other:
Let me know if that seems like a reasonable breakdown, it seems difficult to break this changeset into non-dependent changes. |
06b75d4 to
5748700
Compare
| ch <- p.desc | ||
| } | ||
|
|
||
| // Collect implements prometheus.Collector. |
There was a problem hiding this comment.
Collect sends p (the collector itself) as the metric. This means the same object is both the prometheus.Collector and the prometheus.Metric. This works with the current Prometheus library but is unusual and means re-registering or using the collector in multiple registries would be problematic.
More importantly, a single PrometheusCollector can only represent one metric, so this is correct for the current use case, but the design conflates two distinct roles.
CockroachDB's existing approach in metric.go separates these: histograms implement PrometheusExportable (which has ToPrometheusMetric()) and the registry handles collection. In the HistogramVec below in this same PR, a separate histogramMetric struct is used (vec.go:117-128), which is the cleaner pattern. Consider doing the same for PrometheusCollector for consistency.
There was a problem hiding this comment.
CockroachDB's metric system defines PrometheusExportable (GetType(), ToPrometheusMetric()) and PrometheusVector (ToPrometheusMetrics()) interfaces. When goodhistogram is eventually integrated into CockroachDB, the adapter layer here will need to bridge to those interfaces. It might be worth considering whether PrometheusCollector should also implement a ToPrometheusMetric() *prometheusgo.Metric method (it essentially does this in Write already) to ease future integration.
There was a problem hiding this comment.
Good ideas, I made the collector create a separate histogramMetric, and added a ToPrometheusMetric function for easy integration
| // WithLabelValues returns the Histogram for the given label values, | ||
| // creating it if it doesn't exist. Panics if the number of values | ||
| // doesn't match the number of label names. | ||
| func (v *HistogramVec) WithLabelValues(lvs ...string) *Histogram { |
There was a problem hiding this comment.
do we need cardinality protection here?
In CockroachDB, this is a known concern — HighCardinalityHistogram in aggmetric/histogram.go uses cache-based eviction with configurable MaxLabelValues (default 5000). The SQLHistogram similarly has cardinality controls.
Without any limit, a caller passing user-controlled label values (e.g., SQL query fingerprints, user IDs) could cause unbounded memory growth. Consider:
- Adding a MaxChildren option to HistogramVec with a default cap.
- Alternatively, documenting that callers must ensure bounded cardinality, since this is a library not directly exposed to user input.
There was a problem hiding this comment.
Prometheus doesn't do bounding on cardinality (link), does it feel okay to match that pattern, and push bounding up to the consumers of the library?
There was a problem hiding this comment.
ah cool, yeah that sounds fine
| len(v.labelNames), len(lvs), | ||
| )) | ||
| } | ||
| key := strings.Join(lvs, "\x00") |
There was a problem hiding this comment.
Are we concerned at all about key collisions due to this literal/separator?
There was a problem hiding this comment.
After checking, we changed it '0xFF' instead of 0x00. Does that feel better?
There was a problem hiding this comment.
According to claude:
Prometheus's client_golang does not use
\x00as a separator. It usesmodel.SeparatorBytewhich is255 (0xFF). However, the pattern is also different in a more important way: Prometheus hashes the separator-joined label values into a uint64 and uses that as the map key (inhashLabelValues()invec.go), rather than using the raw joined string. So collisions there are handled by the hash collision resolution, not by relying on the separator being unambiguous.
That said, Brian's updated code does now use \xff (matching Prometheus's separator byte value), and the collision risk is theoretical — label values in Prometheus are valid UTF-8, and 0xFF is not a valid UTF-8 byte, so in practice no well-formed label value would contain it.
So yeah seems like we're ok here.
|
|
||
| // DeleteLabelValues removes the Histogram for the given label values. | ||
| // Returns true if the entry existed. | ||
| func (v *HistogramVec) DeleteLabelValues(lvs ...string) bool { |
There was a problem hiding this comment.
Do we need the same len(lvs) len(v.labelNames) panic/check here as we have in WithLabelValues?
There was a problem hiding this comment.
We do! adding it now
0b75663 to
824c291
Compare
Every Prometheus user needs an adapter to expose goodhistogram metrics through a registry. PrometheusCollector wraps a *Histogram as a prometheus.Collector, implementing Describe/Collect/Write so that histograms can be registered with prometheus.MustRegister and scraped without additional glue code. HistogramVec extends this to labeled metrics, managing a collection of Histograms partitioned by label values with on-demand creation via WithLabelValues. The exported data includes both conventional cumulative buckets and native histogram sparse fields — no remapping required since goodhistogram's bucket layout is schema-aligned by construction. Co-Authored-By: roachdev-claude <[email protected]>
824c291 to
765e86b
Compare
Cumulative histograms are the right primitive for Prometheus scraping, but operators and dashboards need rolling-window quantiles to see recent behavior. Windowed maintains a single cumulative histogram with two baseline snapshots rotated on a configurable interval. The windowed view is computed by subtracting the older baseline from the current cumulative state, covering 1-2x the window duration. Recording cost is identical to a plain Histogram (~20ns, lock-free); the mutex only protects baseline rotation. WindowedCollector and WindowedVec provide Prometheus integration for single and labeled windowed histograms, following the same adapter pattern as PrometheusCollector and HistogramVec. Co-Authored-By: roachdev-claude <[email protected]>
goodhistogram: add windowed histogram with Prometheus integration
Summary
Every Prometheus user needs an adapter to expose goodhistogram metrics through a registry. This adds
PrometheusCollectorandHistogramVecso callers can register histograms withprometheus.MustRegisterand have them scraped without additional glue code.HistogramVecextends this to labeled metrics, managing a collection ofHistograms partitioned by label values with on-demand creation viaWithLabelValues.The exported data includes both conventional cumulative buckets and native histogram sparse fields — no remapping required since goodhistogram's bucket layout is schema-aligned by construction.