diff options
Diffstat (limited to '')
l--------- | src/go/plugin/go.d/modules/exim/README.md (renamed from collectors/python.d.plugin/exim/README.md) | 0 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/charts.go | 27 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/collect.go | 43 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/config_schema.json | 35 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/exec.go | 47 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/exim.go | 97 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/exim_test.go | 217 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/init.go | 23 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/integrations/exim.md | 191 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/metadata.yaml | 100 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/testdata/config.json | 4 | ||||
-rw-r--r-- | src/go/plugin/go.d/modules/exim/testdata/config.yaml | 2 |
12 files changed, 786 insertions, 0 deletions
diff --git a/collectors/python.d.plugin/exim/README.md b/src/go/plugin/go.d/modules/exim/README.md index f1f2ef9f9..f1f2ef9f9 120000 --- a/collectors/python.d.plugin/exim/README.md +++ b/src/go/plugin/go.d/modules/exim/README.md diff --git a/src/go/plugin/go.d/modules/exim/charts.go b/src/go/plugin/go.d/modules/exim/charts.go new file mode 100644 index 000000000..f09faf1d0 --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/charts.go @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package exim + +import ( + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" +) + +const ( + prioQueueEmailsCount = module.Priority + iota +) + +var charts = module.Charts{ + queueEmailsCountChart.Copy(), +} + +var queueEmailsCountChart = module.Chart{ + ID: "qemails", + Title: "Exim Queue Emails", + Units: "emails", + Fam: "queue", + Ctx: "exim.qemails", + Priority: prioQueueEmailsCount, + Dims: module.Dims{ + {ID: "emails"}, + }, +} diff --git a/src/go/plugin/go.d/modules/exim/collect.go b/src/go/plugin/go.d/modules/exim/collect.go new file mode 100644 index 000000000..ce1a34729 --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/collect.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package exim + +import ( + "bufio" + "bytes" + "fmt" + "strconv" + "strings" +) + +func (e *Exim) collect() (map[string]int64, error) { + resp, err := e.exec.countMessagesInQueue() + if err != nil { + return nil, err + } + + emails, err := parseResponse(resp) + if err != nil { + return nil, err + } + + mx := map[string]int64{ + "emails": emails, + } + + return mx, nil +} + +func parseResponse(resp []byte) (int64, error) { + sc := bufio.NewScanner(bytes.NewReader(resp)) + sc.Scan() + + line := strings.TrimSpace(sc.Text()) + + emails, err := strconv.ParseInt(line, 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid response '%s': %v", line, err) + } + + return emails, nil +} diff --git a/src/go/plugin/go.d/modules/exim/config_schema.json b/src/go/plugin/go.d/modules/exim/config_schema.json new file mode 100644 index 000000000..6561ea34f --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/config_schema.json @@ -0,0 +1,35 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Exim collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 10 + }, + "timeout": { + "title": "Timeout", + "description": "Timeout for executing the binary, specified in seconds.", + "type": "number", + "minimum": 0.5, + "default": 2 + } + }, + "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)." + } + } +} diff --git a/src/go/plugin/go.d/modules/exim/exec.go b/src/go/plugin/go.d/modules/exim/exec.go new file mode 100644 index 000000000..241c72aca --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/exec.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package exim + +import ( + "context" + "fmt" + "os/exec" + "time" + + "github.com/netdata/netdata/go/plugins/logger" +) + +type eximBinary interface { + countMessagesInQueue() ([]byte, error) +} + +func newEximExec(ndsudoPath string, timeout time.Duration, log *logger.Logger) *eximExec { + return &eximExec{ + Logger: log, + ndsudoPath: ndsudoPath, + timeout: timeout, + } +} + +type eximExec struct { + *logger.Logger + + ndsudoPath string + timeout time.Duration +} + +func (e *eximExec) countMessagesInQueue() ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), e.timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, e.ndsudoPath, "exim-bpc") + + e.Debugf("executing '%s'", cmd) + + bs, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("error on '%s': %v", cmd, err) + } + + return bs, nil +} diff --git a/src/go/plugin/go.d/modules/exim/exim.go b/src/go/plugin/go.d/modules/exim/exim.go new file mode 100644 index 000000000..f3c3e6e78 --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/exim.go @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package exim + +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" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("exim", module.Creator{ + JobConfigSchema: configSchema, + Defaults: module.Defaults{ + UpdateEvery: 10, + }, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *Exim { + return &Exim{ + Config: Config{ + Timeout: web.Duration(time.Second * 2), + }, + charts: charts.Copy(), + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"` +} + +type Exim struct { + module.Base + Config `yaml:",inline" json:""` + + charts *module.Charts + + exec eximBinary +} + +func (e *Exim) Configuration() any { + return e.Config +} + +func (e *Exim) Init() error { + exim, err := e.initEximExec() + if err != nil { + e.Errorf("exim exec initialization: %v", err) + return err + } + e.exec = exim + + return nil +} + +func (e *Exim) Check() error { + mx, err := e.collect() + if err != nil { + e.Error(err) + return err + } + + if len(mx) == 0 { + return errors.New("no metrics collected") + } + + return nil +} + +func (e *Exim) Charts() *module.Charts { + return e.charts +} + +func (e *Exim) Collect() map[string]int64 { + mx, err := e.collect() + if err != nil { + e.Error(err) + } + + if len(mx) == 0 { + return nil + } + + return mx +} + +func (e *Exim) Cleanup() {} diff --git a/src/go/plugin/go.d/modules/exim/exim_test.go b/src/go/plugin/go.d/modules/exim/exim_test.go new file mode 100644 index 000000000..16eb025e1 --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/exim_test.go @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package exim + +import ( + "errors" + "os" + "testing" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + + "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 TestExim_Configuration(t *testing.T) { + module.TestConfigurationSerialize(t, &Exim{}, dataConfigJSON, dataConfigYAML) +} + +func TestExim_Init(t *testing.T) { + tests := map[string]struct { + config Config + wantFail bool + }{ + "fails if failed to locate ndsudo": { + wantFail: true, + config: New().Config, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + exim := New() + exim.Config = test.config + + if test.wantFail { + assert.Error(t, exim.Init()) + } else { + assert.NoError(t, exim.Init()) + } + }) + } +} + +func TestExim_Cleanup(t *testing.T) { + tests := map[string]struct { + prepare func() *Exim + }{ + "not initialized exec": { + prepare: func() *Exim { + return New() + }, + }, + "after check": { + prepare: func() *Exim { + exim := New() + exim.exec = prepareMockOK() + _ = exim.Check() + return exim + }, + }, + "after collect": { + prepare: func() *Exim { + exim := New() + exim.exec = prepareMockOK() + _ = exim.Collect() + return exim + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + exim := test.prepare() + + assert.NotPanics(t, exim.Cleanup) + }) + } +} + +func TestEximCharts(t *testing.T) { + assert.NotNil(t, New().Charts()) +} + +func TestExim_Check(t *testing.T) { + tests := map[string]struct { + prepareMock func() *mockEximExec + wantFail bool + }{ + "success case": { + prepareMock: prepareMockOK, + wantFail: false, + }, + "error on exec": { + prepareMock: prepareMockErr, + wantFail: true, + }, + "empty response": { + prepareMock: prepareMockEmptyResponse, + wantFail: true, + }, + "unexpected response": { + prepareMock: prepareMockUnexpectedResponse, + wantFail: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + exim := New() + mock := test.prepareMock() + exim.exec = mock + + if test.wantFail { + assert.Error(t, exim.Check()) + } else { + assert.NoError(t, exim.Check()) + } + }) + } +} + +func TestExim_Collect(t *testing.T) { + tests := map[string]struct { + prepareMock func() *mockEximExec + wantMetrics map[string]int64 + }{ + "success case": { + prepareMock: prepareMockOK, + wantMetrics: map[string]int64{ + "emails": 99, + }, + }, + "error on exec": { + prepareMock: prepareMockErr, + wantMetrics: nil, + }, + "empty response": { + prepareMock: prepareMockEmptyResponse, + wantMetrics: nil, + }, + "unexpected response": { + prepareMock: prepareMockUnexpectedResponse, + wantMetrics: nil, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + exim := New() + mock := test.prepareMock() + exim.exec = mock + + mx := exim.Collect() + + assert.Equal(t, test.wantMetrics, mx) + + if len(test.wantMetrics) > 0 { + assert.Len(t, *exim.Charts(), len(charts)) + module.TestMetricsHasAllChartsDims(t, exim.Charts(), mx) + } + }) + } +} + +func prepareMockOK() *mockEximExec { + return &mockEximExec{ + data: []byte("99"), + } +} + +func prepareMockErr() *mockEximExec { + return &mockEximExec{ + err: true, + } +} + +func prepareMockEmptyResponse() *mockEximExec { + return &mockEximExec{} +} + +func prepareMockUnexpectedResponse() *mockEximExec { + return &mockEximExec{ + data: []byte(` +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Nulla malesuada erat id magna mattis, eu viverra tellus rhoncus. +Fusce et felis pulvinar, posuere sem non, porttitor eros. +`), + } +} + +type mockEximExec struct { + err bool + data []byte +} + +func (m *mockEximExec) countMessagesInQueue() ([]byte, error) { + if m.err { + return nil, errors.New("mock.countMessagesInQueue() error") + } + return m.data, nil +} diff --git a/src/go/plugin/go.d/modules/exim/init.go b/src/go/plugin/go.d/modules/exim/init.go new file mode 100644 index 000000000..d1d5c0793 --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/init.go @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package exim + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/netdata/netdata/go/plugins/pkg/executable" +) + +func (e *Exim) initEximExec() (eximBinary, error) { + ndsudoPath := filepath.Join(executable.Directory, "ndsudo") + if _, err := os.Stat(ndsudoPath); err != nil { + return nil, fmt.Errorf("ndsudo executable not found: %v", err) + + } + + exim := newEximExec(ndsudoPath, e.Timeout.Duration(), e.Logger) + + return exim, nil +} diff --git a/src/go/plugin/go.d/modules/exim/integrations/exim.md b/src/go/plugin/go.d/modules/exim/integrations/exim.md new file mode 100644 index 000000000..78f45683c --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/integrations/exim.md @@ -0,0 +1,191 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/exim/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/exim/metadata.yaml" +sidebar_label: "Exim" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/Mail Servers" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# Exim + + +<img src="https://netdata.cloud/img/exim.jpg" width="150"/> + + +Plugin: go.d.plugin +Module: exim + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors Exim mail queue. It relies on the [`exim`](https://www.exim.org/exim-html-3.20/doc/html/spec_5.html) CLI tool but avoids directly executing the binary. Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment. This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management. +Executed commands: +- `exim -bpc` + + + + +This collector is supported on all platforms. + +This collector only supports collecting metrics from a single instance of this integration. + + +### 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 Exim instance + +These metrics refer to the the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| exim.qemails | emails | emails | + + + +## 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/exim.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/exim.conf +``` +#### Options + +The following options can be defined globally: update_every. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update_every | Data collection frequency. | 10 | no | +| timeout | exim binary execution timeout. | 2 | no | + +</details> + +#### Examples + +##### Custom update_every + +Allows you to override the default data collection interval. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: exim + update_every: 5 # Collect logical volume statistics every 5 seconds + +``` +</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 `exim` 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 exim + ``` + +### Getting Logs + +If you're encountering problems with the `exim` 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 exim +``` + +#### 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 exim /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 exim +``` + + diff --git a/src/go/plugin/go.d/modules/exim/metadata.yaml b/src/go/plugin/go.d/modules/exim/metadata.yaml new file mode 100644 index 000000000..c7f4a7a98 --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/metadata.yaml @@ -0,0 +1,100 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-exim + plugin_name: go.d.plugin + module_name: exim + monitored_instance: + name: Exim + link: "https://www.exim.org/" + icon_filename: 'exim.jpg' + categories: + - data-collection.mail-servers + keywords: + - exim + - mail + - email + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: > + This collector monitors Exim mail queue. + It relies on the [`exim`](https://www.exim.org/exim-html-3.20/doc/html/spec_5.html) CLI tool but avoids directly executing the binary. + Instead, it utilizes `ndsudo`, a Netdata helper specifically designed to run privileged commands securely within the Netdata environment. + This approach eliminates the need to use `sudo`, improving security and potentially simplifying permission management. + + Executed commands: + + - `exim -bpc` + method_description: "" + supported_platforms: + include: [] + exclude: [] + multi_instance: false + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: [] + configuration: + file: + name: go.d/exim.conf + options: + description: | + The following options can be defined globally: update_every. + folding: + title: Config options + enabled: true + list: + - name: update_every + description: Data collection frequency. + default_value: 10 + required: false + - name: timeout + description: exim binary execution timeout. + default_value: 2 + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: Custom update_every + description: Allows you to override the default data collection interval. + config: | + jobs: + - name: exim + update_every: 5 # Collect logical volume statistics every 5 seconds + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: These metrics refer to the the entire monitored application. + labels: [] + metrics: + - name: exim.qemails + description: Exim Queue Emails + unit: 'emails' + chart_type: line + dimensions: + - name: emails diff --git a/src/go/plugin/go.d/modules/exim/testdata/config.json b/src/go/plugin/go.d/modules/exim/testdata/config.json new file mode 100644 index 000000000..291ecee3d --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/testdata/config.json @@ -0,0 +1,4 @@ +{ + "update_every": 123, + "timeout": 123.123 +} diff --git a/src/go/plugin/go.d/modules/exim/testdata/config.yaml b/src/go/plugin/go.d/modules/exim/testdata/config.yaml new file mode 100644 index 000000000..25b0b4c78 --- /dev/null +++ b/src/go/plugin/go.d/modules/exim/testdata/config.yaml @@ -0,0 +1,2 @@ +update_every: 123 +timeout: 123.123 |