Labeled metrics interface
[ric-plt/xapp-frame.git] / pkg / xapp / metrics.go
1 /*
2 ==================================================================================
3   Copyright (c) 2019 AT&T Intellectual Property.
4   Copyright (c) 2019 Nokia
5
6    Licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17 ==================================================================================
18 */
19
20 package xapp
21
22 import (
23         "fmt"
24         "github.com/gorilla/mux"
25         "github.com/prometheus/client_golang/prometheus"
26         "github.com/prometheus/client_golang/prometheus/promauto"
27         "github.com/prometheus/client_golang/prometheus/promhttp"
28         "sync"
29 )
30
31 //-----------------------------------------------------------------------------
32 // Alias
33 //-----------------------------------------------------------------------------
34 type CounterOpts prometheus.Opts
35 type Counter prometheus.Counter
36 type Gauge prometheus.Gauge
37
38 type CounterVec struct {
39         Vec    *prometheus.CounterVec
40         Opts   CounterOpts
41         Labels []string
42 }
43
44 type GaugeVec struct {
45         Vec    *prometheus.GaugeVec
46         Opts   CounterOpts
47         Labels []string
48 }
49
50 func strSliceCompare(a, b []string) bool {
51         if len(a) != len(b) {
52                 return false
53         }
54         for i, v := range a {
55                 if v != b[i] {
56                         return false
57                 }
58         }
59         return true
60 }
61
62 //-----------------------------------------------------------------------------
63 //
64 //-----------------------------------------------------------------------------
65
66 type MetricGroupsCache struct {
67         sync.RWMutex //This is for map locking
68         counters     map[string]Counter
69         gauges       map[string]Gauge
70 }
71
72 func (met *MetricGroupsCache) CIs(metric string) bool {
73         met.RLock()
74         defer met.RUnlock()
75         _, ok := met.counters[metric]
76         return ok
77 }
78
79 func (met *MetricGroupsCache) CGet(metric string) Counter {
80         met.RLock()
81         defer met.RUnlock()
82         return met.counters[metric]
83 }
84
85 func (met *MetricGroupsCache) CInc(metric string) {
86         met.RLock()
87         defer met.RUnlock()
88         met.counters[metric].Inc()
89 }
90
91 func (met *MetricGroupsCache) CAdd(metric string, val float64) {
92         met.RLock()
93         defer met.RUnlock()
94         met.counters[metric].Add(val)
95 }
96
97 func (met *MetricGroupsCache) GIs(metric string) bool {
98         met.RLock()
99         defer met.RUnlock()
100         _, ok := met.gauges[metric]
101         return ok
102 }
103
104 func (met *MetricGroupsCache) GGet(metric string) Gauge {
105         met.RLock()
106         defer met.RUnlock()
107         return met.gauges[metric]
108 }
109
110 func (met *MetricGroupsCache) GSet(metric string, val float64) {
111         met.RLock()
112         defer met.RUnlock()
113         met.gauges[metric].Set(val)
114 }
115
116 func (met *MetricGroupsCache) GAdd(metric string, val float64) {
117         met.RLock()
118         defer met.RUnlock()
119         met.gauges[metric].Add(val)
120 }
121
122 func (met *MetricGroupsCache) GInc(metric string) {
123         met.RLock()
124         defer met.RUnlock()
125         met.gauges[metric].Inc()
126 }
127
128 func (met *MetricGroupsCache) GDec(metric string) {
129         met.RLock()
130         defer met.RUnlock()
131         met.gauges[metric].Dec()
132 }
133
134 func (met *MetricGroupsCache) CombineCounterGroupsWithPrefix(prefix string, srcs ...map[string]Counter) {
135         met.Lock()
136         defer met.Unlock()
137         for _, src := range srcs {
138                 for k, v := range src {
139                         met.counters[prefix+k] = v
140                 }
141         }
142 }
143
144 func (met *MetricGroupsCache) CombineCounterGroups(srcs ...map[string]Counter) {
145         met.Lock()
146         defer met.Unlock()
147         for _, src := range srcs {
148                 for k, v := range src {
149                         met.counters[k] = v
150                 }
151         }
152 }
153
154 func (met *MetricGroupsCache) CombineGaugeGroupsWithPrefix(prefix string, srcs ...map[string]Gauge) {
155         met.Lock()
156         defer met.Unlock()
157         for _, src := range srcs {
158                 for k, v := range src {
159                         met.gauges[prefix+k] = v
160                 }
161         }
162 }
163
164 func (met *MetricGroupsCache) CombineGaugeGroups(srcs ...map[string]Gauge) {
165         met.Lock()
166         defer met.Unlock()
167         for _, src := range srcs {
168                 for k, v := range src {
169                         met.gauges[k] = v
170                 }
171         }
172 }
173
174 func NewMetricGroupsCache() *MetricGroupsCache {
175         entry := &MetricGroupsCache{}
176         entry.counters = make(map[string]Counter)
177         entry.gauges = make(map[string]Gauge)
178         return entry
179 }
180
181 //-----------------------------------------------------------------------------
182 // All counters/gauges registered via Metrics instances:
183 // Counter names are build from: namespace, subsystem, metric and possible labels
184 //-----------------------------------------------------------------------------
185 var globalLock sync.Mutex
186 var cache_allcounters map[string]Counter
187 var cache_allgauges map[string]Gauge
188 var cache_allcountervects map[string]CounterVec
189 var cache_allgaugevects map[string]GaugeVec
190
191 func init() {
192         cache_allcounters = make(map[string]Counter)
193         cache_allgauges = make(map[string]Gauge)
194         cache_allcountervects = make(map[string]CounterVec)
195         cache_allgaugevects = make(map[string]GaugeVec)
196 }
197
198 //-----------------------------------------------------------------------------
199 //
200 //-----------------------------------------------------------------------------
201 type Metrics struct {
202         Namespace string
203 }
204
205 func NewMetrics(url, namespace string, r *mux.Router) *Metrics {
206         if url == "" {
207                 url = "/ric/v1/metrics"
208         }
209         if namespace == "" {
210                 namespace = "ricxapp"
211         }
212
213         Logger.Info("Serving metrics on: url=%s namespace=%s", url, namespace)
214
215         // Expose 'metrics' endpoint with standard golang metrics used by prometheus
216         r.Handle(url, promhttp.Handler())
217
218         return &Metrics{Namespace: namespace}
219 }
220
221 /*
222  * Helpers
223  */
224 func (m *Metrics) getFullName(opts prometheus.Opts, labels []string) string {
225         labelname := ""
226         for _, lbl := range labels {
227                 if len(labelname) == 0 {
228                         labelname += lbl
229                 } else {
230                         labelname += "_" + lbl
231                 }
232         }
233         return fmt.Sprintf("%s_%s_%s_%s", opts.Namespace, opts.Subsystem, opts.Name, labelname)
234 }
235
236 //
237 //
238 //
239 func (m *Metrics) RegisterCounter(opts CounterOpts, subsytem string) Counter {
240         globalLock.Lock()
241         defer globalLock.Unlock()
242         opts.Namespace = m.Namespace
243         opts.Subsystem = subsytem
244         id := m.getFullName(prometheus.Opts(opts), []string{})
245         if _, ok := cache_allcounters[id]; !ok {
246                 Logger.Info("Register new counter with opts: %v", opts)
247                 cache_allcounters[id] = promauto.NewCounter(prometheus.CounterOpts(opts))
248         }
249         return cache_allcounters[id]
250 }
251
252 //
253 //
254 //
255 func (m *Metrics) RegisterCounterGroup(optsgroup []CounterOpts, subsytem string) map[string]Counter {
256         c := make(map[string]Counter)
257         for _, opts := range optsgroup {
258                 c[opts.Name] = m.RegisterCounter(opts, subsytem)
259         }
260         return c
261 }
262
263 //
264 //
265 //
266 func (m *Metrics) RegisterLabeledCounter(opts CounterOpts, labelNames []string, labelValues []string, subsytem string) Counter {
267         globalLock.Lock()
268         defer globalLock.Unlock()
269         opts.Namespace = m.Namespace
270         opts.Subsystem = subsytem
271         vecid := m.getFullName(prometheus.Opts(opts), []string{})
272         if _, ok := cache_allcountervects[vecid]; !ok {
273                 Logger.Info("Register new counter vector with opts: %v labelNames: %v", opts, labelNames)
274                 entry := CounterVec{}
275                 entry.Opts = opts
276                 entry.Labels = labelNames
277                 entry.Vec = promauto.NewCounterVec(prometheus.CounterOpts(entry.Opts), entry.Labels)
278                 cache_allcountervects[vecid] = entry
279         }
280         entry := cache_allcountervects[vecid]
281         if strSliceCompare(entry.Labels, labelNames) == false {
282                 Logger.Warn("id:%s cached counter vec labels dont match %v != %v", vecid, entry.Labels, labelNames)
283         }
284
285         valid := m.getFullName(prometheus.Opts(entry.Opts), labelValues)
286         if _, ok := cache_allcounters[valid]; !ok {
287                 Logger.Info("Register new counter from vector with opts: %v labelValues: %v", entry.Opts, labelValues)
288                 cache_allcounters[valid] = entry.Vec.WithLabelValues(labelValues...)
289         }
290         return cache_allcounters[valid]
291
292 }
293
294 //
295 //
296 //
297 func (m *Metrics) RegisterLabeledCounterGroup(optsgroup []CounterOpts, labelNames []string, labelValues []string, subsytem string) map[string]Counter {
298         c := make(map[string]Counter)
299         for _, opts := range optsgroup {
300                 c[opts.Name] = m.RegisterLabeledCounter(opts, labelNames, labelValues, subsytem)
301         }
302         return c
303 }
304
305 //
306 //
307 //
308 func (m *Metrics) RegisterGauge(opts CounterOpts, subsytem string) Gauge {
309         globalLock.Lock()
310         defer globalLock.Unlock()
311         opts.Namespace = m.Namespace
312         opts.Subsystem = subsytem
313         id := m.getFullName(prometheus.Opts(opts), []string{})
314         if _, ok := cache_allgauges[id]; !ok {
315                 Logger.Info("Register new gauge with opts: %v", opts)
316                 cache_allgauges[id] = promauto.NewGauge(prometheus.GaugeOpts(opts))
317         }
318         return cache_allgauges[id]
319 }
320
321 //
322 //
323 //
324 func (m *Metrics) RegisterGaugeGroup(optsgroup []CounterOpts, subsytem string) map[string]Gauge {
325         c := make(map[string]Gauge)
326         for _, opts := range optsgroup {
327                 c[opts.Name] = m.RegisterGauge(opts, subsytem)
328         }
329         return c
330 }
331
332 //
333 //
334 //
335 func (m *Metrics) RegisterLabeledGauge(opt CounterOpts, labelNames []string, labelValues []string, subsytem string) Gauge {
336         globalLock.Lock()
337         defer globalLock.Unlock()
338         opt.Namespace = m.Namespace
339         opt.Subsystem = subsytem
340         vecid := m.getFullName(prometheus.Opts(opt), []string{})
341         if _, ok := cache_allgaugevects[vecid]; !ok {
342                 Logger.Info("Register new gauge vector with opt: %v labelNames: %v", opt, labelNames)
343                 entry := GaugeVec{}
344                 entry.Opts = opt
345                 entry.Labels = labelNames
346                 entry.Vec = promauto.NewGaugeVec(prometheus.GaugeOpts(entry.Opts), entry.Labels)
347                 cache_allgaugevects[vecid] = entry
348         }
349         entry := cache_allgaugevects[vecid]
350         if strSliceCompare(entry.Labels, labelNames) == false {
351                 Logger.Warn("id:%s cached gauge vec labels dont match %v != %v", vecid, entry.Labels, labelNames)
352         }
353         valid := m.getFullName(prometheus.Opts(entry.Opts), labelValues)
354         if _, ok := cache_allgauges[valid]; !ok {
355                 Logger.Info("Register new gauge from vector with opts: %v labelValues: %v", entry.Opts, labelValues)
356                 cache_allgauges[valid] = entry.Vec.WithLabelValues(labelValues...)
357         }
358         return cache_allgauges[valid]
359
360 }
361
362 //
363 //
364 //
365 func (m *Metrics) RegisterLabeledGaugeGroup(opts []CounterOpts, labelNames []string, labelValues []string, subsytem string) map[string]Gauge {
366         c := make(map[string]Gauge)
367         for _, opt := range opts {
368                 c[opt.Name] = m.RegisterLabeledGauge(opt, labelNames, labelValues, subsytem)
369         }
370         return c
371 }
372
373 /*
374  * Handling counter vectors
375  *
376  * Examples:
377
378   //---------
379         vec := Metric.RegisterCounterVec(
380                 CounterOpts{Name: "counter0", Help: "counter0"},
381                 []string{"host"},
382                 "SUBSYSTEM")
383
384         stat:=Metric.GetCounterFromVect([]string{"localhost:8888"},vec)
385         stat.Inc()
386
387   //---------
388         vec := Metric.RegisterCounterVecGroup(
389                 []CounterOpts{
390                         {Name: "counter1", Help: "counter1"},
391                         {Name: "counter2", Help: "counter2"},
392                 },
393                 []string{"host"},
394                 "SUBSYSTEM")
395
396         stats:=Metric.GetCounterGroupFromVects([]string{"localhost:8888"}, vec)
397         stats["counter1"].Inc()
398 */
399
400 // Deprecated: Use RegisterLabeledCounter
401 func (m *Metrics) RegisterCounterVec(opts CounterOpts, labelNames []string, subsytem string) CounterVec {
402         globalLock.Lock()
403         defer globalLock.Unlock()
404         opts.Namespace = m.Namespace
405         opts.Subsystem = subsytem
406         id := m.getFullName(prometheus.Opts(opts), []string{})
407         if _, ok := cache_allcountervects[id]; !ok {
408                 Logger.Info("Register new counter vector with opts: %v labelNames: %v", opts, labelNames)
409                 entry := CounterVec{}
410                 entry.Opts = opts
411                 entry.Labels = labelNames
412                 entry.Vec = promauto.NewCounterVec(prometheus.CounterOpts(entry.Opts), entry.Labels)
413                 cache_allcountervects[id] = entry
414         }
415         entry := cache_allcountervects[id]
416         if strSliceCompare(entry.Labels, labelNames) == false {
417                 Logger.Warn("id:%s cached counter vec labels dont match %v != %v", id, entry.Labels, labelNames)
418         }
419         return entry
420 }
421
422 // Deprecated: Use RegisterLabeledCounterGroup
423 func (m *Metrics) RegisterCounterVecGroup(optsgroup []CounterOpts, labelNames []string, subsytem string) map[string]CounterVec {
424         c := make(map[string]CounterVec)
425         for _, opts := range optsgroup {
426                 c[opts.Name] = m.RegisterCounterVec(opts, labelNames, subsytem)
427         }
428         return c
429 }
430
431 // Deprecated: Use RegisterLabeledCounter
432 func (m *Metrics) GetCounterFromVect(labelValues []string, vec CounterVec) (c Counter) {
433         globalLock.Lock()
434         defer globalLock.Unlock()
435         id := m.getFullName(prometheus.Opts(vec.Opts), labelValues)
436         if _, ok := cache_allcounters[id]; !ok {
437                 Logger.Info("Register new counter from vector with opts: %v labelValues: %v", vec.Opts, labelValues)
438                 cache_allcounters[id] = vec.Vec.WithLabelValues(labelValues...)
439         }
440         return cache_allcounters[id]
441 }
442
443 // Deprecated: Use RegisterLabeledCounterGroup
444 func (m *Metrics) GetCounterGroupFromVects(labelValues []string, vects ...map[string]CounterVec) map[string]Counter {
445         c := make(map[string]Counter)
446         for _, vect := range vects {
447                 for name, vec := range vect {
448                         c[name] = m.GetCounterFromVect(labelValues, vec)
449                 }
450         }
451         return c
452 }
453
454 // Deprecated: Use RegisterLabeledCounterGroup
455 func (m *Metrics) GetCounterGroupFromVectsWithPrefix(prefix string, labelValues []string, vects ...map[string]CounterVec) map[string]Counter {
456         c := make(map[string]Counter)
457         for _, vect := range vects {
458                 for name, vec := range vect {
459                         c[prefix+name] = m.GetCounterFromVect(labelValues, vec)
460                 }
461         }
462         return c
463 }
464
465 /*
466  * Handling gauge vectors
467  *
468  * Examples:
469
470   //---------
471         vec := Metric.RegisterGaugeVec(
472                 CounterOpts{Name: "gauge0", Help: "gauge0"},
473                 []string{"host"},
474                 "SUBSYSTEM")
475
476         stat:=Metric.GetGaugeFromVect([]string{"localhost:8888"},vec)
477         stat.Inc()
478
479   //---------
480         vecgrp := Metric.RegisterGaugeVecGroup(
481                 []CounterOpts{
482                         {Name: "gauge1", Help: "gauge1"},
483                         {Name: "gauge2", Help: "gauge2"},
484                 },
485                 []string{"host"},
486                 "SUBSYSTEM")
487
488         stats:=Metric.GetGaugeGroupFromVects([]string{"localhost:8888"},vecgrp)
489         stats["gauge1"].Inc()
490 */
491
492 // Deprecated: Use RegisterLabeledGauge
493 func (m *Metrics) RegisterGaugeVec(opt CounterOpts, labelNames []string, subsytem string) GaugeVec {
494         globalLock.Lock()
495         defer globalLock.Unlock()
496         opt.Namespace = m.Namespace
497         opt.Subsystem = subsytem
498         id := m.getFullName(prometheus.Opts(opt), []string{})
499         if _, ok := cache_allgaugevects[id]; !ok {
500                 Logger.Info("Register new gauge vector with opt: %v labelNames: %v", opt, labelNames)
501                 entry := GaugeVec{}
502                 entry.Opts = opt
503                 entry.Labels = labelNames
504                 entry.Vec = promauto.NewGaugeVec(prometheus.GaugeOpts(entry.Opts), entry.Labels)
505                 cache_allgaugevects[id] = entry
506         }
507         entry := cache_allgaugevects[id]
508         if strSliceCompare(entry.Labels, labelNames) == false {
509                 Logger.Warn("id:%s cached gauge vec labels dont match %v != %v", id, entry.Labels, labelNames)
510         }
511         return entry
512 }
513
514 // Deprecated: Use RegisterLabeledGaugeGroup
515 func (m *Metrics) RegisterGaugeVecGroup(opts []CounterOpts, labelNames []string, subsytem string) map[string]GaugeVec {
516         c := make(map[string]GaugeVec)
517         for _, opt := range opts {
518                 c[opt.Name] = m.RegisterGaugeVec(opt, labelNames, subsytem)
519         }
520         return c
521 }
522
523 // Deprecated: Use RegisterLabeledGauge
524 func (m *Metrics) GetGaugeFromVect(labelValues []string, vec GaugeVec) Gauge {
525         globalLock.Lock()
526         defer globalLock.Unlock()
527         id := m.getFullName(prometheus.Opts(vec.Opts), labelValues)
528         if _, ok := cache_allgauges[id]; !ok {
529                 Logger.Info("Register new gauge from vector with opts: %v labelValues: %v", vec.Opts, labelValues)
530                 cache_allgauges[id] = vec.Vec.WithLabelValues(labelValues...)
531         }
532         return cache_allgauges[id]
533 }
534
535 // Deprecated: Use RegisterLabeledGaugeGroup
536 func (m *Metrics) GetGaugeGroupFromVects(labelValues []string, vects ...map[string]GaugeVec) map[string]Gauge {
537         c := make(map[string]Gauge)
538         for _, vect := range vects {
539                 for name, vec := range vect {
540                         c[name] = m.GetGaugeFromVect(labelValues, vec)
541                 }
542         }
543         return c
544 }
545
546 // Deprecated: Use RegisterLabeledGaugeGroup
547 func (m *Metrics) GetGaugeGroupFromVectsWithPrefix(prefix string, labelValues []string, vects ...map[string]GaugeVec) map[string]Gauge {
548         c := make(map[string]Gauge)
549         for _, vect := range vects {
550                 for name, vec := range vect {
551                         c[prefix+name] = m.GetGaugeFromVect(labelValues, vec)
552                 }
553         }
554         return c
555 }