github.com/netdata/go.d.plugin@v0.58.1/modules/unbound/charts.go (about) 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/go.d.plugin/agent/module" 10 11 "golang.org/x/text/cases" 12 "golang.org/x/text/language" 13 ) 14 15 type ( 16 // Charts is an alias for module.Charts 17 Charts = module.Charts 18 // Chart is an alias for module.Charts 19 Chart = module.Chart 20 // Dims is an alias for module.Dims 21 Dims = module.Dims 22 // Dim is an alias for module.Dim 23 Dim = module.Dim 24 ) 25 26 const ( 27 prioQueries = module.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: module.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: module.Stacked, 179 Priority: prioCachePercentage, 180 Dims: Dims{ 181 {ID: "total.num.cachehits", Name: "hits", Algo: module.PercentOfAbsolute}, 182 {ID: "total.num.cachemiss", Name: "miss", Algo: module.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: module.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: module.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: "Module Memory", 324 Units: "KB", 325 Fam: "mem", 326 Ctx: "unbound.mod_memory", 327 Type: module.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: module.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: module.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: module.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: module.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: module.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: module.Stacked, 419 Priority: prioReplyRCode, 420 } 421 ) 422 423 func (u *Unbound) updateCharts() { 424 if len(u.curCache.threads) > 1 { 425 for v := range u.curCache.threads { 426 if !u.cache.threads[v] { 427 u.cache.threads[v] = true 428 u.addThreadCharts(v) 429 } 430 } 431 } 432 // 0-6 rcodes always included 433 if hasExtendedData := len(u.curCache.answerRCode) > 0; !hasExtendedData { 434 return 435 } 436 437 if !u.extChartsCreated { 438 charts := extendedCharts(u.Cumulative) 439 if err := u.Charts().Add(*charts...); err != nil { 440 u.Warningf("add extended charts: %v", err) 441 } 442 u.extChartsCreated = true 443 } 444 445 for v := range u.curCache.queryType { 446 if !u.cache.queryType[v] { 447 u.cache.queryType[v] = true 448 u.addDimToQueryTypeChart(v) 449 } 450 } 451 for v := range u.curCache.queryClass { 452 if !u.cache.queryClass[v] { 453 u.cache.queryClass[v] = true 454 u.addDimToQueryClassChart(v) 455 } 456 } 457 for v := range u.curCache.queryOpCode { 458 if !u.cache.queryOpCode[v] { 459 u.cache.queryOpCode[v] = true 460 u.addDimToQueryOpCodeChart(v) 461 } 462 } 463 for v := range u.curCache.answerRCode { 464 if !u.cache.answerRCode[v] { 465 u.cache.answerRCode[v] = true 466 u.addDimToAnswerRcodeChart(v) 467 } 468 } 469 } 470 471 func (u *Unbound) addThreadCharts(thread string) { 472 charts := threadCharts(thread, u.Cumulative) 473 if err := u.Charts().Add(*charts...); err != nil { 474 u.Warningf("add '%s' charts: %v", thread, err) 475 } 476 } 477 478 func (u *Unbound) addDimToQueryTypeChart(typ string) { 479 u.addDimToChart(queryTypeChart.ID, "num.query.type."+typ, typ) 480 } 481 func (u *Unbound) addDimToQueryClassChart(class string) { 482 u.addDimToChart(queryClassChart.ID, "num.query.class."+class, class) 483 } 484 func (u *Unbound) addDimToQueryOpCodeChart(opcode string) { 485 u.addDimToChart(queryOpCodeChart.ID, "num.query.opcode."+opcode, opcode) 486 } 487 func (u *Unbound) addDimToAnswerRcodeChart(rcode string) { 488 u.addDimToChart(answerRCodeChart.ID, "num.answer.rcode."+rcode, rcode) 489 } 490 491 func (u *Unbound) addDimToChart(chartID, dimID, dimName string) { 492 chart := u.Charts().Get(chartID) 493 if chart == nil { 494 u.Warningf("add '%s' dim: couldn't find '%s' chart", dimID, chartID) 495 return 496 } 497 dim := &Dim{ID: dimID, Name: dimName} 498 if u.Cumulative { 499 dim.Algo = module.Incremental 500 } 501 if err := chart.AddDim(dim); err != nil { 502 u.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 = module.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 = module.PercentOfIncremental 525 } 526 return chart 527 }