service: Add parameter to filer service by start mode (#2405)

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
Jan-Otto Kröpke
2026-05-23 15:23:25 +02:00
committed by GitHub
parent d82a387e5b
commit a40424d8f1
3 changed files with 64 additions and 16 deletions

View File

@@ -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 |

View File

@@ -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
}

View File

@@ -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)),