go.d systemdunits add unit files state (#17606)
Ilya Mashchenko committed
May 7, 2024 at 12:16 UTC
50c3b9181284938ebe225fbd191030b7fb7baf9c
12 files changed
+674
-222
src/go/collectors/go.d.plugin/modules/systemdunits/charts.go
+83
-41
@@ -7,6 +7,8 @@ package systemdunits
7
8
import (
9
"fmt"
10
+ "path/filepath"
11
+ "strings"
12
13
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
14
@@ -15,62 +17,102 @@ import (
17
)
18
19
const (
18
- prioServiceUnitState = module.Priority + iota
19
- prioSocketUnitState
20
- prioTargetUnitState
21
- prioPathUnitState
22
- prioDeviceUnitState
23
- prioMountUnitState
24
- prioAutomountUnitState
25
- prioSwapUnitState
26
- prioTimerUnitState
27
- prioScopeUnitState
28
- prioSliceUnitState
20
+ prioUnitState = module.Priority + iota
21
+ prioUnitFileState
22
)
23
31
-var prioMap = map[string]int{
32
- unitTypeService: prioServiceUnitState,
33
- unitTypeSocket: prioSocketUnitState,
34
- unitTypeTarget: prioTargetUnitState,
35
- unitTypePath: prioPathUnitState,
36
- unitTypeDevice: prioDeviceUnitState,
37
- unitTypeMount: prioMountUnitState,
38
- unitTypeAutomount: prioAutomountUnitState,
39
- unitTypeSwap: prioSwapUnitState,
40
- unitTypeTimer: prioTimerUnitState,
41
- unitTypeScope: prioScopeUnitState,
42
- unitTypeSlice: prioSliceUnitState,
43
-}
44
-
45
-func newTypedUnitStateChartTmpl(name, typ string) *module.Chart {
24
+func (s *SystemdUnits) addUnitCharts(name, typ string) {
25
chart := module.Chart{
47
- ID: fmt.Sprintf("unit_%s_%s_state", name, typ),
48
- Title: fmt.Sprintf("%s Unit State", cases.Title(language.English, cases.Compact).String(typ)),
26
+ ID: "unit_%s_%s_state",
27
+ Title: "%s Unit State",
28
Units: "state",
50
- Fam: fmt.Sprintf("%s units", typ),
51
- Ctx: fmt.Sprintf("systemd.%s_unit_state", typ),
52
- Priority: prioMap[typ],
29
+ Fam: "%s units",
30
+ Ctx: "systemd.%s_unit_state",
31
+ Priority: prioUnitState,
32
Labels: []module.Label{
33
{Key: "unit_name", Value: name},
34
},
35
Dims: module.Dims{
57
- {Name: unitStateActive},
58
- {Name: unitStateInactive},
59
- {Name: unitStateActivating},
60
- {Name: unitStateDeactivating},
61
- {Name: unitStateFailed},
36
+ {ID: "unit_%s_%s_state_%s", Name: unitStateActive},
37
+ {ID: "unit_%s_%s_state_%s", Name: unitStateInactive},
38
+ {ID: "unit_%s_%s_state_%s", Name: unitStateActivating},
39
+ {ID: "unit_%s_%s_state_%s", Name: unitStateDeactivating},
40
+ {ID: "unit_%s_%s_state_%s", Name: unitStateFailed},
41
},
42
}
43
+
44
+ chart.ID = fmt.Sprintf(chart.ID, name, typ)
45
+ chart.Title = fmt.Sprintf(chart.Title, cases.Title(language.English, cases.Compact).String(typ))
46
+ chart.Fam = fmt.Sprintf(chart.Fam, typ)
47
+ chart.Ctx = fmt.Sprintf(chart.Ctx, typ)
48
+
49
for _, d := range chart.Dims {
65
- d.ID = fmt.Sprintf("unit_%s_%s_state_%s", name, typ, d.Name)
50
+ d.ID = fmt.Sprintf(d.ID, name, typ, d.Name)
51
+ }
52
+
53
+ if err := s.Charts().Add(&chart); err != nil {
54
+ s.Warning(err)
55
}
67
- return &chart
56
}
57
70
-func (s *SystemdUnits) addUnitToCharts(name, typ string) {
71
- chart := newTypedUnitStateChartTmpl(name, typ)
58
+func (s *SystemdUnits) removeUnitCharts(name, typ string) {
59
+ px := fmt.Sprintf("unit_%s_%s_", name, typ)
60
+ s.removeCharts(px)
61
+}
62
+
63
+func (s *SystemdUnits) addUnitFileCharts(unitPath string) {
64
+ _, unitName := filepath.Split(unitPath)
65
+ unitType := strings.TrimPrefix(filepath.Ext(unitPath), ".")
66
+
67
+ chart := module.Chart{
68
+ ID: "unit_file_%s_state",
69
+ Title: "Unit File State",
70
+ Units: "state",
71
+ Fam: "unit files",
72
+ Ctx: "systemd.unit_file_state",
73
+ Type: module.Line,
74
+ Priority: prioUnitFileState,
75
+ Labels: []module.Label{
76
+ {Key: "unit_file_name", Value: unitName},
77
+ {Key: "unit_file_type", Value: unitType},
78
+ },
79
+ Dims: module.Dims{
80
+ {ID: "unit_file_%s_state_enabled", Name: "enabled"},
81
+ {ID: "unit_file_%s_state_enabled-runtime", Name: "enabled-runtime"},
82
+ {ID: "unit_file_%s_state_linked", Name: "linked"},
83
+ {ID: "unit_file_%s_state_linked-runtime", Name: "linked-runtime"},
84
+ {ID: "unit_file_%s_state_alias", Name: "alias"},
85
+ {ID: "unit_file_%s_state_masked", Name: "masked"},
86
+ {ID: "unit_file_%s_state_masked-runtime", Name: "masked-runtime"},
87
+ {ID: "unit_file_%s_state_static", Name: "static"},
88
+ {ID: "unit_file_%s_state_disabled", Name: "disabled"},
89
+ {ID: "unit_file_%s_state_indirect", Name: "indirect"},
90
+ {ID: "unit_file_%s_state_generated", Name: "generated"},
91
+ {ID: "unit_file_%s_state_transient", Name: "transient"},
92
+ {ID: "unit_file_%s_state_bad", Name: "bad"},
93
+ },
94
+ }
95
+
96
+ chart.ID = fmt.Sprintf(chart.ID, strings.ReplaceAll(unitPath, ".", "_"))
97
+ for _, dim := range chart.Dims {
98
+ dim.ID = fmt.Sprintf(dim.ID, unitPath)
99
+ }
100
73
- if err := s.Charts().Add(chart); err != nil {
101
+ if err := s.Charts().Add(&chart); err != nil {
102
s.Warning(err)
103
}
104
}
105
+
106
+func (s *SystemdUnits) removeUnitFileCharts(unitPath string) {
107
+ px := fmt.Sprintf("unit_file_%s_", strings.ReplaceAll(unitPath, ".", "_"))
108
+ s.removeCharts(px)
109
+}
110
+
111
+func (s *SystemdUnits) removeCharts(prefix string) {
112
+ for _, chart := range *s.Charts() {
113
+ if strings.HasPrefix(chart.ID, prefix) {
114
+ chart.MarkRemove()
115
+ chart.MarkNotCreated()
116
+ }
117
+ }
118
+}
src/go/collectors/go.d.plugin/modules/systemdunits/client.go
+1
@@ -19,6 +19,7 @@ type systemdConnection interface {
19
GetManagerProperty(string) (string, error)
20
ListUnitsContext(ctx context.Context) ([]dbus.UnitStatus, error)
21
ListUnitsByPatternsContext(ctx context.Context, states []string, patterns []string) ([]dbus.UnitStatus, error)
22
+ ListUnitFilesByPatternsContext(ctx context.Context, states []string, patterns []string) ([]dbus.UnitFile, error)
23
}
24
25
type systemdDBusClient struct{}
src/go/collectors/go.d.plugin/modules/systemdunits/collect.go
+8
-131
@@ -6,45 +6,9 @@
6
package systemdunits
7
8
import (
9
- "context"
9
"fmt"
10
"regexp"
11
"strconv"
13
- "strings"
14
-
15
- "github.com/coreos/go-systemd/v22/dbus"
16
-)
17
-
18
-const (
19
- // https://www.freedesktop.org/software/systemd/man/systemd.html
20
- unitStateActive = "active"
21
- unitStateInactive = "inactive"
22
- unitStateActivating = "activating"
23
- unitStateDeactivating = "deactivating"
24
- unitStateFailed = "failed"
25
-
26
- // https://www.freedesktop.org/software/systemd/man/systemd.html
27
- unitTypeService = "service"
28
- unitTypeSocket = "socket"
29
- unitTypeTarget = "target"
30
- unitTypePath = "path"
31
- unitTypeDevice = "device"
32
- unitTypeMount = "mount"
33
- unitTypeAutomount = "automount"
34
- unitTypeSwap = "swap"
35
- unitTypeTimer = "timer"
36
- unitTypeScope = "scope"
37
- unitTypeSlice = "slice"
38
-)
39
-
40
-var (
41
- unitStates = []string{
42
- unitStateActive,
43
- unitStateActivating,
44
- unitStateFailed,
45
- unitStateInactive,
46
- unitStateDeactivating,
47
- }
12
)
13
14
func (s *SystemdUnits) collect() (map[string]int64, error) {
@@ -62,47 +26,23 @@ func (s *SystemdUnits) collect() (map[string]int64, error) {
26
s.systemdVersion = ver
27
}
28
65
- var units []dbus.UnitStatus
66
- if s.systemdVersion >= 230 {
67
- // https://github.com/systemd/systemd/pull/3142
68
- units, err = s.getLoadedUnitsByPatterns(conn)
69
- } else {
70
- units, err = s.getLoadedUnits(conn)
71
- }
72
- if err != nil {
29
+ mx := make(map[string]int64)
30
+
31
+ if err := s.collectUnits(mx, conn); err != nil {
32
s.closeConnection()
33
return nil, err
34
}
35
77
- if len(units) == 0 {
78
- return nil, nil
36
+ if s.CollectUnitFiles && len(s.IncludeUnitFiles) > 0 {
37
+ if err := s.collectUnitFiles(mx, conn); err != nil {
38
+ s.closeConnection()
39
+ return mx, err
40
+ }
41
}
42
81
- mx := make(map[string]int64)
82
- s.collectUnitsStates(mx, units)
83
-
43
return mx, nil
44
}
45
87
-func (s *SystemdUnits) collectUnitsStates(mx map[string]int64, units []dbus.UnitStatus) {
88
- for _, unit := range units {
89
- name, typ := extractUnitNameType(cleanUnitName(unit.Name))
90
- if name == "" || typ == "" {
91
- continue
92
- }
93
-
94
- if !s.units[unit.Name] {
95
- s.units[unit.Name] = true
96
- s.addUnitToCharts(name, typ)
97
- }
98
-
99
- for _, s := range unitStates {
100
- mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, s)] = 0
101
- }
102
- mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, unit.ActiveState)] = 1
103
- }
104
-}
105
-
46
func (s *SystemdUnits) getConnection() (systemdConnection, error) {
47
if s.conn == nil {
48
conn, err := s.client.connect()
@@ -146,66 +86,3 @@ func (s *SystemdUnits) getSystemdVersion(conn systemdConnection) (int, error) {
86
87
return ver, nil
88
}
149
-
150
-func (s *SystemdUnits) getLoadedUnits(conn systemdConnection) ([]dbus.UnitStatus, error) {
151
- ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
152
- defer cancel()
153
-
154
- s.Debugf("calling function 'ListUnits'")
155
- units, err := conn.ListUnitsContext(ctx)
156
- if err != nil {
157
- return nil, fmt.Errorf("error on ListUnits: %v", err)
158
- }
159
-
160
- loaded := units[:0]
161
- for _, unit := range units {
162
- if unit.LoadState == "loaded" && s.sr.MatchString(unit.Name) {
163
- loaded = append(loaded, unit)
164
- }
165
- }
166
- s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
167
-
168
- return loaded, nil
169
-}
170
-
171
-func (s *SystemdUnits) getLoadedUnitsByPatterns(conn systemdConnection) ([]dbus.UnitStatus, error) {
172
- ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
173
- defer cancel()
174
-
175
- s.Debugf("calling function 'ListUnitsByPatterns'")
176
-
177
- units, err := conn.ListUnitsByPatternsContext(ctx, unitStates, s.Include)
178
- if err != nil {
179
- return nil, fmt.Errorf("error on ListUnitsByPatterns: %v", err)
180
- }
181
-
182
- loaded := units[:0]
183
- for _, unit := range units {
184
- if unit.LoadState == "loaded" {
185
- loaded = append(loaded, unit)
186
- }
187
- }
188
- s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
189
-
190
- return loaded, nil
191
-}
192
-
193
-func extractUnitNameType(name string) (string, string) {
194
- idx := strings.LastIndexByte(name, '.')
195
- if idx <= 0 {
196
- return "", ""
197
- }
198
- return name[:idx], name[idx+1:]
199
-}
200
-
201
-func cleanUnitName(name string) string {
202
- // dev-disk-by\x2duuid-DE44\x2dCEE0.device => dev-disk-by-uuid-DE44-CEE0.device
203
- if strings.IndexByte(name, '\\') == -1 {
204
- return name
205
- }
206
- v, err := strconv.Unquote("\"" + name + "\"")
207
- if err != nil {
208
- return name
209
- }
210
- return v
211
-}
src/go/collectors/go.d.plugin/modules/systemdunits/collect_unit_files.go
new
+94
@@ -0,0 +1,94 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+//go:build linux
4
+// +build linux
5
+
6
+package systemdunits
7
+
8
+import (
9
+ "context"
10
+ "fmt"
11
+ "strings"
12
+ "time"
13
+
14
+ "github.com/coreos/go-systemd/v22/dbus"
15
+)
16
+
17
+// https://github.com/systemd/systemd/blob/3d320785c4bbba74459096b07e85a79c4f0cdffb/src/shared/install.c#L3785
18
+// see "is-enabled" in https://www.man7.org/linux/man-pages/man1/systemctl.1.html
19
+var unitFileStates = []string{
20
+ "enabled",
21
+ "enabled-runtime",
22
+ "linked",
23
+ "linked-runtime",
24
+ "alias",
25
+ "masked",
26
+ "masked-runtime",
27
+ "static",
28
+ "disabled",
29
+ "indirect",
30
+ "generated",
31
+ "transient",
32
+ "bad",
33
+}
34
+
35
+func (s *SystemdUnits) collectUnitFiles(mx map[string]int64, conn systemdConnection) error {
36
+ if s.systemdVersion < 230 {
37
+ return nil
38
+ }
39
+
40
+ if now := time.Now(); now.After(s.lastListUnitFilesTime.Add(s.CollectUnitFilesEvery.Duration())) {
41
+ unitFiles, err := s.getUnitFilesByPatterns(conn)
42
+ if err != nil {
43
+ return err
44
+ }
45
+ s.lastListUnitFilesTime = now
46
+ s.cachedUnitFiles = unitFiles
47
+ }
48
+
49
+ seen := make(map[string]bool)
50
+
51
+ for _, unitFile := range s.cachedUnitFiles {
52
+ seen[unitFile.Path] = true
53
+
54
+ if !s.seenUnitFiles[unitFile.Path] {
55
+ s.seenUnitFiles[unitFile.Path] = true
56
+ s.addUnitFileCharts(unitFile.Path)
57
+ }
58
+
59
+ px := fmt.Sprintf("unit_file_%s_state_", unitFile.Path)
60
+ for _, st := range unitFileStates {
61
+ mx[px+st] = 0
62
+ }
63
+ mx[px+strings.ToLower(unitFile.Type)] = 1
64
+ }
65
+
66
+ for k := range s.seenUnitFiles {
67
+ if !seen[k] {
68
+ delete(s.seenUnitFiles, k)
69
+ s.removeUnitFileCharts(k)
70
+ }
71
+ }
72
+
73
+ return nil
74
+}
75
+
76
+func (s *SystemdUnits) getUnitFilesByPatterns(conn systemdConnection) ([]dbus.UnitFile, error) {
77
+ ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
78
+ defer cancel()
79
+
80
+ s.Debugf("calling function 'ListUnitFilesByPatterns'")
81
+
82
+ unitFiles, err := conn.ListUnitFilesByPatternsContext(ctx, nil, []string{"*.service"})
83
+ if err != nil {
84
+ return nil, fmt.Errorf("error on ListUnitFilesByPatterns: %v", err)
85
+ }
86
+
87
+ for i := range unitFiles {
88
+ unitFiles[i].Path = cleanUnitName(unitFiles[i].Path)
89
+ }
90
+
91
+ s.Debugf("got %d unit files", len(unitFiles))
92
+
93
+ return unitFiles, nil
94
+}
src/go/collectors/go.d.plugin/modules/systemdunits/collect_units.go
new
+151
@@ -0,0 +1,151 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+//go:build linux
4
+// +build linux
5
+
6
+package systemdunits
7
+
8
+import (
9
+ "context"
10
+ "fmt"
11
+ "strconv"
12
+ "strings"
13
+
14
+ "github.com/coreos/go-systemd/v22/dbus"
15
+)
16
+
17
+const (
18
+ // https://www.freedesktop.org/software/systemd/man/systemd.html
19
+ unitStateActive = "active"
20
+ unitStateInactive = "inactive"
21
+ unitStateActivating = "activating"
22
+ unitStateDeactivating = "deactivating"
23
+ unitStateFailed = "failed"
24
+)
25
+
26
+var unitStates = []string{
27
+ unitStateActive,
28
+ unitStateActivating,
29
+ unitStateFailed,
30
+ unitStateInactive,
31
+ unitStateDeactivating,
32
+}
33
+
34
+func (s *SystemdUnits) collectUnits(mx map[string]int64, conn systemdConnection) error {
35
+ var units []dbus.UnitStatus
36
+ var err error
37
+
38
+ if s.systemdVersion >= 230 {
39
+ // https://github.com/systemd/systemd/pull/3142
40
+ units, err = s.getLoadedUnitsByPatterns(conn)
41
+ } else {
42
+ units, err = s.getLoadedUnits(conn)
43
+ }
44
+ if err != nil {
45
+ return err
46
+ }
47
+
48
+ seen := make(map[string]bool)
49
+
50
+ for _, unit := range units {
51
+ name, typ, ok := extractUnitNameType(unit.Name)
52
+ if !ok {
53
+ continue
54
+ }
55
+
56
+ seen[unit.Name] = true
57
+
58
+ if !s.seenUnits[unit.Name] {
59
+ s.seenUnits[unit.Name] = true
60
+ s.addUnitCharts(name, typ)
61
+ }
62
+
63
+ for _, s := range unitStates {
64
+ mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, s)] = 0
65
+ }
66
+ mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, unit.ActiveState)] = 1
67
+ }
68
+
69
+ for k := range s.seenUnits {
70
+ if !seen[k] {
71
+ delete(s.seenUnits, k)
72
+ if name, typ, ok := extractUnitNameType(k); ok {
73
+ s.removeUnitCharts(name, typ)
74
+ }
75
+ }
76
+ }
77
+
78
+ return nil
79
+}
80
+
81
+func (s *SystemdUnits) getLoadedUnits(conn systemdConnection) ([]dbus.UnitStatus, error) {
82
+ ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
83
+ defer cancel()
84
+
85
+ s.Debugf("calling function 'ListUnits'")
86
+ units, err := conn.ListUnitsContext(ctx)
87
+ if err != nil {
88
+ return nil, fmt.Errorf("error on ListUnits: %v", err)
89
+ }
90
+
91
+ for i := range units {
92
+ units[i].Name = cleanUnitName(units[i].Name)
93
+ }
94
+
95
+ loaded := units[:0]
96
+ for _, unit := range units {
97
+ if unit.LoadState == "loaded" && s.unitSr.MatchString(unit.Name) {
98
+ loaded = append(loaded, unit)
99
+ }
100
+ }
101
+
102
+ s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
103
+
104
+ return loaded, nil
105
+}
106
+
107
+func (s *SystemdUnits) getLoadedUnitsByPatterns(conn systemdConnection) ([]dbus.UnitStatus, error) {
108
+ ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
109
+ defer cancel()
110
+
111
+ s.Debugf("calling function 'ListUnitsByPatterns'")
112
+
113
+ units, err := conn.ListUnitsByPatternsContext(ctx, unitStates, s.Include)
114
+ if err != nil {
115
+ return nil, fmt.Errorf("error on ListUnitsByPatterns: %v", err)
116
+ }
117
+
118
+ for i := range units {
119
+ units[i].Name = cleanUnitName(units[i].Name)
120
+ }
121
+
122
+ loaded := units[:0]
123
+ for _, unit := range units {
124
+ if unit.LoadState == "loaded" {
125
+ loaded = append(loaded, unit)
126
+ }
127
+ }
128
+ s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
129
+
130
+ return loaded, nil
131
+}
132
+
133
+func extractUnitNameType(name string) (string, string, bool) {
134
+ idx := strings.LastIndexByte(name, '.')
135
+ if idx <= 0 {
136
+ return "", "", false
137
+ }
138
+ return name[:idx], name[idx+1:], true
139
+}
140
+
141
+func cleanUnitName(name string) string {
142
+ // dev-disk-by\x2duuid-DE44\x2dCEE0.device => dev-disk-by-uuid-DE44-CEE0.device
143
+ if strings.IndexByte(name, '\\') == -1 {
144
+ return name
145
+ }
146
+ v, err := strconv.Unquote("\"" + name + "\"")
147
+ if err != nil {
148
+ return name
149
+ }
150
+ return v
151
+}
src/go/collectors/go.d.plugin/modules/systemdunits/config_schema.json
+54
@@ -34,6 +34,36 @@
34
"default": [
35
"*.service"
36
]
37
+ },
38
+ "collect_unit_files": {
39
+ "title": "Collect unit files",
40
+ "description": "If set, collect the state of installed unit files. **Enabling this may increase system overhead**, particularly if the pattern matches a large number of unit files.",
41
+ "type": "boolean",
42
+ "default": false
43
+ },
44
+ "collect_unit_files_every": {
45
+ "title": "Unit files polling interval",
46
+ "description": "Interval for querying systemd about unit files and their enablement state, measured in seconds. Data is cached for this interval to reduce system overhead.",
47
+ "type": "number",
48
+ "minimum": 1,
49
+ "default": 300
50
+ },
51
+ "include_unit_files": {
52
+ "title": "Include unit files",
53
+ "description": "Configuration for monitoring specific systemd unit files. Include systemd unit files whose names match any of the specified [patterns](https://golang.org/pkg/path/filepath/#Match).",
54
+ "type": [
55
+ "array",
56
+ "null"
57
+ ],
58
+ "uniqueItems": true,
59
+ "minItems": 1,
60
+ "items": {
61
+ "title": "Unit file name pattern",
62
+ "type": "string"
63
+ },
64
+ "default": [
65
+ "*.service"
66
+ ]
67
}
68
},
69
"required": [
@@ -48,11 +78,35 @@
78
"uiOptions": {
79
"fullPage": true
80
},
81
+ "ui:flavour": "tabs",
82
+ "ui:options": {
83
+ "tabs": [
84
+ {
85
+ "title": "Base",
86
+ "fields": [
87
+ "update_every",
88
+ "timeout",
89
+ "include"
90
+ ]
91
+ },
92
+ {
93
+ "title": "Unit Files",
94
+ "fields": [
95
+ "collect_unit_files",
96
+ "collect_unit_files_every",
97
+ "include_unit_files"
98
+ ]
99
+ }
100
+ ]
101
+ },
102
"timeout": {
103
"ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
104
},
105
"include": {
106
"ui:listFlavour": "list"
107
+ },
108
+ "include_unit_files": {
109
+ "ui:listFlavour": "list"
110
}
111
}
112
}
src/go/collectors/go.d.plugin/modules/systemdunits/init.go
+1
-1
@@ -19,7 +19,7 @@ func (s *SystemdUnits) validateConfig() error {
19
return nil
20
}
21
22
-func (s *SystemdUnits) initSelector() (matcher.Matcher, error) {
22
+func (s *SystemdUnits) initUnitSelector() (matcher.Matcher, error) {
23
if len(s.Include) == 0 {
24
return matcher.TRUE(), nil
25
}
src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml
+55
-5
@@ -21,7 +21,7 @@ modules:
21
overview:
22
data_collection:
23
metrics_description: |
24
- This collector monitors Systemd units state.
24
+ This collector monitors the state of Systemd units and unit files.
25
method_description: ""
26
supported_platforms:
27
include: []
@@ -57,8 +57,12 @@ modules:
57
description: Recheck interval in seconds. Zero means no recheck will be scheduled.
58
default_value: 0
59
required: false
60
+ - name: timeout
61
+ description: System bus requests timeout.
62
+ default_value: 1
63
+ required: false
64
- name: include
61
- description: Systemd units filter.
65
+ description: Systemd units selector.
66
default_value: "*.service"
67
required: false
68
detailed_description: |
@@ -73,10 +77,30 @@ modules:
77
- pattern1
78
- pattern2
79
```
76
- - name: timeout
77
- description: System bus requests timeout.
78
- default_value: 1
80
+ - name: collect_unit_files
81
+ description: If set to true, collect the state of installed unit files. Enabling this may increase system overhead.
82
+ default_value: "false"
83
required: false
84
+ - name: collect_unit_files_every
85
+ description: Interval for querying systemd about unit files and their enablement state, measured in seconds. Data is cached for this interval to reduce system overhead.
86
+ default_value: 300
87
+ required: false
88
+ - name: include_unit_files
89
+ description: Systemd unit files selector.
90
+ default_value: "*.service"
91
+ required: false
92
+ detailed_description: |
93
+ Systemd unit files matching the selector will be monitored.
94
+
95
+ - Logic: (pattern1 OR pattern2)
96
+ - Pattern syntax: [shell file name pattern](https://golang.org/pkg/path/filepath/#Match)
97
+ - Syntax:
98
+
99
+ ```yaml
100
+ includes:
101
+ - pattern1
102
+ - pattern2
103
+ ```
104
examples:
105
folding:
106
title: Config
@@ -288,3 +312,29 @@ modules:
312
- name: activating
313
- name: deactivating
314
- name: failed
315
+ - name: unit file
316
+ description: These metrics refer to the systemd unit file.
317
+ labels:
318
+ - name: unit_file_name
319
+ description: systemd unit file name
320
+ - name: unit_file_type
321
+ description: systemd unit file type
322
+ metrics:
323
+ - name: systemd.unit_file_state
324
+ description: Unit File State
325
+ unit: state
326
+ chart_type: line
327
+ dimensions:
328
+ - name: enabled
329
+ - name: enabled-runtime
330
+ - name: linked
331
+ - name: linked-runtime
332
+ - name: alias
333
+ - name: masked
334
+ - name: masked-runtime
335
+ - name: static
336
+ - name: disabled
337
+ - name: indirect
338
+ - name: generated
339
+ - name: transient
340
+ - name: bad
src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits.go
+33
-19
@@ -13,6 +13,8 @@ import (
13
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
14
"github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
15
"github.com/netdata/netdata/go/go.d.plugin/pkg/web"
16
+
17
+ "github.com/coreos/go-systemd/v22/dbus"
18
)
19
20
//go:embed "config_schema.json"
@@ -31,22 +33,26 @@ func init() {
33
func New() *SystemdUnits {
34
return &SystemdUnits{
35
Config: Config{
34
- Timeout: web.Duration(time.Second * 2),
35
- Include: []string{
36
- "*.service",
37
- },
36
+ Timeout: web.Duration(time.Second * 2),
37
+ Include: []string{"*.service"},
38
+ CollectUnitFiles: false,
39
+ IncludeUnitFiles: []string{"*.service"},
40
+ CollectUnitFilesEvery: web.Duration(time.Minute * 5),
41
},
39
-
40
- charts: &module.Charts{},
41
- client: newSystemdDBusClient(),
42
- units: make(map[string]bool),
42
+ charts: &module.Charts{},
43
+ client: newSystemdDBusClient(),
44
+ seenUnits: make(map[string]bool),
45
+ seenUnitFiles: make(map[string]bool),
46
}
47
}
48
49
type Config struct {
47
- UpdateEvery int `yaml:"update_every" json:"update_every"`
48
- Timeout web.Duration `yaml:"timeout" json:"timeout"`
49
- Include []string `yaml:"include" json:"include"`
50
+ UpdateEvery int `yaml:"update_every" json:"update_every"`
51
+ Timeout web.Duration `yaml:"timeout" json:"timeout"`
52
+ Include []string `yaml:"include" json:"include"`
53
+ CollectUnitFiles bool `yaml:"collect_unit_files" json:"collect_unit_files"`
54
+ IncludeUnitFiles []string `yaml:"include_unit_files" json:"include_unit_files"`
55
+ CollectUnitFilesEvery web.Duration `yaml:"collect_unit_files_every" json:"collect_unit_files_every"`
56
}
57
58
type SystemdUnits struct {
@@ -57,8 +63,13 @@ type SystemdUnits struct {
63
conn systemdConnection
64
65
systemdVersion int
60
- units map[string]bool
61
- sr matcher.Matcher
66
+
67
+ seenUnits map[string]bool
68
+ unitSr matcher.Matcher
69
+
70
+ lastListUnitFilesTime time.Time
71
+ cachedUnitFiles []dbus.UnitFile
72
+ seenUnitFiles map[string]bool
73
74
charts *module.Charts
75
}
@@ -68,21 +79,22 @@ func (s *SystemdUnits) Configuration() any {
79
}
80
81
func (s *SystemdUnits) Init() error {
71
- err := s.validateConfig()
72
- if err != nil {
82
+ if err := s.validateConfig(); err != nil {
83
s.Errorf("config validation: %v", err)
84
return err
85
}
86
77
- sr, err := s.initSelector()
87
+ sr, err := s.initUnitSelector()
88
if err != nil {
79
- s.Errorf("init selector: %v", err)
89
+ s.Errorf("init unit selector: %v", err)
90
return err
91
}
82
- s.sr = sr
92
+ s.unitSr = sr
93
84
- s.Debugf("unit names patterns: %v", s.Include)
94
s.Debugf("timeout: %s", s.Timeout)
95
+ s.Debugf("units: patterns '%v'", s.Include)
96
+ s.Debugf("unit files: enabled '%v', every '%s', patterns: %v",
97
+ s.CollectUnitFiles, s.CollectUnitFilesEvery, s.IncludeUnitFiles)
98
99
return nil
100
}
@@ -93,9 +105,11 @@ func (s *SystemdUnits) Check() error {
105
s.Error(err)
106
return err
107
}
108
+
109
if len(mx) == 0 {
110
return errors.New("no metrics collected")
111
}
112
+
113
return nil
114
}
115
src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits_test.go
+184
-24
@@ -11,6 +11,7 @@ import (
11
"fmt"
12
"os"
13
"path/filepath"
14
+ "slices"
15
"testing"
16
17
"github.com/netdata/netdata/go/go.d.plugin/agent/module"
@@ -168,7 +169,7 @@ func TestSystemdUnits_Collect(t *testing.T) {
169
prepare func() *SystemdUnits
170
wantCollected map[string]int64
171
}{
171
- "success on systemd v230+ on collecting all unit type": {
172
+ "success v230+ on collecting all unit type": {
173
prepare: func() *SystemdUnits {
174
systemd := New()
175
systemd.Include = []string{"*"}
@@ -383,7 +384,7 @@ func TestSystemdUnits_Collect(t *testing.T) {
384
"unit_var-lib-nfs-rpc_pipefs_mount_state_inactive": 1,
385
},
386
},
386
- "success on systemd v230- on collecting all unit types": {
387
+ "success v230- on collecting all unit types": {
388
prepare: func() *SystemdUnits {
389
systemd := New()
390
systemd.Include = []string{"*"}
@@ -598,7 +599,7 @@ func TestSystemdUnits_Collect(t *testing.T) {
599
"unit_var-lib-nfs-rpc_pipefs_mount_state_inactive": 1,
600
},
601
},
601
- "success on systemd v230+ on collecting only 'service' unit type": {
602
+ "success v230+ on collecting only 'service' units": {
603
prepare: func() *SystemdUnits {
604
systemd := New()
605
systemd.Include = []string{"*.service"}
@@ -628,7 +629,7 @@ func TestSystemdUnits_Collect(t *testing.T) {
629
"unit_user@1000_service_state_inactive": 0,
630
},
631
},
631
- "success on systemd v230- on collecting only 'service' unit type": {
632
+ "success v230- on collecting only 'service' units": {
633
prepare: func() *SystemdUnits {
634
systemd := New()
635
systemd.Include = []string{"*.service"}
@@ -658,6 +659,89 @@ func TestSystemdUnits_Collect(t *testing.T) {
659
"unit_user@1000_service_state_inactive": 0,
660
},
661
},
662
+ "success v230+ on collecting only 'service' units and files": {
663
+ prepare: func() *SystemdUnits {
664
+ systemd := New()
665
+ systemd.Include = []string{"*.service"}
666
+ systemd.CollectUnitFiles = true
667
+ systemd.client = prepareOKClient(230)
668
+ return systemd
669
+ },
670
+ wantCollected: map[string]int64{
671
+ "unit_file_/lib/systemd/system/uuidd.service_state_alias": 0,
672
+ "unit_file_/lib/systemd/system/uuidd.service_state_bad": 0,
673
+ "unit_file_/lib/systemd/system/uuidd.service_state_disabled": 0,
674
+ "unit_file_/lib/systemd/system/uuidd.service_state_enabled": 0,
675
+ "unit_file_/lib/systemd/system/uuidd.service_state_enabled-runtime": 0,
676
+ "unit_file_/lib/systemd/system/uuidd.service_state_generated": 0,
677
+ "unit_file_/lib/systemd/system/uuidd.service_state_indirect": 1,
678
+ "unit_file_/lib/systemd/system/uuidd.service_state_linked": 0,
679
+ "unit_file_/lib/systemd/system/uuidd.service_state_linked-runtime": 0,
680
+ "unit_file_/lib/systemd/system/uuidd.service_state_masked": 0,
681
+ "unit_file_/lib/systemd/system/uuidd.service_state_masked-runtime": 0,
682
+ "unit_file_/lib/systemd/system/uuidd.service_state_static": 0,
683
+ "unit_file_/lib/systemd/system/uuidd.service_state_transient": 0,
684
+ "unit_file_/lib/systemd/system/x11-common.service_state_alias": 0,
685
+ "unit_file_/lib/systemd/system/x11-common.service_state_bad": 0,
686
+ "unit_file_/lib/systemd/system/x11-common.service_state_disabled": 0,
687
+ "unit_file_/lib/systemd/system/x11-common.service_state_enabled": 0,
688
+ "unit_file_/lib/systemd/system/x11-common.service_state_enabled-runtime": 0,
689
+ "unit_file_/lib/systemd/system/x11-common.service_state_generated": 0,
690
+ "unit_file_/lib/systemd/system/x11-common.service_state_indirect": 0,
691
+ "unit_file_/lib/systemd/system/x11-common.service_state_linked": 0,
692
+ "unit_file_/lib/systemd/system/x11-common.service_state_linked-runtime": 0,
693
+ "unit_file_/lib/systemd/system/x11-common.service_state_masked": 1,
694
+ "unit_file_/lib/systemd/system/x11-common.service_state_masked-runtime": 0,
695
+ "unit_file_/lib/systemd/system/x11-common.service_state_static": 0,
696
+ "unit_file_/lib/systemd/system/x11-common.service_state_transient": 0,
697
+ "unit_file_/run/systemd/generator.late/monit.service_state_alias": 0,
698
+ "unit_file_/run/systemd/generator.late/monit.service_state_bad": 0,
699
+ "unit_file_/run/systemd/generator.late/monit.service_state_disabled": 0,
700
+ "unit_file_/run/systemd/generator.late/monit.service_state_enabled": 0,
701
+ "unit_file_/run/systemd/generator.late/monit.service_state_enabled-runtime": 0,
702
+ "unit_file_/run/systemd/generator.late/monit.service_state_generated": 1,
703
+ "unit_file_/run/systemd/generator.late/monit.service_state_indirect": 0,
704
+ "unit_file_/run/systemd/generator.late/monit.service_state_linked": 0,
705
+ "unit_file_/run/systemd/generator.late/monit.service_state_linked-runtime": 0,
706
+ "unit_file_/run/systemd/generator.late/monit.service_state_masked": 0,
707
+ "unit_file_/run/systemd/generator.late/monit.service_state_masked-runtime": 0,
708
+ "unit_file_/run/systemd/generator.late/monit.service_state_static": 0,
709
+ "unit_file_/run/systemd/generator.late/monit.service_state_transient": 0,
710
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_alias": 0,
711
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_bad": 0,
712
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_disabled": 0,
713
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_enabled": 0,
714
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_enabled-runtime": 0,
715
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_generated": 1,
716
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_indirect": 0,
717
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_linked": 0,
718
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_linked-runtime": 0,
719
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_masked": 0,
720
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_masked-runtime": 0,
721
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_static": 0,
722
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_transient": 0,
723
+ "unit_systemd-ask-password-wall_service_state_activating": 0,
724
+ "unit_systemd-ask-password-wall_service_state_active": 0,
725
+ "unit_systemd-ask-password-wall_service_state_deactivating": 0,
726
+ "unit_systemd-ask-password-wall_service_state_failed": 0,
727
+ "unit_systemd-ask-password-wall_service_state_inactive": 1,
728
+ "unit_systemd-fsck-root_service_state_activating": 0,
729
+ "unit_systemd-fsck-root_service_state_active": 0,
730
+ "unit_systemd-fsck-root_service_state_deactivating": 0,
731
+ "unit_systemd-fsck-root_service_state_failed": 0,
732
+ "unit_systemd-fsck-root_service_state_inactive": 1,
733
+ "unit_user-runtime-dir@1000_service_state_activating": 0,
734
+ "unit_user-runtime-dir@1000_service_state_active": 1,
735
+ "unit_user-runtime-dir@1000_service_state_deactivating": 0,
736
+ "unit_user-runtime-dir@1000_service_state_failed": 0,
737
+ "unit_user-runtime-dir@1000_service_state_inactive": 0,
738
+ "unit_user@1000_service_state_activating": 0,
739
+ "unit_user@1000_service_state_active": 1,
740
+ "unit_user@1000_service_state_deactivating": 0,
741
+ "unit_user@1000_service_state_failed": 0,
742
+ "unit_user@1000_service_state_inactive": 0,
743
+ },
744
+ },
745
"fails when all unites are filtered": {
746
prepare: func() *SystemdUnits {
747
systemd := New()
@@ -698,15 +782,15 @@ func TestSystemdUnits_Collect(t *testing.T) {
782
systemd := test.prepare()
783
require.NoError(t, systemd.Init())
784
701
- var collected map[string]int64
785
+ var mx map[string]int64
786
787
for i := 0; i < 10; i++ {
704
- collected = systemd.Collect()
788
+ mx = systemd.Collect()
789
}
790
707
- assert.Equal(t, test.wantCollected, collected)
791
+ assert.Equal(t, test.wantCollected, mx)
792
if len(test.wantCollected) > 0 {
709
- ensureCollectedHasAllChartsDimsVarsIDs(t, systemd, collected)
793
+ ensureCollectedHasAllChartsDimsVarsIDs(t, systemd, mx)
794
}
795
})
796
}
@@ -747,8 +831,9 @@ func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, sd *SystemdUnits, coll
831
func prepareOKClient(ver int) *mockClient {
832
return &mockClient{
833
conn: &mockConn{
750
- version: ver,
751
- units: mockSystemdUnits,
834
+ version: ver,
835
+ units: mockSystemdUnits,
836
+ unitFiles: mockSystemdUnitFiles,
837
},
838
}
839
}
@@ -795,10 +880,15 @@ func (m *mockClient) connect() (systemdConnection, error) {
880
881
type mockConn struct {
882
version int
798
- units []dbus.UnitStatus
883
errOnGetManagerProperty bool
800
- errOnListUnits bool
801
- closeCalled bool
884
+
885
+ units []dbus.UnitStatus
886
+ errOnListUnits bool
887
+
888
+ unitFiles []dbus.UnitFile
889
+ errOnListUnitFiles bool
890
+
891
+ closeCalled bool
892
}
893
894
func (m *mockConn) Close() {
@@ -812,6 +902,7 @@ func (m *mockConn) GetManagerProperty(prop string) (string, error) {
902
if prop != versionProperty {
903
return "", fmt.Errorf("'GetManagerProperty' unkown property: %s", prop)
904
}
905
+
906
return fmt.Sprintf("%d.6-1-manjaro", m.version), nil
907
}
908
@@ -822,10 +913,11 @@ func (m *mockConn) ListUnitsContext(_ context.Context) ([]dbus.UnitStatus, error
913
if m.version >= 230 {
914
return nil, errors.New("'ListUnits' unsupported function error")
915
}
916
+
917
return append([]dbus.UnitStatus{}, m.units...), nil
918
}
919
828
-func (m *mockConn) ListUnitsByPatternsContext(_ context.Context, _ []string, ps []string) ([]dbus.UnitStatus, error) {
920
+func (m *mockConn) ListUnitsByPatternsContext(_ context.Context, _ []string, patterns []string) ([]dbus.UnitStatus, error) {
921
if m.errOnListUnits {
922
return nil, errors.New("'ListUnitsByPatterns' call error")
923
}
@@ -833,22 +925,50 @@ func (m *mockConn) ListUnitsByPatternsContext(_ context.Context, _ []string, ps
925
return nil, errors.New("'ListUnitsByPatterns' unsupported function error")
926
}
927
836
- matches := func(name string) bool {
837
- for _, p := range ps {
928
+ if len(m.units) == 0 {
929
+ return nil, nil
930
+ }
931
+
932
+ units := append([]dbus.UnitStatus{}, m.units...)
933
+
934
+ units = slices.DeleteFunc(units, func(u dbus.UnitStatus) bool {
935
+ name := cleanUnitName(u.Name)
936
+ for _, p := range patterns {
937
if ok, _ := filepath.Match(p, name); ok {
839
- return true
938
+ return false
939
}
940
}
842
- return false
941
+ return true
942
+ })
943
+
944
+ return units, nil
945
+}
946
+
947
+func (m *mockConn) ListUnitFilesByPatternsContext(_ context.Context, _ []string, patterns []string) ([]dbus.UnitFile, error) {
948
+ if m.errOnListUnitFiles {
949
+ return nil, errors.New("'ListUnitFilesByPatternsContex' call error")
950
+ }
951
+ if m.version < 230 {
952
+ return nil, errors.New("'ListUnitFilesByPatternsContex' unsupported function error")
953
}
954
845
- var units []dbus.UnitStatus
846
- for _, unit := range m.units {
847
- if matches(unit.Name) {
848
- units = append(units, unit)
849
- }
955
+ if len(m.unitFiles) == 0 {
956
+ return nil, nil
957
}
851
- return units, nil
958
+
959
+ unitFiles := append([]dbus.UnitFile{}, m.unitFiles...)
960
+
961
+ unitFiles = slices.DeleteFunc(unitFiles, func(file dbus.UnitFile) bool {
962
+ _, name := filepath.Split(file.Path)
963
+ for _, p := range patterns {
964
+ if ok, _ := filepath.Match(p, name); ok {
965
+ return false
966
+ }
967
+ }
968
+ return true
969
+ })
970
+
971
+ return unitFiles, nil
972
}
973
974
var mockSystemdUnits = []dbus.UnitStatus{
@@ -904,3 +1024,43 @@ var mockSystemdUnits = []dbus.UnitStatus{
1024
{Name: `shadow.timer`, LoadState: "loaded", ActiveState: "active"},
1025
{Name: `logrotate.timer`, LoadState: "loaded", ActiveState: "active"},
1026
}
1027
+
1028
+var mockSystemdUnitFiles = []dbus.UnitFile{
1029
+ {Path: "/lib/systemd/system/systemd-tmpfiles-clean.timer", Type: "static"},
1030
+ {Path: "/lib/systemd/system/sysstat-summary.timer", Type: "disabled"},
1031
+ {Path: "/lib/systemd/system/sysstat-collect.timer", Type: "disabled"},
1032
+ {Path: "/lib/systemd/system/pg_dump@.timer", Type: "disabled"},
1033
+
1034
+ {Path: "/lib/systemd/system/veritysetup.target", Type: "static"},
1035
+ {Path: "/lib/systemd/system/veritysetup-pre.target", Type: "static"},
1036
+ {Path: "/lib/systemd/system/usb-gadget.target", Type: "static"},
1037
+ {Path: "/lib/systemd/system/umount.target", Type: "static"},
1038
+
1039
+ {Path: "/lib/systemd/system/syslog.socket", Type: "static"},
1040
+ {Path: "/lib/systemd/system/ssh.socket", Type: "disabled"},
1041
+ {Path: "/lib/systemd/system/docker.socket", Type: "enabled"},
1042
+ {Path: "/lib/systemd/system/dbus.socket", Type: "static"},
1043
+
1044
+ {Path: "/lib/systemd/system/user.slice", Type: "static"},
1045
+ {Path: "/lib/systemd/system/system-systemd\x2dcryptsetup.slice", Type: "static"},
1046
+ {Path: "/lib/systemd/system/machine.slice", Type: "static"},
1047
+
1048
+ {Path: "/run/systemd/generator.late/sendmail.service", Type: "generated"},
1049
+ {Path: "/run/systemd/generator.late/monit.service", Type: "generated"},
1050
+ {Path: "/lib/systemd/system/x11-common.service", Type: "masked"},
1051
+ {Path: "/lib/systemd/system/uuidd.service", Type: "indirect"},
1052
+
1053
+ {Path: "/run/systemd/transient/session-144.scope", Type: "transient"},
1054
+ {Path: "/run/systemd/transient/session-139.scope", Type: "transient"},
1055
+ {Path: "/run/systemd/transient/session-132.scope", Type: "transient"},
1056
+
1057
+ {Path: "/lib/systemd/system/systemd-ask-password-wall.path", Type: "static"},
1058
+ {Path: "/lib/systemd/system/systemd-ask-password-console.path", Type: "static"},
1059
+ {Path: "/lib/systemd/system/postfix-resolvconf.path", Type: "disabled"},
1060
+ {Path: "/lib/systemd/system/ntpsec-systemd-netif.path", Type: "enabled"},
1061
+
1062
+ {Path: "/run/systemd/generator/media-cdrom0.mount", Type: "generated"},
1063
+ {Path: "/run/systemd/generator/boot.mount", Type: "generated"},
1064
+ {Path: "/run/systemd/generator/-.mount", Type: "generated"},
1065
+ {Path: "/lib/systemd/system/sys-kernel-tracing.mount", Type: "static"},
1066
+}
src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.json
+5
@@ -3,5 +3,10 @@
3
"timeout": 123.123,
4
"include": [
5
"ok"
6
+ ],
7
+ "collect_unit_files": true,
8
+ "collect_unit_files_every": 123.123,
9
+ "include_unit_files": [
10
+ "ok"
11
]
12
}
src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.yaml
+5
-1
@@ -1,4 +1,8 @@
1
update_every: 123
2
timeout: 123.123
3
include:
4
- - "ok"
4
+ - ok
5
+collect_unit_files: true
6
+collect_unit_files_every: 123.123
7
+include_unit_files:
8
+ - ok