mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-19 19:19:04 +02:00
performancecounter: fix explicit instance lists and startup panics on invalid config (#2511)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0e45202032
commit
7b20daa9ac
+31
-20
@@ -334,7 +334,7 @@ func (c *Collector) collectWorkerRaw() {
|
||||
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()
|
||||
|
||||
indexMap := map[string]int{}
|
||||
stringMap := map[*uint16]string{}
|
||||
nameCache := map[string]string{}
|
||||
|
||||
for _, counter := range c.counters {
|
||||
for _, instance := range counter.Instances {
|
||||
@@ -365,11 +365,6 @@ func (c *Collector) collectWorkerRaw() {
|
||||
|
||||
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",
|
||||
@@ -381,10 +376,7 @@ func (c *Collector) collectWorkerRaw() {
|
||||
continue
|
||||
}
|
||||
|
||||
if instanceName, ok = stringMap[item.SzName]; !ok {
|
||||
instanceName = windows.UTF16PtrToString(item.SzName)
|
||||
stringMap[item.SzName] = instanceName
|
||||
}
|
||||
instanceName := decodeInstanceName(nameCache, item.SzName)
|
||||
|
||||
if strings.HasSuffix(instanceName, InstanceTotal) && !c.totalCounterRequested {
|
||||
continue
|
||||
@@ -501,7 +493,7 @@ func (c *Collector) collectWorkerFormatted() {
|
||||
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()
|
||||
|
||||
indexMap := map[string]int{}
|
||||
stringMap := map[*uint16]string{}
|
||||
nameCache := map[string]string{}
|
||||
|
||||
for _, counter := range c.counters {
|
||||
for _, instance := range counter.Instances {
|
||||
@@ -532,20 +524,12 @@ func (c *Collector) collectWorkerFormatted() {
|
||||
|
||||
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
|
||||
}
|
||||
instanceName := decodeInstanceName(nameCache, item.SzName)
|
||||
|
||||
if strings.HasSuffix(instanceName, InstanceTotal) && !c.totalCounterRequested {
|
||||
continue
|
||||
@@ -631,6 +615,33 @@ func formatCounterPath(object, instance, counterName string) string {
|
||||
return counterPath
|
||||
}
|
||||
|
||||
// decodeInstanceName returns the instance name p points to.
|
||||
// Decoded names are cached by their raw UTF-16 content and not by p, because p points
|
||||
// into the item buffer, which is overwritten by every Get*CounterArray call. With explicit
|
||||
// instances, every call writes its single name at the same address.
|
||||
func decodeInstanceName(cache map[string]string, p *uint16) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
n := 0
|
||||
for *(*uint16)(unsafe.Add(unsafe.Pointer(p), n*2)) != 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
raw := unsafe.Slice((*byte)(unsafe.Pointer(p)), n*2)
|
||||
|
||||
// The compiler does not allocate for the string conversion in a map lookup.
|
||||
if name, ok := cache[string(raw)]; ok {
|
||||
return name
|
||||
}
|
||||
|
||||
name := windows.UTF16ToString(unsafe.Slice(p, n))
|
||||
cache[string(raw)] = name
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func isKnownCounterDataError(err error) bool {
|
||||
var pdhErr *Error
|
||||
|
||||
|
||||
@@ -71,3 +71,67 @@ func TestCollector(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type processorInformation struct {
|
||||
Name string
|
||||
ProcessorTime float64 `perfdata:"% Processor Time"`
|
||||
IdleTime float64 `perfdata:"% Idle Time"`
|
||||
}
|
||||
|
||||
// TestCollectorExplicitInstances verifies that a collector built with an explicit
|
||||
// list of instances returns one row per requested instance. Each instance has its
|
||||
// own counter handle, and PDH writes every single-item result into the same buffer,
|
||||
// so the instance names must not be resolved through a cache keyed by buffer address.
|
||||
func TestCollectorExplicitInstances(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := slog.New(slog.DiscardHandler)
|
||||
|
||||
discovery, err := pdh.NewCollector[processorInformation](logger, pdh.CounterTypeRaw, "Processor Information", pdh.InstancesAll)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(discovery.Close)
|
||||
|
||||
var all []processorInformation
|
||||
|
||||
require.NoError(t, discovery.Collect(&all))
|
||||
|
||||
instances := make([]string, 0, 3)
|
||||
|
||||
for _, row := range all {
|
||||
if len(instances) == cap(instances) {
|
||||
break
|
||||
}
|
||||
|
||||
instances = append(instances, row.Name)
|
||||
}
|
||||
|
||||
if len(instances) < 2 {
|
||||
t.Skipf("need at least 2 Processor Information instances, got %v", instances)
|
||||
}
|
||||
|
||||
for _, counterType := range []pdh.CounterType{pdh.CounterTypeRaw, pdh.CounterTypeFormatted} {
|
||||
t.Run(string(counterType), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
collector, err := pdh.NewCollector[processorInformation](logger, counterType, "Processor Information", instances)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(collector.Close)
|
||||
|
||||
// Formatted rate counters need two samples before they report a value.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
var data []processorInformation
|
||||
|
||||
require.NoError(t, collector.Collect(&data))
|
||||
|
||||
names := make([]string, 0, len(data))
|
||||
for _, row := range data {
|
||||
names = append(names, row.Name)
|
||||
}
|
||||
|
||||
require.ElementsMatch(t, instances, names)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user