Compare commits

...

11 Commits

Author SHA1 Message Date
Calle Pettersson
7890c9ce91 Merge pull request #506 from martinlindhe/fix-adfs-dependencies
adfs collector missing dependency
2020-04-19 21:51:47 +02:00
Calle Pettersson
bcb6f2b218 adfs collector missing dependency 2020-04-19 21:44:39 +02:00
Calle Pettersson
91a64fecb8 Merge pull request #498 from Mario-Hofstaetter/master
Fix README for process whitelist and expand docs
2020-04-04 15:15:54 +02:00
Mario Hofstätter
9148728b87 Expand process collector docs to show more regexp (#497) 2020-04-03 21:05:05 +02:00
Mario Hofstätter
2290969596 Fix README to use new --collector.process.whitelist (#497)
With PR #489 `--collector.process.processes-where` no longer works, changing example to use `--collector.process.whitelist` with regexp
2020-04-03 20:49:11 +02:00
Calle Pettersson
1d7747b4d1 Merge pull request #473 from martinlindhe/remove-redirect
BREAKING: Remove redirect from unknown paths to /metrics
2020-03-28 13:35:46 +01:00
Calle Pettersson
cba42d24c1 Merge pull request #474 from martinlindhe/concurrency-limit
Add option to limit concurrent requests
2020-03-28 13:35:34 +01:00
Calle Pettersson
58d259a2b6 Merge pull request #489 from martinlindhe/process-perflib
BREAKING: Convert the process collector to use perflib
2020-03-27 20:15:10 +01:00
Calle Pettersson
4f89133893 Convert the process collector to use perflib 2020-03-24 22:46:24 +01:00
Calle Pettersson
df954ddf9d Remove redirect from unknown paths to /metrics 2020-03-02 22:46:50 +01:00
Calle Pettersson
34996b206a Add option to limit concurrent requests 2020-03-02 22:43:29 +01:00
5 changed files with 137 additions and 98 deletions

View File

@@ -97,11 +97,9 @@ The prometheus metrics will be exposed on [localhost:9182](http://localhost:9182
### Enable only process collector and specify a custom query ### Enable only process collector and specify a custom query
.\wmi_exporter.exe --collectors.enabled "process" --collector.process.processes-where "Name LIKE 'firefox%'" .\wmi_exporter.exe --collectors.enabled "process" --collector.process.whitelist="firefox.+"
When there are multiple processes with the same name, WMI represents those after the first instance as `process-name#index`. So to get them all, rather than just the first one, the query needs to be a wildcard search using a `%` character. When there are multiple processes with the same name, WMI represents those after the first instance as `process-name#index`. So to get them all, rather than just the first one, the [regular expression](https://en.wikipedia.org/wiki/Regular_expression) must use `.+`. See [process](docs/collector.process.md) for more information.
Please note that in Windows batch scripts (and when using the `cmd` command prompt), the `%` character is reserved, so it has to be escaped with another `%`. For example, the wildcard syntax for searching for all firefox processes is `firefox%%`.
## License ## License

View File

@@ -7,7 +7,7 @@ import (
) )
func init() { func init() {
registerCollector("adfs", newADFSCollector) registerCollector("adfs", newADFSCollector, "AD FS")
} }
type adfsCollector struct { type adfsCollector struct {

View File

@@ -3,6 +3,8 @@
package collector package collector
import ( import (
"fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
@@ -13,18 +15,21 @@ import (
) )
func init() { func init() {
registerCollector("process", NewProcessCollector) registerCollector("process", newProcessCollector, "Process")
} }
var ( var (
processWhereClause = kingpin.Flag( processWhitelist = kingpin.Flag(
"collector.process.processes-where", "collector.process.whitelist",
"WQL 'where' clause to use in WMI metrics query. Limits the response to the processes you specify and reduces the size of the response.", "Regexp of processes to include. Process name must both match whitelist and not match blacklist to be included.",
).Default(".*").String()
processBlacklist = kingpin.Flag(
"collector.process.blacklist",
"Regexp of processes to exclude. Process name must both match whitelist and not match blacklist to be included.",
).Default("").String() ).Default("").String()
) )
// A ProcessCollector is a Prometheus collector for WMI Win32_PerfRawData_PerfProc_Process metrics type processCollector struct {
type ProcessCollector struct {
StartTime *prometheus.Desc StartTime *prometheus.Desc
CPUTimeTotal *prometheus.Desc CPUTimeTotal *prometheus.Desc
HandleCount *prometheus.Desc HandleCount *prometheus.Desc
@@ -39,18 +44,19 @@ type ProcessCollector struct {
VirtualBytes *prometheus.Desc VirtualBytes *prometheus.Desc
WorkingSet *prometheus.Desc WorkingSet *prometheus.Desc
queryWhereClause string processWhitelistPattern *regexp.Regexp
processBlacklistPattern *regexp.Regexp
} }
// NewProcessCollector ... // NewProcessCollector ...
func NewProcessCollector() (Collector, error) { func newProcessCollector() (Collector, error) {
const subsystem = "process" const subsystem = "process"
if *processWhereClause == "" { if *processWhitelist == ".*" && *processBlacklist == "" {
log.Warn("No where-clause specified for process collector. This will generate a very large number of metrics!") log.Warn("No filters specified for process collector. This will generate a very large number of metrics!")
} }
return &ProcessCollector{ return &processCollector{
StartTime: prometheus.NewDesc( StartTime: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "start_time"), prometheus.BuildFQName(Namespace, subsystem, "start_time"),
"Time of process start.", "Time of process start.",
@@ -129,66 +135,53 @@ func NewProcessCollector() (Collector, error) {
[]string{"process", "process_id", "creating_process_id"}, []string{"process", "process_id", "creating_process_id"},
nil, nil,
), ),
queryWhereClause: *processWhereClause, processWhitelistPattern: regexp.MustCompile(fmt.Sprintf("^(?:%s)$", *processWhitelist)),
processBlacklistPattern: regexp.MustCompile(fmt.Sprintf("^(?:%s)$", *processBlacklist)),
}, nil }, nil
} }
// Collect sends the metric values for each metric type perflibProcess struct {
// to the provided prometheus Metric channel.
func (c *ProcessCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting process metrics:", desc, err)
return err
}
return nil
}
// Win32_PerfRawData_PerfProc_Process docs:
// - https://msdn.microsoft.com/en-us/library/aa394323(v=vs.85).aspx
type Win32_PerfRawData_PerfProc_Process struct {
Name string Name string
CreatingProcessID uint32 PercentProcessorTime float64 `perflib:"% Processor Time"`
ElapsedTime uint64 PercentPrivilegedTime float64 `perflib:"% Privileged Time"`
Frequency_Object uint64 PercentUserTime float64 `perflib:"% User Time"`
HandleCount uint32 CreatingProcessID float64 `perflib:"Creating Process ID"`
IDProcess uint32 ElapsedTime float64 `perflib:"Elapsed Time"`
IODataBytesPersec uint64 HandleCount float64 `perflib:"Handle Count"`
IODataOperationsPersec uint64 IDProcess float64 `perflib:"ID Process"`
IOOtherBytesPersec uint64 IODataBytesPerSec float64 `perflib:"IO Data Bytes/sec"`
IOOtherOperationsPersec uint64 IODataOperationsPerSec float64 `perflib:"IO Data Operations/sec"`
IOReadBytesPersec uint64 IOOtherBytesPerSec float64 `perflib:"IO Other Bytes/sec"`
IOReadOperationsPersec uint64 IOOtherOperationsPerSec float64 `perflib:"IO Other Operations/sec"`
IOWriteBytesPersec uint64 IOReadBytesPerSec float64 `perflib:"IO Read Bytes/sec"`
IOWriteOperationsPersec uint64 IOReadOperationsPerSec float64 `perflib:"IO Read Operations/sec"`
PageFaultsPersec uint32 IOWriteBytesPerSec float64 `perflib:"IO Write Bytes/sec"`
PageFileBytes uint64 IOWriteOperationsPerSec float64 `perflib:"IO Write Operations/sec"`
PageFileBytesPeak uint64 PageFaultsPerSec float64 `perflib:"Page Faults/sec"`
PercentPrivilegedTime uint64 PageFileBytesPeak float64 `perflib:"Page File Bytes Peak"`
PercentProcessorTime uint64 PageFileBytes float64 `perflib:"Page File Bytes"`
PercentUserTime uint64 PoolNonpagedBytes float64 `perflib:"Pool Nonpaged Bytes"`
PoolNonpagedBytes uint32 PoolPagedBytes float64 `perflib:"Pool Paged Bytes"`
PoolPagedBytes uint32 PriorityBase float64 `perflib:"Priority Base"`
PriorityBase uint32 PrivateBytes float64 `perflib:"Private Bytes"`
PrivateBytes uint64 ThreadCount float64 `perflib:"Thread Count"`
ThreadCount uint32 VirtualBytesPeak float64 `perflib:"Virtual Bytes Peak"`
Timestamp_Object uint64 VirtualBytes float64 `perflib:"Virtual Bytes"`
VirtualBytes uint64 WorkingSetPrivate float64 `perflib:"Working Set - Private"`
VirtualBytesPeak uint64 WorkingSetPeak float64 `perflib:"Working Set Peak"`
WorkingSet uint64 WorkingSet float64 `perflib:"Working Set"`
WorkingSetPeak uint64
WorkingSetPrivate uint64
} }
type WorkerProcess struct { type WorkerProcess struct {
AppPoolName string AppPoolName string
ProcessId uint32 ProcessId uint64
} }
func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { func (c *processCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
var dst []Win32_PerfRawData_PerfProc_Process data := make([]perflibProcess, 0)
q := queryAllWhere(&dst, c.queryWhereClause) err := unmarshalObject(ctx.perfObjects["Process"], &data)
if err := wmi.Query(q, &dst); err != nil { if err != nil {
return nil, err return err
} }
var dst_wp []WorkerProcess var dst_wp []WorkerProcess
@@ -197,9 +190,10 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
log.Debugf("Could not query WebAdministration namespace for IIS worker processes: %v. Skipping", err) log.Debugf("Could not query WebAdministration namespace for IIS worker processes: %v. Skipping", err)
} }
for _, process := range dst { for _, process := range data {
if process.Name == "_Total" ||
if process.Name == "_Total" { c.processBlacklistPattern.MatchString(process.Name) ||
!c.processWhitelistPattern.MatchString(process.Name) {
continue continue
} }
// Duplicate processes are suffixed # and an index number. Remove those. // Duplicate processes are suffixed # and an index number. Remove those.
@@ -208,7 +202,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
cpid := strconv.FormatUint(uint64(process.CreatingProcessID), 10) cpid := strconv.FormatUint(uint64(process.CreatingProcessID), 10)
for _, wp := range dst_wp { for _, wp := range dst_wp {
if wp.ProcessId == process.IDProcess { if wp.ProcessId == uint64(process.IDProcess) {
processName = strings.Join([]string{processName, wp.AppPoolName}, "_") processName = strings.Join([]string{processName, wp.AppPoolName}, "_")
break break
} }
@@ -217,8 +211,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.StartTime, c.StartTime,
prometheus.GaugeValue, prometheus.GaugeValue,
// convert from Windows timestamp (1 jan 1601) to unix timestamp (1 jan 1970) process.ElapsedTime,
float64(process.ElapsedTime-116444736000000000)/float64(process.Frequency_Object),
processName, processName,
pid, pid,
cpid, cpid,
@@ -227,7 +220,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.HandleCount, c.HandleCount,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.HandleCount), process.HandleCount,
processName, processName,
pid, pid,
cpid, cpid,
@@ -236,7 +229,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.CPUTimeTotal, c.CPUTimeTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.PercentPrivilegedTime)*ticksToSecondsScaleFactor, process.PercentPrivilegedTime,
processName, processName,
pid, pid,
cpid, cpid,
@@ -246,7 +239,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.CPUTimeTotal, c.CPUTimeTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.PercentUserTime)*ticksToSecondsScaleFactor, process.PercentUserTime,
processName, processName,
pid, pid,
cpid, cpid,
@@ -256,7 +249,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.IOBytesTotal, c.IOBytesTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.IOOtherBytesPersec), process.IOOtherBytesPerSec,
processName, processName,
pid, pid,
cpid, cpid,
@@ -266,7 +259,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.IOOperationsTotal, c.IOOperationsTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.IOOtherOperationsPersec), process.IOOtherOperationsPerSec,
processName, processName,
pid, pid,
cpid, cpid,
@@ -276,7 +269,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.IOBytesTotal, c.IOBytesTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.IOReadBytesPersec), process.IOReadBytesPerSec,
processName, processName,
pid, pid,
cpid, cpid,
@@ -286,7 +279,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.IOOperationsTotal, c.IOOperationsTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.IOReadOperationsPersec), process.IOReadOperationsPerSec,
processName, processName,
pid, pid,
cpid, cpid,
@@ -296,7 +289,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.IOBytesTotal, c.IOBytesTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.IOWriteBytesPersec), process.IOWriteBytesPerSec,
processName, processName,
pid, pid,
cpid, cpid,
@@ -306,7 +299,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.IOOperationsTotal, c.IOOperationsTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.IOWriteOperationsPersec), process.IOWriteOperationsPerSec,
processName, processName,
pid, pid,
cpid, cpid,
@@ -316,7 +309,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.PageFaultsTotal, c.PageFaultsTotal,
prometheus.CounterValue, prometheus.CounterValue,
float64(process.PageFaultsPersec), process.PageFaultsPerSec,
processName, processName,
pid, pid,
cpid, cpid,
@@ -325,7 +318,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.PageFileBytes, c.PageFileBytes,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.PageFileBytes), process.PageFileBytes,
processName, processName,
pid, pid,
cpid, cpid,
@@ -334,7 +327,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.PoolBytes, c.PoolBytes,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.PoolNonpagedBytes), process.PoolNonpagedBytes,
processName, processName,
pid, pid,
cpid, cpid,
@@ -344,7 +337,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.PoolBytes, c.PoolBytes,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.PoolPagedBytes), process.PoolPagedBytes,
processName, processName,
pid, pid,
cpid, cpid,
@@ -354,7 +347,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.PriorityBase, c.PriorityBase,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.PriorityBase), process.PriorityBase,
processName, processName,
pid, pid,
cpid, cpid,
@@ -363,7 +356,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.PrivateBytes, c.PrivateBytes,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.PrivateBytes), process.PrivateBytes,
processName, processName,
pid, pid,
cpid, cpid,
@@ -372,7 +365,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.ThreadCount, c.ThreadCount,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.ThreadCount), process.ThreadCount,
processName, processName,
pid, pid,
cpid, cpid,
@@ -381,7 +374,7 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.VirtualBytes, c.VirtualBytes,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.VirtualBytes), process.VirtualBytes,
processName, processName,
pid, pid,
cpid, cpid,
@@ -390,12 +383,12 @@ func (c *ProcessCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Des
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
c.WorkingSet, c.WorkingSet,
prometheus.GaugeValue, prometheus.GaugeValue,
float64(process.WorkingSet), process.WorkingSet,
processName, processName,
pid, pid,
cpid, cpid,
) )
} }
return nil, nil return nil
} }

