netdata/netdata

View on GitHub
src/go/collectors/go.d.plugin/modules/unbound/charts.go

Summary

Maintainability
A
35 mins
Test Coverage
// SPDX-License-Identifier: GPL-3.0-or-later

package unbound

import (
    "fmt"
    "strings"

    "github.com/netdata/netdata/go/go.d.plugin/agent/module"

    "golang.org/x/text/cases"
    "golang.org/x/text/language"
)

type (
    // Charts is an alias for module.Charts
    Charts = module.Charts
    // Chart is an alias for module.Charts
    Chart = module.Chart
    // Dims is an alias for module.Dims
    Dims = module.Dims
    // Dim is an alias for module.Dim
    Dim = module.Dim
)

const (
    prioQueries = module.Priority + iota
    prioIPRateLimitedQueries
    prioQueryType
    prioQueryClass
    prioQueryOpCode
    prioQueryFlag
    prioDNSCryptQueries

    prioRecurReplies
    prioReplyRCode

    prioRecurTime

    prioCache
    prioCachePercentage
    prioCachePrefetch
    prioCacheExpired
    prioZeroTTL
    prioCacheCount

    prioReqListUsage
    prioReqListCurUsage
    prioReqListJostle

    prioTCPUsage

    prioMemCache
    prioMemMod
    prioMemStreamWait
    prioUptime

    prioThread
)

func charts(cumulative bool) *Charts {
    return &Charts{
        makeIncrIf(queriesChart.Copy(), cumulative),
        makeIncrIf(ipRateLimitedQueriesChart.Copy(), cumulative),
        makeIncrIf(cacheChart.Copy(), cumulative),
        makePercOfIncrIf(cachePercentageChart.Copy(), cumulative),
        makeIncrIf(prefetchChart.Copy(), cumulative),
        makeIncrIf(expiredChart.Copy(), cumulative),
        makeIncrIf(zeroTTLChart.Copy(), cumulative),
        makeIncrIf(dnsCryptChart.Copy(), cumulative),
        makeIncrIf(recurRepliesChart.Copy(), cumulative),
        recurTimeChart.Copy(),
        reqListUsageChart.Copy(),
        reqListCurUsageChart.Copy(),
        makeIncrIf(reqListJostleChart.Copy(), cumulative),
        tcpUsageChart.Copy(),
        uptimeChart.Copy(),
    }
}

func extendedCharts(cumulative bool) *Charts {
    return &Charts{
        memCacheChart.Copy(),
        memModChart.Copy(),
        memStreamWaitChart.Copy(),
        cacheCountChart.Copy(),
        makeIncrIf(queryTypeChart.Copy(), cumulative),
        makeIncrIf(queryClassChart.Copy(), cumulative),
        makeIncrIf(queryOpCodeChart.Copy(), cumulative),
        makeIncrIf(queryFlagChart.Copy(), cumulative),
        makeIncrIf(answerRCodeChart.Copy(), cumulative),
    }
}

func threadCharts(thread string, cumulative bool) *Charts {
    charts := charts(cumulative)
    _ = charts.Remove(uptimeChart.ID)

    for i, chart := range *charts {
        convertTotalChartToThread(chart, thread, prioThread+i)
    }
    return charts
}

func convertTotalChartToThread(chart *Chart, thread string, priority int) {
    chart.ID = fmt.Sprintf("%s_%s", thread, chart.ID)
    chart.Title = fmt.Sprintf("%s %s",
        cases.Title(language.English, cases.Compact).String(thread),
        chart.Title,
    )
    chart.Fam = thread + "_stats"
    chart.Ctx = "thread_" + chart.Ctx
    chart.Priority = priority
    for _, dim := range chart.Dims {
        dim.ID = strings.Replace(dim.ID, "total", thread, 1)
    }
}

