diff --git a/.gitignore b/.gitignore index 39fbde39..57676a59 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ VERSION *.swp *.un~ output/ -.vscode \ No newline at end of file +.vscode +.idea \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 101a4a7b..23202605 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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\ diff --git a/collector/ad.go b/collector/ad.go index 8ded59a2..914bf222 100644 --- a/collector/ad.go +++ b/collector/ad.go @@ -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 diff --git a/collector/collector.go b/collector/collector.go new file mode 100644 index 00000000..3cdac9a7 --- /dev/null +++ b/collector/collector.go @@ -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 +} diff --git a/collector/container.go b/collector/container.go index 87e02e06..6315263a 100644 --- a/collector/container.go +++ b/collector/container.go @@ -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 diff --git a/collector/cpu.go b/collector/cpu.go index 57b8f38d..8aa7fa15 100644 --- a/collector/cpu.go +++ b/collector/cpu.go @@ -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 } diff --git a/collector/cs.go b/collector/cs.go index 47feb3bd..6c4ada3b 100644 --- a/collector/cs.go +++ b/collector/cs.go @@ -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 diff --git a/collector/dns.go b/collector/dns.go index 7df67200..6e7684b8 100644 --- a/collector/dns.go +++ b/collector/dns.go @@ -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 diff --git a/collector/hyperv.go b/collector/hyperv.go index 36fb0980..c0b63778 100644 --- a/collector/hyperv.go +++ b/collector/hyperv.go @@ -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 diff --git a/collector/iis.go b/collector/iis.go index 6cc961c6..1914c3f1 100644 --- a/collector/iis.go +++ b/collector/iis.go @@ -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 diff --git a/collector/logical_disk.go b/collector/logical_disk.go index 8a8a3e43..8f036cc4 100644 --- a/collector/logical_disk.go +++ b/collector/logical_disk.go @@ -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 diff --git a/collector/memory.go b/collector/memory.go index 2a6571c0..ed3b60df 100644 --- a/collector/memory.go +++ b/collector/memory.go @@ -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 diff --git a/collector/msmq.go b/collector/msmq.go index 0498c83a..2dd951e2 100644 --- a/collector/msmq.go +++ b/collector/msmq.go @@ -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 diff --git a/collector/mssql.go b/collector/mssql.go index c166ecbc..9b0d3744 100644 --- a/collector/mssql.go +++ b/collector/mssql.go @@ -1795,7 +1795,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) diff --git a/collector/net.go b/collector/net.go index 6c0c4576..b22f96a3 100644 --- a/collector/net.go +++ b/collector/net.go @@ -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 diff --git a/collector/netframework_clrexceptions.go b/collector/netframework_clrexceptions.go index b94d9915..8030761d 100644 --- a/collector/netframework_clrexceptions.go +++ b/collector/netframework_clrexceptions.go @@ -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 diff --git a/collector/netframework_clrinterop.go b/collector/netframework_clrinterop.go index 2c3c54be..8bfa2e72 100644 --- a/collector/netframework_clrinterop.go +++ b/collector/netframework_clrinterop.go @@ -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 diff --git a/collector/netframework_clrjit.go b/collector/netframework_clrjit.go index 55d3d993..447a3f68 100644 --- a/collector/netframework_clrjit.go +++ b/collector/netframework_clrjit.go @@ -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 diff --git a/collector/netframework_clrloading.go b/collector/netframework_clrloading.go index 6a6a3e27..ae91b95a 100644 --- a/collector/netframework_clrloading.go +++ b/collector/netframework_clrloading.go @@ -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 diff --git a/collector/netframework_clrlocksandthreads.go b/collector/netframework_clrlocksandthreads.go index 32a3ecf0..19b996fc 100644 --- a/collector/netframework_clrlocksandthreads.go +++ b/collector/netframework_clrlocksandthreads.go @@ -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 diff --git a/collector/netframework_clrmemory.go b/collector/netframework_clrmemory.go index b229df1a..c2b9aca4 100644 --- a/collector/netframework_clrmemory.go +++ b/collector/netframework_clrmemory.go @@ -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 diff --git a/collector/netframework_clrremoting.go b/collector/netframework_clrremoting.go index 5a0b850a..44917ac1 100644 --- a/collector/netframework_clrremoting.go +++ b/collector/netframework_clrremoting.go @@ -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 diff --git a/collector/netframework_clrsecurity.go b/collector/netframework_clrsecurity.go index 8def82ac..f7a463cc 100644 --- a/collector/netframework_clrsecurity.go +++ b/collector/netframework_clrsecurity.go @@ -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 diff --git a/collector/os.go b/collector/os.go index d59d5a4b..b13e0d3b 100644 --- a/collector/os.go +++ b/collector/os.go @@ -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 diff --git a/collector/perflib.go b/collector/perflib.go new file mode 100644 index 00000000..3b8af9fa --- /dev/null +++ b/collector/perflib.go @@ -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 +} diff --git a/collector/perflib_test.go b/collector/perflib_test.go new file mode 100644 index 00000000..98f0693b --- /dev/null +++ b/collector/perflib_test.go @@ -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) + } + }) + } +} diff --git a/collector/process.go b/collector/process.go index 5204ecd5..bdcaffa3 100644 --- a/collector/process.go +++ b/collector/process.go @@ -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 diff --git a/collector/service.go b/collector/service.go index e6607435..b195127d 100644 --- a/collector/service.go +++ b/collector/service.go @@ -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 diff --git a/collector/system.go b/collector/system.go index c09993ea..13b5fb46 100644 --- a/collector/system.go +++ b/collector/system.go @@ -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 diff --git a/collector/tcp.go b/collector/tcp.go index 088ac6dd..44a68712 100644 --- a/collector/tcp.go +++ b/collector/tcp.go @@ -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 diff --git a/collector/textfile.go b/collector/textfile.go index e592da0b..72cce988 100644 --- a/collector/textfile.go +++ b/collector/textfile.go @@ -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{} diff --git a/collector/thermalzone.go b/collector/thermalzone.go index 634de333..6afde540 100644 --- a/collector/thermalzone.go +++ b/collector/thermalzone.go @@ -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 { diff --git a/collector/vmware.go b/collector/vmware.go index f71854f1..37de16ab 100644 --- a/collector/vmware.go +++ b/collector/vmware.go @@ -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 diff --git a/collector/wmi.go b/collector/wmi.go index b4a99ba8..615db1f7 100644 --- a/collector/wmi.go +++ b/collector/wmi.go @@ -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() diff --git a/docs/collector.cpu.md b/docs/collector.cpu.md index 96c1ccf8..01c57db0 100644 --- a/docs/collector.cpu.md +++ b/docs/collector.cpu.md @@ -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!_ diff --git a/exporter.go b/exporter.go index f7ac83e2..67a54a02 100644 --- a/exporter.go +++ b/exporter.go @@ -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) +} diff --git a/tools/collector-generator/collector.template b/tools/collector-generator/collector.template index 206fbf83..41aec6ae 100644 --- a/tools/collector-generator/collector.template +++ b/tools/collector-generator/collector.template @@ -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 diff --git a/vendor/github.com/leoluk/perflib_exporter/LICENSE b/vendor/github.com/leoluk/perflib_exporter/LICENSE new file mode 100644 index 00000000..169f2ab9 --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/LICENSE @@ -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. diff --git a/vendor/github.com/leoluk/perflib_exporter/collector/collector.go b/vendor/github.com/leoluk/perflib_exporter/collector/collector.go new file mode 100644 index 00000000..990cfca1 --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/collector/collector.go @@ -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 +} diff --git a/vendor/github.com/leoluk/perflib_exporter/collector/mangle.go b/vendor/github.com/leoluk/perflib_exporter/collector/mangle.go new file mode 100644 index 00000000..cc1ed1b7 --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/collector/mangle.go @@ -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, + ) +} diff --git a/vendor/github.com/leoluk/perflib_exporter/collector/mapper.go b/vendor/github.com/leoluk/perflib_exporter/collector/mapper.go new file mode 100644 index 00000000..f9f2278e --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/collector/mapper.go @@ -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 +} diff --git a/vendor/github.com/leoluk/perflib_exporter/collector/merge.go b/vendor/github.com/leoluk/perflib_exporter/collector/merge.go new file mode 100644 index 00000000..9832e7f5 --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/collector/merge.go @@ -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)) +} diff --git a/vendor/github.com/leoluk/perflib_exporter/collector/promote.go b/vendor/github.com/leoluk/perflib_exporter/collector/promote.go new file mode 100644 index 00000000..db45b5ac --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/collector/promote.go @@ -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 +} diff --git a/vendor/github.com/leoluk/perflib_exporter/perflib/nametable.go b/vendor/github.com/leoluk/perflib_exporter/perflib/nametable.go new file mode 100644 index 00000000..9ad11ef3 --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/perflib/nametable.go @@ -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 +} diff --git a/vendor/github.com/leoluk/perflib_exporter/perflib/perflib.go b/vendor/github.com/leoluk/perflib_exporter/perflib/perflib.go new file mode 100644 index 00000000..81601b43 --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/perflib/perflib.go @@ -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 + }) + +} diff --git a/vendor/github.com/leoluk/perflib_exporter/perflib/raw_types.go b/vendor/github.com/leoluk/perflib_exporter/perflib/raw_types.go new file mode 100644 index 00000000..325756fd --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/perflib/raw_types.go @@ -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) +} diff --git a/vendor/github.com/leoluk/perflib_exporter/perflib/utf16.go b/vendor/github.com/leoluk/perflib_exporter/perflib/utf16.go new file mode 100644 index 00000000..3b0cec9e --- /dev/null +++ b/vendor/github.com/leoluk/perflib_exporter/perflib/utf16.go @@ -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 +}