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  }