/* ================================================================================== Copyright (c) 2019 AT&T Intellectual Property. Copyright (c) 2019 Nokia Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================================================== */ package xapp import ( "fmt" "sync" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" ) //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- func strSliceCompare(a, b []string) bool { if len(a) != len(b) { return false } for i, v := range a { if v != b[i] { return false } } return true } //----------------------------------------------------------------------------- // Alias //----------------------------------------------------------------------------- type CounterOpts prometheus.Opts type Counter prometheus.Counter type Gauge prometheus.Gauge type CounterVec struct { Vec *prometheus.CounterVec Opts CounterOpts Labels []string } type GaugeVec struct { Vec *prometheus.GaugeVec Opts CounterOpts Labels []string } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- type MetricGroupsCacheCounterRegisterer interface { RegisterCounter(CounterOpts) Counter } type MetricGroupsCacheCounterRegistererFunc func(CounterOpts) Counter func (fn MetricGroupsCacheCounterRegistererFunc) RegisterCounter(copts CounterOpts) Counter { return fn(copts) } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- type MetricGroupsCacheGaugeRegisterer interface { RegisterGauge(CounterOpts) Gauge } type MetricGroupsCacheGaugeRegistererFunc func(CounterOpts) Gauge func (fn MetricGroupsCacheGaugeRegistererFunc) RegisterGauge(copts CounterOpts) Gauge { return fn(copts) } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- type MetricGroupsCache struct { sync.RWMutex //This is for map locking counters map[string]Counter gauges map[string]Gauge regcnt MetricGroupsCacheCounterRegisterer reggau MetricGroupsCacheGaugeRegisterer } func (met *MetricGroupsCache) Registerer(regcnt MetricGroupsCacheCounterRegisterer, reggau MetricGroupsCacheGaugeRegisterer) { met.regcnt = regcnt met.reggau = reggau } func (met *MetricGroupsCache) cReg(metric string) Counter { if met.regcnt != nil { cntr := met.regcnt.RegisterCounter(CounterOpts{Name: metric, Help: "Amount of " + metric + "(auto)"}) met.counters[metric] = cntr return cntr } return nil } func (met *MetricGroupsCache) gReg(metric string) Gauge { if met.reggau != nil { gaug := met.reggau.RegisterGauge(CounterOpts{Name: metric, Help: "Amount of " + metric + "(auto)"}) met.gauges[metric] = gaug return gaug } return nil } func (met *MetricGroupsCache) CIs(metric string) bool { met.Lock() defer met.Unlock() _, ok := met.counters[metric] return ok } func (met *MetricGroupsCache) CGet(metric string) Counter { met.Lock() defer met.Unlock() cntr, ok := met.counters[metric] if !ok { cntr = met.cReg(metric) } return cntr } func (met *MetricGroupsCache) CInc(metric string) { met.Lock() defer met.Unlock() cntr, ok := met.counters[metric] if !ok { cntr = met.cReg(metric) } cntr.Inc() } func (met *MetricGroupsCache) CAdd(metric string, val float64) { met.Lock() defer met.Unlock() cntr, ok := met.counters[metric] if !ok { cntr = met.cReg(metric) } cntr.Add(val) } func (met *MetricGroupsCache) GIs(metric string) bool { met.Lock() defer met.Unlock() _, ok := met.gauges[metric] return ok } func (met *MetricGroupsCache) GGet(metric string) Gauge { met.Lock() defer met.Unlock() gaug, ok := met.gauges[metric] if !ok { gaug = met.gReg(metric) } return gaug } func (met *MetricGroupsCache) GSet(metric string, val float64) { met.Lock() defer met.Unlock() gaug, ok := met.gauges[metric] if !ok { gaug = met.gReg(metric) } gaug.Set(val) } func (met *MetricGroupsCache) GAdd(metric string, val float64) { met.Lock() defer met.Unlock() gaug, ok := met.gauges[metric] if !ok { gaug = met.gReg(metric) } gaug.Add(val) } func (met *MetricGroupsCache) GInc(metric string) { met.Lock() defer met.Unlock() gaug, ok := met.gauges[metric] if !ok { gaug = met.gReg(metric) } gaug.Inc() } func (met *MetricGroupsCache) GDec(metric string) { met.Lock() defer met.Unlock() gaug, ok := met.gauges[metric] if !ok { gaug = met.gReg(metric) } gaug.Dec() } func (met *MetricGroupsCache) combineCounterGroupsWithPrefix(prefix string, srcs ...map[string]Counter) { for _, src := range srcs { for k, v := range src { met.counters[prefix+k] = v } } } func (met *MetricGroupsCache) CombineCounterGroupsWithPrefix(prefix string, srcs ...map[string]Counter) { met.Lock() defer met.Unlock() met.combineCounterGroupsWithPrefix(prefix, srcs...) } func (met *MetricGroupsCache) CombineCounterGroups(srcs ...map[string]Counter) { met.Lock() defer met.Unlock() met.combineCounterGroupsWithPrefix("", srcs...) } func (met *MetricGroupsCache) combineGaugeGroupsWithPrefix(prefix string, srcs ...map[string]Gauge) { for _, src := range srcs { for k, v := range src { met.gauges[prefix+k] = v } } } func (met *MetricGroupsCache) CombineGaugeGroupsWithPrefix(prefix string, srcs ...map[string]Gauge) { met.Lock() defer met.Unlock() met.combineGaugeGroupsWithPrefix(prefix, srcs...) } func (met *MetricGroupsCache) CombineGaugeGroups(srcs ...map[string]Gauge) { met.Lock() defer met.Unlock() met.combineGaugeGroupsWithPrefix("", srcs...) } func NewMetricGroupsCache() *MetricGroupsCache { entry := &MetricGroupsCache{} entry.counters = make(map[string]Counter) entry.gauges = make(map[string]Gauge) entry.regcnt = nil entry.reggau = nil return entry } func NewMetricGroupsCacheWithRegisterers(regcnt MetricGroupsCacheCounterRegisterer, reggau MetricGroupsCacheGaugeRegisterer) *MetricGroupsCache { entry := NewMetricGroupsCache() entry.regcnt = regcnt entry.reggau = reggau return entry } //----------------------------------------------------------------------------- // All counters/gauges registered via Metrics instances: // Counter names are build from: namespace, subsystem, metric and possible labels //----------------------------------------------------------------------------- var globalLock sync.Mutex var cache_allcounters map[string]Counter var cache_allgauges map[string]Gauge var cache_allcountervects map[string]CounterVec var cache_allgaugevects map[string]GaugeVec func init() { cache_allcounters = make(map[string]Counter) cache_allgauges = make(map[string]Gauge) cache_allcountervects = make(map[string]CounterVec) cache_allgaugevects = make(map[string]GaugeVec) } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- type Metrics struct { Namespace string } func NewMetrics(url, namespace string, r *mux.Router) *Metrics { if url == "" { url = "/ric/v1/metrics" } Logger.Info("Serving metrics on: url=%s namespace=%s", url, namespace) // Expose 'metrics' endpoint with standard golang metrics used by prometheus r.Handle(url, promhttp.Handler()) return &Metrics{Namespace: namespace} } /* * Helpers */ func (m *Metrics) getFullName(opts prometheus.Opts, labels []string) string { labelname := "" for _, lbl := range labels { if len(labelname) == 0 { labelname += lbl } else { labelname += "_" + lbl } } return fmt.Sprintf("%s_%s_%s_%s", opts.Namespace, opts.Subsystem, opts.Name, labelname) } // // // func (m *Metrics) RegisterCounter(opts CounterOpts, subsytem string) Counter { globalLock.Lock() defer globalLock.Unlock() opts.Namespace = m.Namespace opts.Subsystem = subsytem id := m.getFullName(prometheus.Opts(opts), []string{}) if _, ok := cache_allcountervects[id]; ok { Logger.Warn("Register new counter with opts: %v, name conflicts existing counter vector", opts) return nil } if _, ok := cache_allcounters[id]; !ok { Logger.Debug("Register new counter with opts: %v", opts) cache_allcounters[id] = promauto.NewCounter(prometheus.CounterOpts(opts)) } return cache_allcounters[id] } // // // func (m *Metrics) RegisterCounterGroup(optsgroup []CounterOpts, subsytem string) map[string]Counter { c := make(map[string]Counter) for _, opts := range optsgroup { c[opts.Name] = m.RegisterCounter(opts, subsytem) } return c } // // // func (m *Metrics) RegisterLabeledCounter(opts CounterOpts, labelNames []string, labelValues []string, subsytem string) Counter { globalLock.Lock() defer globalLock.Unlock() opts.Namespace = m.Namespace opts.Subsystem = subsytem vecid := m.getFullName(prometheus.Opts(opts), []string{}) if _, ok := cache_allcounters[vecid]; ok { Logger.Warn("Register new counter vector with opts: %v labelNames: %v, name conflicts existing counter", opts, labelNames) return nil } if _, ok := cache_allcountervects[vecid]; !ok { Logger.Debug("Register new counter vector with opts: %v labelNames: %v", opts, labelNames) entry := CounterVec{} entry.Opts = opts entry.Labels = labelNames entry.Vec = promauto.NewCounterVec(prometheus.CounterOpts(entry.Opts), entry.Labels) cache_allcountervects[vecid] = entry } entry := cache_allcountervects[vecid] if strSliceCompare(entry.Labels, labelNames) == false { Logger.Warn("id:%s cached counter vec labels dont match %v != %v", vecid, entry.Labels, labelNames) return nil } valid := m.getFullName(prometheus.Opts(entry.Opts), labelValues) if _, ok := cache_allcounters[valid]; !ok { Logger.Debug("Register new counter from vector with opts: %v labelValues: %v", entry.Opts, labelValues) cache_allcounters[valid] = entry.Vec.WithLabelValues(labelValues...) } return cache_allcounters[valid] } // // // func (m *Metrics) RegisterLabeledCounterGroup(optsgroup []CounterOpts, labelNames []string, labelValues []string, subsytem string) map[string]Counter { c := make(map[string]Counter) for _, opts := range optsgroup { c[opts.Name] = m.RegisterLabeledCounter(opts, labelNames, labelValues, subsytem) } return c } // // // func (m *Metrics) RegisterGauge(opts CounterOpts, subsytem string) Gauge { globalLock.Lock() defer globalLock.Unlock() opts.Namespace = m.Namespace opts.Subsystem = subsytem id := m.getFullName(prometheus.Opts(opts), []string{}) if _, ok := cache_allgaugevects[id]; ok { Logger.Warn("Register new gauge with opts: %v, name conflicts existing gauge vector", opts) return nil } if _, ok := cache_allgauges[id]; !ok { Logger.Debug("Register new gauge with opts: %v", opts) cache_allgauges[id] = promauto.NewGauge(prometheus.GaugeOpts(opts)) } return cache_allgauges[id] } // // // func (m *Metrics) RegisterGaugeGroup(optsgroup []CounterOpts, subsytem string) map[string]Gauge { c := make(map[string]Gauge) for _, opts := range optsgroup { c[opts.Name] = m.RegisterGauge(opts, subsytem) } return c } // // // func (m *Metrics) RegisterLabeledGauge(opts CounterOpts, labelNames []string, labelValues []string, subsytem string) Gauge { globalLock.Lock() defer globalLock.Unlock() opts.Namespace = m.Namespace opts.Subsystem = subsytem vecid := m.getFullName(prometheus.Opts(opts), []string{}) if _, ok := cache_allgauges[vecid]; ok { Logger.Warn("Register new gauge vector with opts: %v labelNames: %v, name conflicts existing counter", opts, labelNames) return nil } if _, ok := cache_allgaugevects[vecid]; !ok { Logger.Debug("Register new gauge vector with opts: %v labelNames: %v", opts, labelNames) entry := GaugeVec{} entry.Opts = opts entry.Labels = labelNames entry.Vec = promauto.NewGaugeVec(prometheus.GaugeOpts(entry.Opts), entry.Labels) cache_allgaugevects[vecid] = entry } entry := cache_allgaugevects[vecid] if strSliceCompare(entry.Labels, labelNames) == false { Logger.Warn("id:%s cached gauge vec labels dont match %v != %v", vecid, entry.Labels, labelNames) return nil } valid := m.getFullName(prometheus.Opts(entry.Opts), labelValues) if _, ok := cache_allgauges[valid]; !ok { Logger.Debug("Register new gauge from vector with opts: %v labelValues: %v", entry.Opts, labelValues) cache_allgauges[valid] = entry.Vec.WithLabelValues(labelValues...) } return cache_allgauges[valid] } // // // func (m *Metrics) RegisterLabeledGaugeGroup(optsgroup []CounterOpts, labelNames []string, labelValues []string, subsytem string) map[string]Gauge { c := make(map[string]Gauge) for _, opts := range optsgroup { c[opts.Name] = m.RegisterLabeledGauge(opts, labelNames, labelValues, subsytem) } return c }