diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/dnsdist')
l--------- | src/go/plugin/go.d/modules/dnsdist/README.md | 1 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/charts.go | 151 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/collect.go | 76 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/config_schema.json | 183 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/dnsdist.go | 121 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/dnsdist_test.go | 265 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/init.go | 31 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/integrations/dnsdist.md | 245 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/metadata.yaml | 259 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/metrics.go | 41 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/testdata/config.json | 20 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/testdata/config.yaml | 17 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/dnsdist/testdata/v1.5.1/jsonstat.json | 56 |
13 files changed, 1466 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/dnsdist/README.md b/src/go/plugin/go.d/modules/dnsdist/README.md new file mode 120000 index 00000000..c5fd71aa --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/README.md @@ -0,0 +1 @@ +integrations/dnsdist.md
\ No newline at end of file diff --git a/src/go/plugin/go.d/modules/dnsdist/charts.go b/src/go/plugin/go.d/modules/dnsdist/charts.go new file mode 100644 index 00000000..24e1a8c8 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/charts.go @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsdist + +import "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + +var charts = module.Charts{ + { + ID: "queries", + Title: "Client queries received", + Units: "queries/s", + Fam: "queries", + Ctx: "dnsdist.queries", + Dims: module.Dims{ + {ID: "queries", Name: "all", Algo: module.Incremental}, + {ID: "rdqueries", Name: "recursive", Algo: module.Incremental}, + {ID: "empty-queries", Name: "empty", Algo: module.Incremental}, + }, + }, + { + ID: "queries_dropped", + Title: "Client queries dropped", + Units: "queries/s", + Fam: "queries", + Ctx: "dnsdist.queries_dropped", + Dims: module.Dims{ + {ID: "rule-drop", Name: "rule drop", Algo: module.Incremental}, + {ID: "dyn-blocked", Name: "dynamic blocked", Algo: module.Incremental}, + {ID: "no-policy", Name: "no policy", Algo: module.Incremental}, + {ID: "noncompliant-queries", Name: "non queries", Algo: module.Incremental}, + }, + }, + { + ID: "packets_dropped", + Title: "Packets dropped", + Units: "packets/s", + Fam: "packets", + Ctx: "dnsdist.packets_dropped", + Dims: module.Dims{ + {ID: "acl-drops", Name: "acl", Algo: module.Incremental}, + }, + }, + { + ID: "answers", + Title: "Answers statistics", + Units: "answers/s", + Fam: "answers", + Ctx: "dnsdist.answers", + Dims: module.Dims{ + {ID: "self-answered", Name: "self answered", Algo: module.Incremental}, + {ID: "rule-nxdomain", Name: "nxdomain", Algo: module.Incremental, Mul: -1}, + {ID: "rule-refused", Name: "refused", Algo: module.Incremental, Mul: -1}, + {ID: "trunc-failures", Name: "trunc failures", Algo: module.Incremental, Mul: -1}, + }, + }, + { + ID: "backend_responses", + Title: "Backend responses", + Units: "responses/s", + Fam: "backends", + Ctx: "dnsdist.backend_responses", + Dims: module.Dims{ + {ID: "responses", Name: "responses", Algo: module.Incremental}, + }, + }, + { + ID: "backend_commerrors", + Title: "Backend communication errors", + Units: "errors/s", + Fam: "backends", + Ctx: "dnsdist.backend_commerrors", + Dims: module.Dims{ + {ID: "downstream-send-errors", Name: "send errors", Algo: module.Incremental}, + }, + }, + { + ID: "backend_errors", + Title: "Backend error responses", + Units: "responses/s", + Fam: "backends", + Ctx: "dnsdist.backend_errors", + Dims: module.Dims{ + {ID: "downstream-timeouts", Name: "timeouts", Algo: module.Incremental}, + {ID: "servfail-responses", Name: "servfail", Algo: module.Incremental}, + {ID: "noncompliant-responses", Name: "non compliant", Algo: module.Incremental}, + }, + }, + { + ID: "cache", + Title: "Cache performance", + Units: "answers/s", + Fam: "cache", + Ctx: "dnsdist.cache", + Dims: module.Dims{ + {ID: "cache-hits", Name: "hits", Algo: module.Incremental}, + {ID: "cache-misses", Name: "misses", Algo: module.Incremental, Mul: -1}, + }, + }, + { + ID: "servercpu", + Title: "DNSdist server CPU utilization", + Units: "ms/s", + Fam: "server", + Ctx: "dnsdist.servercpu", + Type: module.Stacked, + Dims: module.Dims{ + {ID: "cpu-sys-msec", Name: "system state", Algo: module.Incremental}, + {ID: "cpu-user-msec", Name: "user state", Algo: module.Incremental}, + }, + }, + { + ID: "servermem", + Title: "DNSdist server memory utilization", + Units: "MiB", + Fam: "server", + Ctx: "dnsdist.servermem", + Type: module.Area, + Dims: module.Dims{ + {ID: "real-memory-usage", Name: "memory usage", Div: 1 << 20}, + }, + }, + { + ID: "query_latency", + Title: "Query latency", + Units: "queries/s", + Fam: "latency", + Ctx: "dnsdist.query_latency", + Type: module.Stacked, + Dims: module.Dims{ + {ID: "latency0-1", Name: "1ms", Algo: module.Incremental}, + {ID: "latency1-10", Name: "10ms", Algo: module.Incremental}, + {ID: "latency10-50", Name: "50ms", Algo: module.Incremental}, + {ID: "latency50-100", Name: "100ms", Algo: module.Incremental}, + {ID: "latency100-1000", Name: "1sec", Algo: module.Incremental}, + {ID: "latency-slow", Name: "slow", Algo: module.Incremental}, + }, + }, + { + ID: "query_latency_avg", + Title: "Average latency for the last N queries", + Units: "microseconds", + Fam: "latency", + Ctx: "dnsdist.query_latency_avg", + Dims: module.Dims{ + {ID: "latency-avg100", Name: "100"}, + {ID: "latency-avg1000", Name: "1k"}, + {ID: "latency-avg10000", Name: "10k"}, + {ID: "latency-avg1000000", Name: "1000k"}, + }, + }, +} diff --git a/src/go/plugin/go.d/modules/dnsdist/collect.go b/src/go/plugin/go.d/modules/dnsdist/collect.go new file mode 100644 index 00000000..9b860abf --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/collect.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsdist + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +const ( + urlPathJSONStat = "/jsonstat" +) + +func (d *DNSdist) collect() (map[string]int64, error) { + statistics, err := d.scrapeStatistics() + if err != nil { + return nil, err + } + + collected := make(map[string]int64) + d.collectStatistic(collected, statistics) + + return collected, nil +} + +func (d *DNSdist) collectStatistic(collected map[string]int64, statistics *statisticMetrics) { + for metric, value := range stm.ToMap(statistics) { + collected[metric] = value + } +} + +func (d *DNSdist) scrapeStatistics() (*statisticMetrics, error) { + req, err := web.NewHTTPRequestWithPath(d.Request, urlPathJSONStat) + if err != nil { + return nil, err + } + req.URL.RawQuery = url.Values{"command": []string{"stats"}}.Encode() + + var statistics statisticMetrics + if err := d.doOKDecode(req, &statistics); err != nil { + return nil, err + } + + return &statistics, nil +} + +func (d *DNSdist) doOKDecode(req *http.Request, in interface{}) error { + resp, err := d.httpClient.Do(req) + if err != nil { + return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err) + } + defer closeBody(resp) + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode) + } + + if err := json.NewDecoder(resp.Body).Decode(in); err != nil { + return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err) + } + + return nil +} + +func closeBody(resp *http.Response) { + if resp != nil && resp.Body != nil { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + } +} diff --git a/src/go/plugin/go.d/modules/dnsdist/config_schema.json b/src/go/plugin/go.d/modules/dnsdist/config_schema.json new file mode 100644 index 00000000..a71faaa0 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/config_schema.json @@ -0,0 +1,183 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DNSDist collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "url": { + "title": "URL", + "description": "The URL of the DNSDist [built-in webserver](https://dnsdist.org/guides/webserver.html).", + "type": "string", + "default": "http://127.0.0.1:8083", + "format": "uri" + }, + "timeout": { + "title": "Timeout", + "description": "The timeout in seconds for the HTTP request.", + "type": "number", + "minimum": 0.5, + "default": 1 + }, + "not_follow_redirects": { + "title": "Not follow redirects", + "description": "If set, the client will not follow HTTP redirects automatically.", + "type": "boolean" + }, + "username": { + "title": "Username", + "description": "The username for basic authentication.", + "type": "string", + "sensitive": true + }, + "password": { + "title": "Password", + "description": "The password for basic authentication.", + "type": "string", + "sensitive": true + }, + "proxy_url": { + "title": "Proxy URL", + "description": "The URL of the proxy server.", + "type": "string" + }, + "proxy_username": { + "title": "Proxy username", + "description": "The username for proxy authentication.", + "type": "string", + "sensitive": true + }, + "proxy_password": { + "title": "Proxy password", + "description": "The password for proxy authentication.", + "type": "string", + "sensitive": true + }, + "headers": { + "title": "Headers", + "description": "Additional HTTP headers to include in the request.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "tls_skip_verify": { + "title": "Skip TLS verification", + "description": "If set, TLS certificate verification will be skipped.", + "type": "boolean" + }, + "tls_ca": { + "title": "TLS CA", + "description": "The path to the CA certificate file for TLS verification.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_cert": { + "title": "TLS certificate", + "description": "The path to the client certificate file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_key": { + "title": "TLS key", + "description": "The path to the client key file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "body": { + "title": "Body", + "type": "string" + }, + "method": { + "title": "Method", + "type": "string" + } + }, + "required": [ + "url" + ], + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "ui:flavour": "tabs", + "ui:options": { + "tabs": [ + { + "title": "Base", + "fields": [ + "update_every", + "url", + "timeout", + "not_follow_redirects" + ] + }, + { + "title": "Auth", + "fields": [ + "username", + "password" + ] + }, + { + "title": "TLS", + "fields": [ + "tls_skip_verify", + "tls_ca", + "tls_cert", + "tls_key" + ] + }, + { + "title": "Proxy", + "fields": [ + "proxy_url", + "proxy_username", + "proxy_password" + ] + }, + { + "title": "Headers", + "fields": [ + "headers" + ] + } + ] + }, + "uiOptions": { + "fullPage": true + }, + "body": { + "ui:widget": "hidden" + }, + "method": { + "ui:widget": "hidden" + }, + "timeout": { + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." + }, + "username": { + "ui:widget": "password" + }, + "proxy_username": { + "ui:widget": "password" + }, + "password": { + "ui:widget": "password" + }, + "proxy_password": { + "ui:widget": "password" + } + } +} diff --git a/src/go/plugin/go.d/modules/dnsdist/dnsdist.go b/src/go/plugin/go.d/modules/dnsdist/dnsdist.go new file mode 100644 index 00000000..fd0d8a38 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/dnsdist.go @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsdist + +import ( + _ "embed" + "errors" + "net/http" + "time" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("dnsdist", module.Creator{ + JobConfigSchema: configSchema, + Defaults: module.Defaults{ + UpdateEvery: 1, + }, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *DNSdist { + return &DNSdist{ + Config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "http://127.0.0.1:8083", + }, + Client: web.Client{ + Timeout: web.Duration(time.Second), + }, + }, + }, + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + web.HTTP `yaml:",inline" json:""` +} + +type DNSdist struct { + module.Base + Config `yaml:",inline" json:""` + + charts *module.Charts + + httpClient *http.Client +} + +func (d *DNSdist) Configuration() any { + return d.Config +} + +func (d *DNSdist) Init() error { + err := d.validateConfig() + if err != nil { + d.Errorf("config validation: %v", err) + return err + } + + client, err := d.initHTTPClient() + if err != nil { + d.Errorf("init HTTP client: %v", err) + return err + } + d.httpClient = client + + cs, err := d.initCharts() + if err != nil { + d.Errorf("init charts: %v", err) + return err + } + d.charts = cs + + return nil +} + +func (d *DNSdist) 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 *DNSdist) Charts() *module.Charts { + return d.charts +} + +func (d *DNSdist) Collect() map[string]int64 { + ms, err := d.collect() + if err != nil { + d.Error(err) + } + + if len(ms) == 0 { + return nil + } + + return ms +} + +func (d *DNSdist) Cleanup() { + if d.httpClient == nil { + return + } + d.httpClient.CloseIdleConnections() +} diff --git a/src/go/plugin/go.d/modules/dnsdist/dnsdist_test.go b/src/go/plugin/go.d/modules/dnsdist/dnsdist_test.go new file mode 100644 index 00000000..18212c79 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/dnsdist_test.go @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsdist + +import ( + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/tlscfg" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") + + dataVer151JSONStat, _ = os.ReadFile("testdata/v1.5.1/jsonstat.json") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + "dataVer151JSONStat": dataVer151JSONStat, + } { + require.NotNil(t, data, name) + } +} + +func TestDNSdist_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &DNSdist{}, dataConfigJSON, dataConfigYAML) +} + +func TestDNSdist_Init(t *testing.T) { + tests := map[string]struct { + config Config + wantFail bool + }{ + "success on default config": { + config: New().Config, + }, + "fails on unset URL": { + wantFail: true, + config: Config{ + HTTP: web.HTTP{ + Request: web.Request{URL: ""}, + }, + }, + }, + "fails on invalid TLSCA": { + wantFail: true, + config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "http://127.0.0.1:38001", + }, + Client: web.Client{ + TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"}, + }, + }, + }, + }, + } + + 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 TestDNSdist_Charts(t *testing.T) { + dist := New() + require.NoError(t, dist.Init()) + assert.NotNil(t, dist.Charts()) +} + +func TestDNSdist_Cleanup(t *testing.T) { + assert.NotPanics(t, New().Cleanup) +} + +func TestDNSdist_Check(t *testing.T) { + tests := map[string]struct { + prepare func() (dist *DNSdist, cleanup func()) + wantFail bool + }{ + "success on valid response v1.5.1": { + prepare: preparePowerDNSdistV151, + wantFail: false, + }, + "fails on 404 response": { + prepare: preparePowerDNSdist404, + wantFail: true, + }, + "fails on connection refused": { + prepare: preparePowerDNSdistConnectionRefused, + wantFail: true, + }, + "fails with invalid data": { + prepare: preparePowerDNSdistInvalidData, + wantFail: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + dist, cleanup := test.prepare() + defer cleanup() + require.NoError(t, dist.Init()) + + if test.wantFail { + assert.Error(t, dist.Check()) + } else { + assert.NoError(t, dist.Check()) + } + }) + } +} + +func TestDNSdist_Collect(t *testing.T) { + tests := map[string]struct { + prepare func() (dist *DNSdist, cleanup func()) + wantCollected map[string]int64 + }{ + "success on valid response v1.5.1": { + prepare: preparePowerDNSdistV151, + wantCollected: map[string]int64{ + "acl-drops": 1, + "cache-hits": 1, + "cache-misses": 1, + "cpu-sys-msec": 411, + "cpu-user-msec": 939, + "downstream-send-errors": 1, + "downstream-timeouts": 1, + "dyn-blocked": 1, + "empty-queries": 1, + "latency-avg100": 14237, + "latency-avg1000": 9728, + "latency-avg10000": 1514, + "latency-avg1000000": 15, + "latency-slow": 1, + "latency0-1": 1, + "latency1-10": 3, + "latency10-50": 996, + "latency100-1000": 4, + "latency50-100": 1, + "no-policy": 1, + "noncompliant-queries": 1, + "noncompliant-responses": 1, + "queries": 1003, + "rdqueries": 1003, + "real-memory-usage": 202125312, + "responses": 1003, + "rule-drop": 1, + "rule-nxdomain": 1, + "rule-refused": 1, + "self-answered": 1, + "servfail-responses": 1, + "trunc-failures": 1, + }, + }, + "fails on 404 response": { + prepare: preparePowerDNSdist404, + }, + "fails on connection refused": { + prepare: preparePowerDNSdistConnectionRefused, + }, + "fails with invalid data": { + prepare: preparePowerDNSdistInvalidData, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + dist, cleanup := test.prepare() + defer cleanup() + require.NoError(t, dist.Init()) + + collected := dist.Collect() + + assert.Equal(t, test.wantCollected, collected) + if len(test.wantCollected) > 0 { + ensureCollectedHasAllChartsDimsVarsIDs(t, dist, collected) + } + }) + } +} + +func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, dist *DNSdist, collected map[string]int64) { + for _, chart := range *dist.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 preparePowerDNSdistV151() (*DNSdist, func()) { + srv := preparePowerDNSDistEndpoint() + ns := New() + ns.URL = srv.URL + + return ns, srv.Close +} + +func preparePowerDNSdist404() (*DNSdist, func()) { + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + ns := New() + ns.URL = srv.URL + + return ns, srv.Close +} + +func preparePowerDNSdistConnectionRefused() (*DNSdist, func()) { + ns := New() + ns.URL = "http://127.0.0.1:38001" + + return ns, func() {} +} + +func preparePowerDNSdistInvalidData() (*DNSdist, func()) { + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("hello and\n goodbye")) + })) + ns := New() + ns.URL = srv.URL + + return ns, srv.Close +} + +func preparePowerDNSDistEndpoint() *httptest.Server { + return httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + switch r.URL.String() { + case "/jsonstat?command=stats": + _, _ = w.Write(dataVer151JSONStat) + default: + w.WriteHeader(http.StatusNotFound) + } + })) +} diff --git a/src/go/plugin/go.d/modules/dnsdist/init.go b/src/go/plugin/go.d/modules/dnsdist/init.go new file mode 100644 index 00000000..d331da92 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/init.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsdist + +import ( + "errors" + "net/http" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +func (d *DNSdist) validateConfig() error { + if d.URL == "" { + return errors.New("URL not set") + } + + if _, err := web.NewHTTPRequest(d.Request); err != nil { + return err + } + + return nil +} + +func (d *DNSdist) initHTTPClient() (*http.Client, error) { + return web.NewHTTPClient(d.Client) +} + +func (d *DNSdist) initCharts() (*module.Charts, error) { + return charts.Copy(), nil +} diff --git a/src/go/plugin/go.d/modules/dnsdist/integrations/dnsdist.md b/src/go/plugin/go.d/modules/dnsdist/integrations/dnsdist.md new file mode 100644 index 00000000..934245a5 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/integrations/dnsdist.md @@ -0,0 +1,245 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/dnsdist/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/dnsdist/metadata.yaml" +sidebar_label: "DNSdist" +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--> + +# DNSdist + + +<img src="https://netdata.cloud/img/network-wired.svg" width="150"/> + + +Plugin: go.d.plugin +Module: dnsdist + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors DNSDist 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 DNSdist instance + +These metrics refer to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| dnsdist.queries | all, recursive, empty | queries/s | +| dnsdist.queries_dropped | rule_drop, dynamic_blocked, no_policy, non_queries | queries/s | +| dnsdist.packets_dropped | acl | packets/s | +| dnsdist.answers | self_answered, nxdomain, refused, trunc_failures | answers/s | +| dnsdist.backend_responses | responses | responses/s | +| dnsdist.backend_commerrors | send_errors | errors/s | +| dnsdist.backend_errors | timeouts, servfail, non_compliant | responses/s | +| dnsdist.cache | hits, misses | answers/s | +| dnsdist.servercpu | system_state, user_state | ms/s | +| dnsdist.servermem | memory_usage | MiB | +| dnsdist.query_latency | 1ms, 10ms, 50ms, 100ms, 1sec, slow | queries/s | +| dnsdist.query_latency_avg | 100, 1k, 10k, 1000k | microseconds | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Enable DNSdist built-in Webserver + +For collecting metrics via HTTP, you need to [enable the built-in webserver](https://dnsdist.org/guides/webserver.html). + + + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/dnsdist.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/dnsdist.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 | +| url | Server URL. | http://127.0.0.1:8083 | yes | +| username | Username for basic HTTP authentication. | | no | +| password | Password for basic HTTP authentication. | | no | +| proxy_url | Proxy URL. | | no | +| proxy_username | Username for proxy basic HTTP authentication. | | no | +| proxy_password | Password for proxy basic HTTP authentication. | | no | +| timeout | HTTP request timeout. | 1 | no | +| method | HTTP request method. | GET | no | +| body | HTTP request body. | | no | +| headers | HTTP request headers. | | no | +| not_follow_redirects | Redirect handling policy. Controls whether the client follows redirects. | no | no | +| tls_skip_verify | Server certificate chain and hostname validation policy. Controls whether the client performs this check. | no | no | +| tls_ca | Certification authority that the client uses when verifying the server's certificates. | | no | +| tls_cert | Client tls certificate. | | no | +| tls_key | Client tls key. | | no | + +</details> + +#### Examples + +##### Basic + +An example configuration. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1:8083 + headers: + X-API-Key: your-api-key # static pre-shared authentication key for access to the REST API (api-key). + +``` +</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 + url: http://127.0.0.1:8083 + headers: + X-API-Key: 'your-api-key' # static pre-shared authentication key for access to the REST API (api-key). + + - name: remote + url: http://203.0.113.0:8083 + headers: + X-API-Key: 'your-api-key' + +``` +</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 `dnsdist` 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 dnsdist + ``` + +### Getting Logs + +If you're encountering problems with the `dnsdist` 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 dnsdist +``` + +#### 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 dnsdist /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 dnsdist +``` + + diff --git a/src/go/plugin/go.d/modules/dnsdist/metadata.yaml b/src/go/plugin/go.d/modules/dnsdist/metadata.yaml new file mode 100644 index 00000000..4e7a45d3 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/metadata.yaml @@ -0,0 +1,259 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-dnsdist + plugin_name: go.d.plugin + module_name: dnsdist + monitored_instance: + name: DNSdist + link: https://dnsdist.org/ + icon_filename: network-wired.svg + categories: + - data-collection.dns-and-dhcp-servers + keywords: + - dnsdist + - dns + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: | + This collector monitors DNSDist 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: + - title: Enable DNSdist built-in Webserver + description: | + For collecting metrics via HTTP, you need to [enable the built-in webserver](https://dnsdist.org/guides/webserver.html). + configuration: + file: + name: go.d/dnsdist.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: url + description: Server URL. + default_value: http://127.0.0.1:8083 + required: true + - name: username + description: Username for basic HTTP authentication. + default_value: "" + required: false + - name: password + description: Password for basic HTTP authentication. + default_value: "" + required: false + - name: proxy_url + description: Proxy URL. + default_value: "" + required: false + - name: proxy_username + description: Username for proxy basic HTTP authentication. + default_value: "" + required: false + - name: proxy_password + description: Password for proxy basic HTTP authentication. + default_value: "" + required: false + - name: timeout + description: HTTP request timeout. + default_value: 1 + required: false + - name: method + description: HTTP request method. + default_value: GET + required: false + - name: body + description: HTTP request body. + default_value: "" + required: false + - name: headers + description: HTTP request headers. + default_value: "" + required: false + - name: not_follow_redirects + description: Redirect handling policy. Controls whether the client follows redirects. + default_value: false + required: false + - name: tls_skip_verify + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check. + default_value: false + required: false + - name: tls_ca + description: Certification authority that the client uses when verifying the server's certificates. + default_value: "" + required: false + - name: tls_cert + description: Client tls certificate. + default_value: "" + required: false + - name: tls_key + description: Client tls key. + default_value: "" + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: Basic + description: An example configuration. + config: | + jobs: + - name: local + url: http://127.0.0.1:8083 + headers: + X-API-Key: your-api-key # static pre-shared authentication key for access to the REST API (api-key). + - 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 + url: http://127.0.0.1:8083 + headers: + X-API-Key: 'your-api-key' # static pre-shared authentication key for access to the REST API (api-key). + + - name: remote + url: http://203.0.113.0:8083 + headers: + X-API-Key: 'your-api-key' + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: These metrics refer to the entire monitored application. + labels: [] + metrics: + - name: dnsdist.queries + description: Client queries received + unit: queries/s + chart_type: line + dimensions: + - name: all + - name: recursive + - name: empty + - name: dnsdist.queries_dropped + description: Client queries dropped + unit: queries/s + chart_type: line + dimensions: + - name: rule_drop + - name: dynamic_blocked + - name: no_policy + - name: non_queries + - name: dnsdist.packets_dropped + description: Packets dropped + unit: packets/s + chart_type: line + dimensions: + - name: acl + - name: dnsdist.answers + description: Answers statistics + unit: answers/s + chart_type: line + dimensions: + - name: self_answered + - name: nxdomain + - name: refused + - name: trunc_failures + - name: dnsdist.backend_responses + description: Backend responses + unit: responses/s + chart_type: line + dimensions: + - name: responses + - name: dnsdist.backend_commerrors + description: Backend communication errors + unit: errors/s + chart_type: line + dimensions: + - name: send_errors + - name: dnsdist.backend_errors + description: Backend error responses + unit: responses/s + chart_type: line + dimensions: + - name: timeouts + - name: servfail + - name: non_compliant + - name: dnsdist.cache + description: Cache performance + unit: answers/s + chart_type: line + dimensions: + - name: hits + - name: misses + - name: dnsdist.servercpu + description: DNSdist server CPU utilization + unit: ms/s + chart_type: stacked + dimensions: + - name: system_state + - name: user_state + - name: dnsdist.servermem + description: DNSdist server memory utilization + unit: MiB + chart_type: area + dimensions: + - name: memory_usage + - name: dnsdist.query_latency + description: Query latency + unit: queries/s + chart_type: stacked + dimensions: + - name: 1ms + - name: 10ms + - name: 50ms + - name: 100ms + - name: 1sec + - name: slow + - name: dnsdist.query_latency_avg + description: Average latency for the last N queries + unit: microseconds + chart_type: line + dimensions: + - name: "100" + - name: 1k + - name: 10k + - name: 1000k diff --git a/src/go/plugin/go.d/modules/dnsdist/metrics.go b/src/go/plugin/go.d/modules/dnsdist/metrics.go new file mode 100644 index 00000000..1de04319 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/metrics.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dnsdist + +// https://dnsdist.org/guides/webserver.html#get--jsonstat +// https://dnsdist.org/statistics.html + +type statisticMetrics struct { + AclDrops float64 `stm:"acl-drops" json:"acl-drops"` + CacheHits float64 `stm:"cache-hits" json:"cache-hits"` + CacheMisses float64 `stm:"cache-misses" json:"cache-misses"` + CPUSysMsec float64 `stm:"cpu-sys-msec" json:"cpu-sys-msec"` + CPUUserMsec float64 `stm:"cpu-user-msec" json:"cpu-user-msec"` + DownStreamSendErrors float64 `stm:"downstream-send-errors" json:"downstream-send-errors"` + DownStreamTimeout float64 `stm:"downstream-timeouts" json:"downstream-timeouts"` + DynBlocked float64 `stm:"dyn-blocked" json:"dyn-blocked"` + EmptyQueries float64 `stm:"empty-queries" json:"empty-queries"` + LatencyAvg100 float64 `stm:"latency-avg100" json:"latency-avg100"` + LatencyAvg1000 float64 `stm:"latency-avg1000" json:"latency-avg1000"` + LatencyAvg10000 float64 `stm:"latency-avg10000" json:"latency-avg10000"` + LatencyAvg1000000 float64 `stm:"latency-avg1000000" json:"latency-avg1000000"` + LatencySlow float64 `stm:"latency-slow" json:"latency-slow"` + Latency0 float64 `stm:"latency0-1" json:"latency0-1"` + Latency1 float64 `stm:"latency1-10" json:"latency1-10"` + Latency10 float64 `stm:"latency10-50" json:"latency10-50"` + Latency100 float64 `stm:"latency100-1000" json:"latency100-1000"` + Latency50 float64 `stm:"latency50-100" json:"latency50-100"` + NoPolicy float64 `stm:"no-policy" json:"no-policy"` + NonCompliantQueries float64 `stm:"noncompliant-queries" json:"noncompliant-queries"` + NonCompliantResponses float64 `stm:"noncompliant-responses" json:"noncompliant-responses"` + Queries float64 `stm:"queries" json:"queries"` + RdQueries float64 `stm:"rdqueries" json:"rdqueries"` + RealMemoryUsage float64 `stm:"real-memory-usage" json:"real-memory-usage"` + Responses float64 `stm:"responses" json:"responses"` + RuleDrop float64 `stm:"rule-drop" json:"rule-drop"` + RuleNxDomain float64 `stm:"rule-nxdomain" json:"rule-nxdomain"` + RuleRefused float64 `stm:"rule-refused" json:"rule-refused"` + SelfAnswered float64 `stm:"self-answered" json:"self-answered"` + ServFailResponses float64 `stm:"servfail-responses" json:"servfail-responses"` + TruncFailures float64 `stm:"trunc-failures" json:"trunc-failures"` +} diff --git a/src/go/plugin/go.d/modules/dnsdist/testdata/config.json b/src/go/plugin/go.d/modules/dnsdist/testdata/config.json new file mode 100644 index 00000000..984c3ed6 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/testdata/config.json @@ -0,0 +1,20 @@ +{ + "update_every": 123, + "url": "ok", + "body": "ok", + "method": "ok", + "headers": { + "ok": "ok" + }, + "username": "ok", + "password": "ok", + "proxy_url": "ok", + "proxy_username": "ok", + "proxy_password": "ok", + "timeout": 123.123, + "not_follow_redirects": true, + "tls_ca": "ok", + "tls_cert": "ok", + "tls_key": "ok", + "tls_skip_verify": true +} diff --git a/src/go/plugin/go.d/modules/dnsdist/testdata/config.yaml b/src/go/plugin/go.d/modules/dnsdist/testdata/config.yaml new file mode 100644 index 00000000..8558b61c --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/testdata/config.yaml @@ -0,0 +1,17 @@ +update_every: 123 +url: "ok" +body: "ok" +method: "ok" +headers: + ok: "ok" +username: "ok" +password: "ok" +proxy_url: "ok" +proxy_username: "ok" +proxy_password: "ok" +timeout: 123.123 +not_follow_redirects: yes +tls_ca: "ok" +tls_cert: "ok" +tls_key: "ok" +tls_skip_verify: yes diff --git a/src/go/plugin/go.d/modules/dnsdist/testdata/v1.5.1/jsonstat.json b/src/go/plugin/go.d/modules/dnsdist/testdata/v1.5.1/jsonstat.json new file mode 100644 index 00000000..37b791e4 --- /dev/null +++ b/src/go/plugin/go.d/modules/dnsdist/testdata/v1.5.1/jsonstat.json @@ -0,0 +1,56 @@ +{ + "acl-drops": 1, + "cache-hits": 1, + "cache-misses": 1, + "cpu-iowait": 39284, + "cpu-steal": 1, + "cpu-sys-msec": 411, + "cpu-user-msec": 939, + "doh-query-pipe-full": 1, + "doh-response-pipe-full": 1, + "downstream-send-errors": 1, + "downstream-timeouts": 1, + "dyn-block-nmg-size": 1, + "dyn-blocked": 1, + "empty-queries": 1, + "fd-usage": 22, + "frontend-noerror": 1003, + "frontend-nxdomain": 1, + "frontend-servfail": 1, + "latency-avg100": 14237.416845242331, + "latency-avg1000": 9728.0972656536997, + "latency-avg10000": 1514.0804874856037, + "latency-avg1000000": 15.0804874856037, + "latency-count": 1003, + "latency-slow": 1, + "latency-sum": 15474, + "latency0-1": 1, + "latency1-10": 3, + "latency10-50": 996, + "latency100-1000": 4, + "latency50-100": 1, + "no-policy": 1, + "noncompliant-queries": 1, + "noncompliant-responses": 1, + "over-capacity-drops": 1, + "packetcache-hits": 1, + "packetcache-misses": 1, + "queries": 1003, + "rdqueries": 1003, + "real-memory-usage": 202125312, + "responses": 1003, + "rule-drop": 1, + "rule-nxdomain": 1, + "rule-refused": 1, + "rule-servfail": 1, + "security-status": 1, + "self-answered": 1, + "servfail-responses": 1, + "too-old-drops": 1, + "trunc-failures": 1, + "udp-in-errors": 38, + "udp-noport-errors": 1102, + "udp-recvbuf-errors": 1, + "udp-sndbuf-errors": 179, + "uptime": 394 +} |