Compare commits

...

6 Commits

Author SHA1 Message Date
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
3 changed files with 126 additions and 93 deletions

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,29 @@ 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 must take
these suffixes into consideration.
## 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
} }