Merge pull request #335 from martinlindhe/perflib-cpu

Use perflib for cpu collector
This commit is contained in:
Calle Pettersson
2019-08-04 14:30:46 +02:00
committed by GitHub
47 changed files with 2026 additions and 310 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ VERSION
*.swp
*.un~
output/
.idea

View File

@@ -40,12 +40,13 @@ after_build:
return
}
$ErrorActionPreference = "Stop"
$BuildVersion = Get-Content VERSION
# The MSI version is not semver compliant, so just take the numerical parts
$Version = $env:APPVEYOR_REPO_TAG_NAME -replace '^v?([0-9\.]+).*$','$1'
$MSIVersion = $env:APPVEYOR_REPO_TAG_NAME -replace '^v?([0-9\.]+).*$','$1'
foreach($Arch in "amd64","386") {
Write-Verbose "Building wmi_exporter $Version msi for $Arch"
.\installer\build.ps1 -PathToExecutable .\output\$Arch\wmi_exporter-$Version-$Arch.exe -Version $Version -Arch "$Arch"
Move-Item installer\Output\wmi_exporter-$Version-$Arch.msi output\$Arch\
Write-Verbose "Building wmi_exporter $MSIVersion msi for $Arch"
.\installer\build.ps1 -PathToExecutable .\output\$Arch\wmi_exporter-$BuildVersion-$Arch.exe -Version $MSIVersion -Arch "$Arch"
Move-Item installer\Output\wmi_exporter-$MSIVersion-$Arch.msi output\$Arch\
}
- promu checksum output\

View File

@@ -454,7 +454,7 @@ func NewADCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *ADCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *ADCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting ad metrics:", desc, err)
return err

71
collector/collector.go Normal file
View File

@@ -0,0 +1,71 @@
package collector
import (
"strconv"
"github.com/leoluk/perflib_exporter/perflib"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
"golang.org/x/sys/windows/registry"
)
// ...
const (
// TODO: Make package-local
Namespace = "wmi"
// Conversion factors
ticksToSecondsScaleFactor = 1 / 1e7
windowsEpoch = 116444736000000000
)
// getWindowsVersion reads the version number of the OS from the Registry
// See https://docs.microsoft.com/en-us/windows/desktop/sysinfo/operating-system-version
func getWindowsVersion() float64 {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
log.Warn("Couldn't open registry", err)
return 0
}
defer func() {
err = k.Close()
if err != nil {
log.Warnf("Failed to close registry key: %v", err)
}
}()
currentv, _, err := k.GetStringValue("CurrentVersion")
if err != nil {
log.Warn("Couldn't open registry to determine current Windows version:", err)
return 0
}
currentv_flt, err := strconv.ParseFloat(currentv, 64)
log.Debugf("Detected Windows version %f\n", currentv_flt)
return currentv_flt
}
// Factories ...
var Factories = make(map[string]func() (Collector, error))
// Collector is the interface a collector has to implement.
type Collector interface {
// Get new metrics and expose them via prometheus registry.
Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) (err error)
}
type ScrapeContext struct {
perfObjects map[string]*perflib.PerfObject
}
// PrepareScrapeContext creates a ScrapeContext to be used during a single scrape
func PrepareScrapeContext() (*ScrapeContext, error) {
objs, err := getPerflibSnapshot()
if err != nil {
return nil, err
}
return &ScrapeContext{objs}, nil
}

View File

@@ -1,4 +1,4 @@
// +build windows
// +build windows,cgo
package collector
@@ -131,7 +131,7 @@ func NewContainerMetricsCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *ContainerMetricsCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *ContainerMetricsCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting ContainerMetricsCollector metrics:", desc, err)
return err

View File

