mirror of
https://github.com/luxfi/metric.git
synced 2026-07-27 04:12:44 +00:00
feat(compat): complete prometheus/client_golang surface for CoreDNS
Add the prometheus-compatible symbols CoreDNS (and the kubernetes client)
reference so a mechanical prometheus -> luxfi/metric migration compiles and
behaves unchanged:
- HistogramOpts native (sparse) histogram fields (NativeHistogramBucketFactor et al)
- package MustRegister/Unregister; AlreadyRegisteredError (returned on duplicate
registration so re-registration on reload is tolerated)
- With/AutoFactory (promauto-style registerer-scoped constructors)
- GoCollectorOption/WithGoCollectorRuntimeMetrics/MetricsAll; NewGoCollector(opts...)
- {Counter,Gauge,Histogram,Summary}Vec.Delete(Labels)
- Register treats the no-op Go/process collectors as no-ops rather than errors
This commit is contained in:
@@ -55,6 +55,9 @@ func (c *curriedCounterVec) WithLabelValues(values ...string) Counter {
|
||||
func (c *curriedCounterVec) MustCurryWith(labels Labels) CounterVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedCounterVec) Delete(labels Labels) bool {
|
||||
return c.base.Delete(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedCounterVec) Reset() { c.base.Reset() }
|
||||
|
||||
// --- gauge ---
|
||||
@@ -78,6 +81,9 @@ func (c *curriedGaugeVec) WithLabelValues(values ...string) Gauge {
|
||||
func (c *curriedGaugeVec) MustCurryWith(labels Labels) GaugeVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedGaugeVec) Delete(labels Labels) bool {
|
||||
return c.base.Delete(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedGaugeVec) Reset() { c.base.Reset() }
|
||||
|
||||
// --- histogram ---
|
||||
@@ -101,6 +107,9 @@ func (c *curriedHistogramVec) WithLabelValues(values ...string) Histogram {
|
||||
func (c *curriedHistogramVec) MustCurryWith(labels Labels) HistogramVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedHistogramVec) Delete(labels Labels) bool {
|
||||
return c.base.Delete(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedHistogramVec) Reset() { c.base.Reset() }
|
||||
|
||||
// --- summary ---
|
||||
@@ -124,4 +133,7 @@ func (c *curriedSummaryVec) WithLabelValues(values ...string) Summary {
|
||||
func (c *curriedSummaryVec) MustCurryWith(labels Labels) SummaryVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedSummaryVec) Delete(labels Labels) bool {
|
||||
return c.base.Delete(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedSummaryVec) Reset() { c.base.Reset() }
|
||||
|
||||
@@ -27,8 +27,17 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
|
||||
return &processCollector{opts: opts}
|
||||
}
|
||||
|
||||
// NewGoCollector creates a new Go collector (no-op for now).
|
||||
func NewGoCollector() Collector {
|
||||
// NewGoCollector creates a new Go collector (no-op for now). It accepts the
|
||||
// Prometheus GoCollectorOption surface (e.g. WithGoCollectorRuntimeMetrics) so
|
||||
// callers migrating from prometheus/client_golang compile unchanged; the options
|
||||
// are applied to the collector's config and the no-op collector ignores them.
|
||||
func NewGoCollector(opts ...GoCollectorOption) Collector {
|
||||
cfg := goCollectorConfig{}
|
||||
for _, o := range opts {
|
||||
if o != nil {
|
||||
o(&cfg)
|
||||
}
|
||||
}
|
||||
return &goCollector{}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,19 @@ type HistogramOpts struct {
|
||||
Help string
|
||||
ConstLabels Labels
|
||||
Buckets []float64
|
||||
|
||||
// Native (sparse) histogram configuration. These mirror the Prometheus
|
||||
// HistogramOpts surface so callers written against prometheus/client_golang
|
||||
// (CoreDNS, the kubernetes client) compile and configure unchanged. A
|
||||
// non-zero NativeHistogramBucketFactor requests a native histogram; the
|
||||
// remaining fields tune bucket count, the zero bucket, and the reset cadence.
|
||||
NativeHistogramBucketFactor float64
|
||||
NativeHistogramZeroThreshold float64
|
||||
NativeHistogramMaxBucketNumber uint32
|
||||
NativeHistogramMinResetDuration time.Duration
|
||||
NativeHistogramMaxZeroThreshold float64
|
||||
NativeHistogramMaxExemplars int
|
||||
NativeHistogramExemplarTTL time.Duration
|
||||
}
|
||||
|
||||
// SummaryOpts configures a summary metric.
|
||||
@@ -107,6 +120,9 @@ type CounterVec interface {
|
||||
With(Labels) Counter
|
||||
WithLabelValues(...string) Counter
|
||||
MustCurryWith(Labels) CounterVec
|
||||
// Delete removes the child with the exact label set, returning true if it
|
||||
// was present. Mirrors prometheus.CounterVec.Delete.
|
||||
Delete(Labels) bool
|
||||
Reset()
|
||||
}
|
||||
|
||||
@@ -115,6 +131,9 @@ type GaugeVec interface {
|
||||
With(Labels) Gauge
|
||||
WithLabelValues(...string) Gauge
|
||||
MustCurryWith(Labels) GaugeVec
|
||||
// Delete removes the child with the exact label set, returning true if it
|
||||
// was present. Mirrors prometheus.GaugeVec.Delete.
|
||||
Delete(Labels) bool
|
||||
Reset()
|
||||
}
|
||||
|
||||
@@ -123,6 +142,9 @@ type HistogramVec interface {
|
||||
With(Labels) Histogram
|
||||
WithLabelValues(...string) Histogram
|
||||
MustCurryWith(Labels) HistogramVec
|
||||
// Delete removes the child with the exact label set, returning true if it
|
||||
// was present. Mirrors prometheus.HistogramVec.Delete.
|
||||
Delete(Labels) bool
|
||||
Reset()
|
||||
}
|
||||
|
||||
@@ -131,6 +153,9 @@ type SummaryVec interface {
|
||||
With(Labels) Summary
|
||||
WithLabelValues(...string) Summary
|
||||
MustCurryWith(Labels) SummaryVec
|
||||
// Delete removes the child with the exact label set, returning true if it
|
||||
// was present. Mirrors prometheus.SummaryVec.Delete.
|
||||
Delete(Labels) bool
|
||||
Reset()
|
||||
}
|
||||
|
||||
|
||||
+122
-2
@@ -684,6 +684,70 @@ func (hpr *registry) deregisterLabeled(name string) {
|
||||
delete(hpr.summaries, name)
|
||||
}
|
||||
|
||||
// deregisterLabeled{Counter,Gauge,Histogram,Summary}One drops a SINGLE
|
||||
// label-permutation child (identified by its labels key) for the named metric,
|
||||
// returning true if it was present. Backs {Counter,Gauge,Histogram,Summary}Vec.
|
||||
// Delete — the single-series analogue of deregisterLabeled/Reset.
|
||||
func (hpr *registry) deregisterLabeledCounterOne(name, key string) bool {
|
||||
hpr.mu.Lock()
|
||||
defer hpr.mu.Unlock()
|
||||
m := hpr.counters[name]
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := m[key]
|
||||
delete(m, key)
|
||||
if len(m) == 0 {
|
||||
delete(hpr.counters, name)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (hpr *registry) deregisterLabeledGaugeOne(name, key string) bool {
|
||||
hpr.mu.Lock()
|
||||
defer hpr.mu.Unlock()
|
||||
m := hpr.gauges[name]
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := m[key]
|
||||
delete(m, key)
|
||||
if len(m) == 0 {
|
||||
delete(hpr.gauges, name)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (hpr *registry) deregisterLabeledHistogramOne(name, key string) bool {
|
||||
hpr.mu.Lock()
|
||||
defer hpr.mu.Unlock()
|
||||
m := hpr.histograms[name]
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := m[key]
|
||||
delete(m, key)
|
||||
if len(m) == 0 {
|
||||
delete(hpr.histograms, name)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (hpr *registry) deregisterLabeledSummaryOne(name, key string) bool {
|
||||
hpr.mu.Lock()
|
||||
defer hpr.mu.Unlock()
|
||||
m := hpr.summaries[name]
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := m[key]
|
||||
delete(m, key)
|
||||
if len(m) == 0 {
|
||||
delete(hpr.summaries, name)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// NewCounter creates and registers a counter.
|
||||
func (hpr *registry) NewCounter(name, help string) Counter {
|
||||
counter := newCounter(name, help)
|
||||
@@ -739,6 +803,14 @@ func (hpr *registry) Registry() Registry {
|
||||
|
||||
// Register is a compatibility no-op. Metrics are registered on creation.
|
||||
func (hpr *registry) Register(c Collector) error {
|
||||
// The Go and process collectors are exposition-only shims in this library
|
||||
// (no sparse runtime/process series yet); registering them is a no-op rather
|
||||
// than an "unsupported type" error, so prometheus-style
|
||||
// MustRegister(NewGoCollector(...)) succeeds.
|
||||
switch c.(type) {
|
||||
case *goCollector, *processCollector:
|
||||
return nil
|
||||
}
|
||||
name, typ, ok := collectorIdentity(c)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported collector type %T", c)
|
||||
@@ -909,6 +981,17 @@ func (v *counterVec) Reset() {
|
||||
v.counters = make(map[string]Counter)
|
||||
}
|
||||
|
||||
// Delete removes the child with the exact label set. Mirrors prometheus.
|
||||
func (v *counterVec) Delete(labels Labels) bool {
|
||||
key := labelsKeyFromLabels(labels)
|
||||
v.mu.Lock()
|
||||
_, existed := v.counters[key]
|
||||
delete(v.counters, key)
|
||||
v.mu.Unlock()
|
||||
regExisted := v.registry.deregisterLabeledCounterOne(v.name, key)
|
||||
return existed || regExisted
|
||||
}
|
||||
|
||||
// gaugeVec is a labeled gauge collection.
|
||||
type gaugeVec struct {
|
||||
registry *registry
|
||||
@@ -959,6 +1042,17 @@ func (v *gaugeVec) Reset() {
|
||||
v.gauges = make(map[string]Gauge)
|
||||
}
|
||||
|
||||
// Delete removes the child with the exact label set. Mirrors prometheus.
|
||||
func (v *gaugeVec) Delete(labels Labels) bool {
|
||||
key := labelsKeyFromLabels(labels)
|
||||
v.mu.Lock()
|
||||
_, existed := v.gauges[key]
|
||||
delete(v.gauges, key)
|
||||
v.mu.Unlock()
|
||||
regExisted := v.registry.deregisterLabeledGaugeOne(v.name, key)
|
||||
return existed || regExisted
|
||||
}
|
||||
|
||||
// histogramVec is a labeled histogram collection.
|
||||
type histogramVec struct {
|
||||
registry *registry
|
||||
@@ -1011,6 +1105,17 @@ func (v *histogramVec) Reset() {
|
||||
v.histograms = make(map[string]Histogram)
|
||||
}
|
||||
|
||||
// Delete removes the child with the exact label set. Mirrors prometheus.
|
||||
func (v *histogramVec) Delete(labels Labels) bool {
|
||||
key := labelsKeyFromLabels(labels)
|
||||
v.mu.Lock()
|
||||
_, existed := v.histograms[key]
|
||||
delete(v.histograms, key)
|
||||
v.mu.Unlock()
|
||||
regExisted := v.registry.deregisterLabeledHistogramOne(v.name, key)
|
||||
return existed || regExisted
|
||||
}
|
||||
|
||||
// summaryVec is a labeled summary collection.
|
||||
type summaryVec struct {
|
||||
registry *registry
|
||||
@@ -1067,6 +1172,17 @@ func (v *summaryVec) Reset() {
|
||||
v.summaries = make(map[string]Summary)
|
||||
}
|
||||
|
||||
// Delete removes the child with the exact label set. Mirrors prometheus.
|
||||
func (v *summaryVec) Delete(labels Labels) bool {
|
||||
key := labelsKeyFromLabels(labels)
|
||||
v.mu.Lock()
|
||||
_, existed := v.summaries[key]
|
||||
delete(v.summaries, key)
|
||||
v.mu.Unlock()
|
||||
regExisted := v.registry.deregisterLabeledSummaryOne(v.name, key)
|
||||
return existed || regExisted
|
||||
}
|
||||
|
||||
func labelsFromValues(labelNames []string, values []string) Labels {
|
||||
labels := make(Labels, len(labelNames))
|
||||
for i, name := range labelNames {
|
||||
@@ -1131,8 +1247,12 @@ func labelsToLabelPairs(labels Labels) []LabelPair {
|
||||
func (hpr *registry) registerName(name string, typ MetricType) error {
|
||||
hpr.mu.Lock()
|
||||
defer hpr.mu.Unlock()
|
||||
if existing, ok := hpr.registered[name]; ok {
|
||||
return fmt.Errorf("metric %q already registered as %s", name, existing.String())
|
||||
if _, ok := hpr.registered[name]; ok {
|
||||
// Return the typed AlreadyRegisteredError (mirrors prometheus.Registry)
|
||||
// so callers that tolerate re-registration — CoreDNS re-runs setup on
|
||||
// every `reload` — can detect the duplicate and continue instead of
|
||||
// treating it as a fatal error.
|
||||
return AlreadyRegisteredError{}
|
||||
}
|
||||
hpr.registered[name] = typ
|
||||
return nil
|
||||
|
||||
@@ -71,6 +71,7 @@ type noopCounterVec struct{}
|
||||
func (n *noopCounterVec) With(Labels) Counter { return &noopCounter{} }
|
||||
func (n *noopCounterVec) WithLabelValues(...string) Counter { return &noopCounter{} }
|
||||
func (n *noopCounterVec) MustCurryWith(Labels) CounterVec { return n }
|
||||
func (n *noopCounterVec) Delete(Labels) bool { return false }
|
||||
func (n *noopCounterVec) Reset() {}
|
||||
|
||||
// noopGaugeVec is a gauge vector that does nothing.
|
||||
@@ -79,6 +80,7 @@ type noopGaugeVec struct{}
|
||||
func (n *noopGaugeVec) With(Labels) Gauge { return &noopGauge{} }
|
||||
func (n *noopGaugeVec) WithLabelValues(...string) Gauge { return &noopGauge{} }
|
||||
func (n *noopGaugeVec) MustCurryWith(Labels) GaugeVec { return n }
|
||||
func (n *noopGaugeVec) Delete(Labels) bool { return false }
|
||||
func (n *noopGaugeVec) Reset() {}
|
||||
|
||||
// noopHistogramVec is a histogram vector that does nothing.
|
||||
@@ -87,6 +89,7 @@ type noopHistogramVec struct{}
|
||||
func (n *noopHistogramVec) With(Labels) Histogram { return &noopHistogram{} }
|
||||
func (n *noopHistogramVec) WithLabelValues(...string) Histogram { return &noopHistogram{} }
|
||||
func (n *noopHistogramVec) MustCurryWith(Labels) HistogramVec { return n }
|
||||
func (n *noopHistogramVec) Delete(Labels) bool { return false }
|
||||
func (n *noopHistogramVec) Reset() {}
|
||||
|
||||
// noopSummaryVec is a summary vector that does nothing.
|
||||
@@ -95,6 +98,7 @@ type noopSummaryVec struct{}
|
||||
func (n *noopSummaryVec) With(Labels) Summary { return &noopSummary{} }
|
||||
func (n *noopSummaryVec) WithLabelValues(...string) Summary { return &noopSummary{} }
|
||||
func (n *noopSummaryVec) MustCurryWith(Labels) SummaryVec { return n }
|
||||
func (n *noopSummaryVec) Delete(Labels) bool { return false }
|
||||
func (n *noopSummaryVec) Reset() {}
|
||||
|
||||
// noopRegistry provides a registry that gathers nothing.
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package metric
|
||||
|
||||
// This file completes the prometheus/client_golang compatibility surface so
|
||||
// code migrated mechanically from prometheus (e.g. CoreDNS) compiles and
|
||||
// behaves against luxfi/metric unchanged. Every symbol here mirrors a
|
||||
// prometheus/client_golang (or client_golang/prometheus/{promauto,collectors})
|
||||
// export; the backing implementation is luxfi/metric's own registry.
|
||||
|
||||
// AlreadyRegisteredError is returned by a registry's Register when a collector
|
||||
// with the same fully-qualified name is already registered. It mirrors
|
||||
// prometheus.AlreadyRegisteredError so callers that tolerate re-registration
|
||||
// (CoreDNS re-runs plugin setup on every `reload`) can type-assert on it and
|
||||
// continue rather than failing. The Collector fields are populated when
|
||||
// available; callers typically only test the error's dynamic type.
|
||||
type AlreadyRegisteredError struct {
|
||||
ExistingCollector Collector
|
||||
NewCollector Collector
|
||||
}
|
||||
|
||||
// Error implements error. The message matches prometheus/client_golang's.
|
||||
func (AlreadyRegisteredError) Error() string {
|
||||
return "duplicate metrics collector registration attempted"
|
||||
}
|
||||
|
||||
// MustRegister registers the given collectors on the default registerer and
|
||||
// panics on error. Mirrors prometheus.MustRegister.
|
||||
func MustRegister(cs ...Collector) { DefaultRegisterer.MustRegister(cs...) }
|
||||
|
||||
// Unregister removes a previously registered collector from the default
|
||||
// registerer, returning true if it was present. Mirrors prometheus.Unregister.
|
||||
func Unregister(c Collector) bool { return DefaultRegisterer.Unregister(c) }
|
||||
|
||||
// GoRuntimeMetricsRule selects which runtime/metrics metrics the Go collector
|
||||
// exposes. Mirrors collectors.GoRuntimeMetricsRule. Matcher is the metric-name
|
||||
// pattern (kept as the source string; the no-op Go collector does not evaluate
|
||||
// it).
|
||||
type GoRuntimeMetricsRule struct {
|
||||
Matcher string
|
||||
}
|
||||
|
||||
// MetricsAll enables every available runtime/metrics metric. Mirrors
|
||||
// collectors.MetricsAll.
|
||||
var MetricsAll = GoRuntimeMetricsRule{Matcher: "/.*"}
|
||||
|
||||
// goCollectorConfig accumulates GoCollectorOption settings.
|
||||
type goCollectorConfig struct {
|
||||
runtimeRules []GoRuntimeMetricsRule
|
||||
}
|
||||
|
||||
// GoCollectorOption configures the Go collector. Mirrors
|
||||
// collectors.GoCollectorOption.
|
||||
type GoCollectorOption func(*goCollectorConfig)
|
||||
|
||||
// WithGoCollectorRuntimeMetrics enables the runtime/metrics metrics matched by
|
||||
// the given rules. Mirrors collectors.WithGoCollectorRuntimeMetrics.
|
||||
func WithGoCollectorRuntimeMetrics(rules ...GoRuntimeMetricsRule) GoCollectorOption {
|
||||
return func(c *goCollectorConfig) { c.runtimeRules = append(c.runtimeRules, rules...) }
|
||||
}
|
||||
|
||||
// Factory registers newly-created metrics on a specific Registerer, mirroring
|
||||
// promauto.Factory. Obtain one with With.
|
||||
type AutoFactory struct {
|
||||
r Registerer
|
||||
}
|
||||
|
||||
// With returns a Factory that registers every metric it creates on r. Mirrors
|
||||
// promauto.With. A nil r targets the default registerer.
|
||||
func With(r Registerer) AutoFactory {
|
||||
if r == nil {
|
||||
r = DefaultRegisterer
|
||||
}
|
||||
return AutoFactory{r: r}
|
||||
}
|
||||
|
||||
// NewCounterVec creates a counter vector registered on the factory's registerer.
|
||||
func (f AutoFactory) NewCounterVec(opts CounterOpts, labelNames []string) CounterVec {
|
||||
return f.r.NewCounterVec(prefixedName(AppendNamespace(opts.Namespace, opts.Subsystem), opts.Name), opts.Help, labelNames)
|
||||
}
|
||||
|
||||
// NewGaugeVec creates a gauge vector registered on the factory's registerer.
|
||||
func (f AutoFactory) NewGaugeVec(opts GaugeOpts, labelNames []string) GaugeVec {
|
||||
return f.r.NewGaugeVec(prefixedName(AppendNamespace(opts.Namespace, opts.Subsystem), opts.Name), opts.Help, labelNames)
|
||||
}
|
||||
|
||||
// NewHistogramVec creates a histogram vector registered on the factory's registerer.
|
||||
func (f AutoFactory) NewHistogramVec(opts HistogramOpts, labelNames []string) HistogramVec {
|
||||
return f.r.NewHistogramVec(prefixedName(AppendNamespace(opts.Namespace, opts.Subsystem), opts.Name), opts.Help, labelNames, opts.Buckets)
|
||||
}
|
||||
|
||||
// NewCounter creates a counter registered on the factory's registerer.
|
||||
func (f AutoFactory) NewCounter(opts CounterOpts) Counter {
|
||||
return f.r.NewCounter(prefixedName(AppendNamespace(opts.Namespace, opts.Subsystem), opts.Name), opts.Help)
|
||||
}
|
||||
|
||||
// NewGauge creates a gauge registered on the factory's registerer.
|
||||
func (f AutoFactory) NewGauge(opts GaugeOpts) Gauge {
|
||||
return f.r.NewGauge(prefixedName(AppendNamespace(opts.Namespace, opts.Subsystem), opts.Name), opts.Help)
|
||||
}
|
||||
|
||||
// NewHistogram creates a histogram registered on the factory's registerer.
|
||||
func (f AutoFactory) NewHistogram(opts HistogramOpts) Histogram {
|
||||
return f.r.NewHistogram(prefixedName(AppendNamespace(opts.Namespace, opts.Subsystem), opts.Name), opts.Help, opts.Buckets)
|
||||
}
|
||||
Reference in New Issue
Block a user