Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/dictionaries/project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 84 additions & 52 deletions internal/pdh/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@
)

buf := make([]byte, 1)
// Reuse string cache across collection cycles to reduce allocations
stringCache := make(map[*uint16]string, 64)

for data := range c.collectCh {
err = (func() error {
Expand Down Expand Up @@ -334,42 +336,61 @@
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()

indexMap := map[string]int{}
stringMap := map[*uint16]string{}
// Clear and reuse string cache instead of creating new one each time
for k := range stringCache {
delete(stringCache, k)
}

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])
// First call: get required buffer size (as per PDH documentation)
bytesNeeded = 0
ret := GetRawCounterArray(instance, &bytesNeeded, &itemCount, nil)

Check failure on line 348 in internal/pdh/collector.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (too many statements above if) (wsl_v5)
if ret != MoreData {
if err := NewPdhError(ret); isKnownCounterDataError(err) {
c.logger.Debug("no data for counter instance",
slog.String("counter", counter.Name),
slog.String("object", c.object),
slog.Any("err", err),
)

if ret == ErrorSuccess {
break
}

if err := NewPdhError(ret); ret != MoreData {
if isKnownCounterDataError(err) {
break
}
return fmt.Errorf("GetRawCounterArray size query: %w", NewPdhError(ret))
}

return fmt.Errorf("GetRawCounterArray: %w", err)
// Optimize buffer allocation: grow with headroom to reduce future reallocations
if bytesNeeded > uint32(cap(buf)) {
// Grow buffer with some headroom (minimum 1KB, or 50% larger than needed)
newSize := bytesNeeded
if newSize < 1024 {
newSize = 1024
} else if newSize < 8192 { // For buffers under 8KB, add 50% headroom
newSize = (newSize * 3) / 2
}
buf = make([]byte, newSize)

Check failure on line 372 in internal/pdh/collector.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (invalid statement above assign) (wsl_v5)
}

// Second call: get the actual data (as per PDH documentation)
actualBytesNeeded := bytesNeeded
ret = GetRawCounterArray(instance, &actualBytesNeeded, &itemCount, &buf[0])

Check failure on line 377 in internal/pdh/collector.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (too many statements above if) (wsl_v5)
if ret != ErrorSuccess {
if err := NewPdhError(ret); isKnownCounterDataError(err) {
c.logger.Debug("no data for counter instance",
slog.String("counter", counter.Name),
slog.String("object", c.object),
slog.Any("err", 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))
break
}

buf = make([]byte, bytesNeeded)
return fmt.Errorf("GetRawCounterArray data retrieval: %w", NewPdhError(ret))
}

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 {
c.logger.Debug("skipping counter item with invalid data status",
Expand All @@ -381,9 +402,14 @@
continue
}

if instanceName, ok = stringMap[item.SzName]; !ok {
var (
instanceName string
ok bool
)

if instanceName, ok = stringCache[item.SzName]; !ok {
instanceName = windows.UTF16PtrToString(item.SzName)
stringMap[item.SzName] = instanceName
stringCache[item.SzName] = instanceName
}

if strings.HasSuffix(instanceName, InstanceTotal) && !c.totalCounterRequested {
Expand All @@ -394,12 +420,8 @@
instanceName = InstanceEmpty
}

var (
index int
ok bool
)

if index, ok = indexMap[instanceName]; !ok {
index, indexExists := indexMap[instanceName]
if !indexExists {
index = dv.Len()
indexMap[instanceName] = index

Expand Down Expand Up @@ -468,6 +490,8 @@
)

buf := make([]byte, 1)
// Reuse string cache across collection cycles to reduce allocations
stringCache := make(map[*uint16]string, 64)

for data := range c.collectCh {
err = (func() error {
Expand Down Expand Up @@ -501,33 +525,45 @@
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()

indexMap := map[string]int{}
stringMap := map[*uint16]string{}
// Clear and reuse string cache instead of creating new one each time
for k := range stringCache {
delete(stringCache, k)
}

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 {
// First call: get required buffer size (as per PDH documentation)
bytesNeeded = 0
ret := GetFormattedCounterArrayDouble(instance, &bytesNeeded, &itemCount, nil)

Check failure on line 537 in internal/pdh/collector.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (too many statements above if) (wsl_v5)
if ret != MoreData {
if err := NewPdhError(ret); isKnownCounterDataError(err) {
break
}

if err := NewPdhError(ret); ret != MoreData {
if isKnownCounterDataError(err) {
break
}
return fmt.Errorf("GetFormattedCounterArrayDouble size query: %w", NewPdhError(ret))
}

return fmt.Errorf("GetFormattedCounterArrayDouble: %w", err)
// Optimize buffer allocation: grow with headroom to reduce future reallocations
if bytesNeeded > uint32(cap(buf)) {
// Grow buffer with some headroom (minimum 1KB, or 50% larger than needed)
newSize := bytesNeeded
if newSize < 1024 {
newSize = 1024
} else if newSize < 8192 { // For buffers under 8KB, add 50% headroom
newSize = (newSize * 3) / 2
}
buf = make([]byte, newSize)

Check failure on line 555 in internal/pdh/collector.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (invalid statement above assign) (wsl_v5)
}

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))
// Second call: get the actual data (as per PDH documentation)
actualBytesNeeded := bytesNeeded
ret = GetFormattedCounterArrayDouble(instance, &actualBytesNeeded, &itemCount, &buf[0])

Check failure on line 560 in internal/pdh/collector.go

View workflow job for this annotation

GitHub Actions / lint

missing whitespace above this line (too many statements above if) (wsl_v5)
if ret != ErrorSuccess {
if err := NewPdhError(ret); isKnownCounterDataError(err) {
break
}

buf = make([]byte, bytesNeeded)
return fmt.Errorf("GetFormattedCounterArrayDouble data retrieval: %w", NewPdhError(ret))
}

items = unsafe.Slice((*FmtCounterValueItemDouble)(unsafe.Pointer(&buf[0])), itemCount)
Expand All @@ -542,9 +578,9 @@
continue
}

if instanceName, ok = stringMap[item.SzName]; !ok {
if instanceName, ok = stringCache[item.SzName]; !ok {
instanceName = windows.UTF16PtrToString(item.SzName)
stringMap[item.SzName] = instanceName
stringCache[item.SzName] = instanceName
}

if strings.HasSuffix(instanceName, InstanceTotal) && !c.totalCounterRequested {
Expand All @@ -555,12 +591,8 @@
instanceName = InstanceEmpty
}

var (
index int
ok bool
)

if index, ok = indexMap[instanceName]; !ok {
index, indexExists := indexMap[instanceName]
if !indexExists {
index = dv.Len()
indexMap[instanceName] = index

Expand Down
56 changes: 28 additions & 28 deletions internal/pdh/collector_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,34 @@ import (
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"`
ProcessorTime float64 `perfdata:"% Processor Time"`
PrivilegedTime float64 `perfdata:"% Privileged Time"`
UserTime float64 `perfdata:"% User Time"`
CreatingProcessID float64 `perfdata:"Creating Process ID"`
ElapsedTime float64 `perfdata:"Elapsed Time"`
HandleCount float64 `perfdata:"Handle Count"`
IDProcess float64 `perfdata:"ID Process"`
IODataBytesSec float64 `perfdata:"IO Data Bytes/sec"`
IODataOperationsSec float64 `perfdata:"IO Data Operations/sec"`
IOOtherBytesSec float64 `perfdata:"IO Other Bytes/sec"`
IOOtherOperationsSec float64 `perfdata:"IO Other Operations/sec"`
IOReadBytesSec float64 `perfdata:"IO Read Bytes/sec"`
IOReadOperationsSec float64 `perfdata:"IO Read Operations/sec"`
IOWriteBytesSec float64 `perfdata:"IO Write Bytes/sec"`
IOWriteOperationsSec float64 `perfdata:"IO Write Operations/sec"`
PageFaultsSec float64 `perfdata:"Page Faults/sec"`
PageFileBytesPeak float64 `perfdata:"Page File Bytes Peak"`
PageFileBytes float64 `perfdata:"Page File Bytes"`
PoolNonpagedBytes float64 `perfdata:"Pool Nonpaged Bytes"`
PoolPagedBytes float64 `perfdata:"Pool Paged Bytes"`
PriorityBase float64 `perfdata:"Priority Base"`
PrivateBytes float64 `perfdata:"Private Bytes"`
ThreadCount float64 `perfdata:"Thread Count"`
VirtualBytesPeak float64 `perfdata:"Virtual Bytes Peak"`
VirtualBytes float64 `perfdata:"Virtual Bytes"`
WorkingSetPrivate float64 `perfdata:"Working Set - Private"`
WorkingSetPeak float64 `perfdata:"Working Set Peak"`
WorkingSet float64 `perfdata:"Working Set"`
}

func BenchmarkTestCollector(b *testing.B) {
Expand Down
Loading