Compare commits

..

1 Commits

Author SHA1 Message Date
Ashok Siyani
d9404c6cc2 added terminal services collector for number of session count 2020-03-23 09:40:40 +00:00
6 changed files with 182 additions and 137 deletions

View File

@@ -97,9 +97,11 @@ The prometheus metrics will be exposed on [localhost:9182](http://localhost:9182
### Enable only process collector and specify a custom query
.\wmi_exporter.exe --collectors.enabled "process" --collector.process.whitelist="firefox.+"
.\wmi_exporter.exe --collectors.enabled "process" --collector.process.processes-where "Name LIKE '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 [regular expression](https://en.wikipedia.org/wiki/Regular_expression) must use `.+`. See [process](docs/collector.process.md) for more information.
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.
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

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
// +build windows
package collector
import (
"errors"
"github.com/StackExchange/wmi"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
)
func init() {
registerCollector("terminal_services", NewTerminalServicesCollector)
}
// A TerminalServicesCollector is a Prometheus collector for WMI
// Win32_PerfRawData_LocalSessionManager_TerminalServices & Win32_PerfRawData_TermService_TerminalServicesSession metrics
type TerminalServicesCollector struct {
Local_session_count *prometheus.Desc
}
// NewTerminalServicesCollector ...
func NewTerminalServicesCollector() (Collector, error) {
const subsystem = "terminal_services"
return &TerminalServicesCollector{
Local_session_count: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "local_session_count"),
"Number of Terminal Services sessions",
[]string{"session"},
nil,
),
}, nil
}
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *TerminalServicesCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collectTSSessionCount(ch); err != nil {
log.Error("failed collecting terminal services session count metrics:", desc, err)
return err
}
return nil
}
type Win32_PerfRawData_LocalSessionManager_TerminalServices struct {
ActiveSessions uint32
InactiveSessions uint32
TotalSessions uint32
}
func (c *TerminalServicesCollector) collectTSSessionCount(ch chan<- prometheus.Metric) (*prometheus.Desc, error) {
var dst []Win32_PerfRawData_LocalSessionManager_TerminalServices
q := queryAll(&dst)
if err := wmi.Query(q, &dst); err != nil {
return nil, err
}
if len(dst) == 0 {
return nil, errors.New("WMI query returned empty result set")
}
ch <- prometheus.MustNewConstMetric(
c.Local_session_count,
prometheus.GaugeValue,
float64(dst[0].ActiveSessions),
"active",
)
ch <- prometheus.MustNewConstMetric(
c.Local_session_count,
prometheus.GaugeValue,
float64(dst[0].InactiveSessions),
"inactive",
)
ch <- prometheus.MustNewConstMetric(
c.Local_session_count,
prometheus.GaugeValue,
float64(dst[0].TotalSessions),
"total",
)
return nil, nil
}

View File

@@ -5,37 +5,18 @@ The process collector exposes metrics about processes
|||
-|-
Metric name prefix | `process`
Data source | Perflib
Counters | `Process`
Classes | [`Win32_PerfRawData_PerfProc_Process`](https://msdn.microsoft.com/en-us/library/aa394323(v=vs.85).aspx)
Enabled by default? | No
## Flags
### `--collector.process.whitelist`
### `--collector.process.processes-where`
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.
A WMI filter on which processes to include. Recommended to keep down number of returned metrics.
### `--collector.process.blacklist`
`%` is a wildcard, and can be used to match on substrings.
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` .
Example: `--collector.process.processes-where="Name LIKE 'firefox%'`
## Metrics

View File

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