14285: Merge branch 'master' into 14285-keep-balance-metrics
[arvados.git] / services / keep-balance / metrics.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "fmt"
9         "net/http"
10         "sync"
11
12         "github.com/prometheus/client_golang/prometheus"
13         "github.com/prometheus/client_golang/prometheus/promhttp"
14 )
15
16 type observer interface{ Observe(float64) }
17 type setter interface{ Set(float64) }
18
19 type metrics struct {
20         reg         *prometheus.Registry
21         statsGauges map[string]setter
22         observers   map[string]observer
23         setupOnce   sync.Once
24         mtx         sync.Mutex
25 }
26
27 func newMetrics() *metrics {
28         return &metrics{
29                 reg:         prometheus.NewRegistry(),
30                 statsGauges: map[string]setter{},
31                 observers:   map[string]observer{},
32         }
33 }
34
35 func (m *metrics) DurationObserver(name, help string) observer {
36         m.mtx.Lock()
37         defer m.mtx.Unlock()
38         if obs, ok := m.observers[name]; ok {
39                 return obs
40         }
41         summary := prometheus.NewSummary(prometheus.SummaryOpts{
42                 Name:      name,
43                 Subsystem: "keepbalance",
44                 Help:      help,
45         })
46         m.reg.MustRegister(summary)
47         m.observers[name] = summary
48         return summary
49 }
50
51 // UpdateStats updates prometheus metrics using the given
52 // balancerStats. It creates and registers the needed gauges on its
53 // first invocation.
54 func (m *metrics) UpdateStats(s balancerStats) {
55         type gauge struct {
56                 Value interface{}
57                 Help  string
58         }
59         s2g := map[string]gauge{
60                 "total":           {s.current, "current backend storage usage"},
61                 "garbage":         {s.garbage, "garbage (unreferenced, old)"},
62                 "transient":       {s.unref, "transient (unreferenced, new)"},
63                 "overreplicated":  {s.overrep, "overreplicated"},
64                 "underreplicated": {s.underrep, "underreplicated"},
65                 "lost":            {s.lost, "lost"},
66         }
67         m.setupOnce.Do(func() {
68                 // Register gauge(s) for each balancerStats field.
69                 addGauge := func(name, help string) {
70                         g := prometheus.NewGauge(prometheus.GaugeOpts{
71                                 Name:      name,
72                                 Subsystem: "keep",
73                                 Help:      help,
74                         })
75                         m.reg.MustRegister(g)
76                         m.statsGauges[name] = g
77                 }
78                 for name, gauge := range s2g {
79                         switch gauge.Value.(type) {
80                         case blocksNBytes:
81                                 for _, sub := range []string{"blocks", "bytes", "replicas"} {
82                                         addGauge(name+"_"+sub, sub+" of "+gauge.Help)
83                                 }
84                         case int, int64:
85                                 addGauge(name, gauge.Help)
86                         default:
87                                 panic(fmt.Sprintf("bad gauge type %T", gauge.Value))
88                         }
89                 }
90         })
91         // Set gauges to values from s.
92         for name, gauge := range s2g {
93                 switch val := gauge.Value.(type) {
94                 case blocksNBytes:
95                         m.statsGauges[name+"_blocks"].Set(float64(val.blocks))
96                         m.statsGauges[name+"_bytes"].Set(float64(val.bytes))
97                         m.statsGauges[name+"_replicas"].Set(float64(val.replicas))
98                 case int:
99                         m.statsGauges[name].Set(float64(val))
100                 case int64:
101                         m.statsGauges[name].Set(float64(val))
102                 default:
103                         panic(fmt.Sprintf("bad gauge type %T", gauge.Value))
104                 }
105         }
106 }
107
108 func (m *metrics) Handler(log promhttp.Logger) http.Handler {
109         return promhttp.HandlerFor(m.reg, promhttp.HandlerOpts{
110                 ErrorLog: log,
111         })
112 }