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
import (
"strconv"
"fmt"
"strings"
"github.com/StackExchange/wmi"
"github.com/prometheus-community/windows_exporter/log"
"github.com/newrelic-forks/windows_exporter/log"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc/mgr"
"gopkg.in/alecthomas/kingpin.v2"
)
@ -21,6 +22,10 @@ var (
"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.",
).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
@ -40,6 +45,9 @@ func NewserviceCollector() (Collector, error) {
if *serviceWhereClause == "" {
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{
Information: prometheus.NewDesc(
@ -73,10 +81,17 @@ func NewserviceCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *serviceCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting service metrics:", desc, err)
if *useAPI {
if err := c.collectAPI(ch); err != nil {
log.Error("failed collecting API service metrics:", err)
return err
}
} else {
if err := c.collectWMI(ch); err != nil {
log.Error("failed collecting WMI service metrics:", err)
return err
}
}
return nil
}
@ -103,6 +118,15 @@ var (
"paused",
"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{
"boot",
"system",
@ -110,6 +134,13 @@ var (
"manual",
"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{
"ok",
"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
q := queryAllWhere(&dst, c.queryWhereClause)
if err := wmi.Query(q, &dst); err != nil {
return nil, err
return err
}
for _, service := range dst {
pid := strconv.FormatUint(uint64(service.ProcessId), 10)
pid := fmt.Sprintf("%d", uint64(service.ProcessId))
runAs := ""
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'`
### `--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
Name | Description | Type | Labels
@ -50,7 +54,7 @@ A service can have the following start modes:
- `manual`
- `disabled`
### Status
### Status (not available in API mode)
A service can have any of the following statuses:
- `ok`