// Common stats charts
// see https://nlnetlabs.nl/documentation/unbound/unbound-control for the stats provided by unbound-control
var (
    queriesChart = Chart{
        ID:       "queries",
        Title:    "Received Queries",
        Units:    "queries",
        Fam:      "queries",
        Ctx:      "unbound.queries",
        Priority: prioQueries,
        Dims: Dims{
            {ID: "total.num.queries", Name: "queries"},
        },
    }
    ipRateLimitedQueriesChart = Chart{
        ID:       "queries_ip_ratelimited",
        Title:    "Rate Limited Queries",
        Units:    "queries",
        Fam:      "queries",
        Ctx:      "unbound.queries_ip_ratelimited",
        Priority: prioIPRateLimitedQueries,
        Dims: Dims{
            {ID: "total.num.queries_ip_ratelimited", Name: "ratelimited"},
        },
    }
    // ifdef USE_DNSCRYPT
    dnsCryptChart = Chart{
        ID:       "dnscrypt_queries",
        Title:    "DNSCrypt Queries",
        Units:    "queries",
        Fam:      "queries",
        Ctx:      "unbound.dnscrypt_queries",
        Priority: prioDNSCryptQueries,
        Dims: Dims{
            {ID: "total.num.dnscrypt.crypted", Name: "crypted"},
            {ID: "total.num.dnscrypt.cert", Name: "cert"},
            {ID: "total.num.dnscrypt.cleartext", Name: "cleartext"},
            {ID: "total.num.dnscrypt.malformed", Name: "malformed"},
        },
    }
    cacheChart = Chart{
        ID:       "cache",
        Title:    "Cache Statistics",
        Units:    "events",
        Fam:      "cache",
        Ctx:      "unbound.cache",
        Type:     module.Stacked,
        Priority: prioCache,
        Dims: Dims{
            {ID: "total.num.cachehits", Name: "hits"},
            {ID: "total.num.cachemiss", Name: "miss"},
        },
    }
    cachePercentageChart = Chart{
        ID:       "cache_percentage",
        Title:    "Cache Statistics Percentage",
        Units:    "percentage",
        Fam:      "cache",
        Ctx:      "unbound.cache_percentage",
        Type:     module.Stacked,
        Priority: prioCachePercentage,
        Dims: Dims{
            {ID: "total.num.cachehits", Name: "hits", Algo: module.PercentOfAbsolute},
            {ID: "total.num.cachemiss", Name: "miss", Algo: module.PercentOfAbsolute},
        },
    }
    prefetchChart = Chart{
        ID:       "cache_prefetch",
        Title:    "Cache Prefetches",
        Units:    "prefetches",
        Fam:      "cache",
        Ctx:      "unbound.prefetch",
        Priority: prioCachePrefetch,
        Dims: Dims{
            {ID: "total.num.prefetch", Name: "prefetches"},
        },
    }
    expiredChart = Chart{
        ID:       "cache_expired",
        Title:    "Replies Served From Expired Cache",
        Units:    "replies",
        Fam:      "cache",
        Ctx:      "unbound.expired",
        Priority: prioCacheExpired,
        Dims: Dims{
            {ID: "total.num.expired", Name: "expired"},
        },
    }
    zeroTTLChart = Chart{
        ID:       "zero_ttl_replies",
        Title:    "Replies Served From Expired Cache",
        Units:    "replies",
        Fam:      "cache",
        Ctx:      "unbound.zero_ttl_replies",
        Priority: prioZeroTTL,
        Dims: Dims{
            {ID: "total.num.zero_ttl", Name: "zero_ttl"},
        },
    }
    recurRepliesChart = Chart{
        ID:       "recursive_replies",
        Title:    "Replies That Needed Recursive Processing",
        Units:    "replies",
        Fam:      "replies",
        Ctx:      "unbound.recursive_replies",
        Priority: prioRecurReplies,
        Dims: Dims{
            {ID: "total.num.recursivereplies", Name: "recursive"},
        },
    }
    recurTimeChart = Chart{
        ID:       "recursion_time",
        Title:    "Time Spent On Recursive Processing",
        Units:    "milliseconds",
        Fam:      "recursion timings",
        Ctx:      "unbound.recursion_time",
        Priority: prioRecurTime,
        Dims: Dims{
            {ID: "total.recursion.time.avg", Name: "avg"},
            {ID: "total.recursion.time.median", Name: "median"},
        },
    }
    reqListUsageChart = Chart{
        ID:       "request_list_usage",
        Title:    "Request List Usage",
        Units:    "queries",
        Fam:      "request list",
        Ctx:      "unbound.request_list_usage",
        Priority: prioReqListUsage,
        Dims: Dims{
            {ID: "total.requestlist.avg", Name: "avg", Div: 1000},
            {ID: "total.requestlist.max", Name: "max"}, // all time max in cumulative mode, never resets
        },
    }
    reqListCurUsageChart = Chart{
        ID:       "current_request_list_usage",
        Title:    "Current Request List Usage",
        Units:    "queries",
        Fam:      "request list",
        Ctx:      "unbound.current_request_list_usage",
        Type:     module.Area,
        Priority: prioReqListCurUsage,
        Dims: Dims{
            {ID: "total.requestlist.current.all", Name: "all"},
            {ID: "total.requestlist.current.user", Name: "user"},
        },
    }
    reqListJostleChart = Chart{
        ID:       "request_list_jostle_list",
        Title:    "Request List Jostle List Events",
        Units:    "queries",
        Fam:      "request list",
        Ctx:      "unbound.request_list_jostle_list",
        Priority: prioReqListJostle,
        Dims: Dims{
            {ID: "total.requestlist.overwritten", Name: "overwritten"},
            {ID: "total.requestlist.exceeded", Name: "dropped"},
        },
    }
    tcpUsageChart = Chart{
        ID:       "tcpusage",
        Title:    "TCP Handler Buffers",
        Units:    "buffers",
        Fam:      "tcp buffers",
        Ctx:      "unbound.tcpusage",
        Priority: prioTCPUsage,
        Dims: Dims{
            {ID: "total.tcpusage", Name: "usage"},
        },
    }
    uptimeChart = Chart{
        ID:       "uptime",
        Title:    "Uptime",
        Units:    "seconds",
        Fam:      "uptime",
        Ctx:      "unbound.uptime",
        Priority: prioUptime,
        Dims: Dims{
            {ID: "time.up", Name: "time"},
        },
    }
)

