package metrics import ( "github.com/prometheus/client_golang/prometheus" healthDomain "proxy-pool/internal/domain/health" ) // DrainCollector exposes only fixed Controller Drain reasons. It never uses // proxy, Upstream, Worker, session, routing or endpoint values as labels. type DrainCollector struct { candidates *prometheus.CounterVec started *prometheus.CounterVec } var _ healthDomain.DrainMetricsObserver = (*DrainCollector)(nil) func NewDrainCollector(registerer prometheus.Registerer) (*DrainCollector, error) { if registerer == nil { return nil, ErrInvalidDependencies } candidates, err := registerCounterVec(registerer, prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "proxy_pool", Subsystem: "controller", Name: "drain_candidates_total", Help: "Number of bounded Controller Drain candidates selected by reason.", }, []string{"reason"})) if err != nil { return nil, err } started, err := registerCounterVec(registerer, prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "proxy_pool", Subsystem: "controller", Name: "drains_started_total", Help: "Number of Controller Drain tickets started by reason.", }, []string{"reason"})) if err != nil { return nil, err } return &DrainCollector{candidates: candidates, started: started}, nil } func (collector *DrainCollector) ObserveDrain(reason healthDomain.DrainReason, candidates, started int) { if collector == nil || !validDrainReason(reason) || candidates < 0 || started < 0 || started > candidates { return } if candidates > 0 && collector.candidates != nil { collector.candidates.WithLabelValues(string(reason)).Add(float64(candidates)) } if started > 0 && collector.started != nil { collector.started.WithLabelValues(string(reason)).Add(float64(started)) } } func validDrainReason(reason healthDomain.DrainReason) bool { switch reason { case healthDomain.DrainReasonUnhealthy, healthDomain.DrainReasonUpstreamDisabled: return true default: return false } }