mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-03-02 08:36:36 +00:00
process: Use registry collector for V1 data (#1814)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
459
internal/pdh/collector.go
Normal file
459
internal/pdh/collector.go
Normal file
@@ -0,0 +1,459 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pdh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
InstancesAll = []string{"*"}
|
||||
InstancesTotal = []string{InstanceTotal}
|
||||
)
|
||||
|
||||
type CounterValues = map[string]map[string]CounterValue
|
||||
|
||||
type Collector struct {
|
||||
object string
|
||||
counters map[string]Counter
|
||||
handle pdhQueryHandle
|
||||
totalCounterRequested bool
|
||||
mu sync.RWMutex
|
||||
|
||||
nameIndexValue int
|
||||
metricsTypeIndexValue int
|
||||
|
||||
collectCh chan any
|
||||
errorCh chan error
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
Name string
|
||||
Desc string
|
||||
MetricType prometheus.ValueType
|
||||
Instances map[string]pdhCounterHandle
|
||||
Type uint32
|
||||
Frequency int64
|
||||
|
||||
FieldIndexValue int
|
||||
FieldIndexSecondValue int
|
||||
}
|
||||
|
||||
func NewCollector[T any](object string, instances []string) (*Collector, error) {
|
||||
valueType := reflect.TypeFor[T]()
|
||||
|
||||
return NewCollectorWithReflection(object, instances, valueType)
|
||||
}
|
||||
|
||||
func NewCollectorWithReflection(object string, instances []string, valueType reflect.Type) (*Collector, error) {
|
||||
var handle pdhQueryHandle
|
||||
|
||||
if ret := OpenQuery(0, 0, &handle); ret != ErrorSuccess {
|
||||
return nil, NewPdhError(ret)
|
||||
}
|
||||
|
||||
if len(instances) == 0 {
|
||||
instances = []string{InstanceEmpty}
|
||||
}
|
||||
|
||||
collector := &Collector{
|
||||
object: object,
|
||||
counters: make(map[string]Counter, valueType.NumField()),
|
||||
handle: handle,
|
||||
totalCounterRequested: slices.Contains(instances, InstanceTotal),
|
||||
mu: sync.RWMutex{},
|
||||
nameIndexValue: -1,
|
||||
metricsTypeIndexValue: -1,
|
||||
}
|
||||
|
||||
errs := make([]error, 0, valueType.NumField())
|
||||
|
||||
if f, ok := valueType.FieldByName("Name"); ok {
|
||||
if f.Type.Kind() == reflect.String {
|
||||
collector.nameIndexValue = f.Index[0]
|
||||
}
|
||||
}
|
||||
|
||||
if f, ok := valueType.FieldByName("MetricType"); ok {
|
||||
if f.Type.Kind() == reflect.TypeOf(prometheus.ValueType(0)).Kind() {
|
||||
collector.metricsTypeIndexValue = f.Index[0]
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range reflect.VisibleFields(valueType) {
|
||||
counterName, ok := f.Tag.Lookup("perfdata")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Type.Kind() != reflect.Float64 {
|
||||
errs = append(errs, fmt.Errorf("field %s must be a float64", f.Name))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var counter Counter
|
||||
if counter, ok = collector.counters[counterName]; !ok {
|
||||
counter = Counter{
|
||||
Name: counterName,
|
||||
Instances: make(map[string]pdhCounterHandle, len(instances)),
|
||||
FieldIndexSecondValue: -1,
|
||||
FieldIndexValue: -1,
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasSuffix(counterName, ",secondvalue") {
|
||||
counterName = strings.TrimSuffix(counterName, ",secondvalue")
|
||||
|
||||
counter.FieldIndexSecondValue = f.Index[0]
|
||||
} else {
|
||||
counter.FieldIndexValue = f.Index[0]
|
||||
}
|
||||
|
||||
if len(counter.Instances) != 0 {
|
||||
collector.counters[counterName] = counter
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var counterPath string
|
||||
|
||||
for _, instance := range instances {
|
||||
counterPath = formatCounterPath(object, instance, counterName)
|
||||
|
||||
var counterHandle pdhCounterHandle
|
||||
|
||||
if ret := AddEnglishCounter(handle, counterPath, 0, &counterHandle); ret != ErrorSuccess {
|
||||
errs = append(errs, fmt.Errorf("failed to add counter %s: %w", counterPath, NewPdhError(ret)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
counter.Instances[instance] = counterHandle
|
||||
|
||||
if counter.Type != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the info with the current buffer size
|
||||
bufLen := uint32(0)
|
||||
|
||||
if ret := GetCounterInfo(counterHandle, 0, &bufLen, nil); ret != MoreData {
|
||||
errs = append(errs, fmt.Errorf("GetCounterInfo: %w", NewPdhError(ret)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
buf := make([]byte, bufLen)
|
||||
if ret := GetCounterInfo(counterHandle, 0, &bufLen, &buf[0]); ret != ErrorSuccess {
|
||||
errs = append(errs, fmt.Errorf("GetCounterInfo: %w", NewPdhError(ret)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ci := (*CounterInfo)(unsafe.Pointer(&buf[0]))
|
||||
counter.Type = ci.DwType
|
||||
counter.Desc = windows.UTF16PtrToString(ci.SzExplainText)
|
||||
counter.Desc = windows.UTF16PtrToString(ci.SzExplainText)
|
||||
|
||||
if val, ok := SupportedCounterTypes[counter.Type]; ok {
|
||||
counter.MetricType = val
|
||||
} else {
|
||||
counter.MetricType = prometheus.GaugeValue
|
||||
}
|
||||
|
||||
if counter.Type == PERF_ELAPSED_TIME {
|
||||
if ret := GetCounterTimeBase(counterHandle, &counter.Frequency); ret != ErrorSuccess {
|
||||
errs = append(errs, fmt.Errorf("GetCounterTimeBase: %w", NewPdhError(ret)))
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collector.counters[counterName] = counter
|
||||
}
|
||||
|
||||
if err := errors.Join(errs...); err != nil {
|
||||
return collector, fmt.Errorf("failed to initialize collector: %w", err)
|
||||
}
|
||||
|
||||
if len(collector.counters) == 0 {
|
||||
return nil, errors.New("no counters configured")
|
||||
}
|
||||
|
||||
collector.collectCh = make(chan any)
|
||||
collector.errorCh = make(chan error)
|
||||
|
||||
go collector.collectRoutine()
|
||||
|
||||
// Collect initial data because some counters need to be read twice to get the correct value.
|
||||
collectValues := reflect.New(reflect.SliceOf(valueType)).Elem()
|
||||
if err := collector.Collect(collectValues.Addr().Interface()); err != nil && !errors.Is(err, ErrNoData) {
|
||||
return collector, fmt.Errorf("failed to collect initial data: %w", err)
|
||||
}
|
||||
|
||||
return collector, nil
|
||||
}
|
||||
|
||||
func (c *Collector) Describe() map[string]string {
|
||||
if c == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
desc := make(map[string]string, len(c.counters))
|
||||
|
||||
for _, counter := range c.counters {
|
||||
desc[counter.Name] = counter.Desc
|
||||
}
|
||||
|
||||
return desc
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(dst any) error {
|
||||
if c == nil {
|
||||
return ErrPerformanceCounterNotInitialized
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if len(c.counters) == 0 || c.handle == 0 || c.collectCh == nil || c.errorCh == nil {
|
||||
return ErrPerformanceCounterNotInitialized
|
||||
}
|
||||
|
||||
c.collectCh <- dst
|
||||
|
||||
return <-c.errorCh
|
||||
}
|
||||
|
||||
func (c *Collector) collectRoutine() {
|
||||
var (
|
||||
err error
|
||||
itemCount uint32
|
||||
items []RawCounterItem
|
||||
bytesNeeded uint32
|
||||
)
|
||||
|
||||
buf := make([]byte, 1)
|
||||
|
||||
for data := range c.collectCh {
|
||||
err = (func() error {
|
||||
if ret := CollectQueryData(c.handle); ret != ErrorSuccess {
|
||||
return fmt.Errorf("failed to collect query data: %w", NewPdhError(ret))
|
||||
}
|
||||
|
||||
dv := reflect.ValueOf(data)
|
||||
if dv.Kind() != reflect.Ptr || dv.IsNil() {
|
||||
return fmt.Errorf("expected a pointer, got %s: %w", dv.Kind(), mi.ErrInvalidEntityType)
|
||||
}
|
||||
|
||||
dv = dv.Elem()
|
||||
|
||||
if dv.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("expected a pointer to a slice, got %s: %w", dv.Kind(), mi.ErrInvalidEntityType)
|
||||
}
|
||||
|
||||
elemType := dv.Type().Elem()
|
||||
|
||||
if elemType.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("expected a pointer to a slice of structs, got a slice of %s: %w", elemType.Kind(), mi.ErrInvalidEntityType)
|
||||
}
|
||||
|
||||
if dv.Len() != 0 {
|
||||
dv.Set(reflect.MakeSlice(dv.Type(), 0, 0))
|
||||
}
|
||||
|
||||
dv.Clear()
|
||||
|
||||
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()
|
||||
|
||||
indexMap := map[string]int{}
|
||||
stringMap := map[*uint16]string{}
|
||||
|
||||
for _, counter := range c.counters {
|
||||
for _, instance := range counter.Instances {
|
||||
// Get the info with the current buffer size
|
||||
bytesNeeded = uint32(cap(buf))
|
||||
|
||||
for {
|
||||
ret := GetRawCounterArray(instance, &bytesNeeded, &itemCount, &buf[0])
|
||||
|
||||
if ret == ErrorSuccess {
|
||||
break
|
||||
}
|
||||
|
||||
if err := NewPdhError(ret); ret != MoreData && !isKnownCounterDataError(err) {
|
||||
return fmt.Errorf("GetRawCounterArray: %w", err)
|
||||
}
|
||||
|
||||
if bytesNeeded <= uint32(cap(buf)) {
|
||||
return fmt.Errorf("GetRawCounterArray reports buffer too small (%d), but buffer is large enough (%d): %w", uint32(cap(buf)), bytesNeeded, NewPdhError(ret))
|
||||
}
|
||||
|
||||
buf = make([]byte, bytesNeeded)
|
||||
}
|
||||
|
||||
items = unsafe.Slice((*RawCounterItem)(unsafe.Pointer(&buf[0])), itemCount)
|
||||
|
||||
var (
|
||||
instanceName string
|
||||
ok bool
|
||||
)
|
||||
|
||||
for _, item := range items {
|
||||
if item.RawValue.CStatus != CstatusValidData && item.RawValue.CStatus != CstatusNewData {
|
||||
continue
|
||||
}
|
||||
|
||||
if instanceName, ok = stringMap[item.SzName]; !ok {
|
||||
instanceName = windows.UTF16PtrToString(item.SzName)
|
||||
stringMap[item.SzName] = instanceName
|
||||
}
|
||||
|
||||
if strings.HasSuffix(instanceName, InstanceTotal) && !c.totalCounterRequested {
|
||||
continue
|
||||
}
|
||||
|
||||
if instanceName == "" || instanceName == "*" {
|
||||
instanceName = InstanceEmpty
|
||||
}
|
||||
|
||||
var (
|
||||
index int
|
||||
ok bool
|
||||
)
|
||||
|
||||
if index, ok = indexMap[instanceName]; !ok {
|
||||
index = dv.Len()
|
||||
indexMap[instanceName] = index
|
||||
|
||||
if c.nameIndexValue != -1 {
|
||||
elemValue.Field(c.nameIndexValue).SetString(instanceName)
|
||||
}
|
||||
|
||||
if c.metricsTypeIndexValue != -1 {
|
||||
var metricsType prometheus.ValueType
|
||||
if metricsType, ok = SupportedCounterTypes[counter.Type]; !ok {
|
||||
metricsType = prometheus.GaugeValue
|
||||
}
|
||||
|
||||
elemValue.Field(c.metricsTypeIndexValue).Set(reflect.ValueOf(metricsType))
|
||||
}
|
||||
|
||||
dv.Set(reflect.Append(dv, elemValue))
|
||||
}
|
||||
|
||||
// This is a workaround for the issue with the elapsed time counter type.
|
||||
// Source: https://github.com/prometheus-community/windows_exporter/pull/335/files#diff-d5d2528f559ba2648c2866aec34b1eaa5c094dedb52bd0ff22aa5eb83226bd8dR76-R83
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/perfctrs/calculating-counter-values
|
||||
switch counter.Type {
|
||||
case PERF_ELAPSED_TIME:
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexValue).
|
||||
SetFloat(float64((item.RawValue.FirstValue - WindowsEpoch) / counter.Frequency))
|
||||
case PERF_100NSEC_TIMER, PERF_PRECISION_100NS_TIMER:
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexValue).
|
||||
SetFloat(float64(item.RawValue.FirstValue) * TicksToSecondScaleFactor)
|
||||
default:
|
||||
if counter.FieldIndexSecondValue != -1 {
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexSecondValue).
|
||||
SetFloat(float64(item.RawValue.SecondValue))
|
||||
}
|
||||
|
||||
if counter.FieldIndexValue != -1 {
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexValue).
|
||||
SetFloat(float64(item.RawValue.FirstValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dv.Len() == 0 {
|
||||
return ErrNoData
|
||||
}
|
||||
|
||||
return nil
|
||||
})()
|
||||
|
||||
c.errorCh <- err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collector) Close() {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
CloseQuery(c.handle)
|
||||
|
||||
c.handle = 0
|
||||
|
||||
if c.collectCh != nil {
|
||||
close(c.collectCh)
|
||||
}
|
||||
|
||||
if c.errorCh != nil {
|
||||
close(c.errorCh)
|
||||
}
|
||||
|
||||
c.collectCh = nil
|
||||
c.errorCh = nil
|
||||
}
|
||||
|
||||
func formatCounterPath(object, instance, counterName string) string {
|
||||
var counterPath string
|
||||
|
||||
if instance == InstanceEmpty {
|
||||
counterPath = fmt.Sprintf(`\%s\%s`, object, counterName)
|
||||
} else {
|
||||
counterPath = fmt.Sprintf(`\%s(%s)\%s`, object, instance, counterName)
|
||||
}
|
||||
|
||||
return counterPath
|
||||
}
|
||||
|
||||
func isKnownCounterDataError(err error) bool {
|
||||
var pdhErr *Error
|
||||
|
||||
return errors.As(err, &pdhErr) && (pdhErr.ErrorCode == InvalidData ||
|
||||
pdhErr.ErrorCode == CalcNegativeDenominator ||
|
||||
pdhErr.ErrorCode == CalcNegativeValue ||
|
||||
pdhErr.ErrorCode == CstatusInvalidData ||
|
||||
pdhErr.ErrorCode == CstatusNoInstance ||
|
||||
pdhErr.ErrorCode == NoData)
|
||||
}
|
||||
71
internal/pdh/collector_bench_test.go
Normal file
71
internal/pdh/collector_bench_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pdh_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type processFull struct {
|
||||
Name string
|
||||
|
||||
ProcessorTime float64 `pdh:"% Processor Time"`
|
||||
PrivilegedTime float64 `pdh:"% Privileged Time"`
|
||||
UserTime float64 `pdh:"% User Time"`
|
||||
CreatingProcessID float64 `pdh:"Creating Process ID"`
|
||||
ElapsedTime float64 `pdh:"Elapsed Time"`
|
||||
HandleCount float64 `pdh:"Handle Count"`
|
||||
IDProcess float64 `pdh:"ID Process"`
|
||||
IODataBytesSec float64 `pdh:"IO Data Bytes/sec"`
|
||||
IODataOperationsSec float64 `pdh:"IO Data Operations/sec"`
|
||||
IOOtherBytesSec float64 `pdh:"IO Other Bytes/sec"`
|
||||
IOOtherOperationsSec float64 `pdh:"IO Other Operations/sec"`
|
||||
IOReadBytesSec float64 `pdh:"IO Read Bytes/sec"`
|
||||
IOReadOperationsSec float64 `pdh:"IO Read Operations/sec"`
|
||||
IOWriteBytesSec float64 `pdh:"IO Write Bytes/sec"`
|
||||
IOWriteOperationsSec float64 `pdh:"IO Write Operations/sec"`
|
||||
PageFaultsSec float64 `pdh:"Page Faults/sec"`
|
||||
PageFileBytesPeak float64 `pdh:"Page File Bytes Peak"`
|
||||
PageFileBytes float64 `pdh:"Page File Bytes"`
|
||||
PoolNonpagedBytes float64 `pdh:"Pool Nonpaged Bytes"`
|
||||
PoolPagedBytes float64 `pdh:"Pool Paged Bytes"`
|
||||
PriorityBase float64 `pdh:"Priority Base"`
|
||||
PrivateBytes float64 `pdh:"Private Bytes"`
|
||||
ThreadCount float64 `pdh:"Thread Count"`
|
||||
VirtualBytesPeak float64 `pdh:"Virtual Bytes Peak"`
|
||||
VirtualBytes float64 `pdh:"Virtual Bytes"`
|
||||
WorkingSetPrivate float64 `pdh:"Working Set - Private"`
|
||||
WorkingSetPeak float64 `pdh:"Working Set Peak"`
|
||||
WorkingSet float64 `pdh:"Working Set"`
|
||||
}
|
||||
|
||||
func BenchmarkTestCollector(b *testing.B) {
|
||||
performanceData, err := pdh.NewCollector[processFull]("Process", []string{"*"})
|
||||
require.NoError(b, err)
|
||||
|
||||
var data []processFull
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = performanceData.Collect(&data)
|
||||
}
|
||||
|
||||
performanceData.Close()
|
||||
|
||||
b.ReportAllocs()
|
||||
}
|
||||
71
internal/pdh/collector_test.go
Normal file
71
internal/pdh/collector_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pdh_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type process struct {
|
||||
Name string
|
||||
ThreadCount float64 `perfdata:"Thread Count"`
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
object string
|
||||
instances []string
|
||||
}{
|
||||
{
|
||||
object: "Process",
|
||||
instances: []string{"*"},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.object, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
performanceData, err := pdh.NewCollector[process](tc.object, tc.instances)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
var data []process
|
||||
|
||||
err = performanceData.Collect(&data)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, data)
|
||||
|
||||
err = performanceData.Collect(&data)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, data)
|
||||
|
||||
for _, instance := range data {
|
||||
if instance.Name == "Idle" || instance.Name == "Secure System" {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.NotZerof(t, instance.ThreadCount, "object: %s, instance: %s, counter: %s", tc.object, instance, instance.ThreadCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
92
internal/pdh/const.go
Normal file
92
internal/pdh/const.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pdh
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
// Conversion factors.
|
||||
const (
|
||||
TicksToSecondScaleFactor = 1 / 1e7
|
||||
WindowsEpoch int64 = 116444736000000000
|
||||
)
|
||||
|
||||
// Based on https://github.com/leoluk/perflib_exporter/blob/master/collector/mapper.go
|
||||
//
|
||||
//goland:noinspection GoUnusedConst
|
||||
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
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
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,
|
||||
}
|
||||
53
internal/pdh/error.go
Normal file
53
internal/pdh/error.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pdh
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNoData = NewPdhError(NoData)
|
||||
ErrPerformanceCounterNotInitialized = errors.New("performance counter not initialized")
|
||||
)
|
||||
|
||||
// Error represents error returned from Performance Counters API.
|
||||
type Error struct {
|
||||
ErrorCode uint32
|
||||
errorText string
|
||||
}
|
||||
|
||||
func (m *Error) Is(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var e *Error
|
||||
if errors.As(err, &e) {
|
||||
return m.ErrorCode == e.ErrorCode
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Error) Error() string {
|
||||
return m.errorText
|
||||
}
|
||||
|
||||
func NewPdhError(code uint32) error {
|
||||
return &Error{
|
||||
ErrorCode: code,
|
||||
errorText: FormatError(code),
|
||||
}
|
||||
}
|
||||
633
internal/pdh/pdh.go
Normal file
633
internal/pdh/pdh.go
Normal file
@@ -0,0 +1,633 @@
|
||||
// Copyright (c) 2010-2024 The win Authors. All rights reserved.
|
||||
// Copyright (c) 2024 The prometheus-community Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. The names of the authors may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// This is the official list of 'win' authors for copyright purposes.
|
||||
//
|
||||
// Alexander Neumann <an2048@googlemail.com>
|
||||
// Joseph Watson <jtwatson@linux-consulting.us>
|
||||
// Kevin Pors <krpors@gmail.com>
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pdh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/kernel32"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Error codes.
|
||||
const (
|
||||
ErrorSuccess = 0
|
||||
ErrorFailure = 1
|
||||
ErrorInvalidFunction = 1
|
||||
)
|
||||
|
||||
type (
|
||||
HANDLE uintptr
|
||||
)
|
||||
|
||||
// PDH error codes, which can be returned by all Pdh* functions. Taken from mingw-w64 pdhmsg.h
|
||||
|
||||
const (
|
||||
CstatusValidData uint32 = 0x00000000 // The returned data is valid.
|
||||
CstatusNewData uint32 = 0x00000001 // The return data value is valid and different from the last sample.
|
||||
CstatusNoMachine uint32 = 0x800007D0 // Unable to connect to the specified computer, or the computer is offline.
|
||||
CstatusNoInstance uint32 = 0x800007D1
|
||||
MoreData uint32 = 0x800007D2 // The PdhGetFormattedCounterArray* function can return this if there's 'more data to be displayed'.
|
||||
CstatusItemNotValidated uint32 = 0x800007D3
|
||||
Retry uint32 = 0x800007D4
|
||||
NoData uint32 = 0x800007D5 // The query does not currently contain any counters (for example, limited access)
|
||||
CalcNegativeDenominator uint32 = 0x800007D6
|
||||
CalcNegativeTimebase uint32 = 0x800007D7
|
||||
CalcNegativeValue uint32 = 0x800007D8
|
||||
DialogCancelled uint32 = 0x800007D9
|
||||
EndOfLogFile uint32 = 0x800007DA
|
||||
AsyncQueryTimeout uint32 = 0x800007DB
|
||||
CannotSetDefaultRealtimeDatasource uint32 = 0x800007DC
|
||||
CstatusNoObject uint32 = 0xC0000BB8
|
||||
CstatusNoCounter uint32 = 0xC0000BB9 // The specified counter could not be found.
|
||||
CstatusInvalidData uint32 = 0xC0000BBA // The counter was successfully found, but the data returned is not valid.
|
||||
MemoryAllocationFailure uint32 = 0xC0000BBB
|
||||
InvalidHandle uint32 = 0xC0000BBC
|
||||
InvalidArgument uint32 = 0xC0000BBD // Required argument is missing or incorrect.
|
||||
FunctionNotFound uint32 = 0xC0000BBE
|
||||
CstatusNoCountername uint32 = 0xC0000BBF
|
||||
CstatusBadCountername uint32 = 0xC0000BC0 // Unable to parse the counter path. Check the format and syntax of the specified path.
|
||||
InvalidBuffer uint32 = 0xC0000BC1
|
||||
InsufficientBuffer uint32 = 0xC0000BC2
|
||||
CannotConnectMachine uint32 = 0xC0000BC3
|
||||
InvalidPath uint32 = 0xC0000BC4
|
||||
InvalidInstance uint32 = 0xC0000BC5
|
||||
InvalidData uint32 = 0xC0000BC6 // specified counter does not contain valid data or a successful status code.
|
||||
NoDialogData uint32 = 0xC0000BC7
|
||||
CannotReadNameStrings uint32 = 0xC0000BC8
|
||||
LogFileCreateError uint32 = 0xC0000BC9
|
||||
LogFileOpenError uint32 = 0xC0000BCA
|
||||
LogTypeNotFound uint32 = 0xC0000BCB
|
||||
NoMoreData uint32 = 0xC0000BCC
|
||||
EntryNotInLogFile uint32 = 0xC0000BCD
|
||||
DataSourceIsLogFile uint32 = 0xC0000BCE
|
||||
DataSourceIsRealTime uint32 = 0xC0000BCF
|
||||
UnableReadLogHeader uint32 = 0xC0000BD0
|
||||
FileNotFound uint32 = 0xC0000BD1
|
||||
FileAlreadyExists uint32 = 0xC0000BD2
|
||||
NotImplemented uint32 = 0xC0000BD3
|
||||
StringNotFound uint32 = 0xC0000BD4
|
||||
UnableMapNameFiles uint32 = 0x80000BD5
|
||||
UnknownLogFormat uint32 = 0xC0000BD6
|
||||
UnknownLogsvcCommand uint32 = 0xC0000BD7
|
||||
LogsvcQueryNotFound uint32 = 0xC0000BD8
|
||||
LogsvcNotOpened uint32 = 0xC0000BD9
|
||||
WbemError uint32 = 0xC0000BDA
|
||||
AccessDenied uint32 = 0xC0000BDB
|
||||
LogFileTooSmall uint32 = 0xC0000BDC
|
||||
InvalidDatasource uint32 = 0xC0000BDD
|
||||
InvalidSqldb uint32 = 0xC0000BDE
|
||||
NoCounters uint32 = 0xC0000BDF
|
||||
SQLAllocFailed uint32 = 0xC0000BE0
|
||||
SQLAllocconFailed uint32 = 0xC0000BE1
|
||||
SQLExecDirectFailed uint32 = 0xC0000BE2
|
||||
SQLFetchFailed uint32 = 0xC0000BE3
|
||||
SQLRowcountFailed uint32 = 0xC0000BE4
|
||||
SQLMoreResultsFailed uint32 = 0xC0000BE5
|
||||
SQLConnectFailed uint32 = 0xC0000BE6
|
||||
SQLBindFailed uint32 = 0xC0000BE7
|
||||
CannotConnectWmiServer uint32 = 0xC0000BE8
|
||||
PlaCollectionAlreadyRunning uint32 = 0xC0000BE9
|
||||
PlaErrorScheduleOverlap uint32 = 0xC0000BEA
|
||||
PlaCollectionNotFound uint32 = 0xC0000BEB
|
||||
PlaErrorScheduleElapsed uint32 = 0xC0000BEC
|
||||
PlaErrorNostart uint32 = 0xC0000BED
|
||||
PlaErrorAlreadyExists uint32 = 0xC0000BEE
|
||||
PlaErrorTypeMismatch uint32 = 0xC0000BEF
|
||||
PlaErrorFilepath uint32 = 0xC0000BF0
|
||||
PlaServiceError uint32 = 0xC0000BF1
|
||||
PlaValidationError uint32 = 0xC0000BF2
|
||||
PlaValidationWarning uint32 = 0x80000BF3
|
||||
PlaErrorNameTooLong uint32 = 0xC0000BF4
|
||||
InvalidSQLLogFormat uint32 = 0xC0000BF5
|
||||
CounterAlreadyInQuery uint32 = 0xC0000BF6
|
||||
BinaryLogCorrupt uint32 = 0xC0000BF7
|
||||
LogSampleTooSmall uint32 = 0xC0000BF8
|
||||
OsLaterVersion uint32 = 0xC0000BF9
|
||||
OsEarlierVersion uint32 = 0xC0000BFA
|
||||
IncorrectAppendTime uint32 = 0xC0000BFB
|
||||
UnmatchedAppendCounter uint32 = 0xC0000BFC
|
||||
SQLAlterDetailFailed uint32 = 0xC0000BFD
|
||||
QueryPerfDataTimeout uint32 = 0xC0000BFE
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var Errors = map[uint32]string{
|
||||
CstatusValidData: "PDH_CSTATUS_VALID_DATA",
|
||||
CstatusNewData: "PDH_CSTATUS_NEW_DATA",
|
||||
CstatusNoMachine: "PDH_CSTATUS_NO_MACHINE",
|
||||
CstatusNoInstance: "PDH_CSTATUS_NO_INSTANCE",
|
||||
MoreData: "PDH_MORE_DATA",
|
||||
CstatusItemNotValidated: "PDH_CSTATUS_ITEM_NOT_VALIDATED",
|
||||
Retry: "PDH_RETRY",
|
||||
NoData: "PDH_NO_DATA",
|
||||
CalcNegativeDenominator: "PDH_CALC_NEGATIVE_DENOMINATOR",
|
||||
CalcNegativeTimebase: "PDH_CALC_NEGATIVE_TIMEBASE",
|
||||
CalcNegativeValue: "PDH_CALC_NEGATIVE_VALUE",
|
||||
DialogCancelled: "PDH_DIALOG_CANCELLED",
|
||||
EndOfLogFile: "PDH_END_OF_LOG_FILE",
|
||||
AsyncQueryTimeout: "PDH_ASYNC_QUERY_TIMEOUT",
|
||||
CannotSetDefaultRealtimeDatasource: "PDH_CANNOT_SET_DEFAULT_REALTIME_DATASOURCE",
|
||||
CstatusNoObject: "PDH_CSTATUS_NO_OBJECT",
|
||||
CstatusNoCounter: "PDH_CSTATUS_NO_COUNTER",
|
||||
CstatusInvalidData: "PDH_CSTATUS_INVALID_DATA",
|
||||
MemoryAllocationFailure: "PDH_MEMORY_ALLOCATION_FAILURE",
|
||||
InvalidHandle: "PDH_INVALID_HANDLE",
|
||||
InvalidArgument: "PDH_INVALID_ARGUMENT",
|
||||
FunctionNotFound: "PDH_FUNCTION_NOT_FOUND",
|
||||
CstatusNoCountername: "PDH_CSTATUS_NO_COUNTERNAME",
|
||||
CstatusBadCountername: "PDH_CSTATUS_BAD_COUNTERNAME",
|
||||
InvalidBuffer: "PDH_INVALID_BUFFER",
|
||||
InsufficientBuffer: "PDH_INSUFFICIENT_BUFFER",
|
||||
CannotConnectMachine: "PDH_CANNOT_CONNECT_MACHINE",
|
||||
InvalidPath: "PDH_INVALID_PATH",
|
||||
InvalidInstance: "PDH_INVALID_INSTANCE",
|
||||
InvalidData: "PDH_INVALID_DATA",
|
||||
NoDialogData: "PDH_NO_DIALOG_DATA",
|
||||
CannotReadNameStrings: "PDH_CANNOT_READ_NAME_STRINGS",
|
||||
LogFileCreateError: "PDH_LOG_FILE_CREATE_ERROR",
|
||||
LogFileOpenError: "PDH_LOG_FILE_OPEN_ERROR",
|
||||
LogTypeNotFound: "PDH_LOG_TYPE_NOT_FOUND",
|
||||
NoMoreData: "PDH_NO_MORE_DATA",
|
||||
EntryNotInLogFile: "PDH_ENTRY_NOT_IN_LOG_FILE",
|
||||
DataSourceIsLogFile: "PDH_DATA_SOURCE_IS_LOG_FILE",
|
||||
DataSourceIsRealTime: "PDH_DATA_SOURCE_IS_REAL_TIME",
|
||||
UnableReadLogHeader: "PDH_UNABLE_READ_LOG_HEADER",
|
||||
FileNotFound: "PDH_FILE_NOT_FOUND",
|
||||
FileAlreadyExists: "PDH_FILE_ALREADY_EXISTS",
|
||||
NotImplemented: "PDH_NOT_IMPLEMENTED",
|
||||
StringNotFound: "PDH_STRING_NOT_FOUND",
|
||||
UnableMapNameFiles: "PDH_UNABLE_MAP_NAME_FILES",
|
||||
UnknownLogFormat: "PDH_UNKNOWN_LOG_FORMAT",
|
||||
UnknownLogsvcCommand: "PDH_UNKNOWN_LOGSVC_COMMAND",
|
||||
LogsvcQueryNotFound: "PDH_LOGSVC_QUERY_NOT_FOUND",
|
||||
LogsvcNotOpened: "PDH_LOGSVC_NOT_OPENED",
|
||||
WbemError: "PDH_WBEM_ERROR",
|
||||
AccessDenied: "PDH_ACCESS_DENIED",
|
||||
LogFileTooSmall: "PDH_LOG_FILE_TOO_SMALL",
|
||||
InvalidDatasource: "PDH_INVALID_DATASOURCE",
|
||||
InvalidSqldb: "PDH_INVALID_SQLDB",
|
||||
NoCounters: "PDH_NO_COUNTERS",
|
||||
SQLAllocFailed: "PDH_SQL_ALLOC_FAILED",
|
||||
SQLAllocconFailed: "PDH_SQL_ALLOCCON_FAILED",
|
||||
SQLExecDirectFailed: "PDH_SQL_EXEC_DIRECT_FAILED",
|
||||
SQLFetchFailed: "PDH_SQL_FETCH_FAILED",
|
||||
SQLRowcountFailed: "PDH_SQL_ROWCOUNT_FAILED",
|
||||
SQLMoreResultsFailed: "PDH_SQL_MORE_RESULTS_FAILED",
|
||||
SQLConnectFailed: "PDH_SQL_CONNECT_FAILED",
|
||||
SQLBindFailed: "PDH_SQL_BIND_FAILED",
|
||||
CannotConnectWmiServer: "PDH_CANNOT_CONNECT_WMI_SERVER",
|
||||
PlaCollectionAlreadyRunning: "PDH_PLA_COLLECTION_ALREADY_RUNNING",
|
||||
PlaErrorScheduleOverlap: "PDH_PLA_ERROR_SCHEDULE_OVERLAP",
|
||||
PlaCollectionNotFound: "PDH_PLA_COLLECTION_NOT_FOUND",
|
||||
PlaErrorScheduleElapsed: "PDH_PLA_ERROR_SCHEDULE_ELAPSED",
|
||||
PlaErrorNostart: "PDH_PLA_ERROR_NOSTART",
|
||||
PlaErrorAlreadyExists: "PDH_PLA_ERROR_ALREADY_EXISTS",
|
||||
PlaErrorTypeMismatch: "PDH_PLA_ERROR_TYPE_MISMATCH",
|
||||
PlaErrorFilepath: "PDH_PLA_ERROR_FILEPATH",
|
||||
PlaServiceError: "PDH_PLA_SERVICE_ERROR",
|
||||
PlaValidationError: "PDH_PLA_VALIDATION_ERROR",
|
||||
PlaValidationWarning: "PDH_PLA_VALIDATION_WARNING",
|
||||
PlaErrorNameTooLong: "PDH_PLA_ERROR_NAME_TOO_LONG",
|
||||
InvalidSQLLogFormat: "PDH_INVALID_SQL_LOG_FORMAT",
|
||||
CounterAlreadyInQuery: "PDH_COUNTER_ALREADY_IN_QUERY",
|
||||
BinaryLogCorrupt: "PDH_BINARY_LOG_CORRUPT",
|
||||
LogSampleTooSmall: "PDH_LOG_SAMPLE_TOO_SMALL",
|
||||
OsLaterVersion: "PDH_OS_LATER_VERSION",
|
||||
OsEarlierVersion: "PDH_OS_EARLIER_VERSION",
|
||||
IncorrectAppendTime: "PDH_INCORRECT_APPEND_TIME",
|
||||
UnmatchedAppendCounter: "PDH_UNMATCHED_APPEND_COUNTER",
|
||||
SQLAlterDetailFailed: "PDH_SQL_ALTER_DETAIL_FAILED",
|
||||
QueryPerfDataTimeout: "PDH_QUERY_PERF_DATA_TIMEOUT",
|
||||
}
|
||||
|
||||
// Formatting options for GetFormattedCounterValue().
|
||||
//
|
||||
//goland:noinspection GoUnusedConst
|
||||
const (
|
||||
FmtRaw = 0x00000010
|
||||
FmtAnsi = 0x00000020
|
||||
FmtUnicode = 0x00000040
|
||||
FmtLong = 0x00000100 // Return data as a long int.
|
||||
FmtDouble = 0x00000200 // Return data as a double precision floating point real.
|
||||
FmtLarge = 0x00000400 // Return data as a 64 bit integer.
|
||||
FmtNoscale = 0x00001000 // can be OR-ed: Do not apply the counter's default scaling factor.
|
||||
Fmt1000 = 0x00002000 // can be OR-ed: multiply the actual value by 1,000.
|
||||
FmtNodata = 0x00004000 // can be OR-ed: unknown what this is for, MSDN says nothing.
|
||||
FmtNocap100 = 0x00008000 // can be OR-ed: do not cap values > 100.
|
||||
PerfDetailCostly = 0x00010000
|
||||
PerfDetailStandard = 0x0000FFFF
|
||||
)
|
||||
|
||||
type (
|
||||
pdhQueryHandle HANDLE // query handle
|
||||
pdhCounterHandle HANDLE // counter handle
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
libPdhDll = windows.NewLazySystemDLL("pdh.dll")
|
||||
|
||||
pdhAddCounterW = libPdhDll.NewProc("PdhAddCounterW")
|
||||
pdhAddEnglishCounterW = libPdhDll.NewProc("PdhAddEnglishCounterW")
|
||||
pdhCloseQuery = libPdhDll.NewProc("PdhCloseQuery")
|
||||
pdhCollectQueryData = libPdhDll.NewProc("PdhCollectQueryData")
|
||||
pdhCollectQueryDataWithTime = libPdhDll.NewProc("PdhCollectQueryDataWithTime")
|
||||
pdhGetFormattedCounterValue = libPdhDll.NewProc("PdhGetFormattedCounterValue")
|
||||
pdhGetFormattedCounterArrayW = libPdhDll.NewProc("PdhGetFormattedCounterArrayW")
|
||||
pdhOpenQuery = libPdhDll.NewProc("PdhOpenQuery")
|
||||
pdhValidatePathW = libPdhDll.NewProc("PdhValidatePathW")
|
||||
pdhExpandWildCardPathW = libPdhDll.NewProc("PdhExpandWildCardPathW")
|
||||
pdhGetCounterInfoW = libPdhDll.NewProc("PdhGetCounterInfoW")
|
||||
pdhGetRawCounterValue = libPdhDll.NewProc("PdhGetRawCounterValue")
|
||||
pdhGetRawCounterArrayW = libPdhDll.NewProc("PdhGetRawCounterArrayW")
|
||||
pdhPdhGetCounterTimeBase = libPdhDll.NewProc("PdhGetCounterTimeBase")
|
||||
)
|
||||
|
||||
// AddCounter adds the specified counter to the query. This is the internationalized version. Preferably, use the
|
||||
// function AddEnglishCounter instead. hQuery is the query handle, which has been fetched by OpenQuery.
|
||||
// szFullCounterPath is a full, internationalized counter path (this will differ per Windows language version).
|
||||
// dwUserData is a 'user-defined value', which becomes part of the counter information. To retrieve this value
|
||||
// later, call GetCounterInfo() and access dwQueryUserData of the CounterInfo structure.
|
||||
//
|
||||
// Examples of szFullCounterPath (in an English version of Windows):
|
||||
//
|
||||
// \\Processor(_Total)\\% Idle Time
|
||||
// \\Processor(_Total)\\% Processor Time
|
||||
// \\LogicalDisk(C:)\% Free Space
|
||||
//
|
||||
// To view all (internationalized...) counters on a system, there are three non-programmatic ways: perfmon utility,
|
||||
// the typeperf command, and the v1 editor. perfmon.exe is perhaps the easiest way, because it's basically a
|
||||
// full implementation of the pdh.dll API, except with a GUI and all that. The v1 setting also provides an
|
||||
// interface to the available counters, and can be found at the following key:
|
||||
//
|
||||
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage
|
||||
//
|
||||
// This v1 key contains several values as follows:
|
||||
//
|
||||
// 1
|
||||
// 1847
|
||||
// 2
|
||||
// System
|
||||
// 4
|
||||
// Memory
|
||||
// 6
|
||||
// % Processor Time
|
||||
// ... many, many more
|
||||
//
|
||||
// Somehow, these numeric values can be used as szFullCounterPath too:
|
||||
//
|
||||
// \2\6 will correspond to \\System\% Processor Time
|
||||
//
|
||||
// The typeperf command may also be pretty easy. To find all performance counters, simply execute:
|
||||
//
|
||||
// typeperf -qx
|
||||
func AddCounter(hQuery pdhQueryHandle, szFullCounterPath string, dwUserData uintptr, phCounter *pdhCounterHandle) uint32 {
|
||||
ptxt, _ := windows.UTF16PtrFromString(szFullCounterPath)
|
||||
ret, _, _ := pdhAddCounterW.Call(
|
||||
uintptr(hQuery),
|
||||
uintptr(unsafe.Pointer(ptxt)),
|
||||
dwUserData,
|
||||
uintptr(unsafe.Pointer(phCounter)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// AddEnglishCounter adds the specified language-neutral counter to the query. See the AddCounter function. This function only exists on
|
||||
// Windows versions higher than Vista.
|
||||
func AddEnglishCounter(hQuery pdhQueryHandle, szFullCounterPath string, dwUserData uintptr, phCounter *pdhCounterHandle) uint32 {
|
||||
if pdhAddEnglishCounterW == nil {
|
||||
return ErrorInvalidFunction
|
||||
}
|
||||
|
||||
ptxt, _ := windows.UTF16PtrFromString(szFullCounterPath)
|
||||
ret, _, _ := pdhAddEnglishCounterW.Call(
|
||||
uintptr(hQuery),
|
||||
uintptr(unsafe.Pointer(ptxt)),
|
||||
dwUserData,
|
||||
uintptr(unsafe.Pointer(phCounter)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// CloseQuery closes all counters contained in the specified query, closes all handles related to the query,
|
||||
// and frees all memory associated with the query.
|
||||
func CloseQuery(hQuery pdhQueryHandle) uint32 {
|
||||
ret, _, _ := pdhCloseQuery.Call(uintptr(hQuery))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// CollectQueryData collects the current raw data value for all counters in the specified query and updates the status
|
||||
// code of each counter. With some counters, this function needs to be repeatedly called before the value
|
||||
// of the counter can be extracted with PdhGetFormattedCounterValue(). For example, the following code
|
||||
// requires at least two calls:
|
||||
//
|
||||
// var handle win.PDH_HQUERY
|
||||
// var counterHandle win.PDH_HCOUNTER
|
||||
// ret := win.OpenQuery(0, 0, &handle)
|
||||
// ret = win.AddEnglishCounter(handle, "\\Processor(_Total)\\% Idle Time", 0, &counterHandle)
|
||||
// var derp win.PDH_FMT_COUNTERVALUE_DOUBLE
|
||||
//
|
||||
// ret = win.CollectQueryData(handle)
|
||||
// fmt.Printf("Collect return code is %x\n", ret) // return code will be PDH_CSTATUS_INVALID_DATA
|
||||
// ret = win.GetFormattedCounterValueDouble(counterHandle, 0, &derp)
|
||||
//
|
||||
// ret = win.CollectQueryData(handle)
|
||||
// fmt.Printf("Collect return code is %x\n", ret) // return code will be ERROR_SUCCESS
|
||||
// ret = win.GetFormattedCounterValueDouble(counterHandle, 0, &derp)
|
||||
//
|
||||
// The CollectQueryData will return an error in the first call because it needs two values for
|
||||
// displaying the correct data for the processor idle time. The second call will have a 0 return code.
|
||||
func CollectQueryData(hQuery pdhQueryHandle) uint32 {
|
||||
ret, _, _ := pdhCollectQueryData.Call(uintptr(hQuery))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// CollectQueryDataWithTime queries data from perfmon, retrieving the device/windows timestamp from the node it was collected on.
|
||||
// Converts the filetime structure to a GO time class and returns the native time.
|
||||
func CollectQueryDataWithTime(hQuery pdhQueryHandle) (uint32, time.Time) {
|
||||
var localFileTime windows.Filetime
|
||||
|
||||
ret, _, _ := pdhCollectQueryDataWithTime.Call(uintptr(hQuery), uintptr(unsafe.Pointer(&localFileTime)))
|
||||
|
||||
if ret == ErrorSuccess {
|
||||
var utcFileTime windows.Filetime
|
||||
|
||||
if ret := kernel32.LocalFileTimeToFileTime(&localFileTime, &utcFileTime); ret == 0 {
|
||||
return uint32(ErrorFailure), time.Now()
|
||||
}
|
||||
|
||||
retTime := time.Unix(0, utcFileTime.Nanoseconds())
|
||||
|
||||
return uint32(ErrorSuccess), retTime
|
||||
}
|
||||
|
||||
return uint32(ret), time.Now()
|
||||
}
|
||||
|
||||
// GetFormattedCounterValueDouble formats the given hCounter using a 'double'. The result is set into the specialized union struct pValue.
|
||||
// This function does not directly translate to a Windows counterpart due to union specialization tricks.
|
||||
func GetFormattedCounterValueDouble(hCounter pdhCounterHandle, lpdwType *uint32, pValue *FmtCounterValueDouble) uint32 {
|
||||
ret, _, _ := pdhGetFormattedCounterValue.Call(
|
||||
uintptr(hCounter),
|
||||
uintptr(FmtDouble|FmtNocap100),
|
||||
uintptr(unsafe.Pointer(lpdwType)),
|
||||
uintptr(unsafe.Pointer(pValue)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// GetFormattedCounterArrayDouble returns an array of formatted counter values. Use this function when you want to format the counter values of a
|
||||
// counter that contains a wildcard character for the instance name. The itemBuffer must a slice of type FmtCounterValueItemDouble.
|
||||
// An example of how this function can be used:
|
||||
//
|
||||
// okPath := "\\Process(*)\\% Processor Time" // notice the wildcard * character
|
||||
//
|
||||
// // omitted all necessary stuff ...
|
||||
//
|
||||
// var bufSize uint32
|
||||
// var bufCount uint32
|
||||
// var size uint32 = uint32(unsafe.Sizeof(win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{}))
|
||||
// var emptyBuf [1]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr.
|
||||
//
|
||||
// for {
|
||||
// // collect
|
||||
// ret := win.CollectQueryData(queryHandle)
|
||||
// if ret == win.ERROR_SUCCESS {
|
||||
// ret = win.GetFormattedCounterArrayDouble(counterHandle, &bufSize, &bufCount, &emptyBuf[0]) // uses null ptr here according to MSDN.
|
||||
// if ret == win.PDH_MORE_DATA {
|
||||
// filledBuf := make([]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size)
|
||||
// ret = win.GetFormattedCounterArrayDouble(counterHandle, &bufSize, &bufCount, &filledBuf[0])
|
||||
// for i := 0; i < int(bufCount); i++ {
|
||||
// c := filledBuf[i]
|
||||
// var s string = win.UTF16PtrToString(c.SzName)
|
||||
// fmt.Printf("Index %d -> %s, value %v\n", i, s, c.FmtValue.DoubleValue)
|
||||
// }
|
||||
//
|
||||
// filledBuf = nil
|
||||
// // Need to at least set bufSize to zero, because if not, the function will not
|
||||
// // return PDH_MORE_DATA and will not set the bufSize.
|
||||
// bufCount = 0
|
||||
// bufSize = 0
|
||||
// }
|
||||
//
|
||||
// time.Sleep(2000 * time.Millisecond)
|
||||
// }
|
||||
// }
|
||||
func GetFormattedCounterArrayDouble(hCounter pdhCounterHandle, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *byte) uint32 {
|
||||
ret, _, _ := pdhGetFormattedCounterArrayW.Call(
|
||||
uintptr(hCounter),
|
||||
uintptr(FmtDouble|FmtNocap100),
|
||||
uintptr(unsafe.Pointer(lpdwBufferSize)),
|
||||
uintptr(unsafe.Pointer(lpdwBufferCount)),
|
||||
uintptr(unsafe.Pointer(itemBuffer)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// OpenQuery creates a new query that is used to manage the collection of performance data.
|
||||
// szDataSource is a null terminated string that specifies the name of the log file from which to
|
||||
// retrieve the performance data. If 0, performance data is collected from a real-time data source.
|
||||
// dwUserData is a user-defined value to associate with this query. To retrieve the user data later,
|
||||
// call GetCounterInfo and access dwQueryUserData of the CounterInfo structure. phQuery is
|
||||
// the handle to the query, and must be used in subsequent calls. This function returns a PDH_
|
||||
// constant error code, or ErrorSuccess if the call succeeded.
|
||||
func OpenQuery(szDataSource uintptr, dwUserData uintptr, phQuery *pdhQueryHandle) uint32 {
|
||||
ret, _, _ := pdhOpenQuery.Call(
|
||||
szDataSource,
|
||||
dwUserData,
|
||||
uintptr(unsafe.Pointer(phQuery)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// ExpandWildCardPath examines the specified computer or log file and returns those counter paths that match the given counter path
|
||||
// which contains wildcard characters. The general counter path format is as follows:
|
||||
//
|
||||
// \\computer\object(parent/instance#index)\counter
|
||||
//
|
||||
// The parent, instance, index, and counter components of the counter path may contain either a valid name or a wildcard character.
|
||||
// The computer, parent, instance, and index components are not necessary for all counters.
|
||||
//
|
||||
// The following is a list of the possible formats:
|
||||
//
|
||||
// \\computer\object(parent/instance#index)\counter
|
||||
// \\computer\object(parent/instance)\counter
|
||||
// \\computer\object(instance#index)\counter
|
||||
// \\computer\object(instance)\counter
|
||||
// \\computer\object\counter
|
||||
// \object(parent/instance#index)\counter
|
||||
// \object(parent/instance)\counter
|
||||
// \object(instance#index)\counter
|
||||
// \object(instance)\counter
|
||||
// \object\counter
|
||||
// Use an asterisk (*) as the wildcard character, for example, \object(*)\counter.
|
||||
//
|
||||
// If a wildcard character is specified in the parent name, all instances of the specified object
|
||||
// that match the specified instance and counter fields will be returned.
|
||||
// For example, \object(*/instance)\counter.
|
||||
//
|
||||
// If a wildcard character is specified in the instance name, all instances of the specified object and parent object will be returned if all instance names
|
||||
// corresponding to the specified index match the wildcard character. For example, \object(parent/*)\counter.
|
||||
// If the object does not contain an instance, an error occurs.
|
||||
//
|
||||
// If a wildcard character is specified in the counter name, all counters of the specified object are returned.
|
||||
//
|
||||
// Partial counter path string matches (for example, "pro*") are supported.
|
||||
func ExpandWildCardPath(szWildCardPath string, mszExpandedPathList *uint16, pcchPathListLength *uint32) uint32 {
|
||||
ptxt, _ := windows.UTF16PtrFromString(szWildCardPath)
|
||||
flags := uint32(0) // expand instances and counters
|
||||
ret, _, _ := pdhExpandWildCardPathW.Call(
|
||||
0, // search counters on local computer
|
||||
uintptr(unsafe.Pointer(ptxt)),
|
||||
uintptr(unsafe.Pointer(mszExpandedPathList)),
|
||||
uintptr(unsafe.Pointer(pcchPathListLength)),
|
||||
uintptr(unsafe.Pointer(&flags)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// ValidatePath validates a path. Will return ErrorSuccess when ok, or PdhCstatusBadCountername when the path is erroneous.
|
||||
func ValidatePath(path string) uint32 {
|
||||
ptxt, _ := windows.UTF16PtrFromString(path)
|
||||
ret, _, _ := pdhValidatePathW.Call(uintptr(unsafe.Pointer(ptxt)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
func FormatError(msgID uint32) string {
|
||||
var flags uint32 = windows.FORMAT_MESSAGE_FROM_HMODULE | windows.FORMAT_MESSAGE_ARGUMENT_ARRAY | windows.FORMAT_MESSAGE_IGNORE_INSERTS
|
||||
|
||||
buf := make([]uint16, 300)
|
||||
_, err := windows.FormatMessage(flags, libPdhDll.Handle(), msgID, 0, buf, nil)
|
||||
|
||||
if err == nil {
|
||||
return windows.UTF16PtrToString(&buf[0])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("(pdhErr=%d) %s", msgID, err.Error())
|
||||
}
|
||||
|
||||
// GetCounterInfo retrieves information about a counter, such as data size, counter type, path, and user-supplied data values
|
||||
// hCounter [in]
|
||||
// Handle of the counter from which you want to retrieve information. The AddCounter function returns this handle.
|
||||
//
|
||||
// bRetrieveExplainText [in]
|
||||
// Determines whether explain text is retrieved. If you set this parameter to TRUE, the explain text for the counter is retrieved.
|
||||
// If you set this parameter to FALSE, the field in the returned buffer is NULL.
|
||||
//
|
||||
// pdwBufferSize [in, out]
|
||||
// Size of the lpBuffer buffer, in bytes. If zero on input, the function returns PdhMoreData and sets this parameter to the required buffer size.
|
||||
// If the buffer is larger than the required size, the function sets this parameter to the actual size of the buffer that was used.
|
||||
// If the specified size on input is greater than zero but less than the required size, you should not rely on the returned size to reallocate the buffer.
|
||||
//
|
||||
// lpBuffer [out]
|
||||
// Caller-allocated buffer that receives a CounterInfo structure.
|
||||
// The structure is variable-length, because the string data is appended to the end of the fixed-format portion of the structure.
|
||||
// This is done so that all data is returned in a single buffer allocated by the caller. Set to NULL if pdwBufferSize is zero.
|
||||
func GetCounterInfo(hCounter pdhCounterHandle, bRetrieveExplainText int, pdwBufferSize *uint32, lpBuffer *byte) uint32 {
|
||||
ret, _, _ := pdhGetCounterInfoW.Call(
|
||||
uintptr(hCounter),
|
||||
uintptr(bRetrieveExplainText),
|
||||
uintptr(unsafe.Pointer(pdwBufferSize)),
|
||||
uintptr(unsafe.Pointer(lpBuffer)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// GetRawCounterValue returns the current raw value of the counter.
|
||||
// If the specified counter instance does not exist, this function will return ErrorSuccess
|
||||
// and the CStatus member of the RawCounter structure will contain PdhCstatusNoInstance.
|
||||
//
|
||||
// hCounter [in]
|
||||
// Handle of the counter from which to retrieve the current raw value. The AddCounter function returns this handle.
|
||||
//
|
||||
// lpdwType [out]
|
||||
// Receives the counter type. For a list of counter types, see the Counter Types section of the Windows Server 2003 Deployment Kit.
|
||||
// This parameter is optional.
|
||||
//
|
||||
// pValue [out]
|
||||
// A RawCounter structure that receives the counter value.
|
||||
func GetRawCounterValue(hCounter pdhCounterHandle, lpdwType *uint32, pValue *RawCounter) uint32 {
|
||||
ret, _, _ := pdhGetRawCounterValue.Call(
|
||||
uintptr(hCounter),
|
||||
uintptr(unsafe.Pointer(lpdwType)),
|
||||
uintptr(unsafe.Pointer(pValue)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// GetRawCounterArray returns an array of raw values from the specified counter. Use this function when you want to retrieve the raw counter values
|
||||
// of a counter that contains a wildcard character for the instance name.
|
||||
// hCounter
|
||||
// Handle of the counter for whose current raw instance values you want to retrieve. The AddCounter function returns this handle.
|
||||
//
|
||||
// lpdwBufferSize
|
||||
// Size of the ItemBuffer buffer, in bytes. If zero on input, the function returns PdhMoreData and sets this parameter to the required buffer size.
|
||||
// If the buffer is larger than the required size, the function sets this parameter to the actual size of the buffer that was used.
|
||||
// If the specified size on input is greater than zero but less than the required size, you should not rely on the returned size to reallocate the buffer.
|
||||
//
|
||||
// lpdwItemCount
|
||||
// Number of raw counter values in the ItemBuffer buffer.
|
||||
//
|
||||
// ItemBuffer
|
||||
// Caller-allocated buffer that receives the array of RawCounterItem structures; the structures contain the raw instance counter values.
|
||||
// Set to NULL if lpdwBufferSize is zero.
|
||||
func GetRawCounterArray(hCounter pdhCounterHandle, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *byte) uint32 {
|
||||
ret, _, _ := pdhGetRawCounterArrayW.Call(
|
||||
uintptr(hCounter),
|
||||
uintptr(unsafe.Pointer(lpdwBufferSize)),
|
||||
uintptr(unsafe.Pointer(lpdwBufferCount)),
|
||||
uintptr(unsafe.Pointer(itemBuffer)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
// GetCounterTimeBase returns the time base of the specified counter.
|
||||
// hCounter
|
||||
// Handle of the counter for whose current raw instance values you want to retrieve. The AddCounter function returns this handle.
|
||||
//
|
||||
// lpdwItemCount
|
||||
// Time base that specifies the number of performance values a counter samples per second.
|
||||
func GetCounterTimeBase(hCounter pdhCounterHandle, pTimeBase *int64) uint32 {
|
||||
ret, _, _ := pdhPdhGetCounterTimeBase.Call(
|
||||
uintptr(hCounter),
|
||||
uintptr(unsafe.Pointer(pTimeBase)))
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
21
internal/pdh/registry/LICENSE
Normal file
21
internal/pdh/registry/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Leopold Schabel / The perflib_exporter authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
178
internal/pdh/registry/collector.go
Normal file
178
internal/pdh/registry/collector.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
)
|
||||
|
||||
type Collector struct {
|
||||
object string
|
||||
query string
|
||||
|
||||
counters map[string]Counter
|
||||
nameIndexValue int
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
Name string
|
||||
Desc string
|
||||
Instances map[string]uint32
|
||||
Type uint32
|
||||
Frequency float64
|
||||
|
||||
FieldIndexValue int
|
||||
FieldIndexSecondValue int
|
||||
}
|
||||
|
||||
func NewCollector[T any](object string, _ []string) (*Collector, error) {
|
||||
collector := &Collector{
|
||||
object: object,
|
||||
query: MapCounterToIndex(object),
|
||||
nameIndexValue: -1,
|
||||
counters: make(map[string]Counter),
|
||||
}
|
||||
|
||||
var values [0]T
|
||||
valueType := reflect.TypeOf(values).Elem()
|
||||
|
||||
if f, ok := valueType.FieldByName("Name"); ok {
|
||||
if f.Type.Kind() == reflect.String {
|
||||
collector.nameIndexValue = f.Index[0]
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range reflect.VisibleFields(valueType) {
|
||||
counterName, ok := f.Tag.Lookup("perfdata")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var counter Counter
|
||||
if counter, ok = collector.counters[counterName]; !ok {
|
||||
counter = Counter{
|
||||
Name: counterName,
|
||||
FieldIndexSecondValue: -1,
|
||||
FieldIndexValue: -1,
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasSuffix(counterName, ",secondvalue") {
|
||||
counterName = strings.TrimSuffix(counterName, ",secondvalue")
|
||||
|
||||
counter.FieldIndexSecondValue = f.Index[0]
|
||||
} else {
|
||||
counter.FieldIndexValue = f.Index[0]
|
||||
}
|
||||
|
||||
collector.counters[counterName] = counter
|
||||
}
|
||||
|
||||
var collectValues []T
|
||||
|
||||
if err := collector.Collect(&collectValues); 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(data any) error {
|
||||
dv := reflect.ValueOf(data)
|
||||
if dv.Kind() != reflect.Ptr || dv.IsNil() {
|
||||
return mi.ErrInvalidEntityType
|
||||
}
|
||||
|
||||
dv = dv.Elem()
|
||||
|
||||
elemType := dv.Type().Elem()
|
||||
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()
|
||||
|
||||
if dv.Kind() != reflect.Slice || elemType.Kind() != reflect.Struct {
|
||||
return mi.ErrInvalidEntityType
|
||||
}
|
||||
|
||||
perfObjects, err := QueryPerformanceData(c.query, c.object)
|
||||
if err != nil {
|
||||
return fmt.Errorf("QueryPerformanceData: %w", err)
|
||||
}
|
||||
|
||||
if len(perfObjects) == 0 || perfObjects[0] == nil || len(perfObjects[0].Instances) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dv.Len() != 0 {
|
||||
dv.Set(reflect.MakeSlice(dv.Type(), 0, len(perfObjects[0].Instances)))
|
||||
}
|
||||
|
||||
dv.Clear()
|
||||
|
||||
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 = pdh.InstanceEmpty
|
||||
}
|
||||
|
||||
if c.nameIndexValue != -1 {
|
||||
elemValue.Field(c.nameIndexValue).SetString(instanceName)
|
||||
}
|
||||
|
||||
dv.Set(reflect.Append(dv, elemValue))
|
||||
index := dv.Len() - 1
|
||||
|
||||
for _, perfCounter := range perfInstance.Counters {
|
||||
if perfCounter.Def.IsBaseValue && !perfCounter.Def.IsNanosecondCounter {
|
||||
continue
|
||||
}
|
||||
|
||||
counter, ok := c.counters[perfCounter.Def.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch perfCounter.Def.CounterType {
|
||||
case pdh.PERF_ELAPSED_TIME:
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexValue).
|
||||
SetFloat(float64((perfCounter.Value - pdh.WindowsEpoch) / perfObject.Frequency))
|
||||
case pdh.PERF_100NSEC_TIMER, pdh.PERF_PRECISION_100NS_TIMER:
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexValue).
|
||||
SetFloat(float64(perfCounter.Value) * pdh.TicksToSecondScaleFactor)
|
||||
default:
|
||||
if counter.FieldIndexSecondValue != -1 {
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexSecondValue).
|
||||
SetFloat(float64(perfCounter.SecondValue))
|
||||
}
|
||||
|
||||
if counter.FieldIndexValue != -1 {
|
||||
dv.Index(index).
|
||||
Field(counter.FieldIndexValue).
|
||||
SetFloat(float64(perfCounter.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Close() {}
|
||||
86
internal/pdh/registry/nametable.go
Normal file
86
internal/pdh/registry/nametable.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// CounterNameTable Initialize global name tables
|
||||
// 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)
|
||||
//
|
||||
//nolint:gochecknoglobals
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
493
internal/pdh/registry/perflib.go
Normal file
493
internal/pdh/registry/perflib.go
Normal file
@@ -0,0 +1,493 @@
|
||||
package registry
|
||||
|
||||
/*
|
||||
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/pdh"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// There's a LittleEndian field in the PERF header - we ought to check it.
|
||||
//
|
||||
//nolint:gochecknoglobals
|
||||
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
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
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:
|
||||
// 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 == pdh.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)]))
|
||||
}
|
||||
}
|
||||
11
internal/pdh/registry/perflib_test.go
Normal file
11
internal/pdh/registry/perflib_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkQueryPerformanceData(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = QueryPerformanceData("Global", "")
|
||||
}
|
||||
}
|
||||
173
internal/pdh/registry/raw_types.go
Normal file
173
internal/pdh/registry/raw_types.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package registry
|
||||
|
||||
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 // unknown field
|
||||
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)
|
||||
}
|
||||
49
internal/pdh/registry/utf16.go
Normal file
49
internal/pdh/registry/utf16.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package registry
|
||||
|
||||
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
|
||||
}
|
||||
9
internal/pdh/registry/utils.go
Normal file
9
internal/pdh/registry/utils.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func MapCounterToIndex(name string) string {
|
||||
return strconv.Itoa(int(CounterNameTable.LookupIndex(name)))
|
||||
}
|
||||
139
internal/pdh/types.go
Normal file
139
internal/pdh/types.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pdh
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
InstanceEmpty = "------"
|
||||
InstanceTotal = "_Total"
|
||||
)
|
||||
|
||||
type CounterValue struct {
|
||||
Type prometheus.ValueType
|
||||
FirstValue float64
|
||||
SecondValue float64
|
||||
}
|
||||
|
||||
// FmtCounterValueDouble is a union specialization for double values.
|
||||
type FmtCounterValueDouble struct {
|
||||
CStatus uint32
|
||||
DoubleValue float64
|
||||
}
|
||||
|
||||
// FmtCounterValueLarge is a union specialization for 64-bit integer values.
|
||||
type FmtCounterValueLarge struct {
|
||||
CStatus uint32
|
||||
LargeValue int64
|
||||
}
|
||||
|
||||
// FmtCounterValueLong is a union specialization for long values.
|
||||
type FmtCounterValueLong struct {
|
||||
CStatus uint32
|
||||
LongValue int32
|
||||
padding [4]byte //nolint:unused // Memory reservation
|
||||
}
|
||||
|
||||
// FmtCounterValueItemDouble is a union specialization for double values, used by GetFormattedCounterArrayDouble.
|
||||
type FmtCounterValueItemDouble struct {
|
||||
SzName *uint16
|
||||
FmtValue FmtCounterValueDouble
|
||||
}
|
||||
|
||||
// FmtCounterValueItemLarge is a union specialization for 'large' values, used by PdhGetFormattedCounterArrayLarge().
|
||||
type FmtCounterValueItemLarge struct {
|
||||
SzName *uint16 // pointer to a string
|
||||
FmtValue FmtCounterValueLarge
|
||||
}
|
||||
|
||||
// FmtCounterValueItemLong is a union specialization for long values, used by PdhGetFormattedCounterArrayLong().
|
||||
type FmtCounterValueItemLong struct {
|
||||
SzName *uint16 // pointer to a string
|
||||
FmtValue FmtCounterValueLong
|
||||
}
|
||||
|
||||
// CounterInfo structure contains information describing the properties of a counter. This information also includes the counter path.
|
||||
type CounterInfo struct {
|
||||
// Size of the structure, including the appended strings, in bytes.
|
||||
DwLength uint32
|
||||
// Counter type. For a list of counter types,
|
||||
// see the Counter Types section of the <a "href=http://go.microsoft.com/fwlink/p/?linkid=84422">Windows Server 2003 Deployment Kit</a>.
|
||||
// The counter type constants are defined in Winperf.h.
|
||||
DwType uint32
|
||||
// Counter version information. Not used.
|
||||
CVersion uint32
|
||||
// Counter status that indicates if the counter value is valid. For a list of possible values,
|
||||
// see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa371894(v=vs.85).aspx">Checking PDH Interface Return Values</a>.
|
||||
CStatus uint32
|
||||
// Scale factor to use when computing the displayable value of the counter. The scale factor is a power of ten.
|
||||
// The valid range of this parameter is PDH_MIN_SCALE (–7) (the returned value is the actual value times 10–⁷) to
|
||||
// Pdh_MAX_SCALE (+7) (the returned value is the actual value times 10⁺⁷). A value of zero will set the scale to one, so that the actual value is returned.
|
||||
LScale int32
|
||||
// Default scale factor as suggested by the counter's provider.
|
||||
LDefaultScale int32
|
||||
// The value passed in the dwUserData parameter when calling AddCounter.
|
||||
DwUserData *uint32
|
||||
// The value passed in the dwUserData parameter when calling OpenQuery.
|
||||
DwQueryUserData *uint32
|
||||
// Null-terminated string that specifies the full counter path. The string follows this structure in memory.
|
||||
SzFullPath *uint16 // pointer to a string
|
||||
// Null-terminated string that contains the name of the computer specified in the counter path. Is NULL, if the path does not specify a computer.
|
||||
// The string follows this structure in memory.
|
||||
SzMachineName *uint16 // pointer to a string
|
||||
// Null-terminated string that contains the name of the performance object specified in the counter path. The string follows this structure in memory.
|
||||
SzObjectName *uint16 // pointer to a string
|
||||
// Null-terminated string that contains the name of the object instance specified in the counter path. Is NULL, if the path does not specify an instance.
|
||||
// The string follows this structure in memory.
|
||||
SzInstanceName *uint16 // pointer to a string
|
||||
// Null-terminated string that contains the name of the parent instance specified in the counter path.
|
||||
// Is NULL, if the path does not specify a parent instance. The string follows this structure in memory.
|
||||
SzParentInstance *uint16 // pointer to a string
|
||||
// Instance index specified in the counter path. Is 0, if the path does not specify an instance index.
|
||||
DwInstanceIndex uint32 // pointer to a string
|
||||
// Null-terminated string that contains the counter name. The string follows this structure in memory.
|
||||
SzCounterName *uint16 // pointer to a string
|
||||
// Help text that describes the counter. Is NULL if the source is a log file.
|
||||
SzExplainText *uint16 // pointer to a string
|
||||
// Start of the string data that is appended to the structure.
|
||||
DataBuffer [1]uint32 // pointer to an extra space
|
||||
}
|
||||
|
||||
// The RawCounter structure returns the data as it was collected from the counter provider.
|
||||
// No translation, formatting, or other interpretation is performed on the data.
|
||||
type RawCounter struct {
|
||||
// Counter status that indicates if the counter value is valid. Check this member before using the data in a calculation or displaying its value.
|
||||
// For a list of possible values, see https://docs.microsoft.com/windows/desktop/PerfCtrs/checking-pdh-interface-return-values
|
||||
CStatus uint32
|
||||
// Local time for when the data was collected
|
||||
TimeStamp windows.Filetime
|
||||
// First raw counter value.
|
||||
FirstValue int64
|
||||
// Second raw counter value. Rate counters require two values in order to compute a displayable value.
|
||||
SecondValue int64
|
||||
// If the counter type contains the PERF_MULTI_COUNTER flag, this member contains the additional counter data used in the calculation.
|
||||
// For example, the PERF_100NSEC_MULTI_TIMER counter type contains the PERF_MULTI_COUNTER flag.
|
||||
MultiCount uint32
|
||||
}
|
||||
|
||||
type RawCounterItem struct {
|
||||
// Pointer to a null-terminated string that specifies the instance name of the counter. The string is appended to the end of this structure.
|
||||
SzName *uint16
|
||||
// A RawCounter structure that contains the raw counter value of the instance
|
||||
RawValue RawCounter
|
||||
}
|
||||
6
internal/pdh/types/types.go
Normal file
6
internal/pdh/types/types.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package types
|
||||
|
||||
type Collector interface {
|
||||
Collect(dst any) error
|
||||
Close()
|
||||
}
|
||||
Reference in New Issue
Block a user