@@ -3,60 +3,75 @@
package collector
import (
"strconv"
"strings"
"github.com/StackExchange/wmi"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
"golang.org/x/sys/windows/registry"
)
func init() {
Factories["cpu"] = NewCPUCollector
Factories["cpu"] = newCPUCollector
}
//A function to get windows version from registry
func getWindowsVersion() float64 {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
log.Warn("Couldn't open registry", err)
return 0
}
defer func() {
err = k.Close()
if err != nil {
log.Warnf("Failed to close registry key: %v", err)
}
}()
currentv, _, err := k.GetStringValue("CurrentVersion")
if err != nil {
log.Warn("Couldn't open registry to determine current Windows version:", err)
return 0
}
currentv_flt, err := strconv.ParseFloat(currentv, 64)
log.Debugf("Detected Windows version %f\n", currentv_flt)
return currentv_flt
}
// A CPUCollector is a Prometheus collector for WMI Win32_PerfRawData_PerfOS_Processor metrics
type CPUCollector struct {
type cpuCollectorBasic struct {
CStateSecondsTotal *prometheus.Desc
TimeTotal *prometheus.Desc
InterruptsTotal *prometheus.Desc
DPCsTotal *prometheus.Desc
ProcessorFrequency *prometheus.Desc
}
type cpuCollectorFull struct {
CStateSecondsTotal *prometheus.Desc
TimeTotal *prometheus.Desc
InterruptsTotal *prometheus.Desc
DPCsTotal *prometheus.Desc
ClockInterruptsTotal *prometheus.Desc
IdleBreakEventsTotal *prometheus.Desc
ParkingStatus *prometheus.Desc
ProcessorFrequencyMHz *prometheus.Desc
ProcessorMaxFrequencyMHz *prometheus.Desc
ProcessorPerformance *prometheus.Desc
}
// NewCPUCollector constructs a new CPUCollector
func NewCPUCollector() (Collector, error) {
// newCPUCollector constructs a new cpuCollector, appropriate for the running OS
func newCPUCollector() (Collector, error) {
const subsystem = "cpu"
return &CPUCollector{
version := getWindowsVersion()
// For Windows 2008 (version 6.0) or earlier we only have the "Processor"
// class. As of Windows 2008 R2 (version 6.1) the more detailed
// "ProcessorInformation" set is available (although some of the counters
// are added in later versions, so we aren't guaranteed to get all of
// them).
// Value 6.05 was selected to split between Windows versions.
if version < 6.05 {
return &cpuCollectorBasic{
CStateSecondsTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "cstate_seconds_total"),
"Time spent in low-power idle state",
[]string{"core", "state"},
nil,
),
TimeTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "time_total"),
"Time that processor spent in different modes (idle, user, system, ...)",
[]string{"core", "mode"},
nil,
),
InterruptsTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "interrupts_total"),
"Total number of received and serviced hardware interrupts",
[]string{"core"},
nil,
),
DPCsTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "dpcs_total"),
"Total number of received and serviced deferred procedure calls (DPCs)",
[]string{"core"},
nil,
),
}, nil
}
return &cpuCollectorFull{
CStateSecondsTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "cstate_seconds_total"),
"Time spent in low-power idle state",
@@ -69,7 +84,6 @@ func NewCPUCollector() (Collector, error) {
[]string{"core", "mode"},
nil,
),
InterruptsTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "interrupts_total"),
"Total number of received and serviced hardware interrupts",
@@ -82,241 +96,273 @@ func NewCPUCollector() (Collector, error) {
[]string{"core"},
nil,
),
ProcessorFrequency: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "core_frequence_mhz"),
ClockInterruptsTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "clock_interrupts_total"),
"Total number of received and serviced clock tick interrupts",
[]string{"core"},
nil,
),
IdleBreakEventsTotal: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "idle_break_events_total"),
"Total number of time processor was woken from idle",
[]string{"core"},
nil,
),
ParkingStatus: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "parking_status"),
"Parking Status represents whether a processor is parked or not",
[]string{"core"},
nil,
),
ProcessorFrequencyMHz: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "core_frequency_mhz"),
"Core frequency in megahertz",
[]string{"core"},
nil,
),
ProcessorPerformance: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "processor_performance"),
"Processor Performance is the average performance of the processor while it is executing instructions, as a percentage of the nominal performance of the processor. On some processors, Processor Performance may exceed 100%",
[]string{"core"},
nil,
),
}, nil
}
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *CPUCollector) Collect(ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting cpu metrics:", desc, err)
type perflibProcessor struct {
Name string
C1Transitions float64 `perflib:"C1 Transitions/sec"`
C2Transitions float64 `perflib:"C2 Transitions/sec"`
C3Transitions float64 `perflib:"C3 Transitions/sec"`
DPCRate float64 `perflib:"DPC Rate"`
DPCsQueued float64 `perflib:"DPCs Queued/sec"`
Interrupts float64 `perflib:"Interrupts/sec"`
PercentC2Time float64 `perflib:"% C1 Time"`
PercentC3Time float64 `perflib:"% C2 Time"`
PercentC1Time float64 `perflib:"% C3 Time"`
PercentDPCTime float64 `perflib:"% DPC Time"`
PercentIdleTime float64 `perflib:"% Idle Time"`
PercentInterruptTime float64 `perflib:"% Interrupt Time"`
PercentPrivilegedTime float64 `perflib:"% Privileged Time"`
PercentProcessorTime float64 `perflib:"% Processor Time"`
PercentUserTime float64 `perflib:"% User Time"`
}
func (c *cpuCollectorBasic) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
data := make([]perflibProcessor, 0)
err := unmarshalObject(ctx.perfObjects["Processor"], &data)
if err != nil {
return err
}
return nil
}
// Win32_PerfRawData_PerfOS_Processor docs:
// - https://msdn.microsoft.com/en-us/library/aa394317(v=vs.90).aspx
type Win32_PerfRawData_PerfOS_Processor struct {
Name string
C1TransitionsPersec uint64
C2TransitionsPersec uint64
C3TransitionsPersec uint64
DPCRate uint32
DPCsQueuedPersec uint32
InterruptsPersec uint32
PercentC1Time uint64
PercentC2Time uint64
PercentC3Time uint64
PercentDPCTime uint64
PercentIdleTime uint64
PercentInterruptTime uint64
PercentPrivilegedTime uint64
PercentProcessorTime uint64
PercentUserTime uint64
}
type Win32_PerfRawData_Counters_ProcessorInformation struct {
Name string
AverageIdleTime uint64
C1TransitionsPersec uint64
C2TransitionsPersec uint64
C3TransitionsPersec uint64
ClockInterruptsPersec uint64
DPCRate uint64
DPCsQueuedPersec uint64
IdleBreakEventsPersec uint64
InterruptsPersec uint64
ParkingStatus uint64
PercentC1Time uint64
PercentC2Time uint64
PercentC3Time uint64
PercentDPCTime uint64
PercentIdleTime uint64
PercentInterruptTime uint64
PercentofMaximumFrequency uint64
PercentPerformanceLimit uint64
PercentPriorityTime uint64
PercentPrivilegedTime uint64
PercentPrivilegedUtility uint64
PercentProcessorPerformance uint64
PercentProcessorTime uint64
PercentProcessorUtility uint64
PercentUserTime uint64
PerformanceLimitFlags uint64
ProcessorFrequency uint64
ProcessorStateFlags uint64
}
func (c *CPUCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) {
version := getWindowsVersion()
// Windows version by number https://docs.microsoft.com/en-us/windows/desktop/sysinfo/operating-system-version
// For Windows 2008 or earlier Windows version is 6.0 or lower, so we use Win32_PerfRawData_PerfOS_Processor class
// For Windows 2008 R2 or later Windows version is 6.1 or higher, so we use Win32_PerfRawData_Counters_ProcessorInformation
// Value 6.05 was selected just to split between WIndows versions
if version > 6.05 {
var dst []Win32_PerfRawData_Counters_ProcessorInformation
q := queryAll(&dst)
if err := wmi.Query(q, &dst); err != nil {
return nil, err
}
for _, data := range dst {
if strings.Contains(data.Name, "_Total") {
continue
}
core := data.Name
ch <- prometheus.MustNewConstMetric(
c.ProcessorFrequency,
prometheus.GaugeValue,
float64(data.ProcessorFrequency),
core,
)
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
float64(data.PercentC1Time)*ticksToSecondsScaleFactor,
core, "c1",
)
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
float64(data.PercentC2Time)*ticksToSecondsScaleFactor,
core, "c2",
)
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
float64(data.PercentC3Time)*ticksToSecondsScaleFactor,
core, "c3",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentIdleTime)*ticksToSecondsScaleFactor,
core, "idle",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentInterruptTime)*ticksToSecondsScaleFactor,
core, "interrupt",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentDPCTime)*ticksToSecondsScaleFactor,
core, "dpc",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentPrivilegedTime)*ticksToSecondsScaleFactor,
core, "privileged",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentUserTime)*ticksToSecondsScaleFactor,
core, "user",
)
ch <- prometheus.MustNewConstMetric(
c.InterruptsTotal,
prometheus.CounterValue,
float64(data.InterruptsPersec),
core,
)
ch <- prometheus.MustNewConstMetric(
c.DPCsTotal,
prometheus.CounterValue,
float64(data.DPCsQueuedPersec),
core,
)
}
return nil, nil
}
var dst []Win32_PerfRawData_PerfOS_Processor
q := queryAll(&dst)
if err := wmi.Query(q, &dst); err != nil {
return nil, err
}
for _, data := range dst {
if strings.Contains(data.Name, "_Total") {
for _, cpu := range data {
if strings.Contains(strings.ToLower(cpu.Name), "_total") {
continue
}
core := data.Name
core := cpu.Name
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
float64(data.PercentC1Time)*ticksToSecondsScaleFactor,
cpu.PercentC1Time,
core, "c1",
)
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
float64(data.PercentC2Time)*ticksToSecondsScaleFactor,
cpu.PercentC2Time,
core, "c2",
)
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
float64(data.PercentC3Time)*ticksToSecondsScaleFactor,
cpu.PercentC3Time,
core, "c3",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentIdleTime)*ticksToSecondsScaleFactor,
cpu.PercentIdleTime,
core, "idle",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentInterruptTime)*ticksToSecondsScaleFactor,
cpu.PercentInterruptTime,
core, "interrupt",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentDPCTime)*ticksToSecondsScaleFactor,
cpu.PercentDPCTime,
core, "dpc",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentPrivilegedTime)*ticksToSecondsScaleFactor,
cpu.PercentPrivilegedTime,
core, "privileged",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
float64(data.PercentUserTime)*ticksToSecondsScaleFactor,
cpu.PercentUserTime,
core, "user",
)
ch <- prometheus.MustNewConstMetric(
c.InterruptsTotal,
prometheus.CounterValue,
float64(data.InterruptsPersec),
cpu.Interrupts,
core,
)
ch <- prometheus.MustNewConstMetric(
c.DPCsTotal,
prometheus.CounterValue,
float64(data.DPCsQueuedPersec),
cpu.DPCsQueued,
core,
)
}
return nil, nil
return nil
}
type perflibProcessorInformation struct {
Name string
C1TimeSeconds float64 `perflib:"% C1 Time"`
C2TimeSeconds float64 `perflib:"% C2 Time"`
C3TimeSeconds float64 `perflib:"% C3 Time"`
C1TransitionsTotal float64 `perflib:"C1 Transitions/sec"`
C2TransitionsTotal float64 `perflib:"C2 Transitions/sec"`
C3TransitionsTotal float64 `perflib:"C3 Transitions/sec"`
ClockInterruptsTotal float64 `perflib:"Clock Interrupts/sec"`
DPCsQueuedTotal float64 `perflib:"DPCs Queued/sec"`
DPCTimeSeconds float64 `perflib:"% DPC Time"`
IdleBreakEventsTotal float64 `perflib:"Idle Break Events/sec"`
IdleTimeSeconds float64 `perflib:"% Idle Time"`
InterruptsTotal float64 `perflib:"Interrupts/sec"`
InterruptTimeSeconds float64 `perflib:"% Interrupt Time"`
ParkingStatus float64 `perflib:"Parking Status"`
PerformanceLimitPercent float64 `perflib:"% Performance Limit"`
PriorityTimeSeconds float64 `perflib:"% Priority Time"`
PrivilegedTimeSeconds float64 `perflib:"% Privileged Time"`
PrivilegedUtilitySeconds float64 `perflib:"% Privileged Utility"`
ProcessorFrequencyMHz float64 `perflib:"Processor Frequency"`
ProcessorPerformance float64 `perflib:"% Processor Performance"`
ProcessorTimeSeconds float64 `perflib:"% Processor Time"`
ProcessorUtilityRate float64 `perflib:"% Processor Utility"`
UserTimeSeconds float64 `perflib:"% User Time"`
}
func (c *cpuCollectorFull) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
data := make([]perflibProcessorInformation, 0)
err := unmarshalObject(ctx.perfObjects["Processor Information"], &data)
if err != nil {
return err
}
for _, cpu := range data {
if strings.Contains(strings.ToLower(cpu.Name), "_total") {
continue
}
core := cpu.Name
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
cpu.C1TimeSeconds,
core, "c1",
)
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
cpu.C2TimeSeconds,
core, "c2",
)
ch <- prometheus.MustNewConstMetric(
c.CStateSecondsTotal,
prometheus.CounterValue,
cpu.C3TimeSeconds,
core, "c3",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
cpu.IdleTimeSeconds,
core, "idle",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
cpu.InterruptTimeSeconds,
core, "interrupt",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
cpu.DPCTimeSeconds,
core, "dpc",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
cpu.PrivilegedTimeSeconds,
core, "privileged",
)
ch <- prometheus.MustNewConstMetric(
c.TimeTotal,
prometheus.CounterValue,
cpu.UserTimeSeconds,
core, "user",
)
ch <- prometheus.MustNewConstMetric(
c.InterruptsTotal,
prometheus.CounterValue,
cpu.InterruptsTotal,
core,
)
ch <- prometheus.MustNewConstMetric(
c.DPCsTotal,
prometheus.CounterValue,
cpu.DPCsQueuedTotal,
core,
)
ch <- prometheus.MustNewConstMetric(
c.ClockInterruptsTotal,
prometheus.CounterValue,
cpu.ClockInterruptsTotal,
core,
)
ch <- prometheus.MustNewConstMetric(
c.IdleBreakEventsTotal,
prometheus.CounterValue,
cpu.IdleBreakEventsTotal,
core,
)
ch <- prometheus.MustNewConstMetric(
c.ParkingStatus,
prometheus.GaugeValue,
cpu.ParkingStatus,
core,
)
ch <- prometheus.MustNewConstMetric(
c.ProcessorFrequencyMHz,
prometheus.GaugeValue,
cpu.ProcessorFrequencyMHz,
core,
)
ch <- prometheus.MustNewConstMetric(
c.ProcessorPerformance,
prometheus.GaugeValue,
cpu.ProcessorPerformance,
core,
)
}
return nil
}

View File

@@ -42,7 +42,7 @@ func NewCSCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *CSCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *CSCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting cs metrics:", desc, err)
return err

