go.d/postfix: simplify and fix tests (#18029)
* go.d/postfix: simplify and fix tests * update python.d.conf
Ilya Mashchenko committed
Jun 27, 2024 at 21:41 UTC
396ad748cef6f8261336c19188d7c6315c1cc509
10 files changed
+103
-225
src/collectors/python.d.plugin/python.d.conf
+1
-2
@@ -29,7 +29,6 @@ gc_interval: 300
29
# am2320: yes
30
# anomalies: no
31
# beanstalk: yes
32
-# bind_rndc: yes
32
# boinc: yes
33
# ceph: yes
34
# changefinder: no
@@ -49,7 +48,6 @@ go_expvar: no
48
# openldap: yes
49
# oracledb: yes
50
# pandas: yes
52
-# postfix: yes
51
# puppet: yes
52
# rethinkdbs: yes
53
# retroshare: yes
@@ -80,6 +78,7 @@ megacli: no # Removed (replaced with go.d/megacli).
78
mongodb: no # Removed (replaced with go.d/mongodb).
79
mysql: no # Removed (replaced with go.d/mysql).
80
nginx: no # Removed (replaced with go.d/nginx).
81
+postfix: no # Removed (replaced with go.d/postfix).
82
postgres: no # Removed (replaced with go.d/postgres).
83
proxysql: no # Removed (replaced with go.d/proxysql).
84
redis: no # Removed (replaced with go.d/redis).
src/go/collectors/go.d.plugin/config/go.d.conf
+1
@@ -74,6 +74,7 @@ modules:
74
# pika: yes
75
# portcheck: yes
76
# postgres: yes
77
+# postfix: yes
78
# powerdns: yes
79
# powerdns_recursor: yes
80
# prometheus: yes
src/go/collectors/go.d.plugin/config/go.d/postfix.conf
new
+12
@@ -0,0 +1,12 @@
1
+## All available configuration options, their descriptions and default values:
2
+## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/postfix#readme
3
+
4
+jobs:
5
+ - name: local
6
+ binary_path: /usr/sbin/postqueue
7
+
8
+ - name: local
9
+ binary_path: /usr/local/sbin/postqueue # FreeBSD
10
+
11
+ - name: local
12
+ binary_path: postqueue
src/go/collectors/go.d.plugin/modules/postfix/charts.go
+12
-29
@@ -7,55 +7,38 @@ import (
7
)
8
9
const (
10
- prioPostfixQEmailsChartTmpl = 1000 + iota
11
- prioPostfixQSizeChartTmpl
10
+ prioPostfixQueueEmailsCount = module.Priority + iota
11
+ prioPostfixQueueSize
12
)
13
14
-var postfixChartsTmpl = module.Charts{
15
- postfixQEmailsChartTmpl.Copy(),
16
- postfixQSizeChartTmpl.Copy(),
14
+var charts = module.Charts{
15
+ queueEmailsCountChart.Copy(),
16
+ queueSizeChart.Copy(),
17
}
18
19
var (
20
- postfixQEmailsChartTmpl = module.Chart{
20
+ queueEmailsCountChart = module.Chart{
21
ID: "postfix_queue_emails",
22
Title: "Postfix Queue Emails",
23
Units: "emails",
24
Fam: "queue",
25
Ctx: "postfix.qemails",
26
- Type: module.Area,
27
- Priority: prioPostfixQEmailsChartTmpl,
26
+ Type: module.Line,
27
+ Priority: prioPostfixQueueEmailsCount,
28
Dims: module.Dims{
29
- {ID: "emails", Name: "emails"},
29
+ {ID: "emails"},
30
},
31
}
32
- postfixQSizeChartTmpl = module.Chart{
32
+ queueSizeChart = module.Chart{
33
ID: "postfix_queue_size",
34
Title: "Postfix Queue Size",
35
Units: "KiB",
36
Fam: "queue",
37
Ctx: "postfix.qsize",
38
Type: module.Area,
39
- Priority: prioPostfixQSizeChartTmpl,
39
+ Priority: prioPostfixQueueSize,
40
Dims: module.Dims{
41
- {ID: "size", Name: "size"},
41
+ {ID: "size"},
42
},
43
}
44
)
45
-
46
-func (p *Postfix) addPostfixCharts() {
47
- charts := postfixChartsTmpl.Copy()
48
-
49
- if err := p.Charts().Add(*charts...); err != nil {
50
- p.Warning(err)
51
- }
52
-}
53
-
54
-// func (p *Postfix) removePostfixCharts(name string) {
55
-// for _, chart := range *p.Charts() {
56
-// if strings.HasPrefix(chart.ID, name) {
57
-// chart.MarkRemove()
58
-// chart.MarkNotCreated()
59
-// }
60
-// }
61
-// }
src/go/collectors/go.d.plugin/modules/postfix/collect.go
+27
-53
@@ -5,15 +5,15 @@ package postfix
5
import (
6
"bufio"
7
"bytes"
8
+ "errors"
9
"fmt"
9
- "log"
10
- "regexp"
10
"strconv"
11
+ "strings"
12
)
13
14
type postqueueStats struct {
15
- kbytes int64
16
- requests int64
15
+ sizeKbyte int64
16
+ requests int64
17
}
18
19
func (p *Postfix) collect() (map[string]int64, error) {
@@ -22,76 +22,50 @@ func (p *Postfix) collect() (map[string]int64, error) {
22
return nil, err
23
}
24
25
- stats, err := parsePostfixOutput(bs)
25
+ stats, err := parsePostqueueOutput(bs)
26
if err != nil {
27
return nil, err
28
}
29
30
mx := make(map[string]int64)
31
32
- p.collectPostqueueStats(mx, *stats)
33
-
34
- return mx, nil
35
-}
36
-
37
-func (p *Postfix) collectPostqueueStats(mx map[string]int64, stats postqueueStats) {
38
-
39
- if !p.seen_metrics {
40
- p.addPostfixCharts()
41
- p.seen_metrics = true
42
- }
43
-
32
mx["emails"] = stats.requests
45
- mx["size"] = stats.kbytes
33
+ mx["size"] = stats.sizeKbyte
34
35
+ return mx, nil
36
}
37
49
-func parsePostfixOutput(bs []byte) (*postqueueStats, error) {
38
+func parsePostqueueOutput(bs []byte) (*postqueueStats, error) {
39
if len(bs) == 0 {
51
- return nil, fmt.Errorf("error: No bytes to read")
40
+ return nil, errors.New("empty postqueue output")
41
}
42
54
- /*
55
- $ postqueue -p
56
- 752741009D2A* 10438 Wed Jun 26 13:39:26 root@localhost.test
57
- fotis@localhost.test
58
- 6B5FA10033D4* 10438 Wed Jun 26 13:39:23 root@localhost.test
59
- fotis@localhost.test
60
- -- 132422 Kbytes in 12991 Requests.
61
- */
62
-
43
+ var lastLine string
44
sc := bufio.NewScanner(bytes.NewReader(bs))
64
-
65
- kbytes := int64(-1)
66
- requests := int64(-1)
67
- re := regexp.MustCompile(`-- (\d+) Kbytes in (\d+) Requests\.`)
68
-
45
for sc.Scan() {
70
- line := sc.Text()
71
- if line == "Mail queue is empty" {
72
- kbytes = 0
73
- requests = 0
74
- break
46
+ if line := strings.TrimSpace(sc.Text()); line != "" {
47
+ lastLine = strings.TrimSpace(sc.Text())
48
}
49
+ }
50
77
- matches := re.FindStringSubmatch(line)
78
- if matches != nil {
79
- kbytes, _ = strconv.ParseInt(matches[1], 10, 64)
80
- requests, _ = strconv.ParseInt(matches[2], 10, 64)
81
- break
82
- }
51
+ if lastLine == "Mail queue is empty" {
52
+ return &postqueueStats{}, nil
53
}
54
85
- if err := sc.Err(); err != nil {
86
- log.Fatalf("Error reading output: %v", err)
55
+ // -- 3 Kbytes in 3 Requests.
56
+ parts := strings.Fields(lastLine)
57
+ if len(parts) < 5 {
58
+ return nil, fmt.Errorf("unexpected postqueue output ('%s')", lastLine)
59
}
60
89
- if kbytes == -1 && requests == -1 {
90
- return nil, fmt.Errorf("unexpected response")
61
+ size, err := strconv.ParseInt(parts[1], 10, 64)
62
+ if err != nil {
63
+ return nil, fmt.Errorf("unexpected postqueue output ('%s')", lastLine)
64
+ }
65
+ requests, err := strconv.ParseInt(parts[4], 10, 64)
66
+ if err != nil {
67
+ return nil, fmt.Errorf("unexpected postqueue output ('%s')", lastLine)
68
}
69
93
- return &postqueueStats{
94
- kbytes: kbytes,
95
- requests: requests,
96
- }, nil
70
+ return &postqueueStats{sizeKbyte: size, requests: requests}, nil
71
}
src/go/collectors/go.d.plugin/modules/postfix/init.go
+4
-4
@@ -16,7 +16,7 @@ func (p *Postfix) validateConfig() error {
16
return nil
17
}
18
19
-func (p *Postfix) initPostfixExec() (postqueue, error) {
19
+func (p *Postfix) initPostqueueExec() (postqueueBinary, error) {
20
binPath := p.BinaryPath
21
22
if !strings.HasPrefix(binPath, "/") {
@@ -31,8 +31,8 @@ func (p *Postfix) initPostfixExec() (postqueue, error) {
31
return nil, err
32
}
33
34
- postqueueExec := newPostqueueExec(binPath, p.Timeout.Duration())
35
- postqueueExec.Logger = p.Logger
34
+ pq := newPostqueueExec(binPath, p.Timeout.Duration())
35
+ pq.Logger = p.Logger
36
37
- return postqueueExec, nil
37
+ return pq, nil
38
}
src/go/collectors/go.d.plugin/modules/postfix/metadata.yaml
+17
-39
@@ -22,22 +22,17 @@ modules:
22
overview:
23
data_collection:
24
metrics_description: >
25
- Keep an eye on Postfix metrics for efficient mail server operations.
26
-
27
- Improve your mail server performance with Netdata's real-time metrics and built-in alerts.
25
+ This collector retrieves statistics about the Postfix mail queue using the [postqueue](https://www.postfix.org/postqueue.1.html) command-line tool.
26
method_description: >
29
- Monitors MTA email queue statistics using [postqueue](http://www.postfix.org/postqueue.1.html) tool.
30
-
31
- Runs `postqueue -p` every 10 seconds.
27
+ It periodically executes the `postqueue -p` command. The collection interval is set to 10 seconds by default, but this can be configurable.
28
supported_platforms:
29
include: []
30
exclude: []
35
- multi_instance: true
31
+ multi_instance: false
32
additional_permissions:
33
description: >
38
- Postfix has internal access controls that limit activities on the mail queue. By default, all users are allowed to view the queue. If your system is configured with more strict access controls, you need to grant the `netdata` user access to view the mail queue. In order to do it, add `netdata` to `authorized_mailq_users` in the `/etc/postfix/main.cf` file.
39
-
40
- See the `authorized_mailq_users` setting in the [Postfix documentation](https://www.postfix.org/postconf.5.html) for more details.
34
+ Postfix has internal access controls for the mail queue. By default, all users can view the queue. If your system has stricter controls, grant the `netdata` user access by adding it to `authorized_mailq_users` in the `/etc/postfix/main.cf `file.
35
+ For more details, refer to the `authorized_mailq_users` setting in the [Postfix documentation](https://www.postfix.org/postconf.5.html).
36
default_behavior:
37
auto_detection:
38
description: "The collector executes `postqueue -p` to get Postfix queue statistics."
@@ -54,39 +49,22 @@ modules:
49
description: ""
50
options:
51
description: |
57
- There are 2 sections:
58
-
59
- * Global variables
60
- * One or more JOBS that can define multiple different instances to monitor.
61
-
62
- The following options can be defined globally: priority, penalty, autodetection_retry, update_every, but can also be defined per JOB to override the global values.
63
-
64
- Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
65
-
66
- Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
52
+ The following options can be defined globally: update_every.
53
folding:
54
title: Config options
55
enabled: true
56
list:
57
- name: update_every
72
- description: Sets the default data collection frequency.
73
- default_value: 1
58
+ description: Data collection frequency.
59
+ default_value: 10
60
required: false
75
- - name: priority
76
- description: Controls the order of charts at the netdata dashboard.
77
- default_value: 60000
78
- required: false
79
- - name: autodetection_retry
80
- description: Sets the job re-check interval in seconds.
81
- default_value: 0
82
- required: false
83
- - name: penalty
84
- description: Indicates whether to apply penalty to update_every in case of failures.
85
- default_value: yes
86
- required: false
87
- - name: name
88
- description: Job name. This value will overwrite the `job_name` value. JOBS with the same name are mutually exclusive. Only one of them will be allowed running at any time. This allows autodetection to try several alternatives and pick the one that works.
89
- default_value: ""
61
+ - name: binary_path
62
+ description: Path to the `postqueue` binary. If an absolute path is provided, the collector will use it directly; otherwise, it will search for the binary in directories specified in the PATH environment variable.
63
+ default_value: /usr/sbin/postqueue
64
+ required: true
65
+ - name: timeout
66
+ description: Timeout for executing the binary, specified in seconds.
67
+ default_value: 2
68
required: false
69
examples:
70
folding:
@@ -111,13 +89,13 @@ modules:
89
metrics:
90
- name: postfix.qemails
91
description: Postfix Queue Emails
114
- unit: "emails"
92
+ unit: emails
93
chart_type: line
94
dimensions:
95
- name: emails
96
- name: postfix.qsize
97
description: Postfix Queue Emails Size
120
- unit: "KiB"
98
+ unit: KiB
99
chart_type: area
100
dimensions:
101
- name: size
src/go/collectors/go.d.plugin/modules/postfix/postfix.conf
deleted
-72
@@ -1,72 +0,0 @@
1
-# netdata python.d.plugin configuration for postfix
2
-#
3
-# This file is in YaML format. Generally the format is:
4
-#
5
-# name: value
6
-#
7
-# There are 2 sections:
8
-# - global variables
9
-# - one or more JOBS
10
-#
11
-# JOBS allow you to collect values from multiple sources.
12
-# Each source will have its own set of charts.
13
-#
14
-# JOB parameters have to be indented (using spaces only, example below).
15
-
16
-# ----------------------------------------------------------------------
17
-# Global Variables
18
-# These variables set the defaults for all JOBs, however each JOB
19
-# may define its own, overriding the defaults.
20
-
21
-# update_every sets the default data collection frequency.
22
-# If unset, the python.d.plugin default is used.
23
-# postfix is slow, so once every 10 seconds
24
-update_every: 10
25
-
26
-# priority controls the order of charts at the netdata dashboard.
27
-# Lower numbers move the charts towards the top of the page.
28
-# If unset, the default for python.d.plugin is used.
29
-# priority: 60000
30
-
31
-# penalty indicates whether to apply penalty to update_every in case of failures.
32
-# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes.
33
-# penalty: yes
34
-
35
-# autodetection_retry sets the job re-check interval in seconds.
36
-# The job is not deleted if check fails.
37
-# Attempts to start the job are made once every autodetection_retry.
38
-# This feature is disabled by default.
39
-# autodetection_retry: 0
40
-
41
-# ----------------------------------------------------------------------
42
-# JOBS (data collection sources)
43
-#
44
-# The default JOBS share the same *name*. JOBS with the same name
45
-# are mutually exclusive. Only one of them will be allowed running at
46
-# any time. This allows autodetection to try several alternatives and
47
-# pick the one that works.
48
-#
49
-# Any number of jobs is supported.
50
-#
51
-# All python.d.plugin JOBS (for all its modules) support a set of
52
-# predefined parameters. These are:
53
-#
54
-# job_name:
55
-# name: myname # the JOB's name as it will appear at the
56
-# # dashboard (by default is the job_name)
57
-# # JOBs sharing a name are mutually exclusive
58
-# update_every: 1 # the JOB's data collection frequency
59
-# priority: 60000 # the JOB's order on the dashboard
60
-# penalty: yes # the JOB's penalty
61
-# autodetection_retry: 0 # the JOB's re-check interval in seconds
62
-#
63
-# Additionally to the above, postfix also supports the following:
64
-#
65
-# command: 'postqueue -p' # the command to run
66
-#
67
-
68
-# ----------------------------------------------------------------------
69
-# AUTO-DETECTION JOBS
70
-
71
-local:
72
- command: 'postqueue -p'
src/go/collectors/go.d.plugin/modules/postfix/postfix.go
+6
-9
@@ -31,8 +31,7 @@ func New() *Postfix {
31
BinaryPath: "/usr/sbin/postqueue",
32
Timeout: web.Duration(time.Second * 2),
33
},
34
- charts: &module.Charts{},
35
- // zpools: make(map[string]bool),
34
+ charts: charts.Copy(),
35
}
36
}
37
@@ -49,11 +48,9 @@ type (
48
49
charts *module.Charts
50
52
- exec postqueue
53
-
54
- seen_metrics bool
51
+ exec postqueueBinary
52
}
56
- postqueue interface {
53
+ postqueueBinary interface {
54
list() ([]byte, error)
55
}
56
)
@@ -68,12 +65,12 @@ func (p *Postfix) Init() error {
65
return err
66
}
67
71
- postfixExec, err := p.initPostfixExec()
68
+ pq, err := p.initPostqueueExec()
69
if err != nil {
73
- p.Errorf("postfix exec initialization: %v", err)
70
+ p.Errorf("postqueue exec initialization: %v", err)
71
return err
72
}
76
- p.exec = postfixExec
73
+ p.exec = pq
74
75
return nil
76
}
src/go/collectors/go.d.plugin/modules/postfix/postfix_test.go
+23
-17
@@ -9,8 +9,6 @@ import (
9
10
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
11
12
- "fmt"
13
-
12
"github.com/stretchr/testify/assert"
13
"github.com/stretchr/testify/require"
14
)
@@ -26,13 +24,10 @@ func Test_testDataIsValid(t *testing.T) {
24
for name, data := range map[string][]byte{
25
"dataConfigJSON": dataConfigJSON,
26
"dataConfigYAML": dataConfigYAML,
29
-
30
- "dataPostqueue": dataPostqueue,
27
+ "dataPostqueue": dataPostqueue,
28
} {
29
require.NotNil(t, data, name)
33
-
30
}
35
- // fmt.Println(string(dataPostqueue))
31
}
32
33
func TestPostfix_Configuration(t *testing.T) {
@@ -118,20 +113,24 @@ func TestPostfix_Check(t *testing.T) {
113
wantFail bool
114
}{
115
"success case": {
116
+ wantFail: false,
117
prepareMock: prepareMockOK,
118
+ },
119
+ "mail queue is empty": {
120
wantFail: false,
121
+ prepareMock: prepareMockEmptyMailQueue,
122
},
123
"error on list call": {
125
- prepareMock: prepareMockErrOnList,
124
wantFail: true,
125
+ prepareMock: prepareMockErrOnList,
126
},
127
"empty response": {
129
- prepareMock: prepareMockEmptyResponse,
128
wantFail: true,
129
+ prepareMock: prepareMockEmptyResponse,
130
},
131
"unexpected response": {
133
- prepareMock: prepareMockUnexpectedResponse,
132
wantFail: true,
133
+ prepareMock: prepareMockUnexpectedResponse,
134
},
135
}
136
@@ -158,8 +157,15 @@ func TestPostfix_Collect(t *testing.T) {
157
"success case": {
158
prepareMock: prepareMockOK,
159
wantMetrics: map[string]int64{
161
- "qemails": 12991,
162
- "qsize": 132422,
160
+ "emails": 12991,
161
+ "size": 132422,
162
+ },
163
+ },
164
+ "mail queue is empty": {
165
+ prepareMock: prepareMockEmptyMailQueue,
166
+ wantMetrics: map[string]int64{
167
+ "emails": 0,
168
+ "size": 0,
169
},
170
},
171
"error on list call": {
@@ -185,12 +191,6 @@ func TestPostfix_Collect(t *testing.T) {
191
mx := pf.Collect()
192
193
assert.Equal(t, test.wantMetrics, mx)
188
-
189
- fmt.Println(assert.Equal(t, test.wantMetrics, mx))
190
-
191
- if len(test.wantMetrics) > 0 {
192
- assert.Len(t, *pf.Charts(), len(postfixChartsTmpl))
193
- }
194
})
195
}
196
}
@@ -201,6 +201,12 @@ func prepareMockOK() *mockPostqueueExec {
201
}
202
}
203
204
+func prepareMockEmptyMailQueue() *mockPostqueueExec {
205
+ return &mockPostqueueExec{
206
+ listData: []byte("Mail queue is empty"),
207
+ }
208
+}
209
+
210
func prepareMockErrOnList() *mockPostqueueExec {
211
return &mockPostqueueExec{
212
errOnList: true,