Improved metrics
[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  * Handling counters
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 func (m *Metrics) RegisterCounterGroup(optsgroup []CounterOpts, subsytem string) map[string]Counter {
253         c := make(map[string]Counter)
254         for _, opts := range optsgroup {
255                 c[opts.Name] = m.RegisterCounter(opts, subsytem)
256         }
257         return c
258 }
259
260 /*
261  * Handling gauges
262  */
263 func (m *Metrics) RegisterGauge(opts CounterOpts, subsytem string) Gauge {
264         globalLock.Lock()
265         defer globalLock.Unlock()
266         opts.Namespace = m.Namespace
267         opts.Subsystem = subsytem
268         id := m.getFullName(prometheus.Opts(opts), []string{})
269         if _, ok := cache_allgauges[id]; !ok {
270                 Logger.Info("Register new gauge with opts: %v", opts)
271                 cache_allgauges[id] = promauto.NewGauge(prometheus.GaugeOpts(opts))
272         }
273         return cache_allgauges[id]
274 }
275
276 func (m *Metrics) RegisterGaugeGroup(optsgroup []CounterOpts, subsytem string) map[string]Gauge {
277         c := make(map[string]Gauge)
278         for _, opts := range optsgroup {
279                 c[opts.Name] = m.RegisterGauge(opts, subsytem)
280         }
281         return c
282 }
283
284 /*
285  * Handling counter vectors
286  *
287  * Examples:
288
289   //---------
290         vec := Metric.RegisterCounterVec(
291                 CounterOpts{Name: "counter0", Help: "counter0"},
292                 []string{"host"},
293                 "SUBSYSTEM")
294
295         stat:=Metric.GetCounterFromVect([]string{"localhost:8888"},vec)
296         stat.Inc()
297
298   //---------
299         vec := Metric.RegisterCounterVecGroup(
300                 []CounterOpts{
301                         {Name: "counter1", Help: "counter1"},
302                         {Name: "counter2", Help: "counter2"},
303                 },
304                 []string{"host"},
305                 "SUBSYSTEM")
306
307         stats:=Metric.GetCounterGroupFromVects([]string{"localhost:8888"}, vec)
308         stats["counter1"].Inc()
309 */
310
311 func (m *Metrics) RegisterCounterVec(opts CounterOpts, labelNames []string, subsytem string) CounterVec {
312         globalLock.Lock()
313         defer globalLock.Unlock()
314         opts.Namespace = m.Namespace
315         opts.Subsystem = subsytem
316         id := m.getFullName(prometheus.Opts(opts), []string{})
317         if _, ok := cache_allcountervects[id]; !ok {
318                 Logger.Info("Register new counter vector with opts: %v labelNames: %v", opts, labelNames)
319                 entry := CounterVec{}
320                 entry.Opts = opts
321                 entry.Labels = labelNames
322                 entry.Vec = promauto.NewCounterVec(prometheus.CounterOpts(entry.Opts), entry.Labels)
323                 cache_allcountervects[id] = entry
324         }
325         entry := cache_allcountervects[id]
326         if strSliceCompare(entry.Labels, labelNames) == false {
327                 Logger.Warn("id:%s cached counter vec labels dont match %v != %v", id, entry.Labels, labelNames)
328         }
329         return entry
330 }
331
332 func (m *Metrics) RegisterCounterVecGroup(optsgroup []CounterOpts, labelNames []string, subsytem string) map[string]CounterVec {
333         c := make(map[string]CounterVec)
334         for _, opts := range optsgroup {
335                 c[opts.Name] = m.RegisterCounterVec(opts, labelNames, subsytem)
336         }
337         return c
338 }
339
340 func (m *Metrics) GetCounterFromVect(labelValues []string, vec CounterVec) (c Counter) {
341         globalLock.Lock()
342         defer globalLock.Unlock()
343         id := m.getFullName(prometheus.Opts(vec.Opts), labelValues)
344         if _, ok := cache_allcounters[id]; !ok {
345                 Logger.Info("Register new counter from vector with opts: %v labelValues: %v", vec.Opts, labelValues)
346                 cache_allcounters[id] = vec.Vec.WithLabelValues(labelValues...)
347         }
348         return cache_allcounters[id]
349 }
350
351 func (m *Metrics) GetCounterGroupFromVects(labelValues []string, vects ...map[string]CounterVec) map[string]Counter {
352         c := make(map[string]Counter)
353         for _, vect := range vects {
354                 for name, vec := range vect {
355                         c[name] = m.GetCounterFromVect(labelValues, vec)
356                 }
357         }
358         return c
359 }
360
361 func (m *Metrics) GetCounterGroupFromVectsWithPrefix(prefix string, labelValues []string, vects ...map[string]CounterVec) map[string]Counter {
362         c := make(map[string]Counter)
363         for _, vect := range vects {
364                 for name, vec := range vect {
365                         c[prefix+name] = m.GetCounterFromVect(labelValues, vec)
366                 }
367         }
368         return c
369 }
370
371 /*
372  * Handling gauge vectors
373  *
374  * Examples:
375
376   //---------
377         vec := Metric.RegisterGaugeVec(
378                 CounterOpts{Name: "gauge0", Help: "gauge0"},
379                 []string{"host"},
380                 "SUBSYSTEM")
381
382         stat:=Metric.GetGaugeFromVect([]string{"localhost:8888"},vec)
383         stat.Inc()
384
385   //---------
386         vecgrp := Metric.RegisterGaugeVecGroup(
387                 []CounterOpts{
388                         {Name: "gauge1", Help: "gauge1"},
389                         {Name: "gauge2", Help: "gauge2"},
390                 },
391                 []string{"host"},
392                 "SUBSYSTEM")
393
394         stats:=Metric.GetGaugeGroupFromVects([]string{"localhost:8888"},vecgrp)
395         stats["gauge1"].Inc()
396 */
397
398 func (m *Metrics) RegisterGaugeVec(opt CounterOpts, labelNames []string, subsytem string) GaugeVec {
399         globalLock.Lock()
400         defer globalLock.Unlock()
401         opt.Namespace = m.Namespace
402         opt.Subsystem = subsytem
403         id := m.getFullName(prometheus.Opts(opt), []string{})
404         if _, ok := cache_allgaugevects[id]; !ok {
405                 Logger.Info("Register new gauge vector with opt: %v labelNames: %v", opt, labelNames)
406                 entry := GaugeVec{}
407                 entry.Opts = opt
408                 entry.Labels = labelNames
409                 entry.Vec = promauto.NewGaugeVec(prometheus.GaugeOpts(entry.Opts), entry.Labels)
410                 cache_allgaugevects[id] = entry
411         }
412         entry := cache_allgaugevects[id]
413         if strSliceCompare(entry.Labels, labelNames) == false {
414                 Logger.Warn("id:%s cached gauge vec labels dont match %v != %v", id, entry.Labels, labelNames)
415         }
416         return entry
417 }
418
419 func (m *Metrics) RegisterGaugeVecGroup(opts []CounterOpts, labelNames []string, subsytem string) map[string]GaugeVec {
420         c := make(map[string]GaugeVec)
421         for _, opt := range opts {
422                 c[opt.Name] = m.RegisterGaugeVec(opt, labelNames, subsytem)
423         }
424         return c
425 }
426
427 func (m *Metrics) GetGaugeFromVect(labelValues []string, vec GaugeVec) Gauge {
428         globalLock.Lock()
429         defer globalLock.Unlock()
430         id := m.getFullName(prometheus.Opts(vec.Opts), labelValues)
431         if _, ok := cache_allgauges[id]; !ok {
432                 Logger.Info("Register new gauge from vector with opts: %v labelValues: %v", vec.Opts, labelValues)
433                 cache_allgauges[id] = vec.Vec.WithLabelValues(labelValues...)
434         }
435         return cache_allgauges[id]
436 }
437
438 func (m *Metrics) GetGaugeGroupFromVects(labelValues []string, vects ...map[string]GaugeVec) map[string]Gauge {
439         c := make(map[string]Gauge)
440         for _, vect := range vects {
441                 for name, vec := range vect {
442                         c[name] = m.GetGaugeFromVect(labelValues, vec)
443                 }
444         }
445         return c
446 }
447
448 func (m *Metrics) GetGaugeGroupFromVectsWithPrefix(prefix string, labelValues []string, vects ...map[string]GaugeVec) map[string]Gauge {
449         c := make(map[string]Gauge)
450         for _, vect := range vects {
451                 for name, vec := range vect {
452                         c[prefix+name] = m.GetGaugeFromVect(labelValues, vec)
453                 }
454         }
455         return c
456 }