View File

@@ -181,7 +181,7 @@ func NewDNSCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *DNSCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *DNSCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting dns metrics:", desc, err)
return err

View File

@@ -597,7 +597,7 @@ func NewHyperVCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *HyperVCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *HyperVCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collectVmHealth(ch); err != nil {
log.Error("failed collecting hyperV health status metrics:", desc, err)
return err

View File

@@ -818,7 +818,7 @@ func NewIISCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *IISCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *IISCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting iis metrics:", desc, err)
return err

View File

@@ -134,7 +134,7 @@ func NewLogicalDiskCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *LogicalDiskCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *LogicalDiskCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting logical_disk metrics:", desc, err)
return err

View File

@@ -256,7 +256,7 @@ func NewMemoryCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *MemoryCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *MemoryCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting memory metrics:", desc, err)
return err

View File

@@ -68,7 +68,7 @@ func NewMSMQCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *Win32_PerfRawData_MSMQ_MSMQQueueCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *Win32_PerfRawData_MSMQ_MSMQQueueCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting msmq metrics:", desc, err)
return err

View File

@@ -1699,7 +1699,7 @@ func (c *MSSQLCollector) execute(name string, fn mssqlCollectorFunc, ch chan<- p
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *MSSQLCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *MSSQLCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
wg := sync.WaitGroup{}
enabled := mssqlExpandEnabledCollectors(*mssqlEnabledCollectors)

View File

@@ -132,7 +132,7 @@ func NewNetworkCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NetworkCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NetworkCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting net metrics:", desc, err)
return err

View File

@@ -53,7 +53,7 @@ func NewNETFramework_NETCLRExceptionsCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRExceptionsCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRExceptionsCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrexceptions metrics:", desc, err)
return err

View File

@@ -46,7 +46,7 @@ func NewNETFramework_NETCLRInteropCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRInteropCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRInteropCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrinterop metrics:", desc, err)
return err

View File

@@ -53,7 +53,7 @@ func NewNETFramework_NETCLRJitCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRJitCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRJitCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrjit metrics:", desc, err)
return err

View File

@@ -88,7 +88,7 @@ func NewNETFramework_NETCLRLoadingCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRLoadingCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRLoadingCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrloading metrics:", desc, err)
return err

View File

@@ -74,7 +74,7 @@ func NewNETFramework_NETCLRLocksAndThreadsCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRLocksAndThreadsCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRLocksAndThreadsCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrlocksandthreads metrics:", desc, err)
return err

View File

@@ -112,7 +112,7 @@ func NewNETFramework_NETCLRMemoryCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRMemoryCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRMemoryCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrmemory metrics:", desc, err)
return err

View File

@@ -67,7 +67,7 @@ func NewNETFramework_NETCLRRemotingCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRRemotingCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRRemotingCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrremoting metrics:", desc, err)
return err

View File

@@ -53,7 +53,7 @@ func NewNETFramework_NETCLRSecurityCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *NETFramework_NETCLRSecurityCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *NETFramework_NETCLRSecurityCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting win32_perfrawdata_netframework_netclrsecurity metrics:", desc, err)
return err

View File

@@ -113,7 +113,7 @@ func NewOSCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *OSCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *OSCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting os metrics:", desc, err)
return err

100
collector/perflib.go Normal file
View File

@@ -0,0 +1,100 @@
package collector
import (
"fmt"
"reflect"
perflibCollector "github.com/leoluk/perflib_exporter/collector"
"github.com/leoluk/perflib_exporter/perflib"
"github.com/prometheus/common/log"
)
func getPerflibSnapshot() (map[string]*perflib.PerfObject, error) {
objects, err := perflib.QueryPerformanceData("Global")
if err != nil {
return nil, err
}
indexed := make(map[string]*perflib.PerfObject)
for _, obj := range objects {
indexed[obj.Name] = obj
}
return indexed, nil
}
func unmarshalObject(obj *perflib.PerfObject, vs interface{}) error {
if obj == nil {
return fmt.Errorf("counter not found")
}
rv := reflect.ValueOf(vs)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return fmt.Errorf("%v is nil or not a pointer to slice", reflect.TypeOf(vs))
}
ev := rv.Elem()
if ev.Kind() != reflect.Slice {
return fmt.Errorf("%v is not slice", reflect.TypeOf(vs))
}
// Ensure sufficient length
if ev.Cap() < len(obj.Instances) {
nvs := reflect.MakeSlice(ev.Type(), len(obj.Instances), len(obj.Instances))
ev.Set(nvs)
}
for idx, instance := range obj.Instances {
target := ev.Index(idx)
rt := target.Type()
counters := make(map[string]*perflib.PerfCounter, len(instance.Counters))
for _, ctr := range instance.Counters {
if ctr.Def.IsBaseValue && !ctr.Def.IsNanosecondCounter {
counters[ctr.Def.Name+"_Base"] = ctr
} else {
counters[ctr.Def.Name] = ctr
}
}
for i := 0; i < target.NumField(); i++ {
f := rt.Field(i)
tag := f.Tag.Get("perflib")
if tag == "" {
continue
}
ctr, found := counters[tag]
if !found {
log.Debugf("missing counter %q, have %v", tag, counterMapKeys(counters))
continue
}
if !target.Field(i).CanSet() {
return fmt.Errorf("tagged field %v cannot be written to", f.Name)
}
if fieldType := target.Field(i).Type(); fieldType != reflect.TypeOf((*float64)(nil)).Elem() {
return fmt.Errorf("tagged field %v has wrong type %v, must be float64", f.Name, fieldType)
}
switch ctr.Def.CounterType {
case perflibCollector.PERF_ELAPSED_TIME:
target.Field(i).SetFloat(float64(ctr.Value-windowsEpoch) / float64(obj.Frequency))
case perflibCollector.PERF_100NSEC_TIMER, perflibCollector.PERF_PRECISION_100NS_TIMER:
target.Field(i).SetFloat(float64(ctr.Value) * ticksToSecondsScaleFactor)
default:
target.Field(i).SetFloat(float64(ctr.Value))
}
}
if instance.Name != "" && target.FieldByName("Name").CanSet() {
target.FieldByName("Name").SetString(instance.Name)
}
}
return nil
}
func counterMapKeys(m map[string]*perflib.PerfCounter) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}

125
collector/perflib_test.go Normal file
View File

@@ -0,0 +1,125 @@
package collector
import (
"reflect"
"testing"
perflibCollector "github.com/leoluk/perflib_exporter/collector"
"github.com/leoluk/perflib_exporter/perflib"
)
type simple struct {
ValA float64 `perflib:"Something"`
ValB float64 `perflib:"Something Else"`
}
func TestUnmarshalPerflib(t *testing.T) {
cases := []struct {
name string
obj *perflib.PerfObject
expectedOutput []simple
expectError bool
}{
{
name: "nil check",
obj: nil,
expectedOutput: []simple{},
expectError: true,
},
{
name: "Simple",
obj: &perflib.PerfObject{
Instances: []*perflib.PerfInstance{
{
Counters: []*perflib.PerfCounter{
{
Def: &perflib.PerfCounterDef{
Name: "Something",
CounterType: perflibCollector.PERF_COUNTER_COUNTER,
},
Value: 123,
},
},
},
},
},
expectedOutput: []simple{{ValA: 123}},
expectError: false,
},
{
name: "Multiple properties",
obj: &perflib.PerfObject{
Instances: []*perflib.PerfInstance{
{
Counters: []*perflib.PerfCounter{
{
Def: &perflib.PerfCounterDef{
Name: "Something",
CounterType: perflibCollector.PERF_COUNTER_COUNTER,
},
Value: 123,
},
{
Def: &perflib.PerfCounterDef{
Name: "Something Else",
CounterType: perflibCollector.PERF_COUNTER_COUNTER,
},
Value: 256,
},
},
},
},
},
expectedOutput: []simple{{ValA: 123, ValB: 256}},
expectError: false,
},
{
name: "Multiple instances",
obj: &perflib.PerfObject{
Instances: []*perflib.PerfInstance{
{
Counters: []*perflib.PerfCounter{
{
Def: &perflib.PerfCounterDef{
Name: "Something",
CounterType: perflibCollector.PERF_COUNTER_COUNTER,
},
Value: 321,
},
},
},
{
Counters: []*perflib.PerfCounter{
{
Def: &perflib.PerfCounterDef{
Name: "Something",
CounterType: perflibCollector.PERF_COUNTER_COUNTER,
},
Value: 231,
},
},
},
},
},
expectedOutput: []simple{{ValA: 321}, {ValA: 231}},
expectError: false,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
output := make([]simple, 0)
err := unmarshalObject(c.obj, &output)
if err != nil && !c.expectError {
t.Errorf("Did not expect error, got %q", err)
}
if err == nil && c.expectError {
t.Errorf("Expected an error, but got ok")
}
if err == nil && !reflect.DeepEqual(output, c.expectedOutput) {
t.Errorf("Output mismatch, expected %+v, got %+v", c.expectedOutput, output)
}
})
}
}

View File

@@ -135,7 +135,7 @@ func NewProcessCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *ProcessCollector) Collect(ch chan<- prometheus.Metric) error {
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

View File

@@ -64,7 +64,7 @@ func NewserviceCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *serviceCollector) Collect(ch chan<- prometheus.Metric) error {
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)
return err

View File

