master
go 527 lines 13.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package unbound
4
5 import (
6 "fmt"
7 "strings"
8
9 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
10
11 "golang.org/x/text/cases"
12 "golang.org/x/text/language"
13 )
14
15 type (
16 // Charts is an alias for collectorapi.Charts
17 Charts = collectorapi.Charts
18 // Chart is an alias for collectorapi.Charts
19 Chart = collectorapi.Chart
20 // Dims is an alias for collectorapi.Dims
21 Dims = collectorapi.Dims
22 // Dim is an alias for collectorapi.Dim
23 Dim = collectorapi.Dim
24 )
25
26 const (
27 prioQueries = collectorapi.Priority + iota
28 prioIPRateLimitedQueries
29 prioQueryType
30 prioQueryClass
31 prioQueryOpCode
32 prioQueryFlag
33 prioDNSCryptQueries
34
35 prioRecurReplies
36 prioReplyRCode
37
38 prioRecurTime
39
40 prioCache
41 prioCachePercentage
42 prioCachePrefetch
43 prioCacheExpired
44 prioZeroTTL
45 prioCacheCount
46
47 prioReqListUsage
48 prioReqListCurUsage
49 prioReqListJostle
50
51 prioTCPUsage
52
53 prioMemCache
54 prioMemMod
55 prioMemStreamWait
56 prioUptime
57
58 prioThread
59 )
60
61 func charts(cumulative bool) *Charts {
62 return &Charts{
63 makeIncrIf(queriesChart.Copy(), cumulative),
64 makeIncrIf(ipRateLimitedQueriesChart.Copy(), cumulative),
65 makeIncrIf(cacheChart.Copy(), cumulative),
66 makePercOfIncrIf(cachePercentageChart.Copy(), cumulative),
67 makeIncrIf(prefetchChart.Copy(), cumulative),
68 makeIncrIf(expiredChart.Copy(), cumulative),
69 makeIncrIf(zeroTTLChart.Copy(), cumulative),
70 makeIncrIf(dnsCryptChart.Copy(), cumulative),
71 makeIncrIf(recurRepliesChart.Copy(), cumulative),
72 recurTimeChart.Copy(),
73 reqListUsageChart.Copy(),
74 reqListCurUsageChart.Copy(),
75 makeIncrIf(reqListJostleChart.Copy(), cumulative),
76 tcpUsageChart.Copy(),
77 uptimeChart.Copy(),
78 }
79 }
80
81 func extendedCharts(cumulative bool) *Charts {
82 return &Charts{
83 memCacheChart.Copy(),
84 memModChart.Copy(),
85 memStreamWaitChart.Copy(),
86 cacheCountChart.Copy(),
87 makeIncrIf(queryTypeChart.Copy(), cumulative),
88 makeIncrIf(queryClassChart.Copy(), cumulative),
89 makeIncrIf(queryOpCodeChart.Copy(), cumulative),
90 makeIncrIf(queryFlagChart.Copy(), cumulative),
91 makeIncrIf(answerRCodeChart.Copy(), cumulative),
92 }
93 }
94
95 func threadCharts(thread string, cumulative bool) *Charts {
96 charts := charts(cumulative)
97 _ = charts.Remove(uptimeChart.ID)
98
99 for i, chart := range *charts {
100 convertTotalChartToThread(chart, thread, prioThread+i)
101 }
102 return charts
103 }
104
105 func convertTotalChartToThread(chart *Chart, thread string, priority int) {
106 chart.ID = fmt.Sprintf("%s_%s", thread, chart.ID)
107 chart.Title = fmt.Sprintf("%s %s",
108 cases.Title(language.English, cases.Compact).String(thread),
109 chart.Title,
110 )
111 chart.Fam = thread + "_stats"
112 chart.Ctx = "thread_" + chart.Ctx
113 chart.Priority = priority
114 for _, dim := range chart.Dims {
115 dim.ID = strings.Replace(dim.ID, "total", thread, 1)
116 }
117 }
118
119 // Common stats charts
120 // see https://nlnetlabs.nl/documentation/unbound/unbound-control for the stats provided by unbound-control
121 var (
122 queriesChart = Chart{
123 ID: "queries",
124 Title: "Received Queries",
125 Units: "queries",
126 Fam: "queries",
127 Ctx: "unbound.queries",
128 Priority: prioQueries,
129 Dims: Dims{
130 {ID: "total.num.queries", Name: "queries"},
131 },
132 }
133 ipRateLimitedQueriesChart = Chart{
134 ID: "queries_ip_ratelimited",
135 Title: "Rate Limited Queries",
136 Units: "queries",
137 Fam: "queries",
138 Ctx: "unbound.queries_ip_ratelimited",
139 Priority: prioIPRateLimitedQueries,
140 Dims: Dims{
141 {ID: "total.num.queries_ip_ratelimited", Name: "ratelimited"},
142 },
143 }
144 // ifdef USE_DNSCRYPT
145 dnsCryptChart = Chart{
146 ID: "dnscrypt_queries",
147 Title: "DNSCrypt Queries",
148 Units: "queries",
149 Fam: "queries",
150 Ctx: "unbound.dnscrypt_queries",
151 Priority: prioDNSCryptQueries,
152 Dims: Dims{
153 {ID: "total.num.dnscrypt.crypted", Name: "crypted"},
154 {ID: "total.num.dnscrypt.cert", Name: "cert"},
155 {ID: "total.num.dnscrypt.cleartext", Name: "cleartext"},
156 {ID: "total.num.dnscrypt.malformed", Name: "malformed"},
157 },
158 }
159 cacheChart = Chart{
160 ID: "cache",
161 Title: "Cache Statistics",
162 Units: "events",
163 Fam: "cache",
164 Ctx: "unbound.cache",
165 Type: collectorapi.Stacked,
166 Priority: prioCache,
167 Dims: Dims{
168 {ID: "total.num.cachehits", Name: "hits"},
169 {ID: "total.num.cachemiss", Name: "miss"},
170 },
171 }
172 cachePercentageChart = Chart{
173 ID: "cache_percentage",
174 Title: "Cache Statistics Percentage",
175 Units: "percentage",
176 Fam: "cache",
177 Ctx: "unbound.cache_percentage",
178 Type: collectorapi.Stacked,
179 Priority: prioCachePercentage,
180 Dims: Dims{
181 {ID: "total.num.cachehits", Name: "hits", Algo: collectorapi.PercentOfAbsolute},
182 {ID: "total.num.cachemiss", Name: "miss", Algo: collectorapi.PercentOfAbsolute},
183 },
184 }
185 prefetchChart = Chart{
186 ID: "cache_prefetch",
187 Title: "Cache Prefetches",
188 Units: "prefetches",
189 Fam: "cache",
190 Ctx: "unbound.prefetch",
191 Priority: prioCachePrefetch,
192 Dims: Dims{
193 {ID: "total.num.prefetch", Name: "prefetches"},
194 },
195 }
196 expiredChart = Chart{
197 ID: "cache_expired",
198 Title: "Replies Served From Expired Cache",
199 Units: "replies",
200 Fam: "cache",
201 Ctx: "unbound.expired",
202 Priority: prioCacheExpired,
203 Dims: Dims{
204 {ID: "total.num.expired", Name: "expired"},
205 },
206 }
207 zeroTTLChart = Chart{
208 ID: "zero_ttl_replies",
209 Title: "Replies Served From Expired Cache",
210 Units: "replies",
211 Fam: "cache",
212 Ctx: "unbound.zero_ttl_replies",
213 Priority: prioZeroTTL,
214 Dims: Dims{
215 {ID: "total.num.zero_ttl", Name: "zero_ttl"},
216 },
217 }
218 recurRepliesChart = Chart{
219 ID: "recursive_replies",
220 Title: "Replies That Needed Recursive Processing",
221 Units: "replies",
222 Fam: "replies",
223 Ctx: "unbound.recursive_replies",
224 Priority: prioRecurReplies,
225 Dims: Dims{
226 {ID: "total.num.recursivereplies", Name: "recursive"},
227 },
228 }
229 recurTimeChart = Chart{
230 ID: "recursion_time",
231 Title: "Time Spent On Recursive Processing",
232 Units: "milliseconds",
233 Fam: "recursion timings",
234 Ctx: "unbound.recursion_time",
235 Priority: prioRecurTime,
236 Dims: Dims{
237 {ID: "total.recursion.time.avg", Name: "avg"},
238 {ID: "total.recursion.time.median", Name: "median"},
239 },
240 }
241 reqListUsageChart = Chart{
242 ID: "request_list_usage",
243 Title: "Request List Usage",
244 Units: "queries",
245 Fam: "request list",
246 Ctx: "unbound.request_list_usage",
247 Priority: prioReqListUsage,
248 Dims: Dims{
249 {ID: "total.requestlist.avg", Name: "avg", Div: 1000},
250 {ID: "total.requestlist.max", Name: "max"}, // all time max in cumulative mode, never resets
251 },
252 }
253 reqListCurUsageChart = Chart{
254 ID: "current_request_list_usage",
255 Title: "Current Request List Usage",
256 Units: "queries",
257 Fam: "request list",
258 Ctx: "unbound.current_request_list_usage",
259 Type: collectorapi.Area,
260 Priority: prioReqListCurUsage,
261 Dims: Dims{
262 {ID: "total.requestlist.current.all", Name: "all"},
263 {ID: "total.requestlist.current.user", Name: "user"},
264 },
265 }
266 reqListJostleChart = Chart{
267 ID: "request_list_jostle_list",
268 Title: "Request List Jostle List Events",
269 Units: "queries",
270 Fam: "request list",
271 Ctx: "unbound.request_list_jostle_list",
272 Priority: prioReqListJostle,
273 Dims: Dims{
274 {ID: "total.requestlist.overwritten", Name: "overwritten"},
275 {ID: "total.requestlist.exceeded", Name: "dropped"},
276 },
277 }
278 tcpUsageChart = Chart{
279 ID: "tcpusage",
280 Title: "TCP Handler Buffers",
281 Units: "buffers",
282 Fam: "tcp buffers",
283 Ctx: "unbound.tcpusage",
284 Priority: prioTCPUsage,
285 Dims: Dims{
286 {ID: "total.tcpusage", Name: "usage"},
287 },
288 }
289 uptimeChart = Chart{
290 ID: "uptime",
291 Title: "Uptime",
292 Units: "seconds",
293 Fam: "uptime",
294 Ctx: "unbound.uptime",
295 Priority: prioUptime,
296 Dims: Dims{
297 {ID: "time.up", Name: "time"},
298 },
299 }
300 )
301
302 // Extended stats charts
303 var (
304 // TODO: do not add dnscrypt stuff by default?
305 memCacheChart = Chart{
306 ID: "cache_memory",
307 Title: "Cache Memory",
308 Units: "KB",
309 Fam: "mem",
310 Ctx: "unbound.cache_memory",
311 Type: collectorapi.Stacked,
312 Priority: prioMemCache,
313 Dims: Dims{
314 {ID: "mem.cache.message", Name: "message", Div: 1024},
315 {ID: "mem.cache.rrset", Name: "rrset", Div: 1024},
316 {ID: "mem.cache.dnscrypt_nonce", Name: "dnscrypt_nonce", Div: 1024}, // ifdef USE_DNSCRYPT
317 {ID: "mem.cache.dnscrypt_shared_secret", Name: "dnscrypt_shared_secret", Div: 1024}, // ifdef USE_DNSCRYPT
318 },
319 }
320 // TODO: do not add subnet and ipsecmod stuff by default?
321 memModChart = Chart{
322 ID: "mod_memory",
323 Title: "CollectorV1 Memory",
324 Units: "KB",
325 Fam: "mem",
326 Ctx: "unbound.mod_memory",
327 Type: collectorapi.Stacked,
328 Priority: prioMemMod,
329 Dims: Dims{
330 {ID: "mem.mod.iterator", Name: "iterator", Div: 1024},
331 {ID: "mem.mod.respip", Name: "respip", Div: 1024},
332 {ID: "mem.mod.validator", Name: "validator", Div: 1024},
333 {ID: "mem.mod.subnet", Name: "subnet", Div: 1024}, // ifdef CLIENT_SUBNET
334 {ID: "mem.mod.ipsecmod", Name: "ipsec", Div: 1024}, // ifdef USE_IPSECMOD
335 },
336 }
337 memStreamWaitChart = Chart{
338 ID: "mem_stream_wait",
339 Title: "TCP and TLS Stream Waif Buffer Memory",
340 Units: "KB",
341 Fam: "mem",
342 Ctx: "unbound.mem_streamwait",
343 Priority: prioMemStreamWait,
344 Dims: Dims{
345 {ID: "mem.streamwait", Name: "streamwait", Div: 1024},
346 },
347 }
348 // NOTE: same family as for cacheChart
349 cacheCountChart = Chart{
350 ID: "cache_count",
351 Title: "Cache Items Count",
352 Units: "items",
353 Fam: "cache",
354 Ctx: "unbound.cache_count",
355 Type: collectorapi.Stacked,
356 Priority: prioCacheCount,
357 Dims: Dims{
358 {ID: "infra.cache.count", Name: "infra"},
359 {ID: "key.cache.count", Name: "key"},
360 {ID: "msg.cache.count", Name: "msg"},
361 {ID: "rrset.cache.count", Name: "rrset"},
362 {ID: "dnscrypt_nonce.cache.count", Name: "dnscrypt_nonce"},
363 {ID: "dnscrypt_shared_secret.cache.count", Name: "shared_secret"},
364 },
365 }
366 queryTypeChart = Chart{
367 ID: "queries_by_type",
368 Title: "Queries By Type",
369 Units: "queries",
370 Fam: "queries",
371 Ctx: "unbound.type_queries",
372 Type: collectorapi.Stacked,
373 Priority: prioQueryType,
374 }
375 queryClassChart = Chart{
376 ID: "queries_by_class",
377 Title: "Queries By Class",
378 Units: "queries",
379 Fam: "queries",
380 Ctx: "unbound.class_queries",
381 Type: collectorapi.Stacked,
382 Priority: prioQueryClass,
383 }
384 queryOpCodeChart = Chart{
385 ID: "queries_by_opcode",
386 Title: "Queries By OpCode",
387 Units: "queries",
388 Fam: "queries",
389 Ctx: "unbound.opcode_queries",
390 Type: collectorapi.Stacked,
391 Priority: prioQueryOpCode,
392 }
393 queryFlagChart = Chart{
394 ID: "queries_by_flag",
395 Title: "Queries By Flag",
396 Units: "queries",
397 Fam: "queries",
398 Ctx: "unbound.flag_queries",
399 Type: collectorapi.Stacked,
400 Priority: prioQueryFlag,
401 Dims: Dims{
402 {ID: "num.query.flags.QR", Name: "QR"},
403 {ID: "num.query.flags.AA", Name: "AA"},
404 {ID: "num.query.flags.TC", Name: "TC"},
405 {ID: "num.query.flags.RD", Name: "RD"},
406 {ID: "num.query.flags.RA", Name: "RA"},
407 {ID: "num.query.flags.Z", Name: "Z"},
408 {ID: "num.query.flags.AD", Name: "AD"},
409 {ID: "num.query.flags.CD", Name: "CD"},
410 },
411 }
412 answerRCodeChart = Chart{
413 ID: "replies_by_rcode",
414 Title: "Replies By RCode",
415 Units: "replies",
416 Fam: "replies",
417 Ctx: "unbound.rcode_answers",
418 Type: collectorapi.Stacked,
419 Priority: prioReplyRCode,
420 }
421 )
422
423 func (c *Collector) updateCharts() {
424 if len(c.curCache.threads) > 1 {
425 for v := range c.curCache.threads {
426 if !c.cache.threads[v] {
427 c.cache.threads[v] = true
428 c.addThreadCharts(v)
429 }
430 }
431 }
432 // 0-6 rcodes always included
433 if hasExtendedData := len(c.curCache.answerRCode) > 0; !hasExtendedData {
434 return
435 }
436
437 if !c.extChartsCreated {
438 charts := extendedCharts(c.Cumulative)
439 if err := c.Charts().Add(*charts...); err != nil {
440 c.Warningf("add extended charts: %v", err)
441 }
442 c.extChartsCreated = true
443 }
444
445 for v := range c.curCache.queryType {
446 if !c.cache.queryType[v] {
447 c.cache.queryType[v] = true
448 c.addDimToQueryTypeChart(v)
449 }
450 }
451 for v := range c.curCache.queryClass {
452 if !c.cache.queryClass[v] {
453 c.cache.queryClass[v] = true
454 c.addDimToQueryClassChart(v)
455 }
456 }
457 for v := range c.curCache.queryOpCode {
458 if !c.cache.queryOpCode[v] {
459 c.cache.queryOpCode[v] = true
460 c.addDimToQueryOpCodeChart(v)
461 }
462 }
463 for v := range c.curCache.answerRCode {
464 if !c.cache.answerRCode[v] {
465 c.cache.answerRCode[v] = true
466 c.addDimToAnswerRcodeChart(v)
467 }
468 }
469 }
470
471 func (c *Collector) addThreadCharts(thread string) {
472 charts := threadCharts(thread, c.Cumulative)
473 if err := c.Charts().Add(*charts...); err != nil {
474 c.Warningf("add '%s' charts: %v", thread, err)
475 }
476 }
477
478 func (c *Collector) addDimToQueryTypeChart(typ string) {
479 c.addDimToChart(queryTypeChart.ID, "num.query.type."+typ, typ)
480 }
481 func (c *Collector) addDimToQueryClassChart(class string) {
482 c.addDimToChart(queryClassChart.ID, "num.query.class."+class, class)
483 }
484 func (c *Collector) addDimToQueryOpCodeChart(opcode string) {
485 c.addDimToChart(queryOpCodeChart.ID, "num.query.opcode."+opcode, opcode)
486 }
487 func (c *Collector) addDimToAnswerRcodeChart(rcode string) {
488 c.addDimToChart(answerRCodeChart.ID, "num.answer.rcode."+rcode, rcode)
489 }
490
491 func (c *Collector) addDimToChart(chartID, dimID, dimName string) {
492 chart := c.Charts().Get(chartID)
493 if chart == nil {
494 c.Warningf("add '%s' dim: couldn't find '%s' chart", dimID, chartID)
495 return
496 }
497 dim := &Dim{ID: dimID, Name: dimName}
498 if c.Cumulative {
499 dim.Algo = collectorapi.Incremental
500 }
501 if err := chart.AddDim(dim); err != nil {
502 c.Warningf("add '%s' dim: %v", dimID, err)
503 return
504 }
505 chart.MarkNotCreated()
506 }
507
508 func makeIncrIf(chart *Chart, do bool) *Chart {
509 if !do {
510 return chart
511 }
512 chart.Units += "/s"
513 for _, d := range chart.Dims {
514 d.Algo = collectorapi.Incremental
515 }
516 return chart
517 }
518
519 func makePercOfIncrIf(chart *Chart, do bool) *Chart {
520 if !do {
521 return chart
522 }
523 for _, d := range chart.Dims {
524 d.Algo = collectorapi.PercentOfIncremental
525 }
526 return chart
527 }