From 2a9a11bd0181bd96444d4a2d05d71b85f43f12b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 3 Oct 2024 23:44:36 +0200 Subject: [PATCH] process: fix fallback to V1 collector (#1667) --- internal/collector/process/process.go | 35 +---- internal/collector/process/process_test.go | 53 +++++++ internal/perfdata/error.go | 15 ++ internal/perfdata/pdh.go | 172 ++++++++++----------- internal/types/regexp.go | 4 +- 5 files changed, 159 insertions(+), 120 deletions(-) diff --git a/internal/collector/process/process.go b/internal/collector/process/process.go index 4aed2245..def1dc61 100644 --- a/internal/collector/process/process.go +++ b/internal/collector/process/process.go @@ -162,6 +162,7 @@ func (c *Collector) Build(logger *slog.Logger, wmiClient *wmi.Client) error { if utils.PDHEnabled() { counters := []string{ + processID, percentProcessorTime, percentPrivilegedTime, percentUserTime, @@ -181,7 +182,6 @@ func (c *Collector) Build(logger *slog.Logger, wmiClient *wmi.Client) error { pageFileBytes, poolNonPagedBytes, poolPagedBytes, - processID, priorityBase, privateBytes, threadCount, @@ -195,37 +195,8 @@ func (c *Collector) Build(logger *slog.Logger, wmiClient *wmi.Client) error { var err error c.perfDataCollector, err = perfdata.NewCollector("Process V2", c.config.PerfCounterInstances, counters) - if errors.Is(err, perfdata.NewPdhError(perfdata.PdhNoData)) { - counters = []string{ - percentProcessorTime, - percentPrivilegedTime, - percentUserTime, - creatingProcessID, - elapsedTime, - handleCount, - idProcess, - ioDataBytesPerSec, - ioDataOperationsPerSec, - ioOtherBytesPerSec, - ioOtherOperationsPerSec, - ioReadBytesPerSec, - ioReadOperationsPerSec, - ioWriteBytesPerSec, - ioWriteOperationsPerSec, - pageFaultsPerSec, - pageFileBytesPeak, - pageFileBytes, - poolNonPagedBytes, - poolPagedBytes, - priorityBase, - privateBytes, - threadCount, - virtualBytesPeak, - virtualBytes, - workingSetPrivate, - workingSetPeak, - workingSet, - } + if errors.Is(err, perfdata.NewPdhError(perfdata.PdhCstatusNoObject)) { + counters[0] = idProcess c.perfDataCollector, err = perfdata.NewCollector("Process", c.config.PerfCounterInstances, counters) } diff --git a/internal/collector/process/process_test.go b/internal/collector/process/process_test.go index 592b83c6..4c213a2e 100644 --- a/internal/collector/process/process_test.go +++ b/internal/collector/process/process_test.go @@ -1,11 +1,18 @@ package process_test import ( + "io" + "log/slog" + "sync" "testing" + "time" "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/collector/process" "github.com/prometheus-community/windows_exporter/internal/testutils" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" + "github.com/yusufpapurcu/wmi" ) func BenchmarkProcessCollector(b *testing.B) { @@ -15,3 +22,49 @@ func BenchmarkProcessCollector(b *testing.B) { // No context name required as collector source is WMI testutils.FuncBenchmarkCollector(b, process.Name, process.NewWithFlags) } + +func TestProcessCollector(t *testing.T) { + t.Setenv("WINDOWS_EXPORTER_PERF_COUNTERS_ENGINE", "pdh") + + var ( + metrics []prometheus.Metric + err error + ) + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + c := process.New(nil) + ch := make(chan prometheus.Metric, 10000) + + wmiClient := &wmi.Client{ + AllowMissingFields: true, + } + wmiClient.SWbemServicesClient, err = wmi.InitializeSWbemServices(wmiClient) + require.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, c.Close(logger)) + }) + + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + defer wg.Done() + + for metric := range ch { + metrics = append(metrics, metric) + } + }() + + require.NoError(t, c.Build(logger, wmiClient)) + + time.Sleep(1 * time.Second) + + require.NoError(t, c.Collect(nil, logger, ch)) + + close(ch) + + wg.Wait() + + require.NotEmpty(t, metrics) +} diff --git a/internal/perfdata/error.go b/internal/perfdata/error.go index 46fa4fb9..acff5ebb 100644 --- a/internal/perfdata/error.go +++ b/internal/perfdata/error.go @@ -1,11 +1,26 @@ package perfdata +import "errors" + // Error represents error returned from Performance Counters API. type Error struct { ErrorCode uint32 errorText string } +func (m *Error) Is(err error) bool { + if err == nil { + return false + } + + var e *Error + if errors.As(err, &e) { + return m.ErrorCode == e.ErrorCode + } + + return false +} + func (m *Error) Error() string { return m.errorText } diff --git a/internal/perfdata/pdh.go b/internal/perfdata/pdh.go index 599a6fc7..e2a90914 100644 --- a/internal/perfdata/pdh.go +++ b/internal/perfdata/pdh.go @@ -57,92 +57,92 @@ type ( // PDH error codes, which can be returned by all Pdh* functions. Taken from mingw-w64 pdhmsg.h const ( - PdhCstatusValidData = 0x00000000 // The returned data is valid. - PdhCstatusNewData = 0x00000001 // The return data value is valid and different from the last sample. - PdhCstatusNoMachine = 0x800007D0 // Unable to connect to the specified computer, or the computer is offline. - PdhCstatusNoInstance = 0x800007D1 - PdhMoreData = 0x800007D2 // The PdhGetFormattedCounterArray* function can return this if there's 'more data to be displayed'. - PdhCstatusItemNotValidated = 0x800007D3 - PdhRetry = 0x800007D4 - PdhNoData = 0x800007D5 // The query does not currently contain any counters (for example, limited access) - PdhCalcNegativeDenominator = 0x800007D6 - PdhCalcNegativeTimebase = 0x800007D7 - PdhCalcNegativeValue = 0x800007D8 - PdhDialogCancelled = 0x800007D9 - PdhEndOfLogFile = 0x800007DA - PdhAsyncQueryTimeout = 0x800007DB - PdhCannotSetDefaultRealtimeDatasource = 0x800007DC - PdhCstatusNoObject = 0xC0000BB8 - PdhCstatusNoCounter = 0xC0000BB9 // The specified counter could not be found. - PdhCstatusInvalidData = 0xC0000BBA // The counter was successfully found, but the data returned is not valid. - PdhMemoryAllocationFailure = 0xC0000BBB - PdhInvalidHandle = 0xC0000BBC - PdhInvalidArgument = 0xC0000BBD // Required argument is missing or incorrect. - PdhFunctionNotFound = 0xC0000BBE - PdhCstatusNoCountername = 0xC0000BBF - PdhCstatusBadCountername = 0xC0000BC0 // Unable to parse the counter path. Check the format and syntax of the specified path. - PdhInvalidBuffer = 0xC0000BC1 - PdhInsufficientBuffer = 0xC0000BC2 - PdhCannotConnectMachine = 0xC0000BC3 - PdhInvalidPath = 0xC0000BC4 - PdhInvalidInstance = 0xC0000BC5 - PdhInvalidData = 0xC0000BC6 // specified counter does not contain valid data or a successful status code. - PdhNoDialogData = 0xC0000BC7 - PdhCannotReadNameStrings = 0xC0000BC8 - PdhLogFileCreateError = 0xC0000BC9 - PdhLogFileOpenError = 0xC0000BCA - PdhLogTypeNotFound = 0xC0000BCB - PdhNoMoreData = 0xC0000BCC - PdhEntryNotInLogFile = 0xC0000BCD - PdhDataSourceIsLogFile = 0xC0000BCE - PdhDataSourceIsRealTime = 0xC0000BCF - PdhUnableReadLogHeader = 0xC0000BD0 - PdhFileNotFound = 0xC0000BD1 - PdhFileAlreadyExists = 0xC0000BD2 - PdhNotImplemented = 0xC0000BD3 - PdhStringNotFound = 0xC0000BD4 - PdhUnableMapNameFiles = 0x80000BD5 - PdhUnknownLogFormat = 0xC0000BD6 - PdhUnknownLogsvcCommand = 0xC0000BD7 - PdhLogsvcQueryNotFound = 0xC0000BD8 - PdhLogsvcNotOpened = 0xC0000BD9 - PdhWbemError = 0xC0000BDA - PdhAccessDenied = 0xC0000BDB - PdhLogFileTooSmall = 0xC0000BDC - PdhInvalidDatasource = 0xC0000BDD - PdhInvalidSqldb = 0xC0000BDE - PdhNoCounters = 0xC0000BDF - PdhSQLAllocFailed = 0xC0000BE0 - PdhSQLAllocconFailed = 0xC0000BE1 - PdhSQLExecDirectFailed = 0xC0000BE2 - PdhSQLFetchFailed = 0xC0000BE3 - PdhSQLRowcountFailed = 0xC0000BE4 - PdhSQLMoreResultsFailed = 0xC0000BE5 - PdhSQLConnectFailed = 0xC0000BE6 - PdhSQLBindFailed = 0xC0000BE7 - PdhCannotConnectWmiServer = 0xC0000BE8 - PdhPlaCollectionAlreadyRunning = 0xC0000BE9 - PdhPlaErrorScheduleOverlap = 0xC0000BEA - PdhPlaCollectionNotFound = 0xC0000BEB - PdhPlaErrorScheduleElapsed = 0xC0000BEC - PdhPlaErrorNostart = 0xC0000BED - PdhPlaErrorAlreadyExists = 0xC0000BEE - PdhPlaErrorTypeMismatch = 0xC0000BEF - PdhPlaErrorFilepath = 0xC0000BF0 - PdhPlaServiceError = 0xC0000BF1 - PdhPlaValidationError = 0xC0000BF2 - PdhPlaValidationWarning = 0x80000BF3 - PdhPlaErrorNameTooLong = 0xC0000BF4 - PdhInvalidSQLLogFormat = 0xC0000BF5 - PdhCounterAlreadyInQuery = 0xC0000BF6 - PdhBinaryLogCorrupt = 0xC0000BF7 - PdhLogSampleTooSmall = 0xC0000BF8 - PdhOsLaterVersion = 0xC0000BF9 - PdhOsEarlierVersion = 0xC0000BFA - PdhIncorrectAppendTime = 0xC0000BFB - PdhUnmatchedAppendCounter = 0xC0000BFC - PdhSQLAlterDetailFailed = 0xC0000BFD - PdhQueryPerfDataTimeout = 0xC0000BFE + PdhCstatusValidData uint32 = 0x00000000 // The returned data is valid. + PdhCstatusNewData uint32 = 0x00000001 // The return data value is valid and different from the last sample. + PdhCstatusNoMachine uint32 = 0x800007D0 // Unable to connect to the specified computer, or the computer is offline. + PdhCstatusNoInstance uint32 = 0x800007D1 + PdhMoreData uint32 = 0x800007D2 // The PdhGetFormattedCounterArray* function can return this if there's 'more data to be displayed'. + PdhCstatusItemNotValidated uint32 = 0x800007D3 + PdhRetry uint32 = 0x800007D4 + PdhNoData uint32 = 0x800007D5 // The query does not currently contain any counters (for example, limited access) + PdhCalcNegativeDenominator uint32 = 0x800007D6 + PdhCalcNegativeTimebase uint32 = 0x800007D7 + PdhCalcNegativeValue uint32 = 0x800007D8 + PdhDialogCancelled uint32 = 0x800007D9 + PdhEndOfLogFile uint32 = 0x800007DA + PdhAsyncQueryTimeout uint32 = 0x800007DB + PdhCannotSetDefaultRealtimeDatasource uint32 = 0x800007DC + PdhCstatusNoObject uint32 = 0xC0000BB8 + PdhCstatusNoCounter uint32 = 0xC0000BB9 // The specified counter could not be found. + PdhCstatusInvalidData uint32 = 0xC0000BBA // The counter was successfully found, but the data returned is not valid. + PdhMemoryAllocationFailure uint32 = 0xC0000BBB + PdhInvalidHandle uint32 = 0xC0000BBC + PdhInvalidArgument uint32 = 0xC0000BBD // Required argument is missing or incorrect. + PdhFunctionNotFound uint32 = 0xC0000BBE + PdhCstatusNoCountername uint32 = 0xC0000BBF + PdhCstatusBadCountername uint32 = 0xC0000BC0 // Unable to parse the counter path. Check the format and syntax of the specified path. + PdhInvalidBuffer uint32 = 0xC0000BC1 + PdhInsufficientBuffer uint32 = 0xC0000BC2 + PdhCannotConnectMachine uint32 = 0xC0000BC3 + PdhInvalidPath uint32 = 0xC0000BC4 + PdhInvalidInstance uint32 = 0xC0000BC5 + PdhInvalidData uint32 = 0xC0000BC6 // specified counter does not contain valid data or a successful status code. + PdhNoDialogData uint32 = 0xC0000BC7 + PdhCannotReadNameStrings uint32 = 0xC0000BC8 + PdhLogFileCreateError uint32 = 0xC0000BC9 + PdhLogFileOpenError uint32 = 0xC0000BCA + PdhLogTypeNotFound uint32 = 0xC0000BCB + PdhNoMoreData uint32 = 0xC0000BCC + PdhEntryNotInLogFile uint32 = 0xC0000BCD + PdhDataSourceIsLogFile uint32 = 0xC0000BCE + PdhDataSourceIsRealTime uint32 = 0xC0000BCF + PdhUnableReadLogHeader uint32 = 0xC0000BD0 + PdhFileNotFound uint32 = 0xC0000BD1 + PdhFileAlreadyExists uint32 = 0xC0000BD2 + PdhNotImplemented uint32 = 0xC0000BD3 + PdhStringNotFound uint32 = 0xC0000BD4 + PdhUnableMapNameFiles uint32 = 0x80000BD5 + PdhUnknownLogFormat uint32 = 0xC0000BD6 + PdhUnknownLogsvcCommand uint32 = 0xC0000BD7 + PdhLogsvcQueryNotFound uint32 = 0xC0000BD8 + PdhLogsvcNotOpened uint32 = 0xC0000BD9 + PdhWbemError uint32 = 0xC0000BDA + PdhAccessDenied uint32 = 0xC0000BDB + PdhLogFileTooSmall uint32 = 0xC0000BDC + PdhInvalidDatasource uint32 = 0xC0000BDD + PdhInvalidSqldb uint32 = 0xC0000BDE + PdhNoCounters uint32 = 0xC0000BDF + PdhSQLAllocFailed uint32 = 0xC0000BE0 + PdhSQLAllocconFailed uint32 = 0xC0000BE1 + PdhSQLExecDirectFailed uint32 = 0xC0000BE2 + PdhSQLFetchFailed uint32 = 0xC0000BE3 + PdhSQLRowcountFailed uint32 = 0xC0000BE4 + PdhSQLMoreResultsFailed uint32 = 0xC0000BE5 + PdhSQLConnectFailed uint32 = 0xC0000BE6 + PdhSQLBindFailed uint32 = 0xC0000BE7 + PdhCannotConnectWmiServer uint32 = 0xC0000BE8 + PdhPlaCollectionAlreadyRunning uint32 = 0xC0000BE9 + PdhPlaErrorScheduleOverlap uint32 = 0xC0000BEA + PdhPlaCollectionNotFound uint32 = 0xC0000BEB + PdhPlaErrorScheduleElapsed uint32 = 0xC0000BEC + PdhPlaErrorNostart uint32 = 0xC0000BED + PdhPlaErrorAlreadyExists uint32 = 0xC0000BEE + PdhPlaErrorTypeMismatch uint32 = 0xC0000BEF + PdhPlaErrorFilepath uint32 = 0xC0000BF0 + PdhPlaServiceError uint32 = 0xC0000BF1 + PdhPlaValidationError uint32 = 0xC0000BF2 + PdhPlaValidationWarning uint32 = 0x80000BF3 + PdhPlaErrorNameTooLong uint32 = 0xC0000BF4 + PdhInvalidSQLLogFormat uint32 = 0xC0000BF5 + PdhCounterAlreadyInQuery uint32 = 0xC0000BF6 + PdhBinaryLogCorrupt uint32 = 0xC0000BF7 + PdhLogSampleTooSmall uint32 = 0xC0000BF8 + PdhOsLaterVersion uint32 = 0xC0000BF9 + PdhOsEarlierVersion uint32 = 0xC0000BFA + PdhIncorrectAppendTime uint32 = 0xC0000BFB + PdhUnmatchedAppendCounter uint32 = 0xC0000BFC + PdhSQLAlterDetailFailed uint32 = 0xC0000BFD + PdhQueryPerfDataTimeout uint32 = 0xC0000BFE ) var PDHErrors = map[uint32]string{ diff --git a/internal/types/regexp.go b/internal/types/regexp.go index e2b75cf6..c8826da5 100644 --- a/internal/types/regexp.go +++ b/internal/types/regexp.go @@ -3,6 +3,6 @@ package types import "regexp" var ( - RegExpAny = regexp.MustCompile(".+") - RegExpEmpty = regexp.MustCompile("") + RegExpAny = regexp.MustCompile("^(?:.+)$") + RegExpEmpty = regexp.MustCompile("^(?:)$") )