mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
netframework: collect CLR performance counters through PDH (#2632)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
# netframework collector
|
||||
|
||||
The netframework collector exposes metrics about dotnet framework.
|
||||
The netframework collector exposes .NET Framework performance counters using PDH. Raw counter values preserve the existing metric types and units; counters named per second are published as cumulative values where specified below. The `_Global_` instance is excluded. Instance occurrence suffixes distinguish processes with the same name within a scrape and are not stable process identifiers.
|
||||
|
||||
For compatibility, JIT and runtime security check time metrics retain their existing raw numerator divided by the performance-clock frequency. The GC time metric uses 100 times its raw numerator divided by the raw base; samples with a zero base omit that metric while retaining the other valid metrics. Missing performance counter objects return no data; initialization errors for missing individual counters are reported.
|
||||
|
||||
| | |
|
||||
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Metric name prefix** | `netframework_clrexceptions` |
|
||||
| **Classes** | `Win32_PerfRawData_NETFramework_NETCLRExceptions`, `Win32_PerfRawData_NETFramework_NETCLRInterop`, `Win32_PerfRawData_NETFramework_NETCLRJit`, `Win32_PerfRawData_NETFramework_NETCLRLoading`, `Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads`, `Win32_PerfRawData_NETFramework_NETCLRMemory`, `Win32_PerfRawData_NETFramework_NETCLRRemoting`, `Win32_PerfRawData_NETFramework_NETCLRSecurity` |
|
||||
| **Performance counter objects** | `.NET CLR Exceptions`, `.NET CLR Interop`, `.NET CLR Jit`, `.NET CLR Loading`, `.NET CLR LocksAndThreads`, `.NET CLR Memory`, `.NET CLR Remoting`, `.NET CLR Security` |
|
||||
| **Enabled by default?** | No |
|
||||
|
||||
## Flags
|
||||
|
||||
@@ -25,13 +25,21 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/recovery"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type perfCollector[T any] interface {
|
||||
Collect(dst *[]T) error
|
||||
Close()
|
||||
}
|
||||
|
||||
const Name = "netframework"
|
||||
|
||||
type Config struct {
|
||||
@@ -63,10 +71,20 @@ const (
|
||||
collectorClrSecurity = "clrsecurity"
|
||||
)
|
||||
|
||||
// A Collector is a Prometheus Collector for WMI Win32_PerfRawData_NETFramework_NETCLRExceptions metrics.
|
||||
// A Collector collects .NET Framework performance counters.
|
||||
type Collector struct {
|
||||
config Config
|
||||
miSession *mi.Session
|
||||
config Config
|
||||
logger *slog.Logger
|
||||
perfFrequency float64
|
||||
closeFns []func()
|
||||
perfClrExceptions perfCollector[perfDataClrExceptions]
|
||||
perfClrInterop perfCollector[perfDataClrInterop]
|
||||
perfClrJIT perfCollector[perfDataClrJIT]
|
||||
perfClrLoading perfCollector[perfDataClrLoading]
|
||||
perfClrLocksAndThreads perfCollector[perfDataClrLocksAndThreads]
|
||||
perfClrMemory perfCollector[perfDataClrMemory]
|
||||
perfClrRemoting perfCollector[perfDataClrRemoting]
|
||||
perfClrSecurity perfCollector[perfDataClrSecurity]
|
||||
|
||||
collectorFns []func(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error
|
||||
|
||||
@@ -179,58 +197,111 @@ func (c *Collector) GetName() string {
|
||||
}
|
||||
|
||||
func (c *Collector) Close() error {
|
||||
for _, closeFn := range c.closeFns {
|
||||
closeFn()
|
||||
}
|
||||
|
||||
c.closeFns = nil
|
||||
c.collectorFns = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error {
|
||||
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
if len(c.config.CollectorsEnabled) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if miSession == nil {
|
||||
return errors.New("miSession is nil")
|
||||
}
|
||||
c.logger = logger
|
||||
if slices.Contains(c.config.CollectorsEnabled, collectorClrJIT) || slices.Contains(c.config.CollectorsEnabled, collectorClrSecurity) {
|
||||
var frequency int64
|
||||
|
||||
c.miSession = miSession
|
||||
ret, _, err := windows.NewLazySystemDLL("kernel32.dll").NewProc("QueryPerformanceFrequency").Call(uintptr(unsafe.Pointer(&frequency)))
|
||||
if ret == 0 || frequency <= 0 {
|
||||
return fmt.Errorf("QueryPerformanceFrequency: %w", err)
|
||||
}
|
||||
|
||||
c.perfFrequency = float64(frequency)
|
||||
}
|
||||
|
||||
c.collectorFns = make([]func(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error, 0, len(c.config.CollectorsEnabled))
|
||||
|
||||
subCollectors := map[string]struct {
|
||||
build func()
|
||||
build func() error
|
||||
collect func(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error
|
||||
close func()
|
||||
}{
|
||||
collectorClrExceptions: {
|
||||
build: c.buildClrExceptions,
|
||||
collect: c.collectClrExceptions,
|
||||
close: func() {
|
||||
if c.perfClrExceptions != nil {
|
||||
c.perfClrExceptions.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
collectorClrJIT: {
|
||||
build: c.buildClrJIT,
|
||||
collect: c.collectClrJIT,
|
||||
close: func() {
|
||||
if c.perfClrJIT != nil {
|
||||
c.perfClrJIT.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
collectorClrLoading: {
|
||||
build: c.buildClrLoading,
|
||||
collect: c.collectClrLoading,
|
||||
close: func() {
|
||||
if c.perfClrLoading != nil {
|
||||
c.perfClrLoading.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
collectorClrInterop: {
|
||||
build: c.buildClrInterop,
|
||||
collect: c.collectClrInterop,
|
||||
close: func() {
|
||||
if c.perfClrInterop != nil {
|
||||
c.perfClrInterop.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
collectorClrLocksAndThreads: {
|
||||
build: c.buildClrLocksAndThreads,
|
||||
collect: c.collectClrLocksAndThreads,
|
||||
close: func() {
|
||||
if c.perfClrLocksAndThreads != nil {
|
||||
c.perfClrLocksAndThreads.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
collectorClrMemory: {
|
||||
build: c.buildClrMemory,
|
||||
collect: c.collectClrMemory,
|
||||
close: func() {
|
||||
if c.perfClrMemory != nil {
|
||||
c.perfClrMemory.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
collectorClrRemoting: {
|
||||
build: c.buildClrRemoting,
|
||||
collect: c.collectClrRemoting,
|
||||
close: func() {
|
||||
if c.perfClrRemoting != nil {
|
||||
c.perfClrRemoting.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
collectorClrSecurity: {
|
||||
build: c.buildClrSecurity,
|
||||
collect: c.collectClrSecurity,
|
||||
close: func() {
|
||||
if c.perfClrSecurity != nil {
|
||||
c.perfClrSecurity.Close()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -247,7 +318,12 @@ func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error {
|
||||
}
|
||||
|
||||
for _, name := range collectorsEnabled {
|
||||
subCollectors[name].build()
|
||||
c.closeFns = append(c.closeFns, subCollectors[name].close)
|
||||
if err := subCollectors[name].build(); err != nil {
|
||||
_ = c.Close()
|
||||
|
||||
return fmt.Errorf("failed to build %s: %w", name, err)
|
||||
}
|
||||
|
||||
c.collectorFns = append(c.collectorFns, subCollectors[name].collect)
|
||||
}
|
||||
@@ -268,3 +344,55 @@ 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)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
if onlyMissingObject(err) {
|
||||
return unavailablePerfCollector[T]{}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func onlyMissingObject(err error) bool {
|
||||
if joined, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
children := joined.Unwrap()
|
||||
if len(children) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, child := range children {
|
||||
if !onlyMissingObject(child) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if wrapped, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return onlyMissingObject(wrapped.Unwrap())
|
||||
}
|
||||
|
||||
return errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject))
|
||||
}
|
||||
|
||||
type unavailablePerfCollector[T any] struct{}
|
||||
|
||||
func (unavailablePerfCollector[T]) Collect(dst *[]T) error {
|
||||
*dst = nil
|
||||
|
||||
return pdh.ErrNoData
|
||||
}
|
||||
|
||||
func (unavailablePerfCollector[T]) Close() {}
|
||||
|
||||
@@ -21,13 +21,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrExceptions() {
|
||||
func (c *Collector) describeClrExceptions() {
|
||||
c.numberOfExceptionsThrown = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrExceptions+"_exceptions_thrown_total"),
|
||||
"Displays the total number of exceptions thrown since the application started. This includes both .NET exceptions and unmanaged exceptions that are converted into .NET exceptions.",
|
||||
@@ -54,20 +52,28 @@ func (c *Collector) buildClrExceptions() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRExceptions struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrExceptions() error {
|
||||
c.describeClrExceptions()
|
||||
|
||||
NumberofExcepsThrown uint32 `mi:"NumberofExcepsThrown"`
|
||||
NumberofExcepsThrownPersec uint32 `mi:"NumberofExcepsThrownPersec"`
|
||||
NumberofFiltersPersec uint32 `mi:"NumberofFiltersPersec"`
|
||||
NumberofFinallysPersec uint32 `mi:"NumberofFinallysPersec"`
|
||||
ThrowToCatchDepthPersec uint32 `mi:"ThrowToCatchDepthPersec"`
|
||||
var err error
|
||||
|
||||
c.perfClrExceptions, err = newPerfCollector[perfDataClrExceptions](c.logger, ".NET CLR Exceptions")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrExceptions(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRExceptions
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRExceptions")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrExceptions struct {
|
||||
Name string
|
||||
NumberofExcepsThrown float64 `perfdata:"# of Exceps Thrown"`
|
||||
NumberofFiltersPersec float64 `perfdata:"# of Filters / sec"`
|
||||
NumberofFinallysPersec float64 `perfdata:"# of Finallys / sec"`
|
||||
ThrowToCatchDepthPersec float64 `perfdata:"Throw To Catch Depth / sec"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrExceptions(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrExceptions
|
||||
if err := c.perfClrExceptions.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR Exceptions: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
|
||||
@@ -21,13 +21,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrInterop() {
|
||||
func (c *Collector) describeClrInterop() {
|
||||
c.numberOfCCWs = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrInterop+"_com_callable_wrappers_total"),
|
||||
"Displays the current number of COM callable wrappers (CCWs). A CCW is a proxy for a managed object being referenced from an unmanaged COM client.",
|
||||
@@ -48,20 +46,27 @@ func (c *Collector) buildClrInterop() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRInterop struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrInterop() error {
|
||||
c.describeClrInterop()
|
||||
|
||||
NumberofCCWs uint32 `mi:"NumberofCCWs"`
|
||||
Numberofmarshalling uint32 `mi:"Numberofmarshalling"`
|
||||
NumberofStubs uint32 `mi:"NumberofStubs"`
|
||||
NumberofTLBexportsPersec uint32 `mi:"NumberofTLBexportsPersec"`
|
||||
NumberofTLBimportsPersec uint32 `mi:"NumberofTLBimportsPersec"`
|
||||
var err error
|
||||
|
||||
c.perfClrInterop, err = newPerfCollector[perfDataClrInterop](c.logger, ".NET CLR Interop")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrInterop(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRInterop
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRInterop")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrInterop struct {
|
||||
Name string
|
||||
NumberofCCWs float64 `perfdata:"# of CCWs"`
|
||||
NumberofStubs float64 `perfdata:"# of Stubs"`
|
||||
Numberofmarshalling float64 `perfdata:"# of marshalling"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrInterop(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrInterop
|
||||
if err := c.perfClrInterop.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR Interop: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
|
||||
@@ -21,13 +21,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrJIT() {
|
||||
func (c *Collector) describeClrJIT() {
|
||||
c.numberOfMethodsJitted = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrJIT+"_jit_methods_total"),
|
||||
"Displays the total number of methods JIT-compiled since the application started. This counter does not include pre-JIT-compiled methods.",
|
||||
@@ -54,22 +52,28 @@ func (c *Collector) buildClrJIT() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRJit struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrJIT() error {
|
||||
c.describeClrJIT()
|
||||
|
||||
Frequency_PerfTime uint64 `mi:"Frequency_PerfTime"`
|
||||
ILBytesJittedPersec uint32 `mi:"ILBytesJittedPersec"`
|
||||
NumberofILBytesJitted uint32 `mi:"NumberofILBytesJitted"`
|
||||
NumberofMethodsJitted uint32 `mi:"NumberofMethodsJitted"`
|
||||
PercentTimeinJit uint32 `mi:"PercentTimeinJit"`
|
||||
StandardJitFailures uint32 `mi:"StandardJitFailures"`
|
||||
TotalNumberofILBytesJitted uint32 `mi:"TotalNumberofILBytesJitted"`
|
||||
var err error
|
||||
|
||||
c.perfClrJIT, err = newPerfCollector[perfDataClrJIT](c.logger, ".NET CLR Jit")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrJIT(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRJit
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRJit")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrJIT struct {
|
||||
Name string
|
||||
NumberofMethodsJitted float64 `perfdata:"# of Methods JITted"`
|
||||
PercentTimeinJit float64 `perfdata:"% Time in Jit"`
|
||||
StandardJitFailures float64 `perfdata:"Standard Jit Failures"`
|
||||
TotalNumberofILBytesJitted float64 `perfdata:"Total # of IL Bytes Jitted"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrJIT(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrJIT
|
||||
if err := c.perfClrJIT.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR Jit: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
@@ -87,7 +91,7 @@ func (c *Collector) collectClrJIT(ch chan<- prometheus.Metric, maxScrapeDuration
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.timeInJit,
|
||||
prometheus.GaugeValue,
|
||||
float64(process.PercentTimeinJit)/float64(process.Frequency_PerfTime),
|
||||
float64(process.PercentTimeinJit)/c.perfFrequency,
|
||||
process.Name,
|
||||
)
|
||||
|
||||
|
||||
@@ -21,13 +21,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrLoading() {
|
||||
func (c *Collector) describeClrLoading() {
|
||||
c.bytesInLoaderHeap = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrLoading+"_loader_heap_size_bytes"),
|
||||
"Displays the current size, in bytes, of the memory committed by the class loader across all application domains. Committed memory is the physical space reserved in the disk paging file.",
|
||||
@@ -84,31 +82,33 @@ func (c *Collector) buildClrLoading() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRLoading struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrLoading() error {
|
||||
c.describeClrLoading()
|
||||
|
||||
AssemblySearchLength uint32 `mi:"AssemblySearchLength"`
|
||||
BytesinLoaderHeap uint64 `mi:"BytesinLoaderHeap"`
|
||||
Currentappdomains uint32 `mi:"Currentappdomains"`
|
||||
CurrentAssemblies uint32 `mi:"CurrentAssemblies"`
|
||||
CurrentClassesLoaded uint32 `mi:"CurrentClassesLoaded"`
|
||||
PercentTimeLoading uint64 `mi:"PercentTimeLoading"`
|
||||
Rateofappdomains uint32 `mi:"Rateofappdomains"`
|
||||
Rateofappdomainsunloaded uint32 `mi:"Rateofappdomainsunloaded"`
|
||||
RateofAssemblies uint32 `mi:"RateofAssemblies"`
|
||||
RateofClassesLoaded uint32 `mi:"RateofClassesLoaded"`
|
||||
RateofLoadFailures uint32 `mi:"RateofLoadFailures"`
|
||||
TotalAppdomains uint32 `mi:"TotalAppdomains"`
|
||||
Totalappdomainsunloaded uint32 `mi:"Totalappdomainsunloaded"`
|
||||
TotalAssemblies uint32 `mi:"TotalAssemblies"`
|
||||
TotalClassesLoaded uint32 `mi:"TotalClassesLoaded"`
|
||||
TotalNumberofLoadFailures uint32 `mi:"TotalNumberofLoadFailures"`
|
||||
var err error
|
||||
|
||||
c.perfClrLoading, err = newPerfCollector[perfDataClrLoading](c.logger, ".NET CLR Loading")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrLoading(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRLoading
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRLoading")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrLoading struct {
|
||||
Name string
|
||||
BytesinLoaderHeap float64 `perfdata:"Bytes in Loader Heap"`
|
||||
CurrentAssemblies float64 `perfdata:"Current Assemblies"`
|
||||
CurrentClassesLoaded float64 `perfdata:"Current Classes Loaded"`
|
||||
Currentappdomains float64 `perfdata:"Current appdomains"`
|
||||
TotalAppdomains float64 `perfdata:"Total Appdomains"`
|
||||
TotalAssemblies float64 `perfdata:"Total Assemblies"`
|
||||
TotalClassesLoaded float64 `perfdata:"Total Classes Loaded"`
|
||||
TotalNumberofLoadFailures float64 `perfdata:"Total # of Load Failures"`
|
||||
Totalappdomainsunloaded float64 `perfdata:"Total appdomains unloaded"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrLoading(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrLoading
|
||||
if err := c.perfClrLoading.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR Loading: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
|
||||
@@ -21,13 +21,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrLocksAndThreads() {
|
||||
func (c *Collector) describeClrLocksAndThreads() {
|
||||
c.currentQueueLength = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrLocksAndThreads+"_current_queue_length"),
|
||||
"Displays the total number of threads that are currently waiting to acquire a managed lock in the application.",
|
||||
@@ -72,25 +70,31 @@ func (c *Collector) buildClrLocksAndThreads() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrLocksAndThreads() error {
|
||||
c.describeClrLocksAndThreads()
|
||||
|
||||
ContentionRatePersec uint32 `mi:"ContentionRatePersec"`
|
||||
CurrentQueueLength uint32 `mi:"CurrentQueueLength"`
|
||||
NumberofcurrentlogicalThreads uint32 `mi:"NumberofcurrentlogicalThreads"`
|
||||
NumberofcurrentphysicalThreads uint32 `mi:"NumberofcurrentphysicalThreads"`
|
||||
Numberofcurrentrecognizedthreads uint32 `mi:"Numberofcurrentrecognizedthreads"`
|
||||
Numberoftotalrecognizedthreads uint32 `mi:"Numberoftotalrecognizedthreads"`
|
||||
QueueLengthPeak uint32 `mi:"QueueLengthPeak"`
|
||||
QueueLengthPersec uint32 `mi:"QueueLengthPersec"`
|
||||
RateOfRecognizedThreadsPersec uint32 `mi:"RateOfRecognizedThreadsPersec"`
|
||||
TotalNumberofContentions uint32 `mi:"TotalNumberofContentions"`
|
||||
var err error
|
||||
|
||||
c.perfClrLocksAndThreads, err = newPerfCollector[perfDataClrLocksAndThreads](c.logger, ".NET CLR LocksAndThreads")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrLocksAndThreads(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrLocksAndThreads struct {
|
||||
Name string
|
||||
CurrentQueueLength float64 `perfdata:"Current Queue Length"`
|
||||
NumberofcurrentlogicalThreads float64 `perfdata:"# of current logical Threads"`
|
||||
NumberofcurrentphysicalThreads float64 `perfdata:"# of current physical Threads"`
|
||||
Numberofcurrentrecognizedthreads float64 `perfdata:"# of current recognized threads"`
|
||||
Numberoftotalrecognizedthreads float64 `perfdata:"# of total recognized threads"`
|
||||
QueueLengthPeak float64 `perfdata:"Queue Length Peak"`
|
||||
TotalNumberofContentions float64 `perfdata:"Total # of Contentions"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrLocksAndThreads(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrLocksAndThreads
|
||||
if err := c.perfClrLocksAndThreads.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR LocksAndThreads: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
|
||||
@@ -22,13 +22,11 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrMemory() {
|
||||
func (c *Collector) describeClrMemory() {
|
||||
c.allocatedBytes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrMemory+"_allocated_bytes_total"),
|
||||
"Displays the total number of bytes allocated on the garbage collection heap.",
|
||||
@@ -103,44 +101,44 @@ func (c *Collector) buildClrMemory() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRMemory struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrMemory() error {
|
||||
c.describeClrMemory()
|
||||
|
||||
AllocatedBytesPersec uint64 `mi:"AllocatedBytesPersec"`
|
||||
FinalizationSurvivors uint64 `mi:"FinalizationSurvivors"`
|
||||
Frequency_PerfTime uint64 `mi:"Frequency_PerfTime"`
|
||||
Gen0heapsize uint64 `mi:"Gen0heapsize"`
|
||||
Gen0PromotedBytesPerSec uint64 `mi:"Gen0PromotedBytesPersec"`
|
||||
Gen1heapsize uint64 `mi:"Gen1heapsize"`
|
||||
Gen1PromotedBytesPerSec uint64 `mi:"Gen1PromotedBytesPersec"`
|
||||
Gen2heapsize uint64 `mi:"Gen2heapsize"`
|
||||
LargeObjectHeapsize uint64 `mi:"LargeObjectHeapsize"`
|
||||
NumberBytesinallHeaps uint64 `mi:"NumberBytesinallHeaps"`
|
||||
NumberGCHandles uint64 `mi:"NumberGCHandles"`
|
||||
NumberGen0Collections uint64 `mi:"NumberGen0Collections"`
|
||||
NumberGen1Collections uint64 `mi:"NumberGen1Collections"`
|
||||
NumberGen2Collections uint64 `mi:"NumberGen2Collections"`
|
||||
NumberInducedGC uint64 `mi:"NumberInducedGC"`
|
||||
NumberofPinnedObjects uint64 `mi:"NumberofPinnedObjects"`
|
||||
NumberofSinkBlocksinuse uint64 `mi:"NumberofSinkBlocksinuse"`
|
||||
NumberTotalcommittedBytes uint64 `mi:"NumberTotalcommittedBytes"`
|
||||
NumberTotalreservedBytes uint64 `mi:"NumberTotalreservedBytes"`
|
||||
// PercentTimeinGC has countertype=PERF_RAW_FRACTION.
|
||||
// Formula: (100 * CounterValue) / BaseValue
|
||||
// By docs https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/scripting-articles/ms974615(v=msdn.10)#perf_raw_fraction
|
||||
PercentTimeinGC uint32 `mi:"PercentTimeinGC"`
|
||||
// BaseValue is just a "magic" number used to make the calculation come out right.
|
||||
PercentTimeinGC_base uint32 `mi:"PercentTimeinGC_base"`
|
||||
ProcessID uint64 `mi:"ProcessID"`
|
||||
PromotedFinalizationMemoryfromGen0 uint64 `mi:"PromotedFinalizationMemoryfromGen0"`
|
||||
PromotedMemoryfromGen0 uint64 `mi:"PromotedMemoryfromGen0"`
|
||||
PromotedMemoryfromGen1 uint64 `mi:"PromotedMemoryfromGen1"`
|
||||
var err error
|
||||
|
||||
c.perfClrMemory, err = newPerfCollector[perfDataClrMemory](c.logger, ".NET CLR Memory")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRMemory
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRMemory")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrMemory struct {
|
||||
Name string
|
||||
AllocatedBytesPersec float64 `perfdata:"Allocated Bytes/sec"`
|
||||
FinalizationSurvivors float64 `perfdata:"Finalization Survivors"`
|
||||
Gen0PromotedBytesPerSec float64 `perfdata:"Gen 0 Promoted Bytes/Sec"`
|
||||
Gen0heapsize float64 `perfdata:"Gen 0 heap size"`
|
||||
Gen1PromotedBytesPerSec float64 `perfdata:"Gen 1 Promoted Bytes/Sec"`
|
||||
Gen1heapsize float64 `perfdata:"Gen 1 heap size"`
|
||||
Gen2heapsize float64 `perfdata:"Gen 2 heap size"`
|
||||
LargeObjectHeapsize float64 `perfdata:"Large Object Heap size"`
|
||||
NumberGCHandles float64 `perfdata:"# GC Handles"`
|
||||
NumberGen0Collections float64 `perfdata:"# Gen 0 Collections"`
|
||||
NumberGen1Collections float64 `perfdata:"# Gen 1 Collections"`
|
||||
NumberGen2Collections float64 `perfdata:"# Gen 2 Collections"`
|
||||
NumberInducedGC float64 `perfdata:"# Induced GC"`
|
||||
NumberTotalcommittedBytes float64 `perfdata:"# Total committed Bytes"`
|
||||
NumberTotalreservedBytes float64 `perfdata:"# Total reserved Bytes"`
|
||||
NumberofPinnedObjects float64 `perfdata:"# of Pinned Objects"`
|
||||
NumberofSinkBlocksinuse float64 `perfdata:"# of Sink Blocks in use"`
|
||||
PercentTimeinGC float64 `perfdata:"% Time in GC"`
|
||||
PercentTimeinGC_base float64 `perfdata:"% Time in GC,secondvalue"`
|
||||
ProcessID float64 `perfdata:"Process ID"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrMemory
|
||||
if err := c.perfClrMemory.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR Memory: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
@@ -153,7 +151,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.CounterValue,
|
||||
float64(process.AllocatedBytesPersec),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
@@ -161,7 +159,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.FinalizationSurvivors),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
@@ -169,7 +167,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.Gen0heapsize),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen0",
|
||||
)
|
||||
|
||||
@@ -178,7 +176,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.Gen0PromotedBytesPerSec),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen0",
|
||||
)
|
||||
|
||||
@@ -187,7 +185,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.Gen1heapsize),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen1",
|
||||
)
|
||||
|
||||
@@ -196,7 +194,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.Gen1PromotedBytesPerSec),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen1",
|
||||
)
|
||||
|
||||
@@ -205,7 +203,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.Gen2heapsize),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen2",
|
||||
)
|
||||
|
||||
@@ -214,7 +212,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.LargeObjectHeapsize),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"LOH",
|
||||
)
|
||||
|
||||
@@ -223,7 +221,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.NumberGCHandles),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
@@ -231,7 +229,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.CounterValue,
|
||||
float64(process.NumberGen0Collections),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen0",
|
||||
)
|
||||
|
||||
@@ -240,7 +238,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.CounterValue,
|
||||
float64(process.NumberGen1Collections),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen1",
|
||||
)
|
||||
|
||||
@@ -249,7 +247,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.CounterValue,
|
||||
float64(process.NumberGen2Collections),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
"Gen2",
|
||||
)
|
||||
|
||||
@@ -258,7 +256,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.CounterValue,
|
||||
float64(process.NumberInducedGC),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
@@ -266,7 +264,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.NumberofPinnedObjects),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
@@ -274,7 +272,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.NumberofSinkBlocksinuse),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
@@ -282,7 +280,7 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.NumberTotalcommittedBytes),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
@@ -290,15 +288,19 @@ func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, maxScrapeDurat
|
||||
prometheus.GaugeValue,
|
||||
float64(process.NumberTotalreservedBytes),
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
|
||||
if process.PercentTimeinGC_base <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.timeInGC,
|
||||
prometheus.GaugeValue,
|
||||
float64(100*process.PercentTimeinGC)/float64(process.PercentTimeinGC_base),
|
||||
100*process.PercentTimeinGC/process.PercentTimeinGC_base,
|
||||
process.Name,
|
||||
strconv.FormatUint(process.ProcessID, 10),
|
||||
strconv.FormatUint(uint64(process.ProcessID), 10),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrRemoting() {
|
||||
func (c *Collector) describeClrRemoting() {
|
||||
c.channels = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrRemoting+"_channels_total"),
|
||||
"Displays the total number of remoting channels registered across all application domains since application started.",
|
||||
@@ -66,22 +64,30 @@ func (c *Collector) buildClrRemoting() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRRemoting struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrRemoting() error {
|
||||
c.describeClrRemoting()
|
||||
|
||||
Channels uint32 `mi:"Channels"`
|
||||
ContextBoundClassesLoaded uint32 `mi:"ContextBoundClassesLoaded"`
|
||||
ContextBoundObjectsAllocPersec uint32 `mi:"ContextBoundObjectsAllocPersec"`
|
||||
ContextProxies uint32 `mi:"ContextProxies"`
|
||||
Contexts uint32 `mi:"Contexts"`
|
||||
RemoteCallsPersec uint32 `mi:"RemoteCallsPersec"`
|
||||
TotalRemoteCalls uint32 `mi:"TotalRemoteCalls"`
|
||||
var err error
|
||||
|
||||
c.perfClrRemoting, err = newPerfCollector[perfDataClrRemoting](c.logger, ".NET CLR Remoting")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrRemoting(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRRemoting
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRRemoting")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrRemoting struct {
|
||||
Name string
|
||||
Channels float64 `perfdata:"Channels"`
|
||||
ContextBoundClassesLoaded float64 `perfdata:"Context-Bound Classes Loaded"`
|
||||
ContextBoundObjectsAllocPersec float64 `perfdata:"Context-Bound Objects Alloc / sec"`
|
||||
ContextProxies float64 `perfdata:"Context Proxies"`
|
||||
Contexts float64 `perfdata:"Contexts"`
|
||||
TotalRemoteCalls float64 `perfdata:"Total Remote Calls"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrRemoting(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrRemoting
|
||||
if err := c.perfClrRemoting.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR Remoting: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
|
||||
@@ -21,13 +21,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *Collector) buildClrSecurity() {
|
||||
func (c *Collector) describeClrSecurity() {
|
||||
c.numberLinkTimeChecks = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, collectorClrSecurity+"_link_time_checks_total"),
|
||||
"Displays the total number of link-time code access security checks since the application started.",
|
||||
@@ -54,21 +52,28 @@ func (c *Collector) buildClrSecurity() {
|
||||
)
|
||||
}
|
||||
|
||||
type Win32_PerfRawData_NETFramework_NETCLRSecurity struct {
|
||||
Name string `mi:"Name"`
|
||||
func (c *Collector) buildClrSecurity() error {
|
||||
c.describeClrSecurity()
|
||||
|
||||
Frequency_PerfTime uint64 `mi:"Frequency_PerfTime"`
|
||||
NumberLinkTimeChecks uint32 `mi:"NumberLinkTimeChecks"`
|
||||
PercentTimeinRTchecks uint32 `mi:"PercentTimeinRTchecks"`
|
||||
PercentTimeSigAuthenticating uint64 `mi:"PercentTimeSigAuthenticating"`
|
||||
StackWalkDepth uint32 `mi:"StackWalkDepth"`
|
||||
TotalRuntimeChecks uint32 `mi:"TotalRuntimeChecks"`
|
||||
var err error
|
||||
|
||||
c.perfClrSecurity, err = newPerfCollector[perfDataClrSecurity](c.logger, ".NET CLR Security")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrSecurity(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
var dst []Win32_PerfRawData_NETFramework_NETCLRSecurity
|
||||
if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRSecurity")), maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
type perfDataClrSecurity struct {
|
||||
Name string
|
||||
NumberLinkTimeChecks float64 `perfdata:"# Link Time Checks"`
|
||||
PercentTimeinRTchecks float64 `perfdata:"% Time in RT checks"`
|
||||
StackWalkDepth float64 `perfdata:"Stack Walk Depth"`
|
||||
TotalRuntimeChecks float64 `perfdata:"Total Runtime Checks"`
|
||||
}
|
||||
|
||||
func (c *Collector) collectClrSecurity(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
var dst []perfDataClrSecurity
|
||||
if err := c.perfClrSecurity.Collect(&dst); err != nil {
|
||||
return fmt.Errorf("failed to collect .NET CLR Security: %w", err)
|
||||
}
|
||||
|
||||
for _, process := range dst {
|
||||
@@ -86,7 +91,7 @@ func (c *Collector) collectClrSecurity(ch chan<- prometheus.Metric, maxScrapeDur
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.timeInRTChecks,
|
||||
prometheus.GaugeValue,
|
||||
float64(process.PercentTimeinRTchecks)/float64(process.Frequency_PerfTime),
|
||||
float64(process.PercentTimeinRTchecks)/c.perfFrequency,
|
||||
process.Name,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package netframework
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// These paths are independently listed by Netdata's native Windows perflib
|
||||
// collector and Mono's CLR counter definitions. Remoting is absent from the
|
||||
// current Microsoft .NET Framework performance-counter documentation.
|
||||
// https://github.com/netdata/netdata/blob/master/src/collectors/windows.plugin/perflib-netframework.c
|
||||
// https://github.com/mono/mono/blob/main/mono/metadata/mono-perfcounters-def.h
|
||||
func TestClrRemotingCounterPaths(t *testing.T) {
|
||||
expected := map[string]string{
|
||||
"Channels": "Channels",
|
||||
"ContextBoundClassesLoaded": "Context-Bound Classes Loaded",
|
||||
"ContextBoundObjectsAllocPersec": "Context-Bound Objects Alloc / sec",
|
||||
"ContextProxies": "Context Proxies",
|
||||
"Contexts": "Contexts",
|
||||
"TotalRemoteCalls": "Total Remote Calls",
|
||||
}
|
||||
rowType := reflect.TypeFor[perfDataClrRemoting]()
|
||||
require.Equal(t, len(expected)+1, rowType.NumField())
|
||||
|
||||
for field, counter := range expected {
|
||||
f, found := rowType.FieldByName(field)
|
||||
require.True(t, found, field)
|
||||
require.Equal(t, counter, f.Tag.Get("perfdata"), field)
|
||||
}
|
||||
}
|
||||
|
||||
// Counter names also appear in the Windows counter inventory captured by:
|
||||
// https://gist.github.com/hdansou/040207527657de0225b68912ff94400e
|
||||
// Memory names are corroborated by Mono's primary CLR counter definitions.
|
||||
func TestClrCounterPaths(t *testing.T) {
|
||||
cases := []struct {
|
||||
rowType reflect.Type
|
||||
counters []string
|
||||
}{
|
||||
{reflect.TypeFor[perfDataClrExceptions](), []string{"# of Exceps Thrown", "# of Filters / sec", "# of Finallys / sec", "Throw To Catch Depth / sec"}},
|
||||
{reflect.TypeFor[perfDataClrInterop](), []string{"# of CCWs", "# of Stubs", "# of marshalling"}},
|
||||
{reflect.TypeFor[perfDataClrJIT](), []string{"# of Methods Jitted", "% Time in Jit", "Standard Jit Failures", "Total # of IL Bytes Jitted"}},
|
||||
{reflect.TypeFor[perfDataClrLoading](), []string{"Bytes in Loader Heap", "Current appdomains", "Current Assemblies", "Current Classes Loaded", "Total Appdomains", "Total appdomains unloaded", "Total Assemblies", "Total Classes Loaded", "Total # of Load Failures"}},
|
||||
{reflect.TypeFor[perfDataClrLocksAndThreads](), []string{"Current Queue Length", "# of current logical Threads", "# of current physical Threads", "# of current recognized threads", "# of total recognized threads", "Queue Length Peak", "Total # of Contentions"}},
|
||||
{reflect.TypeFor[perfDataClrMemory](), []string{"Allocated Bytes/sec", "Finalization Survivors", "Gen 0 heap size", "Gen 0 Promoted Bytes/Sec", "Gen 1 heap size", "Gen 1 Promoted Bytes/Sec", "Gen 2 heap size", "Large Object Heap size", "# GC Handles", "# Gen 0 Collections", "# Gen 1 Collections", "# Gen 2 Collections", "# Induced GC", "# of Pinned Objects", "# of Sink Blocks in use", "# Total committed Bytes", "# Total reserved Bytes", "% Time in GC", "% Time in GC,secondvalue", "Process ID"}},
|
||||
{reflect.TypeFor[perfDataClrRemoting](), []string{"Channels", "Context-Bound Classes Loaded", "Context-Bound Objects Alloc / sec", "Context Proxies", "Contexts", "Total Remote Calls"}},
|
||||
{reflect.TypeFor[perfDataClrSecurity](), []string{"# Link Time Checks", "% Time in RT checks", "Stack Walk Depth", "Total Runtime Checks"}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.rowType.Name(), func(t *testing.T) {
|
||||
actual := make([]string, 0, tc.rowType.NumField()-1)
|
||||
for field := range tc.rowType.Fields() {
|
||||
if tag, found := field.Tag.Lookup("perfdata"); found {
|
||||
actual = append(actual, strings.ToLower(tag))
|
||||
}
|
||||
}
|
||||
|
||||
expected := make([]string, len(tc.counters))
|
||||
for i, counter := range tc.counters {
|
||||
expected[i] = strings.ToLower(counter)
|
||||
}
|
||||
|
||||
require.ElementsMatch(t, expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package netframework
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fixturePerfCollector[T any] struct {
|
||||
rows []T
|
||||
err error
|
||||
closed int
|
||||
}
|
||||
|
||||
func (c *fixturePerfCollector[T]) Collect(dst *[]T) error {
|
||||
*dst = append((*dst)[:0], c.rows...)
|
||||
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *fixturePerfCollector[T]) Close() { c.closed++ }
|
||||
|
||||
type fixturePromCollector struct {
|
||||
collect func(chan<- prometheus.Metric)
|
||||
}
|
||||
|
||||
func (fixturePromCollector) Describe(chan<- *prometheus.Desc) {}
|
||||
func (c fixturePromCollector) Collect(ch chan<- prometheus.Metric) { c.collect(ch) }
|
||||
|
||||
type expectedMetric struct {
|
||||
name string
|
||||
metricType dto.MetricType
|
||||
value float64
|
||||
area string
|
||||
}
|
||||
|
||||
func assertFixtureMetrics(t *testing.T, collect func(chan<- prometheus.Metric, time.Duration) error, expected []expectedMetric, withPID bool) {
|
||||
t.Helper()
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
var collectErr error
|
||||
|
||||
registry.MustRegister(fixturePromCollector{collect: func(ch chan<- prometheus.Metric) { collectErr = collect(ch, time.Second) }})
|
||||
families, err := registry.Gather()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, collectErr)
|
||||
|
||||
wanted := make(map[string]expectedMetric)
|
||||
|
||||
for _, process := range []string{"worker", "worker#1", "report_Total"} {
|
||||
for _, metric := range expected {
|
||||
wanted[metric.name+"|"+process+"|"+metric.area] = metric
|
||||
}
|
||||
}
|
||||
|
||||
gotCount := 0
|
||||
|
||||
for _, family := range families {
|
||||
for _, metric := range family.GetMetric() {
|
||||
labels := make(map[string]string)
|
||||
for _, label := range metric.GetLabel() {
|
||||
labels[label.GetName()] = label.GetValue()
|
||||
}
|
||||
|
||||
key := family.GetName() + "|" + labels["process"] + "|" + labels["area"]
|
||||
want, ok := wanted[key]
|
||||
require.True(t, ok, "unexpected metric %s", key)
|
||||
require.Equal(t, want.metricType, family.GetType(), key)
|
||||
|
||||
value := metric.GetGauge().GetValue()
|
||||
if family.GetType() == dto.MetricType_COUNTER {
|
||||
value = metric.GetCounter().GetValue()
|
||||
}
|
||||
|
||||
require.InDelta(t, want.value, value, 1e-12, key)
|
||||
|
||||
labelCount := 1
|
||||
|
||||
if withPID {
|
||||
require.Equal(t, "123", labels["process_id"])
|
||||
|
||||
labelCount++
|
||||
}
|
||||
|
||||
if want.area != "" {
|
||||
labelCount++
|
||||
}
|
||||
|
||||
require.Len(t, labels, labelCount)
|
||||
delete(wanted, key)
|
||||
|
||||
gotCount++
|
||||
}
|
||||
}
|
||||
|
||||
require.Empty(t, wanted)
|
||||
require.Equal(t, len(expected)*3, gotCount)
|
||||
}
|
||||
|
||||
func TestClrExceptionsMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrExceptions()
|
||||
|
||||
row := perfDataClrExceptions{
|
||||
NumberofExcepsThrown: 11,
|
||||
NumberofFiltersPersec: 12,
|
||||
NumberofFinallysPersec: 13,
|
||||
ThrowToCatchDepthPersec: 14,
|
||||
}
|
||||
rows := make([]perfDataClrExceptions, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrExceptions = &fixturePerfCollector[perfDataClrExceptions]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrExceptions, []expectedMetric{
|
||||
{"windows_netframework_clrexceptions_exceptions_thrown_total", dto.MetricType_COUNTER, 11, ""},
|
||||
{"windows_netframework_clrexceptions_exceptions_filters_total", dto.MetricType_COUNTER, 12, ""},
|
||||
{"windows_netframework_clrexceptions_exceptions_finallys_total", dto.MetricType_COUNTER, 13, ""},
|
||||
{"windows_netframework_clrexceptions_throw_to_catch_depth_total", dto.MetricType_COUNTER, 14, ""},
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestClrInteropMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrInterop()
|
||||
|
||||
row := perfDataClrInterop{
|
||||
NumberofCCWs: 11,
|
||||
NumberofStubs: 12,
|
||||
Numberofmarshalling: 13,
|
||||
}
|
||||
rows := make([]perfDataClrInterop, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrInterop = &fixturePerfCollector[perfDataClrInterop]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrInterop, []expectedMetric{
|
||||
{"windows_netframework_clrinterop_com_callable_wrappers_total", dto.MetricType_COUNTER, 11, ""},
|
||||
{"windows_netframework_clrinterop_interop_marshalling_total", dto.MetricType_COUNTER, 13, ""},
|
||||
{"windows_netframework_clrinterop_interop_stubs_created_total", dto.MetricType_COUNTER, 12, ""},
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestClrJITMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrJIT()
|
||||
|
||||
row := perfDataClrJIT{
|
||||
NumberofMethodsJitted: 11,
|
||||
PercentTimeinJit: 50,
|
||||
StandardJitFailures: 13,
|
||||
TotalNumberofILBytesJitted: 14,
|
||||
}
|
||||
rows := make([]perfDataClrJIT, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrJIT = &fixturePerfCollector[perfDataClrJIT]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrJIT, []expectedMetric{
|
||||
{"windows_netframework_clrjit_jit_methods_total", dto.MetricType_COUNTER, 11, ""},
|
||||
{"windows_netframework_clrjit_jit_time_percent", dto.MetricType_GAUGE, 0.05, ""},
|
||||
{"windows_netframework_clrjit_jit_standard_failures_total", dto.MetricType_GAUGE, 13, ""},
|
||||
{"windows_netframework_clrjit_jit_il_bytes_total", dto.MetricType_COUNTER, 14, ""},
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestClrLoadingMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrLoading()
|
||||
|
||||
row := perfDataClrLoading{
|
||||
BytesinLoaderHeap: 11,
|
||||
CurrentAssemblies: 12,
|
||||
CurrentClassesLoaded: 13,
|
||||
Currentappdomains: 14,
|
||||
TotalAppdomains: 15,
|
||||
TotalAssemblies: 16,
|
||||
TotalClassesLoaded: 17,
|
||||
TotalNumberofLoadFailures: 18,
|
||||
Totalappdomainsunloaded: 19,
|
||||
}
|
||||
rows := make([]perfDataClrLoading, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrLoading = &fixturePerfCollector[perfDataClrLoading]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrLoading, []expectedMetric{
|
||||
{"windows_netframework_clrloading_loader_heap_size_bytes", dto.MetricType_GAUGE, 11, ""},
|
||||
{"windows_netframework_clrloading_appdomains_loaded_current", dto.MetricType_GAUGE, 14, ""},
|
||||
{"windows_netframework_clrloading_assemblies_loaded_current", dto.MetricType_GAUGE, 12, ""},
|
||||
{"windows_netframework_clrloading_classes_loaded_current", dto.MetricType_GAUGE, 13, ""},
|
||||
{"windows_netframework_clrloading_appdomains_loaded_total", dto.MetricType_COUNTER, 15, ""},
|
||||
{"windows_netframework_clrloading_appdomains_unloaded_total", dto.MetricType_COUNTER, 19, ""},
|
||||
{"windows_netframework_clrloading_assemblies_loaded_total", dto.MetricType_COUNTER, 16, ""},
|
||||
{"windows_netframework_clrloading_classes_loaded_total", dto.MetricType_COUNTER, 17, ""},
|
||||
{"windows_netframework_clrloading_class_load_failures_total", dto.MetricType_COUNTER, 18, ""},
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestClrLocksAndThreadsMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrLocksAndThreads()
|
||||
|
||||
row := perfDataClrLocksAndThreads{
|
||||
CurrentQueueLength: 11,
|
||||
NumberofcurrentlogicalThreads: 12,
|
||||
NumberofcurrentphysicalThreads: 13,
|
||||
Numberofcurrentrecognizedthreads: 14,
|
||||
Numberoftotalrecognizedthreads: 15,
|
||||
QueueLengthPeak: 16,
|
||||
TotalNumberofContentions: 17,
|
||||
}
|
||||
rows := make([]perfDataClrLocksAndThreads, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrLocksAndThreads = &fixturePerfCollector[perfDataClrLocksAndThreads]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrLocksAndThreads, []expectedMetric{
|
||||
{"windows_netframework_clrlocksandthreads_current_queue_length", dto.MetricType_GAUGE, 11, ""},
|
||||
{"windows_netframework_clrlocksandthreads_current_logical_threads", dto.MetricType_GAUGE, 12, ""},
|
||||
{"windows_netframework_clrlocksandthreads_physical_threads_current", dto.MetricType_GAUGE, 13, ""},
|
||||
{"windows_netframework_clrlocksandthreads_recognized_threads_current", dto.MetricType_GAUGE, 14, ""},
|
||||
{"windows_netframework_clrlocksandthreads_recognized_threads_total", dto.MetricType_COUNTER, 15, ""},
|
||||
{"windows_netframework_clrlocksandthreads_queue_length_total", dto.MetricType_COUNTER, 16, ""},
|
||||
{"windows_netframework_clrlocksandthreads_contentions_total", dto.MetricType_COUNTER, 17, ""},
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestClrMemoryMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrMemory()
|
||||
|
||||
row := perfDataClrMemory{
|
||||
AllocatedBytesPersec: 11,
|
||||
FinalizationSurvivors: 12,
|
||||
Gen0PromotedBytesPerSec: 13,
|
||||
Gen0heapsize: 14,
|
||||
Gen1PromotedBytesPerSec: 15,
|
||||
Gen1heapsize: 16,
|
||||
Gen2heapsize: 17,
|
||||
LargeObjectHeapsize: 18,
|
||||
NumberGCHandles: 19,
|
||||
NumberGen0Collections: 20,
|
||||
NumberGen1Collections: 21,
|
||||
NumberGen2Collections: 22,
|
||||
NumberInducedGC: 23,
|
||||
NumberTotalcommittedBytes: 24,
|
||||
NumberTotalreservedBytes: 25,
|
||||
NumberofPinnedObjects: 26,
|
||||
NumberofSinkBlocksinuse: 27,
|
||||
PercentTimeinGC: 50,
|
||||
PercentTimeinGC_base: 200,
|
||||
ProcessID: 123,
|
||||
}
|
||||
rows := make([]perfDataClrMemory, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrMemory = &fixturePerfCollector[perfDataClrMemory]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrMemory, []expectedMetric{
|
||||
{"windows_netframework_clrmemory_allocated_bytes_total", dto.MetricType_COUNTER, 11, ""},
|
||||
{"windows_netframework_clrmemory_finalization_survivors", dto.MetricType_GAUGE, 12, ""},
|
||||
{"windows_netframework_clrmemory_heap_size_bytes", dto.MetricType_GAUGE, 14, "Gen0"},
|
||||
{"windows_netframework_clrmemory_promoted_bytes", dto.MetricType_GAUGE, 13, "Gen0"},
|
||||
{"windows_netframework_clrmemory_heap_size_bytes", dto.MetricType_GAUGE, 16, "Gen1"},
|
||||
{"windows_netframework_clrmemory_promoted_bytes", dto.MetricType_GAUGE, 15, "Gen1"},
|
||||
{"windows_netframework_clrmemory_heap_size_bytes", dto.MetricType_GAUGE, 17, "Gen2"},
|
||||
{"windows_netframework_clrmemory_heap_size_bytes", dto.MetricType_GAUGE, 18, "LOH"},
|
||||
{"windows_netframework_clrmemory_number_gc_handles", dto.MetricType_GAUGE, 19, ""},
|
||||
{"windows_netframework_clrmemory_collections_total", dto.MetricType_COUNTER, 20, "Gen0"},
|
||||
{"windows_netframework_clrmemory_collections_total", dto.MetricType_COUNTER, 21, "Gen1"},
|
||||
{"windows_netframework_clrmemory_collections_total", dto.MetricType_COUNTER, 22, "Gen2"},
|
||||
{"windows_netframework_clrmemory_induced_gc_total", dto.MetricType_COUNTER, 23, ""},
|
||||
{"windows_netframework_clrmemory_number_pinned_objects", dto.MetricType_GAUGE, 26, ""},
|
||||
{"windows_netframework_clrmemory_number_sink_blocksinuse", dto.MetricType_GAUGE, 27, ""},
|
||||
{"windows_netframework_clrmemory_committed_bytes", dto.MetricType_GAUGE, 24, ""},
|
||||
{"windows_netframework_clrmemory_reserved_bytes", dto.MetricType_GAUGE, 25, ""},
|
||||
{"windows_netframework_clrmemory_gc_time_percent", dto.MetricType_GAUGE, 25, ""},
|
||||
}, true)
|
||||
}
|
||||
|
||||
func TestClrRemotingMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrRemoting()
|
||||
|
||||
row := perfDataClrRemoting{
|
||||
Channels: 11,
|
||||
ContextBoundClassesLoaded: 12,
|
||||
ContextBoundObjectsAllocPersec: 13,
|
||||
ContextProxies: 14,
|
||||
Contexts: 15,
|
||||
TotalRemoteCalls: 16,
|
||||
}
|
||||
rows := make([]perfDataClrRemoting, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrRemoting = &fixturePerfCollector[perfDataClrRemoting]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrRemoting, []expectedMetric{
|
||||
{"windows_netframework_clrremoting_channels_total", dto.MetricType_COUNTER, 11, ""},
|
||||
{"windows_netframework_clrremoting_context_bound_classes_loaded", dto.MetricType_GAUGE, 12, ""},
|
||||
{"windows_netframework_clrremoting_context_bound_objects_total", dto.MetricType_COUNTER, 13, ""},
|
||||
{"windows_netframework_clrremoting_context_proxies_total", dto.MetricType_COUNTER, 14, ""},
|
||||
{"windows_netframework_clrremoting_contexts", dto.MetricType_GAUGE, 15, ""},
|
||||
{"windows_netframework_clrremoting_remote_calls_total", dto.MetricType_COUNTER, 16, ""},
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestClrSecurityMetricCompatibility(t *testing.T) {
|
||||
c := &Collector{perfFrequency: 1000}
|
||||
c.describeClrSecurity()
|
||||
|
||||
row := perfDataClrSecurity{
|
||||
NumberLinkTimeChecks: 11,
|
||||
PercentTimeinRTchecks: 50,
|
||||
StackWalkDepth: 13,
|
||||
TotalRuntimeChecks: 14,
|
||||
}
|
||||
rows := make([]perfDataClrSecurity, 0, 4)
|
||||
|
||||
for _, name := range []string{"_Global_", "worker", "worker#1", "report_Total"} {
|
||||
row.Name = name
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
c.perfClrSecurity = &fixturePerfCollector[perfDataClrSecurity]{rows: rows}
|
||||
assertFixtureMetrics(t, c.collectClrSecurity, []expectedMetric{
|
||||
{"windows_netframework_clrsecurity_link_time_checks_total", dto.MetricType_COUNTER, 11, ""},
|
||||
{"windows_netframework_clrsecurity_rt_checks_time_percent", dto.MetricType_GAUGE, 0.05, ""},
|
||||
{"windows_netframework_clrsecurity_stack_walk_depth", dto.MetricType_GAUGE, 13, ""},
|
||||
{"windows_netframework_clrsecurity_runtime_checks_total", dto.MetricType_COUNTER, 14, ""},
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestGCZeroBaseDoesNotPublishInvalidRatio(t *testing.T) {
|
||||
c := &Collector{}
|
||||
c.describeClrMemory()
|
||||
c.perfClrMemory = &fixturePerfCollector[perfDataClrMemory]{rows: []perfDataClrMemory{{Name: "worker", ProcessID: 123, AllocatedBytesPersec: 99}}}
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(fixturePromCollector{collect: func(ch chan<- prometheus.Metric) { require.NoError(t, c.collectClrMemory(ch, time.Second)) }})
|
||||
families, err := registry.Gather()
|
||||
require.NoError(t, err)
|
||||
|
||||
foundAllocated := false
|
||||
|
||||
for _, family := range families {
|
||||
require.NotEqual(t, "windows_netframework_clrmemory_gc_time_percent", family.GetName())
|
||||
|
||||
if family.GetName() == "windows_netframework_clrmemory_allocated_bytes_total" {
|
||||
require.InDelta(t, 99.0, family.GetMetric()[0].GetCounter().GetValue(), 1e-12)
|
||||
|
||||
foundAllocated = true
|
||||
}
|
||||
}
|
||||
|
||||
require.True(t, foundAllocated)
|
||||
}
|
||||
|
||||
func TestMissingObjectErrorClassification(t *testing.T) {
|
||||
missing := pdh.NewPdhError(pdh.CstatusNoObject)
|
||||
unrelated := errors.New("access denied")
|
||||
|
||||
require.True(t, onlyMissingObject(fmt.Errorf("initialization: %w", errors.Join(missing, missing))))
|
||||
require.False(t, onlyMissingObject(errors.Join(missing, unrelated)))
|
||||
require.False(t, onlyMissingObject(fmt.Errorf("initialization: %w", errors.Join(missing, unrelated))))
|
||||
require.False(t, onlyMissingObject(pdh.NewPdhError(pdh.CstatusNoCounter)))
|
||||
}
|
||||
|
||||
func TestNoDataIsReturned(t *testing.T) {
|
||||
c := &Collector{}
|
||||
c.describeClrExceptions()
|
||||
c.perfClrExceptions = unavailablePerfCollector[perfDataClrExceptions]{}
|
||||
ch := make(chan prometheus.Metric, 16)
|
||||
require.ErrorIs(t, c.collectClrExceptions(ch, time.Second), pdh.ErrNoData)
|
||||
require.Empty(t, ch)
|
||||
}
|
||||
|
||||
func TestCloseReleasesInitializedCollectorOnce(t *testing.T) {
|
||||
c := &Collector{}
|
||||
fixture := &fixturePerfCollector[perfDataClrExceptions]{}
|
||||
c.closeFns = []func(){func() {
|
||||
if c.perfClrExceptions != nil {
|
||||
c.perfClrExceptions.Close()
|
||||
}
|
||||
}}
|
||||
c.perfClrExceptions = fixture
|
||||
require.NoError(t, c.Close())
|
||||
require.NoError(t, c.Close())
|
||||
require.Equal(t, 1, fixture.closed)
|
||||
}
|
||||
|
||||
func BenchmarkClrMemoryMetricPublication(b *testing.B) {
|
||||
c := &Collector{}
|
||||
c.describeClrMemory()
|
||||
|
||||
rows := make([]perfDataClrMemory, 100)
|
||||
for i := range rows {
|
||||
rows[i] = perfDataClrMemory{Name: fmt.Sprintf("worker#%d", i), ProcessID: float64(i), PercentTimeinGC: 50, PercentTimeinGC_base: 200}
|
||||
}
|
||||
|
||||
c.perfClrMemory = &fixturePerfCollector[perfDataClrMemory]{rows: rows}
|
||||
ch := make(chan prometheus.Metric, 2100)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for b.Loop() {
|
||||
if err := c.collectClrMemory(ch, time.Second); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
for len(ch) != 0 {
|
||||
<-ch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectRetainsSuccessfulChildMetrics(t *testing.T) {
|
||||
c := &Collector{}
|
||||
c.describeClrExceptions()
|
||||
c.describeClrInterop()
|
||||
c.perfClrExceptions = &fixturePerfCollector[perfDataClrExceptions]{rows: []perfDataClrExceptions{{Name: "worker", NumberofExcepsThrown: 37}}}
|
||||
failure := errors.New("provider failed")
|
||||
c.perfClrInterop = &fixturePerfCollector[perfDataClrInterop]{err: failure}
|
||||
c.collectorFns = []func(chan<- prometheus.Metric, time.Duration) error{c.collectClrExceptions, c.collectClrInterop}
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
var collectErr error
|
||||
|
||||
registry.MustRegister(fixturePromCollector{collect: func(ch chan<- prometheus.Metric) { collectErr = c.Collect(ch, time.Second) }})
|
||||
families, err := registry.Gather()
|
||||
require.NoError(t, err)
|
||||
require.ErrorIs(t, collectErr, failure)
|
||||
require.Len(t, families, 4)
|
||||
|
||||
for _, family := range families {
|
||||
if family.GetName() == "windows_netframework_clrexceptions_exceptions_thrown_total" {
|
||||
require.InDelta(t, 37.0, family.GetMetric()[0].GetCounter().GetValue(), 1e-12)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
// No context name required as Collector source is WMI
|
||||
testutils.FuncBenchmarkCollector(b, netframework.Name, netframework.NewWithFlags)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user