// Extended stats charts
var (
    // TODO: do not add dnscrypt stuff by default?
    memCacheChart = Chart{
        ID:       "cache_memory",
        Title:    "Cache Memory",
        Units:    "KB",
        Fam:      "mem",
        Ctx:      "unbound.cache_memory",
        Type:     module.Stacked,
        Priority: prioMemCache,
        Dims: Dims{
            {ID: "mem.cache.message", Name: "message", Div: 1024},
            {ID: "mem.cache.rrset", Name: "rrset", Div: 1024},
            {ID: "mem.cache.dnscrypt_nonce", Name: "dnscrypt_nonce", Div: 1024},                 // ifdef USE_DNSCRYPT
            {ID: "mem.cache.dnscrypt_shared_secret", Name: "dnscrypt_shared_secret", Div: 1024}, // ifdef USE_DNSCRYPT
        },
    }
    // TODO: do not add subnet and ipsecmod stuff by default?
    memModChart = Chart{
        ID:       "mod_memory",
        Title:    "Module Memory",
        Units:    "KB",
        Fam:      "mem",
        Ctx:      "unbound.mod_memory",
        Type:     module.Stacked,
        Priority: prioMemMod,
        Dims: Dims{
            {ID: "mem.mod.iterator", Name: "iterator", Div: 1024},
            {ID: "mem.mod.respip", Name: "respip", Div: 1024},
            {ID: "mem.mod.validator", Name: "validator", Div: 1024},
            {ID: "mem.mod.subnet", Name: "subnet", Div: 1024},  // ifdef CLIENT_SUBNET
            {ID: "mem.mod.ipsecmod", Name: "ipsec", Div: 1024}, // ifdef USE_IPSECMOD
        },
    }
    memStreamWaitChart = Chart{
        ID:       "mem_stream_wait",
        Title:    "TCP and TLS Stream Waif Buffer Memory",
        Units:    "KB",
        Fam:      "mem",
        Ctx:      "unbound.mem_streamwait",
        Priority: prioMemStreamWait,
        Dims: Dims{
            {ID: "mem.streamwait", Name: "streamwait", Div: 1024},
        },
    }
    // NOTE: same family as for cacheChart
    cacheCountChart = Chart{
        ID:       "cache_count",
        Title:    "Cache Items Count",
        Units:    "items",
        Fam:      "cache",
        Ctx:      "unbound.cache_count",
        Type:     module.Stacked,
        Priority: prioCacheCount,
        Dims: Dims{
            {ID: "infra.cache.count", Name: "infra"},
            {ID: "key.cache.count", Name: "key"},
            {ID: "msg.cache.count", Name: "msg"},
            {ID: "rrset.cache.count", Name: "rrset"},
            {ID: "dnscrypt_nonce.cache.count", Name: "dnscrypt_nonce"},
            {ID: "dnscrypt_shared_secret.cache.count", Name: "shared_secret"},
        },
    }
    queryTypeChart = Chart{
        ID:       "queries_by_type",
        Title:    "Queries By Type",
        Units:    "queries",
        Fam:      "queries",
        Ctx:      "unbound.type_queries",
        Type:     module.Stacked,
        Priority: prioQueryType,
    }
    queryClassChart = Chart{
        ID:       "queries_by_class",
        Title:    "Queries By Class",
        Units:    "queries",
        Fam:      "queries",
        Ctx:      "unbound.class_queries",
        Type:     module.Stacked,
        Priority: prioQueryClass,
    }
    queryOpCodeChart = Chart{
        ID:       "queries_by_opcode",
        Title:    "Queries By OpCode",
        Units:    "queries",
        Fam:      "queries",
        Ctx:      "unbound.opcode_queries",
        Type:     module.Stacked,
        Priority: prioQueryOpCode,
    }
    queryFlagChart = Chart{
        ID:       "queries_by_flag",
        Title:    "Queries By Flag",
        Units:    "queries",
        Fam:      "queries",
        Ctx:      "unbound.flag_queries",
        Type:     module.Stacked,
        Priority: prioQueryFlag,
        Dims: Dims{
            {ID: "num.query.flags.QR", Name: "QR"},
            {ID: "num.query.flags.AA", Name: "AA"},
            {ID: "num.query.flags.TC", Name: "TC"},
            {ID: "num.query.flags.RD", Name: "RD"},
            {ID: "num.query.flags.RA", Name: "RA"},
            {ID: "num.query.flags.Z", Name: "Z"},
            {ID: "num.query.flags.AD", Name: "AD"},
            {ID: "num.query.flags.CD", Name: "CD"},
        },
    }
    answerRCodeChart = Chart{
        ID:       "replies_by_rcode",
        Title:    "Replies By RCode",
        Units:    "replies",
        Fam:      "replies",
        Ctx:      "unbound.rcode_answers",
        Type:     module.Stacked,
        Priority: prioReplyRCode,
    }
)