@@ -69,7 +69,7 @@ func NewSystemCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *SystemCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *SystemCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting system metrics:", desc, err)
return err

View File

@@ -90,7 +90,7 @@ func NewTCPCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *TCPCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *TCPCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting tcp metrics:", desc, err)
return err

View File

@@ -212,7 +212,7 @@ func (cr carriageReturnFilteringReader) Read(p []byte) (int, error) {
}
// Update implements the Collector interface.
func (c *textFileCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *textFileCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
error := 0.0
mtimes := map[string]time.Time{}

View File

@@ -10,8 +10,8 @@ func init() {
Factories["thermalzone"] = NewThermalZoneCollector
}
// A ThermalZoneCollector is a Prometheus collector for WMI Win32_PerfRawData_Counters_ThermalZoneInformation metrics
type ThermalZoneCollector struct {
// A thermalZoneCollector is a Prometheus collector for WMI Win32_PerfRawData_Counters_ThermalZoneInformation metrics
type thermalZoneCollector struct {
PercentPassiveLimit *prometheus.Desc
Temperature *prometheus.Desc
ThrottleReasons *prometheus.Desc
@@ -20,7 +20,7 @@ type ThermalZoneCollector struct {
// NewThermalZoneCollector ...
func NewThermalZoneCollector() (Collector, error) {
const subsystem = "thermalzone"
return &ThermalZoneCollector{
return &thermalZoneCollector{
Temperature: prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, "temperature_celsius"),
"(Temperature)",
@@ -50,7 +50,7 @@ func NewThermalZoneCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *ThermalZoneCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *thermalZoneCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting thermalzone metrics:", desc, err)
return err
@@ -68,7 +68,7 @@ type Win32_PerfRawData_Counters_ThermalZoneInformation struct {
ThrottleReasons uint32
}
func (c *ThermalZoneCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) {
func (c *thermalZoneCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) {
var dst []Win32_PerfRawData_Counters_ThermalZoneInformation
q := queryAll(&dst)
if err := wmi.Query(q, &dst); err != nil {

View File

@@ -162,7 +162,7 @@ func NewVmwareCollector() (Collector, error) {
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *VmwareCollector) Collect(ch chan<- prometheus.Metric) error {
func (c *VmwareCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collectMem(ch); err != nil {
log.Error("failed collecting vmware memory metrics:", desc, err)
return err

View File

@@ -4,27 +4,9 @@ import (
"bytes"
"reflect"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
)
// ...
const (
Namespace = "wmi"
// Conversion factors
ticksToSecondsScaleFactor = 1 / 1e7
)
// Factories ...
var Factories = make(map[string]func() (Collector, error))
// Collector is the interface a collector has to implement.
type Collector interface {
// Get new metrics and expose them via prometheus registry.
Collect(ch chan<- prometheus.Metric) (err error)
}
func className(src interface{}) string {
s := reflect.Indirect(reflect.ValueOf(src))
t := s.Type()

View File

@@ -5,7 +5,8 @@ The cpu collector exposes metrics about CPU usage
|||
-|-
Metric name prefix | `cpu`
Classes | [`Win32_PerfRawData_PerfOS_Processor`](https://msdn.microsoft.com/en-us/library/aa394317(v=vs.90).aspx)
Data source | Perflib
Counters | `ProcessorInformation` (Windows Server 2008R2 and later) `Processor` (older versions)
Enabled by default? | Yes
## Flags
@@ -13,6 +14,7 @@ Enabled by default? | Yes
None
## Metrics
These metrics are available on all versions of Windows:
Name | Description | Type | Labels
-----|-------------|------|-------
@@ -21,6 +23,16 @@ Name | Description | Type | Labels
`wmi_cpu_interrupts_total` | Total number of received and serviced hardware interrupts | counter | `core`
`wmi_cpu_dpcs_total` | Total number of received and serviced deferred procedure calls (DPCs) | counter | `core`
These metrics are only exposed on Windows Server 2008R2 and later:
Name | Description | Type | Labels
-----|-------------|------|-------
`wmi_cpu_clock_interrupts_total` | Total number of received and serviced clock tick interrupts | `core`
`wmi_cpu_idle_break_events_total` | Total number of time processor was woken from idle | `core`
`wmi_cpu_parking_status` | Parking Status represents whether a processor is parked or not | `gauge`
`wmi_cpu_core_frequency_mhz` | Core frequency in megahertz | `gauge`
`wmi_cpu_processor_performance` | Processor Performance is the average performance of the processor while it is executing instructions, as a percentage of the nominal performance of the processor. On some processors, Processor Performance may exceed 100% | `gauge`
### Example metric
_This collector does not yet have explained examples, we would appreciate your help adding them!_

View File

@@ -5,7 +5,9 @@ package main
import (
"fmt"
"net/http"
"os"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -23,7 +25,8 @@ import (
// WmiCollector implements the prometheus.Collector interface.
type WmiCollector struct {
collectors map[string]collector.Collector
maxScrapeDuration time.Duration
collectors map[string]collector.Collector
}
const (
@@ -45,6 +48,18 @@ var (
[]string{"collector"},
nil,
)
scrapeTimeoutDesc = prometheus.NewDesc(
prometheus.BuildFQName(collector.Namespace, "exporter", "collector_timeout"),
"wmi_exporter: Whether the collector timed out.",
[]string{"collector"},
nil,
)
snapshotDuration = prometheus.NewDesc(
prometheus.BuildFQName(collector.Namespace, "exporter", "perflib_snapshot_duration_seconds"),
"Duration of perflib snapshot capture",
nil,
nil,
)
// This can be removed when client_golang exposes this on Windows
// (See https://github.com/prometheus/client_golang/issues/376)
@@ -64,25 +79,112 @@ func (coll WmiCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- scrapeSuccessDesc
}
// Collect sends the collected metrics from each of the collectors to
// prometheus. Collect could be called several times concurrently
// and thus its run is protected by a single mutex.
func (coll WmiCollector) Collect(ch chan<- prometheus.Metric) {
wg := sync.WaitGroup{}
wg.Add(len(coll.collectors))
for name, c := range coll.collectors {
go func(name string, c collector.Collector) {
execute(name, c, ch)
wg.Done()
}(name, c)
}
type collectorOutcome int
const (
pending collectorOutcome = iota
success
failed
)
// Collect sends the collected metrics from each of the collectors to
// prometheus.
func (coll WmiCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
startTimeDesc,
prometheus.CounterValue,
startTime,
)
wg.Wait()
t := time.Now()
scrapeContext, err := collector.PrepareScrapeContext()
ch <- prometheus.MustNewConstMetric(
snapshotDuration,
prometheus.GaugeValue,
time.Since(t).Seconds(),
)
if err != nil {
ch <- prometheus.NewInvalidMetric(scrapeSuccessDesc, fmt.Errorf("failed to prepare scrape: %v", err))
return
}
wg := sync.WaitGroup{}
wg.Add(len(coll.collectors))
collectorOutcomes := make(map[string]collectorOutcome)
for name := range coll.collectors {
collectorOutcomes[name] = pending
}
metricsBuffer := make(chan prometheus.Metric)
l := sync.Mutex{}
finished := false
go func() {
for m := range metricsBuffer {
l.Lock()
if !finished {
ch <- m
}
l.Unlock()
}
}()
for name, c := range coll.collectors {
go func(name string, c collector.Collector) {
defer wg.Done()
outcome := execute(name, c, scrapeContext, metricsBuffer)
l.Lock()
if !finished {
collectorOutcomes[name] = outcome
}
l.Unlock()
}(name, c)
}
allDone := make(chan struct{})
go func() {
wg.Wait()
close(allDone)
}()
// Wait until either all collectors finish, or timeout expires
select {
case <-allDone:
case <-time.After(coll.maxScrapeDuration):
}
l.Lock()
finished = true
remainingCollectorNames := make([]string, 0)
for name, outcome := range collectorOutcomes {
var successValue, timeoutValue float64
if outcome == pending {
timeoutValue = 1.0
remainingCollectorNames = append(remainingCollectorNames, name)
}
if outcome == success {
successValue = 1.0
}
ch <- prometheus.MustNewConstMetric(
scrapeSuccessDesc,
prometheus.GaugeValue,
successValue,
name,
)
ch <- prometheus.MustNewConstMetric(
scrapeTimeoutDesc,
prometheus.GaugeValue,
timeoutValue,
name,
)
}
if len(remainingCollectorNames) > 0 {
log.Warn("Collection timed out, still waiting for ", remainingCollectorNames)
}
l.Unlock()
}
func filterAvailableCollectors(collectors string) string {
@@ -96,31 +198,23 @@ func filterAvailableCollectors(collectors string) string {
return strings.Join(availableCollectors, ",")
}
func execute(name string, c collector.Collector, ch chan<- prometheus.Metric) {
begin := time.Now()
err := c.Collect(ch)
duration := time.Since(begin)
var success float64
if err != nil {
log.Errorf("collector %s failed after %fs: %s", name, duration.Seconds(), err)
success = 0
} else {
log.Debugf("collector %s succeeded after %fs.", name, duration.Seconds())
success = 1
}
func execute(name string, c collector.Collector, ctx *collector.ScrapeContext, ch chan<- prometheus.Metric) collectorOutcome {
t := time.Now()
err := c.Collect(ctx, ch)
duration := time.Since(t).Seconds()
ch <- prometheus.MustNewConstMetric(
scrapeDurationDesc,
prometheus.GaugeValue,
duration.Seconds(),
name,
)
ch <- prometheus.MustNewConstMetric(
scrapeSuccessDesc,
prometheus.GaugeValue,
success,
duration,
name,
)
if err != nil {
log.Errorf("collector %s failed after %fs: %s", name, duration, err)
return failed
}
log.Debugf("collector %s succeeded after %fs.", name, duration)
return success
}
func expandEnabledCollectors(enabled string) []string {
@@ -157,10 +251,6 @@ func loadCollectors(list string) (map[string]collector.Collector, error) {
return collectors, nil
}
func init() {
prometheus.MustRegister(version.NewCollector("wmi_exporter"))
}
func initWbem() {
// This initialization prevents a memory leak on WMF 5+. See
// https://github.com/martinlindhe/wmi_exporter/issues/77 and linked issues
@@ -192,6 +282,10 @@ func main() {
"collectors.print",
"If true, print available collectors and exit.",
).Bool()
timeoutMargin = kingpin.Flag(
"scrape.timeout-margin",
"Seconds to subtract from the timeout allowed by the client. Tune to allow for overhead or high loads.",
).Default("0.5").Float64()
)
log.AddFlags(kingpin.CommandLine)
@@ -236,10 +330,17 @@ func main() {
log.Infof("Enabled collectors: %v", strings.Join(keys(collectors), ", "))
nodeCollector := WmiCollector{collectors: collectors}
prometheus.MustRegister(nodeCollector)
h := &metricsHandler{
timeoutMargin: *timeoutMargin,
collectorFactory: func(timeout time.Duration) *WmiCollector {
return &WmiCollector{
collectors: collectors,
maxScrapeDuration: timeout,
}
},
}
http.Handle(*metricsPath, promhttp.Handler())
http.Handle(*metricsPath, h)
http.HandleFunc("/health", healthCheck)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, *metricsPath, http.StatusMovedPermanently)
@@ -303,3 +404,36 @@ loop:
changes <- svc.Status{State: svc.StopPending}
return
}
type metricsHandler struct {
timeoutMargin float64
collectorFactory func(timeout time.Duration) *WmiCollector
}
func (mh *metricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
const defaultTimeout = 10.0
var timeoutSeconds float64
if v := r.Header.Get("X-Prometheus-Scrape-Timeout-Seconds"); v != "" {
var err error
timeoutSeconds, err = strconv.ParseFloat(v, 64)
if err != nil {
log.Warnf("Couldn't parse X-Prometheus-Scrape-Timeout-Seconds: %q. Defaulting timeout to %f", v, defaultTimeout)
}
}
if timeoutSeconds == 0 {
timeoutSeconds = defaultTimeout
}
timeoutSeconds = timeoutSeconds - mh.timeoutMargin
reg := prometheus.NewRegistry()
reg.MustRegister(mh.collectorFactory(time.Duration(timeoutSeconds * float64(time.Second))))
reg.MustRegister(
prometheus.NewProcessCollector(os.Getpid(), ""),
prometheus.NewGoCollector(),
version.NewCollector("wmi_exporter"),
)
h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
h.ServeHTTP(w, r)
}

View File

@@ -29,7 +29,7 @@ func New{{ .CollectorName }}Collector() (Collector, error) {
}
// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *{{ .CollectorName }}Collector) Collect(ch chan<- prometheus.Metric) error {
func (c *{{ .CollectorName }}Collector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error {
if desc, err := c.collect(ch); err != nil {
log.Error("failed collecting {{ .CollectorName | toLower }} metrics:", desc, err)
return err

21
vendor/github.com/leoluk/perflib_exporter/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Leopold Schabel / The perflib_exporter authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,184 @@
package collector
import (
"strings"
"github.com/leoluk/perflib_exporter/perflib"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
)
// ...
const (
Namespace = "perflib"
// Conversion factors
hundredNsToSecondsScaleFactor = 1 / 1e7
)
// Collector is the interface a collector has to implement.
type Collector interface {
// Get new metrics and expose them via prometheus registry.
Collect(ch chan<- prometheus.Metric) (err error)
}
type CounterKey struct {
ObjectIndex uint
CounterIndex uint
CounterType uint32 // This is a bit mask
}
func NewCounterKey(object *perflib.PerfObject, def *perflib.PerfCounterDef) CounterKey {
return CounterKey{object.NameIndex, def.NameIndex, def.CounterType}
}
type PerflibCollector struct {
perflibQuery string
perflibDescs map[CounterKey]*prometheus.Desc
}
func NewPerflibCollector(query string) (c PerflibCollector) {
c.perflibQuery = query
objects, err := perflib.QueryPerformanceData(c.perflibQuery)
if err != nil {
panic(err)
}
log.Debugf("Number of objects: %d", len(objects))
c.perflibDescs = make(map[CounterKey]*prometheus.Desc)
for _, object := range objects {
for _, def := range object.CounterDefs {
desc := descFromCounterDef(*object, *def)
key := NewCounterKey(object, def)
c.perflibDescs[key] = desc
}
}
return
}
func (c PerflibCollector) Collect(ch chan<- prometheus.Metric) (err error) {
// TODO QueryPerformanceData timing metric
objects, err := perflib.QueryPerformanceData(c.perflibQuery)
if err != nil {
// TODO - we shouldn't panic if a single call fails
panic(err)
}
log.Debugf("Number of objects: %d", len(objects))
for _, object := range objects {
n := object.NameIndex
for _, instance := range object.Instances {
name := instance.Name
// _Total metrics do not fit into the Prometheus model - we try to merge similar
// metrics and give them labels, so you'd sum() them instead. Having a _Total label
// would make
if strings.HasSuffix(name, "_Total") || strings.HasPrefix(name, "Total") {
continue
}
for _, counter := range instance.Counters {
if IsDefPromotedLabel(n, counter.Def.NameIndex) {
continue
}
if counter == nil {
log.Debugf("nil counter for %s -> %s", object.Name, instance.Name)
continue
}
if counter.Def.NameIndex == 0 {
log.Debugf("null counter index for %s -> %s", object.Name, instance.Name)
continue
}
if counter.Def.Name == "" {
log.Debugf("no counter name for %s -> %s", object.Name, instance.Name)
continue
}
if counter.Def.Name == "No name" {
log.Debugf("no name counter %s -> %s -> %s", object.Name, instance.Name, counter.Def.Name)
continue
}
key := NewCounterKey(object, counter.Def)
desc, ok := c.perflibDescs[key]
if !ok {
log.Debugf("missing metric description for counter %s -> %s -> %s", object.Name, instance.Name, counter.Def.Name)
continue
}
labels := []string{name}
if len(object.Instances) == 1 {
labels = []string{}
}
if HasPromotedLabels(n) {
labels = append(labels, PromotedLabelValuesForInstance(n, instance)...)
}
// TODO - Label merging needs to be fixed for [230] Process
//if HasMergedLabels(n) {
// _, value := MergedMetricForInstance(n, counter.Def.NameIndex)
//
// // Null string in definition means we should skip this metric (it's probably a sum)
// if value == "" {
// log.Debugf("Skipping %d -> %s (empty merge label)", n, counter.Def.NameIndex)
// continue
// }
// labels = append(labels, value)
//}
valueType, err := GetPrometheusValueType(counter.Def.CounterType)
if err != nil {
// TODO - Is this too verbose? There will always be counter types we don't support
log.Debug(err)
continue
}
value := float64(counter.Value)
if counter.Def.IsNanosecondCounter {
value = value * hundredNsToSecondsScaleFactor
}
if IsElapsedTime(counter.Def.CounterType) {
// convert from Windows timestamp (1 jan 1601) to unix timestamp (1 jan 1970)
value = float64(counter.Value-116444736000000000) / float64(object.Frequency)
}
metric := prometheus.MustNewConstMetric(
desc,
valueType,
value,
labels...,
)
ch <- metric
}
}
}
/*ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(),
prometheus.CounterValue,
float64(0),
"ds_client",
)*/
return nil
}

View File

@@ -0,0 +1,90 @@
package collector
import (
"fmt"
"strings"
"github.com/leoluk/perflib_exporter/perflib"
"github.com/prometheus/client_golang/prometheus"
)
func manglePerflibName(s string) string {
s = strings.ToLower(s)
s = strings.Replace(s, " ", "_", -1)
s = strings.Replace(s, ".", "", -1)
s = strings.Replace(s, "(", "", -1)
s = strings.Replace(s, ")", "", -1)
s = strings.Replace(s, "+", "", -1)
s = strings.Replace(s, "-", "", -1)
s = strings.Replace(s, ",", "", -1)
return s
}
func manglePerflibCounterName(s string) string {
s = manglePerflibName(s)
s = strings.Replace(s, "total_", "", -1)
s = strings.Replace(s, "_total", "", -1)
s = strings.Replace(s, "/second", "", -1)
s = strings.Replace(s, "/sec", "", -1)
s = strings.Replace(s, "_%", "", -1)
s = strings.Replace(s, "%_", "", -1)
s = strings.Replace(s, "/", "_per_", -1)
s = strings.Replace(s, "&", "and", -1)
s = strings.Replace(s, "#_of_", "", -1)
s = strings.Replace(s, ":", "", -1)
s = strings.Replace(s, "__", "_", -1)
s = strings.Trim(s, " _")
return s
}
func MakePrometheusLabel(def *perflib.PerfCounterDef) (s string) {
s = manglePerflibCounterName(def.Name)
if len(s) > 0 {
if IsCounter(def.CounterType) {
s += "_total"
} else if IsBaseValue(def.CounterType) && !strings.HasSuffix(s, "_base") {
s += "_max"
}
}
return
}
func pdhNameFromCounterDef(obj perflib.PerfObject, def perflib.PerfCounterDef) string {
return fmt.Sprintf(`\%s(*)\%s`, obj.Name, def.Name)
}
func descFromCounterDef(obj perflib.PerfObject, def perflib.PerfCounterDef) *prometheus.Desc {
subsystem := manglePerflibName(obj.Name)
counterName := MakePrometheusLabel(&def)
labels := []string{"name"}
if len(obj.Instances) == 1 {
labels = []string{}
}
if HasPromotedLabels(obj.NameIndex) {
labels = append(labels, PromotedLabelsForObject(obj.NameIndex)...)
}
// TODO - Label merging needs to be fixed for [230] Process
//if HasMergedLabels(obj.NameIndex) {
// s, labelsForObject := MergedLabelsForInstance(obj.NameIndex, def.NameIndex)
// counterName = s
// labels = append(labels, labelsForObject)
//}
return prometheus.NewDesc(
prometheus.BuildFQName(Namespace, subsystem, counterName),
fmt.Sprintf("perflib metric: %s (see /dump for docs) [%d]",
pdhNameFromCounterDef(obj, def), def.NameIndex),
labels,
nil,
)
}

View File

@@ -0,0 +1,91 @@
package collector
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
)
const (
PERF_COUNTER_RAWCOUNT_HEX = 0x00000000
PERF_COUNTER_LARGE_RAWCOUNT_HEX = 0x00000100
PERF_COUNTER_TEXT = 0x00000b00
PERF_COUNTER_RAWCOUNT = 0x00010000
PERF_COUNTER_LARGE_RAWCOUNT = 0x00010100
PERF_DOUBLE_RAW = 0x00012000
PERF_COUNTER_DELTA = 0x00400400
PERF_COUNTER_LARGE_DELTA = 0x00400500
PERF_SAMPLE_COUNTER = 0x00410400
PERF_COUNTER_QUEUELEN_TYPE = 0x00450400
PERF_COUNTER_LARGE_QUEUELEN_TYPE = 0x00450500
PERF_COUNTER_100NS_QUEUELEN_TYPE = 0x00550500
PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE = 0x00650500
PERF_COUNTER_COUNTER = 0x10410400
PERF_COUNTER_BULK_COUNT = 0x10410500
PERF_RAW_FRACTION = 0x20020400
PERF_LARGE_RAW_FRACTION = 0x20020500
PERF_COUNTER_TIMER = 0x20410500
PERF_PRECISION_SYSTEM_TIMER = 0x20470500
PERF_100NSEC_TIMER = 0x20510500
PERF_PRECISION_100NS_TIMER = 0x20570500
PERF_OBJ_TIME_TIMER = 0x20610500
PERF_PRECISION_OBJECT_TIMER = 0x20670500
PERF_SAMPLE_FRACTION = 0x20c20400
PERF_COUNTER_TIMER_INV = 0x21410500
PERF_100NSEC_TIMER_INV = 0x21510500
PERF_COUNTER_MULTI_TIMER = 0x22410500
PERF_100NSEC_MULTI_TIMER = 0x22510500
PERF_COUNTER_MULTI_TIMER_INV = 0x23410500
PERF_100NSEC_MULTI_TIMER_INV = 0x23510500
PERF_AVERAGE_TIMER = 0x30020400
PERF_ELAPSED_TIME = 0x30240500
PERF_COUNTER_NODATA = 0x40000200
PERF_AVERAGE_BULK = 0x40020500
PERF_SAMPLE_BASE = 0x40030401
PERF_AVERAGE_BASE = 0x40030402
PERF_RAW_BASE = 0x40030403
PERF_PRECISION_TIMESTAMP = 0x40030500
PERF_LARGE_RAW_BASE = 0x40030503
PERF_COUNTER_MULTI_BASE = 0x42030500
PERF_COUNTER_HISTOGRAM_TYPE = 0x80000000
)
var supportedCounterTypes = map[uint32]prometheus.ValueType{
PERF_COUNTER_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT_HEX: prometheus.GaugeValue,
PERF_COUNTER_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_LARGE_RAWCOUNT: prometheus.GaugeValue,
PERF_COUNTER_DELTA: prometheus.CounterValue,
PERF_COUNTER_COUNTER: prometheus.CounterValue,
PERF_COUNTER_BULK_COUNT: prometheus.CounterValue,
PERF_RAW_FRACTION: prometheus.GaugeValue,
PERF_LARGE_RAW_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER: prometheus.CounterValue,
PERF_PRECISION_100NS_TIMER: prometheus.CounterValue,
PERF_SAMPLE_FRACTION: prometheus.GaugeValue,
PERF_100NSEC_TIMER_INV: prometheus.CounterValue,
PERF_ELAPSED_TIME: prometheus.GaugeValue,
PERF_SAMPLE_BASE: prometheus.GaugeValue,
PERF_RAW_BASE: prometheus.GaugeValue,
PERF_LARGE_RAW_BASE: prometheus.GaugeValue,
}
func IsCounter(counterType uint32) bool {
return supportedCounterTypes[counterType] == prometheus.CounterValue
}
func IsBaseValue(counterType uint32) bool {
return counterType == PERF_SAMPLE_BASE || counterType == PERF_RAW_BASE || counterType == PERF_LARGE_RAW_BASE
}
func IsElapsedTime(counterType uint32) bool {
return counterType == PERF_ELAPSED_TIME
}
func GetPrometheusValueType(counterType uint32) (prometheus.ValueType, error) {
val, ok := supportedCounterTypes[counterType]
if !ok {
return 0, fmt.Errorf("counter type %#08x is not supported", counterType)
}
return val, nil
}

View File

@@ -0,0 +1,38 @@
package collector
import "fmt"
var mergedDefinitions = map[uint]map[string]map[uint]string{
230: {
"processor_time_total": {
0: "mode",
6: "", // Processor Time (drop)
142: "user", // User Time
144: "privileged", // Privileged Time
},
},
}
// Return if a given object has merge definitions
func HasMergedLabels(index uint) bool {
_, ok := mergedDefinitions[index]
return ok
}
// Return a list of merged label names for an instance
func MergedLabelsForInstance(objIndex uint, def uint) (name string, labelName string) {
return MergedMetricForInstance(objIndex, 0)
}
// Return merged metric name and label value for an instance
func MergedMetricForInstance(objIndex uint, def uint) (name string, label string) {
for k, v := range mergedDefinitions[objIndex] {
for n := range v {
if def == n {
return k, v[n]
}
}
}
panic(fmt.Sprintf("No merge definition for obj %d, inst %d", objIndex, def))
}

View File

@@ -0,0 +1,56 @@
package collector
import (
"strconv"
"github.com/leoluk/perflib_exporter/perflib"
)
var labelPromotionLabels = map[uint][]string{
230: {
"process_id",
"creating_process_id",
},
}
var labelPromotionValues = map[uint][]uint{
230: {
784, // process_id
1410, // creating_process_id
},
}
// Get a list of promoted labels for an object
func PromotedLabelsForObject(index uint) []string {
return labelPromotionLabels[index]
}
// Get a list of label values for a given object and instance
func PromotedLabelValuesForInstance(index uint, instance *perflib.PerfInstance) []string {
values := make([]string, len(labelPromotionValues[index]))
for _, c := range instance.Counters {
for i, v := range labelPromotionValues[index] {
if c.Def.NameIndex == v {
values[i] = strconv.Itoa(int(c.Value))
}
}
}
return values
}
// Return if a given object has label promotion definitions
func HasPromotedLabels(index uint) bool {
_, ok := labelPromotionLabels[index]
return ok
}
// Return if a given definition is a promoted label for an object
func IsDefPromotedLabel(objIndex uint, def uint) bool {
for _, v := range labelPromotionValues[objIndex] {
if v == def {
return true
}
}
return false
}

View File

@@ -0,0 +1,75 @@
package perflib
import (
"bytes"
"fmt"
"strconv"
)
type nameTableLookuper interface {
LookupName() string
LookupHelp() string
}
func (p *perfObjectType) LookupName() string {
return counterNameTable.LookupString(p.ObjectNameTitleIndex)
}
func (p *perfObjectType) LookupHelp() string {
return helpNameTable.LookupString(p.ObjectHelpTitleIndex)
}
type NameTable struct {
byIndex map[uint32]string
byString map[string]uint32
}
func (t *NameTable) LookupString(index uint32) string {
return t.byIndex[index]
}
func (t *NameTable) LookupIndex(str string) uint32 {
return t.byString[str]
}
// Query a perflib name table from the registry. Specify the type and the language
// code (i.e. "Counter 009" or "Help 009") for English language.
func QueryNameTable(tableName string) *NameTable {
nameTable := new(NameTable)
nameTable.byIndex = make(map[uint32]string)
buffer, err := queryRawData(tableName)
if err != nil {
panic(err)
}
r := bytes.NewReader(buffer)
for {
index, err := readUTF16String(r)
if err != nil {
break
}
desc, err := readUTF16String(r)
if err != nil {
break
}
indexInt, _ := strconv.Atoi(index)
if err != nil {
panic(fmt.Sprint("Invalid index ", index))
}
nameTable.byIndex[uint32(indexInt)] = desc
}
nameTable.byString = make(map[string]uint32)
for k, v := range nameTable.byIndex {
nameTable.byString[v] = k
}
return nameTable
}

View File

@@ -0,0 +1,460 @@
/*
Go bindings for the HKEY_PERFORMANCE_DATA perflib / Performance Counters interface.
Overview
HKEY_PERFORMANCE_DATA is a low-level alternative to the higher-level PDH library and WMI.
It operates on blocks of counters and only returns raw values without calculating rates
or formatting them, which is exactly what you want for, say, a Prometheus exporter
(not so much for a GUI like Windows Performance Monitor).
Its overhead is much lower than the high-level libraries.
It operates on the same set of perflib providers as PDH and WMI. See this document
for more details on the relationship between the different libraries:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa371643(v=vs.85).aspx
Example C++ source code:
https://msdn.microsoft.com/de-de/library/windows/desktop/aa372138(v=vs.85).aspx
For now, the API is not stable and is probably going to change in future
perflib_exporter releases. If you want to use this library, send the author an email
so we can discuss your requirements and stabilize the API.
Names
Counter names and help texts are resolved by looking up an index in a name table.
Since Microsoft loves internalization, both names and help texts can be requested
any locally available language.
The library automatically loads the name tables and resolves all identifiers
in English ("Name" and "HelpText" struct members). You can manually resolve
identifiers in a different language by using the NameTable API.
Performance Counters intro
Windows has a system-wide performance counter mechanism. Most performance counters
are stored as actual counters, not gauges (with some exceptions).
There's additional metadata which defines how the counter should be presented to the user
(for example, as a calculated rate). This library disregards all of the display metadata.
At the top level, there's a number of performance counter objects.
Each object has counter definitions, which contain the metadata for a particular
counter, and either zero or multiple instances. We hide the fact that there are
objects with no instances, and simply return a single null instance.
There's one counter per counter definition and instance (or the object itself, if
there are no instances).
Behind the scenes, every perflib DLL provides one or more objects.
Perflib has a registry where DLLs are dynamically registered and
unregistered. Some third party applications like VMWare provide their own counters,
but this is, sadly, a rare occurrence.
Different Windows releases have different numbers of counters.
Objects and counters are identified by well-known indices.
Here's an example object with one instance:
4320 WSMan Quota Statistics [7 counters, 1 instance(s)]
`-- "WinRMService"
`-- Total Requests/Second [4322] = 59
`-- User Quota Violations/Second [4324] = 0
`-- System Quota Violations/Second [4326] = 0
`-- Active Shells [4328] = 0
`-- Active Operations [4330] = 0
`-- Active Users [4332] = 0
`-- Process ID [4334] = 928
All "per second" metrics are counters, the rest are gauges.
Another example, with no instance:
4600 Network QoS Policy [6 counters, 1 instance(s)]
`-- (default)
`-- Packets transmitted [4602] = 1744
`-- Packets transmitted/sec [4604] = 4852
`-- Bytes transmitted [4606] = 4853
`-- Bytes transmitted/sec [4608] = 180388626632
`-- Packets dropped [4610] = 0
`-- Packets dropped/sec [4612] = 0
You can access the same values using PowerShell's Get-Counter cmdlet
or the Performance Monitor.
> Get-Counter '\WSMan Quota Statistics(WinRMService)\Process ID'
Timestamp CounterSamples
--------- --------------
1/28/2018 10:18:00 PM \\DEV\wsman quota statistics(winrmservice)\process id :
928
> (Get-Counter '\Process(Idle)\% Processor Time').CounterSamples[0] | Format-List *
[..detailed output...]
Data for some of the objects is also available through WMI:
> Get-CimInstance Win32_PerfRawData_Counters_WSManQuotaStatistics
Name : WinRMService
[...]
ActiveOperations : 0
ActiveShells : 0
ActiveUsers : 0
ProcessID : 928
SystemQuotaViolationsPerSecond : 0
TotalRequestsPerSecond : 59
UserQuotaViolationsPerSecond : 0
*/
package perflib
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"sort"
"strings"
"syscall"
"unsafe"
)
// TODO: There's a LittleEndian field in the PERF header - we ought to check it
var bo = binary.LittleEndian
var counterNameTable NameTable
var helpNameTable NameTable
// Top-level performance object (like "Process").
type PerfObject struct {
Name string
// Same index you pass to QueryPerformanceData
NameIndex uint
HelpText string
HelpTextIndex uint
Instances []*PerfInstance
CounterDefs []*PerfCounterDef
Frequency int64
rawData *perfObjectType
}
// Each object can have multiple instances. For example,
// In case the object has no instances, we return one single PerfInstance with an empty name.
type PerfInstance struct {
// *not* resolved using a name table
Name string
Counters []*PerfCounter
rawData *perfInstanceDefinition
rawCounterBlock *perfCounterBlock
}
type PerfCounterDef struct {
Name string
NameIndex uint
HelpText string
HelpTextIndex uint
// For debugging - subject to removal. CounterType is a perflib
// implementation detail (see perflib.h) and should not be used outside
// of this package. We export it so we can show it on /dump.
CounterType uint32
// PERF_TYPE_COUNTER (otherwise, it's a gauge)
IsCounter bool
// PERF_COUNTER_BASE (base value of a multi-value fraction)
IsBaseValue bool
// PERF_TIMER_100NS
IsNanosecondCounter bool
rawData *perfCounterDefinition
}
type PerfCounter struct {
Value int64
Def *PerfCounterDef
}
// Error value returned by RegQueryValueEx if the buffer isn't sufficiently large
const errorMoreData = syscall.Errno(234)
var (
bufLenGlobal = uint32(400000)
bufLenCostly = uint32(2000000)
)
// Queries the performance counter buffer using RegQueryValueEx, returning raw bytes. See:
// https://msdn.microsoft.com/de-de/library/windows/desktop/aa373219(v=vs.85).aspx
func queryRawData(query string) ([]byte, error) {
var (
valType uint32
buffer []byte
bufLen uint32
)
switch query {
case "Global":
bufLen = bufLenGlobal
case "Costly":
bufLen = bufLenCostly
default:
// TODO: depends on the number of values requested
// need make an educated guess
numCounters := len(strings.Split(query, " "))
bufLen = uint32(150000 * numCounters)
}
buffer = make([]byte, bufLen)
name, err := syscall.UTF16PtrFromString(query)
if err != nil {
return nil, fmt.Errorf("failed to encode query string: %v", err)
}
for {
bufLen := uint32(len(buffer))
err := syscall.RegQueryValueEx(
syscall.HKEY_PERFORMANCE_DATA,
name,
nil,
&valType,
(*byte)(unsafe.Pointer(&buffer[0])),
&bufLen)
if err == errorMoreData {
newBuffer := make([]byte, len(buffer)+16384)
copy(newBuffer, buffer)
buffer = newBuffer
continue
} else if err != nil {
if errno, ok := err.(syscall.Errno); ok {
return nil, fmt.Errorf("ReqQueryValueEx failed: %v errno %d", err, uint(errno))
}
return nil, err
}
buffer = buffer[:bufLen]
switch query {
case "Global":
if bufLen > bufLenGlobal {
bufLenGlobal = bufLen
}
case "Costly":
if bufLen > bufLenCostly {
bufLenCostly = bufLen
}
}
return buffer, nil
}
}
func init() {
// Initialize global name tables
// TODO: profiling, add option to disable name tables if necessary
// Not sure if we should resolve the names at all or just have the caller do it on demand
// (for many use cases the index is sufficient)
counterNameTable = *QueryNameTable("Counter 009")
helpNameTable = *QueryNameTable("Help 009")
}
/*
Query all performance counters that match a given query.
The query can be any of the following:
- "Global" (all performance counters except those Windows marked as costly)
- "Costly" (only the costly ones)
- One or more object indices, separated by spaces ("238 2 5")
Many objects have dependencies - if you query one of them, you often get back
more than you asked for.
*/
func QueryPerformanceData(query string) ([]*PerfObject, error) {
buffer, err := queryRawData(query)
if err != nil {
return nil, err
}
r := bytes.NewReader(buffer)
// Read global header
header := new(perfDataBlock)
err = header.BinaryReadFrom(r)
if err != nil {
return nil, err
}
// Check for "PERF" signature
if header.Signature != [4]uint16{80, 69, 82, 70} {
panic("Invalid performance block header")
}
// Parse the performance data
numObjects := int(header.NumObjectTypes)
objects := make([]*PerfObject, numObjects)
objOffset := int64(header.HeaderLength)
for i := 0; i < numObjects; i++ {
r.Seek(objOffset, io.SeekStart)
obj := new(perfObjectType)
obj.BinaryReadFrom(r)
numCounterDefs := int(obj.NumCounters)
numInstances := int(obj.NumInstances)
// Perf objects can have no instances. The perflib differentiates
// between objects with instances and without, but we just create
// an empty instance in order to simplify the interface.
if numInstances <= 0 {
numInstances = 1
}
instances := make([]*PerfInstance, numInstances)
counterDefs := make([]*PerfCounterDef, numCounterDefs)
objects[i] = &PerfObject{
Name: obj.LookupName(),
NameIndex: uint(obj.ObjectNameTitleIndex),
HelpText: obj.LookupHelp(),
HelpTextIndex: uint(obj.ObjectHelpTitleIndex),
Instances: instances,
CounterDefs: counterDefs,
Frequency: obj.PerfFreq,
rawData: obj,
}
for i := 0; i < numCounterDefs; i++ {
def := new(perfCounterDefinition)
def.BinaryReadFrom(r)
counterDefs[i] = &PerfCounterDef{
Name: def.LookupName(),
NameIndex: uint(def.CounterNameTitleIndex),
HelpText: def.LookupHelp(),
HelpTextIndex: uint(def.CounterHelpTitleIndex),
rawData: def,
CounterType: def.CounterType,
IsCounter: def.CounterType&0x400 == 0x400,
IsBaseValue: def.CounterType&0x00030000 == 0x00030000,
IsNanosecondCounter: def.CounterType&0x00100000 == 0x00100000,
}
}
if obj.NumInstances <= 0 {
blockOffset := objOffset + int64(obj.DefinitionLength)
r.Seek(blockOffset, io.SeekStart)
_, counters := parseCounterBlock(buffer, r, blockOffset, counterDefs)
instances[0] = &PerfInstance{
Name: "",
Counters: counters,
rawData: nil,
rawCounterBlock: nil,
}
} else {
instOffset := objOffset + int64(obj.DefinitionLength)
for i := 0; i < numInstances; i++ {
r.Seek(instOffset, io.SeekStart)
inst := new(perfInstanceDefinition)
inst.BinaryReadFrom(r)
name, _ := readUTF16StringAtPos(r, instOffset+int64(inst.NameOffset), inst.NameLength)
pos := instOffset + int64(inst.ByteLength)
offset, counters := parseCounterBlock(buffer, r, pos, counterDefs)
instances[i] = &PerfInstance{
Name: name,
Counters: counters,
rawData: inst,
}
instOffset = pos + offset
}
}
// Next perfObjectType
objOffset += int64(obj.TotalByteLength)
}
return objects, nil
}
func parseCounterBlock(b []byte, r io.ReadSeeker, pos int64, defs []*PerfCounterDef) (int64, []*PerfCounter) {
r.Seek(pos, io.SeekStart)
block := new(perfCounterBlock)
block.BinaryReadFrom(r)
counters := make([]*PerfCounter, len(defs))
for i, def := range defs {
valueOffset := pos + int64(def.rawData.CounterOffset)
value := convertCounterValue(def.rawData, b, valueOffset)
counters[i] = &PerfCounter{
Value: value,
Def: def,
}
}
return int64(block.ByteLength), counters
}
func convertCounterValue(counterDef *perfCounterDefinition, buffer []byte, valueOffset int64) (value int64) {
/*
We can safely ignore the type since we're not interested in anything except the raw value.
We also ignore all of the other attributes (timestamp, presentation, multi counter values...)
See also: winperf.h.
Here's the most common value for CounterType:
65536 32bit counter
65792 64bit counter
272696320 32bit rate
272696576 64bit rate
*/
switch counterDef.CounterSize {
case 4:
value = int64(bo.Uint32(buffer[valueOffset:(valueOffset + 4)]))
case 8:
value = int64(bo.Uint64(buffer[valueOffset:(valueOffset + 8)]))
default:
value = int64(bo.Uint32(buffer[valueOffset:(valueOffset + 4)]))
}
return
}
// Sort slice of objects by index. This is useful for displaying
// a human-readable list or dump, but unnecessary otherwise.
func SortObjects(p []*PerfObject) {
sort.Slice(p, func(i, j int) bool {
return p[i].NameIndex < p[j].NameIndex
})
}

View File

@@ -0,0 +1,180 @@
package perflib
import (
"encoding/binary"
"io"
"syscall"
)
type binaryReaderFrom interface {
BinaryReadFrom(r io.Reader) error
}
/*
https://msdn.microsoft.com/de-de/library/windows/desktop/aa373157(v=vs.85).aspx
typedef struct _PERF_DATA_BLOCK {
WCHAR Signature[4];
DWORD LittleEndian;
DWORD Version;
DWORD Revision;
DWORD TotalByteLength;
DWORD HeaderLength;
DWORD NumObjectTypes;
DWORD DefaultObject;
SYSTEMTIME SystemTime;
LARGE_INTEGER PerfTime;
LARGE_INTEGER PerfFreq;
LARGE_INTEGER PerfTime100nSec;
DWORD SystemNameLength;
DWORD SystemNameOffset;
} PERF_DATA_BLOCK;
*/
type perfDataBlock struct {
Signature [4]uint16
LittleEndian uint32
Version uint32
Revision uint32
TotalByteLength uint32
HeaderLength uint32
NumObjectTypes uint32
DefaultObject int32
SystemTime syscall.Systemtime
_ uint32 // TODO
PerfTime int64
PerfFreq int64
PerfTime100nSec int64
SystemNameLength uint32
SystemNameOffset uint32
}
func (p *perfDataBlock) BinaryReadFrom(r io.Reader) error {
return binary.Read(r, bo, p)
}
/*
https://msdn.microsoft.com/en-us/library/windows/desktop/aa373160(v=vs.85).aspx
typedef struct _PERF_OBJECT_TYPE {
DWORD TotalByteLength;
DWORD DefinitionLength;
DWORD HeaderLength;
DWORD ObjectNameTitleIndex;
LPWSTR ObjectNameTitle;
DWORD ObjectHelpTitleIndex;
LPWSTR ObjectHelpTitle;
DWORD DetailLevel;
DWORD NumCounters;
DWORD DefaultCounter;
DWORD NumInstances;
DWORD CodePage;
LARGE_INTEGER PerfTime;
LARGE_INTEGER PerfFreq;
} PERF_OBJECT_TYPE;
*/
type perfObjectType struct {
TotalByteLength uint32
DefinitionLength uint32
HeaderLength uint32
ObjectNameTitleIndex uint32
ObjectNameTitle uint32
ObjectHelpTitleIndex uint32
ObjectHelpTitle uint32
DetailLevel uint32
NumCounters uint32
DefaultCounter int32
NumInstances int32
CodePage uint32
PerfTime int64
PerfFreq int64
}
func (p *perfObjectType) BinaryReadFrom(r io.Reader) error {
return binary.Read(r, bo, p)
}
/*
https://msdn.microsoft.com/en-us/library/windows/desktop/aa373150(v=vs.85).aspx
typedef struct _PERF_COUNTER_DEFINITION {
DWORD ByteLength;
DWORD CounterNameTitleIndex;
LPWSTR CounterNameTitle;
DWORD CounterHelpTitleIndex;
LPWSTR CounterHelpTitle;
LONG DefaultScale;
DWORD DetailLevel;
DWORD CounterType;
DWORD CounterSize;
DWORD CounterOffset;
} PERF_COUNTER_DEFINITION;
*/
type perfCounterDefinition struct {
ByteLength uint32
CounterNameTitleIndex uint32
CounterNameTitle uint32
CounterHelpTitleIndex uint32
CounterHelpTitle uint32
DefaultScale int32
DetailLevel uint32
CounterType uint32
CounterSize uint32
CounterOffset uint32
}
func (p *perfCounterDefinition) BinaryReadFrom(r io.Reader) error {
return binary.Read(r, bo, p)
}
func (p *perfCounterDefinition) LookupName() string {
return counterNameTable.LookupString(p.CounterNameTitleIndex)
}
func (p *perfCounterDefinition) LookupHelp() string {
return helpNameTable.LookupString(p.CounterHelpTitleIndex)
}
/*
https://msdn.microsoft.com/en-us/library/windows/desktop/aa373147(v=vs.85).aspx
typedef struct _PERF_COUNTER_BLOCK {
DWORD ByteLength;
} PERF_COUNTER_BLOCK;
*/
type perfCounterBlock struct {
ByteLength uint32
}
func (p *perfCounterBlock) BinaryReadFrom(r io.Reader) error {
return binary.Read(r, bo, p)
}
/*
https://msdn.microsoft.com/en-us/library/windows/desktop/aa373159(v=vs.85).aspx
typedef struct _PERF_INSTANCE_DEFINITION {
DWORD ByteLength;
DWORD ParentObjectTitleIndex;
DWORD ParentObjectInstance;
DWORD UniqueID;
DWORD NameOffset;
DWORD NameLength;
} PERF_INSTANCE_DEFINITION;
*/
type perfInstanceDefinition struct {
ByteLength uint32
ParentObjectTitleIndex uint32
ParentObjectInstance uint32
UniqueID uint32
NameOffset uint32
NameLength uint32
}
func (p *perfInstanceDefinition) BinaryReadFrom(r io.Reader) error {
return binary.Read(r, bo, p)
}

View File

@@ -0,0 +1,49 @@
package perflib
import (
"encoding/binary"
"io"
"syscall"
)
// Read an unterminated UTF16 string at a given position, specifying its length
func readUTF16StringAtPos(r io.ReadSeeker, absPos int64, length uint32) (string, error) {
value := make([]uint16, length/2)
_, err := r.Seek(absPos, io.SeekStart)
if err != nil {
return "", err
}
err = binary.Read(r, bo, value)
if err != nil {
return "", err
}
return syscall.UTF16ToString(value), nil
}
// Reads a null-terminated UTF16 string at the current offset
func readUTF16String(r io.Reader) (string, error) {
var err error
b := make([]byte, 2)
out := make([]uint16, 0, 100)
for i := 0; err == nil; i += 2 {
_, err = r.Read(b)
if b[0] == 0 && b[1] == 0 {
break
}
out = append(out, bo.Uint16(b))
}
if err != nil {
return "", err
}
return syscall.UTF16ToString(out), nil
}