add go.d/nsd (#18302)
Ilya Mashchenko committed
Aug 11, 2024 at 19:12 UTC
1cd805d515f0f9c5bb97d5abba680d28e1f6a351
14 files changed
+1367
-1
src/collectors/python.d.plugin/python.d.conf
+1
-1
@@ -38,7 +38,6 @@ go_expvar: no
38
# haproxy: yes
39
# monit: yes
40
# nvidia_smi: yes
41
-# nsd: yes
41
# openldap: yes
42
# oracledb: yes
43
# pandas: yes
@@ -73,6 +72,7 @@ memcached: no # Removed (replaced with go.d/memcached).
72
mongodb: no # Removed (replaced with go.d/mongodb).
73
mysql: no # Removed (replaced with go.d/mysql).
74
nginx: no # Removed (replaced with go.d/nginx).
75
+nsd: no # Removed (replaced with go.d/nsd).
76
postfix: no # Removed (replaced with go.d/postfix).
77
postgres: no # Removed (replaced with go.d/postgres).
78
proxysql: no # Removed (replaced with go.d/proxysql).
src/go/plugin/go.d/modules/init.go
+1
@@ -58,6 +58,7 @@ import (
58
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginx"
59
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginxplus"
60
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginxvts"
61
+ _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nsd"
62
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/ntpd"
63
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nvidia_smi"
64
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nvme"
src/go/plugin/go.d/modules/nsd/charts.go
new
+249
@@ -0,0 +1,249 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nsd
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
7
+)
8
+
9
+const (
10
+ prioQueries = module.Priority + iota
11
+ prioQueriesByType
12
+ prioQueriesByOpcode
13
+ prioQueriesByClass
14
+ prioQueriesByProtocol
15
+
16
+ prioAnswersByRcode
17
+
18
+ prioErrors
19
+
20
+ prioDrops
21
+
22
+ prioZones
23
+ prioZoneTransfersRequests
24
+ prioZoneTransferMemory
25
+
26
+ prioDatabaseSize
27
+
28
+ prioUptime
29
+)
30
+
31
+var charts = module.Charts{
32
+ queriesChart.Copy(),
33
+ queriesByTypeChart.Copy(),
34
+ queriesByOpcodeChart.Copy(),
35
+ queriesByClassChart.Copy(),
36
+ queriesByProtocolChart.Copy(),
37
+
38
+ answersByRcodeChart.Copy(),
39
+
40
+ zonesChart.Copy(),
41
+ zoneTransfersRequestsChart.Copy(),
42
+ zoneTransferMemoryChart.Copy(),
43
+
44
+ databaseSizeChart.Copy(),
45
+
46
+ errorsChart.Copy(),
47
+
48
+ dropsChart.Copy(),
49
+
50
+ uptimeChart.Copy(),
51
+}
52
+
53
+var (
54
+ queriesChart = module.Chart{
55
+ ID: "queries",
56
+ Title: "Queries",
57
+ Units: "queries/s",
58
+ Fam: "queries",
59
+ Ctx: "nsd.queries",
60
+ Priority: prioQueries,
61
+ Dims: module.Dims{
62
+ {ID: "num.queries", Name: "queries", Algo: module.Incremental},
63
+ },
64
+ }
65
+ queriesByTypeChart = func() module.Chart {
66
+ chart := module.Chart{
67
+ ID: "queries_by_type",
68
+ Title: "Queries Type",
69
+ Units: "queries/s",
70
+ Fam: "queries",
71
+ Ctx: "nsd.queries_by_type",
72
+ Priority: prioQueriesByType,
73
+ Type: module.Stacked,
74
+ }
75
+ for _, v := range queryTypes {
76
+ name := v
77
+ if s, ok := queryTypeNumberMap[v]; ok {
78
+ name = s
79
+ }
80
+ chart.Dims = append(chart.Dims, &module.Dim{
81
+ ID: "num.type." + v,
82
+ Name: name,
83
+ Algo: module.Incremental,
84
+ })
85
+ }
86
+ return chart
87
+ }()
88
+ queriesByOpcodeChart = func() module.Chart {
89
+ chart := module.Chart{
90
+ ID: "queries_by_opcode",
91
+ Title: "Queries Opcode",
92
+ Units: "queries/s",
93
+ Fam: "queries",
94
+ Ctx: "nsd.queries_by_opcode",
95
+ Priority: prioQueriesByOpcode,
96
+ Type: module.Stacked,
97
+ }
98
+ for _, v := range queryOpcodes {
99
+ chart.Dims = append(chart.Dims, &module.Dim{
100
+ ID: "num.opcode." + v,
101
+ Name: v,
102
+ Algo: module.Incremental,
103
+ })
104
+ }
105
+ return chart
106
+ }()
107
+ queriesByClassChart = func() module.Chart {
108
+ chart := module.Chart{
109
+ ID: "queries_by_class",
110
+ Title: "Queries Class",
111
+ Units: "queries/s",
112
+ Fam: "queries",
113
+ Ctx: "nsd.queries_by_class",
114
+ Priority: prioQueriesByClass,
115
+ Type: module.Stacked,
116
+ }
117
+ for _, v := range queryClasses {
118
+ chart.Dims = append(chart.Dims, &module.Dim{
119
+ ID: "num.class." + v,
120
+ Name: v,
121
+ Algo: module.Incremental,
122
+ })
123
+ }
124
+ return chart
125
+ }()
126
+ queriesByProtocolChart = module.Chart{
127
+ ID: "queries_by_protocol",
128
+ Title: "Queries Protocol",
129
+ Units: "queries/s",
130
+ Fam: "queries",
131
+ Ctx: "nsd.queries_by_protocol",
132
+ Priority: prioQueriesByProtocol,
133
+ Type: module.Stacked,
134
+ Dims: module.Dims{
135
+ {ID: "num.udp", Name: "udp", Algo: module.Incremental},
136
+ {ID: "num.udp6", Name: "udp6", Algo: module.Incremental},
137
+ {ID: "num.tcp", Name: "tcp", Algo: module.Incremental},
138
+ {ID: "num.tcp6", Name: "tcp6", Algo: module.Incremental},
139
+ {ID: "num.tls", Name: "tls", Algo: module.Incremental},
140
+ {ID: "num.tls6", Name: "tls6", Algo: module.Incremental},
141
+ },
142
+ }
143
+
144
+ answersByRcodeChart = func() module.Chart {
145
+ chart := module.Chart{
146
+ ID: "answers_by_rcode",
147
+ Title: "Answers Rcode",
148
+ Units: "answers/s",
149
+ Fam: "answers",
150
+ Ctx: "nsd.answers_by_rcode",
151
+ Priority: prioAnswersByRcode,
152
+ Type: module.Stacked,
153
+ }
154
+ for _, v := range answerRcodes {
155
+ chart.Dims = append(chart.Dims, &module.Dim{
156
+ ID: "num.rcode." + v,
157
+ Name: v,
158
+ Algo: module.Incremental,
159
+ })
160
+ }
161
+ return chart
162
+ }()
163
+
164
+ errorsChart = module.Chart{
165
+ ID: "errors",
166
+ Title: "Errors",
167
+ Units: "errors/s",
168
+ Fam: "errors",
169
+ Ctx: "nsd.errors",
170
+ Priority: prioErrors,
171
+ Dims: module.Dims{
172
+ {ID: "num.rxerr", Name: "query", Algo: module.Incremental},
173
+ {ID: "num.txerr", Name: "answer", Mul: -1, Algo: module.Incremental},
174
+ },
175
+ }
176
+
177
+ dropsChart = module.Chart{
178
+ ID: "drops",
179
+ Title: "Drops",
180
+ Units: "drops/s",
181
+ Fam: "drops",
182
+ Ctx: "nsd.drops",
183
+ Priority: prioDrops,
184
+ Dims: module.Dims{
185
+ {ID: "num.dropped", Name: "query", Algo: module.Incremental},
186
+ },
187
+ }
188
+
189
+ zonesChart = module.Chart{
190
+ ID: "zones",
191
+ Title: "Zones",
192
+ Units: "zones",
193
+ Fam: "zones",
194
+ Ctx: "nsd.zones",
195
+ Priority: prioZones,
196
+ Dims: module.Dims{
197
+ {ID: "zone.master", Name: "master"},
198
+ {ID: "zone.slave", Name: "slave"},
199
+ },
200
+ }
201
+ zoneTransfersRequestsChart = module.Chart{
202
+ ID: "zone_transfers_requests",
203
+ Title: "Zone Transfers",
204
+ Units: "requests/s",
205
+ Fam: "zones",
206
+ Ctx: "nsd.zone_transfers_requests",
207
+ Priority: prioZoneTransfersRequests,
208
+ Dims: module.Dims{
209
+ {ID: "num.raxfr", Name: "AXFR", Algo: module.Incremental},
210
+ {ID: "num.rixfr", Name: "IXFR", Algo: module.Incremental},
211
+ },
212
+ }
213
+ zoneTransferMemoryChart = module.Chart{
214
+ ID: "zone_transfer_memory",
215
+ Title: "Zone Transfer Memory",
216
+ Units: "bytes",
217
+ Fam: "zones",
218
+ Ctx: "nsd.zone_transfer_memory",
219
+ Priority: prioZoneTransferMemory,
220
+ Dims: module.Dims{
221
+ {ID: "size.xfrd.mem", Name: "used"},
222
+ },
223
+ }
224
+
225
+ databaseSizeChart = module.Chart{
226
+ ID: "database_size",
227
+ Title: "Database Size",
228
+ Units: "bytes",
229
+ Fam: "database",
230
+ Ctx: "nsd.database_size",
231
+ Priority: prioDatabaseSize,
232
+ Dims: module.Dims{
233
+ {ID: "size.db.disk", Name: "disk"},
234
+ {ID: "size.db.mem", Name: "mem"},
235
+ },
236
+ }
237
+
238
+ uptimeChart = module.Chart{
239
+ ID: "uptime",
240
+ Title: "Uptime",
241
+ Units: "seconds",
242
+ Fam: "uptime",
243
+ Ctx: "nsd.uptime",
244
+ Priority: prioUptime,
245
+ Dims: module.Dims{
246
+ {ID: "time.boot", Name: "uptime"},
247
+ },
248
+ }
249
+)
src/go/plugin/go.d/modules/nsd/collect.go
new
+81
@@ -0,0 +1,81 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nsd
4
+
5
+import (
6
+ "bufio"
7
+ "bytes"
8
+ "errors"
9
+ "strconv"
10
+ "strings"
11
+)
12
+
13
+func (n *Nsd) collect() (map[string]int64, error) {
14
+ stats, err := n.exec.stats()
15
+ if err != nil {
16
+ return nil, err
17
+ }
18
+
19
+ if len(stats) == 0 {
20
+ return nil, errors.New("empty stats response")
21
+ }
22
+
23
+ mx := make(map[string]int64)
24
+
25
+ sc := bufio.NewScanner(bytes.NewReader(stats))
26
+
27
+ for sc.Scan() {
28
+ n.collectStatsLine(mx, sc.Text())
29
+ }
30
+
31
+ if len(mx) == 0 {
32
+ return nil, errors.New("unexpected stats response: no metrics found")
33
+ }
34
+
35
+ addMissingMetrics(mx, "num.rcode.", answerRcodes)
36
+ addMissingMetrics(mx, "num.opcode.", queryOpcodes)
37
+ addMissingMetrics(mx, "num.class.", queryClasses)
38
+ addMissingMetrics(mx, "num.type.", queryTypes)
39
+
40
+ return mx, nil
41
+}
42
+
43
+func (n *Nsd) collectStatsLine(mx map[string]int64, line string) {
44
+ if line = strings.TrimSpace(line); line == "" {
45
+ return
46
+ }
47
+
48
+ key, value, ok := strings.Cut(line, "=")
49
+ if !ok {
50
+ n.Debugf("invalid line in stats: '%s'", line)
51
+ return
52
+ }
53
+
54
+ var v int64
55
+ var f float64
56
+ var err error
57
+
58
+ switch key {
59
+ case "time.boot":
60
+ f, err = strconv.ParseFloat(value, 64)
61
+ v = int64(f)
62
+ default:
63
+ v, err = strconv.ParseInt(value, 10, 64)
64
+ }
65
+
66
+ if err != nil {
67
+ n.Debugf("invalid value in stats line '%s': '%s'", line, value)
68
+ return
69
+ }
70
+
71
+ mx[key] = v
72
+}
73
+
74
+func addMissingMetrics(mx map[string]int64, prefix string, values []string) {
75
+ for _, v := range values {
76
+ k := prefix + v
77
+ if _, ok := mx[k]; !ok {
78
+ mx[k] = 0
79
+ }
80
+ }
81
+}
src/go/plugin/go.d/modules/nsd/config_schema.json
new
+35
@@ -0,0 +1,35 @@
1
+{
2
+ "jsonSchema": {
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "NSD collector configuration.",
5
+ "type": "object",
6
+ "properties": {
7
+ "update_every": {
8
+ "title": "Update every",
9
+ "description": "Data collection interval, measured in seconds.",
10
+ "type": "integer",
11
+ "minimum": 1,
12
+ "default": 10
13
+ },
14
+ "timeout": {
15
+ "title": "Timeout",
16
+ "description": "Timeout for executing the binary, specified in seconds.",
17
+ "type": "number",
18
+ "minimum": 0.5,
19
+ "default": 2
20
+ }
21
+ },
22
+ "additionalProperties": false,
23
+ "patternProperties": {
24
+ "^name$": {}
25
+ }
26
+ },
27
+ "uiSchema": {
28
+ "uiOptions": {
29
+ "fullPage": true
30
+ },
31
+ "timeout": {
32
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
33
+ }
34
+ }
35
+}
src/go/plugin/go.d/modules/nsd/exec.go
new
+47
@@ -0,0 +1,47 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nsd
4
+
5
+import (
6
+ "context"
7
+ "fmt"
8
+ "os/exec"
9
+ "time"
10
+
11
+ "github.com/netdata/netdata/go/plugins/logger"
12
+)
13
+
14
+type nsdControlBinary interface {
15
+ stats() ([]byte, error)
16
+}
17
+
18
+func newNsdControlExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *nsdControlExec {
19
+ return &nsdControlExec{
20
+ Logger: log,
21
+ ndsudoPath: ndsudoPath,
22
+ timeout: timeout,
23
+ }
24
+}
25
+
26
+type nsdControlExec struct {
27
+ *logger.Logger
28
+
29
+ ndsudoPath string
30
+ timeout time.Duration
31
+}
32
+
33
+func (e *nsdControlExec) stats() ([]byte, error) {
34
+ ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
35
+ defer cancel()
36
+
37
+ cmd := exec.CommandContext(ctx, e.ndsudoPath, "nsd-control-stats")
38
+
39
+ e.Debugf("executing '%s'", cmd)
40
+
41
+ bs, err := cmd.Output()
42
+ if err != nil {
43
+ return nil, fmt.Errorf("error on '%s': %v", cmd, err)
44
+ }
45
+
46
+ return bs, nil
47
+}
src/go/plugin/go.d/modules/nsd/init.go
new
+23
@@ -0,0 +1,23 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nsd
4
+
5
+import (
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+
10
+ "github.com/netdata/netdata/go/plugins/pkg/executable"
11
+)
12
+
13
+func (n *Nsd) initNsdControlExec() (nsdControlBinary, error) {
14
+ ndsudoPath := filepath.Join(executable.Directory, "ndsudo")
15
+ if _, err := os.Stat(ndsudoPath); err != nil {
16
+ return nil, fmt.Errorf("ndsudo executable not found: %v", err)
17
+
18
+ }
19
+
20
+ nsdControl := newNsdControlExec(ndsudoPath, n.Timeout.Duration(), n.Logger)
21
+
22
+ return nsdControl, nil
23
+}
src/go/plugin/go.d/modules/nsd/metadata.yaml
new
+272
@@ -0,0 +1,272 @@
1
+plugin_name: go.d.plugin
2
+modules:
3
+ - meta:
4
+ id: collector-go.d.plugin-nsd
5
+ plugin_name: go.d.plugin
6
+ module_name: nsd
7
+ monitored_instance:
8
+ name: NSD
9
+ link: "https://nsd.docs.nlnetlabs.nl/en/latest"
10
+ icon_filename: 'nsd.svg'
11
+ categories:
12
+ - data-collection.dns-and-dhcp-servers
13
+ keywords:
14
+ - nsd
15
+ - dns
16
+ related_resources:
17
+ integrations:
18
+ list: []
19
+ info_provided_to_referring_integrations:
20
+ description: ""
21
+ most_popular: false
22
+ overview:
23
+ data_collection:
24
+ metrics_description: >
25
+ This collector monitors NSD statistics like queries, zones, protocols, query types and more.
26
+ It relies on the [`nsd-control`](https://nsd.docs.nlnetlabs.nl/en/latest/manpages/nsd-control.html) CLI tool but avoids directly executing the binary.
27
+ Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment.
28
+ This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management.
29
+
30
+ Executed commands:
31
+
32
+ - `nsd-control stats_noreset`
33
+ method_description: ""
34
+ supported_platforms:
35
+ include: []
36
+ exclude: []
37
+ multi_instance: false
38
+ additional_permissions:
39
+ description: ""
40
+ default_behavior:
41
+ auto_detection:
42
+ description: ""
43
+ limits:
44
+ description: ""
45
+ performance_impact:
46
+ description: ""
47
+ setup:
48
+ prerequisites:
49
+ list: []
50
+ configuration:
51
+ file:
52
+ name: go.d/nsd.conf
53
+ options:
54
+ description: |
55
+ The following options can be defined globally: update_every.
56
+ folding:
57
+ title: Config options
58
+ enabled: true
59
+ list:
60
+ - name: update_every
61
+ description: Data collection frequency.
62
+ default_value: 10
63
+ required: false
64
+ - name: timeout
65
+ description: nsd-control binary execution timeout.
66
+ default_value: 2
67
+ required: false
68
+ examples:
69
+ folding:
70
+ title: Config
71
+ enabled: true
72
+ list:
73
+ - name: Custom update_every
74
+ description: Allows you to override the default data collection interval.
75
+ config: |
76
+ jobs:
77
+ - name: nsd
78
+ update_every: 5 # Collect logical volume statistics every 5 seconds
79
+ troubleshooting:
80
+ problems:
81
+ list: []
82
+ alerts: []
83
+ metrics:
84
+ folding:
85
+ title: Metrics
86
+ enabled: false
87
+ description: ""
88
+ availability: []
89
+ scopes:
90
+ - name: global
91
+ description: These metrics refer to the the entire monitored application.
92
+ labels: []
93
+ metrics:
94
+ - name: nsd.queries
95
+ description: Queries
96
+ unit: 'queries/s'
97
+ chart_type: line
98
+ dimensions:
99
+ - name: queries
100
+ - name: nsd.queries_by_type
101
+ description: Queries Type
102
+ unit: 'queries/s'
103
+ chart_type: stacked
104
+ dimensions:
105
+ - name: "A"
106
+ - name: "NS"
107
+ - name: "MD"
108
+ - name: "MF"
109
+ - name: "CNAME"
110
+ - name: "SOA"
111
+ - name: "MB"
112
+ - name: "MG"
113
+ - name: "MR"
114
+ - name: "NULL"
115
+ - name: "WKS"
116
+ - name: "PTR"
117
+ - name: "HINFO"
118
+ - name: "MINFO"
119
+ - name: "MX"
120
+ - name: "TXT"
121
+ - name: "RP"
122
+ - name: "AFSDB"
123
+ - name: "X25"
124
+ - name: "ISDN"
125
+ - name: "RT"
126
+ - name: "NSAP"
127
+ - name: "SIG"
128
+ - name: "KEY"
129
+ - name: "PX"
130
+ - name: "AAAA"
131
+ - name: "LOC"
132
+ - name: "NXT"
133
+ - name: "SRV"
134
+ - name: "NAPTR"
135
+ - name: "KX"
136
+ - name: "CERT"
137
+ - name: "DNAME"
138
+ - name: "OPT"
139
+ - name: "APL"
140
+ - name: "DS"
141
+ - name: "SSHFP"
142
+ - name: "IPSECKEY"
143
+ - name: "RRSIG"
144
+ - name: "NSEC"
145
+ - name: "DNSKEY"
146
+ - name: "DHCID"
147
+ - name: "NSEC3"
148
+ - name: "NSEC3PARAM"
149
+ - name: "TLSA"
150
+ - name: "SMIMEA"
151
+ - name: "CDS"
152
+ - name: "CDNSKEY"
153
+ - name: "OPENPGPKEY"
154
+ - name: "CSYNC"
155
+ - name: "ZONEMD"
156
+ - name: "SVCB"
157
+ - name: "HTTPS"
158
+ - name: "SPF"
159
+ - name: "NID"
160
+ - name: "L32"
161
+ - name: "L64"
162
+ - name: "LP"
163
+ - name: "EUI48"
164
+ - name: "EUI64"
165
+ - name: "URI"
166
+ - name: "CAA"
167
+ - name: "AVC"
168
+ - name: "DLV"
169
+ - name: "IXFR"
170
+ - name: "AXFR"
171
+ - name: "MAILB"
172
+ - name: "MAILA"
173
+ - name: "ANY"
174
+ - name: nsd.queries_by_opcode
175
+ description: Queries Opcode
176
+ unit: 'queries/s'
177
+ chart_type: stacked
178
+ dimensions:
179
+ - name: "QUERY"
180
+ - name: "IQUERY"
181
+ - name: "STATUS"
182
+ - name: "NOTIFY"
183
+ - name: "UPDATE"
184
+ - name: "OTHER"
185
+ - name: nsd.queries_by_class
186
+ description: Queries Class
187
+ unit: 'queries/s'
188
+ chart_type: stacked
189
+ dimensions:
190
+ - name: "IN"
191
+ - name: "CS"
192
+ - name: "CH"
193
+ - name: "HS"
194
+ - name: nsd.queries_by_protocol
195
+ description: Queries Protocol
196
+ unit: 'queries/s'
197
+ chart_type: stacked
198
+ dimensions:
199
+ - name: "udp"
200
+ - name: "udp6"
201
+ - name: "tcp"
202
+ - name: "tcp6"
203
+ - name: "tls"
204
+ - name: "tls6"
205
+ - name: nsd.answers_by_rcode
206
+ description: Answers Rcode
207
+ unit: 'answers/s'
208
+ chart_type: stacked
209
+ dimensions:
210
+ - name: "NOERROR"
211
+ - name: "FORMERR"
212
+ - name: "SERVFAIL"
213
+ - name: "NXDOMAIN"
214
+ - name: "NOTIMP"
215
+ - name: "REFUSED"
216
+ - name: "YXDOMAIN"
217
+ - name: "YXRRSET"
218
+ - name: "NXRRSET"
219
+ - name: "NOTAUTH"
220
+ - name: "NOTZONE"
221
+ - name: "RCODE11"
222
+ - name: "RCODE12"
223
+ - name: "RCODE13"
224
+ - name: "RCODE14"
225
+ - name: "RCODE15"
226
+ - name: "BADVERS"
227
+ - name: nsd.errors
228
+ description: Errors
229
+ unit: 'errors/s'
230
+ chart_type: line
231
+ dimensions:
232
+ - name: "query"
233
+ - name: "answer"
234
+ - name: nsd.drops
235
+ description: Drops
236
+ unit: 'drops/s'
237
+ chart_type: line
238
+ dimensions:
239
+ - name: "query"
240
+ - name: nsd.zones
241
+ description: Zones
242
+ unit: 'zones'
243
+ chart_type: line
244
+ dimensions:
245
+ - name: "master"
246
+ - name: "slave"
247
+ - name: nsd.zone_transfers_requests
248
+ description: Zone Transfers
249
+ unit: 'requests/s'
250
+ chart_type: line
251
+ dimensions:
252
+ - name: "AXFR"
253
+ - name: "IXFR"
254
+ - name: nsd.zone_transfer_memory
255
+ description: Zone Transfer Memory
256
+ unit: 'bytes'
257
+ chart_type: line
258
+ dimensions:
259
+ - name: "used"
260
+ - name: nsd.database_size
261
+ description: Database Size
262
+ unit: 'bytes'
263
+ chart_type: line
264
+ dimensions:
265
+ - name: "disk"
266
+ - name: "mem"
267
+ - name: nsd.uptime
268
+ description: Uptime
269
+ unit: 'seconds'
270
+ chart_type: line
271
+ dimensions:
272
+ - name: "uptime"
src/go/plugin/go.d/modules/nsd/nsd.go
new
+97
@@ -0,0 +1,97 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nsd
4
+
5
+import (
6
+ _ "embed"
7
+ "errors"
8
+ "time"
9
+
10
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
11
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
12
+)
13
+
14
+//go:embed "config_schema.json"
15
+var configSchema string
16
+
17
+func init() {
18
+ module.Register("nsd", module.Creator{
19
+ JobConfigSchema: configSchema,
20
+ Defaults: module.Defaults{
21
+ UpdateEvery: 10,
22
+ },
23
+ Create: func() module.Module { return New() },
24
+ Config: func() any { return &Config{} },
25
+ })
26
+}
27
+
28
+func New() *Nsd {
29
+ return &Nsd{
30
+ Config: Config{
31
+ Timeout: web.Duration(time.Second * 2),
32
+ },
33
+ charts: charts.Copy(),
34
+ }
35
+}
36
+
37
+type Config struct {
38
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
39
+ Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
40
+}
41
+
42
+type Nsd struct {
43
+ module.Base
44
+ Config `yaml:",inline" json:""`
45
+
46
+ charts *module.Charts
47
+
48
+ exec nsdControlBinary
49
+}
50
+
51
+func (n *Nsd) Configuration() any {
52
+ return n.Config
53
+}
54
+
55
+func (n *Nsd) Init() error {
56
+ nsdControl, err := n.initNsdControlExec()
57
+ if err != nil {
58
+ n.Errorf("nvm-control exec initialization: %v", err)
59
+ return err
60
+ }
61
+ n.exec = nsdControl
62
+
63
+ return nil
64
+}
65
+
66
+func (n *Nsd) Check() error {
67
+ mx, err := n.collect()
68
+ if err != nil {
69
+ n.Error(err)
70
+ return err
71
+ }
72
+
73
+ if len(mx) == 0 {
74
+ return errors.New("no metrics collected")
75
+ }
76
+
77
+ return nil
78
+}
79
+
80
+func (n *Nsd) Charts() *module.Charts {
81
+ return n.charts
82
+}
83
+
84
+func (n *Nsd) Collect() map[string]int64 {
85
+ mx, err := n.collect()
86
+ if err != nil {
87
+ n.Error(err)
88
+ }
89
+
90
+ if len(mx) == 0 {
91
+ return nil
92
+ }
93
+
94
+ return mx
95
+}
96
+
97
+func (n *Nsd) Cleanup() {}
src/go/plugin/go.d/modules/nsd/nsd_test.go
new
+337
@@ -0,0 +1,337 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nsd
4
+
5
+import (
6
+ "errors"
7
+ "os"
8
+ "testing"
9
+
10
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
11
+
12
+ "github.com/stretchr/testify/assert"
13
+ "github.com/stretchr/testify/require"
14
+)
15
+
16
+var (
17
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
18
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
19
+
20
+ dataStats, _ = os.ReadFile("testdata/stats.txt")
21
+)
22
+
23
+func Test_testDataIsValid(t *testing.T) {
24
+ for name, data := range map[string][]byte{
25
+ "dataConfigJSON": dataConfigJSON,
26
+ "dataConfigYAML": dataConfigYAML,
27
+ "dataStats": dataStats,
28
+ } {
29
+ require.NotNil(t, data, name)
30
+
31
+ }
32
+}
33
+
34
+func TestNsd_Configuration(t *testing.T) {
35
+ module.TestConfigurationSerialize(t, &Nsd{}, dataConfigJSON, dataConfigYAML)
36
+}
37
+
38
+func TestNsd_Init(t *testing.T) {
39
+ tests := map[string]struct {
40
+ config Config
41
+ wantFail bool
42
+ }{
43
+ "fails if failed to locate ndsudo": {
44
+ wantFail: true,
45
+ config: New().Config,
46
+ },
47
+ }
48
+
49
+ for name, test := range tests {
50
+ t.Run(name, func(t *testing.T) {
51
+ nsd := New()
52
+ nsd.Config = test.config
53
+
54
+ if test.wantFail {
55
+ assert.Error(t, nsd.Init())
56
+ } else {
57
+ assert.NoError(t, nsd.Init())
58
+ }
59
+ })
60
+ }
61
+}
62
+
63
+func TestNsd_Cleanup(t *testing.T) {
64
+ tests := map[string]struct {
65
+ prepare func() *Nsd
66
+ }{
67
+ "not initialized exec": {
68
+ prepare: func() *Nsd {
69
+ return New()
70
+ },
71
+ },
72
+ "after check": {
73
+ prepare: func() *Nsd {
74
+ nsd := New()
75
+ nsd.exec = prepareMockOK()
76
+ _ = nsd.Check()
77
+ return nsd
78
+ },
79
+ },
80
+ "after collect": {
81
+ prepare: func() *Nsd {
82
+ nsd := New()
83
+ nsd.exec = prepareMockOK()
84
+ _ = nsd.Collect()
85
+ return nsd
86
+ },
87
+ },
88
+ }
89
+
90
+ for name, test := range tests {
91
+ t.Run(name, func(t *testing.T) {
92
+ nsd := test.prepare()
93
+
94
+ assert.NotPanics(t, nsd.Cleanup)
95
+ })
96
+ }
97
+}
98
+
99
+func TestNsd_Charts(t *testing.T) {
100
+ assert.NotNil(t, New().Charts())
101
+}
102
+
103
+func TestNsd_Check(t *testing.T) {
104
+ tests := map[string]struct {
105
+ prepareMock func() *mockNsdControl
106
+ wantFail bool
107
+ }{
108
+ "success case": {
109
+ prepareMock: prepareMockOK,
110
+ wantFail: false,
111
+ },
112
+ "error on stats call": {
113
+ prepareMock: prepareMockErrOnStats,
114
+ wantFail: true,
115
+ },
116
+ "empty response": {
117
+ prepareMock: prepareMockEmptyResponse,
118
+ wantFail: true,
119
+ },
120
+ "unexpected response": {
121
+ prepareMock: prepareMockUnexpectedResponse,
122
+ wantFail: true,
123
+ },
124
+ }
125
+
126
+ for name, test := range tests {
127
+ t.Run(name, func(t *testing.T) {
128
+ nsd := New()
129
+ mock := test.prepareMock()
130
+ nsd.exec = mock
131
+
132
+ if test.wantFail {
133
+ assert.Error(t, nsd.Check())
134
+ } else {
135
+ assert.NoError(t, nsd.Check())
136
+ }
137
+ })
138
+ }
139
+}
140
+
141
+func TestNsd_Collect(t *testing.T) {
142
+ tests := map[string]struct {
143
+ prepareMock func() *mockNsdControl
144
+ wantMetrics map[string]int64
145
+ }{
146
+ "success case": {
147
+ prepareMock: prepareMockOK,
148
+ wantMetrics: map[string]int64{
149
+ "num.answer_wo_aa": 1,
150
+ "num.class.CH": 0,
151
+ "num.class.CS": 0,
152
+ "num.class.HS": 0,
153
+ "num.class.IN": 1,
154
+ "num.dropped": 1,
155
+ "num.edns": 1,
156
+ "num.ednserr": 1,
157
+ "num.opcode.IQUERY": 0,
158
+ "num.opcode.NOTIFY": 0,
159
+ "num.opcode.OTHER": 0,
160
+ "num.opcode.QUERY": 1,
161
+ "num.opcode.STATUS": 0,
162
+ "num.opcode.UPDATE": 0,
163
+ "num.queries": 1,
164
+ "num.raxfr": 1,
165
+ "num.rcode.BADVERS": 0,
166
+ "num.rcode.FORMERR": 1,
167
+ "num.rcode.NOERROR": 1,
168
+ "num.rcode.NOTAUTH": 0,
169
+ "num.rcode.NOTIMP": 1,
170
+ "num.rcode.NOTZONE": 0,
171
+ "num.rcode.NXDOMAIN": 1,
172
+ "num.rcode.NXRRSET": 0,
173
+ "num.rcode.RCODE11": 0,
174
+ "num.rcode.RCODE12": 0,
175
+ "num.rcode.RCODE13": 0,
176
+ "num.rcode.RCODE14": 0,
177
+ "num.rcode.RCODE15": 0,
178
+ "num.rcode.REFUSED": 1,
179
+ "num.rcode.SERVFAIL": 1,
180
+ "num.rcode.YXDOMAIN": 1,
181
+ "num.rcode.YXRRSET": 0,
182
+ "num.rixfr": 1,
183
+ "num.rxerr": 1,
184
+ "num.tcp": 1,
185
+ "num.tcp6": 1,
186
+ "num.tls": 1,
187
+ "num.tls6": 1,
188
+ "num.truncated": 1,
189
+ "num.txerr": 1,
190
+ "num.type.A": 1,
191
+ "num.type.AAAA": 1,
192
+ "num.type.AFSDB": 1,
193
+ "num.type.APL": 1,
194
+ "num.type.AVC": 0,
195
+ "num.type.CAA": 0,
196
+ "num.type.CDNSKEY": 1,
197
+ "num.type.CDS": 1,
198
+ "num.type.CERT": 1,
199
+ "num.type.CNAME": 1,
200
+ "num.type.CSYNC": 1,
201
+ "num.type.DHCID": 1,
202
+ "num.type.DLV": 0,
203
+ "num.type.DNAME": 1,
204
+ "num.type.DNSKEY": 1,
205
+ "num.type.DS": 1,
206
+ "num.type.EUI48": 1,
207
+ "num.type.EUI64": 1,
208
+ "num.type.HINFO": 1,
209
+ "num.type.HTTPS": 1,
210
+ "num.type.IPSECKEY": 1,
211
+ "num.type.ISDN": 1,
212
+ "num.type.KEY": 1,
213
+ "num.type.KX": 1,
214
+ "num.type.L32": 1,
215
+ "num.type.L64": 1,
216
+ "num.type.LOC": 1,
217
+ "num.type.LP": 1,
218
+ "num.type.MB": 1,
219
+ "num.type.MD": 1,
220
+ "num.type.MF": 1,
221
+ "num.type.MG": 1,
222
+ "num.type.MINFO": 1,
223
+ "num.type.MR": 1,
224
+ "num.type.MX": 1,
225
+ "num.type.NAPTR": 1,
226
+ "num.type.NID": 1,
227
+ "num.type.NS": 1,
228
+ "num.type.NSAP": 1,
229
+ "num.type.NSEC": 1,
230
+ "num.type.NSEC3": 1,
231
+ "num.type.NSEC3PARAM": 1,
232
+ "num.type.NULL": 1,
233
+ "num.type.NXT": 1,
234
+ "num.type.OPENPGPKEY": 1,
235
+ "num.type.OPT": 1,
236
+ "num.type.PTR": 1,
237
+ "num.type.PX": 1,
238
+ "num.type.RP": 1,
239
+ "num.type.RRSIG": 1,
240
+ "num.type.RT": 1,
241
+ "num.type.SIG": 1,
242
+ "num.type.SMIMEA": 1,
243
+ "num.type.SOA": 1,
244
+ "num.type.SPF": 1,
245
+ "num.type.SRV": 1,
246
+ "num.type.SSHFP": 1,
247
+ "num.type.SVCB": 1,
248
+ "num.type.TLSA": 1,
249
+ "num.type.TXT": 1,
250
+ "num.type.TYPE252": 0,
251
+ "num.type.TYPE255": 0,
252
+ "num.type.URI": 0,
253
+ "num.type.WKS": 1,
254
+ "num.type.X25": 1,
255
+ "num.type.ZONEMD": 1,
256
+ "num.udp": 1,
257
+ "num.udp6": 1,
258
+ "server0.queries": 1,
259
+ "size.config.disk": 1,
260
+ "size.config.mem": 1064,
261
+ "size.db.disk": 576,
262
+ "size.db.mem": 920,
263
+ "size.xfrd.mem": 1160464,
264
+ "time.boot": 556,
265
+ "zone.master": 1,
266
+ "zone.slave": 1,
267
+ },
268
+ },
269
+ "error on lvs report call": {
270
+ prepareMock: prepareMockErrOnStats,
271
+ wantMetrics: nil,
272
+ },
273
+ "empty response": {
274
+ prepareMock: prepareMockEmptyResponse,
275
+ wantMetrics: nil,
276
+ },
277
+ "unexpected response": {
278
+ prepareMock: prepareMockUnexpectedResponse,
279
+ wantMetrics: nil,
280
+ },
281
+ }
282
+
283
+ for name, test := range tests {
284
+ t.Run(name, func(t *testing.T) {
285
+ nsd := New()
286
+ mock := test.prepareMock()
287
+ nsd.exec = mock
288
+
289
+ mx := nsd.Collect()
290
+
291
+ assert.Equal(t, test.wantMetrics, mx)
292
+
293
+ if len(test.wantMetrics) > 0 {
294
+ assert.Len(t, *nsd.Charts(), len(charts))
295
+ module.TestMetricsHasAllChartsDims(t, nsd.Charts(), mx)
296
+ }
297
+ })
298
+ }
299
+}
300
+
301
+func prepareMockOK() *mockNsdControl {
302
+ return &mockNsdControl{
303
+ dataStats: dataStats,
304
+ }
305
+}
306
+
307
+func prepareMockErrOnStats() *mockNsdControl {
308
+ return &mockNsdControl{
309
+ errOnStatus: true,
310
+ }
311
+}
312
+
313
+func prepareMockEmptyResponse() *mockNsdControl {
314
+ return &mockNsdControl{}
315
+}
316
+
317
+func prepareMockUnexpectedResponse() *mockNsdControl {
318
+ return &mockNsdControl{
319
+ dataStats: []byte(`
320
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
321
+Nulla malesuada erat id magna mattis, eu viverra tellus rhoncus.
322
+Fusce et felis pulvinar, posuere sem non, porttitor eros.
323
+`),
324
+ }
325
+}
326
+
327
+type mockNsdControl struct {
328
+ errOnStatus bool
329
+ dataStats []byte
330
+}
331
+
332
+func (m *mockNsdControl) stats() ([]byte, error) {
333
+ if m.errOnStatus {
334
+ return nil, errors.New("mock.status() error")
335
+ }
336
+ return m.dataStats, nil
337
+}
src/go/plugin/go.d/modules/nsd/stats_counters.go
new
+123
@@ -0,0 +1,123 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package nsd
4
+
5
+// Docs: https://nsd.docs.nlnetlabs.nl/en/latest/manpages/nsd-control.html?highlight=elapsed#statistics-counters
6
+// Source: https://github.com/NLnetLabs/nsd/blob/b4a5ccd2235a1f8f71f7c640390e409bf123c963/remote.c#L2735
7
+
8
+// https://github.com/NLnetLabs/nsd/blob/b4a5ccd2235a1f8f71f7c640390e409bf123c963/remote.c#L2737
9
+var answerRcodes = []string{
10
+ "NOERROR",
11
+ "FORMERR",
12
+ "SERVFAIL",
13
+ "NXDOMAIN",
14
+ "NOTIMP",
15
+ "REFUSED",
16
+ "YXDOMAIN",
17
+ "YXRRSET",
18
+ "NXRRSET",
19
+ "NOTAUTH",
20
+ "NOTZONE",
21
+ "RCODE11",
22
+ "RCODE12",
23
+ "RCODE13",
24
+ "RCODE14",
25
+ "RCODE15",
26
+ "BADVERS",
27
+}
28
+
29
+// https://github.com/NLnetLabs/nsd/blob/b4a5ccd2235a1f8f71f7c640390e409bf123c963/remote.c#L2706
30
+var queryOpcodes = []string{
31
+ "QUERY",
32
+ "IQUERY",
33
+ "STATUS",
34
+ "NOTIFY",
35
+ "UPDATE",
36
+ "OTHER",
37
+}
38
+
39
+// https://github.com/NLnetLabs/nsd/blob/b4a5ccd2235a1f8f71f7c640390e409bf123c963/dns.c#L27
40
+var queryClasses = []string{
41
+ "IN",
42
+ "CS",
43
+ "CH",
44
+ "HS",
45
+}
46
+
47
+// https://github.com/NLnetLabs/nsd/blob/b4a5ccd2235a1f8f71f7c640390e409bf123c963/dns.c#L35
48
+var queryTypes = []string{
49
+ "A",
50
+ "NS",
51
+ "MD",
52
+ "MF",
53
+ "CNAME",
54
+ "SOA",
55
+ "MB",
56
+ "MG",
57
+ "MR",
58
+ "NULL",
59
+ "WKS",
60
+ "PTR",
61
+ "HINFO",
62
+ "MINFO",
63
+ "MX",
64
+ "TXT",
65
+ "RP",
66
+ "AFSDB",
67
+ "X25",
68
+ "ISDN",
69
+ "RT",
70
+ "NSAP",
71
+ "SIG",
72
+ "KEY",
73
+ "PX",
74
+ "AAAA",
75
+ "LOC",
76
+ "NXT",
77
+ "SRV",
78
+ "NAPTR",
79
+ "KX",
80
+ "CERT",
81
+ "DNAME",
82
+ "OPT",
83
+ "APL",
84
+ "DS",
85
+ "SSHFP",
86
+ "IPSECKEY",
87
+ "RRSIG",
88
+ "NSEC",
89
+ "DNSKEY",
90
+ "DHCID",
91
+ "NSEC3",
92
+ "NSEC3PARAM",
93
+ "TLSA",
94
+ "SMIMEA",
95
+ "CDS",
96
+ "CDNSKEY",
97
+ "OPENPGPKEY",
98
+ "CSYNC",
99
+ "ZONEMD",
100
+ "SVCB",
101
+ "HTTPS",
102
+ "SPF",
103
+ "NID",
104
+ "L32",
105
+ "L64",
106
+ "LP",
107
+ "EUI48",
108
+ "EUI64",
109
+ "URI",
110
+ "CAA",
111
+ "AVC",
112
+ "DLV",
113
+ "TYPE252",
114
+ "TYPE255",
115
+}
116
+
117
+var queryTypeNumberMap = map[string]string{
118
+ "TYPE251": "IXFR",
119
+ "TYPE252": "AXFR",
120
+ "TYPE253": "MAILB",
121
+ "TYPE254": "MAILA",
122
+ "TYPE255": "ANY",
123
+}
src/go/plugin/go.d/modules/nsd/testdata/config.json
new
+4
@@ -0,0 +1,4 @@
1
+{
2
+ "update_every": 123,
3
+ "timeout": 123.123
4
+}
src/go/plugin/go.d/modules/nsd/testdata/config.yaml
new
+2
@@ -0,0 +1,2 @@
1
+update_every: 123
2
+timeout: 123.123
src/go/plugin/go.d/modules/nsd/testdata/stats.txt
new
+95
@@ -0,0 +1,95 @@
1
+server0.queries=1
2
+num.queries=1
3
+time.boot=556.488415
4
+time.elapsed=556.488415
5
+size.db.disk=576
6
+size.db.mem=920
7
+size.xfrd.mem=1160464
8
+size.config.disk=1
9
+size.config.mem=1064
10
+num.type.A=1
11
+num.type.NS=1
12
+num.type.MD=1
13
+num.type.MF=1
14
+num.type.CNAME=1
15
+num.type.SOA=1
16
+num.type.MB=1
17
+num.type.MG=1
18
+num.type.MR=1
19
+num.type.NULL=1
20
+num.type.WKS=1
21
+num.type.PTR=1
22
+num.type.HINFO=1
23
+num.type.MINFO=1
24
+num.type.MX=1
25
+num.type.TXT=1
26
+num.type.RP=1
27
+num.type.AFSDB=1
28
+num.type.X25=1
29
+num.type.ISDN=1
30
+num.type.RT=1
31
+num.type.NSAP=1
32
+num.type.SIG=1
33
+num.type.KEY=1
34
+num.type.PX=1
35
+num.type.AAAA=1
36
+num.type.LOC=1
37
+num.type.NXT=1
38
+num.type.SRV=1
39
+num.type.NAPTR=1
40
+num.type.KX=1
41
+num.type.CERT=1
42
+num.type.DNAME=1
43
+num.type.OPT=1
44
+num.type.APL=1
45
+num.type.DS=1
46
+num.type.SSHFP=1
47
+num.type.IPSECKEY=1
48
+num.type.RRSIG=1
49
+num.type.NSEC=1
50
+num.type.DNSKEY=1
51
+num.type.DHCID=1
52
+num.type.NSEC3=1
53
+num.type.NSEC3PARAM=1
54
+num.type.TLSA=1
55
+num.type.SMIMEA=1
56
+num.type.CDS=1
57
+num.type.CDNSKEY=1
58
+num.type.OPENPGPKEY=1
59
+num.type.CSYNC=1
60
+num.type.ZONEMD=1
61
+num.type.SVCB=1
62
+num.type.HTTPS=1
63
+num.type.SPF=1
64
+num.type.NID=1
65
+num.type.L32=1
66
+num.type.L64=1
67
+num.type.LP=1
68
+num.type.EUI48=1
69
+num.type.EUI64=1
70
+num.opcode.QUERY=1
71
+num.class.IN=1
72
+num.rcode.NOERROR=1
73
+num.rcode.FORMERR=1
74
+num.rcode.SERVFAIL=1
75
+num.rcode.NXDOMAIN=1
76
+num.rcode.NOTIMP=1
77
+num.rcode.REFUSED=1
78
+num.rcode.YXDOMAIN=1
79
+num.edns=1
80
+num.ednserr=1
81
+num.udp=1
82
+num.udp6=1
83
+num.tcp=1
84
+num.tcp6=1
85
+num.tls=1
86
+num.tls6=1
87
+num.answer_wo_aa=1
88
+num.rxerr=1
89
+num.txerr=1
90
+num.raxfr=1
91
+num.rixfr=1
92
+num.truncated=1
93
+num.dropped=1
94
+zone.master=1
95
+zone.slave=1