mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-16 09:46:35 +00:00
chore: Remove registry based perfdata collector (#1742)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -1,24 +1,30 @@
|
||||
//go:build windows
|
||||
|
||||
package v2
|
||||
package perfdata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
InstanceAll = []string{"*"}
|
||||
InstanceTotal = []string{"_Total"}
|
||||
)
|
||||
|
||||
type Collector struct {
|
||||
object string
|
||||
counters map[string]Counter
|
||||
handle pdhQueryHandle
|
||||
totalCounterRequested bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
@@ -37,7 +43,7 @@ func NewCollector(object string, instances []string, counters []string) (*Collec
|
||||
}
|
||||
|
||||
if len(instances) == 0 {
|
||||
instances = []string{perftypes.EmptyInstance}
|
||||
instances = []string{EmptyInstance}
|
||||
}
|
||||
|
||||
collector := &Collector{
|
||||
@@ -45,6 +51,7 @@ func NewCollector(object string, instances []string, counters []string) (*Collec
|
||||
counters: make(map[string]Counter, len(counters)),
|
||||
handle: handle,
|
||||
totalCounterRequested: slices.Contains(instances, "_Total"),
|
||||
mu: sync.RWMutex{},
|
||||
}
|
||||
|
||||
for _, counterName := range counters {
|
||||
@@ -90,7 +97,7 @@ func NewCollector(object string, instances []string, counters []string) (*Collec
|
||||
counter.Type = ci.DwType
|
||||
counter.Desc = windows.UTF16PtrToString(ci.SzExplainText)
|
||||
|
||||
if counter.Type == perftypes.PERF_ELAPSED_TIME {
|
||||
if counter.Type == PERF_ELAPSED_TIME {
|
||||
if ret := PdhGetCounterTimeBase(counterHandle, &counter.Frequency); ret != ErrorSuccess {
|
||||
return nil, fmt.Errorf("PdhGetCounterTimeBase: %w", NewPdhError(ret))
|
||||
}
|
||||
@@ -121,16 +128,23 @@ func (c *Collector) Describe() map[string]string {
|
||||
return desc
|
||||
}
|
||||
|
||||
func (c *Collector) Collect() (map[string]map[string]perftypes.CounterValues, error) {
|
||||
func (c *Collector) Collect() (map[string]map[string]CounterValues, error) {
|
||||
if len(c.counters) == 0 {
|
||||
return map[string]map[string]perftypes.CounterValues{}, nil
|
||||
return map[string]map[string]CounterValues{}, nil
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if c.handle == 0 {
|
||||
return map[string]map[string]CounterValues{}, nil
|
||||
}
|
||||
|
||||
if ret := PdhCollectQueryData(c.handle); ret != ErrorSuccess {
|
||||
return nil, fmt.Errorf("failed to collect query data: %w", NewPdhError(ret))
|
||||
}
|
||||
|
||||
var data map[string]map[string]perftypes.CounterValues
|
||||
var data map[string]map[string]CounterValues
|
||||
|
||||
for _, counter := range c.counters {
|
||||
for _, instance := range counter.Instances {
|
||||
@@ -159,11 +173,11 @@ func (c *Collector) Collect() (map[string]map[string]perftypes.CounterValues, er
|
||||
items := unsafe.Slice((*PdhRawCounterItem)(unsafe.Pointer(&buf[0])), itemCount)
|
||||
|
||||
if data == nil {
|
||||
data = make(map[string]map[string]perftypes.CounterValues, itemCount)
|
||||
data = make(map[string]map[string]CounterValues, itemCount)
|
||||
}
|
||||
|
||||
var metricType prometheus.ValueType
|
||||
if val, ok := perftypes.SupportedCounterTypes[counter.Type]; ok {
|
||||
if val, ok := SupportedCounterTypes[counter.Type]; ok {
|
||||
metricType = val
|
||||
} else {
|
||||
metricType = prometheus.GaugeValue
|
||||
@@ -177,14 +191,14 @@ func (c *Collector) Collect() (map[string]map[string]perftypes.CounterValues, er
|
||||
}
|
||||
|
||||
if instanceName == "" || instanceName == "*" {
|
||||
instanceName = perftypes.EmptyInstance
|
||||
instanceName = EmptyInstance
|
||||
}
|
||||
|
||||
if _, ok := data[instanceName]; !ok {
|
||||
data[instanceName] = make(map[string]perftypes.CounterValues, len(c.counters))
|
||||
data[instanceName] = make(map[string]CounterValues, len(c.counters))
|
||||
}
|
||||
|
||||
values := perftypes.CounterValues{
|
||||
values := CounterValues{
|
||||
Type: metricType,
|
||||
}
|
||||
|
||||
@@ -193,11 +207,11 @@ func (c *Collector) Collect() (map[string]map[string]perftypes.CounterValues, er
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/perfctrs/calculating-counter-values
|
||||
|
||||
switch counter.Type {
|
||||
case perftypes.PERF_ELAPSED_TIME:
|
||||
values.FirstValue = float64((item.RawValue.FirstValue - perftypes.WindowsEpoch) / counter.Frequency)
|
||||
case perftypes.PERF_100NSEC_TIMER, perftypes.PERF_PRECISION_100NS_TIMER:
|
||||
values.FirstValue = float64(item.RawValue.FirstValue) * perftypes.TicksToSecondScaleFactor
|
||||
case perftypes.PERF_AVERAGE_BULK:
|
||||
case PERF_ELAPSED_TIME:
|
||||
values.FirstValue = float64((item.RawValue.FirstValue - WindowsEpoch) / counter.Frequency)
|
||||
case PERF_100NSEC_TIMER, PERF_PRECISION_100NS_TIMER:
|
||||
values.FirstValue = float64(item.RawValue.FirstValue) * TicksToSecondScaleFactor
|
||||
case PERF_AVERAGE_BULK:
|
||||
values.FirstValue = float64(item.RawValue.FirstValue)
|
||||
values.SecondValue = float64(item.RawValue.SecondValue)
|
||||
default:
|
||||
@@ -214,13 +228,18 @@ func (c *Collector) Collect() (map[string]map[string]perftypes.CounterValues, er
|
||||
}
|
||||
|
||||
func (c *Collector) Close() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
PdhCloseQuery(c.handle)
|
||||
|
||||
c.handle = 0
|
||||
}
|
||||
|
||||
func formatCounterPath(object, instance, counterName string) string {
|
||||
var counterPath string
|
||||
|
||||
if instance == perftypes.EmptyInstance {
|
||||
if instance == EmptyInstance {
|
||||
counterPath = fmt.Sprintf(`\%s\%s`, object, counterName)
|
||||
} else {
|
||||
counterPath = fmt.Sprintf(`\%s(%s)\%s`, object, instance, counterName)
|
||||
@@ -1,9 +1,11 @@
|
||||
package v2_test
|
||||
//go:build windows
|
||||
|
||||
package perfdata_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v2 "github.com/prometheus-community/windows_exporter/internal/perfdata/v2"
|
||||
v2 "github.com/prometheus-community/windows_exporter/internal/perfdata"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//go:build windows
|
||||
|
||||
package v2_test
|
||||
package perfdata_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v2 "github.com/prometheus-community/windows_exporter/internal/perfdata/v2"
|
||||
v2 "github.com/prometheus-community/windows_exporter/internal/perfdata"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -1,4 +1,6 @@
|
||||
package perftypes
|
||||
//go:build windows
|
||||
|
||||
package perfdata
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package v2
|
||||
//go:build windows
|
||||
|
||||
package perfdata
|
||||
|
||||
import "errors"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
//go:build windows
|
||||
|
||||
package v2
|
||||
package perfdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
//go:build windows
|
||||
|
||||
package v2
|
||||
package perfdata
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
//go:build windows
|
||||
|
||||
package v2
|
||||
package perfdata
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package perfdata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
|
||||
v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1"
|
||||
v2 "github.com/prometheus-community/windows_exporter/internal/perfdata/v2"
|
||||
)
|
||||
|
||||
type Collector interface {
|
||||
Describe() map[string]string
|
||||
Collect() (map[string]map[string]perftypes.CounterValues, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
type Engine int
|
||||
|
||||
const (
|
||||
_ Engine = iota
|
||||
V1
|
||||
V2
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownEngine = errors.New("unknown engine")
|
||||
AllInstances = []string{"*"}
|
||||
)
|
||||
|
||||
func NewCollector(engine Engine, object string, instances []string, counters []string) (Collector, error) { //nolint:ireturn
|
||||
switch engine {
|
||||
case V1:
|
||||
return v1.NewCollector(object, instances, counters)
|
||||
case V2:
|
||||
return v2.NewCollector(object, instances, counters)
|
||||
default:
|
||||
return nil, ErrUnknownEngine
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package perftypes
|
||||
//go:build windows
|
||||
|
||||
package perfdata
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
const EmptyInstance = "------"
|
||||
|
||||
var TotalInstance = []string{"_Total"}
|
||||
|
||||
type CounterValues struct {
|
||||
Type prometheus.ValueType
|
||||
FirstValue float64
|
||||
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
@@ -1,115 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type Collector struct {
|
||||
object string
|
||||
query string
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
Name string
|
||||
Desc string
|
||||
Instances map[string]uint32
|
||||
Type uint32
|
||||
Frequency float64
|
||||
}
|
||||
|
||||
func NewCollector(object string, _ []string, _ []string) (*Collector, error) {
|
||||
collector := &Collector{
|
||||
object: object,
|
||||
query: MapCounterToIndex(object),
|
||||
}
|
||||
|
||||
if _, err := collector.Collect(); err != nil {
|
||||
return nil, fmt.Errorf("failed to collect initial data: %w", err)
|
||||
}
|
||||
|
||||
return collector, nil
|
||||
}
|
||||
|
||||
func (c *Collector) Describe() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (c *Collector) Collect() (map[string]map[string]perftypes.CounterValues, error) {
|
||||
perfObjects, err := QueryPerformanceData(c.query, c.object)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("QueryPerformanceData: %w", err)
|
||||
}
|
||||
|
||||
if len(perfObjects) == 0 || perfObjects[0] == nil || len(perfObjects[0].Instances) == 0 {
|
||||
return map[string]map[string]perftypes.CounterValues{}, nil
|
||||
}
|
||||
|
||||
data := make(map[string]map[string]perftypes.CounterValues, len(perfObjects[0].Instances))
|
||||
|
||||
for _, perfObject := range perfObjects {
|
||||
if perfObject.Name != c.object {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, perfInstance := range perfObject.Instances {
|
||||
instanceName := perfInstance.Name
|
||||
if strings.HasSuffix(instanceName, "_Total") {
|
||||
continue
|
||||
}
|
||||
|
||||
if instanceName == "" || instanceName == "*" {
|
||||
instanceName = perftypes.EmptyInstance
|
||||
}
|
||||
|
||||
if _, ok := data[instanceName]; !ok {
|
||||
data[instanceName] = make(map[string]perftypes.CounterValues, len(perfInstance.Counters))
|
||||
}
|
||||
|
||||
for _, perfCounter := range perfInstance.Counters {
|
||||
if perfCounter.Def.IsBaseValue && !perfCounter.Def.IsNanosecondCounter {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := data[instanceName][perfCounter.Def.Name]; !ok {
|
||||
data[instanceName][perfCounter.Def.Name] = perftypes.CounterValues{
|
||||
Type: prometheus.GaugeValue,
|
||||
}
|
||||
}
|
||||
|
||||
var metricType prometheus.ValueType
|
||||
if val, ok := perftypes.SupportedCounterTypes[perfCounter.Def.CounterType]; ok {
|
||||
metricType = val
|
||||
} else {
|
||||
metricType = prometheus.GaugeValue
|
||||
}
|
||||
|
||||
values := perftypes.CounterValues{
|
||||
Type: metricType,
|
||||
}
|
||||
|
||||
switch perfCounter.Def.CounterType {
|
||||
case perftypes.PERF_ELAPSED_TIME:
|
||||
values.FirstValue = float64(perfCounter.Value-perftypes.WindowsEpoch) / float64(perfObject.Frequency)
|
||||
values.SecondValue = float64(perfCounter.SecondValue-perftypes.WindowsEpoch) / float64(perfObject.Frequency)
|
||||
case perftypes.PERF_100NSEC_TIMER, perftypes.PERF_PRECISION_100NS_TIMER:
|
||||
values.FirstValue = float64(perfCounter.Value) * perftypes.TicksToSecondScaleFactor
|
||||
values.SecondValue = float64(perfCounter.SecondValue) * perftypes.TicksToSecondScaleFactor
|
||||
default:
|
||||
values.FirstValue = float64(perfCounter.Value)
|
||||
values.SecondValue = float64(perfCounter.SecondValue)
|
||||
}
|
||||
|
||||
data[instanceName][perfCounter.Def.Name] = values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Collector) Close() {
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
var CounterNameTable = *QueryNameTable("Counter 009")
|
||||
|
||||
func (p *perfObjectType) LookupName() string {
|
||||
return CounterNameTable.LookupString(p.ObjectNameTitleIndex)
|
||||
}
|
||||
|
||||
type NameTable struct {
|
||||
once sync.Once
|
||||
|
||||
name string
|
||||
|
||||
table struct {
|
||||
index map[uint32]string
|
||||
string map[string]uint32
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NameTable) LookupString(index uint32) string {
|
||||
t.initialize()
|
||||
|
||||
return t.table.index[index]
|
||||
}
|
||||
|
||||
func (t *NameTable) LookupIndex(str string) uint32 {
|
||||
t.initialize()
|
||||
|
||||
return t.table.string[str]
|
||||
}
|
||||
|
||||
// QueryNameTable Query a perflib name table from the v1. Specify the type and the language
|
||||
// code (i.e. "Counter 009" or "Help 009") for English language.
|
||||
func QueryNameTable(tableName string) *NameTable {
|
||||
return &NameTable{
|
||||
name: tableName,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NameTable) initialize() {
|
||||
t.once.Do(func() {
|
||||
t.table.index = make(map[uint32]string)
|
||||
t.table.string = make(map[string]uint32)
|
||||
|
||||
buffer, err := queryRawData(t.name)
|
||||
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
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("Invalid index ", index))
|
||||
}
|
||||
|
||||
indexInt, _ := strconv.Atoi(index)
|
||||
|
||||
t.table.index[uint32(indexInt)] = desc
|
||||
t.table.string[desc] = uint32(indexInt)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,490 +0,0 @@
|
||||
package v1
|
||||
|
||||
/*
|
||||
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 v1 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
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// TODO: There's a LittleEndian field in the PERF header - we ought to check it.
|
||||
var bo = binary.LittleEndian
|
||||
|
||||
// PerfObject Top-level performance object (like "Process").
|
||||
type PerfObject struct {
|
||||
Name string
|
||||
// NameIndex Same index you pass to QueryPerformanceData
|
||||
NameIndex uint
|
||||
Instances []*PerfInstance
|
||||
CounterDefs []*PerfCounterDef
|
||||
|
||||
Frequency int64
|
||||
|
||||
rawData *perfObjectType
|
||||
}
|
||||
|
||||
// PerfInstance 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
|
||||
|
||||
// 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
|
||||
HasSecondValue bool
|
||||
|
||||
rawData *perfCounterDefinition
|
||||
}
|
||||
|
||||
type PerfCounter struct {
|
||||
Value int64
|
||||
Def *PerfCounterDef
|
||||
SecondValue int64
|
||||
}
|
||||
|
||||
var (
|
||||
bufLenGlobal = uint32(400000)
|
||||
bufLenCostly = uint32(2000000)
|
||||
)
|
||||
|
||||
// queryRawData 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 := windows.UTF16PtrFromString(query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode query string: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
bufLen := uint32(len(buffer))
|
||||
|
||||
err := windows.RegQueryValueEx(
|
||||
windows.HKEY_PERFORMANCE_DATA,
|
||||
name,
|
||||
nil,
|
||||
&valType,
|
||||
(*byte)(unsafe.Pointer(&buffer[0])),
|
||||
&bufLen)
|
||||
|
||||
switch {
|
||||
case errors.Is(err, error(windows.ERROR_MORE_DATA)):
|
||||
newBuffer := make([]byte, len(buffer)+16384)
|
||||
copy(newBuffer, buffer)
|
||||
buffer = newBuffer
|
||||
|
||||
continue
|
||||
case errors.Is(err, error(windows.ERROR_BUSY)):
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
continue
|
||||
case err != nil:
|
||||
var errNo windows.Errno
|
||||
if errors.As(err, &errNo) {
|
||||
return nil, fmt.Errorf("ReqQueryValueEx failed: %w 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
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
QueryPerformanceData 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, counterName 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, fmt.Errorf("failed to read performance data block for %q with: %w", query, 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)
|
||||
numFilteredObjects := 0
|
||||
|
||||
objects := make([]*PerfObject, numObjects)
|
||||
|
||||
objOffset := int64(header.HeaderLength)
|
||||
|
||||
for i := range numObjects {
|
||||
_, err := r.Seek(objOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj := new(perfObjectType)
|
||||
|
||||
err = obj.BinaryReadFrom(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
perfCounterName := obj.LookupName()
|
||||
|
||||
if counterName != "" && perfCounterName != counterName {
|
||||
objOffset += int64(obj.TotalByteLength)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
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: perfCounterName,
|
||||
NameIndex: uint(obj.ObjectNameTitleIndex),
|
||||
Instances: instances,
|
||||
CounterDefs: counterDefs,
|
||||
Frequency: obj.PerfFreq,
|
||||
rawData: obj,
|
||||
}
|
||||
|
||||
for i := range numCounterDefs {
|
||||
def := new(perfCounterDefinition)
|
||||
|
||||
err := def.BinaryReadFrom(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
counterDefs[i] = &PerfCounterDef{
|
||||
Name: def.LookupName(),
|
||||
NameIndex: uint(def.CounterNameTitleIndex),
|
||||
rawData: def,
|
||||
|
||||
CounterType: def.CounterType,
|
||||
|
||||
IsCounter: def.CounterType&0x400 == 0x400,
|
||||
IsBaseValue: def.CounterType&0x00030000 == 0x00030000,
|
||||
IsNanosecondCounter: def.CounterType&0x00100000 == 0x00100000,
|
||||
HasSecondValue: def.CounterType == perftypes.PERF_AVERAGE_BULK,
|
||||
}
|
||||
}
|
||||
|
||||
if obj.NumInstances <= 0 { //nolint:nestif
|
||||
blockOffset := objOffset + int64(obj.DefinitionLength)
|
||||
|
||||
if _, err := r.Seek(blockOffset, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, counters, err := parseCounterBlock(buffer, r, blockOffset, counterDefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances[0] = &PerfInstance{
|
||||
Name: "",
|
||||
Counters: counters,
|
||||
rawData: nil,
|
||||
rawCounterBlock: nil,
|
||||
}
|
||||
} else {
|
||||
instOffset := objOffset + int64(obj.DefinitionLength)
|
||||
|
||||
for i := range numInstances {
|
||||
if _, err := r.Seek(instOffset, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inst := new(perfInstanceDefinition)
|
||||
|
||||
if err = inst.BinaryReadFrom(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name, _ := readUTF16StringAtPos(r, instOffset+int64(inst.NameOffset), inst.NameLength)
|
||||
pos := instOffset + int64(inst.ByteLength)
|
||||
|
||||
offset, counters, err := parseCounterBlock(buffer, r, pos, counterDefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances[i] = &PerfInstance{
|
||||
Name: name,
|
||||
Counters: counters,
|
||||
rawData: inst,
|
||||
}
|
||||
|
||||
instOffset = pos + offset
|
||||
}
|
||||
}
|
||||
|
||||
if counterName != "" {
|
||||
return objects[i : i+1], nil
|
||||
}
|
||||
|
||||
// Next perfObjectType
|
||||
objOffset += int64(obj.TotalByteLength)
|
||||
numFilteredObjects++
|
||||
}
|
||||
|
||||
return objects[:numFilteredObjects], nil
|
||||
}
|
||||
|
||||
func parseCounterBlock(b []byte, r io.ReadSeeker, pos int64, defs []*PerfCounterDef) (int64, []*PerfCounter, error) {
|
||||
_, err := r.Seek(pos, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
block := new(perfCounterBlock)
|
||||
|
||||
err = block.BinaryReadFrom(r)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
counters := make([]*PerfCounter, len(defs))
|
||||
|
||||
for i, def := range defs {
|
||||
valueOffset := pos + int64(def.rawData.CounterOffset)
|
||||
value := convertCounterValue(def.rawData, b, valueOffset)
|
||||
secondValue := int64(0)
|
||||
|
||||
if def.HasSecondValue {
|
||||
secondValue = convertCounterValue(def.rawData, b, valueOffset+8)
|
||||
}
|
||||
|
||||
counters[i] = &PerfCounter{
|
||||
Value: value,
|
||||
Def: def,
|
||||
SecondValue: secondValue,
|
||||
}
|
||||
}
|
||||
|
||||
return int64(block.ByteLength), counters, nil
|
||||
}
|
||||
|
||||
func convertCounterValue(counterDef *perfCounterDefinition, buffer []byte, valueOffset int64) 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:
|
||||
return int64(bo.Uint32(buffer[valueOffset:(valueOffset + 4)]))
|
||||
case 8:
|
||||
return int64(bo.Uint64(buffer[valueOffset:(valueOffset + 8)]))
|
||||
default:
|
||||
return int64(bo.Uint32(buffer[valueOffset:(valueOffset + 4)]))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkQueryPerformanceData(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = QueryPerformanceData("Global", "")
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
/*
|
||||
perfDataBlock
|
||||
See: 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 windows.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)
|
||||
}
|
||||
|
||||
/*
|
||||
perfObjectType
|
||||
See: 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)
|
||||
}
|
||||
|
||||
/*
|
||||
perfCounterDefinition
|
||||
See: 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)
|
||||
}
|
||||
|
||||
/*
|
||||
perfCounterBlock
|
||||
See: 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)
|
||||
}
|
||||
|
||||
/*
|
||||
perfInstanceDefinition
|
||||
See: 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)
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
|
||||
)
|
||||
|
||||
func UnmarshalObject(obj *PerfObject, vs interface{}, logger *slog.Logger) error {
|
||||
if obj == nil {
|
||||
return errors.New("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]*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 := range target.NumField() {
|
||||
f := rt.Field(i)
|
||||
|
||||
tag := f.Tag.Get("perflib")
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
secondValue := false
|
||||
|
||||
st := strings.Split(tag, ",")
|
||||
tag = st[0]
|
||||
|
||||
for _, t := range st {
|
||||
if t == "secondvalue" {
|
||||
secondValue = true
|
||||
}
|
||||
}
|
||||
|
||||
ctr, found := counters[tag]
|
||||
if !found {
|
||||
logger.Debug(fmt.Sprintf("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)
|
||||
}
|
||||
|
||||
if secondValue {
|
||||
if !ctr.Def.HasSecondValue {
|
||||
return fmt.Errorf("tagged field %v expected a SecondValue, which was not present", f.Name)
|
||||
}
|
||||
|
||||
target.Field(i).SetFloat(float64(ctr.SecondValue))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch ctr.Def.CounterType {
|
||||
case perftypes.PERF_ELAPSED_TIME:
|
||||
target.Field(i).SetFloat(float64(ctr.Value-perftypes.WindowsEpoch) / float64(obj.Frequency))
|
||||
case perftypes.PERF_100NSEC_TIMER, perftypes.PERF_PRECISION_100NS_TIMER:
|
||||
target.Field(i).SetFloat(float64(ctr.Value) * perftypes.TicksToSecondScaleFactor)
|
||||
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]*PerfCounter) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// readUTF16StringAtPos 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 windows.UTF16ToString(value), nil
|
||||
}
|
||||
|
||||
// readUTF16String 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 windows.UTF16ToString(out), nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func MapCounterToIndex(name string) string {
|
||||
return strconv.Itoa(int(CounterNameTable.LookupIndex(name)))
|
||||
}
|
||||
|
||||
func GetPerflibSnapshot(objNames string) (map[string]*PerfObject, error) {
|
||||
objects, err := QueryPerformanceData(objNames, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexed := make(map[string]*PerfObject)
|
||||
for _, obj := range objects {
|
||||
indexed[obj.Name] = obj
|
||||
}
|
||||
|
||||
return indexed, nil
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
|
||||
)
|
||||
|
||||
type simple struct {
|
||||
ValA float64 `perflib:"Something"`
|
||||
ValB float64 `perflib:"Something Else"`
|
||||
ValC float64 `perflib:"Something Else,secondvalue"`
|
||||
}
|
||||
|
||||
func TestUnmarshalPerflib(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
obj *PerfObject
|
||||
|
||||
expectedOutput []simple
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "nil check",
|
||||
obj: nil,
|
||||
expectedOutput: []simple{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Simple",
|
||||
obj: &PerfObject{
|
||||
Instances: []*PerfInstance{
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: perftypes.PERF_COUNTER_COUNTER,
|
||||
},
|
||||
Value: 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: []simple{{ValA: 123}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple properties",
|
||||
obj: &PerfObject{
|
||||
Instances: []*PerfInstance{
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: perftypes.PERF_COUNTER_COUNTER,
|
||||
},
|
||||
Value: 123,
|
||||
},
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something Else",
|
||||
CounterType: perftypes.PERF_COUNTER_COUNTER,
|
||||
HasSecondValue: true,
|
||||
},
|
||||
Value: 256,
|
||||
SecondValue: 222,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOutput: []simple{{ValA: 123, ValB: 256, ValC: 222}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple instances",
|
||||
obj: &PerfObject{
|
||||
Instances: []*PerfInstance{
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: perftypes.PERF_COUNTER_COUNTER,
|
||||
},
|
||||
Value: 321,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Counters: []*PerfCounter{
|
||||
{
|
||||
Def: &PerfCounterDef{
|
||||
Name: "Something",
|
||||
CounterType: perftypes.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) {
|
||||
t.Parallel()
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
output := make([]simple, 0)
|
||||
|
||||
err := UnmarshalObject(c.obj, &output, logger)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user