mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 18:19:04 +02:00
pdh: report duplicate instances by occurrence instead of renaming them (#2742)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -380,6 +380,34 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time.
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// clrInstance is implemented by the rows of the .NET CLR objects.
|
||||
type clrInstance interface {
|
||||
instance() (name *string, occurrence int)
|
||||
}
|
||||
|
||||
// suffixDuplicateProcesses keeps processes with the same name apart. The .NET
|
||||
// CLR objects name instances after the process image and report a name once
|
||||
// per process, e.g. "powershell" twice for two PowerShell processes. Later
|
||||
// processes get the suffix "#1", "#2" and so on, which depends on the order
|
||||
// of the sample and does not identify a process across scrapes.
|
||||
func suffixDuplicateProcesses[T any, P interface {
|
||||
*T
|
||||
clrInstance
|
||||
}](rows []T) {
|
||||
pdh.SuffixDuplicates(rows,
|
||||
func(row *T) *string {
|
||||
name, _ := P(row).instance()
|
||||
|
||||
return name
|
||||
},
|
||||
func(row *T) int {
|
||||
_, occurrence := P(row).instance()
|
||||
|
||||
return occurrence
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// newPerfCollector adds the counters of object to query. On error,
|
||||
// pdh.NewQueryCollector has already removed the counters it added. Missing
|
||||
// objects are optional; missing individual counters remain errors.
|
||||
|
||||
@@ -65,18 +65,23 @@ func (c *Collector) buildClrExceptions() error {
|
||||
|
||||
type perfDataClrExceptions struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
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 (p *perfDataClrExceptions) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrExceptions(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -59,17 +59,22 @@ func (c *Collector) buildClrInterop() error {
|
||||
|
||||
type perfDataClrInterop struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
NumberofCCWs float64 `perfdata:"# of CCWs"`
|
||||
NumberofStubs float64 `perfdata:"# of Stubs"`
|
||||
Numberofmarshalling float64 `perfdata:"# of marshalling"`
|
||||
}
|
||||
|
||||
func (p *perfDataClrInterop) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrInterop(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -65,18 +65,23 @@ func (c *Collector) buildClrJIT() error {
|
||||
|
||||
type perfDataClrJIT struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
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 (p *perfDataClrJIT) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrJIT(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -95,6 +95,7 @@ func (c *Collector) buildClrLoading() error {
|
||||
|
||||
type perfDataClrLoading struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
BytesinLoaderHeap float64 `perfdata:"Bytes in Loader Heap"`
|
||||
CurrentAssemblies float64 `perfdata:"Current Assemblies"`
|
||||
CurrentClassesLoaded float64 `perfdata:"Current Classes Loaded"`
|
||||
@@ -106,12 +107,16 @@ type perfDataClrLoading struct {
|
||||
Totalappdomainsunloaded float64 `perfdata:"Total appdomains unloaded"`
|
||||
}
|
||||
|
||||
func (p *perfDataClrLoading) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrLoading(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -83,6 +83,7 @@ func (c *Collector) buildClrLocksAndThreads() error {
|
||||
|
||||
type perfDataClrLocksAndThreads struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
CurrentQueueLength float64 `perfdata:"Current Queue Length"`
|
||||
NumberofcurrentlogicalThreads float64 `perfdata:"# of current logical Threads"`
|
||||
NumberofcurrentphysicalThreads float64 `perfdata:"# of current physical Threads"`
|
||||
@@ -92,12 +93,16 @@ type perfDataClrLocksAndThreads struct {
|
||||
TotalNumberofContentions float64 `perfdata:"Total # of Contentions"`
|
||||
}
|
||||
|
||||
func (p *perfDataClrLocksAndThreads) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrLocksAndThreads(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -114,6 +114,7 @@ func (c *Collector) buildClrMemory() error {
|
||||
|
||||
type perfDataClrMemory struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
AllocatedBytesPersec float64 `perfdata:"Allocated Bytes/sec"`
|
||||
FinalizationSurvivors float64 `perfdata:"Finalization Survivors"`
|
||||
Gen0PromotedBytesPerSec float64 `perfdata:"Gen 0 Promoted Bytes/Sec"`
|
||||
@@ -136,12 +137,16 @@ type perfDataClrMemory struct {
|
||||
ProcessID float64 `perfdata:"Process ID"`
|
||||
}
|
||||
|
||||
func (p *perfDataClrMemory) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -77,6 +77,7 @@ func (c *Collector) buildClrRemoting() error {
|
||||
|
||||
type perfDataClrRemoting struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
Channels float64 `perfdata:"Channels"`
|
||||
ContextBoundClassesLoaded float64 `perfdata:"Context-Bound Classes Loaded"`
|
||||
ContextBoundObjectsAllocPersec float64 `perfdata:"Context-Bound Objects Alloc / sec"`
|
||||
@@ -85,12 +86,16 @@ type perfDataClrRemoting struct {
|
||||
TotalRemoteCalls float64 `perfdata:"Total Remote Calls"`
|
||||
}
|
||||
|
||||
func (p *perfDataClrRemoting) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrRemoting(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -65,18 +65,23 @@ func (c *Collector) buildClrSecurity() error {
|
||||
|
||||
type perfDataClrSecurity struct {
|
||||
Name string
|
||||
Occurrence int
|
||||
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 (p *perfDataClrSecurity) instance() (*string, int) { return &p.Name, p.Occurrence }
|
||||
|
||||
func (c *Collector) collectClrSecurity(ch chan<- prometheus.Metric, scrape *labelcache.Scrape, _ 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)
|
||||
}
|
||||
|
||||
suffixDuplicateProcesses(dst)
|
||||
|
||||
for _, process := range dst {
|
||||
if process.Name == "_Global_" {
|
||||
continue
|
||||
|
||||
@@ -40,7 +40,8 @@ func TestClrRemotingCounterPaths(t *testing.T) {
|
||||
"TotalRemoteCalls": "Total Remote Calls",
|
||||
}
|
||||
rowType := reflect.TypeFor[perfDataClrRemoting]()
|
||||
require.Equal(t, len(expected)+1, rowType.NumField())
|
||||
// Name and Occurrence hold the instance.
|
||||
require.Equal(t, len(expected)+2, rowType.NumField())
|
||||
|
||||
for field, counter := range expected {
|
||||
f, found := rowType.FieldByName(field)
|
||||
|
||||
@@ -415,6 +415,42 @@ func TestGCZeroBaseDoesNotPublishInvalidRatio(t *testing.T) {
|
||||
require.True(t, foundAllocated)
|
||||
}
|
||||
|
||||
// TestDuplicateProcessNames checks that processes with the same name, which
|
||||
// the CLR objects report with the same instance name, are published apart.
|
||||
func TestDuplicateProcessNames(t *testing.T) {
|
||||
c := &Collector{}
|
||||
c.describeClrExceptions()
|
||||
c.perfClrExceptions = &fixturePerfCollector[perfDataClrExceptions]{rows: []perfDataClrExceptions{
|
||||
{Name: "powershell", NumberofExcepsThrown: 1},
|
||||
{Name: "powershell", Occurrence: 1, NumberofExcepsThrown: 2},
|
||||
{Name: "powershell#1", NumberofExcepsThrown: 3},
|
||||
{Name: "powershell", Occurrence: 2, NumberofExcepsThrown: 4},
|
||||
}}
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(fixturePromCollector{collect: func(ch chan<- prometheus.Metric) {
|
||||
require.NoError(t, c.collectClrExceptions(ch, nil, time.Second))
|
||||
}})
|
||||
|
||||
families, err := registry.Gather()
|
||||
require.NoError(t, err)
|
||||
|
||||
got := map[string]float64{}
|
||||
|
||||
for _, family := range families {
|
||||
if family.GetName() != "windows_netframework_clrexceptions_exceptions_thrown_total" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, metric := range family.GetMetric() {
|
||||
got[metric.GetLabel()[0].GetValue()] = metric.GetCounter().GetValue()
|
||||
}
|
||||
}
|
||||
|
||||
// "powershell#1" is the name of a process, so the suffixes skip it.
|
||||
require.Equal(t, map[string]float64{"powershell": 1, "powershell#2": 2, "powershell#1": 3, "powershell#3": 4}, got)
|
||||
}
|
||||
|
||||
func TestMissingObjectErrorClassification(t *testing.T) {
|
||||
missing := pdh.NewPdhError(pdh.CstatusNoObject)
|
||||
unrelated := errors.New("access denied")
|
||||
|
||||
Reference in New Issue
Block a user