// Package metrics defines the operator's Prometheus metrics. Nothing here // registers itself — no init(), per house rules — the composition root // calls Register explicitly, which also lets every test use a fresh // registry. package metrics import ( "time" "github.com/prometheus/client_golang/prometheus" ) // Metrics holds the vector metrics the operator's components feed. The // consuming packages (health, discovery, provider) each define their own // small recorder interface; *Metrics satisfies all of them structurally, // so none of them import this package's prometheus surface. type Metrics struct { healthcheckDuration *prometheus.HistogramVec healthcheckFailures *prometheus.CounterVec leaseRequests *prometheus.CounterVec providerRequests *prometheus.CounterVec } // New builds the metric set, unregistered. func New() *Metrics { return &Metrics{ healthcheckDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "proxy_operator_healthcheck_duration_seconds", Help: "Duration of through-the-proxy health probes.", Buckets: prometheus.DefBuckets, }, []string{"proxy"}), healthcheckFailures: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "proxy_operator_healthcheck_failures_total", Help: "Failed health probes.", }, []string{"proxy"}), leaseRequests: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "proxy_operator_lease_requests_total", Help: "Lease acquisition requests by outcome.", }, []string{"outcome"}), providerRequests: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "proxy_operator_provider_requests_total", Help: "Provider API calls by operation and classified result.", }, []string{"provider", "op", "result"}), } } // Register registers the vectors plus the two scrape-time collectors. // proxyPhases and activeLeases are read at every scrape: gauges derived // from reconcile-time increments inevitably drift and leak series on // delete; reading the source of truth cannot. func (m *Metrics) Register(reg prometheus.Registerer, proxyPhases func() map[string]int, activeLeases func() int) error { collectors := []prometheus.Collector{ m.healthcheckDuration, m.healthcheckFailures, m.leaseRequests, m.providerRequests, &constCollector{ desc: prometheus.NewDesc("proxy_operator_proxies", "Proxy objects by phase.", []string{"phase"}, nil), read: proxyPhases, }, &constCollector{ desc: prometheus.NewDesc("proxy_operator_leases_active", "Currently active leases.", nil, nil), read: func() map[string]int { return map[string]int{"": activeLeases()} }, }, } for _, c := range collectors { if err := reg.Register(c); err != nil { return err } } return nil } // ObserveProbe records one health probe. Called on every probe — metrics // are the home for high-frequency signal that must never touch status. func (m *Metrics) ObserveProbe(proxy string, latency time.Duration, success bool) { m.healthcheckDuration.WithLabelValues(proxy).Observe(latency.Seconds()) if !success { m.healthcheckFailures.WithLabelValues(proxy).Inc() } } // ForgetProxy drops the per-proxy series when the health engine prunes its // state — without this, series for deleted proxies leak forever. func (m *Metrics) ForgetProxy(proxy string) { m.healthcheckDuration.DeleteLabelValues(proxy) m.healthcheckFailures.DeleteLabelValues(proxy) } // LeaseRequest records a lease acquisition outcome ("granted"|"no_match"). func (m *Metrics) LeaseRequest(outcome string) { m.leaseRequests.WithLabelValues(outcome).Inc() } // ProviderRequest records one provider API call with its classified result. func (m *Metrics) ProviderRequest(provider, op, result string) { m.providerRequests.WithLabelValues(provider, op, result).Inc() } // constCollector reads a label→value map at scrape time and emits one // gauge sample per entry. An empty-string label key means "no labels". type constCollector struct { desc *prometheus.Desc read func() map[string]int } func (c *constCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.desc } func (c *constCollector) Collect(ch chan<- prometheus.Metric) { for label, value := range c.read() { if label == "" { ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, float64(value)) continue } ch <- prometheus.MustNewConstMetric(c.desc, prometheus.GaugeValue, float64(value), label) } }