pdh: load a missing elapsed time base on a later sample (#2707)

Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jan-Otto Kröpke
2026-10-15 11:16:29 +02:00
committed by GitHub
co-authored by Claude
parent 5d6cf525bf
commit c3b7a03128
2 changed files with 56 additions and 0 deletions
+22
View File
@@ -509,6 +509,8 @@ func (c *Collector[T]) collect(dst *[]T, state *collectState) (err error) {
defer state.names.removeUnseen()
for counterIndex := range c.counters {
c.loadTimeBase(&c.counters[counterIndex])
for _, instance := range c.counters[counterIndex].Instances {
itemCount, ok, err := c.getCounterArray(instance, &state.buf)
if err != nil {
@@ -538,6 +540,26 @@ func (c *Collector[T]) collect(dst *[]T, state *collectState) (err error) {
return nil
}
// loadTimeBase reads the time base of a raw elapsed time counter that had none
// when the collector was created. Early after a reboot, PDH can report no data
// for it, e.g. for APP_POOL_WAS while WAS is still starting (#2313). Without a
// time base, the counter has no valid value and every instance is omitted.
func (c *Collector[T]) loadTimeBase(counter *Counter) {
if c.resultType != CounterTypeRaw || counter.Type != PERF_ELAPSED_TIME || counter.Frequency > 0 {
return
}
for _, instance := range counter.Instances {
var frequency int64
if ret := GetCounterTimeBase(instance, &frequency); ret == ErrorSuccess && frequency > 0 {
counter.Frequency = frequency
return
}
}
}
// pdhBufferSlack is extra space allocated beyond the buffer size reported by PDH.
// On Windows Server 2012 R2, PdhGetRawCounterArrayW reports the size of the item array of a single-instance
// object (e.g. System) without the terminator of the empty instance name, and writes that terminator right after
+34
View File
@@ -363,6 +363,40 @@ func TestSetRawValueElapsedTime(t *testing.T) {
require.True(t, c.setRawValue(&values, counter, RawCounter{FirstValue: 1, SecondValue: 2}))
}
// TestCollectLoadsMissingTimeBase checks that an elapsed time counter whose time
// base was not available when the collector was created gets it on a later
// sample, instead of omitting every instance until a restart (#2313).
func TestCollectLoadsMissingTimeBase(t *testing.T) {
t.Parallel()
type systemUpTime struct {
SystemUpTime float64 `perfdata:"System Up Time"`
}
collector, err := NewCollector[systemUpTime](slog.New(slog.DiscardHandler), CounterTypeRaw, "System", nil)
require.NoError(t, err)
t.Cleanup(collector.Close)
require.Len(t, collector.counters, 1)
counter := &collector.counters[0]
require.Equal(t, uint32(PERF_ELAPSED_TIME), counter.Type)
frequency := counter.Frequency
require.Positive(t, frequency)
// As if GetCounterTimeBase reported no data at startup.
counter.Frequency = 0
var dst []systemUpTime
require.NoError(t, collector.Collect(&dst))
require.Equal(t, frequency, counter.Frequency)
require.Len(t, dst, 1)
require.Positive(t, dst[0].SystemUpTime)
}
func TestMetricTypeForCounterType(t *testing.T) {
t.Parallel()