diff options
Diffstat (limited to 'bind-exporter/metrics.go')
-rw-r--r-- | bind-exporter/metrics.go | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/bind-exporter/metrics.go b/bind-exporter/metrics.go new file mode 100644 index 0000000..185f437 --- /dev/null +++ b/bind-exporter/metrics.go | |||
@@ -0,0 +1,434 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "encoding/json" | ||
6 | "fmt" | ||
7 | "net/http" | ||
8 | |||
9 | "github.com/prometheus/client_golang/prometheus" | ||
10 | ) | ||
11 | |||
12 | type bindMetric struct { | ||
13 | Name string | ||
14 | Help string | ||
15 | Labels []string | ||
16 | Extract func(*prometheus.Desc, *BindStats) []prometheus.Metric | ||
17 | desc *prometheus.Desc | ||
18 | } | ||
19 | |||
20 | type bindViewMetric struct { | ||
21 | Name string | ||
22 | Help string | ||
23 | Labels []string | ||
24 | Extract func(*prometheus.Desc, string, *View) []prometheus.Metric | ||
25 | desc *prometheus.Desc | ||
26 | } | ||
27 | |||
28 | type bindZoneMetric struct { | ||
29 | Name string | ||
30 | Help string | ||
31 | Labels []string | ||
32 | Extract func(*prometheus.Desc, string, *View, *Zone) []prometheus.Metric | ||
33 | desc *prometheus.Desc | ||
34 | } | ||
35 | |||
36 | type bindSocketMetric struct { | ||
37 | Name string | ||
38 | Help string | ||
39 | Labels []string | ||
40 | Suffix string | ||
41 | Extract func(*prometheus.Desc, string, map[string]int) []prometheus.Metric | ||
42 | desc *prometheus.Desc | ||
43 | } | ||
44 | |||
45 | type bindCollector struct { | ||
46 | ctx context.Context | ||
47 | target string | ||
48 | metrics []*bindMetric | ||
49 | viewMetrics []*bindViewMetric | ||
50 | zoneMetrics []*bindZoneMetric | ||
51 | socketMetrics []*bindSocketMetric | ||
52 | } | ||
53 | |||
54 | var _ prometheus.Collector = (*bindCollector)(nil) | ||
55 | |||
56 | func socketMetricExtractor(d *prometheus.Desc, suffix string, m map[string]int) []prometheus.Metric { | ||
57 | out := []prometheus.Metric{} | ||
58 | |||
59 | for _, p := range []string{"UDP", "TCP"} { | ||
60 | for _, v := range []string{"4", "6"} { | ||
61 | if value, ok := m[p+v+suffix]; ok { | ||
62 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(value), p, v)) | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | if value, ok := m["Raw"+suffix]; ok { | ||
68 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(value), "Raw", "")) | ||
69 | } | ||
70 | |||
71 | return out | ||
72 | } | ||
73 | |||
74 | func kvExtractor(d *prometheus.Desc, m map[string]int) []prometheus.Metric { | ||
75 | out := make([]prometheus.Metric, 0, len(m)) | ||
76 | |||
77 | for k, v := range m { | ||
78 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v), k)) | ||
79 | } | ||
80 | |||
81 | return out | ||
82 | } | ||
83 | |||
84 | func singleGaugeIntMetric(d *prometheus.Desc, v int) []prometheus.Metric { | ||
85 | return singleGaugeInt64Metric(d, int64(v)) | ||
86 | } | ||
87 | |||
88 | func singleGaugeInt64Metric(d *prometheus.Desc, v int64) []prometheus.Metric { | ||
89 | return []prometheus.Metric{prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v))} | ||
90 | } | ||
91 | |||
92 | func NewBindCollector(ctx context.Context, target string) prometheus.Collector { | ||
93 | c := &bindCollector{ | ||
94 | ctx: ctx, | ||
95 | target: target, | ||
96 | metrics: []*bindMetric{ | ||
97 | &bindMetric{ | ||
98 | Name: "bind_boot_time", | ||
99 | Help: "Unix timestamp of when the server was last booted", | ||
100 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
101 | return singleGaugeInt64Metric(d, s.BootTime.Unix()) | ||
102 | }, | ||
103 | }, | ||
104 | &bindMetric{ | ||
105 | Name: "bind_config_time", | ||
106 | Help: "Unix timestamp of when the server was last configured", | ||
107 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
108 | return singleGaugeInt64Metric(d, s.ConfigTime.Unix()) | ||
109 | }, | ||
110 | }, | ||
111 | &bindMetric{ | ||
112 | Name: "bind_memory_total_use", | ||
113 | Help: "Total memory bytes in used by the server", | ||
114 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
115 | return singleGaugeIntMetric(d, s.MemoryStats.TotalUse) | ||
116 | }, | ||
117 | }, | ||
118 | &bindMetric{ | ||
119 | Name: "bind_memory_in_use", | ||
120 | Help: "Total memory bytes currently in use by the server", | ||
121 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
122 | return singleGaugeIntMetric(d, s.MemoryStats.InUse) | ||
123 | }, | ||
124 | }, | ||
125 | &bindMetric{ | ||
126 | Name: "bind_memory_malloced", | ||
127 | Help: "Total memory bytes currently malloced by the server", | ||
128 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
129 | return singleGaugeIntMetric(d, s.MemoryStats.Malloced) | ||
130 | }, | ||
131 | }, | ||
132 | &bindMetric{ | ||
133 | Name: "bind_server_op_codes", | ||
134 | Help: "Operation codes requested from the server as a whole", | ||
135 | Labels: []string{"opcode"}, | ||
136 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
137 | return kvExtractor(d, s.OperationCodeCount) | ||
138 | }, | ||
139 | }, | ||
140 | &bindMetric{ | ||
141 | Name: "bind_notify_out", | ||
142 | Help: "Count of notifies sent by IP version", | ||
143 | Labels: []string{"ipv"}, | ||
144 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
145 | out := []prometheus.Metric{} | ||
146 | if v4out, ok := s.ZoneStats["NotifyOutv4"]; ok { | ||
147 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v4out), "4")) | ||
148 | } | ||
149 | if v6out, ok := s.ZoneStats["NotifyOutv6"]; ok { | ||
150 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v6out), "6")) | ||
151 | } | ||
152 | return out | ||
153 | }, | ||
154 | }, | ||
155 | &bindMetric{ | ||
156 | Name: "bind_server_response_codes", | ||
157 | Help: "Response codes sent from the server as a whole", | ||
158 | Labels: []string{"rcode"}, | ||
159 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
160 | return kvExtractor(d, s.ResponseCodeCount) | ||
161 | }, | ||
162 | }, | ||
163 | &bindMetric{ | ||
164 | Name: "bind_server_query_types", | ||
165 | Help: "Query types sent to the server as a whole", | ||
166 | Labels: []string{"qtype"}, | ||
167 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
168 | return kvExtractor(d, s.QueryTypeCount) | ||
169 | }, | ||
170 | }, | ||
171 | &bindMetric{ | ||
172 | Name: "bind_server_stats", | ||
173 | Help: "Name server statistics for the server as a whole", | ||
174 | Labels: []string{"stat"}, | ||
175 | Extract: func(d *prometheus.Desc, s *BindStats) []prometheus.Metric { | ||
176 | return kvExtractor(d, s.NameserverStats) | ||
177 | }, | ||
178 | }, | ||
179 | }, | ||
180 | viewMetrics: []*bindViewMetric{ | ||
181 | &bindViewMetric{ | ||
182 | Name: "bind_view_resolver_stats", | ||
183 | Help: "Resolver stats for a view", | ||
184 | Labels: []string{"view", "stat"}, | ||
185 | Extract: func(d *prometheus.Desc, name string, v *View) []prometheus.Metric { | ||
186 | out := make([]prometheus.Metric, 0, len(v.Resolver.Stats)) | ||
187 | for k, v := range v.Resolver.Stats { | ||
188 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v), name, k)) | ||
189 | } | ||
190 | return out | ||
191 | }, | ||
192 | }, | ||
193 | &bindViewMetric{ | ||
194 | Name: "bind_view_cache_stats", | ||
195 | Help: "Cache stats for a view", | ||
196 | Labels: []string{"view", "stat"}, | ||
197 | Extract: func(d *prometheus.Desc, name string, v *View) []prometheus.Metric { | ||
198 | out := make([]prometheus.Metric, 0, len(v.Resolver.CacheStats)) | ||
199 | for k, v := range v.Resolver.CacheStats { | ||
200 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v), name, k)) | ||
201 | } | ||
202 | return out | ||
203 | }, | ||
204 | }, | ||
205 | &bindViewMetric{ | ||
206 | Name: "bind_view_query_count", | ||
207 | Help: "Count of queries by qtype in a view", | ||
208 | Labels: []string{"view", "qtype"}, | ||
209 | Extract: func(d *prometheus.Desc, name string, v *View) []prometheus.Metric { | ||
210 | out := make([]prometheus.Metric, 0, len(v.Resolver.QueryTypes)) | ||
211 | for k, v := range v.Resolver.QueryTypes { | ||
212 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v), name, k)) | ||
213 | } | ||
214 | return out | ||
215 | }, | ||
216 | }, | ||
217 | &bindViewMetric{ | ||
218 | Name: "bind_view_cache_entry_count", | ||
219 | Help: "Cache entry count for a view by RRtype", | ||
220 | Labels: []string{"view", "rrtype"}, | ||
221 | Extract: func(d *prometheus.Desc, name string, v *View) []prometheus.Metric { | ||
222 | out := make([]prometheus.Metric, 0, len(v.Resolver.CacheRecordTypes)) | ||
223 | for k, v := range v.Resolver.CacheRecordTypes { | ||
224 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v), name, k)) | ||
225 | } | ||
226 | return out | ||
227 | }, | ||
228 | }, | ||
229 | }, | ||
230 | zoneMetrics: []*bindZoneMetric{ | ||
231 | &bindZoneMetric{ | ||
232 | Name: "bind_zone_serial", | ||
233 | Help: "Serial number of zone", | ||
234 | Labels: []string{"view", "zone", "type"}, | ||
235 | Extract: func(d *prometheus.Desc, viewName string, v *View, z *Zone) []prometheus.Metric { | ||
236 | return []prometheus.Metric{ | ||
237 | prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(z.Serial), viewName, z.Name, z.Type), | ||
238 | } | ||
239 | }, | ||
240 | }, | ||
241 | &bindZoneMetric{ | ||
242 | Name: "bind_zone_load_time", | ||
243 | Help: "Zone load time of a zone", | ||
244 | Labels: []string{"view", "zone", "type"}, | ||
245 | Extract: func(d *prometheus.Desc, viewName string, v *View, z *Zone) []prometheus.Metric { | ||
246 | return []prometheus.Metric{ | ||
247 | prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(z.LoadTime.Unix()), viewName, z.Name, z.Type), | ||
248 | } | ||
249 | }, | ||
250 | }, | ||
251 | &bindZoneMetric{ | ||
252 | Name: "bind_zone_response_codes", | ||
253 | Help: "Response codes sent from the server for a zone in a view", | ||
254 | Labels: []string{"view", "zone", "type", "rcode"}, | ||
255 | Extract: func(d *prometheus.Desc, viewName string, v *View, z *Zone) []prometheus.Metric { | ||
256 | out := make([]prometheus.Metric, 0, len(z.ResponseCodes)) | ||
257 | for k, v := range z.ResponseCodes { | ||
258 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v), viewName, z.Name, z.Type, k)) | ||
259 | } | ||
260 | return out | ||
261 | }, | ||
262 | }, | ||
263 | &bindZoneMetric{ | ||
264 | Name: "bind_zone_query_types", | ||
265 | Help: "Query types sent to the server for a zone in a view", | ||
266 | Labels: []string{"view", "zone", "type", "qtype"}, | ||
267 | Extract: func(d *prometheus.Desc, viewName string, v *View, z *Zone) []prometheus.Metric { | ||
268 | out := make([]prometheus.Metric, 0, len(z.QueryTypes)) | ||
269 | for k, v := range z.QueryTypes { | ||
270 | out = append(out, prometheus.MustNewConstMetric(d, prometheus.GaugeValue, float64(v), viewName, z.Name, z.Type, k)) | ||
271 | } | ||
272 | return out | ||
273 | }, | ||
274 | }, | ||
275 | }, | ||
276 | socketMetrics: []*bindSocketMetric{ | ||
277 | &bindSocketMetric{ | ||
278 | Name: "bind_socket_open_count", | ||
279 | Help: "Count of sockets opened by protocol and version", | ||
280 | Suffix: "Open", | ||
281 | Labels: []string{"proto", "version"}, | ||
282 | Extract: socketMetricExtractor, | ||
283 | }, | ||
284 | &bindSocketMetric{ | ||
285 | Name: "bind_socket_close_count", | ||
286 | Help: "Count of sockets closed by protocol and version", | ||
287 | Suffix: "Close", | ||
288 | Labels: []string{"proto", "version"}, | ||
289 | Extract: socketMetricExtractor, | ||
290 | }, | ||
291 | &bindSocketMetric{ | ||
292 | Name: "bind_socket_bind_fail_count", | ||
293 | Help: "Count of sockets bind failures by protocol and version", | ||
294 | Suffix: "BindFail", | ||
295 | Labels: []string{"proto", "version"}, | ||
296 | Extract: socketMetricExtractor, | ||
297 | }, | ||
298 | &bindSocketMetric{ | ||
299 | Name: "bind_socket_connect_count", | ||
300 | Help: "Count of sockets connections by protocol and version", | ||
301 | Suffix: "Conn", | ||
302 | Labels: []string{"proto", "version"}, | ||
303 | Extract: socketMetricExtractor, | ||
304 | }, | ||
305 | &bindSocketMetric{ | ||
306 | Name: "bind_tcp_accept_fail_count", | ||
307 | Help: "Count of TCP sockets accept failures by version", | ||
308 | Suffix: "AcceptFail", | ||
309 | Labels: []string{"proto", "version"}, | ||
310 | Extract: socketMetricExtractor, | ||
311 | }, | ||
312 | &bindSocketMetric{ | ||
313 | Name: "bind_tcp_connect_fail_count", | ||
314 | Help: "Count of sockets connect failures by version", | ||
315 | Suffix: "ConnFail", | ||
316 | Labels: []string{"proto", "version"}, | ||
317 | Extract: socketMetricExtractor, | ||
318 | }, | ||
319 | &bindSocketMetric{ | ||
320 | Name: "bind_accept_count", | ||
321 | Help: "Count of sockets accept success by version", | ||
322 | Suffix: "Accept", | ||
323 | Labels: []string{"proto", "version"}, | ||
324 | Extract: socketMetricExtractor, | ||
325 | }, | ||
326 | &bindSocketMetric{ | ||
327 | Name: "bind_socket_recv_error_count", | ||
328 | Help: "Count of sockets receive error by protocol and version", | ||
329 | Suffix: "RecvErr", | ||
330 | Labels: []string{"proto", "version"}, | ||
331 | Extract: socketMetricExtractor, | ||
332 | }, | ||
333 | &bindSocketMetric{ | ||
334 | Name: "bind_socket_active_count", | ||
335 | Help: "Count of active sockets by protocol and version", | ||
336 | Suffix: "Active", | ||
337 | Labels: []string{"proto", "version"}, | ||
338 | Extract: socketMetricExtractor, | ||
339 | }, | ||
340 | }, | ||
341 | } | ||
342 | |||
343 | for _, m := range c.metrics { | ||
344 | m.desc = prometheus.NewDesc(m.Name, m.Help, m.Labels, nil) | ||
345 | } | ||
346 | |||
347 | for _, m := range c.viewMetrics { | ||
348 | m.desc = prometheus.NewDesc(m.Name, m.Help, m.Labels, nil) | ||
349 | } | ||
350 | |||
351 | for _, m := range c.zoneMetrics { | ||
352 | m.desc = prometheus.NewDesc(m.Name, m.Help, m.Labels, nil) | ||
353 | } | ||
354 | |||
355 | for _, m := range c.socketMetrics { | ||
356 | m.desc = prometheus.NewDesc(m.Name, m.Help, m.Labels, nil) | ||
357 | } | ||
358 | |||
359 | return c | ||
360 | } | ||
361 | |||
362 | func (c *bindCollector) Describe(ch chan<- *prometheus.Desc) { | ||
363 | for _, m := range c.metrics { | ||
364 | ch <- m.desc | ||
365 | } | ||
366 | |||
367 | for _, m := range c.viewMetrics { | ||
368 | ch <- m.desc | ||
369 | } | ||
370 | |||
371 | for _, m := range c.zoneMetrics { | ||
372 | ch <- m.desc | ||
373 | } | ||
374 | |||
375 | for _, m := range c.socketMetrics { | ||
376 | ch <- m.desc | ||
377 | } | ||
378 | } | ||
379 | |||
380 | func (c *bindCollector) Collect(ch chan<- prometheus.Metric) { | ||
381 | var s BindStats | ||
382 | |||
383 | req, err := http.NewRequestWithContext(c.ctx, http.MethodGet, c.target, nil) | ||
384 | if err != nil { | ||
385 | ch <- prometheus.NewInvalidMetric( | ||
386 | prometheus.NewDesc("bind_error", "Error building http request", nil, nil), | ||
387 | fmt.Errorf("Error building http request: %w", err)) | ||
388 | return | ||
389 | } | ||
390 | |||
391 | res, err := http.DefaultClient.Do(req) | ||
392 | if err != nil { | ||
393 | ch <- prometheus.NewInvalidMetric( | ||
394 | prometheus.NewDesc("bind_error", "Error retrieving bind metrics", nil, nil), | ||
395 | fmt.Errorf("HTTP error while retrieving bind metrics: %w", err)) | ||
396 | return | ||
397 | } | ||
398 | defer res.Body.Close() | ||
399 | |||
400 | if err := json.NewDecoder(res.Body).Decode(&s); err != nil { | ||
401 | ch <- prometheus.NewInvalidMetric( | ||
402 | prometheus.NewDesc("bind_error", "Error decoding bind metrics", nil, nil), | ||
403 | fmt.Errorf("Error JSON decoding bind metrics: %w", err)) | ||
404 | return | ||
405 | } | ||
406 | |||
407 | for _, m := range c.metrics { | ||
408 | for _, v := range m.Extract(m.desc, &s) { | ||
409 | ch <- v | ||
410 | } | ||
411 | } | ||
412 | |||
413 | for vn, v := range s.Views { | ||
414 | for _, m := range c.viewMetrics { | ||
415 | for _, mv := range m.Extract(m.desc, vn, &v) { | ||
416 | ch <- mv | ||
417 | } | ||
418 | } | ||
419 | |||
420 | for _, z := range v.Zones { | ||
421 | for _, m := range c.zoneMetrics { | ||
422 | for _, mv := range m.Extract(m.desc, vn, &v, &z) { | ||
423 | ch <- mv | ||
424 | } | ||
425 | } | ||
426 | } | ||
427 | } | ||
428 | |||
429 | for _, m := range c.socketMetrics { | ||
430 | for _, mv := range m.Extract(m.desc, m.Suffix, s.SocketStats) { | ||
431 | ch <- mv | ||
432 | } | ||
433 | } | ||
434 | } | ||