1 package metrics
2
3 import (
4 "fmt"
5 "sync"
6
7 "github.com/prometheus/client_golang/prometheus"
8 )
9
10 type Labels map[string]string
11
12
13
14
15
16
17 func NewNamespace(name, subsystem string, labels Labels) *Namespace {
18 if labels == nil {
19 labels = make(map[string]string)
20 }
21 return &Namespace{
22 name: name,
23 subsystem: subsystem,
24 labels: labels,
25 }
26 }
27
28
29 type Namespace struct {
30 name string
31 subsystem string
32 labels Labels
33 mu sync.Mutex
34 metrics []prometheus.Collector
35 }
36
37
38
39
40
41
42 func (n *Namespace) WithConstLabels(labels Labels) *Namespace {
43 n.mu.Lock()
44 ns := &Namespace{
45 name: n.name,
46 subsystem: n.subsystem,
47 labels: mergeLabels(n.labels, labels),
48 }
49 n.mu.Unlock()
50 return ns
51 }
52
53 func (n *Namespace) NewCounter(name, help string) Counter {
54 c := &counter{pc: prometheus.NewCounter(n.newCounterOpts(name, help))}
55 n.Add(c)
56 return c
57 }
58
59 func (n *Namespace) NewLabeledCounter(name, help string, labels ...string) LabeledCounter {
60 c := &labeledCounter{pc: prometheus.NewCounterVec(n.newCounterOpts(name, help), labels)}
61 n.Add(c)
62 return c
63 }
64
65 func (n *Namespace) newCounterOpts(name, help string) prometheus.CounterOpts {
66 return prometheus.CounterOpts{
67 Namespace: n.name,
68 Subsystem: n.subsystem,
69 Name: makeName(name, Total),
70 Help: help,
71 ConstLabels: prometheus.Labels(n.labels),
72 }
73 }
74
75 func (n *Namespace) NewTimer(name, help string) Timer {
76 t := &timer{
77 m: prometheus.NewHistogram(n.newTimerOpts(name, help)),
78 }
79 n.Add(t)
80 return t
81 }
82
83 func (n *Namespace) NewLabeledTimer(name, help string, labels ...string) LabeledTimer {
84 t := &labeledTimer{
85 m: prometheus.NewHistogramVec(n.newTimerOpts(name, help), labels),
86 }
87 n.Add(t)
88 return t
89 }
90
91 func (n *Namespace) newTimerOpts(name, help string) prometheus.HistogramOpts {
92 return prometheus.HistogramOpts{
93 Namespace: n.name,
94 Subsystem: n.subsystem,
95 Name: makeName(name, Seconds),
96 Help: help,
97 ConstLabels: prometheus.Labels(n.labels),
98 }
99 }
100
101 func (n *Namespace) NewGauge(name, help string, unit Unit) Gauge {
102 g := &gauge{
103 pg: prometheus.NewGauge(n.newGaugeOpts(name, help, unit)),
104 }
105 n.Add(g)
106 return g
107 }
108
109 func (n *Namespace) NewLabeledGauge(name, help string, unit Unit, labels ...string) LabeledGauge {
110 g := &labeledGauge{
111 pg: prometheus.NewGaugeVec(n.newGaugeOpts(name, help, unit), labels),
112 }
113 n.Add(g)
114 return g
115 }
116
117 func (n *Namespace) newGaugeOpts(name, help string, unit Unit) prometheus.GaugeOpts {
118 return prometheus.GaugeOpts{
119 Namespace: n.name,
120 Subsystem: n.subsystem,
121 Name: makeName(name, unit),
122 Help: help,
123 ConstLabels: prometheus.Labels(n.labels),
124 }
125 }
126
127 func (n *Namespace) Describe(ch chan<- *prometheus.Desc) {
128 n.mu.Lock()
129 defer n.mu.Unlock()
130
131 for _, metric := range n.metrics {
132 metric.Describe(ch)
133 }
134 }
135
136 func (n *Namespace) Collect(ch chan<- prometheus.Metric) {
137 n.mu.Lock()
138 defer n.mu.Unlock()
139
140 for _, metric := range n.metrics {
141 metric.Collect(ch)
142 }
143 }
144
145 func (n *Namespace) Add(collector prometheus.Collector) {
146 n.mu.Lock()
147 n.metrics = append(n.metrics, collector)
148 n.mu.Unlock()
149 }
150
151 func (n *Namespace) NewDesc(name, help string, unit Unit, labels ...string) *prometheus.Desc {
152 name = makeName(name, unit)
153 namespace := n.name
154 if n.subsystem != "" {
155 namespace = fmt.Sprintf("%s_%s", namespace, n.subsystem)
156 }
157 name = fmt.Sprintf("%s_%s", namespace, name)
158 return prometheus.NewDesc(name, help, labels, prometheus.Labels(n.labels))
159 }
160
161
162
163 func mergeLabels(lbs ...Labels) Labels {
164 merged := make(Labels)
165
166 for _, target := range lbs {
167 for k, v := range target {
168 merged[k] = v
169 }
170 }
171
172 return merged
173 }
174
175 func makeName(name string, unit Unit) string {
176 if unit == "" {
177 return name
178 }
179
180 return fmt.Sprintf("%s_%s", name, unit)
181 }
182
183 func (n *Namespace) NewDefaultHttpMetrics(handlerName string) []*HTTPMetric {
184 return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
185 DurationBuckets: defaultDurationBuckets,
186 RequestSizeBuckets: defaultResponseSizeBuckets,
187 ResponseSizeBuckets: defaultResponseSizeBuckets,
188 })
189 }
190
191 func (n *Namespace) NewHttpMetrics(handlerName string, durationBuckets, requestSizeBuckets, responseSizeBuckets []float64) []*HTTPMetric {
192 return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
193 DurationBuckets: durationBuckets,
194 RequestSizeBuckets: requestSizeBuckets,
195 ResponseSizeBuckets: responseSizeBuckets,
196 })
197 }
198
199 func (n *Namespace) NewHttpMetricsWithOpts(handlerName string, opts HTTPHandlerOpts) []*HTTPMetric {
200 var httpMetrics []*HTTPMetric
201 inFlightMetric := n.NewInFlightGaugeMetric(handlerName)
202 requestTotalMetric := n.NewRequestTotalMetric(handlerName)
203 requestDurationMetric := n.NewRequestDurationMetric(handlerName, opts.DurationBuckets)
204 requestSizeMetric := n.NewRequestSizeMetric(handlerName, opts.RequestSizeBuckets)
205 responseSizeMetric := n.NewResponseSizeMetric(handlerName, opts.ResponseSizeBuckets)
206 httpMetrics = append(httpMetrics, inFlightMetric, requestDurationMetric, requestTotalMetric, requestSizeMetric, responseSizeMetric)
207 return httpMetrics
208 }
209
210 func (n *Namespace) NewInFlightGaugeMetric(handlerName string) *HTTPMetric {
211 labels := prometheus.Labels(n.labels)
212 labels["handler"] = handlerName
213 metric := prometheus.NewGauge(prometheus.GaugeOpts{
214 Namespace: n.name,
215 Subsystem: n.subsystem,
216 Name: "in_flight_requests",
217 Help: "The in-flight HTTP requests",
218 ConstLabels: prometheus.Labels(labels),
219 })
220 httpMetric := &HTTPMetric{
221 Collector: metric,
222 handlerType: InstrumentHandlerInFlight,
223 }
224 n.Add(httpMetric)
225 return httpMetric
226 }
227
228 func (n *Namespace) NewRequestTotalMetric(handlerName string) *HTTPMetric {
229 labels := prometheus.Labels(n.labels)
230 labels["handler"] = handlerName
231 metric := prometheus.NewCounterVec(
232 prometheus.CounterOpts{
233 Namespace: n.name,
234 Subsystem: n.subsystem,
235 Name: "requests_total",
236 Help: "Total number of HTTP requests made.",
237 ConstLabels: prometheus.Labels(labels),
238 },
239 []string{"code", "method"},
240 )
241 httpMetric := &HTTPMetric{
242 Collector: metric,
243 handlerType: InstrumentHandlerCounter,
244 }
245 n.Add(httpMetric)
246 return httpMetric
247 }
248 func (n *Namespace) NewRequestDurationMetric(handlerName string, buckets []float64) *HTTPMetric {
249 if len(buckets) == 0 {
250 panic("DurationBuckets must be provided")
251 }
252 labels := prometheus.Labels(n.labels)
253 labels["handler"] = handlerName
254 opts := prometheus.HistogramOpts{
255 Namespace: n.name,
256 Subsystem: n.subsystem,
257 Name: "request_duration_seconds",
258 Help: "The HTTP request latencies in seconds.",
259 Buckets: buckets,
260 ConstLabels: prometheus.Labels(labels),
261 }
262 metric := prometheus.NewHistogramVec(opts, []string{"method"})
263 httpMetric := &HTTPMetric{
264 Collector: metric,
265 handlerType: InstrumentHandlerDuration,
266 }
267 n.Add(httpMetric)
268 return httpMetric
269 }
270
271 func (n *Namespace) NewRequestSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
272 if len(buckets) == 0 {
273 panic("RequestSizeBuckets must be provided")
274 }
275 labels := prometheus.Labels(n.labels)
276 labels["handler"] = handlerName
277 opts := prometheus.HistogramOpts{
278 Namespace: n.name,
279 Subsystem: n.subsystem,
280 Name: "request_size_bytes",
281 Help: "The HTTP request sizes in bytes.",
282 Buckets: buckets,
283 ConstLabels: prometheus.Labels(labels),
284 }
285 metric := prometheus.NewHistogramVec(opts, []string{})
286 httpMetric := &HTTPMetric{
287 Collector: metric,
288 handlerType: InstrumentHandlerRequestSize,
289 }
290 n.Add(httpMetric)
291 return httpMetric
292 }
293
294 func (n *Namespace) NewResponseSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
295 if len(buckets) == 0 {
296 panic("ResponseSizeBuckets must be provided")
297 }
298 labels := prometheus.Labels(n.labels)
299 labels["handler"] = handlerName
300 opts := prometheus.HistogramOpts{
301 Namespace: n.name,
302 Subsystem: n.subsystem,
303 Name: "response_size_bytes",
304 Help: "The HTTP response sizes in bytes.",
305 Buckets: buckets,
306 ConstLabels: prometheus.Labels(labels),
307 }
308 metrics := prometheus.NewHistogramVec(opts, []string{})
309 httpMetric := &HTTPMetric{
310 Collector: metrics,
311 handlerType: InstrumentHandlerResponseSize,
312 }
313 n.Add(httpMetric)
314 return httpMetric
315 }
316
View as plain text