Collect services using windows api

Signed-off-by: Carlos Castro <ccastro@newrelic.com>
This commit is contained in:
Carlos Castro 2021-08-23 16:19:40 +01:00
parent 1b96bb6d08
commit a9ac2d4672
No known key found for this signature in database
GPG Key ID: F170A867AE8B0492
2 changed files with 124 additions and 11 deletions

View File

@ -3,12 +3,13 @@
package collector package collector
import ( import (
"strconv" "fmt"
"strings" "strings"
"github.com/StackExchange/wmi" "github.com/newrelic-forks/windows_exporter/log"
"github.com/prometheus-community/windows_exporter/log"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc/mgr"
"gopkg.in/alecthomas/kingpin.v2" "gopkg.in/alecthomas/kingpin.v2"
) )
@ -21,6 +22,10 @@ var (
"collector.service.services-where", "collector.service.services-where",
"WQL 'where' clause to use in WMI metrics query. Limits the response to the services you specify and reduces the size of the response.", "WQL 'where' clause to use in WMI metrics query. Limits the response to the services you specify and reduces the size of the response.",
).Default("").String() ).Default("").String()
useAPI = kingpin.Flag(
"collector.service.use-api",
"Use API calls to collect service data instead of WMI. Flag 'collector.service.services-where' won't be effective.",
).Default("false").Bool()
) )
// A serviceCollector is a Prometheus collector for WMI Win32_Service metrics // A serviceCollector is a Prometheus collector for WMI Win32_Service metrics
@ -40,6 +45,9 @@ func NewserviceCollector() (Collector, error) {
if *serviceWhereClause == "" { if *serviceWhereClause == "" {
log.Warn("No where-clause specified for service collector. This will generate a very large number of metrics!") log.Warn("No where-clause specified for service collector. This will generate a very large number of metrics!")
} }
if *useAPI {
log.Warn("API collection is enabled.")
}
return &serviceCollector{ return &serviceCollector{
Information: prometheus.NewDesc( Information: prometheus.NewDesc(
@ -73,10 +81,17 @@ func NewserviceCollector() (Collector, error) {
// Collect sends the metric values for each metric // Collect sends the metric values for each metric
// to the provided prometheus Metric channel. // to the provided prometheus Metric channel.
func (c *serviceCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error { func (c *serviceCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil { if *useAPI {
log.Error("failed collecting service metrics:", desc, err) if err := c.collectAPI(ch); err != nil {
log.Error("failed collecting API service metrics:", err)
return err return err
} }
} else {
if err := c.collectWMI(ch); err != nil {
log.Error("failed collecting WMI service metrics:", err)
return err
}
}
return nil return nil
} }
@ -103,6 +118,15 @@ var (
"paused", "paused",
"unknown", "unknown",
} }
apiStateValues = map[uint]string{
windows.SERVICE_CONTINUE_PENDING: "continue pending",
windows.SERVICE_PAUSE_PENDING: "pause pending",
windows.SERVICE_PAUSED: "paused",
windows.SERVICE_RUNNING: "running",
windows.SERVICE_START_PENDING: "start pending",
windows.SERVICE_STOP_PENDING: "stop pending",
windows.SERVICE_STOPPED: "stopped",
}
allStartModes = []string{ allStartModes = []string{
"boot", "boot",
"system", "system",
@ -110,6 +134,13 @@ var (
"manual", "manual",
"disabled", "disabled",
} }
apiStartModeValues = map[uint32]string{
windows.SERVICE_AUTO_START: "auto",
windows.SERVICE_BOOT_START: "boot",
windows.SERVICE_DEMAND_START: "manual",
windows.SERVICE_DISABLED: "disabled",
windows.SERVICE_SYSTEM_START: "system",
}
allStatuses = []string{ allStatuses = []string{
"ok", "ok",
"error", "error",
@ -126,14 +157,14 @@ var (
} }
) )
func (c *serviceCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { func (c *serviceCollector) collectWMI(ch chan<- prometheus.Metric) error {
var dst []Win32_Service var dst []Win32_Service
q := queryAllWhere(&dst, c.queryWhereClause) q := queryAllWhere(&dst, c.queryWhereClause)
if err := wmi.Query(q, &dst); err != nil { if err := wmi.Query(q, &dst); err != nil {
return nil, err return err
} }
for _, service := range dst { for _, service := range dst {
pid := strconv.FormatUint(uint64(service.ProcessId), 10) pid := fmt.Sprintf("%d", uint64(service.ProcessId))
runAs := "" runAs := ""
if service.StartName != nil { if service.StartName != nil {
@ -191,5 +222,83 @@ func (c *serviceCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
) )
} }
} }
return nil, nil return nil
}
func (c *serviceCollector) collectAPI(ch chan<- prometheus.Metric) error {
svcmgrConnection, err := mgr.Connect()
if err != nil {
return err
}
defer svcmgrConnection.Disconnect()
// List All Services from the Services Manager
serviceList, err := svcmgrConnection.ListServices()
if err != nil {
return err
}
// Iterate through the Services List
for _, service := range serviceList {
// Retrieve handle for each service
serviceHandle, err := svcmgrConnection.OpenService(service)
if err != nil {
continue
}
// Get Service Configuration
serviceConfig, err := serviceHandle.Config()
if err != nil {
_ = serviceHandle.Close()
continue
}
// Get Service Current Status
serviceStatus, err := serviceHandle.Query()
if err != nil {
_ = serviceHandle.Close()
continue
}
pid := fmt.Sprintf("%d", uint64(serviceStatus.ProcessId))
ch <- prometheus.MustNewConstMetric(
c.Information,
prometheus.GaugeValue,
1.0,
strings.ToLower(service),
serviceConfig.DisplayName,
pid,
serviceConfig.ServiceStartName,
)
for _, state := range apiStateValues {
isCurrentState := 0.0
if state == apiStateValues[uint(serviceStatus.State)] {
isCurrentState = 1.0
}
ch <- prometheus.MustNewConstMetric(
c.State,
prometheus.GaugeValue,
isCurrentState,
strings.ToLower(service),
state,
)
}
for _, startMode := range apiStartModeValues {
isCurrentStartMode := 0.0
if startMode == apiStartModeValues[serviceConfig.StartType] {
isCurrentStartMode = 1.0
}
ch <- prometheus.MustNewConstMetric(
c.StartMode,
prometheus.GaugeValue,
isCurrentStartMode,
strings.ToLower(service),
startMode,
)
}
}
return nil
} }

View File

@ -18,6 +18,10 @@ Example: `--collector.service.services-where="Name='windows_exporter'"`
Example config win_exporter.yml for multiple services: `services-where: Name='SQLServer' OR Name='Couchbase' OR Name='Spooler' OR Name='ActiveMQ'` Example config win_exporter.yml for multiple services: `services-where: Name='SQLServer' OR Name='Couchbase' OR Name='Spooler' OR Name='ActiveMQ'`
### `--collector.service.use-api`
Uses API calls instead of WMI for performance optimization. **Note** the previous flag (`--collector.service.services-where`) won't have any effect on this mode.
## Metrics ## Metrics
Name | Description | Type | Labels Name | Description | Type | Labels
@ -50,7 +54,7 @@ A service can have the following start modes:
- `manual` - `manual`
- `disabled` - `disabled`
### Status ### Status (not available in API mode)
A service can have any of the following statuses: A service can have any of the following statuses:
- `ok` - `ok`