performancecounter: Add the possibility to request formatted values (#1830)

This commit is contained in:
Jan-Otto Kröpke
2025-01-14 23:32:44 +01:00
committed by GitHub
parent 4cd9627ebf
commit 86e6d12518
80 changed files with 354 additions and 132 deletions

View File

@@ -63,13 +63,13 @@ type Counter struct {
FieldIndexSecondValue int
}
func NewCollector[T any](object string, instances []string) (*Collector, error) {
func NewCollector[T any](resultType CounterType, object string, instances []string) (*Collector, error) {
valueType := reflect.TypeFor[T]()
return NewCollectorWithReflection(object, instances, valueType)
return NewCollectorWithReflection(resultType, object, instances, valueType)
}
func NewCollectorWithReflection(object string, instances []string, valueType reflect.Type) (*Collector, error) {
func NewCollectorWithReflection(resultType CounterType, object string, instances []string, valueType reflect.Type) (*Collector, error) {
var handle pdhQueryHandle
if ret := OpenQuery(0, 0, &handle); ret != ErrorSuccess {
@@ -80,6 +80,10 @@ func NewCollectorWithReflection(object string, instances []string, valueType ref
instances = []string{InstanceEmpty}
}
if resultType != CounterTypeRaw && resultType != CounterTypeFormatted {
return nil, fmt.Errorf("invalid result type: %v", resultType)
}
collector := &Collector{
object: object,
counters: make(map[string]Counter, valueType.NumField()),
@@ -209,7 +213,11 @@ func NewCollectorWithReflection(object string, instances []string, valueType ref
collector.collectCh = make(chan any)
collector.errorCh = make(chan error)
go collector.collectRoutine()
if resultType == CounterTypeRaw {
go collector.collectWorkerRaw()
} else {
go collector.collectWorkerFormatted()
}
// Collect initial data because some counters need to be read twice to get the correct value.
collectValues := reflect.New(reflect.SliceOf(valueType)).Elem()
@@ -254,7 +262,7 @@ func (c *Collector) Collect(dst any) error {
return <-c.errorCh
}
func (c *Collector) collectRoutine() {
func (c *Collector) collectWorkerRaw() {
var (
err error
itemCount uint32
@@ -310,7 +318,11 @@ func (c *Collector) collectRoutine() {
break
}
if err := NewPdhError(ret); ret != MoreData && !isKnownCounterDataError(err) {
if err := NewPdhError(ret); ret != MoreData {
if isKnownCounterDataError(err) {
break
}
return fmt.Errorf("GetRawCounterArray: %w", err)
}
@@ -411,6 +423,142 @@ func (c *Collector) collectRoutine() {
}
}
func (c *Collector) collectWorkerFormatted() {
var (
err error
itemCount uint32
items []FmtCounterValueItemDouble
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 := GetFormattedCounterArrayDouble(instance, &bytesNeeded, &itemCount, &buf[0])
if ret == ErrorSuccess {
break
}
if err := NewPdhError(ret); ret != MoreData {
if isKnownCounterDataError(err) {
break
}
return fmt.Errorf("GetFormattedCounterArrayDouble: %w", err)
}
if bytesNeeded <= uint32(cap(buf)) {
return fmt.Errorf("GetFormattedCounterArrayDouble 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((*FmtCounterValueItemDouble)(unsafe.Pointer(&buf[0])), itemCount)
var (
instanceName string
ok bool
)
for _, item := range items {
if item.FmtValue.CStatus != CstatusValidData && item.FmtValue.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 {
elemValue.Field(c.metricsTypeIndexValue).Set(reflect.ValueOf(prometheus.GaugeValue))
}
dv.Set(reflect.Append(dv, elemValue))
}
if counter.FieldIndexValue != -1 {
dv.Index(index).
Field(counter.FieldIndexValue).
SetFloat(item.FmtValue.DoubleValue)
}
}
}
}
if dv.Len() == 0 {
return ErrNoData
}
return nil
})()
c.errorCh <- err
}
}
func (c *Collector) Close() {
if c == nil {
return

View File

@@ -56,7 +56,7 @@ type processFull struct {
}
func BenchmarkTestCollector(b *testing.B) {
performanceData, err := pdh.NewCollector[processFull]("Process", []string{"*"})
performanceData, err := pdh.NewCollector[processFull](pdh.CounterTypeRaw, "Process", []string{"*"})
require.NoError(b, err)
var data []processFull

View File

@@ -44,7 +44,7 @@ func TestCollector(t *testing.T) {
t.Run(tc.object, func(t *testing.T) {
t.Parallel()
performanceData, err := pdh.NewCollector[process](tc.object, tc.instances)
performanceData, err := pdh.NewCollector[process](pdh.CounterTypeRaw, tc.object, tc.instances)
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)

View File

@@ -452,7 +452,7 @@ func GetFormattedCounterValueDouble(hCounter pdhCounterHandle, lpdwType *uint32,
func GetFormattedCounterArrayDouble(hCounter pdhCounterHandle, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *byte) uint32 {
ret, _, _ := pdhGetFormattedCounterArrayW.Call(
uintptr(hCounter),
uintptr(FmtDouble|FmtNocap100),
uintptr(FmtDouble),
uintptr(unsafe.Pointer(lpdwBufferSize)),
uintptr(unsafe.Pointer(lpdwBufferCount)),
uintptr(unsafe.Pointer(itemBuffer)))

View File

@@ -20,6 +20,13 @@ import (
"golang.org/x/sys/windows"
)
type CounterType string
const (
CounterTypeRaw CounterType = "raw"
CounterTypeFormatted CounterType = "formatted"
)
const (
InstanceEmpty = "------"
InstanceTotal = "_Total"