summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/systemdunits
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/go/collectors/go.d.plugin/modules/systemdunits/README.md1
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/charts.go118
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/client.go34
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/collect.go88
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/collect_unit_files.go94
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/collect_units.go187
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/config_schema.json122
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/doc.go4
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/init.go29
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/integrations/systemd_units.md289
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml344
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits.go139
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits_test.go1156
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.json13
-rw-r--r--src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.yaml9
15 files changed, 2627 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/README.md b/src/go/collectors/go.d.plugin/modules/systemdunits/README.md
new file mode 120000
index 000000000..68dd433bf
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/README.md
@@ -0,0 +1 @@
+integrations/systemd_units.md \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/charts.go b/src/go/collectors/go.d.plugin/modules/systemdunits/charts.go
new file mode 100644
index 000000000..18d8838fb
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/charts.go
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+const (
+ prioUnitState = module.Priority + iota
+ prioUnitFileState
+)
+
+func (s *SystemdUnits) addUnitCharts(name, typ string) {
+ chart := module.Chart{
+ ID: "unit_%s_%s_state",
+ Title: "%s Unit State",
+ Units: "state",
+ Fam: "%s units",
+ Ctx: "systemd.%s_unit_state",
+ Priority: prioUnitState,
+ Labels: []module.Label{
+ {Key: "unit_name", Value: name},
+ },
+ Dims: module.Dims{
+ {ID: "unit_%s_%s_state_%s", Name: unitStateActive},
+ {ID: "unit_%s_%s_state_%s", Name: unitStateInactive},
+ {ID: "unit_%s_%s_state_%s", Name: unitStateActivating},
+ {ID: "unit_%s_%s_state_%s", Name: unitStateDeactivating},
+ {ID: "unit_%s_%s_state_%s", Name: unitStateFailed},
+ },
+ }
+
+ chart.ID = fmt.Sprintf(chart.ID, name, typ)
+ chart.Title = fmt.Sprintf(chart.Title, cases.Title(language.English, cases.Compact).String(typ))
+ chart.Fam = fmt.Sprintf(chart.Fam, typ)
+ chart.Ctx = fmt.Sprintf(chart.Ctx, typ)
+
+ for _, d := range chart.Dims {
+ d.ID = fmt.Sprintf(d.ID, name, typ, d.Name)
+ }
+
+ if err := s.Charts().Add(&chart); err != nil {
+ s.Warning(err)
+ }
+}
+
+func (s *SystemdUnits) removeUnitCharts(name, typ string) {
+ px := fmt.Sprintf("unit_%s_%s_", name, typ)
+ s.removeCharts(px)
+}
+
+func (s *SystemdUnits) addUnitFileCharts(unitPath string) {
+ _, unitName := filepath.Split(unitPath)
+ unitType := strings.TrimPrefix(filepath.Ext(unitPath), ".")
+
+ chart := module.Chart{
+ ID: "unit_file_%s_state",
+ Title: "Unit File State",
+ Units: "state",
+ Fam: "unit files",
+ Ctx: "systemd.unit_file_state",
+ Type: module.Line,
+ Priority: prioUnitFileState,
+ Labels: []module.Label{
+ {Key: "unit_file_name", Value: unitName},
+ {Key: "unit_file_type", Value: unitType},
+ },
+ Dims: module.Dims{
+ {ID: "unit_file_%s_state_enabled", Name: "enabled"},
+ {ID: "unit_file_%s_state_enabled-runtime", Name: "enabled-runtime"},
+ {ID: "unit_file_%s_state_linked", Name: "linked"},
+ {ID: "unit_file_%s_state_linked-runtime", Name: "linked-runtime"},
+ {ID: "unit_file_%s_state_alias", Name: "alias"},
+ {ID: "unit_file_%s_state_masked", Name: "masked"},
+ {ID: "unit_file_%s_state_masked-runtime", Name: "masked-runtime"},
+ {ID: "unit_file_%s_state_static", Name: "static"},
+ {ID: "unit_file_%s_state_disabled", Name: "disabled"},
+ {ID: "unit_file_%s_state_indirect", Name: "indirect"},
+ {ID: "unit_file_%s_state_generated", Name: "generated"},
+ {ID: "unit_file_%s_state_transient", Name: "transient"},
+ {ID: "unit_file_%s_state_bad", Name: "bad"},
+ },
+ }
+
+ chart.ID = fmt.Sprintf(chart.ID, strings.ReplaceAll(unitPath, ".", "_"))
+ for _, dim := range chart.Dims {
+ dim.ID = fmt.Sprintf(dim.ID, unitPath)
+ }
+
+ if err := s.Charts().Add(&chart); err != nil {
+ s.Warning(err)
+ }
+}
+
+func (s *SystemdUnits) removeUnitFileCharts(unitPath string) {
+ px := fmt.Sprintf("unit_file_%s_", strings.ReplaceAll(unitPath, ".", "_"))
+ s.removeCharts(px)
+}
+
+func (s *SystemdUnits) removeCharts(prefix string) {
+ for _, chart := range *s.Charts() {
+ if strings.HasPrefix(chart.ID, prefix) {
+ chart.MarkRemove()
+ chart.MarkNotCreated()
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/client.go b/src/go/collectors/go.d.plugin/modules/systemdunits/client.go
new file mode 100644
index 000000000..e6363d132
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/client.go
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ "context"
+
+ "github.com/coreos/go-systemd/v22/dbus"
+)
+
+type systemdClient interface {
+ connect() (systemdConnection, error)
+}
+type systemdConnection interface {
+ Close()
+ GetManagerProperty(string) (string, error)
+ GetUnitPropertyContext(ctx context.Context, unit string, propertyName string) (*dbus.Property, error)
+ ListUnitsContext(ctx context.Context) ([]dbus.UnitStatus, error)
+ ListUnitsByPatternsContext(ctx context.Context, states []string, patterns []string) ([]dbus.UnitStatus, error)
+ ListUnitFilesByPatternsContext(ctx context.Context, states []string, patterns []string) ([]dbus.UnitFile, error)
+}
+
+type systemdDBusClient struct{}
+
+func (systemdDBusClient) connect() (systemdConnection, error) {
+ return dbus.NewWithContext(context.Background())
+}
+
+func newSystemdDBusClient() *systemdDBusClient {
+ return &systemdDBusClient{}
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/collect.go b/src/go/collectors/go.d.plugin/modules/systemdunits/collect.go
new file mode 100644
index 000000000..0d61c9998
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/collect.go
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ "fmt"
+ "regexp"
+ "strconv"
+)
+
+func (s *SystemdUnits) collect() (map[string]int64, error) {
+ conn, err := s.getConnection()
+ if err != nil {
+ return nil, err
+ }
+
+ if s.systemdVersion == 0 {
+ ver, err := s.getSystemdVersion(conn)
+ if err != nil {
+ s.closeConnection()
+ return nil, err
+ }
+ s.systemdVersion = ver
+ }
+
+ mx := make(map[string]int64)
+
+ if err := s.collectUnits(mx, conn); err != nil {
+ s.closeConnection()
+ return nil, err
+ }
+
+ if s.CollectUnitFiles && len(s.IncludeUnitFiles) > 0 {
+ if err := s.collectUnitFiles(mx, conn); err != nil {
+ s.closeConnection()
+ return mx, err
+ }
+ }
+
+ return mx, nil
+}
+
+func (s *SystemdUnits) getConnection() (systemdConnection, error) {
+ if s.conn == nil {
+ conn, err := s.client.connect()
+ if err != nil {
+ return nil, fmt.Errorf("error on creating a connection: %v", err)
+ }
+ s.conn = conn
+ }
+ return s.conn, nil
+}
+
+func (s *SystemdUnits) closeConnection() {
+ if s.conn != nil {
+ s.conn.Close()
+ s.conn = nil
+ }
+}
+
+var reVersion = regexp.MustCompile(`[0-9][0-9][0-9]`)
+
+const versionProperty = "Version"
+
+func (s *SystemdUnits) getSystemdVersion(conn systemdConnection) (int, error) {
+ s.Debugf("calling function 'GetManagerProperty'")
+ version, err := conn.GetManagerProperty(versionProperty)
+ if err != nil {
+ return 0, fmt.Errorf("error on getting '%s' manager property: %v", versionProperty, err)
+ }
+
+ s.Debugf("systemd version: %s", version)
+
+ major := reVersion.FindString(version)
+ if major == "" {
+ return 0, fmt.Errorf("couldn't parse systemd version string '%s'", version)
+ }
+
+ ver, err := strconv.Atoi(major)
+ if err != nil {
+ return 0, fmt.Errorf("couldn't parse systemd version string '%s': %v", version, err)
+ }
+
+ return ver, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/collect_unit_files.go b/src/go/collectors/go.d.plugin/modules/systemdunits/collect_unit_files.go
new file mode 100644
index 000000000..eff2d6ecb
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/collect_unit_files.go
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/coreos/go-systemd/v22/dbus"
+)
+
+// https://github.com/systemd/systemd/blob/3d320785c4bbba74459096b07e85a79c4f0cdffb/src/shared/install.c#L3785
+// see "is-enabled" in https://www.man7.org/linux/man-pages/man1/systemctl.1.html
+var unitFileStates = []string{
+ "enabled",
+ "enabled-runtime",
+ "linked",
+ "linked-runtime",
+ "alias",
+ "masked",
+ "masked-runtime",
+ "static",
+ "disabled",
+ "indirect",
+ "generated",
+ "transient",
+ "bad",
+}
+
+func (s *SystemdUnits) collectUnitFiles(mx map[string]int64, conn systemdConnection) error {
+ if s.systemdVersion < 230 {
+ return nil
+ }
+
+ if now := time.Now(); now.After(s.lastListUnitFilesTime.Add(s.CollectUnitFilesEvery.Duration())) {
+ unitFiles, err := s.getUnitFilesByPatterns(conn)
+ if err != nil {
+ return err
+ }
+ s.lastListUnitFilesTime = now
+ s.cachedUnitFiles = unitFiles
+ }
+
+ seen := make(map[string]bool)
+
+ for _, unitFile := range s.cachedUnitFiles {
+ seen[unitFile.Path] = true
+
+ if !s.seenUnitFiles[unitFile.Path] {
+ s.seenUnitFiles[unitFile.Path] = true
+ s.addUnitFileCharts(unitFile.Path)
+ }
+
+ px := fmt.Sprintf("unit_file_%s_state_", unitFile.Path)
+ for _, st := range unitFileStates {
+ mx[px+st] = 0
+ }
+ mx[px+strings.ToLower(unitFile.Type)] = 1
+ }
+
+ for k := range s.seenUnitFiles {
+ if !seen[k] {
+ delete(s.seenUnitFiles, k)
+ s.removeUnitFileCharts(k)
+ }
+ }
+
+ return nil
+}
+
+func (s *SystemdUnits) getUnitFilesByPatterns(conn systemdConnection) ([]dbus.UnitFile, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
+ defer cancel()
+
+ s.Debugf("calling function 'ListUnitFilesByPatterns'")
+
+ unitFiles, err := conn.ListUnitFilesByPatternsContext(ctx, nil, s.IncludeUnitFiles)
+ if err != nil {
+ return nil, fmt.Errorf("error on ListUnitFilesByPatterns: %v", err)
+ }
+
+ for i := range unitFiles {
+ unitFiles[i].Path = cleanUnitName(unitFiles[i].Path)
+ }
+
+ s.Debugf("got %d unit files", len(unitFiles))
+
+ return unitFiles, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/collect_units.go b/src/go/collectors/go.d.plugin/modules/systemdunits/collect_units.go
new file mode 100644
index 000000000..0cf97af03
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/collect_units.go
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/coreos/go-systemd/v22/dbus"
+)
+
+const transientProperty = "Transient"
+
+const (
+ // https://www.freedesktop.org/software/systemd/man/systemd.html
+ unitStateActive = "active"
+ unitStateInactive = "inactive"
+ unitStateActivating = "activating"
+ unitStateDeactivating = "deactivating"
+ unitStateFailed = "failed"
+)
+
+var unitStates = []string{
+ unitStateActive,
+ unitStateActivating,
+ unitStateFailed,
+ unitStateInactive,
+ unitStateDeactivating,
+}
+
+func (s *SystemdUnits) collectUnits(mx map[string]int64, conn systemdConnection) error {
+ var units []dbus.UnitStatus
+ var err error
+
+ if s.systemdVersion >= 230 {
+ // https://github.com/systemd/systemd/pull/3142
+ units, err = s.getLoadedUnitsByPatterns(conn)
+ } else {
+ units, err = s.getLoadedUnits(conn)
+ }
+ if err != nil {
+ return err
+ }
+
+ seen := make(map[string]bool)
+
+ for _, unit := range units {
+ name, typ, ok := extractUnitNameType(unit.Name)
+ if !ok {
+ continue
+ }
+
+ seen[unit.Name] = true
+
+ if s.SkipTransient {
+ if _, ok := s.unitTransient[unit.Name]; !ok {
+ prop, err := s.getUnitTransientProperty(conn, unit.Name)
+ if err != nil {
+ return err
+ }
+ prop = strings.Trim(prop, "\"")
+ s.unitTransient[unit.Name] = prop == "true"
+ }
+ if s.unitTransient[unit.Name] {
+ continue
+ }
+ }
+
+ if !s.seenUnits[unit.Name] {
+ s.seenUnits[unit.Name] = true
+ s.addUnitCharts(name, typ)
+ }
+
+ for _, s := range unitStates {
+ mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, s)] = 0
+ }
+ mx[fmt.Sprintf("unit_%s_%s_state_%s", name, typ, unit.ActiveState)] = 1
+ }
+
+ for k := range s.seenUnits {
+ if !seen[k] {
+ delete(s.seenUnits, k)
+ if name, typ, ok := extractUnitNameType(k); ok {
+ s.removeUnitCharts(name, typ)
+ }
+ }
+ }
+
+ for k := range s.unitTransient {
+ if !seen[k] {
+ delete(s.unitTransient, k)
+ }
+ }
+
+ return nil
+}
+
+func (s *SystemdUnits) getLoadedUnits(conn systemdConnection) ([]dbus.UnitStatus, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
+ defer cancel()
+
+ s.Debugf("calling function 'ListUnits'")
+ units, err := conn.ListUnitsContext(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("error on ListUnits: %v", err)
+ }
+
+ for i := range units {
+ units[i].Name = cleanUnitName(units[i].Name)
+ }
+
+ loaded := units[:0]
+ for _, unit := range units {
+ if unit.LoadState == "loaded" && s.unitSr.MatchString(unit.Name) {
+ loaded = append(loaded, unit)
+ }
+ }
+
+ s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
+
+ return loaded, nil
+}
+
+func (s *SystemdUnits) getLoadedUnitsByPatterns(conn systemdConnection) ([]dbus.UnitStatus, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
+ defer cancel()
+
+ s.Debugf("calling function 'ListUnitsByPatterns'")
+
+ units, err := conn.ListUnitsByPatternsContext(ctx, unitStates, s.Include)
+ if err != nil {
+ return nil, fmt.Errorf("error on ListUnitsByPatterns: %v", err)
+ }
+
+ for i := range units {
+ units[i].Name = cleanUnitName(units[i].Name)
+ }
+
+ loaded := units[:0]
+ for _, unit := range units {
+ if unit.LoadState == "loaded" {
+ loaded = append(loaded, unit)
+ }
+ }
+ s.Debugf("got total/loaded %d/%d units", len(units), len(loaded))
+
+ return loaded, nil
+}
+
+func (s *SystemdUnits) getUnitTransientProperty(conn systemdConnection, unit string) (string, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), s.Timeout.Duration())
+ defer cancel()
+
+ s.Debugf("calling function 'GetUnitProperty' for unit '%s'", unit)
+
+ prop, err := conn.GetUnitPropertyContext(ctx, unit, transientProperty)
+ if err != nil {
+ return "", fmt.Errorf("error on GetUnitProperty: %v", err)
+ }
+
+ return prop.Value.String(), nil
+}
+
+func extractUnitNameType(name string) (string, string, bool) {
+ idx := strings.LastIndexByte(name, '.')
+ if idx <= 0 {
+ return "", "", false
+ }
+ return name[:idx], name[idx+1:], true
+}
+
+func cleanUnitName(name string) string {
+ // dev-disk-by\x2duuid-DE44\x2dCEE0.device => dev-disk-by-uuid-DE44-CEE0.device
+ if strings.IndexByte(name, '\\') == -1 {
+ return name
+ }
+ v, err := strconv.Unquote("\"" + name + "\"")
+ if err != nil {
+ return name
+ }
+ return v
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/config_schema.json b/src/go/collectors/go.d.plugin/modules/systemdunits/config_schema.json
new file mode 100644
index 000000000..016e984ce
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/config_schema.json
@@ -0,0 +1,122 @@
+{
+ "jsonSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Systemdunits 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": "The timeout, in seconds, for connecting and querying systemd's D-Bus endpoint.",
+ "type": "number",
+ "minimum": 0.5,
+ "default": 2
+ },
+ "skip_transient": {
+ "title": "Skip transient units",
+ "description": "If set, skip data collection for systemd transient units.",
+ "type": "boolean",
+ "default": false
+ },
+ "include": {
+ "title": "Include",
+ "description": "Configuration for monitoring specific systemd units. Include systemd units whose names match any of the specified [patterns](https://golang.org/pkg/path/filepath/#Match).",
+ "type": [
+ "array",
+ "null"
+ ],
+ "uniqueItems": true,
+ "minItems": 1,
+ "items": {
+ "title": "Unit pattern",
+ "type": "string"
+ },
+ "default": [
+ "*.service"
+ ]
+ },
+ "collect_unit_files": {
+ "title": "Collect unit files",
+ "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.",
+ "type": "boolean",
+ "default": false
+ },
+ "collect_unit_files_every": {
+ "title": "Unit files polling interval",
+ "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.",
+ "type": "number",
+ "minimum": 1,
+ "default": 300
+ },
+ "include_unit_files": {
+ "title": "Include unit files",
+ "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).",
+ "type": [
+ "array",
+ "null"
+ ],
+ "uniqueItems": true,
+ "minItems": 1,
+ "items": {
+ "title": "Unit file name pattern",
+ "type": "string"
+ },
+ "default": [
+ "*.service"
+ ]
+ }
+ },
+ "required": [
+ "include"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^name$": {}
+ }
+ },
+ "uiSchema": {
+ "uiOptions": {
+ "fullPage": true
+ },
+ "ui:flavour": "tabs",
+ "ui:options": {
+ "tabs": [
+ {
+ "title": "Base",
+ "fields": [
+ "update_every",
+ "timeout",
+ "skip_transient",
+ "include"
+ ]
+ },
+ {
+ "title": "Unit Files",
+ "fields": [
+ "collect_unit_files",
+ "collect_unit_files_every",
+ "include_unit_files"
+ ]
+ }
+ ]
+ },
+ "timeout": {
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
+ },
+ "skip_transient": {
+ "ui:help": "A systemd transient unit is a temporary unit created on-the-fly, typically used for ad-hoc tasks or testing purposes. They are created using the `systemd-run` command, which allows you to specify unit properties directly on the command line."
+ },
+ "include": {
+ "ui:listFlavour": "list"
+ },
+ "include_unit_files": {
+ "ui:listFlavour": "list"
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/doc.go b/src/go/collectors/go.d.plugin/modules/systemdunits/doc.go
new file mode 100644
index 000000000..8bb45fab9
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/doc.go
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Package systemdunits is a systemd units states collector
+package systemdunits
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/init.go b/src/go/collectors/go.d.plugin/modules/systemdunits/init.go
new file mode 100644
index 000000000..ea3d21d37
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/init.go
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
+)
+
+func (s *SystemdUnits) validateConfig() error {
+ if len(s.Include) == 0 {
+ return errors.New("'include' option not set")
+ }
+ return nil
+}
+
+func (s *SystemdUnits) initUnitSelector() (matcher.Matcher, error) {
+ if len(s.Include) == 0 {
+ return matcher.TRUE(), nil
+ }
+
+ expr := strings.Join(s.Include, " ")
+ return matcher.NewSimplePatternsMatcher(expr)
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/integrations/systemd_units.md b/src/go/collectors/go.d.plugin/modules/systemdunits/integrations/systemd_units.md
new file mode 100644
index 000000000..431f084ba
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/integrations/systemd_units.md
@@ -0,0 +1,289 @@
+<!--startmeta
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/systemdunits/README.md"
+meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml"
+sidebar_label: "Systemd Units"
+learn_status: "Published"
+learn_rel_path: "Collecting Metrics/Systemd"
+most_popular: False
+message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
+endmeta-->
+
+# Systemd Units
+
+
+<img src="https://netdata.cloud/img/systemd.svg" width="150"/>
+
+
+Plugin: go.d.plugin
+Module: systemdunits
+
+<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
+
+## Overview
+
+This collector monitors the state of Systemd units and unit files.
+
+
+
+
+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 unit
+
+These metrics refer to the systemd unit.
+
+Labels:
+
+| Label | Description |
+|:-----------|:----------------|
+| unit_name | systemd unit name |
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| systemd.service_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.socket_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.target_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.path_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.device_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.mount_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.automount_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.swap_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.timer_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.scope_unit_state | active, inactive, activating, deactivating, failed | state |
+| systemd.slice_unit_state | active, inactive, activating, deactivating, failed | state |
+
+### Per unit file
+
+These metrics refer to the systemd unit file.
+
+Labels:
+
+| Label | Description |
+|:-----------|:----------------|
+| unit_file_name | systemd unit file name |
+| unit_file_type | systemd unit file type |
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| systemd.unit_file_state | enabled, enabled-runtime, linked, linked-runtime, alias, masked, masked-runtime, static, disabled, indirect, generated, transient, bad | state |
+
+
+
+## Alerts
+
+
+The following alerts are available:
+
+| Alert name | On metric | Description |
+|:------------|:----------|:------------|
+| [ systemd_service_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.service_unit_state | systemd service unit in the failed state |
+| [ systemd_socket_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.socket_unit_state | systemd socket unit in the failed state |
+| [ systemd_target_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.target_unit_state | systemd target unit in the failed state |
+| [ systemd_path_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.path_unit_state | systemd path unit in the failed state |
+| [ systemd_device_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.device_unit_state | systemd device unit in the failed state |
+| [ systemd_mount_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.mount_unit_state | systemd mount unit in the failed state |
+| [ systemd_automount_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.automount_unit_state | systemd automount unit in the failed state |
+| [ systemd_swap_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.swap_unit_state | systemd swap unit in the failed state |
+| [ systemd_scope_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.scope_unit_state | systemd scope unit in the failed state |
+| [ systemd_slice_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.slice_unit_state | systemd slice unit in the failed state |
+| [ systemd_timer_unit_failed_state ](https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf) | systemd.timer_unit_state | systemd timer unit in the failed state |
+
+
+## Setup
+
+### Prerequisites
+
+No action required.
+
+### Configuration
+
+#### File
+
+The configuration file name for this integration is `go.d/systemdunits.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/systemdunits.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 |
+| timeout | System bus requests timeout. | 1 | no |
+| include | Systemd units selector. | *.service | no |
+| skip_transient | If set, skip data collection for systemd transient units. | false | no |
+| collect_unit_files | If set to true, collect the state of installed unit files. Enabling this may increase system overhead. | false | no |
+| collect_unit_files_every | Interval for querying systemd about unit files and their enablement state, measured in seconds. Data is cached for this interval to reduce system overhead. | 300 | no |
+| include_unit_files | Systemd unit files selector. | *.service | no |
+
+##### include
+
+Systemd units matching the selector will be monitored.
+
+- Logic: (pattern1 OR pattern2)
+- Pattern syntax: [shell file name pattern](https://golang.org/pkg/path/filepath/#Match)
+- Syntax:
+
+```yaml
+includes:
+ - pattern1
+ - pattern2
+```
+
+
+##### include_unit_files
+
+Systemd unit files matching the selector will be monitored.
+
+- Logic: (pattern1 OR pattern2)
+- Pattern syntax: [shell file name pattern](https://golang.org/pkg/path/filepath/#Match)
+- Syntax:
+
+```yaml
+includes:
+ - pattern1
+ - pattern2
+```
+
+
+</details>
+
+#### Examples
+
+##### Service units
+
+Collect state of all service type units.
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: service
+ include:
+ - '*.service'
+
+```
+</details>
+
+##### One specific unit
+
+Collect state of one specific unit.
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: my-specific-service
+ include:
+ - 'my-specific.service'
+
+```
+</details>
+
+##### All unit types
+
+Collect state of all units.
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: my-specific-service-unit
+ include:
+ - '*'
+
+```
+</details>
+
+##### Multi-instance
+
+> **Note**: When you define multiple jobs, their names must be unique.
+
+Collect state of all service and socket type units.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: service
+ include:
+ - '*.service'
+
+ - name: socket
+ include:
+ - '*.socket'
+
+```
+</details>
+
+
+
+## Troubleshooting
+
+### Debug Mode
+
+To troubleshoot issues with the `systemdunits` 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 systemdunits
+ ```
+
+
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml b/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml
new file mode 100644
index 000000000..791e58400
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml
@@ -0,0 +1,344 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-systemdunits
+ plugin_name: go.d.plugin
+ module_name: systemdunits
+ monitored_instance:
+ name: Systemd Units
+ link: https://www.freedesktop.org/wiki/Software/systemd/
+ icon_filename: systemd.svg
+ categories:
+ - data-collection.systemd
+ keywords:
+ - systemd
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ most_popular: false
+ overview:
+ data_collection:
+ metrics_description: |
+ This collector monitors the state of Systemd units and unit files.
+ method_description: ""
+ supported_platforms:
+ include: []
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: ""
+ limits:
+ description: ""
+ performance_impact:
+ description: ""
+ setup:
+ prerequisites:
+ list: []
+ configuration:
+ file:
+ name: go.d/systemdunits.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: timeout
+ description: System bus requests timeout.
+ default_value: 1
+ required: false
+ - name: include
+ description: Systemd units selector.
+ default_value: "*.service"
+ required: false
+ detailed_description: |
+ Systemd units matching the selector will be monitored.
+
+ - Logic: (pattern1 OR pattern2)
+ - Pattern syntax: [shell file name pattern](https://golang.org/pkg/path/filepath/#Match)
+ - Syntax:
+
+ ```yaml
+ includes:
+ - pattern1
+ - pattern2
+ ```
+ - name: skip_transient
+ description: If set, skip data collection for systemd transient units.
+ default_value: "false"
+ required: false
+ - name: collect_unit_files
+ description: If set to true, collect the state of installed unit files. Enabling this may increase system overhead.
+ default_value: "false"
+ required: false
+ - name: collect_unit_files_every
+ 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.
+ default_value: 300
+ required: false
+ - name: include_unit_files
+ description: Systemd unit files selector.
+ default_value: "*.service"
+ required: false
+ detailed_description: |
+ Systemd unit files matching the selector will be monitored.
+
+ - Logic: (pattern1 OR pattern2)
+ - Pattern syntax: [shell file name pattern](https://golang.org/pkg/path/filepath/#Match)
+ - Syntax:
+
+ ```yaml
+ includes:
+ - pattern1
+ - pattern2
+ ```
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: Service units
+ description: Collect state of all service type units.
+ config: |
+ jobs:
+ - name: service
+ include:
+ - '*.service'
+ - name: One specific unit
+ description: Collect state of one specific unit.
+ config: |
+ jobs:
+ - name: my-specific-service
+ include:
+ - 'my-specific.service'
+ - name: All unit types
+ description: Collect state of all units.
+ config: |
+ jobs:
+ - name: my-specific-service-unit
+ include:
+ - '*'
+ - name: Multi-instance
+ description: |
+ > **Note**: When you define multiple jobs, their names must be unique.
+
+ Collect state of all service and socket type units.
+ config: |
+ jobs:
+ - name: service
+ include:
+ - '*.service'
+
+ - name: socket
+ include:
+ - '*.socket'
+ troubleshooting:
+ problems:
+ list: []
+ alerts:
+ - name: systemd_service_unit_failed_state
+ metric: systemd.service_unit_state
+ info: systemd service unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_socket_unit_failed_state
+ metric: systemd.socket_unit_state
+ info: systemd socket unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_target_unit_failed_state
+ metric: systemd.target_unit_state
+ info: systemd target unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_path_unit_failed_state
+ metric: systemd.path_unit_state
+ info: systemd path unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_device_unit_failed_state
+ metric: systemd.device_unit_state
+ info: systemd device unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_mount_unit_failed_state
+ metric: systemd.mount_unit_state
+ info: systemd mount unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_automount_unit_failed_state
+ metric: systemd.automount_unit_state
+ info: systemd automount unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_swap_unit_failed_state
+ metric: systemd.swap_unit_state
+ info: systemd swap unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_scope_unit_failed_state
+ metric: systemd.scope_unit_state
+ info: systemd scope unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_slice_unit_failed_state
+ metric: systemd.slice_unit_state
+ info: systemd slice unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ - name: systemd_timer_unit_failed_state
+ metric: systemd.timer_unit_state
+ info: systemd timer unit in the failed state
+ link: https://github.com/netdata/netdata/blob/master/src/health/health.d/systemdunits.conf
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: ""
+ availability: []
+ scopes:
+ - name: unit
+ description: These metrics refer to the systemd unit.
+ labels:
+ - name: unit_name
+ description: systemd unit name
+ metrics:
+ - name: systemd.service_unit_state
+ description: Service Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.socket_unit_state
+ description: Socket Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.target_unit_state
+ description: Target Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.path_unit_state
+ description: Path Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.device_unit_state
+ description: Device Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.mount_unit_state
+ description: Mount Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.automount_unit_state
+ description: Automount Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.swap_unit_state
+ description: Swap Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.timer_unit_state
+ description: Timer Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.scope_unit_state
+ description: Scope Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: systemd.slice_unit_state
+ description: Slice Unit State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: active
+ - name: inactive
+ - name: activating
+ - name: deactivating
+ - name: failed
+ - name: unit file
+ description: These metrics refer to the systemd unit file.
+ labels:
+ - name: unit_file_name
+ description: systemd unit file name
+ - name: unit_file_type
+ description: systemd unit file type
+ metrics:
+ - name: systemd.unit_file_state
+ description: Unit File State
+ unit: state
+ chart_type: line
+ dimensions:
+ - name: enabled
+ - name: enabled-runtime
+ - name: linked
+ - name: linked-runtime
+ - name: alias
+ - name: masked
+ - name: masked-runtime
+ - name: static
+ - name: disabled
+ - name: indirect
+ - name: generated
+ - name: transient
+ - name: bad
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits.go b/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits.go
new file mode 100644
index 000000000..367fa2a44
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits.go
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ _ "embed"
+ "errors"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+
+ "github.com/coreos/go-systemd/v22/dbus"
+)
+
+//go:embed "config_schema.json"
+var configSchema string
+
+func init() {
+ module.Register("systemdunits", module.Creator{
+ JobConfigSchema: configSchema,
+ Defaults: module.Defaults{
+ UpdateEvery: 10, // gathering systemd units can be a CPU intensive op
+ },
+ Create: func() module.Module { return New() },
+ Config: func() any { return &Config{} },
+ })
+}
+
+func New() *SystemdUnits {
+ return &SystemdUnits{
+ Config: Config{
+ Timeout: web.Duration(time.Second * 2),
+ Include: []string{"*.service"},
+ SkipTransient: false,
+ CollectUnitFiles: false,
+ IncludeUnitFiles: []string{"*.service"},
+ CollectUnitFilesEvery: web.Duration(time.Minute * 5),
+ },
+ charts: &module.Charts{},
+ client: newSystemdDBusClient(),
+ seenUnits: make(map[string]bool),
+ unitTransient: make(map[string]bool),
+ seenUnitFiles: make(map[string]bool),
+ }
+}
+
+type Config struct {
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
+ Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
+ Include []string `yaml:"include,omitempty" json:"include"`
+ SkipTransient bool `yaml:"skip_transient" json:"skip_transient"`
+ CollectUnitFiles bool `yaml:"collect_unit_files" json:"collect_unit_files"`
+ IncludeUnitFiles []string `yaml:"include_unit_files,omitempty" json:"include_unit_files"`
+ CollectUnitFilesEvery web.Duration `yaml:"collect_unit_files_every,omitempty" json:"collect_unit_files_every"`
+}
+
+type SystemdUnits struct {
+ module.Base
+ Config `yaml:",inline" json:""`
+
+ client systemdClient
+ conn systemdConnection
+
+ systemdVersion int
+
+ seenUnits map[string]bool
+ unitTransient map[string]bool
+ unitSr matcher.Matcher
+
+ lastListUnitFilesTime time.Time
+ cachedUnitFiles []dbus.UnitFile
+ seenUnitFiles map[string]bool
+
+ charts *module.Charts
+}
+
+func (s *SystemdUnits) Configuration() any {
+ return s.Config
+}
+
+func (s *SystemdUnits) Init() error {
+ if err := s.validateConfig(); err != nil {
+ s.Errorf("config validation: %v", err)
+ return err
+ }
+
+ sr, err := s.initUnitSelector()
+ if err != nil {
+ s.Errorf("init unit selector: %v", err)
+ return err
+ }
+ s.unitSr = sr
+
+ s.Debugf("timeout: %s", s.Timeout)
+ s.Debugf("units: patterns '%v'", s.Include)
+ s.Debugf("unit files: enabled '%v', every '%s', patterns: %v",
+ s.CollectUnitFiles, s.CollectUnitFilesEvery, s.IncludeUnitFiles)
+
+ return nil
+}
+
+func (s *SystemdUnits) Check() error {
+ mx, err := s.collect()
+ if err != nil {
+ s.Error(err)
+ return err
+ }
+
+ if len(mx) == 0 {
+ return errors.New("no metrics collected")
+ }
+
+ return nil
+}
+
+func (s *SystemdUnits) Charts() *module.Charts {
+ return s.charts
+}
+
+func (s *SystemdUnits) Collect() map[string]int64 {
+ mx, err := s.collect()
+ if err != nil {
+ s.Error(err)
+ }
+
+ if len(mx) == 0 {
+ return nil
+ }
+ return mx
+}
+
+func (s *SystemdUnits) Cleanup() {
+ s.closeConnection()
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits_test.go b/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits_test.go
new file mode 100644
index 000000000..89c0a92dd
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits_test.go
@@ -0,0 +1,1156 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//go:build linux
+// +build linux
+
+package systemdunits
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "slices"
+ "strings"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+ "github.com/coreos/go-systemd/v22/dbus"
+ "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 TestSystemdUnits_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &SystemdUnits{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestSystemdUnits_Init(t *testing.T) {
+ tests := map[string]struct {
+ config Config
+ wantFail bool
+ }{
+ "success on default config": {
+ config: New().Config,
+ },
+ "success when 'include' option set": {
+ config: Config{
+ Include: []string{"*"},
+ },
+ },
+ "fails when 'include' option not set": {
+ wantFail: true,
+ config: Config{Include: []string{}},
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ systemd := New()
+ systemd.Config = test.config
+
+ if test.wantFail {
+ assert.Error(t, systemd.Init())
+ } else {
+ assert.NoError(t, systemd.Init())
+ }
+ })
+ }
+}
+
+func TestSystemdUnits_Check(t *testing.T) {
+ tests := map[string]struct {
+ prepare func() *SystemdUnits
+ wantFail bool
+ }{
+ "success on systemd v230+": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*"}
+ systemd.client = prepareOKClient(230)
+ return systemd
+ },
+ },
+ "success on systemd v230-": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*"}
+ systemd.client = prepareOKClient(220)
+ return systemd
+ },
+ },
+ "fails when all unites are filtered": {
+ wantFail: true,
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*.not_exists"}
+ systemd.client = prepareOKClient(230)
+ return systemd
+ },
+ },
+ "fails on error on connect": {
+ wantFail: true,
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.client = prepareClientErrOnConnect()
+ return systemd
+ },
+ },
+ "fails on error on get manager property": {
+ wantFail: true,
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.client = prepareClientErrOnGetManagerProperty()
+ return systemd
+ },
+ },
+ "fails on error on list units": {
+ wantFail: true,
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.client = prepareClientErrOnListUnits()
+ return systemd
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ systemd := test.prepare()
+ require.NoError(t, systemd.Init())
+
+ if test.wantFail {
+ assert.Error(t, systemd.Check())
+ } else {
+ assert.NoError(t, systemd.Check())
+ }
+ })
+ }
+}
+
+func TestSystemdUnits_Charts(t *testing.T) {
+ systemd := New()
+ require.NoError(t, systemd.Init())
+ assert.NotNil(t, systemd.Charts())
+}
+
+func TestSystemdUnits_Cleanup(t *testing.T) {
+ systemd := New()
+ systemd.Include = []string{"*"}
+ client := prepareOKClient(230)
+ systemd.client = client
+
+ require.NoError(t, systemd.Init())
+ require.NotNil(t, systemd.Collect())
+ conn := systemd.conn
+ systemd.Cleanup()
+
+ assert.Nil(t, systemd.conn)
+ v, _ := conn.(*mockConn)
+ assert.True(t, v.closeCalled)
+}
+
+func TestSystemdUnits_Collect(t *testing.T) {
+ tests := map[string]struct {
+ prepare func() *SystemdUnits
+ wantCollected map[string]int64
+ }{
+ "success v230+ on collecting all unit type": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*"}
+ systemd.client = prepareOKClient(230)
+ return systemd
+ },
+ wantCollected: map[string]int64{
+ "unit_dbus_socket_state_activating": 0,
+ "unit_dbus_socket_state_active": 1,
+ "unit_dbus_socket_state_deactivating": 0,
+ "unit_dbus_socket_state_failed": 0,
+ "unit_dbus_socket_state_inactive": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_activating": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_active": 1,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_deactivating": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_failed": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_inactive": 0,
+ "unit_dev-nvme0n1_device_state_activating": 0,
+ "unit_dev-nvme0n1_device_state_active": 1,
+ "unit_dev-nvme0n1_device_state_deactivating": 0,
+ "unit_dev-nvme0n1_device_state_failed": 0,
+ "unit_dev-nvme0n1_device_state_inactive": 0,
+ "unit_docker_socket_state_activating": 0,
+ "unit_docker_socket_state_active": 1,
+ "unit_docker_socket_state_deactivating": 0,
+ "unit_docker_socket_state_failed": 0,
+ "unit_docker_socket_state_inactive": 0,
+ "unit_getty-pre_target_state_activating": 0,
+ "unit_getty-pre_target_state_active": 0,
+ "unit_getty-pre_target_state_deactivating": 0,
+ "unit_getty-pre_target_state_failed": 0,
+ "unit_getty-pre_target_state_inactive": 1,
+ "unit_init_scope_state_activating": 0,
+ "unit_init_scope_state_active": 1,
+ "unit_init_scope_state_deactivating": 0,
+ "unit_init_scope_state_failed": 0,
+ "unit_init_scope_state_inactive": 0,
+ "unit_logrotate_timer_state_activating": 0,
+ "unit_logrotate_timer_state_active": 1,
+ "unit_logrotate_timer_state_deactivating": 0,
+ "unit_logrotate_timer_state_failed": 0,
+ "unit_logrotate_timer_state_inactive": 0,
+ "unit_lvm2-lvmetad_socket_state_activating": 0,
+ "unit_lvm2-lvmetad_socket_state_active": 1,
+ "unit_lvm2-lvmetad_socket_state_deactivating": 0,
+ "unit_lvm2-lvmetad_socket_state_failed": 0,
+ "unit_lvm2-lvmetad_socket_state_inactive": 0,
+ "unit_lvm2-lvmpolld_socket_state_activating": 0,
+ "unit_lvm2-lvmpolld_socket_state_active": 1,
+ "unit_lvm2-lvmpolld_socket_state_deactivating": 0,
+ "unit_lvm2-lvmpolld_socket_state_failed": 0,
+ "unit_lvm2-lvmpolld_socket_state_inactive": 0,
+ "unit_man-db_timer_state_activating": 0,
+ "unit_man-db_timer_state_active": 1,
+ "unit_man-db_timer_state_deactivating": 0,
+ "unit_man-db_timer_state_failed": 0,
+ "unit_man-db_timer_state_inactive": 0,
+ "unit_org.cups.cupsd_path_state_activating": 0,
+ "unit_org.cups.cupsd_path_state_active": 1,
+ "unit_org.cups.cupsd_path_state_deactivating": 0,
+ "unit_org.cups.cupsd_path_state_failed": 0,
+ "unit_org.cups.cupsd_path_state_inactive": 0,
+ "unit_pamac-cleancache_timer_state_activating": 0,
+ "unit_pamac-cleancache_timer_state_active": 1,
+ "unit_pamac-cleancache_timer_state_deactivating": 0,
+ "unit_pamac-cleancache_timer_state_failed": 0,
+ "unit_pamac-cleancache_timer_state_inactive": 0,
+ "unit_pamac-mirrorlist_timer_state_activating": 0,
+ "unit_pamac-mirrorlist_timer_state_active": 1,
+ "unit_pamac-mirrorlist_timer_state_deactivating": 0,
+ "unit_pamac-mirrorlist_timer_state_failed": 0,
+ "unit_pamac-mirrorlist_timer_state_inactive": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_activating": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_active": 1,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_deactivating": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_failed": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_inactive": 0,
+ "unit_remote-fs-pre_target_state_activating": 0,
+ "unit_remote-fs-pre_target_state_active": 0,
+ "unit_remote-fs-pre_target_state_deactivating": 0,
+ "unit_remote-fs-pre_target_state_failed": 0,
+ "unit_remote-fs-pre_target_state_inactive": 1,
+ "unit_rpc_pipefs_target_state_activating": 0,
+ "unit_rpc_pipefs_target_state_active": 0,
+ "unit_rpc_pipefs_target_state_deactivating": 0,
+ "unit_rpc_pipefs_target_state_failed": 0,
+ "unit_rpc_pipefs_target_state_inactive": 1,
+ "unit_run-user-1000-gvfs_mount_state_activating": 0,
+ "unit_run-user-1000-gvfs_mount_state_active": 1,
+ "unit_run-user-1000-gvfs_mount_state_deactivating": 0,
+ "unit_run-user-1000-gvfs_mount_state_failed": 0,
+ "unit_run-user-1000-gvfs_mount_state_inactive": 0,
+ "unit_run-user-1000_mount_state_activating": 0,
+ "unit_run-user-1000_mount_state_active": 1,
+ "unit_run-user-1000_mount_state_deactivating": 0,
+ "unit_run-user-1000_mount_state_failed": 0,
+ "unit_run-user-1000_mount_state_inactive": 0,
+ "unit_session-1_scope_state_activating": 0,
+ "unit_session-1_scope_state_active": 1,
+ "unit_session-1_scope_state_deactivating": 0,
+ "unit_session-1_scope_state_failed": 0,
+ "unit_session-1_scope_state_inactive": 0,
+ "unit_session-2_scope_state_activating": 0,
+ "unit_session-2_scope_state_active": 1,
+ "unit_session-2_scope_state_deactivating": 0,
+ "unit_session-2_scope_state_failed": 0,
+ "unit_session-2_scope_state_inactive": 0,
+ "unit_session-3_scope_state_activating": 0,
+ "unit_session-3_scope_state_active": 1,
+ "unit_session-3_scope_state_deactivating": 0,
+ "unit_session-3_scope_state_failed": 0,
+ "unit_session-3_scope_state_inactive": 0,
+ "unit_session-6_scope_state_activating": 0,
+ "unit_session-6_scope_state_active": 1,
+ "unit_session-6_scope_state_deactivating": 0,
+ "unit_session-6_scope_state_failed": 0,
+ "unit_session-6_scope_state_inactive": 0,
+ "unit_shadow_timer_state_activating": 0,
+ "unit_shadow_timer_state_active": 1,
+ "unit_shadow_timer_state_deactivating": 0,
+ "unit_shadow_timer_state_failed": 0,
+ "unit_shadow_timer_state_inactive": 0,
+ "unit_sound_target_state_activating": 0,
+ "unit_sound_target_state_active": 1,
+ "unit_sound_target_state_deactivating": 0,
+ "unit_sound_target_state_failed": 0,
+ "unit_sound_target_state_inactive": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_activating": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_active": 1,
+ "unit_sys-devices-virtual-net-loopback1_device_state_deactivating": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_failed": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_inactive": 0,
+ "unit_sys-module-fuse_device_state_activating": 0,
+ "unit_sys-module-fuse_device_state_active": 1,
+ "unit_sys-module-fuse_device_state_deactivating": 0,
+ "unit_sys-module-fuse_device_state_failed": 0,
+ "unit_sys-module-fuse_device_state_inactive": 0,
+ "unit_sysinit_target_state_activating": 0,
+ "unit_sysinit_target_state_active": 1,
+ "unit_sysinit_target_state_deactivating": 0,
+ "unit_sysinit_target_state_failed": 0,
+ "unit_sysinit_target_state_inactive": 0,
+ "unit_system-getty_slice_state_activating": 0,
+ "unit_system-getty_slice_state_active": 1,
+ "unit_system-getty_slice_state_deactivating": 0,
+ "unit_system-getty_slice_state_failed": 0,
+ "unit_system-getty_slice_state_inactive": 0,
+ "unit_system-netctl_slice_state_activating": 0,
+ "unit_system-netctl_slice_state_active": 1,
+ "unit_system-netctl_slice_state_deactivating": 0,
+ "unit_system-netctl_slice_state_failed": 0,
+ "unit_system-netctl_slice_state_inactive": 0,
+ "unit_system-systemd-fsck_slice_state_activating": 0,
+ "unit_system-systemd-fsck_slice_state_active": 1,
+ "unit_system-systemd-fsck_slice_state_deactivating": 0,
+ "unit_system-systemd-fsck_slice_state_failed": 0,
+ "unit_system-systemd-fsck_slice_state_inactive": 0,
+ "unit_system_slice_state_activating": 0,
+ "unit_system_slice_state_active": 1,
+ "unit_system_slice_state_deactivating": 0,
+ "unit_system_slice_state_failed": 0,
+ "unit_system_slice_state_inactive": 0,
+ "unit_systemd-ask-password-console_path_state_activating": 0,
+ "unit_systemd-ask-password-console_path_state_active": 1,
+ "unit_systemd-ask-password-console_path_state_deactivating": 0,
+ "unit_systemd-ask-password-console_path_state_failed": 0,
+ "unit_systemd-ask-password-console_path_state_inactive": 0,
+ "unit_systemd-ask-password-wall_path_state_activating": 0,
+ "unit_systemd-ask-password-wall_path_state_active": 1,
+ "unit_systemd-ask-password-wall_path_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_path_state_failed": 0,
+ "unit_systemd-ask-password-wall_path_state_inactive": 0,
+ "unit_systemd-ask-password-wall_service_state_activating": 0,
+ "unit_systemd-ask-password-wall_service_state_active": 0,
+ "unit_systemd-ask-password-wall_service_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_service_state_failed": 0,
+ "unit_systemd-ask-password-wall_service_state_inactive": 1,
+ "unit_systemd-fsck-root_service_state_activating": 0,
+ "unit_systemd-fsck-root_service_state_active": 0,
+ "unit_systemd-fsck-root_service_state_deactivating": 0,
+ "unit_systemd-fsck-root_service_state_failed": 0,
+ "unit_systemd-fsck-root_service_state_inactive": 1,
+ "unit_systemd-udevd-kernel_socket_state_activating": 0,
+ "unit_systemd-udevd-kernel_socket_state_active": 1,
+ "unit_systemd-udevd-kernel_socket_state_deactivating": 0,
+ "unit_systemd-udevd-kernel_socket_state_failed": 0,
+ "unit_systemd-udevd-kernel_socket_state_inactive": 0,
+ "unit_tmp_mount_state_activating": 0,
+ "unit_tmp_mount_state_active": 1,
+ "unit_tmp_mount_state_deactivating": 0,
+ "unit_tmp_mount_state_failed": 0,
+ "unit_tmp_mount_state_inactive": 0,
+ "unit_user-runtime-dir@1000_service_state_activating": 0,
+ "unit_user-runtime-dir@1000_service_state_active": 1,
+ "unit_user-runtime-dir@1000_service_state_deactivating": 0,
+ "unit_user-runtime-dir@1000_service_state_failed": 0,
+ "unit_user-runtime-dir@1000_service_state_inactive": 0,
+ "unit_user@1000_service_state_activating": 0,
+ "unit_user@1000_service_state_active": 1,
+ "unit_user@1000_service_state_deactivating": 0,
+ "unit_user@1000_service_state_failed": 0,
+ "unit_user@1000_service_state_inactive": 0,
+ "unit_user_slice_state_activating": 0,
+ "unit_user_slice_state_active": 1,
+ "unit_user_slice_state_deactivating": 0,
+ "unit_user_slice_state_failed": 0,
+ "unit_user_slice_state_inactive": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_activating": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_active": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_deactivating": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_failed": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_inactive": 1,
+ },
+ },
+ "success v230+ on collecting all unit type with skip transient": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*"}
+ systemd.SkipTransient = true
+ systemd.client = prepareOKClient(230)
+ return systemd
+ },
+ wantCollected: map[string]int64{
+ "unit_systemd-ask-password-wall_service_state_activating": 0,
+ "unit_systemd-ask-password-wall_service_state_active": 0,
+ "unit_systemd-ask-password-wall_service_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_service_state_failed": 0,
+ "unit_systemd-ask-password-wall_service_state_inactive": 1,
+ "unit_systemd-fsck-root_service_state_activating": 0,
+ "unit_systemd-fsck-root_service_state_active": 0,
+ "unit_systemd-fsck-root_service_state_deactivating": 0,
+ "unit_systemd-fsck-root_service_state_failed": 0,
+ "unit_systemd-fsck-root_service_state_inactive": 1,
+ "unit_user-runtime-dir@1000_service_state_activating": 0,
+ "unit_user-runtime-dir@1000_service_state_active": 1,
+ "unit_user-runtime-dir@1000_service_state_deactivating": 0,
+ "unit_user-runtime-dir@1000_service_state_failed": 0,
+ "unit_user-runtime-dir@1000_service_state_inactive": 0,
+ "unit_user@1000_service_state_activating": 0,
+ "unit_user@1000_service_state_active": 1,
+ "unit_user@1000_service_state_deactivating": 0,
+ "unit_user@1000_service_state_failed": 0,
+ "unit_user@1000_service_state_inactive": 0,
+ },
+ },
+ "success v230- on collecting all unit types": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*"}
+ systemd.client = prepareOKClient(220)
+ return systemd
+ },
+ wantCollected: map[string]int64{
+ "unit_dbus_socket_state_activating": 0,
+ "unit_dbus_socket_state_active": 1,
+ "unit_dbus_socket_state_deactivating": 0,
+ "unit_dbus_socket_state_failed": 0,
+ "unit_dbus_socket_state_inactive": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_activating": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_active": 1,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_deactivating": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_failed": 0,
+ "unit_dev-disk-by-uuid-DE44-CEE0_device_state_inactive": 0,
+ "unit_dev-nvme0n1_device_state_activating": 0,
+ "unit_dev-nvme0n1_device_state_active": 1,
+ "unit_dev-nvme0n1_device_state_deactivating": 0,
+ "unit_dev-nvme0n1_device_state_failed": 0,
+ "unit_dev-nvme0n1_device_state_inactive": 0,
+ "unit_docker_socket_state_activating": 0,
+ "unit_docker_socket_state_active": 1,
+ "unit_docker_socket_state_deactivating": 0,
+ "unit_docker_socket_state_failed": 0,
+ "unit_docker_socket_state_inactive": 0,
+ "unit_getty-pre_target_state_activating": 0,
+ "unit_getty-pre_target_state_active": 0,
+ "unit_getty-pre_target_state_deactivating": 0,
+ "unit_getty-pre_target_state_failed": 0,
+ "unit_getty-pre_target_state_inactive": 1,
+ "unit_init_scope_state_activating": 0,
+ "unit_init_scope_state_active": 1,
+ "unit_init_scope_state_deactivating": 0,
+ "unit_init_scope_state_failed": 0,
+ "unit_init_scope_state_inactive": 0,
+ "unit_logrotate_timer_state_activating": 0,
+ "unit_logrotate_timer_state_active": 1,
+ "unit_logrotate_timer_state_deactivating": 0,
+ "unit_logrotate_timer_state_failed": 0,
+ "unit_logrotate_timer_state_inactive": 0,
+ "unit_lvm2-lvmetad_socket_state_activating": 0,
+ "unit_lvm2-lvmetad_socket_state_active": 1,
+ "unit_lvm2-lvmetad_socket_state_deactivating": 0,
+ "unit_lvm2-lvmetad_socket_state_failed": 0,
+ "unit_lvm2-lvmetad_socket_state_inactive": 0,
+ "unit_lvm2-lvmpolld_socket_state_activating": 0,
+ "unit_lvm2-lvmpolld_socket_state_active": 1,
+ "unit_lvm2-lvmpolld_socket_state_deactivating": 0,
+ "unit_lvm2-lvmpolld_socket_state_failed": 0,
+ "unit_lvm2-lvmpolld_socket_state_inactive": 0,
+ "unit_man-db_timer_state_activating": 0,
+ "unit_man-db_timer_state_active": 1,
+ "unit_man-db_timer_state_deactivating": 0,
+ "unit_man-db_timer_state_failed": 0,
+ "unit_man-db_timer_state_inactive": 0,
+ "unit_org.cups.cupsd_path_state_activating": 0,
+ "unit_org.cups.cupsd_path_state_active": 1,
+ "unit_org.cups.cupsd_path_state_deactivating": 0,
+ "unit_org.cups.cupsd_path_state_failed": 0,
+ "unit_org.cups.cupsd_path_state_inactive": 0,
+ "unit_pamac-cleancache_timer_state_activating": 0,
+ "unit_pamac-cleancache_timer_state_active": 1,
+ "unit_pamac-cleancache_timer_state_deactivating": 0,
+ "unit_pamac-cleancache_timer_state_failed": 0,
+ "unit_pamac-cleancache_timer_state_inactive": 0,
+ "unit_pamac-mirrorlist_timer_state_activating": 0,
+ "unit_pamac-mirrorlist_timer_state_active": 1,
+ "unit_pamac-mirrorlist_timer_state_deactivating": 0,
+ "unit_pamac-mirrorlist_timer_state_failed": 0,
+ "unit_pamac-mirrorlist_timer_state_inactive": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_activating": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_active": 1,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_deactivating": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_failed": 0,
+ "unit_proc-sys-fs-binfmt_misc_automount_state_inactive": 0,
+ "unit_remote-fs-pre_target_state_activating": 0,
+ "unit_remote-fs-pre_target_state_active": 0,
+ "unit_remote-fs-pre_target_state_deactivating": 0,
+ "unit_remote-fs-pre_target_state_failed": 0,
+ "unit_remote-fs-pre_target_state_inactive": 1,
+ "unit_rpc_pipefs_target_state_activating": 0,
+ "unit_rpc_pipefs_target_state_active": 0,
+ "unit_rpc_pipefs_target_state_deactivating": 0,
+ "unit_rpc_pipefs_target_state_failed": 0,
+ "unit_rpc_pipefs_target_state_inactive": 1,
+ "unit_run-user-1000-gvfs_mount_state_activating": 0,
+ "unit_run-user-1000-gvfs_mount_state_active": 1,
+ "unit_run-user-1000-gvfs_mount_state_deactivating": 0,
+ "unit_run-user-1000-gvfs_mount_state_failed": 0,
+ "unit_run-user-1000-gvfs_mount_state_inactive": 0,
+ "unit_run-user-1000_mount_state_activating": 0,
+ "unit_run-user-1000_mount_state_active": 1,
+ "unit_run-user-1000_mount_state_deactivating": 0,
+ "unit_run-user-1000_mount_state_failed": 0,
+ "unit_run-user-1000_mount_state_inactive": 0,
+ "unit_session-1_scope_state_activating": 0,
+ "unit_session-1_scope_state_active": 1,
+ "unit_session-1_scope_state_deactivating": 0,
+ "unit_session-1_scope_state_failed": 0,
+ "unit_session-1_scope_state_inactive": 0,
+ "unit_session-2_scope_state_activating": 0,
+ "unit_session-2_scope_state_active": 1,
+ "unit_session-2_scope_state_deactivating": 0,
+ "unit_session-2_scope_state_failed": 0,
+ "unit_session-2_scope_state_inactive": 0,
+ "unit_session-3_scope_state_activating": 0,
+ "unit_session-3_scope_state_active": 1,
+ "unit_session-3_scope_state_deactivating": 0,
+ "unit_session-3_scope_state_failed": 0,
+ "unit_session-3_scope_state_inactive": 0,
+ "unit_session-6_scope_state_activating": 0,
+ "unit_session-6_scope_state_active": 1,
+ "unit_session-6_scope_state_deactivating": 0,
+ "unit_session-6_scope_state_failed": 0,
+ "unit_session-6_scope_state_inactive": 0,
+ "unit_shadow_timer_state_activating": 0,
+ "unit_shadow_timer_state_active": 1,
+ "unit_shadow_timer_state_deactivating": 0,
+ "unit_shadow_timer_state_failed": 0,
+ "unit_shadow_timer_state_inactive": 0,
+ "unit_sound_target_state_activating": 0,
+ "unit_sound_target_state_active": 1,
+ "unit_sound_target_state_deactivating": 0,
+ "unit_sound_target_state_failed": 0,
+ "unit_sound_target_state_inactive": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_activating": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_active": 1,
+ "unit_sys-devices-virtual-net-loopback1_device_state_deactivating": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_failed": 0,
+ "unit_sys-devices-virtual-net-loopback1_device_state_inactive": 0,
+ "unit_sys-module-fuse_device_state_activating": 0,
+ "unit_sys-module-fuse_device_state_active": 1,
+ "unit_sys-module-fuse_device_state_deactivating": 0,
+ "unit_sys-module-fuse_device_state_failed": 0,
+ "unit_sys-module-fuse_device_state_inactive": 0,
+ "unit_sysinit_target_state_activating": 0,
+ "unit_sysinit_target_state_active": 1,
+ "unit_sysinit_target_state_deactivating": 0,
+ "unit_sysinit_target_state_failed": 0,
+ "unit_sysinit_target_state_inactive": 0,
+ "unit_system-getty_slice_state_activating": 0,
+ "unit_system-getty_slice_state_active": 1,
+ "unit_system-getty_slice_state_deactivating": 0,
+ "unit_system-getty_slice_state_failed": 0,
+ "unit_system-getty_slice_state_inactive": 0,
+ "unit_system-netctl_slice_state_activating": 0,
+ "unit_system-netctl_slice_state_active": 1,
+ "unit_system-netctl_slice_state_deactivating": 0,
+ "unit_system-netctl_slice_state_failed": 0,
+ "unit_system-netctl_slice_state_inactive": 0,
+ "unit_system-systemd-fsck_slice_state_activating": 0,
+ "unit_system-systemd-fsck_slice_state_active": 1,
+ "unit_system-systemd-fsck_slice_state_deactivating": 0,
+ "unit_system-systemd-fsck_slice_state_failed": 0,
+ "unit_system-systemd-fsck_slice_state_inactive": 0,
+ "unit_system_slice_state_activating": 0,
+ "unit_system_slice_state_active": 1,
+ "unit_system_slice_state_deactivating": 0,
+ "unit_system_slice_state_failed": 0,
+ "unit_system_slice_state_inactive": 0,
+ "unit_systemd-ask-password-console_path_state_activating": 0,
+ "unit_systemd-ask-password-console_path_state_active": 1,
+ "unit_systemd-ask-password-console_path_state_deactivating": 0,
+ "unit_systemd-ask-password-console_path_state_failed": 0,
+ "unit_systemd-ask-password-console_path_state_inactive": 0,
+ "unit_systemd-ask-password-wall_path_state_activating": 0,
+ "unit_systemd-ask-password-wall_path_state_active": 1,
+ "unit_systemd-ask-password-wall_path_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_path_state_failed": 0,
+ "unit_systemd-ask-password-wall_path_state_inactive": 0,
+ "unit_systemd-ask-password-wall_service_state_activating": 0,
+ "unit_systemd-ask-password-wall_service_state_active": 0,
+ "unit_systemd-ask-password-wall_service_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_service_state_failed": 0,
+ "unit_systemd-ask-password-wall_service_state_inactive": 1,
+ "unit_systemd-fsck-root_service_state_activating": 0,
+ "unit_systemd-fsck-root_service_state_active": 0,
+ "unit_systemd-fsck-root_service_state_deactivating": 0,
+ "unit_systemd-fsck-root_service_state_failed": 0,
+ "unit_systemd-fsck-root_service_state_inactive": 1,
+ "unit_systemd-udevd-kernel_socket_state_activating": 0,
+ "unit_systemd-udevd-kernel_socket_state_active": 1,
+ "unit_systemd-udevd-kernel_socket_state_deactivating": 0,
+ "unit_systemd-udevd-kernel_socket_state_failed": 0,
+ "unit_systemd-udevd-kernel_socket_state_inactive": 0,
+ "unit_tmp_mount_state_activating": 0,
+ "unit_tmp_mount_state_active": 1,
+ "unit_tmp_mount_state_deactivating": 0,
+ "unit_tmp_mount_state_failed": 0,
+ "unit_tmp_mount_state_inactive": 0,
+ "unit_user-runtime-dir@1000_service_state_activating": 0,
+ "unit_user-runtime-dir@1000_service_state_active": 1,
+ "unit_user-runtime-dir@1000_service_state_deactivating": 0,
+ "unit_user-runtime-dir@1000_service_state_failed": 0,
+ "unit_user-runtime-dir@1000_service_state_inactive": 0,
+ "unit_user@1000_service_state_activating": 0,
+ "unit_user@1000_service_state_active": 1,
+ "unit_user@1000_service_state_deactivating": 0,
+ "unit_user@1000_service_state_failed": 0,
+ "unit_user@1000_service_state_inactive": 0,
+ "unit_user_slice_state_activating": 0,
+ "unit_user_slice_state_active": 1,
+ "unit_user_slice_state_deactivating": 0,
+ "unit_user_slice_state_failed": 0,
+ "unit_user_slice_state_inactive": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_activating": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_active": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_deactivating": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_failed": 0,
+ "unit_var-lib-nfs-rpc_pipefs_mount_state_inactive": 1,
+ },
+ },
+ "success v230+ on collecting only 'service' units": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*.service"}
+ systemd.client = prepareOKClient(230)
+ return systemd
+ },
+ wantCollected: map[string]int64{
+ "unit_systemd-ask-password-wall_service_state_activating": 0,
+ "unit_systemd-ask-password-wall_service_state_active": 0,
+ "unit_systemd-ask-password-wall_service_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_service_state_failed": 0,
+ "unit_systemd-ask-password-wall_service_state_inactive": 1,
+ "unit_systemd-fsck-root_service_state_activating": 0,
+ "unit_systemd-fsck-root_service_state_active": 0,
+ "unit_systemd-fsck-root_service_state_deactivating": 0,
+ "unit_systemd-fsck-root_service_state_failed": 0,
+ "unit_systemd-fsck-root_service_state_inactive": 1,
+ "unit_user-runtime-dir@1000_service_state_activating": 0,
+ "unit_user-runtime-dir@1000_service_state_active": 1,
+ "unit_user-runtime-dir@1000_service_state_deactivating": 0,
+ "unit_user-runtime-dir@1000_service_state_failed": 0,
+ "unit_user-runtime-dir@1000_service_state_inactive": 0,
+ "unit_user@1000_service_state_activating": 0,
+ "unit_user@1000_service_state_active": 1,
+ "unit_user@1000_service_state_deactivating": 0,
+ "unit_user@1000_service_state_failed": 0,
+ "unit_user@1000_service_state_inactive": 0,
+ },
+ },
+ "success v230- on collecting only 'service' units": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*.service"}
+ systemd.client = prepareOKClient(220)
+ return systemd
+ },
+ wantCollected: map[string]int64{
+ "unit_systemd-ask-password-wall_service_state_activating": 0,
+ "unit_systemd-ask-password-wall_service_state_active": 0,
+ "unit_systemd-ask-password-wall_service_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_service_state_failed": 0,
+ "unit_systemd-ask-password-wall_service_state_inactive": 1,
+ "unit_systemd-fsck-root_service_state_activating": 0,
+ "unit_systemd-fsck-root_service_state_active": 0,
+ "unit_systemd-fsck-root_service_state_deactivating": 0,
+ "unit_systemd-fsck-root_service_state_failed": 0,
+ "unit_systemd-fsck-root_service_state_inactive": 1,
+ "unit_user-runtime-dir@1000_service_state_activating": 0,
+ "unit_user-runtime-dir@1000_service_state_active": 1,
+ "unit_user-runtime-dir@1000_service_state_deactivating": 0,
+ "unit_user-runtime-dir@1000_service_state_failed": 0,
+ "unit_user-runtime-dir@1000_service_state_inactive": 0,
+ "unit_user@1000_service_state_activating": 0,
+ "unit_user@1000_service_state_active": 1,
+ "unit_user@1000_service_state_deactivating": 0,
+ "unit_user@1000_service_state_failed": 0,
+ "unit_user@1000_service_state_inactive": 0,
+ },
+ },
+ "success v230+ on collecting only 'service' units and files": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*.service"}
+ systemd.IncludeUnitFiles = []string{"*.service", "*.slice"}
+ systemd.CollectUnitFiles = true
+ systemd.client = prepareOKClient(230)
+ return systemd
+ },
+ wantCollected: map[string]int64{
+ "unit_file_/lib/systemd/system/machine.slice_state_alias": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_bad": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_disabled": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_enabled": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_enabled-runtime": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_generated": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_indirect": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_linked": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_linked-runtime": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_masked": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_masked-runtime": 0,
+ "unit_file_/lib/systemd/system/machine.slice_state_static": 1,
+ "unit_file_/lib/systemd/system/machine.slice_state_transient": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_alias": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_bad": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_disabled": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_enabled": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_enabled-runtime": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_generated": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_indirect": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_linked": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_linked-runtime": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_masked": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_masked-runtime": 0,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_static": 1,
+ "unit_file_/lib/systemd/system/system-systemd-cryptsetup.slice_state_transient": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_alias": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_bad": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_disabled": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_enabled": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_enabled-runtime": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_generated": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_indirect": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_linked": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_linked-runtime": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_masked": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_masked-runtime": 0,
+ "unit_file_/lib/systemd/system/user.slice_state_static": 1,
+ "unit_file_/lib/systemd/system/user.slice_state_transient": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_alias": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_bad": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_disabled": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_enabled": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_enabled-runtime": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_generated": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_indirect": 1,
+ "unit_file_/lib/systemd/system/uuidd.service_state_linked": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_linked-runtime": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_masked": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_masked-runtime": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_static": 0,
+ "unit_file_/lib/systemd/system/uuidd.service_state_transient": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_alias": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_bad": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_disabled": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_enabled": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_enabled-runtime": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_generated": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_indirect": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_linked": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_linked-runtime": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_masked": 1,
+ "unit_file_/lib/systemd/system/x11-common.service_state_masked-runtime": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_static": 0,
+ "unit_file_/lib/systemd/system/x11-common.service_state_transient": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_alias": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_bad": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_disabled": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_enabled": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_enabled-runtime": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_generated": 1,
+ "unit_file_/run/systemd/generator.late/monit.service_state_indirect": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_linked": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_linked-runtime": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_masked": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_masked-runtime": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_static": 0,
+ "unit_file_/run/systemd/generator.late/monit.service_state_transient": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_alias": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_bad": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_disabled": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_enabled": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_enabled-runtime": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_generated": 1,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_indirect": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_linked": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_linked-runtime": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_masked": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_masked-runtime": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_static": 0,
+ "unit_file_/run/systemd/generator.late/sendmail.service_state_transient": 0,
+ "unit_systemd-ask-password-wall_service_state_activating": 0,
+ "unit_systemd-ask-password-wall_service_state_active": 0,
+ "unit_systemd-ask-password-wall_service_state_deactivating": 0,
+ "unit_systemd-ask-password-wall_service_state_failed": 0,
+ "unit_systemd-ask-password-wall_service_state_inactive": 1,
+ "unit_systemd-fsck-root_service_state_activating": 0,
+ "unit_systemd-fsck-root_service_state_active": 0,
+ "unit_systemd-fsck-root_service_state_deactivating": 0,
+ "unit_systemd-fsck-root_service_state_failed": 0,
+ "unit_systemd-fsck-root_service_state_inactive": 1,
+ "unit_user-runtime-dir@1000_service_state_activating": 0,
+ "unit_user-runtime-dir@1000_service_state_active": 1,
+ "unit_user-runtime-dir@1000_service_state_deactivating": 0,
+ "unit_user-runtime-dir@1000_service_state_failed": 0,
+ "unit_user-runtime-dir@1000_service_state_inactive": 0,
+ "unit_user@1000_service_state_activating": 0,
+ "unit_user@1000_service_state_active": 1,
+ "unit_user@1000_service_state_deactivating": 0,
+ "unit_user@1000_service_state_failed": 0,
+ "unit_user@1000_service_state_inactive": 0,
+ },
+ },
+ "fails when all unites are filtered": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.Include = []string{"*.not_exists"}
+ systemd.client = prepareOKClient(230)
+ return systemd
+ },
+ wantCollected: nil,
+ },
+ "fails on error on connect": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.client = prepareClientErrOnConnect()
+ return systemd
+ },
+ wantCollected: nil,
+ },
+ "fails on error on get manager property": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.client = prepareClientErrOnGetManagerProperty()
+ return systemd
+ },
+ wantCollected: nil,
+ },
+ "fails on error on list units": {
+ prepare: func() *SystemdUnits {
+ systemd := New()
+ systemd.client = prepareClientErrOnListUnits()
+ return systemd
+ },
+ wantCollected: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ systemd := test.prepare()
+ require.NoError(t, systemd.Init())
+
+ var mx map[string]int64
+
+ for i := 0; i < 10; i++ {
+ mx = systemd.Collect()
+ }
+
+ assert.Equal(t, test.wantCollected, mx)
+ if len(test.wantCollected) > 0 {
+ ensureCollectedHasAllChartsDimsVarsIDs(t, systemd, mx)
+ }
+ })
+ }
+}
+
+func TestSystemdUnits_connectionReuse(t *testing.T) {
+ systemd := New()
+ systemd.Include = []string{"*"}
+ client := prepareOKClient(230)
+ systemd.client = client
+ require.NoError(t, systemd.Init())
+
+ var collected map[string]int64
+ for i := 0; i < 10; i++ {
+ collected = systemd.Collect()
+ }
+
+ assert.NotEmpty(t, collected)
+ assert.Equal(t, 1, client.connectCalls)
+}
+
+func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, sd *SystemdUnits, collected map[string]int64) {
+ for _, chart := range *sd.Charts() {
+ if chart.Obsolete {
+ continue
+ }
+ for _, dim := range chart.Dims {
+ _, ok := collected[dim.ID]
+ assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
+ }
+ for _, v := range chart.Vars {
+ _, ok := collected[v.ID]
+ assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
+ }
+ }
+}
+
+func prepareOKClient(ver int) *mockClient {
+ return &mockClient{
+ conn: &mockConn{
+ version: ver,
+ units: mockSystemdUnits,
+ unitFiles: mockSystemdUnitFiles,
+ },
+ }
+}
+
+func prepareClientErrOnConnect() *mockClient {
+ return &mockClient{
+ errOnConnect: true,
+ }
+}
+
+func prepareClientErrOnGetManagerProperty() *mockClient {
+ return &mockClient{
+ conn: &mockConn{
+ version: 230,
+ errOnGetManagerProperty: true,
+ units: mockSystemdUnits,
+ },
+ }
+}
+
+func prepareClientErrOnListUnits() *mockClient {
+ return &mockClient{
+ conn: &mockConn{
+ version: 230,
+ errOnListUnits: true,
+ units: mockSystemdUnits,
+ },
+ }
+}
+
+type mockClient struct {
+ conn systemdConnection
+ connectCalls int
+ errOnConnect bool
+}
+
+func (m *mockClient) connect() (systemdConnection, error) {
+ m.connectCalls++
+ if m.errOnConnect {
+ return nil, errors.New("mock 'connect' error")
+ }
+ return m.conn, nil
+}
+
+type mockConn struct {
+ version int
+ errOnGetManagerProperty bool
+
+ units []dbus.UnitStatus
+ errOnListUnits bool
+
+ unitFiles []dbus.UnitFile
+ errOnListUnitFiles bool
+
+ closeCalled bool
+}
+
+func (m *mockConn) Close() {
+ m.closeCalled = true
+}
+
+func (m *mockConn) GetManagerProperty(prop string) (string, error) {
+ if m.errOnGetManagerProperty {
+ return "", errors.New("'GetManagerProperty' call error")
+ }
+ if prop != versionProperty {
+ return "", fmt.Errorf("'GetManagerProperty' unkown property: %s", prop)
+ }
+
+ return fmt.Sprintf("%d.6-1-manjaro", m.version), nil
+}
+
+func (m *mockConn) GetUnitPropertyContext(_ context.Context, unit string, propertyName string) (*dbus.Property, error) {
+ if propertyName != transientProperty {
+ return nil, fmt.Errorf("'GetUnitProperty' unkown property name: %s", propertyName)
+ }
+
+ var prop dbus.Property
+
+ if strings.HasSuffix(unit, ".service") {
+ prop = dbus.PropDescription("false")
+ } else {
+ prop = dbus.PropDescription("true")
+ }
+
+ prop.Name = propertyName
+
+ return &prop, nil
+}
+
+func (m *mockConn) ListUnitsContext(_ context.Context) ([]dbus.UnitStatus, error) {
+ if m.errOnListUnits {
+ return nil, errors.New("'ListUnits' call error")
+ }
+ if m.version >= 230 {
+ return nil, errors.New("'ListUnits' unsupported function error")
+ }
+
+ return append([]dbus.UnitStatus{}, m.units...), nil
+}
+
+func (m *mockConn) ListUnitsByPatternsContext(_ context.Context, _ []string, patterns []string) ([]dbus.UnitStatus, error) {
+ if m.errOnListUnits {
+ return nil, errors.New("'ListUnitsByPatterns' call error")
+ }
+ if m.version < 230 {
+ return nil, errors.New("'ListUnitsByPatterns' unsupported function error")
+ }
+
+ if len(m.units) == 0 {
+ return nil, nil
+ }
+
+ units := append([]dbus.UnitStatus{}, m.units...)
+
+ units = slices.DeleteFunc(units, func(u dbus.UnitStatus) bool {
+ name := cleanUnitName(u.Name)
+ for _, p := range patterns {
+ if ok, _ := filepath.Match(p, name); ok {
+ return false
+ }
+ }
+ return true
+ })
+
+ return units, nil
+}
+
+func (m *mockConn) ListUnitFilesByPatternsContext(_ context.Context, _ []string, patterns []string) ([]dbus.UnitFile, error) {
+ if m.errOnListUnitFiles {
+ return nil, errors.New("'ListUnitFilesByPatternsContex' call error")
+ }
+ if m.version < 230 {
+ return nil, errors.New("'ListUnitFilesByPatternsContex' unsupported function error")
+ }
+
+ if len(m.unitFiles) == 0 {
+ return nil, nil
+ }
+
+ unitFiles := append([]dbus.UnitFile{}, m.unitFiles...)
+
+ unitFiles = slices.DeleteFunc(unitFiles, func(file dbus.UnitFile) bool {
+ _, name := filepath.Split(file.Path)
+ for _, p := range patterns {
+ if ok, _ := filepath.Match(p, name); ok {
+ return false
+ }
+ }
+ return true
+ })
+
+ return unitFiles, nil
+}
+
+var mockSystemdUnits = []dbus.UnitStatus{
+ {Name: `proc-sys-fs-binfmt_misc.automount`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `dev-nvme0n1.device`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `sys-devices-virtual-net-loopback1.device`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `sys-module-fuse.device`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `dev-disk-by\x2duuid-DE44\x2dCEE0.device`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `var-lib-nfs-rpc_pipefs.mount`, LoadState: "loaded", ActiveState: "inactive"},
+ {Name: `var.mount`, LoadState: "not-found", ActiveState: "inactive"},
+ {Name: `run-user-1000.mount`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `tmp.mount`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `run-user-1000-gvfs.mount`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `org.cups.cupsd.path`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `systemd-ask-password-wall.path`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `systemd-ask-password-console.path`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `init.scope`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `session-3.scope`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `session-6.scope`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `session-1.scope`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `session-2.scope`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `systemd-fsck-root.service`, LoadState: "loaded", ActiveState: "inactive"},
+ {Name: `httpd.service`, LoadState: "not-found", ActiveState: "inactive"},
+ {Name: `user-runtime-dir@1000.service`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `systemd-ask-password-wall.service`, LoadState: "loaded", ActiveState: "inactive"},
+ {Name: `user@1000.service`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `user.slice`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `system-getty.slice`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `system-netctl.slice`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `system.slice`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `system-systemd\x2dfsck.slice`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `lvm2-lvmpolld.socket`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `docker.socket`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `systemd-udevd-kernel.socket`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `dbus.socket`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `lvm2-lvmetad.socket`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `getty-pre.target`, LoadState: "loaded", ActiveState: "inactive"},
+ {Name: `rpc_pipefs.target`, LoadState: "loaded", ActiveState: "inactive"},
+ {Name: `remote-fs-pre.target`, LoadState: "loaded", ActiveState: "inactive"},
+ {Name: `sysinit.target`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `sound.target`, LoadState: "loaded", ActiveState: "active"},
+
+ {Name: `man-db.timer`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `pamac-mirrorlist.timer`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `pamac-cleancache.timer`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `shadow.timer`, LoadState: "loaded", ActiveState: "active"},
+ {Name: `logrotate.timer`, LoadState: "loaded", ActiveState: "active"},
+}
+
+var mockSystemdUnitFiles = []dbus.UnitFile{
+ {Path: "/lib/systemd/system/systemd-tmpfiles-clean.timer", Type: "static"},
+ {Path: "/lib/systemd/system/sysstat-summary.timer", Type: "disabled"},
+ {Path: "/lib/systemd/system/sysstat-collect.timer", Type: "disabled"},
+ {Path: "/lib/systemd/system/pg_dump@.timer", Type: "disabled"},
+
+ {Path: "/lib/systemd/system/veritysetup.target", Type: "static"},
+ {Path: "/lib/systemd/system/veritysetup-pre.target", Type: "static"},
+ {Path: "/lib/systemd/system/usb-gadget.target", Type: "static"},
+ {Path: "/lib/systemd/system/umount.target", Type: "static"},
+
+ {Path: "/lib/systemd/system/syslog.socket", Type: "static"},
+ {Path: "/lib/systemd/system/ssh.socket", Type: "disabled"},
+ {Path: "/lib/systemd/system/docker.socket", Type: "enabled"},
+ {Path: "/lib/systemd/system/dbus.socket", Type: "static"},
+
+ {Path: "/lib/systemd/system/user.slice", Type: "static"},
+ {Path: "/lib/systemd/system/system-systemd\x2dcryptsetup.slice", Type: "static"},
+ {Path: "/lib/systemd/system/machine.slice", Type: "static"},
+
+ {Path: "/run/systemd/generator.late/sendmail.service", Type: "generated"},
+ {Path: "/run/systemd/generator.late/monit.service", Type: "generated"},
+ {Path: "/lib/systemd/system/x11-common.service", Type: "masked"},
+ {Path: "/lib/systemd/system/uuidd.service", Type: "indirect"},
+
+ {Path: "/run/systemd/transient/session-144.scope", Type: "transient"},
+ {Path: "/run/systemd/transient/session-139.scope", Type: "transient"},
+ {Path: "/run/systemd/transient/session-132.scope", Type: "transient"},
+
+ {Path: "/lib/systemd/system/systemd-ask-password-wall.path", Type: "static"},
+ {Path: "/lib/systemd/system/systemd-ask-password-console.path", Type: "static"},
+ {Path: "/lib/systemd/system/postfix-resolvconf.path", Type: "disabled"},
+ {Path: "/lib/systemd/system/ntpsec-systemd-netif.path", Type: "enabled"},
+
+ {Path: "/run/systemd/generator/media-cdrom0.mount", Type: "generated"},
+ {Path: "/run/systemd/generator/boot.mount", Type: "generated"},
+ {Path: "/run/systemd/generator/-.mount", Type: "generated"},
+ {Path: "/lib/systemd/system/sys-kernel-tracing.mount", Type: "static"},
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.json b/src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.json
new file mode 100644
index 000000000..1ab5b47ea
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.json
@@ -0,0 +1,13 @@
+{
+ "update_every": 123,
+ "timeout": 123.123,
+ "include": [
+ "ok"
+ ],
+ "skip_transient": true,
+ "collect_unit_files": true,
+ "collect_unit_files_every": 123.123,
+ "include_unit_files": [
+ "ok"
+ ]
+}
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.yaml
new file mode 100644
index 000000000..d1894aea1
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/systemdunits/testdata/config.yaml
@@ -0,0 +1,9 @@
+update_every: 123
+timeout: 123.123
+include:
+ - ok
+skip_transient: true
+collect_unit_files: true
+collect_unit_files_every: 123.123
+include_unit_files:
+ - ok