diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/dnsmasq')
l--------- | src/go/plugin/go.d/modules/dnsmasq/README.md | 1 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/charts.go | 51 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/collect.go | 139 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/config_schema.json | 61 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/dnsmasq.go | 123 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/dnsmasq_test.go | 278 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/init.go | 43 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/integrations/dnsmasq.md | 230 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/metadata.yaml | 144 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/testdata/config.json | 6 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsmasq/testdata/config.yaml | 4 |
11 files changed, 1080 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/dnsmasq/README.md b/src/go/plugin/go.d/modules/dnsmasq/README.md new file mode 120000 index 000000000..a424dd9c6 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/README.md @@ -0,0 +1 @@ +integrations/dnsmasq.md
\ No newline at end of file diff --git a/src/go/plugin/go.d/modules/dnsmasq/charts.go b/src/go/plugin/go.d/modules/dnsmasq/charts.go new file mode 100644 index 000000000..403e7862c --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/charts.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsmasq + +import "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + +var cacheCharts = module.Charts{ + { + ID: "servers_queries", + Title: "Queries forwarded to the upstream servers", + Units: "queries/s", + Fam: "servers", + Ctx: "dnsmasq.servers_queries", + Dims: module.Dims{ + {ID: "queries", Name: "success", Algo: module.Incremental}, + {ID: "failed_queries", Name: "failed", Algo: module.Incremental}, + }, + }, + { + ID: "cache_performance", + Title: "Cache performance", + Units: "events/s", + Fam: "cache", + Ctx: "dnsmasq.cache_performance", + Dims: module.Dims{ + {ID: "hits", Algo: module.Incremental}, + {ID: "misses", Algo: module.Incremental}, + }, + }, + { + ID: "cache_operations", + Title: "Cache operations", + Units: "operations/s", + Fam: "cache", + Ctx: "dnsmasq.cache_operations", + Dims: module.Dims{ + {ID: "insertions", Algo: module.Incremental}, + {ID: "evictions", Algo: module.Incremental}, + }, + }, + { + ID: "cache_size", + Title: "Cache size", + Units: "entries", + Fam: "cache", + Ctx: "dnsmasq.cache_size", + Dims: module.Dims{ + {ID: "cachesize", Name: "size"}, + }, + }, +} diff --git a/src/go/plugin/go.d/modules/dnsmasq/collect.go b/src/go/plugin/go.d/modules/dnsmasq/collect.go new file mode 100644 index 000000000..9f3f963f0 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/collect.go @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsmasq + +import ( + "fmt" + "strconv" + "strings" + + "github.com/miekg/dns" +) + +func (d *Dnsmasq) collect() (map[string]int64, error) { + mx := make(map[string]int64) + + if err := d.collectCacheStatistics(mx); err != nil { + return nil, err + } + + return mx, nil +} + +func (d *Dnsmasq) collectCacheStatistics(mx map[string]int64) error { + /* + ;; flags: qr aa rd ra; QUERY: 7, ANSWER: 7, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;cachesize.bind. CH TXT + ;insertions.bind. CH TXT + ;evictions.bind. CH TXT + ;hits.bind. CH TXT + ;misses.bind. CH TXT + ;auth.bind. CH TXT + ;servers.bind. CH TXT + + ;; ANSWER SECTION: + cachesize.bind. 0 CH TXT "150" + insertions.bind. 0 CH TXT "1" + evictions.bind. 0 CH TXT "0" + hits.bind. 0 CH TXT "176" + misses.bind. 0 CH TXT "4" + auth.bind. 0 CH TXT "0" + servers.bind. 0 CH TXT "10.0.0.1#53 0 0" "1.1.1.1#53 4 3" "1.0.0.1#53 3 0" + */ + + questions := []string{ + "servers.bind.", + "cachesize.bind.", + "insertions.bind.", + "evictions.bind.", + "hits.bind.", + "misses.bind.", + // auth.bind query is only supported if dnsmasq has been built to support running as an authoritative name server + // See https://github.com/netdata/netdata/issues/13766 + //"auth.bind.", + } + + for _, q := range questions { + resp, err := d.query(q) + if err != nil { + return err + } + + for _, a := range resp.Answer { + txt, ok := a.(*dns.TXT) + if !ok { + continue + } + + idx := strings.IndexByte(txt.Hdr.Name, '.') + if idx == -1 { + continue + } + + name := txt.Hdr.Name[:idx] + + switch name { + case "servers": + for _, entry := range txt.Txt { + parts := strings.Fields(entry) + if len(parts) != 3 { + return fmt.Errorf("parse %s (%s): unexpected format", txt.Hdr.Name, entry) + } + queries, err := strconv.ParseFloat(parts[1], 64) + if err != nil { + return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err) + } + failedQueries, err := strconv.ParseFloat(parts[2], 64) + if err != nil { + return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err) + } + + mx["queries"] += int64(queries) + mx["failed_queries"] += int64(failedQueries) + } + case "cachesize", "insertions", "evictions", "hits", "misses", "auth": + if len(txt.Txt) != 1 { + return fmt.Errorf("parse '%s' (%v): unexpected format", txt.Hdr.Name, txt.Txt) + } + v, err := strconv.ParseFloat(txt.Txt[0], 64) + if err != nil { + return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, txt.Txt[0], err) + } + + mx[name] = int64(v) + } + } + } + + return nil +} + +func (d *Dnsmasq) query(question string) (*dns.Msg, error) { + msg := &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + RecursionDesired: true, + }, + Question: []dns.Question{ + {Name: question, Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS}, + }, + } + + r, _, err := d.dnsClient.Exchange(msg, d.Address) + if err != nil { + return nil, err + } + + if r == nil { + return nil, fmt.Errorf("'%s' question '%s', returned an empty response", d.Address, question) + } + + if r.Rcode != dns.RcodeSuccess { + s := dns.RcodeToString[r.Rcode] + return nil, fmt.Errorf("'%s' question '%s' returned '%s' (%d) response code", d.Address, question, s, r.Rcode) + } + + return r, nil +} diff --git a/src/go/plugin/go.d/modules/dnsmasq/config_schema.json b/src/go/plugin/go.d/modules/dnsmasq/config_schema.json new file mode 100644 index 000000000..79396b364 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/config_schema.json @@ -0,0 +1,61 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Dnsmasq collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "address": { + "title": "Address", + "description": "The IP address and port where the Dnsmasq daemon listens for connections.", + "type": "string", + "default": "127.0.0.1:53" + }, + "protocol": { + "title": "Protocol", + "description": "DNS query transport protocol.", + "type": "string", + "enum": [ + "udp", + "tcp", + "tcp-tls" + ], + "default": "udp" + }, + "timeout": { + "title": "Timeout", + "description": "Timeout for establishing a connection and communication (reading and writing) in seconds.", + "type": "number", + "default": 1 + } + }, + "required": [ + "address", + "protocol" + ], + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "uiOptions": { + "fullPage": true + }, + "timeout": { + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." + }, + "protocol": { + "ui:widget": "radio", + "ui:options": { + "inline": true + } + } + } +} diff --git a/src/go/plugin/go.d/modules/dnsmasq/dnsmasq.go b/src/go/plugin/go.d/modules/dnsmasq/dnsmasq.go new file mode 100644 index 000000000..2d2112c05 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/dnsmasq.go @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsmasq + +import ( + _ "embed" + "errors" + "time" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" + + "github.com/miekg/dns" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("dnsmasq", module.Creator{ + JobConfigSchema: configSchema, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *Dnsmasq { + return &Dnsmasq{ + Config: Config{ + Protocol: "udp", + Address: "127.0.0.1:53", + Timeout: web.Duration(time.Second), + }, + + newDNSClient: func(network string, timeout time.Duration) dnsClient { + return &dns.Client{ + Net: network, + Timeout: timeout, + } + }, + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + Address string `yaml:"address" json:"address"` + Protocol string `yaml:"protocol,omitempty" json:"protocol"` + Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"` +} + +type ( + Dnsmasq struct { + module.Base + Config `yaml:",inline" json:""` + + charts *module.Charts + + dnsClient dnsClient + newDNSClient func(network string, timeout time.Duration) dnsClient + } + dnsClient interface { + Exchange(msg *dns.Msg, address string) (resp *dns.Msg, rtt time.Duration, err error) + } +) + +func (d *Dnsmasq) Configuration() any { + return d.Config +} + +func (d *Dnsmasq) Init() error { + err := d.validateConfig() + if err != nil { + d.Errorf("config validation: %v", err) + return err + } + + client, err := d.initDNSClient() + if err != nil { + d.Errorf("init DNS client: %v", err) + return err + } + d.dnsClient = client + + charts, err := d.initCharts() + if err != nil { + d.Errorf("init charts: %v", err) + return err + } + d.charts = charts + + return nil +} + +func (d *Dnsmasq) Check() error { + mx, err := d.collect() + if err != nil { + d.Error(err) + return err + } + if len(mx) == 0 { + return errors.New("no metrics collected") + + } + return nil +} + +func (d *Dnsmasq) Charts() *module.Charts { + return d.charts +} + +func (d *Dnsmasq) Collect() map[string]int64 { + ms, err := d.collect() + if err != nil { + d.Error(err) + } + + if len(ms) == 0 { + return nil + } + return ms +} + +func (d *Dnsmasq) Cleanup() {} diff --git a/src/go/plugin/go.d/modules/dnsmasq/dnsmasq_test.go b/src/go/plugin/go.d/modules/dnsmasq/dnsmasq_test.go new file mode 100644 index 000000000..b3d54ac9c --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/dnsmasq_test.go @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsmasq + +import ( + "errors" + "fmt" + "os" + "testing" + "time" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + } { + require.NotNil(t, data, name) + } +} + +func TestDnsmasq_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &Dnsmasq{}, dataConfigJSON, dataConfigYAML) +} + +func TestDnsmasq_Init(t *testing.T) { + tests := map[string]struct { + config Config + wantFail bool + }{ + "success on default config": { + config: New().Config, + }, + "fails on unset 'address'": { + wantFail: true, + config: Config{ + Protocol: "udp", + Address: "", + }, + }, + "fails on unset 'protocol'": { + wantFail: true, + config: Config{ + Protocol: "", + Address: "127.0.0.1:53", + }, + }, + "fails on invalid 'protocol'": { + wantFail: true, + config: Config{ + Protocol: "http", + Address: "127.0.0.1:53", + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ns := New() + ns.Config = test.config + + if test.wantFail { + assert.Error(t, ns.Init()) + } else { + assert.NoError(t, ns.Init()) + } + }) + } +} + +func TestDnsmasq_Check(t *testing.T) { + tests := map[string]struct { + prepare func() *Dnsmasq + wantFail bool + }{ + "success on valid response": { + prepare: prepareOKDnsmasq, + }, + "fails on error on cache stats query": { + wantFail: true, + prepare: prepareErrorOnExchangeDnsmasq, + }, + "fails on response rcode is not success": { + wantFail: true, + prepare: prepareRcodeServerFailureOnExchangeDnsmasq, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + dnsmasq := test.prepare() + require.NoError(t, dnsmasq.Init()) + + if test.wantFail { + assert.Error(t, dnsmasq.Check()) + } else { + assert.NoError(t, dnsmasq.Check()) + } + }) + } +} + +func TestDnsmasq_Charts(t *testing.T) { + dnsmasq := New() + require.NoError(t, dnsmasq.Init()) + assert.NotNil(t, dnsmasq.Charts()) +} + +func TestDnsmasq_Cleanup(t *testing.T) { + assert.NotPanics(t, New().Cleanup) +} + +func TestDnsmasq_Collect(t *testing.T) { + tests := map[string]struct { + prepare func() *Dnsmasq + wantCollected map[string]int64 + }{ + "success on valid response": { + prepare: prepareOKDnsmasq, + wantCollected: map[string]int64{ + //"auth": 5, + "cachesize": 999, + "evictions": 5, + "failed_queries": 9, + "hits": 100, + "insertions": 10, + "misses": 50, + "queries": 17, + }, + }, + "fails on error on cache stats query": { + prepare: prepareErrorOnExchangeDnsmasq, + }, + "fails on response rcode is not success": { + prepare: prepareRcodeServerFailureOnExchangeDnsmasq, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + dnsmasq := test.prepare() + require.NoError(t, dnsmasq.Init()) + + collected := dnsmasq.Collect() + + assert.Equal(t, test.wantCollected, collected) + if len(test.wantCollected) > 0 { + ensureCollectedHasAllChartsDimsVarsIDs(t, dnsmasq, collected) + } + }) + } +} + +func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, dnsmasq *Dnsmasq, collected map[string]int64) { + for _, chart := range *dnsmasq.Charts() { + if chart.Obsolete { + continue + } + for _, dim := range chart.Dims { + _, ok := collected[dim.ID] + assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", dim.ID, chart.ID) + } + for _, v := range chart.Vars { + _, ok := collected[v.ID] + assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", v.ID, chart.ID) + } + } +} + +func prepareOKDnsmasq() *Dnsmasq { + dnsmasq := New() + dnsmasq.newDNSClient = func(network string, timeout time.Duration) dnsClient { + return &mockDNSClient{} + } + return dnsmasq +} + +func prepareErrorOnExchangeDnsmasq() *Dnsmasq { + dnsmasq := New() + dnsmasq.newDNSClient = func(network string, timeout time.Duration) dnsClient { + return &mockDNSClient{ + errOnExchange: true, + } + } + return dnsmasq +} + +func prepareRcodeServerFailureOnExchangeDnsmasq() *Dnsmasq { + dnsmasq := New() + dnsmasq.newDNSClient = func(network string, timeout time.Duration) dnsClient { + return &mockDNSClient{ + rcodeServerFailureOnExchange: true, + } + } + return dnsmasq +} + +type mockDNSClient struct { + errOnExchange bool + rcodeServerFailureOnExchange bool +} + +func (m mockDNSClient) Exchange(msg *dns.Msg, _ string) (*dns.Msg, time.Duration, error) { + if m.errOnExchange { + return nil, 0, errors.New("'Exchange' error") + } + if m.rcodeServerFailureOnExchange { + resp := &dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}} + return resp, 0, nil + } + + var answers []dns.RR + for _, q := range msg.Question { + a, err := prepareDNSAnswer(q) + if err != nil { + return nil, 0, err + } + answers = append(answers, a) + } + + resp := &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Rcode: dns.RcodeSuccess, + }, + Answer: answers, + } + return resp, 0, nil +} + +func prepareDNSAnswer(q dns.Question) (dns.RR, error) { + if want, got := dns.TypeToString[dns.TypeTXT], dns.TypeToString[q.Qtype]; want != got { + return nil, fmt.Errorf("unexpected Qtype, want=%s, got=%s", want, got) + } + if want, got := dns.ClassToString[dns.ClassCHAOS], dns.ClassToString[q.Qclass]; want != got { + return nil, fmt.Errorf("unexpected Qclass, want=%s, got=%s", want, got) + } + + var txt []string + switch q.Name { + case "cachesize.bind.": + txt = []string{"999"} + case "insertions.bind.": + txt = []string{"10"} + case "evictions.bind.": + txt = []string{"5"} + case "hits.bind.": + txt = []string{"100"} + case "misses.bind.": + txt = []string{"50"} + case "auth.bind.": + txt = []string{"5"} + case "servers.bind.": + txt = []string{"10.0.0.1#53 10 5", "1.1.1.1#53 4 3", "1.0.0.1#53 3 1"} + default: + return nil, fmt.Errorf("unexpected question Name: %s", q.Name) + } + + rr := &dns.TXT{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeTXT, + Class: dns.ClassCHAOS, + }, + Txt: txt, + } + return rr, nil +} diff --git a/src/go/plugin/go.d/modules/dnsmasq/init.go b/src/go/plugin/go.d/modules/dnsmasq/init.go new file mode 100644 index 000000000..a660ac774 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/init.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsmasq + +import ( + "errors" + "fmt" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" +) + +func (d *Dnsmasq) validateConfig() error { + if d.Address == "" { + return errors.New("'address' parameter not set") + } + if !isProtocolValid(d.Protocol) { + return fmt.Errorf("'protocol' (%s) is not valid, expected one of %v", d.Protocol, validProtocols) + } + return nil +} + +func (d *Dnsmasq) initDNSClient() (dnsClient, error) { + return d.newDNSClient(d.Protocol, d.Timeout.Duration()), nil +} + +func (d *Dnsmasq) initCharts() (*module.Charts, error) { + return cacheCharts.Copy(), nil +} + +func isProtocolValid(protocol string) bool { + for _, v := range validProtocols { + if protocol == v { + return true + } + } + return false +} + +var validProtocols = []string{ + "udp", + "tcp", + "tcp-tls", +} diff --git a/src/go/plugin/go.d/modules/dnsmasq/integrations/dnsmasq.md b/src/go/plugin/go.d/modules/dnsmasq/integrations/dnsmasq.md new file mode 100644 index 000000000..d5c358a29 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/integrations/dnsmasq.md @@ -0,0 +1,230 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/dnsmasq/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/dnsmasq/metadata.yaml" +sidebar_label: "Dnsmasq" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/DNS and DHCP Servers" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# Dnsmasq + + +<img src="https://netdata.cloud/img/dnsmasq.svg" width="150"/> + + +Plugin: go.d.plugin +Module: dnsmasq + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors Dnsmasq servers. + + + + +This collector is supported on all platforms. + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + + +### Default Behavior + +#### Auto-Detection + +This integration doesn't support auto-detection. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per Dnsmasq instance + +The metrics apply to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| dnsmasq.servers_queries | success, failed | queries/s | +| dnsmasq.cache_performance | hist, misses | events/s | +| dnsmasq.cache_operations | insertions, evictions | operations/s | +| dnsmasq.cache_size | size | entries | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +No action required. + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/dnsmasq.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config go.d/dnsmasq.conf +``` +#### Options + +The following options can be defined globally: update_every, autodetection_retry. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update_every | Data collection frequency. | 1 | no | +| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no | +| address | Server address in `ip:port` format. | 127.0.0.1:53 | yes | +| protocol | DNS query transport protocol. Supported protocols: udp, tcp, tcp-tls. | udp | no | +| timeout | DNS query timeout (dial, write and read) in seconds. | 1 | no | + +</details> + +#### Examples + +##### Basic + +An example configuration. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + address: 127.0.0.1:53 + +``` +</details> + +##### Using TCP protocol + +Local server with specific DNS query transport protocol. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + address: 127.0.0.1:53 + protocol: tcp + +``` +</details> + +##### Multi-instance + +> **Note**: When you define multiple jobs, their names must be unique. + +Collecting metrics from local and remote instances. + + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + address: 127.0.0.1:53 + + - name: remote + address: 203.0.113.0:53 + +``` +</details> + + + +## Troubleshooting + +### Debug Mode + +**Important**: Debug mode is not supported for data collection jobs created via the UI using the Dyncfg feature. + +To troubleshoot issues with the `dnsmasq` collector, run the `go.d.plugin` with the debug option enabled. The output +should give you clues as to why the collector isn't working. + +- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on + your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`. + + ```bash + cd /usr/libexec/netdata/plugins.d/ + ``` + +- Switch to the `netdata` user. + + ```bash + sudo -u netdata -s + ``` + +- Run the `go.d.plugin` to debug the collector: + + ```bash + ./go.d.plugin -d -m dnsmasq + ``` + +### Getting Logs + +If you're encountering problems with the `dnsmasq` collector, follow these steps to retrieve logs and identify potential issues: + +- **Run the command** specific to your system (systemd, non-systemd, or Docker container). +- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem. + +#### System with systemd + +Use the following command to view logs generated since the last Netdata service restart: + +```bash +journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep dnsmasq +``` + +#### System without systemd + +Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name: + +```bash +grep dnsmasq /var/log/netdata/collector.log +``` + +**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues. + +#### Docker Container + +If your Netdata runs in a Docker container named "netdata" (replace if different), use this command: + +```bash +docker logs netdata 2>&1 | grep dnsmasq +``` + + diff --git a/src/go/plugin/go.d/modules/dnsmasq/metadata.yaml b/src/go/plugin/go.d/modules/dnsmasq/metadata.yaml new file mode 100644 index 000000000..6911a323a --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/metadata.yaml @@ -0,0 +1,144 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-dnsmasq + plugin_name: go.d.plugin + module_name: dnsmasq + monitored_instance: + name: Dnsmasq + link: https://thekelleys.org.uk/dnsmasq/doc.html + icon_filename: dnsmasq.svg + categories: + - data-collection.dns-and-dhcp-servers + keywords: + - dnsmasq + - dns + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: | + This collector monitors Dnsmasq servers. + method_description: "" + supported_platforms: + include: [] + exclude: [] + multi_instance: true + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: [] + configuration: + file: + name: go.d/dnsmasq.conf + options: + description: | + The following options can be defined globally: update_every, autodetection_retry. + folding: + title: Config options + enabled: true + list: + - name: update_every + description: Data collection frequency. + default_value: 1 + required: false + - name: autodetection_retry + description: Recheck interval in seconds. Zero means no recheck will be scheduled. + default_value: 0 + required: false + - name: address + description: Server address in `ip:port` format. + default_value: 127.0.0.1:53 + required: true + - name: protocol + description: 'DNS query transport protocol. Supported protocols: udp, tcp, tcp-tls.' + default_value: udp + required: false + - name: timeout + description: DNS query timeout (dial, write and read) in seconds. + default_value: 1 + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: Basic + description: An example configuration. + config: | + jobs: + - name: local + address: 127.0.0.1:53 + - name: Using TCP protocol + description: Local server with specific DNS query transport protocol. + config: | + jobs: + - name: local + address: 127.0.0.1:53 + protocol: tcp + - name: Multi-instance + description: | + > **Note**: When you define multiple jobs, their names must be unique. + + Collecting metrics from local and remote instances. + config: | + jobs: + - name: local + address: 127.0.0.1:53 + + - name: remote + address: 203.0.113.0:53 + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: The metrics apply to the entire monitored application. + labels: [] + metrics: + - name: dnsmasq.servers_queries + description: Queries forwarded to the upstream servers + unit: queries/s + chart_type: line + dimensions: + - name: success + - name: failed + - name: dnsmasq.cache_performance + description: Cache performance + unit: events/s + chart_type: line + dimensions: + - name: hist + - name: misses + - name: dnsmasq.cache_operations + description: Cache operations + unit: operations/s + chart_type: line + dimensions: + - name: insertions + - name: evictions + - name: dnsmasq.cache_size + description: Cache size + unit: entries + chart_type: line + dimensions: + - name: size diff --git a/src/go/plugin/go.d/modules/dnsmasq/testdata/config.json b/src/go/plugin/go.d/modules/dnsmasq/testdata/config.json new file mode 100644 index 000000000..4fff563b8 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/testdata/config.json @@ -0,0 +1,6 @@ +{ + "update_every": 123, + "protocol": "ok", + "address": "ok", + "timeout": 123.123 +} diff --git a/src/go/plugin/go.d/modules/dnsmasq/testdata/config.yaml b/src/go/plugin/go.d/modules/dnsmasq/testdata/config.yaml new file mode 100644 index 000000000..1a79b8773 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsmasq/testdata/config.yaml @@ -0,0 +1,4 @@ +update_every: 123 +protocol: "ok" +address: "ok" +timeout: 123.123 |