diff --git a/internal/collector/netframework/netframework.go b/internal/collector/netframework/netframework.go index 116e15b4..4c2bf4d0 100644 --- a/internal/collector/netframework/netframework.go +++ b/internal/collector/netframework/netframework.go @@ -77,11 +77,15 @@ type Collector struct { // labels keeps the label pairs of the series of the last scrape. The // counters change on every scrape, but their process labels stay the // same while the process runs. - labels labelcache.Cache - config Config - logger *slog.Logger - perfFrequency float64 - closeFns []func() + labels labelcache.Cache + config Config + logger *slog.Logger + perfFrequency float64 + closeFns []func() + // perfQuery samples the counters of all enabled sub-collectors at once. + // The .NET CLR counter provider serves one query at a time and walks + // every .NET process for each, so separate queries wait for each other. + perfQuery *pdh.Query perfClrExceptions perfCollector[perfDataClrExceptions] perfClrInterop perfCollector[perfDataClrInterop] perfClrJIT perfCollector[perfDataClrJIT] @@ -210,6 +214,9 @@ func (c *Collector) Close() error { c.collectorFns = nil c.labels.Reset() + c.perfQuery.Close() + c.perfQuery = nil + return nil } @@ -230,6 +237,13 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { c.perfFrequency = float64(frequency) } + var err error + + c.perfQuery, err = pdh.NewQuery() + if err != nil { + return fmt.Errorf("failed to open .NET CLR performance counter query: %w", err) + } + c.collectorFns = make([]func(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, maxScrapeDuration time.Duration) error, 0, len(c.config.CollectorsEnabled)) subCollectors := map[string]struct { @@ -340,6 +354,14 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { // Collect sends the metric values for each metric // to the provided prometheus Metric channel. func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error { + // The sub-collectors read this sample. Without any .NET process there is + // no data; the sub-collectors report that themselves. + if c.perfQuery != nil { + if err := c.perfQuery.Collect(); err != nil && !errors.Is(err, pdh.ErrNoData) { + return fmt.Errorf("failed to collect .NET CLR performance counters: %w", err) + } + } + scrape := c.labels.Begin() defer scrape.Commit() @@ -354,10 +376,11 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time. return g.Wait() } -// newPerfCollector retains ownership of partially initialized PDH queries. -// Missing objects are optional; missing individual counters remain errors. -func newPerfCollector[T any](logger *slog.Logger, object string) (perfCollector[T], error) { - c, err := pdh.NewCollector[T](logger, pdh.CounterTypeRaw, object, pdh.InstancesAll) +// newPerfCollector adds the counters of object to query and retains +// ownership of partially initialized collectors. Missing objects are optional; +// missing individual counters remain errors. +func newPerfCollector[T any](logger *slog.Logger, query *pdh.Query, object string) (perfCollector[T], error) { + c, err := pdh.NewQueryCollector[T](logger, query, pdh.CounterTypeRaw, object, pdh.InstancesAll) if err == nil { return c, nil } diff --git a/internal/collector/netframework/netframework_clrexceptions.go b/internal/collector/netframework/netframework_clrexceptions.go index 37ea00b9..620cbb8f 100644 --- a/internal/collector/netframework/netframework_clrexceptions.go +++ b/internal/collector/netframework/netframework_clrexceptions.go @@ -58,7 +58,7 @@ func (c *Collector) buildClrExceptions() error { var err error - c.perfClrExceptions, err = newPerfCollector[perfDataClrExceptions](c.logger, ".NET CLR Exceptions") + c.perfClrExceptions, err = newPerfCollector[perfDataClrExceptions](c.logger, c.perfQuery, ".NET CLR Exceptions") return err } diff --git a/internal/collector/netframework/netframework_clrinterop.go b/internal/collector/netframework/netframework_clrinterop.go index a3adbea7..66a5c33b 100644 --- a/internal/collector/netframework/netframework_clrinterop.go +++ b/internal/collector/netframework/netframework_clrinterop.go @@ -52,7 +52,7 @@ func (c *Collector) buildClrInterop() error { var err error - c.perfClrInterop, err = newPerfCollector[perfDataClrInterop](c.logger, ".NET CLR Interop") + c.perfClrInterop, err = newPerfCollector[perfDataClrInterop](c.logger, c.perfQuery, ".NET CLR Interop") return err } diff --git a/internal/collector/netframework/netframework_clrjit.go b/internal/collector/netframework/netframework_clrjit.go index 3b5d1156..b7309fd7 100644 --- a/internal/collector/netframework/netframework_clrjit.go +++ b/internal/collector/netframework/netframework_clrjit.go @@ -58,7 +58,7 @@ func (c *Collector) buildClrJIT() error { var err error - c.perfClrJIT, err = newPerfCollector[perfDataClrJIT](c.logger, ".NET CLR Jit") + c.perfClrJIT, err = newPerfCollector[perfDataClrJIT](c.logger, c.perfQuery, ".NET CLR Jit") return err } diff --git a/internal/collector/netframework/netframework_clrloading.go b/internal/collector/netframework/netframework_clrloading.go index c06a9d41..7fe03622 100644 --- a/internal/collector/netframework/netframework_clrloading.go +++ b/internal/collector/netframework/netframework_clrloading.go @@ -88,7 +88,7 @@ func (c *Collector) buildClrLoading() error { var err error - c.perfClrLoading, err = newPerfCollector[perfDataClrLoading](c.logger, ".NET CLR Loading") + c.perfClrLoading, err = newPerfCollector[perfDataClrLoading](c.logger, c.perfQuery, ".NET CLR Loading") return err } diff --git a/internal/collector/netframework/netframework_clrlocksandthreads.go b/internal/collector/netframework/netframework_clrlocksandthreads.go index 2ad3b9d8..050c999a 100644 --- a/internal/collector/netframework/netframework_clrlocksandthreads.go +++ b/internal/collector/netframework/netframework_clrlocksandthreads.go @@ -76,7 +76,7 @@ func (c *Collector) buildClrLocksAndThreads() error { var err error - c.perfClrLocksAndThreads, err = newPerfCollector[perfDataClrLocksAndThreads](c.logger, ".NET CLR LocksAndThreads") + c.perfClrLocksAndThreads, err = newPerfCollector[perfDataClrLocksAndThreads](c.logger, c.perfQuery, ".NET CLR LocksAndThreads") return err } diff --git a/internal/collector/netframework/netframework_clrmemory.go b/internal/collector/netframework/netframework_clrmemory.go index 6848fe5a..80b46452 100644 --- a/internal/collector/netframework/netframework_clrmemory.go +++ b/internal/collector/netframework/netframework_clrmemory.go @@ -107,7 +107,7 @@ func (c *Collector) buildClrMemory() error { var err error - c.perfClrMemory, err = newPerfCollector[perfDataClrMemory](c.logger, ".NET CLR Memory") + c.perfClrMemory, err = newPerfCollector[perfDataClrMemory](c.logger, c.perfQuery, ".NET CLR Memory") return err } diff --git a/internal/collector/netframework/netframework_clrremoting.go b/internal/collector/netframework/netframework_clrremoting.go index 8eee12dc..c25399b3 100644 --- a/internal/collector/netframework/netframework_clrremoting.go +++ b/internal/collector/netframework/netframework_clrremoting.go @@ -70,7 +70,7 @@ func (c *Collector) buildClrRemoting() error { var err error - c.perfClrRemoting, err = newPerfCollector[perfDataClrRemoting](c.logger, ".NET CLR Remoting") + c.perfClrRemoting, err = newPerfCollector[perfDataClrRemoting](c.logger, c.perfQuery, ".NET CLR Remoting") return err } diff --git a/internal/collector/netframework/netframework_clrsecurity.go b/internal/collector/netframework/netframework_clrsecurity.go index 8ec5979f..9edd9fd2 100644 --- a/internal/collector/netframework/netframework_clrsecurity.go +++ b/internal/collector/netframework/netframework_clrsecurity.go @@ -58,7 +58,7 @@ func (c *Collector) buildClrSecurity() error { var err error - c.perfClrSecurity, err = newPerfCollector[perfDataClrSecurity](c.logger, ".NET CLR Security") + c.perfClrSecurity, err = newPerfCollector[perfDataClrSecurity](c.logger, c.perfQuery, ".NET CLR Security") return err }