func (u *Unbound) updateCharts() {
    if len(u.curCache.threads) > 1 {
        for v := range u.curCache.threads {
            if !u.cache.threads[v] {
                u.cache.threads[v] = true
                u.addThreadCharts(v)
            }
        }
    }
    // 0-6 rcodes always included
    if hasExtendedData := len(u.curCache.answerRCode) > 0; !hasExtendedData {
        return
    }

    if !u.extChartsCreated {
        charts := extendedCharts(u.Cumulative)
        if err := u.Charts().Add(*charts...); err != nil {
            u.Warningf("add extended charts: %v", err)
        }
        u.extChartsCreated = true
    }

    for v := range u.curCache.queryType {
        if !u.cache.queryType[v] {
            u.cache.queryType[v] = true
            u.addDimToQueryTypeChart(v)
        }
    }
    for v := range u.curCache.queryClass {
        if !u.cache.queryClass[v] {
            u.cache.queryClass[v] = true
            u.addDimToQueryClassChart(v)
        }
    }
    for v := range u.curCache.queryOpCode {
        if !u.cache.queryOpCode[v] {
            u.cache.queryOpCode[v] = true
            u.addDimToQueryOpCodeChart(v)
        }
    }
    for v := range u.curCache.answerRCode {
        if !u.cache.answerRCode[v] {
            u.cache.answerRCode[v] = true
            u.addDimToAnswerRcodeChart(v)
        }
    }
}

func (u *Unbound) addThreadCharts(thread string) {
    charts := threadCharts(thread, u.Cumulative)
    if err := u.Charts().Add(*charts...); err != nil {
        u.Warningf("add '%s' charts: %v", thread, err)
    }
}

func (u *Unbound) addDimToQueryTypeChart(typ string) {
    u.addDimToChart(queryTypeChart.ID, "num.query.type."+typ, typ)
}
func (u *Unbound) addDimToQueryClassChart(class string) {
    u.addDimToChart(queryClassChart.ID, "num.query.class."+class, class)
}
func (u *Unbound) addDimToQueryOpCodeChart(opcode string) {
    u.addDimToChart(queryOpCodeChart.ID, "num.query.opcode."+opcode, opcode)
}
func (u *Unbound) addDimToAnswerRcodeChart(rcode string) {
    u.addDimToChart(answerRCodeChart.ID, "num.answer.rcode."+rcode, rcode)
}

func (u *Unbound) addDimToChart(chartID, dimID, dimName string) {
    chart := u.Charts().Get(chartID)
    if chart == nil {
        u.Warningf("add '%s' dim: couldn't find '%s' chart", dimID, chartID)
        return
    }
    dim := &Dim{ID: dimID, Name: dimName}
    if u.Cumulative {
        dim.Algo = module.Incremental
    }
    if err := chart.AddDim(dim); err != nil {
        u.Warningf("add '%s' dim: %v", dimID, err)
        return
    }
    chart.MarkNotCreated()
}

func makeIncrIf(chart *Chart, do bool) *Chart {
    if !do {
        return chart
    }
    chart.Units += "/s"
    for _, d := range chart.Dims {
        d.Algo = module.Incremental
    }
    return chart
}

func makePercOfIncrIf(chart *Chart, do bool) *Chart {
    if !do {
        return chart
    }
    for _, d := range chart.Dims {
        d.Algo = module.PercentOfIncremental
    }
    return chart
}