diff options
author | Ilya Mashchenko <ilya@netdata.cloud> | 2024-05-07 12:16:08 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-07 12:16:08 +0300 |
commit | 50c3b9181284938ebe225fbd191030b7fb7baf9c (patch) | |
tree | 46143763d6ab45757bc4deb1b64f23e4d4a3660d /src | |
parent | 4ab59ea27e568f5d3954e6de48299bf9d4e8d452 (diff) |
go.d systemdunits add unit files state (#17606)
Diffstat (limited to 'src')
12 files changed, 674 insertions, 222 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/charts.go b/src/go/collectors/go.d.plugin/modules/systemdunits/charts.go index 210fc568d6..18d8838fbc 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/charts.go +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/charts.go @@ -7,6 +7,8 @@ package systemdunits import ( "fmt" + "path/filepath" + "strings" "github.com/netdata/netdata/go/go.d.plugin/agent/module" @@ -15,62 +17,102 @@ import ( ) const ( - prioServiceUnitState = module.Priority + iota - prioSocketUnitState - prioTargetUnitState - prioPathUnitState - prioDeviceUnitState - prioMountUnitState - prioAutomountUnitState - prioSwapUnitState - prioTimerUnitState - prioScopeUnitState - prioSliceUnitState + prioUnitState = module.Priority + iota + prioUnitFileState ) -var prioMap = map[string]int{ - unitTypeService: prioServiceUnitState, - unitTypeSocket: prioSocketUnitState, - unitTypeTarget: prioTargetUnitState, - unitTypePath: prioPathUnitState, - unitTypeDevice: prioDeviceUnitState, - unitTypeMount: prioMountUnitState, - unitTypeAutomount: prioAutomountUnitState, - unitTypeSwap: prioSwapUnitState, - unitTypeTimer: prioTimerUnitState, - unitTypeScope: prioScopeUnitState, - unitTypeSlice: prioSliceUnitState, -} - -func newTypedUnitStateChartTmpl(name, typ string) *module.Chart { +func (s *SystemdUnits) addUnitCharts(name, typ string) { chart := module.Chart{ - ID: fmt.Sprintf("unit_%s_%s_state", name, typ), - Title: fmt.Sprintf("%s Unit State", cases.Title(language.English, cases.Compact).String(typ)), + ID: "unit_%s_%s_state", + Title: "%s Unit State", Units: "state", - Fam: fmt.Sprintf("%s units", typ), - Ctx: fmt.Sprintf("systemd.%s_unit_state", typ), - Priority: prioMap[typ], + Fam: "%s units", + Ctx: "systemd.%s_unit_state", + Priority: prioUnitState, Labels: []module.Label{ {Key: "unit_name", Value: name}, }, Dims: module.Dims{ - {Name: unitStateActive}, - {Name: unitStateInactive}, - {Name: unitStateActivating}, - {Name: unitStateDeactivating}, - {Name: unitStateFailed}, + {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("unit_%s_%s_state_%s", name, typ, d.Name) + d.ID = fmt.Sprintf(d.ID, name, typ, d.Name) + } + + if err := s.Charts().Add(&chart); err != nil { + s.Warning(err) } - return &chart } -func (s *SystemdUnits) addUnitToCharts(name, typ string) { - chart := newTypedUnitStateChartTmpl(name, typ) +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 { + 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 index a2787c4ec1..b1152865f4 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/client.go +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/client.go @@ -19,6 +19,7 @@ type systemdConnection interface { GetManagerProperty(string) (string, 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{} diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/collect.go b/src/go/collectors/go.d.plugin/modules/systemdunits/collect.go index eb596605fc..0d61c9998e 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/collect.go +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/collect.go @@ -6,45 +6,9 @@ package systemdunits import ( - "context" "fmt" "regexp" "strconv" - "strings" - - "github.com/coreos/go-systemd/v22/dbus" -) - -const ( - // https://www.freedesktop.org/software/systemd/man/systemd.html - unitStateActive = "active" - unitStateInactive = "inactive" - unitStateActivating = "activating" - unitStateDeactivating = "deactivating" - unitStateFailed = "failed" - - // https://www.freedesktop.org/software/systemd/man/systemd.html - unitTypeService = "service" - unitTypeSocket = "socket" - unitTypeTarget = "target" - unitTypePath = "path" - unitTypeDevice = "device" - unitTypeMount = "mount" - unitTypeAutomount = "automount" - unitTypeSwap = "swap" - unitTypeTimer = "timer" - unitTypeScope = "scope" - unitTypeSlice = "slice" -) - -var ( - unitStates = []string{ - unitStateActive, - unitStateActivating, - unitStateFailed, - unitStateInactive, - unitStateDeactivating, - } ) func (s *SystemdUnits) collect() (map[string]int64, error) { @@ -62,47 +26,23 @@ func (s *SystemdUnits) collect() (map[string]int64, error) { s.systemdVersion = ver } - var units []dbus.UnitStatus - 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 { + mx := make(map[string]int64) + + if err := s.collectUnits(mx, conn); err != nil { s.closeConnection() return nil, err } - if len(units) == 0 { - return nil, nil + if s.CollectUnitFiles && len(s.IncludeUnitFiles) > 0 { + if err := s.collectUnitFiles(mx, conn); err != nil { + s.closeConnection() + return mx, err + } } - mx := make(map[string]int64) - s.collectUnitsStates(mx, units) - return mx, nil } -func (s *SystemdUnits) collectUnitsStates(mx map[string]int64, units []dbus.UnitStatus) { - for _, unit := range units { - name, typ := extractUnitNameType(cleanUnitName(unit.Name)) - if name == "" || typ == "" { - continue - } - - if !s.units[unit.Name] { - s.units[unit.Name] = true - s.addUnitToCharts(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 - } -} - func (s *SystemdUnits) getConnection() (systemdConnection, error) { if s.conn == nil { conn, err := s.client.connect() @@ -146,66 +86,3 @@ func (s *SystemdUnits) getSystemdVersion(conn systemdConnection) (int, error) { return ver, 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) - } - - loaded := units[:0] - for _, unit := range units { - if unit.LoadState == "loaded" && s.sr.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) - } - - 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 extractUnitNameType(name string) (string, string) { - idx := strings.LastIndexByte(name, '.') - if idx <= 0 { - return "", "" - } - return name[:idx], name[idx+1:] -} - -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/collect_unit_files.go b/src/go/collectors/go.d.plugin/modules/systemdunits/collect_unit_files.go new file mode 100644 index 0000000000..c1b12cf119 --- /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, []string{"*.service"}) + 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 0000000000..4cfc89d604 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/collect_units.go @@ -0,0 +1,151 @@ +// 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 ( + // 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.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) + } + } + } + + 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 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 index 8dc57a3d86..c13cabc6ec 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/config_schema.json +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/config_schema.json @@ -34,6 +34,36 @@ "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": [ @@ -48,11 +78,35 @@ "uiOptions": { "fullPage": true }, + "ui:flavour": "tabs", + "ui:options": { + "tabs": [ + { + "title": "Base", + "fields": [ + "update_every", + "timeout", + "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)." }, "include": { "ui:listFlavour": "list" + }, + "include_unit_files": { + "ui:listFlavour": "list" } } } diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/init.go b/src/go/collectors/go.d.plugin/modules/systemdunits/init.go index e59290ace4..ea3d21d379 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/init.go +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/init.go @@ -19,7 +19,7 @@ func (s *SystemdUnits) validateConfig() error { return nil } -func (s *SystemdUnits) initSelector() (matcher.Matcher, error) { +func (s *SystemdUnits) initUnitSelector() (matcher.Matcher, error) { if len(s.Include) == 0 { return matcher.TRUE(), nil } diff --git a/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml b/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml index 21755bb698..6ca804fb30 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/metadata.yaml @@ -21,7 +21,7 @@ modules: overview: data_collection: metrics_description: | - This collector monitors Systemd units state. + This collector monitors the state of Systemd units and unit files. method_description: "" supported_platforms: include: [] @@ -57,8 +57,12 @@ modules: 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 filter. + description: Systemd units selector. default_value: "*.service" required: false detailed_description: | @@ -73,10 +77,30 @@ modules: - pattern1 - pattern2 ``` - - name: timeout - description: System bus requests timeout. - default_value: 1 + - 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 @@ -288,3 +312,29 @@ modules: - 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 index 345b2525a6..a6e82cbcbe 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits.go +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits.go @@ -13,6 +13,8 @@ import ( "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" @@ -31,22 +33,26 @@ func init() { func New() *SystemdUnits { return &SystemdUnits{ Config: Config{ - Timeout: web.Duration(time.Second * 2), - Include: []string{ - "*.service", - }, + Timeout: web.Duration(time.Second * 2), + Include: []string{"*.service"}, + CollectUnitFiles: false, + IncludeUnitFiles: []string{"*.service"}, + CollectUnitFilesEvery: web.Duration(time.Minute * 5), }, - - charts: &module.Charts{}, - client: newSystemdDBusClient(), - units: make(map[string]bool), + charts: &module.Charts{}, + client: newSystemdDBusClient(), + seenUnits: make(map[string]bool), + seenUnitFiles: make(map[string]bool), } } type Config struct { - UpdateEvery int `yaml:"update_every" json:"update_every"` - Timeout web.Duration `yaml:"timeout" json:"timeout"` - Include []string `yaml:"include" json:"include"` + UpdateEvery int `yaml:"update_every" json:"update_every"` + Timeout web.Duration `yaml:"timeout" json:"timeout"` + Include []string `yaml:"include" json:"include"` + CollectUnitFiles bool `yaml:"collect_unit_files" json:"collect_unit_files"` + IncludeUnitFiles []string `yaml:"include_unit_files" json:"include_unit_files"` + CollectUnitFilesEvery web.Duration `yaml:"collect_unit_files_every" json:"collect_unit_files_every"` } type SystemdUnits struct { @@ -57,8 +63,13 @@ type SystemdUnits struct { conn systemdConnection systemdVersion int - units map[string]bool - sr matcher.Matcher + + seenUnits map[string]bool + unitSr matcher.Matcher + + lastListUnitFilesTime time.Time + cachedUnitFiles []dbus.UnitFile + seenUnitFiles map[string]bool charts *module.Charts } @@ -68,21 +79,22 @@ func (s *SystemdUnits) Configuration() any { } func (s *SystemdUnits) Init() error { - err := s.validateConfig() - if err != nil { + if err := s.validateConfig(); err != nil { s.Errorf("config validation: %v", err) return err } - sr, err := s.initSelector() + sr, err := s.initUnitSelector() if err != nil { - s.Errorf("init selector: %v", err) + s.Errorf("init unit selector: %v", err) return err } - s.sr = sr + s.unitSr = sr - s.Debugf("unit names patterns: %v", s.Include) 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 } @@ -93,9 +105,11 @@ func (s *SystemdUnits) Check() error { s.Error(err) return err } + if len(mx) == 0 { return errors.New("no metrics collected") } + return nil } 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 index 3a1a594247..d87ae66782 100644 --- a/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits_test.go +++ b/src/go/collectors/go.d.plugin/modules/systemdunits/systemdunits_test.go @@ -11,6 +11,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "testing" "github.com/netdata/netdata/go/go.d.plugin/agent/module" @@ -168,7 +169,7 @@ func TestSystemdUnits_Collect(t *testing.T) { prepare func() *SystemdUnits wantCollected map[string]int64 }{ - "success on systemd v230+ on collecting all unit type": { + "success v230+ on collecting all unit type": { prepare: func() *SystemdUnits { systemd := New() systemd.Include = []string{"*"} @@ -383,7 +384,7 @@ func TestSystemdUnits_Collect(t *testing.T) { "unit_var-lib-nfs-rpc_pipefs_mount_state_inactive": 1, }, }, - "success on systemd v230- on collecting all unit types": { + "success v230- on collecting all unit types": { prepare: func() *SystemdUnits { systemd := New() systemd.Include = []string{"*"} @@ -598,7 +599,7 @@ func TestSystemdUnits_Collect(t *testing.T) { "unit_var-lib-nfs-rpc_pipefs_mount_state_inactive": 1, }, }, - "success on systemd v230+ on collecting only 'service' unit type": { + "success v230+ on collecting only 'service' units": { prepare: func() *SystemdUnits { systemd := New() systemd.Include = []string{"*.service"} @@ -628,7 +629,7 @@ func TestSystemdUnits_Collect(t *testing.T) { "unit_user@1000_service_state_inactive": 0, |