diff --git a/docs/collector.service.md b/docs/collector.service.md index 7b6a01b0..92a6da4c 100644 --- a/docs/collector.service.md +++ b/docs/collector.service.md @@ -2,11 +2,11 @@ The service collector exposes metrics about Windows Services -||| --|- -Metric name prefix | `service` -Classes | none -Enabled by default? | Yes +| | | +|---------------------|-----------| +| Metric name prefix | `service` | +| Classes | none | +| Enabled by default? | Yes | ## Flags @@ -22,6 +22,12 @@ Regexp of service to include. Process name (not the display name!) must both match `include` and not match `exclude` to be included. Recommended to keep down number of returned metrics. +### `--collector.service.start-mode-include` + +Comma separated list of service start modes to include. +Possible values: auto, manual, disabled, system. +Recommended to keep down number of returned metrics. + ## Metrics | Name | Description | Type | Labels | diff --git a/internal/collector/service/service.go b/internal/collector/service/service.go index 5a1b8573..7f9dfeca 100644 --- a/internal/collector/service/service.go +++ b/internal/collector/service/service.go @@ -22,8 +22,11 @@ import ( "errors" "fmt" "log/slog" + "maps" "regexp" + "slices" "strconv" + "strings" "sync" "time" "unsafe" @@ -39,14 +42,16 @@ import ( const Name = "service" type Config struct { - ServiceInclude *regexp.Regexp `yaml:"include"` - ServiceExclude *regexp.Regexp `yaml:"exclude"` + ServiceInclude *regexp.Regexp `yaml:"include"` + ServiceExclude *regexp.Regexp `yaml:"exclude"` + ServiceStartModeInclude []string `yaml:"start-mode-include"` } //nolint:gochecknoglobals var ConfigDefaults = Config{ - ServiceInclude: types.RegExpAny, - ServiceExclude: types.RegExpEmpty, + ServiceInclude: types.RegExpAny, + ServiceExclude: types.RegExpEmpty, + ServiceStartModeInclude: []string{"auto", "boot", "manual", "disabled", "system"}, } // A Collector is a Prometheus Collector for service metrics. @@ -84,6 +89,10 @@ func New(config *Config) *Collector { config.ServiceInclude = ConfigDefaults.ServiceInclude } + if config.ServiceStartModeInclude == nil { + config.ServiceStartModeInclude = ConfigDefaults.ServiceStartModeInclude + } + c := &Collector{ config: *config, } @@ -95,8 +104,9 @@ func NewWithFlags(app *kingpin.Application) *Collector { c := &Collector{ config: ConfigDefaults, } + c.config.ServiceStartModeInclude = make([]string, 0) - var serviceExclude, serviceInclude string + var serviceExclude, serviceInclude, serviceStartModeInclude string app.Flag( "collector.service.exclude", @@ -108,6 +118,11 @@ func NewWithFlags(app *kingpin.Application) *Collector { "Regexp of service to include. Process name (not the display name!) must both match include and not match exclude to be included.", ).Default(".+").StringVar(&serviceInclude) + app.Flag( + "collector.service.start-mode-include", + "Comma separated list of service start modes to include. Possible values: auto, boot, manual, disabled, system.", + ).Default(strings.Join(ConfigDefaults.ServiceStartModeInclude, ",")).StringVar(&serviceStartModeInclude) + app.Action(func(*kingpin.ParseContext) error { var err error @@ -121,6 +136,8 @@ func NewWithFlags(app *kingpin.Application) *Collector { return fmt.Errorf("collector.process.include: %w", err) } + c.config.ServiceStartModeInclude = strings.Split(serviceStartModeInclude, ",") + return nil }) @@ -134,10 +151,6 @@ func (c *Collector) GetName() string { func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { c.logger = logger.With(slog.String("collector", Name)) - if c.config.ServiceInclude.String() == "^(?:.*)$" && c.config.ServiceExclude.String() == "^(?:)$" { - c.logger.Warn("No filters specified for service collector. This will generate a very large number of metrics!") - } - c.serviceConfigPoolBytes = sync.Pool{ New: func() any { return new([]byte) @@ -195,6 +208,14 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { return fmt.Errorf("failed to open scm: %w", err) } + for _, startMode := range c.config.ServiceStartModeInclude { + if !slices.Contains(slices.Collect(maps.Values(c.apiStartModeValues)), startMode) { + return fmt.Errorf("unknown start mode: %s. Possible values: %s", startMode, + strings.Join(slices.Collect(maps.Values(c.apiStartModeValues)), ", "), + ) + } + } + c.serviceManagerHandle = &mgr.Mgr{Handle: handle} return nil @@ -297,6 +318,25 @@ func (c *Collector) collectService(ch chan<- prometheus.Metric, serviceName stri ) } + serviceStartMode, ok := c.apiStartModeValues[serviceConfig.StartType] + if !ok { + c.logger.Log(context.Background(), slog.LevelWarn, "unknown service start mode", + slog.String("service", serviceName), + slog.Uint64("start_mode", uint64(serviceConfig.StartType)), + ) + + return nil + } + + if !slices.Contains(c.config.ServiceStartModeInclude, serviceStartMode) { + c.logger.Log(context.Background(), slog.LevelDebug, "service start mode excluded by config", + slog.String("service", serviceName), + slog.String("start_mode", serviceStartMode), + ) + + return nil + } + ch <- prometheus.MustNewConstMetric( c.info, prometheus.GaugeValue, @@ -314,7 +354,7 @@ func (c *Collector) collectService(ch chan<- prometheus.Metric, serviceName stri for _, startMode := range c.apiStartModeValues { isCurrentStartMode = 0.0 - if startMode == c.apiStartModeValues[serviceConfig.StartType] { + if startMode == serviceStartMode { isCurrentStartMode = 1.0 } diff --git a/internal/headers/shell32/shell32.go b/internal/headers/shell32/shell32.go index 83213faa..ce60b5c3 100644 --- a/internal/headers/shell32/shell32.go +++ b/internal/headers/shell32/shell32.go @@ -36,6 +36,8 @@ var ( iidIShellItem2 = ole.NewGUID("{7E9FB0D3-919F-4307-AB2E-9B1860310C93}") ) +// SHCreateItemFromParsingName creates and initializes a Shell item object from a parsing name. +// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-shcreateitemfromparsingname func SHCreateItemFromParsingName(path string) (*IShellItem2, error) { ptrPath, err := windows.UTF16PtrFromString(path) if err != nil { @@ -44,7 +46,7 @@ func SHCreateItemFromParsingName(path string) (*IShellItem2, error) { var result *IShellItem2 - hr, _, err := procSHCreateItemFromParsingName.Call( + hr, _, _ := procSHCreateItemFromParsingName.Call( uintptr(unsafe.Pointer(ptrPath)), 0, uintptr(unsafe.Pointer(iidIShellItem2)),