View File

@@ -5,18 +5,37 @@ The process collector exposes metrics about processes
||| |||
-|- -|-
Metric name prefix | `process` Metric name prefix | `process`
Classes | [`Win32_PerfRawData_PerfProc_Process`](https://msdn.microsoft.com/en-us/library/aa394323(v=vs.85).aspx) Data source | Perflib
Counters | `Process`
Enabled by default? | No Enabled by default? | No
## Flags ## Flags
### `--collector.process.processes-where` ### `--collector.process.whitelist`
A WMI filter on which processes to include. Recommended to keep down number of returned metrics. Regexp of processes to include. Process name must both match whitelist and not
match blacklist to be included. Recommended to keep down number of returned
metrics.
`%` is a wildcard, and can be used to match on substrings. ### `--collector.process.blacklist`
Example: `--collector.process.processes-where="Name LIKE 'firefox%'` Regexp of processes to exclude. Process name must both match whitelist and not
match blacklist to be included. Recommended to keep down number of returned
metrics.
### Example
To match all firefox processes: `--collector.process.whitelist="firefox.+"`.
Note that multiple processes with the same name will be disambiguated by
Windows by adding a number suffix, such as `firefox#2`. Your [regexp](https://en.wikipedia.org/wiki/Regular_expression) must take
these suffixes into consideration.
:warning: The regexp is case-sensitive, so `--collector.process.whitelist="FIREFOX.+"` will **NOT** match a process named `firefox` .
To specify multiple names, use the pipe `|` character:
```
--collector.process.whitelist="firefox.+|FIREFOX.+|chrome.+"
```
This will match all processes named `firefox`, `FIREFOX` or `chrome` .
## Metrics ## Metrics

View File

@@ -265,6 +265,10 @@ func main() {
"telemetry.path", "telemetry.path",
"URL path for surfacing collected metrics.", "URL path for surfacing collected metrics.",
).Default("/metrics").String() ).Default("/metrics").String()
maxRequests = kingpin.Flag(
"telemetry.max-requests",
"Maximum number of concurrent requests. 0 to disable.",
).Default("5").Int()
enabledCollectors = kingpin.Flag( enabledCollectors = kingpin.Flag(
"collectors.enabled", "collectors.enabled",
"Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default."). "Comma-separated list of collectors to use. Use '[defaults]' as a placeholder for all the collectors enabled by default.").
@@ -332,10 +336,16 @@ func main() {
}, },
} }
http.Handle(*metricsPath, h) http.HandleFunc(*metricsPath, withConcurrencyLimit(*maxRequests, h.ServeHTTP))
http.HandleFunc("/health", healthCheck) http.HandleFunc("/health", healthCheck)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, *metricsPath, http.StatusMovedPermanently) _, _ = w.Write([]byte(`<html>
<head><title>WMI Exporter</title></head>
<body>
<h1>WMI Exporter</h1>
<p><a href="` + *metricsPath + `">Metrics</a></p>
</body>
</html>`))
}) })
log.Infoln("Starting WMI exporter", version.Info()) log.Infoln("Starting WMI exporter", version.Info())
@@ -370,6 +380,25 @@ func keys(m map[string]collector.Collector) []string {
return ret return ret
} }
func withConcurrencyLimit(n int, next http.HandlerFunc) http.HandlerFunc {
if n <= 0 {
return next
}
sem := make(chan struct{}, n)
return func(w http.ResponseWriter, r *http.Request) {
select {
case sem <- struct{}{}:
defer func() { <-sem }()
default:
w.WriteHeader(http.StatusServiceUnavailable)
_, _ = w.Write([]byte("Too many concurrent requests"))
return
}
next(w, r)
}
}
type wmiExporterService struct { type wmiExporterService struct {
stopCh chan<- bool stopCh chan<- bool
} }