package metrics import ( "errors" "time" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/multi" ) // Registry has to implemented by any system that wants to monitor and expose metrics. type Registry interface { // IsEpEnabled shows whether metrics instrumentation is enabled on entry points. IsEpEnabled() bool // IsSvcEnabled shows whether metrics instrumentation is enabled on services. IsSvcEnabled() bool // server metrics ConfigReloadsCounter() metrics.Counter ConfigReloadsFailureCounter() metrics.Counter LastConfigReloadSuccessGauge() metrics.Gauge LastConfigReloadFailureGauge() metrics.Gauge // entry point metrics EntryPointReqsCounter() metrics.Counter EntryPointReqsTLSCounter() metrics.Counter EntryPointReqDurationHistogram() ScalableHistogram EntryPointOpenConnsGauge() metrics.Gauge // service metrics ServiceReqsCounter() metrics.Counter ServiceReqsTLSCounter() metrics.Counter ServiceReqDurationHistogram() ScalableHistogram ServiceOpenConnsGauge() metrics.Gauge ServiceRetriesCounter() metrics.Counter ServiceServerUpGauge() metrics.Gauge } // NewVoidRegistry is a noop implementation of metrics.Registry. // It is used to avoid nil checking in components that do metric collections. func NewVoidRegistry() Registry { return NewMultiRegistry([]Registry{}) } // NewMultiRegistry is an implementation of metrics.Registry that wraps multiple registries. // It handles the case when a registry hasn't registered some metric and returns nil. // This allows for feature imparity between the different metric implementations. func NewMultiRegistry(registries []Registry) Registry { var configReloadsCounter []metrics.Counter var configReloadsFailureCounter []metrics.Counter var lastConfigReloadSuccessGauge []metrics.Gauge var lastConfigReloadFailureGauge []metrics.Gauge var entryPointReqsCounter []metrics.Counter var entryPointReqsTLSCounter []metrics.Counter var entryPointReqDurationHistogram []ScalableHistogram var entryPointOpenConnsGauge []metrics.Gauge var serviceReqsCounter []metrics.Counter var serviceReqsTLSCounter []metrics.Counter var serviceReqDurationHistogram []ScalableHistogram var serviceOpenConnsGauge []metrics.Gauge var serviceRetriesCounter []metrics.Counter var serviceServerUpGauge []metrics.Gauge for _, r := range registries { if r.ConfigReloadsCounter() != nil { configReloadsCounter = append(configReloadsCounter, r.ConfigReloadsCounter()) } if r.ConfigReloadsFailureCounter() != nil { configReloadsFailureCounter = append(configReloadsFailureCounter, r.ConfigReloadsFailureCounter()) } if r.LastConfigReloadSuccessGauge() != nil { lastConfigReloadSuccessGauge = append(lastConfigReloadSuccessGauge, r.LastConfigReloadSuccessGauge()) } if r.LastConfigReloadFailureGauge() != nil { lastConfigReloadFailureGauge = append(lastConfigReloadFailureGauge, r.LastConfigReloadFailureGauge()) } if r.EntryPointReqsCounter() != nil { entryPointReqsCounter = append(entryPointReqsCounter, r.EntryPointReqsCounter()) } if r.EntryPointReqsTLSCounter() != nil { entryPointReqsTLSCounter = append(entryPointReqsTLSCounter, r.EntryPointReqsTLSCounter()) } if r.EntryPointReqDurationHistogram() != nil { entryPointReqDurationHistogram = append(entryPointReqDurationHistogram, r.EntryPointReqDurationHistogram()) } if r.EntryPointOpenConnsGauge() != nil { entryPointOpenConnsGauge = append(entryPointOpenConnsGauge, r.EntryPointOpenConnsGauge()) } if r.ServiceReqsCounter() != nil { serviceReqsCounter = append(serviceReqsCounter, r.ServiceReqsCounter()) } if r.ServiceReqsTLSCounter() != nil { serviceReqsTLSCounter = append(serviceReqsTLSCounter, r.ServiceReqsTLSCounter()) } if r.ServiceReqDurationHistogram() != nil { serviceReqDurationHistogram = append(serviceReqDurationHistogram, r.ServiceReqDurationHistogram()) } if r.ServiceOpenConnsGauge() != nil { serviceOpenConnsGauge = append(serviceOpenConnsGauge, r.ServiceOpenConnsGauge()) } if r.ServiceRetriesCounter() != nil { serviceRetriesCounter = append(serviceRetriesCounter, r.ServiceRetriesCounter()) } if r.ServiceServerUpGauge() != nil { serviceServerUpGauge = append(serviceServerUpGauge, r.ServiceServerUpGauge()) } } return &standardRegistry{ epEnabled: len(entryPointReqsCounter) > 0 || len(entryPointReqDurationHistogram) > 0 || len(entryPointOpenConnsGauge) > 0, svcEnabled: len(serviceReqsCounter) > 0 || len(serviceReqDurationHistogram) > 0 || len(serviceOpenConnsGauge) > 0 || len(serviceRetriesCounter) > 0 || len(serviceServerUpGauge) > 0, configReloadsCounter: multi.NewCounter(configReloadsCounter...), configReloadsFailureCounter: multi.NewCounter(configReloadsFailureCounter...), lastConfigReloadSuccessGauge: multi.NewGauge(lastConfigReloadSuccessGauge...), lastConfigReloadFailureGauge: multi.NewGauge(lastConfigReloadFailureGauge...), entryPointReqsCounter: multi.NewCounter(entryPointReqsCounter...), entryPointReqsTLSCounter: multi.NewCounter(entryPointReqsTLSCounter...), entryPointReqDurationHistogram: NewMultiHistogram(entryPointReqDurationHistogram...), entryPointOpenConnsGauge: multi.NewGauge(entryPointOpenConnsGauge...), serviceReqsCounter: multi.NewCounter(serviceReqsCounter...), serviceReqsTLSCounter: multi.NewCounter(serviceReqsTLSCounter...), serviceReqDurationHistogram: NewMultiHistogram(serviceReqDurationHistogram...), serviceOpenConnsGauge: multi.NewGauge(serviceOpenConnsGauge...), serviceRetriesCounter: multi.NewCounter(serviceRetriesCounter...), serviceServerUpGauge: multi.NewGauge(serviceServerUpGauge...), } } type standardRegistry struct { epEnabled bool svcEnabled bool configReloadsCounter metrics.Counter configReloadsFailureCounter metrics.Counter lastConfigReloadSuccessGauge metrics.Gauge lastConfigReloadFailureGauge metrics.Gauge entryPointReqsCounter metrics.Counter entryPointReqsTLSCounter metrics.Counter entryPointReqDurationHistogram ScalableHistogram entryPointOpenConnsGauge metrics.Gauge serviceReqsCounter metrics.Counter serviceReqsTLSCounter metrics.Counter serviceReqDurationHistogram ScalableHistogram serviceOpenConnsGauge metrics.Gauge serviceRetriesCounter metrics.Counter serviceServerUpGauge metrics.Gauge } func (r *standardRegistry) IsEpEnabled() bool { return r.epEnabled } func (r *standardRegistry) IsSvcEnabled() bool { return r.svcEnabled } func (r *standardRegistry) ConfigReloadsCounter() metrics.Counter { return r.configReloadsCounter } func (r *standardRegistry) ConfigReloadsFailureCounter() metrics.Counter { return r.configReloadsFailureCounter } func (r *standardRegistry) LastConfigReloadSuccessGauge() metrics.Gauge { return r.lastConfigReloadSuccessGauge } func (r *standardRegistry) LastConfigReloadFailureGauge() metrics.Gauge { return r.lastConfigReloadFailureGauge } func (r *standardRegistry) EntryPointReqsCounter() metrics.Counter { return r.entryPointReqsCounter } func (r *standardRegistry) EntryPointReqsTLSCounter() metrics.Counter { return r.entryPointReqsTLSCounter } func (r *standardRegistry) EntryPointReqDurationHistogram() ScalableHistogram { return r.entryPointReqDurationHistogram } func (r *standardRegistry) EntryPointOpenConnsGauge() metrics.Gauge { return r.entryPointOpenConnsGauge } func (r *standardRegistry) ServiceReqsCounter() metrics.Counter { return r.serviceReqsCounter } func (r *standardRegistry) ServiceReqsTLSCounter() metrics.Counter { return r.serviceReqsTLSCounter } func (r *standardRegistry) ServiceReqDurationHistogram() ScalableHistogram { return r.serviceReqDurationHistogram } func (r *standardRegistry) ServiceOpenConnsGauge() metrics.Gauge { return r.serviceOpenConnsGauge } func (r *standardRegistry) ServiceRetriesCounter() metrics.Counter { return r.serviceRetriesCounter } func (r *standardRegistry) ServiceServerUpGauge() metrics.Gauge { return r.serviceServerUpGauge } // ScalableHistogram is a Histogram with a predefined time unit, // used when producing observations without explicitly setting the observed value. type ScalableHistogram interface { With(labelValues ...string) ScalableHistogram StartAt(t time.Time) Observe(v float64) ObserveDuration() } // HistogramWithScale is a histogram that will convert its observed value to the specified unit. type HistogramWithScale struct { histogram metrics.Histogram unit time.Duration start time.Time } // With implements ScalableHistogram. func (s *HistogramWithScale) With(labelValues ...string) ScalableHistogram { s.histogram = s.histogram.With(labelValues...) return s } // StartAt implements ScalableHistogram. func (s *HistogramWithScale) StartAt(t time.Time) { s.start = t } // ObserveDuration implements ScalableHistogram. func (s *HistogramWithScale) ObserveDuration() { if s.unit <= 0 { return } d := float64(time.Since(s.start).Nanoseconds()) / float64(s.unit) if d < 0 { d = 0 } s.histogram.Observe(d) } // Observe implements ScalableHistogram. func (s *HistogramWithScale) Observe(v float64) { s.histogram.Observe(v) } // NewHistogramWithScale returns a ScalableHistogram. It returns an error if the given unit is <= 0. func NewHistogramWithScale(histogram metrics.Histogram, unit time.Duration) (ScalableHistogram, error) { if unit <= 0 { return nil, errors.New("invalid time unit") } return &HistogramWithScale{ histogram: histogram, unit: unit, }, nil } // MultiHistogram collects multiple individual histograms and treats them as a unit. type MultiHistogram []ScalableHistogram // NewMultiHistogram returns a multi-histogram, wrapping the passed histograms. func NewMultiHistogram(h ...ScalableHistogram) MultiHistogram { return MultiHistogram(h) } // StartAt implements ScalableHistogram. func (h MultiHistogram) StartAt(t time.Time) { for _, histogram := range h { histogram.StartAt(t) } } // ObserveDuration implements ScalableHistogram. func (h MultiHistogram) ObserveDuration() { for _, histogram := range h { histogram.ObserveDuration() } } // Observe implements ScalableHistogram. func (h MultiHistogram) Observe(v float64) { for _, histogram := range h { histogram.Observe(v) } } // With implements ScalableHistogram. func (h MultiHistogram) With(labelValues ...string) ScalableHistogram { next := make(MultiHistogram, len(h)) for i := range h { next[i] = h[i].With(labelValues...) } return next }