From 3ef1c73582de41aafcc2b734ec0b57c857782008 Mon Sep 17 00:00:00 2001 From: Vlasta Hajek Date: Thu, 10 Feb 2022 23:03:52 +0100 Subject: [PATCH] feat: Implemented support for reading raw values, added tests and doc (#6501) --- plugins/inputs/win_perf_counters/README.md | 14 +- plugins/inputs/win_perf_counters/pdh.go | 51 ++++ plugins/inputs/win_perf_counters/pdh_386.go | 23 ++ plugins/inputs/win_perf_counters/pdh_amd64.go | 23 ++ .../win_perf_counters/performance_query.go | 48 +++- .../win_perf_counters/win_perf_counters.go | 89 +++++-- .../win_perf_counters_integration_test.go | 161 ++++++++++-- .../win_perf_counters_test.go | 244 +++++++++++++----- 8 files changed, 542 insertions(+), 111 deletions(-) diff --git a/plugins/inputs/win_perf_counters/README.md b/plugins/inputs/win_perf_counters/README.md index 15d6363d44cca..36ad348a4edf3 100644 --- a/plugins/inputs/win_perf_counters/README.md +++ b/plugins/inputs/win_perf_counters/README.md @@ -179,7 +179,19 @@ So for ordering your data in a good manner, this is a good key to set with a value when you want your IIS and Disk results stored separately from Processor results. -Example: `Measurement = "win_disk"`` +Example: `Measurement = "win_disk"` + +#### UseRawValues + +(Optional) + +This key is optional. It is a simple bool. +If set to `true`, counter values will be provided in the raw, integer, form. This is in contrast with the default behavior, where values are returned in a formatted, displayable, form +as seen in the Windows Performance Monitor. +A field representing raw counter value has the `_Raw` suffix. Raw values should be further used in a calculation, e.g. `100-(non_negative_derivative("Percent_Processor_Time_Raw",1s)/100000` +Note: Time based counters (i.e. _% Processor Time_) are reported in hundredths of nanoseconds. + +Example: `UseRawValues = true` #### IncludeTotal diff --git a/plugins/inputs/win_perf_counters/pdh.go b/plugins/inputs/win_perf_counters/pdh.go index 46048bee0e38d..4b5540dc53af0 100644 --- a/plugins/inputs/win_perf_counters/pdh.go +++ b/plugins/inputs/win_perf_counters/pdh.go @@ -272,6 +272,8 @@ var ( pdh_ValidatePathW *syscall.Proc pdh_ExpandWildCardPathW *syscall.Proc pdh_GetCounterInfoW *syscall.Proc + pdh_GetRawCounterValue *syscall.Proc + pdh_GetRawCounterArrayW *syscall.Proc ) func init() { @@ -290,6 +292,8 @@ func init() { pdh_ValidatePathW = libpdhDll.MustFindProc("PdhValidatePathW") pdh_ExpandWildCardPathW = libpdhDll.MustFindProc("PdhExpandWildCardPathW") pdh_GetCounterInfoW = libpdhDll.MustFindProc("PdhGetCounterInfoW") + pdh_GetRawCounterValue = libpdhDll.MustFindProc("PdhGetRawCounterValue") + pdh_GetRawCounterArrayW = libpdhDll.MustFindProc("PdhGetRawCounterArrayW") } // PdhAddCounter adds the specified counter to the query. This is the internationalized version. Preferably, use the @@ -591,3 +595,50 @@ func PdhGetCounterInfo(hCounter PDH_HCOUNTER, bRetrieveExplainText int, pdwBuffe return uint32(ret) } + +// Returns the current raw value of the counter. +// If the specified counter instance does not exist, this function will return ERROR_SUCCESS +// and the CStatus member of the PDH_RAW_COUNTER structure will contain PDH_CSTATUS_NO_INSTANCE. +// +// hCounter [in] +// Handle of the counter from which to retrieve the current raw value. The PdhAddCounter function returns this handle. +// +// lpdwType [out] +// Receives the counter type. For a list of counter types, see the Counter Types section of the Windows Server 2003 Deployment Kit. +// This parameter is optional. +// +// pValue [out] +// A PDH_RAW_COUNTER structure that receives the counter value. +func PdhGetRawCounterValue(hCounter PDH_HCOUNTER, lpdwType *uint32, pValue *PDH_RAW_COUNTER) uint32 { + ret, _, _ := pdh_GetRawCounterValue.Call( + uintptr(hCounter), + uintptr(unsafe.Pointer(lpdwType)), + uintptr(unsafe.Pointer(pValue))) + + return uint32(ret) +} + +// Returns an array of raw values from the specified counter. Use this function when you want to retrieve the raw counter values +// of a counter that contains a wildcard character for the instance name. +// hCounter +// Handle of the counter for whose current raw instance values you want to retrieve. The PdhAddCounter function returns this handle. +// +// lpdwBufferSize +// Size of the ItemBuffer buffer, in bytes. If zero on input, the function returns PDH_MORE_DATA and sets this parameter to the required buffer size. +// If the buffer is larger than the required size, the function sets this parameter to the actual size of the buffer that was used. +// If the specified size on input is greater than zero but less than the required size, you should not rely on the returned size to reallocate the buffer. +// +// lpdwItemCount +// Number of raw counter values in the ItemBuffer buffer. +// +// ItemBuffer +// Caller-allocated buffer that receives the array of PDH_RAW_COUNTER_ITEM structures; the structures contain the raw instance counter values. +// Set to NULL if lpdwBufferSize is zero. +func PdhGetRawCounterArray(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *byte) uint32 { + ret, _, _ := pdh_GetRawCounterArrayW.Call( + uintptr(hCounter), + uintptr(unsafe.Pointer(lpdwBufferSize)), + uintptr(unsafe.Pointer(lpdwBufferCount)), + uintptr(unsafe.Pointer(itemBuffer))) + return uint32(ret) +} diff --git a/plugins/inputs/win_perf_counters/pdh_386.go b/plugins/inputs/win_perf_counters/pdh_386.go index ec572db72447e..ba0bf36cdf9ce 100644 --- a/plugins/inputs/win_perf_counters/pdh_386.go +++ b/plugins/inputs/win_perf_counters/pdh_386.go @@ -120,3 +120,26 @@ type PDH_COUNTER_INFO struct { //Start of the string data that is appended to the structure. DataBuffer [1]uint32 // pointer to an extra space } + +// The PDH_RAW_COUNTER structure returns the data as it was collected from the counter provider. No translation, formatting, or other interpretation is performed on the data +type PDH_RAW_COUNTER struct { + // Counter status that indicates if the counter value is valid. Check this member before using the data in a calculation or displaying its value. For a list of possible values, + // see https://docs.microsoft.com/windows/desktop/PerfCtrs/checking-pdh-interface-return-values + CStatus uint32 + // Local time for when the data was collected + TimeStamp FILETIME + // First raw counter value. + FirstValue int64 + // Second raw counter value. Rate counters require two values in order to compute a displayable value. + SecondValue int64 + // If the counter type contains the PERF_MULTI_COUNTER flag, this member contains the additional counter data used in the calculation. + // For example, the PERF_100NSEC_MULTI_TIMER counter type contains the PERF_MULTI_COUNTER flag. + MultiCount uint32 +} + +type PDH_RAW_COUNTER_ITEM struct { + // Pointer to a null-terminated string that specifies the instance name of the counter. The string is appended to the end of this structure. + SzName *uint16 + //A PDH_RAW_COUNTER structure that contains the raw counter value of the instance + RawValue PDH_RAW_COUNTER +} diff --git a/plugins/inputs/win_perf_counters/pdh_amd64.go b/plugins/inputs/win_perf_counters/pdh_amd64.go index 1afedc317260e..94fd2ab156dbd 100644 --- a/plugins/inputs/win_perf_counters/pdh_amd64.go +++ b/plugins/inputs/win_perf_counters/pdh_amd64.go @@ -113,3 +113,26 @@ type PDH_COUNTER_INFO struct { //Start of the string data that is appended to the structure. DataBuffer [1]uint32 // pointer to an extra space } + +// The PDH_RAW_COUNTER structure returns the data as it was collected from the counter provider. No translation, formatting, or other interpretation is performed on the data +type PDH_RAW_COUNTER struct { + // Counter status that indicates if the counter value is valid. Check this member before using the data in a calculation or displaying its value. For a list of possible values, + // see https://docs.microsoft.com/windows/desktop/PerfCtrs/checking-pdh-interface-return-values + CStatus uint32 + // Local time for when the data was collected + TimeStamp FILETIME + // First raw counter value. + FirstValue int64 + // Second raw counter value. Rate counters require two values in order to compute a displayable value. + SecondValue int64 + // If the counter type contains the PERF_MULTI_COUNTER flag, this member contains the additional counter data used in the calculation. + // For example, the PERF_100NSEC_MULTI_TIMER counter type contains the PERF_MULTI_COUNTER flag. + MultiCount uint32 +} + +type PDH_RAW_COUNTER_ITEM struct { + // Pointer to a null-terminated string that specifies the instance name of the counter. The string is appended to the end of this structure. + SzName *uint16 + //A PDH_RAW_COUNTER structure that contains the raw counter value of the instance + RawValue PDH_RAW_COUNTER +} diff --git a/plugins/inputs/win_perf_counters/performance_query.go b/plugins/inputs/win_perf_counters/performance_query.go index ab130a41dec3f..232c239fc13dc 100644 --- a/plugins/inputs/win_perf_counters/performance_query.go +++ b/plugins/inputs/win_perf_counters/performance_query.go @@ -14,7 +14,7 @@ import ( //PerformanceQuery is abstraction for PDH_FMT_COUNTERVALUE_ITEM_DOUBLE type CounterValue struct { InstanceName string - Value float64 + Value interface{} } //PerformanceQuery provides wrappers around Windows performance counters API for easy usage in GO @@ -26,7 +26,9 @@ type PerformanceQuery interface { GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) ExpandWildCardPath(counterPath string) ([]string, error) GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error) + GetRawCounterValue(hCounter PDH_HCOUNTER) (int64, error) GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error) + GetRawCounterArray(hCounter PDH_HCOUNTER) ([]CounterValue, error) CollectData() error CollectDataWithTime() (time.Time, error) IsVistaOrNewer() bool @@ -182,6 +184,29 @@ func (m *PerformanceQueryImpl) GetFormattedCounterArrayDouble(hCounter PDH_HCOUN return nil, NewPdhError(ret) } +func (m *PerformanceQueryImpl) GetRawCounterArray(hCounter PDH_HCOUNTER) ([]CounterValue, error) { + var buffSize uint32 + var itemCount uint32 + var ret uint32 + + if ret = PdhGetRawCounterArray(hCounter, &buffSize, &itemCount, nil); ret == PDH_MORE_DATA { + buff := make([]byte, buffSize) + + if ret = PdhGetRawCounterArray(hCounter, &buffSize, &itemCount, &buff[0]); ret == ERROR_SUCCESS { + items := (*[1 << 20]PDH_RAW_COUNTER_ITEM)(unsafe.Pointer(&buff[0]))[:itemCount] + values := make([]CounterValue, 0, itemCount) + for _, item := range items { + if item.RawValue.CStatus == PDH_CSTATUS_VALID_DATA || item.RawValue.CStatus == PDH_CSTATUS_NEW_DATA { + val := CounterValue{UTF16PtrToString(item.SzName), item.RawValue.FirstValue} + values = append(values, val) + } + } + return values, nil + } + } + return nil, NewPdhError(ret) +} + func (m *PerformanceQueryImpl) CollectData() error { var ret uint32 if m.query == 0 { @@ -209,6 +234,27 @@ func (m *PerformanceQueryImpl) IsVistaOrNewer() bool { return PdhAddEnglishCounterSupported() } +func (m *PerformanceQueryImpl) GetRawCounterValue(hCounter PDH_HCOUNTER) (int64, error) { + if m.query == 0 { + return 0, errors.New("uninitialised query") + } + + var counterType uint32 + var value PDH_RAW_COUNTER + var ret uint32 + + if ret = PdhGetRawCounterValue(hCounter, &counterType, &value); ret == ERROR_SUCCESS { + if value.CStatus == PDH_CSTATUS_VALID_DATA || value.CStatus == PDH_CSTATUS_NEW_DATA { + return value.FirstValue, nil + } else { + return 0, NewPdhError(value.CStatus) + } + } else { + return 0, NewPdhError(ret) + } + +} + // UTF16PtrToString converts Windows API LPTSTR (pointer to string) to go string func UTF16PtrToString(s *uint16) string { if s == nil { diff --git a/plugins/inputs/win_perf_counters/win_perf_counters.go b/plugins/inputs/win_perf_counters/win_perf_counters.go index 4effb3bc0c5fd..05031671d834f 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters.go @@ -59,6 +59,8 @@ var sampleConfig = ` # IncludeTotal=false # Print out when the performance counter is missing from object, counter or instance. # WarnOnMissing = false + # Gather raw values instead of formatted. Raw value is stored in the field name with the "_Raw" suffix, e.g. "Disk_Read_Bytes_sec_Raw". + # UseRawValues = true [[inputs.win_perf_counters.object]] # Disk times and queues @@ -174,6 +176,7 @@ type perfobject struct { WarnOnMissing bool FailOnMissing bool IncludeTotal bool + UseRawValues bool } type counter struct { @@ -183,6 +186,7 @@ type counter struct { instance string measurement string includeTotal bool + useRawValue bool counterHandle PDH_HCOUNTER } @@ -257,8 +261,20 @@ func (m *Win_PerfCounters) SampleConfig() string { return sampleConfig } -//objectName string, counter string, instance string, measurement string, include_total bool -func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instance string, counterName string, measurement string, includeTotal bool) error { +func newCounter(counterHandle PDH_HCOUNTER, counterPath string, objectName string, instance string, counterName string, measurement string, includeTotal bool, useRawValue bool) *counter { + measurementName := sanitizedChars.Replace(measurement) + if measurementName == "" { + measurementName = "win_perf_counters" + } + newCounterName := sanitizedChars.Replace(counterName) + if useRawValue { + newCounterName += "_Raw" + } + return &counter{counterPath, objectName, newCounterName, instance, measurementName, + includeTotal, useRawValue, counterHandle} +} + +func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instance string, counterName string, measurement string, includeTotal bool, useRawValue bool) error { origCounterPath := counterPath var err error var counterHandle PDH_HCOUNTER @@ -315,20 +331,27 @@ func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instan } counterPath = formatPath(origObjectName, newInstance, origCounterName) counterHandle, err = m.query.AddEnglishCounterToQuery(counterPath) - newItem = &counter{ + newItem = newCounter( + counterHandle, counterPath, - origObjectName, origCounterName, - instance, measurement, - includeTotal, counterHandle, - } + origObjectName, instance, + origCounterName, + measurement, + includeTotal, + useRawValue, + ) } else { counterHandle, err = m.query.AddCounterToQuery(counterPath) - newItem = &counter{ + newItem = newCounter( + counterHandle, counterPath, - objectName, counterName, - instance, measurement, - includeTotal, counterHandle, - } + objectName, + instance, + counterName, + measurement, + includeTotal, + useRawValue, + ) } if instance == "_Total" && origInstance == "*" && !includeTotal { @@ -342,8 +365,16 @@ func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instan } } } else { - newItem := &counter{counterPath, objectName, counterName, instance, measurement, - includeTotal, counterHandle} + newItem := newCounter( + counterHandle, + counterPath, + objectName, + instance, + counterName, + measurement, + includeTotal, + useRawValue, + ) m.counters = append(m.counters, newItem) if m.PrintValid { m.Log.Infof("Valid: %s", counterPath) @@ -369,12 +400,15 @@ func (m *Win_PerfCounters) ParseConfig() error { if len(m.Object) > 0 { for _, PerfObject := range m.Object { for _, counter := range PerfObject.Counters { + if len(PerfObject.Instances) == 0 { + m.Log.Warnf("Missing 'Instances' param for object '%s'\n", PerfObject.ObjectName) + } for _, instance := range PerfObject.Instances { objectname := PerfObject.ObjectName counterPath = formatPath(objectname, instance, counter) - err := m.AddItem(counterPath, objectname, instance, counter, PerfObject.Measurement, PerfObject.IncludeTotal) + err := m.AddItem(counterPath, objectname, instance, counter, PerfObject.Measurement, PerfObject.IncludeTotal, PerfObject.UseRawValues) if err != nil { if PerfObject.FailOnMissing || PerfObject.WarnOnMissing { @@ -447,12 +481,16 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { return err } } - + var value interface{} // For iterate over the known metrics and get the samples. for _, metric := range m.counters { // collect if m.UseWildcardsExpansion { - value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle) + if metric.useRawValue { + value, err = m.query.GetRawCounterValue(metric.counterHandle) + } else { + value, err = m.query.GetFormattedCounterValueDouble(metric.counterHandle) + } if err != nil { //ignore invalid data as some counters from process instances returns this sometimes if !isKnownCounterDataError(err) { @@ -463,7 +501,12 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { } addCounterMeasurement(metric, metric.instance, value, collectFields) } else { - counterValues, err := m.query.GetFormattedCounterArrayDouble(metric.counterHandle) + var counterValues []CounterValue + if metric.useRawValue { + counterValues, err = m.query.GetRawCounterArray(metric.counterHandle) + } else { + counterValues, err = m.query.GetFormattedCounterArrayDouble(metric.counterHandle) + } if err != nil { //ignore invalid data as some counters from process instances returns this sometimes if !isKnownCounterDataError(err) { @@ -519,16 +562,12 @@ func shouldIncludeMetric(metric *counter, cValue CounterValue) bool { return false } -func addCounterMeasurement(metric *counter, instanceName string, value float64, collectFields map[instanceGrouping]map[string]interface{}) { - measurement := sanitizedChars.Replace(metric.measurement) - if measurement == "" { - measurement = "win_perf_counters" - } - var instance = instanceGrouping{measurement, instanceName, metric.objectName} +func addCounterMeasurement(metric *counter, instanceName string, value interface{}, collectFields map[instanceGrouping]map[string]interface{}) { + var instance = instanceGrouping{metric.measurement, instanceName, metric.objectName} if collectFields[instance] == nil { collectFields[instance] = make(map[string]interface{}) } - collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value) + collectFields[instance][sanitizedChars.Replace(metric.counter)] = value } func isKnownCounterDataError(err error) bool { diff --git a/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go b/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go index 63483379315ee..344cdc4ed2fb7 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go @@ -69,8 +69,13 @@ func TestWinPerformanceQueryImplIntegration(t *testing.T) { err = query.CollectData() require.NoError(t, err) - _, err = query.GetFormattedCounterValueDouble(hCounter) + fcounter, err := query.GetFormattedCounterValueDouble(hCounter) require.NoError(t, err) + require.True(t, fcounter > 0) + + rcounter, err := query.GetRawCounterValue(hCounter) + require.NoError(t, err) + require.True(t, rcounter > 10000000) now := time.Now() mtime, err := query.CollectDataWithTime() @@ -104,13 +109,17 @@ func TestWinPerformanceQueryImplIntegration(t *testing.T) { err = query.CollectData() require.NoError(t, err) - arr, err := query.GetFormattedCounterArrayDouble(hCounter) + farr, err := query.GetFormattedCounterArrayDouble(hCounter) if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE { time.Sleep(time.Second) - arr, err = query.GetFormattedCounterArrayDouble(hCounter) + farr, err = query.GetFormattedCounterArrayDouble(hCounter) } require.NoError(t, err) - require.True(t, len(arr) > 0, "Too") + require.True(t, len(farr) > 0) + + rarr, err := query.GetRawCounterArray(hCounter) + require.NoError(t, err) + require.True(t, len(rarr) > 0, "Too") err = query.Close() require.NoError(t, err) @@ -144,8 +153,13 @@ func TestWinPerfcountersConfigGet1Integration(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} - m.query.Open() + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + _ = m.query.Open() err := m.ParseConfig() require.NoError(t, err) @@ -178,8 +192,13 @@ func TestWinPerfcountersConfigGet2Integration(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} - m.query.Open() + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + _ = m.query.Open() err := m.ParseConfig() require.NoError(t, err) @@ -225,8 +244,13 @@ func TestWinPerfcountersConfigGet3Integration(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} - m.query.Open() + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + _ = m.query.Open() err := m.ParseConfig() require.NoError(t, err) @@ -274,8 +298,13 @@ func TestWinPerfcountersConfigGet4Integration(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} - m.query.Open() + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + _ = m.query.Open() err := m.ParseConfig() require.NoError(t, err) @@ -324,8 +353,13 @@ func TestWinPerfcountersConfigGet5Integration(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} - m.query.Open() + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + _ = m.query.Open() err := m.ParseConfig() require.NoError(t, err) @@ -370,8 +404,13 @@ func TestWinPerfcountersConfigGet6Integration(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} - m.query.Open() + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + _ = m.query.Open() err := m.ParseConfig() require.NoError(t, err) @@ -402,12 +441,18 @@ func TestWinPerfcountersConfigGet7Integration(t *testing.T) { false, false, false, + false, } perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} - m.query.Open() + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + _ = m.query.Open() err := m.ParseConfig() require.NoError(t, err) @@ -458,7 +503,7 @@ func TestWinPerfcountersConfigError1Integration(t *testing.T) { query: &PerformanceQueryImpl{}, Log: testutil.Logger{}, } - m.query.Open() + _ = m.query.Open() err := m.ParseConfig() require.Error(t, err) @@ -497,7 +542,7 @@ func TestWinPerfcountersConfigError2Integration(t *testing.T) { query: &PerformanceQueryImpl{}, Log: testutil.Logger{}, } - m.query.Open() + _ = m.query.Open() err := m.ParseConfig() var acc testutil.Accumulator @@ -538,7 +583,7 @@ func TestWinPerfcountersConfigError3Integration(t *testing.T) { query: &PerformanceQueryImpl{}, Log: testutil.Logger{}, } - m.query.Open() + _ = m.query.Open() err := m.ParseConfig() require.Error(t, err) @@ -647,3 +692,77 @@ func TestWinPerfcountersCollect2Integration(t *testing.T) { } } + +func TestWinPerfcountersCollectRawIntegration(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + var instances = make([]string, 1) + var counters = make([]string, 1) + var perfobjects = make([]perfobject, 1) + + objectname := "Processor" + instances[0] = "*" + counters[0] = "% Idle Time" + + var expectedCounter = "Percent_Idle_Time_Raw" + + var measurement = "test" + + PerfObject := perfobject{ + ObjectName: objectname, + Instances: instances, + Counters: counters, + Measurement: measurement, + WarnOnMissing: false, + FailOnMissing: true, + IncludeTotal: false, + UseRawValues: true, + } + + perfobjects[0] = PerfObject + + m := Win_PerfCounters{ + PrintValid: false, + Object: perfobjects, + UseWildcardsExpansion: true, + query: &PerformanceQueryImpl{}, + Log: testutil.Logger{}, + } + var acc testutil.Accumulator + err := m.Gather(&acc) + require.NoError(t, err) + + time.Sleep(2000 * time.Millisecond) + err = m.Gather(&acc) + require.NoError(t, err) + require.True(t, len(acc.Metrics) > 1) + + for _, metric := range acc.Metrics { + val, ok := metric.Fields[expectedCounter] + require.True(t, ok, "Expected presence of %s field", expectedCounter) + valInt64, ok := val.(int64) + require.True(t, ok, fmt.Sprintf("Expected int64, got %T", val)) + require.True(t, valInt64 > 0, fmt.Sprintf("Expected > 0, got %d, for %#v", valInt64, metric)) + } + + // Test *Array way + m = Win_PerfCounters{PrintValid: false, Object: perfobjects, UseWildcardsExpansion: false, query: &PerformanceQueryImpl{}, Log: testutil.Logger{}} + var acc2 testutil.Accumulator + err = m.Gather(&acc) + require.NoError(t, err) + + time.Sleep(2000 * time.Millisecond) + err = m.Gather(&acc2) + require.NoError(t, err) + require.True(t, len(acc2.Metrics) > 1) + + for _, metric := range acc2.Metrics { + val, ok := metric.Fields[expectedCounter] + require.True(t, ok, "Expected presence of %s field", expectedCounter) + valInt64, ok := val.(int64) + require.True(t, ok, fmt.Sprintf("Expected int64, got %T", val)) + require.True(t, valInt64 > 0, fmt.Sprintf("Expected > 0, got %d, for %#v", valInt64, metric)) + } + +} diff --git a/plugins/inputs/win_perf_counters/win_perf_counters_test.go b/plugins/inputs/win_perf_counters/win_perf_counters_test.go index 97787cb3114df..cafe732e180a4 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters_test.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters_test.go @@ -6,6 +6,7 @@ package win_perf_counters import ( "errors" "fmt" + "github.com/stretchr/testify/assert" "testing" "time" @@ -30,12 +31,19 @@ type FakePerformanceQuery struct { var MetricTime = time.Date(2018, 5, 28, 12, 0, 0, 0, time.UTC) -func (m *testCounter) ToCounterValue() *CounterValue { +func (m *testCounter) ToCounterValue(raw bool) *CounterValue { _, inst, _, _ := extractCounterInfoFromCounterPath(m.path) if inst == "" { inst = "--" } - return &CounterValue{inst, m.value} + var val interface{} + if raw { + val = int64(m.value) + } else { + val = m.value + } + + return &CounterValue{inst, val} } func (m *FakePerformanceQuery) Open() error { @@ -110,6 +118,22 @@ func (m *FakePerformanceQuery) GetFormattedCounterValueDouble(counterHandle PDH_ } return 0, fmt.Errorf("GetFormattedCounterValueDouble: invalid handle: %d", counterHandle) } + +func (m *FakePerformanceQuery) GetRawCounterValue(counterHandle PDH_HCOUNTER) (int64, error) { + if !m.openCalled { + return 0, errors.New("GetRawCounterValue: uninitialised query") + } + for _, counter := range m.counters { + if counter.handle == counterHandle { + if counter.status > 0 { + return 0, NewPdhError(counter.status) + } + return int64(counter.value), nil + } + } + return 0, fmt.Errorf("GetRawCounterValue: invalid handle: %d", counterHandle) +} + func (m *FakePerformanceQuery) findCounterByPath(counterPath string) *testCounter { for _, c := range m.counters { if c.path == counterPath { @@ -142,7 +166,7 @@ func (m *FakePerformanceQuery) GetFormattedCounterArrayDouble(hCounter PDH_HCOUN if counter.status > 0 { return nil, NewPdhError(counter.status) } - counters = append(counters, *counter.ToCounterValue()) + counters = append(counters, *counter.ToCounterValue(false)) } else { return nil, fmt.Errorf("GetFormattedCounterArrayDouble: invalid counter : %s", p) } @@ -156,6 +180,34 @@ func (m *FakePerformanceQuery) GetFormattedCounterArrayDouble(hCounter PDH_HCOUN return nil, fmt.Errorf("GetFormattedCounterArrayDouble: invalid counter : %d, no paths found", hCounter) } +func (m *FakePerformanceQuery) GetRawCounterArray(hCounter PDH_HCOUNTER) ([]CounterValue, error) { + if !m.openCalled { + return nil, errors.New("GetRawCounterArray: uninitialised query") + } + for _, c := range m.counters { + if c.handle == hCounter { + if e, ok := m.expandPaths[c.path]; ok { + counters := make([]CounterValue, 0, len(e)) + for _, p := range e { + counter := m.findCounterByPath(p) + if counter != nil { + if counter.status > 0 { + return nil, NewPdhError(counter.status) + } + counters = append(counters, *counter.ToCounterValue(true)) + } else { + return nil, fmt.Errorf("GetRawCounterArray: invalid counter : %s", p) + } + } + return counters, nil + } else { + return nil, fmt.Errorf("GetRawCounterArray: invalid counter : %d", hCounter) + } + } + } + return nil, fmt.Errorf("GetRawCounterArray: invalid counter : %d, no paths found", hCounter) +} + func (m *FakePerformanceQuery) CollectData() error { if !m.openCalled { return errors.New("CollectData: uninitialized query") @@ -174,7 +226,7 @@ func (m *FakePerformanceQuery) IsVistaOrNewer() bool { return m.vistaAndNewer } -func createPerfObject(measurement string, object string, instances []string, counters []string, failOnMissing bool, includeTotal bool) []perfobject { +func createPerfObject(measurement string, object string, instances []string, counters []string, failOnMissing, includeTotal, useRawValues bool) []perfobject { PerfObject := perfobject{ ObjectName: object, Instances: instances, @@ -183,6 +235,7 @@ func createPerfObject(measurement string, object string, instances []string, cou WarnOnMissing: false, FailOnMissing: failOnMissing, IncludeTotal: includeTotal, + UseRawValues: useRawValues, } perfobjects := []perfobject{PerfObject} return perfobjects @@ -261,7 +314,7 @@ func TestAddItemSimple(t *testing.T) { }} err = m.query.Open() require.NoError(t, err) - err = m.AddItem(cps1[0], "O", "I", "c", "test", false) + err = m.AddItem(cps1[0], "O", "I", "c", "test", false, true) require.NoError(t, err) err = m.query.Close() require.NoError(t, err) @@ -284,7 +337,7 @@ func TestAddItemInvalidCountPath(t *testing.T) { }} err = m.query.Open() require.NoError(t, err) - err = m.AddItem("\\O\\C", "O", "------", "C", "test", false) + err = m.AddItem("\\O\\C", "O", "------", "C", "test", false, false) require.Error(t, err) err = m.query.Close() require.NoError(t, err) @@ -292,7 +345,7 @@ func TestAddItemInvalidCountPath(t *testing.T) { func TestParseConfigBasic(t *testing.T) { var err error - perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false) + perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -330,7 +383,7 @@ func TestParseConfigBasic(t *testing.T) { func TestParseConfigNoInstance(t *testing.T) { var err error - perfObjects := createPerfObject("m", "O", []string{"------"}, []string{"C1", "C2"}, false, false) + perfObjects := createPerfObject("m", "O", []string{"------"}, []string{"C1", "C2"}, false, false, false) cps1 := []string{"\\O\\C1", "\\O\\C2"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -367,7 +420,7 @@ func TestParseConfigNoInstance(t *testing.T) { func TestParseConfigInvalidCounterError(t *testing.T) { var err error - perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, true, false) + perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, true, false, false) cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -402,7 +455,7 @@ func TestParseConfigInvalidCounterError(t *testing.T) { func TestParseConfigInvalidCounterNoError(t *testing.T) { var err error - perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false) + perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false) cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -438,7 +491,7 @@ func TestParseConfigInvalidCounterNoError(t *testing.T) { func TestParseConfigTotalExpansion(t *testing.T) { var err error - perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true) + perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true, false) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -485,7 +538,7 @@ func TestParseConfigTotalExpansion(t *testing.T) { func TestParseConfigExpand(t *testing.T) { var err error - perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, false, false) + perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, false, false, false) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -514,7 +567,7 @@ func TestSimpleGather(t *testing.T) { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false) + perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false, false) cp1 := "\\O(I)\\C" m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -532,7 +585,7 @@ func TestSimpleGather(t *testing.T) { require.NoError(t, err) fields1 := map[string]interface{}{ - "C": float32(1.2), + "C": 1.2, } tags1 := map[string]string{ "instance": "I", @@ -557,7 +610,7 @@ func TestSimpleGatherNoData(t *testing.T) { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false) + perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false, false) cp1 := "\\O(I)\\C" m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -577,7 +630,7 @@ func TestSimpleGatherNoData(t *testing.T) { // fields would contain if the error was ignored, and we simply added garbage fields1 := map[string]interface{}{ - "C": float32(1.2), + "C": 1.2, } // tags would contain if the error was ignored, and we simply added garbage tags1 := map[string]string{ @@ -603,7 +656,7 @@ func TestSimpleGatherWithTimestamp(t *testing.T) { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false) + perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false, false) cp1 := "\\O(I)\\C" m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -622,7 +675,7 @@ func TestSimpleGatherWithTimestamp(t *testing.T) { require.NoError(t, err) fields1 := map[string]interface{}{ - "C": float32(1.2), + "C": 1.2, } tags1 := map[string]string{ "instance": "I", @@ -634,12 +687,12 @@ func TestSimpleGatherWithTimestamp(t *testing.T) { func TestGatherError(t *testing.T) { var err error - expected_error := "error while getting value for counter \\O(I)\\C: The information passed is not valid.\r\n" + expectedError := "error while getting value for counter \\O(I)\\C: The information passed is not valid.\r\n" if testing.Short() { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false) + perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false, false) cp1 := "\\O(I)\\C" m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -655,7 +708,7 @@ func TestGatherError(t *testing.T) { var acc1 testutil.Accumulator err = m.Gather(&acc1) require.Error(t, err) - require.Equal(t, expected_error, err.Error()) + require.Equal(t, expectedError, err.Error()) m.UseWildcardsExpansion = true m.counters = nil @@ -665,7 +718,7 @@ func TestGatherError(t *testing.T) { err = m.Gather(&acc2) require.Error(t, err) - require.Equal(t, expected_error, err.Error()) + require.Equal(t, expectedError, err.Error()) } func TestGatherInvalidDataIgnore(t *testing.T) { @@ -674,7 +727,7 @@ func TestGatherInvalidDataIgnore(t *testing.T) { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C1", "C2", "C3"}, false, false) + perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C1", "C2", "C3"}, false, false, false) cps1 := []string{"\\O(I)\\C1", "\\O(I)\\C2", "\\O(I)\\C3"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -694,8 +747,8 @@ func TestGatherInvalidDataIgnore(t *testing.T) { require.NoError(t, err) fields1 := map[string]interface{}{ - "C1": float32(1.2), - "C3": float32(0), + "C1": 1.2, + "C3": float64(0), } tags1 := map[string]string{ "instance": "I", @@ -720,7 +773,7 @@ func TestGatherRefreshingWithExpansion(t *testing.T) { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"*"}, true, false) + perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"*"}, true, false, false) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} fpm := &FakePerformanceQuery{ counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}, []uint32{0, 0, 0, 0, 0}), @@ -730,12 +783,13 @@ func TestGatherRefreshingWithExpansion(t *testing.T) { vistaAndNewer: true, } m := Win_PerfCounters{ - Log: testutil.Logger{}, - PrintValid: false, - Object: perfObjects, - UseWildcardsExpansion: true, - query: fpm, - CountersRefreshInterval: config.Duration(time.Second * 10), + Log: testutil.Logger{}, + PrintValid: false, + Object: perfObjects, + UseWildcardsExpansion: true, + query: fpm, + CountersRefreshInterval: config.Duration(time.Second * 10), + LocalizeWildcardsExpansion: true, } var acc1 testutil.Accumulator err = m.Gather(&acc1) @@ -744,8 +798,8 @@ func TestGatherRefreshingWithExpansion(t *testing.T) { require.Len(t, acc1.Metrics, 2) fields1 := map[string]interface{}{ - "C1": float32(1.1), - "C2": float32(1.2), + "C1": 1.1, + "C2": 1.2, } tags1 := map[string]string{ "instance": "I1", @@ -754,8 +808,8 @@ func TestGatherRefreshingWithExpansion(t *testing.T) { acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) fields2 := map[string]interface{}{ - "C1": float32(1.3), - "C2": float32(1.4), + "C1": 1.3, + "C2": 1.4, } tags2 := map[string]string{ "instance": "I2", @@ -771,12 +825,12 @@ func TestGatherRefreshingWithExpansion(t *testing.T) { vistaAndNewer: true, } m.query = fpm - fpm.Open() + _ = fpm.Open() var acc2 testutil.Accumulator fields3 := map[string]interface{}{ - "C1": float32(1.5), - "C2": float32(1.6), + "C1": 1.5, + "C2": 1.6, } tags3 := map[string]string{ "instance": "I3", @@ -812,7 +866,7 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, false) + perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, false, false) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} fpm := &FakePerformanceQuery{ counters: createCounterMap(append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps1...), []float64{0, 0, 1.1, 1.2, 1.3, 1.4}, []uint32{0, 0, 0, 0, 0, 0}), @@ -836,8 +890,8 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { require.Len(t, acc1.Metrics, 2) fields1 := map[string]interface{}{ - "C1": float32(1.1), - "C2": float32(1.2), + "C1": 1.1, + "C2": 1.2, } tags1 := map[string]string{ "instance": "I1", @@ -846,8 +900,8 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) fields2 := map[string]interface{}{ - "C1": float32(1.3), - "C2": float32(1.4), + "C1": 1.3, + "C2": 1.4, } tags2 := map[string]string{ "instance": "I2", @@ -865,12 +919,12 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { vistaAndNewer: true, } m.query = fpm - fpm.Open() + _ = fpm.Open() var acc2 testutil.Accumulator fields3 := map[string]interface{}{ - "C1": float32(1.5), - "C2": float32(1.6), + "C1": 1.5, + "C2": 1.6, } tags3 := map[string]string{ "instance": "I3", @@ -887,7 +941,7 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2) acc2.AssertContainsTaggedFields(t, measurement, fields3, tags3) //test changed configuration - perfObjects = createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2", "C3"}, true, false) + perfObjects = createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2", "C3"}, true, false, false) cps3 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I1)\\C3", "\\O(I2)\\C1", "\\O(I2)\\C2", "\\O(I2)\\C3"} fpm = &FakePerformanceQuery{ counters: createCounterMap(append([]string{"\\O(*)\\C1", "\\O(*)\\C2", "\\O(*)\\C3"}, cps3...), []float64{0, 0, 0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6}, []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0}), @@ -901,7 +955,7 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { m.query = fpm m.Object = perfObjects - fpm.Open() + _ = fpm.Open() time.Sleep(time.Duration(m.CountersRefreshInterval)) @@ -910,18 +964,18 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { require.NoError(t, err) require.Len(t, acc3.Metrics, 2) fields4 := map[string]interface{}{ - "C1": float32(1.1), - "C2": float32(1.2), - "C3": float32(1.3), + "C1": 1.1, + "C2": 1.2, + "C3": 1.3, } tags4 := map[string]string{ "instance": "I1", "objectname": "O", } fields5 := map[string]interface{}{ - "C1": float32(1.4), - "C2": float32(1.5), - "C3": float32(1.6), + "C1": 1.4, + "C2": 1.5, + "C3": 1.6, } tags5 := map[string]string{ "instance": "I2", @@ -934,9 +988,12 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { } func TestGatherTotalNoExpansion(t *testing.T) { + if testing.Short() { + t.Skip("Skipping long taking test in short mode") + } var err error measurement := "m" - perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, true) + perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, true, false) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"} m := Win_PerfCounters{ Log: testutil.Logger{}, @@ -957,8 +1014,8 @@ func TestGatherTotalNoExpansion(t *testing.T) { require.Len(t, m.counters, 2) require.Len(t, acc1.Metrics, 2) fields1 := map[string]interface{}{ - "C1": float32(1.1), - "C2": float32(1.2), + "C1": 1.1, + "C2": 1.2, } tags1 := map[string]string{ "instance": "I1", @@ -967,8 +1024,8 @@ func TestGatherTotalNoExpansion(t *testing.T) { acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) fields2 := map[string]interface{}{ - "C1": float32(1.3), - "C2": float32(1.4), + "C1": 1.3, + "C2": 1.4, } tags2 := map[string]string{ "instance": "_Total", @@ -992,6 +1049,67 @@ func TestGatherTotalNoExpansion(t *testing.T) { acc2.AssertDoesNotContainsTaggedFields(t, measurement, fields2, tags2) } +func TestGatherRaw(t *testing.T) { + if testing.Short() { + t.Skip("Skipping long taking test in short mode") + } + var err error + measurement := "m" + perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, true, true) + cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"} + m := Win_PerfCounters{ + Log: testutil.Logger{}, + PrintValid: false, + UseWildcardsExpansion: false, + Object: perfObjects, + query: &FakePerformanceQuery{ + counters: createCounterMap(append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps1...), []float64{0, 0, 1.1, 2.2, 3.3, 4.4}, []uint32{0, 0, 0, 0, 0, 0}), + expandPaths: map[string][]string{ + "\\O(*)\\C1": {cps1[0], cps1[2]}, + "\\O(*)\\C2": {cps1[1], cps1[3]}, + }, + vistaAndNewer: true, + }} + var acc1 testutil.Accumulator + err = m.Gather(&acc1) + require.NoError(t, err) + assert.Len(t, m.counters, 2) + assert.Len(t, acc1.Metrics, 2) + fields1 := map[string]interface{}{ + "C1_Raw": int64(1), + "C2_Raw": int64(2), + } + tags1 := map[string]string{ + "instance": "I1", + "objectname": "O", + } + acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) + + fields2 := map[string]interface{}{ + "C1_Raw": int64(3), + "C2_Raw": int64(4), + } + tags2 := map[string]string{ + "instance": "_Total", + "objectname": "O", + } + acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2) + + m.UseWildcardsExpansion = true + m.counters = nil + m.lastRefreshed = time.Time{} + + var acc2 testutil.Accumulator + err = m.Gather(&acc2) + require.NoError(t, err) + assert.Len(t, m.counters, 4) //expanded counters + assert.Len(t, acc2.Metrics, 2) + + acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1) + + acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2) +} + // list of nul terminated strings from WinAPI var unicodeStringListWithEnglishChars = []uint16{0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x6b, 0x28, 0x30, 0x20, 0x43, 0x3a, 0x29, 0x5c, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x20, 0x51, 0x75, 0x65, 0x75, 0x65, 0x20, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x6b, 0x28, 0x5f, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x29, 0x5c, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x20, 0x51, 0x75, 0x65, 0x75, 0x65, 0x20, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0} var unicodeStringListWithCzechChars = []uint16{0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x46, 0x79, 0x7a, 0x69, 0x63, 0x6b, 0xfd, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x28, 0x30, 0x20, 0x43, 0x3a, 0x29, 0x5c, 0x41, 0x6b, 0x74, 0x75, 0xe1, 0x6c, 0x6e, 0xed, 0x20, 0x64, 0xe9, 0x6c, 0x6b, 0x61, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x79, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x75, 0x0, 0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x46, 0x79, 0x7a, 0x69, 0x63, 0x6b, 0xfd, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x28, 0x5f, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x29, 0x5c, 0x41, 0x6b, 0x74, 0x75, 0xe1, 0x6c, 0x6e, 0xed, 0x20, 0x64, 0xe9, 0x6c, 0x6b, 0x61, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x79, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x75, 0x0, 0x0} @@ -1027,14 +1145,14 @@ func TestUTF16ToStringArray(t *testing.T) { func TestNoWildcards(t *testing.T) { m := Win_PerfCounters{ - Object: createPerfObject("measurement", "object", []string{"instance"}, []string{"counter*"}, false, false), + Object: createPerfObject("measurement", "object", []string{"instance"}, []string{"counter*"}, false, false, false), UseWildcardsExpansion: true, LocalizeWildcardsExpansion: false, Log: testutil.Logger{}, } require.Error(t, m.Init()) m = Win_PerfCounters{ - Object: createPerfObject("measurement", "object?", []string{"instance"}, []string{"counter"}, false, false), + Object: createPerfObject("measurement", "object?", []string{"instance"}, []string{"counter"}, false, false, false), UseWildcardsExpansion: true, LocalizeWildcardsExpansion: false, Log: testutil.Logger{}, @@ -1053,7 +1171,7 @@ func TestLocalizeWildcardsExpansion(t *testing.T) { query: &PerformanceQueryImpl{}, CountersRefreshInterval: config.Duration(time.Second * 60), Object: createPerfObject("measurement", "Processor Information", - []string{"_Total"}, []string{counter}, false, false), + []string{"_Total"}, []string{counter}, false, false, false), LocalizeWildcardsExpansion: false, UseWildcardsExpansion: true, Log: testutil.Logger{},