From c03a1446404607e4cd0612430905873811996679 Mon Sep 17 00:00:00 2001 From: Vlasta Hajek Date: Mon, 11 Jun 2018 20:10:53 +0200 Subject: [PATCH] Add option to enable wildcard expansion (#4265) This is needed because wildcard expansion causes counters to be localized. --- plugins/inputs/win_perf_counters/README.md | 29 +- plugins/inputs/win_perf_counters/pdh.go | 2 +- .../win_perf_counters/performance_query.go | 29 ++ .../win_perf_counters/win_perf_counters.go | 130 ++++++-- .../win_perf_counters_integration_test.go | 25 +- .../win_perf_counters_test.go | 312 +++++++++++++++++- 6 files changed, 471 insertions(+), 56 deletions(-) diff --git a/plugins/inputs/win_perf_counters/README.md b/plugins/inputs/win_perf_counters/README.md index f986a278f..422ef6a31 100644 --- a/plugins/inputs/win_perf_counters/README.md +++ b/plugins/inputs/win_perf_counters/README.md @@ -8,9 +8,6 @@ whether the Object, Instance and Counter exist on Telegraf startup. Counter paths are refreshed periodically, see [CountersRefreshInterval](#countersrefreshinterval) configuration parameter for more info. -Wildcards can be used in instance and counter names. Partial wildcards are supported only -in instance names on Windows Vista and newer. - In case of query for all instances `["*"]`, the plugin does not return the instance `_Total` by default. See [IncludeTotal](#includetotal) for more info. @@ -34,18 +31,36 @@ Bool, if set to `true` will print out all matching performance objects. Example: `PrintValid=true` +#### UseWildcardsExpansion + +If `UseWildcardsExpansion` is set to true, wildcards can be used in instance name and counter name +. +Partial wildcard (e.g. `chrome*`) is supported only in instance name on Windows Vista and newer. + +On localized Windows, returned counters will be also localized. + +It also returns instance indexes in instance names. + +If set to `false`, wildcards (not partial) in instance names can still be used, but instance indexes will not be returned in instance names. + +Example: +`UseWildcardsExpansion=true` + #### CountersRefreshInterval Configured counters are matched against available counters at the interval specified by the `CountersRefreshInterval` parameter. Default value is `1m` (1 minute). -If wildcards are used in instance or counter names, they are expanded at this point. +If wildcards are used in instance or counter names, they are expanded at this point, if the `UseWildcardsExpansion` param is set to `true`. Setting `CountersRefreshInterval` too low (order of seconds) can cause Telegraf to create a high CPU load. Set to `0s` to disable periodic refreshing. +Example: +`CountersRefreshInterval=1m` + #### PreVistaSupport _Deprecated. Necessary features on Windows Vista and newer are checked dynamically_ @@ -88,7 +103,7 @@ By default any results containing `_Total` are stripped, unless this is specified as the wanted instance. Alternatively see the option `IncludeTotal` below. -It is also possible to set partial wildcards, eg. `["chrome*"]` +It is also possible to set partial wildcards, eg. `["chrome*"]`, if the `UseWildcardsExpansion` param is set to `true` Some Objects do not have instances to select from at all. Here only one option is valid if you want data back, @@ -101,8 +116,8 @@ Counters key (this is an array) is the counters of the ObjectName you would like returned, it can also be one or more values. Example: `Counters = ["% Idle Time", "% Disk Read Time", "% Disk Write Time"]` -This must be specified for every counter you want the results of, -or use `["*"]` for all the counters for object. +This must be specified for every counter you want the results of, or use `["*"]` for all the counters for object, +if the `UseWildcardsExpansion` param is set to `true` #### Measurement *Optional* diff --git a/plugins/inputs/win_perf_counters/pdh.go b/plugins/inputs/win_perf_counters/pdh.go index f637eb256..e6f7d5196 100644 --- a/plugins/inputs/win_perf_counters/pdh.go +++ b/plugins/inputs/win_perf_counters/pdh.go @@ -352,7 +352,7 @@ func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32, // time.Sleep(2000 * time.Millisecond) // } // } -func PdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *PDH_FMT_COUNTERVALUE_ITEM_DOUBLE) uint32 { +func PdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *byte) uint32 { ret, _, _ := pdh_GetFormattedCounterArrayW.Call( uintptr(hCounter), uintptr(PDH_FMT_DOUBLE|PDH_FMT_NOCAP100), diff --git a/plugins/inputs/win_perf_counters/performance_query.go b/plugins/inputs/win_perf_counters/performance_query.go index 3c82c1cfc..e2e2ed866 100644 --- a/plugins/inputs/win_perf_counters/performance_query.go +++ b/plugins/inputs/win_perf_counters/performance_query.go @@ -9,6 +9,12 @@ import ( "unsafe" ) +//PerformanceQuery is abstraction for PDH_FMT_COUNTERVALUE_ITEM_DOUBLE +type CounterValue struct { + InstanceName string + Value float64 +} + //PerformanceQuery provides wrappers around Windows performance counters API for easy usage in GO type PerformanceQuery interface { Open() error @@ -18,6 +24,7 @@ type PerformanceQuery interface { GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) ExpandWildCardPath(counterPath string) ([]string, error) GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error) + GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error) CollectData() error AddEnglishCounterSupported() bool } @@ -151,6 +158,28 @@ func (m *PerformanceQueryImpl) GetFormattedCounterValueDouble(hCounter PDH_HCOUN } } +func (m *PerformanceQueryImpl) GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error) { + var buffSize uint32 + var itemCount uint32 + ret := PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, nil) + if ret == PDH_MORE_DATA { + buff := make([]byte, buffSize) + ret = PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, &buff[0]) + if ret == ERROR_SUCCESS { + items := (*[1 << 20]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE)(unsafe.Pointer(&buff[0]))[:itemCount] + values := make([]CounterValue, 0, itemCount) + for _, item := range items { + if item.FmtValue.CStatus == PDH_CSTATUS_VALID_DATA || item.FmtValue.CStatus == PDH_CSTATUS_NEW_DATA { + val := CounterValue{UTF16PtrToString(item.SzName), item.FmtValue.DoubleValue} + values = append(values, val) + } + } + return values, nil + } + } + return nil, NewPdhError(ret) +} + func (m *PerformanceQueryImpl) CollectData() error { if m.query == 0 { return errors.New("uninitialised query") diff --git a/plugins/inputs/win_perf_counters/win_perf_counters.go b/plugins/inputs/win_perf_counters/win_perf_counters.go index 97c6e50c6..26f1d3486 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters.go @@ -22,6 +22,10 @@ var sampleConfig = ` ## agent, it will not be gathered. ## Settings: # PrintValid = false # Print All matching performance counters + # If UseWildcardsExpansion params is set to true, wildcards (partial wildcards in instance names and wildcards in counters names) in configured counter paths will be expanded + # and in case of localized Windows, counter paths will be also localized. It also returns instance indexes in instance names. + # If false, wildcards (not partial) in instance names will be still expanded, but instance indexes will not be returned in instance names. + #UseWildcardsExpansion = false # Period after which counters will be reread from configuration and wildcards in counter paths expanded CountersRefreshInterval="1m" @@ -75,6 +79,7 @@ type Win_PerfCounters struct { PreVistaSupport bool Object []perfobject CountersRefreshInterval internal.Duration + UseWildcardsExpansion bool lastRefreshed time.Time counters []*counter @@ -137,45 +142,59 @@ func (m *Win_PerfCounters) SampleConfig() string { return sampleConfig } -func (m *Win_PerfCounters) AddItem(counterPath string, instance string, measurement string, includeTotal bool) error { +//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 { + var err error + var counterHandle PDH_HCOUNTER if !m.query.AddEnglishCounterSupported() { - _, err := m.query.AddCounterToQuery(counterPath) + counterHandle, err = m.query.AddCounterToQuery(counterPath) if err != nil { return err } } else { - counterHandle, err := m.query.AddEnglishCounterToQuery(counterPath) + counterHandle, err = m.query.AddEnglishCounterToQuery(counterPath) if err != nil { return err } + + } + + if m.UseWildcardsExpansion { + origInstance := instance counterPath, err = m.query.GetCounterPath(counterHandle) if err != nil { return err } - } - - counters, err := m.query.ExpandWildCardPath(counterPath) - if err != nil { - return err - } - - for _, counterPath := range counters { - var err error - counterHandle, err := m.query.AddCounterToQuery(counterPath) - - parsedObjectName, parsedInstance, parsedCounter, err := extractObjectInstanceCounterFromQuery(counterPath) + counters, err := m.query.ExpandWildCardPath(counterPath) if err != nil { return err } - if parsedInstance == "_Total" && instance == "*" && !includeTotal { - continue - } + for _, counterPath := range counters { + var err error + counterHandle, err := m.query.AddCounterToQuery(counterPath) - newItem := &counter{counterPath, parsedObjectName, parsedCounter, parsedInstance, measurement, + objectName, instance, counterName, err = extractObjectInstanceCounterFromQuery(counterPath) + if err != nil { + return err + } + + if instance == "_Total" && origInstance == "*" && !includeTotal { + continue + } + + newItem := &counter{counterPath, objectName, counterName, instance, measurement, + includeTotal, counterHandle} + m.counters = append(m.counters, newItem) + + if m.PrintValid { + log.Printf("Valid: %s\n", counterPath) + } + } + } else { + newItem := &counter{counterPath, objectName, counterName, instance, measurement, includeTotal, counterHandle} m.counters = append(m.counters, newItem) - if m.PrintValid { log.Printf("Valid: %s\n", counterPath) } @@ -199,7 +218,7 @@ func (m *Win_PerfCounters) ParseConfig() error { counterPath = "\\" + objectname + "(" + instance + ")\\" + counter } - err := m.AddItem(counterPath, instance, PerfObject.Measurement, PerfObject.IncludeTotal) + err := m.AddItem(counterPath, objectname, instance, counter, PerfObject.Measurement, PerfObject.IncludeTotal) if err != nil { if PerfObject.FailOnMissing || PerfObject.WarnOnMissing { @@ -225,7 +244,9 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { var err error if m.lastRefreshed.IsZero() || (m.CountersRefreshInterval.Duration.Nanoseconds() > 0 && m.lastRefreshed.Add(m.CountersRefreshInterval.Duration).Before(time.Now())) { - m.counters = m.counters[:0] + if m.counters != nil { + m.counters = m.counters[:0] + } err = m.query.Open() if err != nil { @@ -261,22 +282,61 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { // For iterate over the known metrics and get the samples. for _, metric := range m.counters { // collect - value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle) - if err == nil { - measurement := sanitizedChars.Replace(metric.measurement) - if measurement == "" { - measurement = "win_perf_counters" - } + if m.UseWildcardsExpansion { + value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle) + if err == nil { + measurement := sanitizedChars.Replace(metric.measurement) + if measurement == "" { + measurement = "win_perf_counters" + } - var instance = InstanceGrouping{measurement, metric.instance, metric.objectName} - if collectFields[instance] == nil { - collectFields[instance] = make(map[string]interface{}) + var instance = InstanceGrouping{measurement, metric.instance, metric.objectName} + if collectFields[instance] == nil { + collectFields[instance] = make(map[string]interface{}) + } + collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value) + } else { + //ignore invalid data from as some counters from process instances returns this sometimes + if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE { + return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err) + } } - collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value) } else { - //ignore invalid data from as some counters from process instances returns this sometimes - if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE { - return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err) + counterValues, err := m.query.GetFormattedCounterArrayDouble(metric.counterHandle) + if err == nil { + for _, cValue := range counterValues { + var add bool + if metric.includeTotal { + // If IncludeTotal is set, include all. + add = true + } else if metric.instance == "*" && !strings.Contains(cValue.InstanceName, "_Total") { + // Catch if set to * and that it is not a '*_Total*' instance. + add = true + } else if metric.instance == cValue.InstanceName { + // Catch if we set it to total or some form of it + add = true + } else if strings.Contains(metric.instance, "#") && strings.HasPrefix(metric.instance, cValue.InstanceName) { + // If you are using a multiple instance identifier such as "w3wp#1" + // phd.dll returns only the first 2 characters of the identifier. + add = true + cValue.InstanceName = metric.instance + } else if metric.instance == "------" { + add = true + } + + if add { + measurement := sanitizedChars.Replace(metric.measurement) + if measurement == "" { + measurement = "win_perf_counters" + } + var instance = InstanceGrouping{measurement, cValue.InstanceName, metric.objectName} + + if collectFields[instance] == nil { + collectFields[instance] = make(map[string]interface{}) + } + collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(cValue.Value) + } + } } } } 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 7a1e5403e..c5136d30b 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 @@ -81,6 +81,29 @@ func TestWinPerformanceQueryImpl(t *testing.T) { require.NoError(t, err) require.NotNil(t, paths) assert.True(t, len(paths) > 1) + + err = query.Open() + require.NoError(t, err) + + counterPath = "\\Process(*)\\% Processor Time" + hCounter, err = query.AddEnglishCounterToQuery(counterPath) + require.NoError(t, err) + assert.NotEqual(t, 0, hCounter) + + err = query.CollectData() + require.NoError(t, err) + time.Sleep(time.Second) + + err = query.CollectData() + require.NoError(t, err) + + arr, err := query.GetFormattedCounterArrayDouble(hCounter) + require.NoError(t, err) + assert.True(t, len(arr) > 0, "Too") + + err = query.Close() + require.NoError(t, err) + } func TestWinPerfcountersConfigGet1(t *testing.T) { @@ -573,7 +596,7 @@ func TestWinPerfcountersCollect2(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}} + m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}, UseWildcardsExpansion: true} var acc testutil.Accumulator err := m.Gather(&acc) require.NoError(t, err) 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 41345a94e..14aec375b 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters_test.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters_test.go @@ -25,6 +25,14 @@ type FakePerformanceQuery struct { openCalled bool } +func (m *testCounter) ToCounterValue() *CounterValue { + _, inst, _, _ := extractObjectInstanceCounterFromQuery(m.path) + if inst == "" { + inst = "--" + } + return &CounterValue{inst, m.value} +} + func (m *FakePerformanceQuery) Open() error { if m.openCalled { err := m.Close() @@ -102,6 +110,48 @@ func (m *FakePerformanceQuery) GetFormattedCounterValueDouble(counterHandle PDH_ } return 0, fmt.Errorf("GetFormattedCounterValueDouble: invalid handle: %d", counterHandle) } +func (m *FakePerformanceQuery) findCounterByPath(counterPath string) *testCounter { + for _, c := range m.counters { + if c.path == counterPath { + return &c + } + } + return nil +} + +func (m *FakePerformanceQuery) findCounterByHandle(counterHandle PDH_HCOUNTER) *testCounter { + for _, c := range m.counters { + if c.handle == counterHandle { + return &c + } + } + return nil +} + +func (m *FakePerformanceQuery) GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error) { + if !m.openCalled { + return nil, errors.New("GetFormattedCounterArrayDouble: 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 && counter.value > 0 { + counters = append(counters, *counter.ToCounterValue()) + } else { + return nil, fmt.Errorf("GetFormattedCounterArrayDouble: invalid counter : %s", p) + } + } + return counters, nil + } else { + return nil, fmt.Errorf("GetFormattedCounterArrayDouble: invalid counter : %d", hCounter) + } + } + } + return nil, fmt.Errorf("GetFormattedCounterArrayDouble: invalid counter : %d, no paths found", hCounter) +} func (m *FakePerformanceQuery) CollectData() error { if !m.openCalled { @@ -152,7 +202,7 @@ func TestAddItemSimple(t *testing.T) { }} err = m.query.Open() require.NoError(t, err) - err = m.AddItem(cps1[0], "I", "test", false) + err = m.AddItem(cps1[0], "O", "I", "c", "test", false) require.NoError(t, err) err = m.query.Close() require.NoError(t, err) @@ -161,7 +211,7 @@ func TestAddItemSimple(t *testing.T) { func TestAddItemInvalidCountPath(t *testing.T) { var err error cps1 := []string{"\\O\\C"} - m := Win_PerfCounters{PrintValid: false, Object: nil, query: &FakePerformanceQuery{ + m := Win_PerfCounters{PrintValid: false, Object: nil, UseWildcardsExpansion: true, query: &FakePerformanceQuery{ counters: createCounterMap(cps1, []float64{1.1}), expandPaths: map[string][]string{ cps1[0]: {"\\O/C"}, @@ -170,7 +220,7 @@ func TestAddItemInvalidCountPath(t *testing.T) { }} err = m.query.Open() require.NoError(t, err) - err = m.AddItem("\\O\\C", "*", "test", false) + err = m.AddItem("\\O\\C", "O", "------", "C", "test", false) require.Error(t, err) err = m.query.Close() require.NoError(t, err) @@ -197,13 +247,24 @@ func TestParseConfigBasic(t *testing.T) { assert.Len(t, m.counters, 4) err = m.query.Close() require.NoError(t, err) + + m.UseWildcardsExpansion = true + m.counters = nil + + err = m.query.Open() + require.NoError(t, err) + err = m.ParseConfig() + require.NoError(t, err) + assert.Len(t, m.counters, 4) + err = m.query.Close() + require.NoError(t, err) } func TestParseConfigNoInstance(t *testing.T) { var err error perfObjects := createPerfObject("m", "O", []string{"------"}, []string{"C1", "C2"}, false, false) cps1 := []string{"\\O\\C1", "\\O\\C2"} - m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{ + m := Win_PerfCounters{PrintValid: false, Object: perfObjects, UseWildcardsExpansion: false, query: &FakePerformanceQuery{ counters: createCounterMap(cps1, []float64{1.1, 1.2}), expandPaths: map[string][]string{ cps1[0]: {cps1[0]}, @@ -218,6 +279,17 @@ func TestParseConfigNoInstance(t *testing.T) { assert.Len(t, m.counters, 2) err = m.query.Close() require.NoError(t, err) + + m.UseWildcardsExpansion = true + m.counters = nil + + err = m.query.Open() + require.NoError(t, err) + err = m.ParseConfig() + require.NoError(t, err) + assert.Len(t, m.counters, 2) + err = m.query.Close() + require.NoError(t, err) } func TestParseConfigInvalidCounterError(t *testing.T) { @@ -239,6 +311,16 @@ func TestParseConfigInvalidCounterError(t *testing.T) { require.Error(t, err) err = m.query.Close() require.NoError(t, err) + + m.UseWildcardsExpansion = true + m.counters = nil + + err = m.query.Open() + require.NoError(t, err) + err = m.ParseConfig() + require.Error(t, err) + err = m.query.Close() + require.NoError(t, err) } func TestParseConfigInvalidCounterNoError(t *testing.T) { @@ -260,13 +342,24 @@ func TestParseConfigInvalidCounterNoError(t *testing.T) { require.NoError(t, err) err = m.query.Close() require.NoError(t, err) + + m.UseWildcardsExpansion = true + m.counters = nil + + err = m.query.Open() + require.NoError(t, err) + err = m.ParseConfig() + require.NoError(t, err) + err = m.query.Close() + require.NoError(t, err) + } -func TestParseConfigTotal(t *testing.T) { +func TestParseConfigTotalExpansion(t *testing.T) { var err error perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"} - m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{ + m := Win_PerfCounters{PrintValid: false, UseWildcardsExpansion: true, Object: perfObjects, query: &FakePerformanceQuery{ counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}), expandPaths: map[string][]string{ "\\O(*)\\*": cps1, @@ -283,7 +376,7 @@ func TestParseConfigTotal(t *testing.T) { perfObjects[0].IncludeTotal = false - m = Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{ + m = Win_PerfCounters{PrintValid: false, UseWildcardsExpansion: true, Object: perfObjects, query: &FakePerformanceQuery{ counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}), expandPaths: map[string][]string{ "\\O(*)\\*": cps1, @@ -303,7 +396,7 @@ func TestParseConfigExpand(t *testing.T) { var err error perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, false, false) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} - m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{ + m := Win_PerfCounters{PrintValid: false, UseWildcardsExpansion: true, Object: perfObjects, query: &FakePerformanceQuery{ counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}), expandPaths: map[string][]string{ "\\O(*)\\*": cps1, @@ -346,6 +439,17 @@ func TestSimpleGather(t *testing.T) { "objectname": "O", } acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) + + m.UseWildcardsExpansion = true + m.counters = nil + m.lastRefreshed = time.Time{} + + var acc2 testutil.Accumulator + + err = m.Gather(&acc2) + require.NoError(t, err) + acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) + } func TestGatherInvalidDataIgnore(t *testing.T) { @@ -377,15 +481,25 @@ func TestGatherInvalidDataIgnore(t *testing.T) { "objectname": "O", } acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) + + m.UseWildcardsExpansion = true + m.counters = nil + m.lastRefreshed = time.Time{} + + var acc2 testutil.Accumulator + err = m.Gather(&acc2) + require.NoError(t, err) + acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) } -func TestGatherRefreshing(t *testing.T) { +//tests with expansion +func TestGatherRefreshingWithExpansion(t *testing.T) { var err error if testing.Short() { t.Skip("Skipping long taking test in short mode") } measurement := "test" - perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"*"}, false, false) + perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"*"}, true, 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}), @@ -394,7 +508,7 @@ func TestGatherRefreshing(t *testing.T) { }, addEnglishSupported: true, } - m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: fpm, CountersRefreshInterval: internal.Duration{Duration: time.Second * 10}} + m := Win_PerfCounters{PrintValid: false, Object: perfObjects, UseWildcardsExpansion: true, query: fpm, CountersRefreshInterval: internal.Duration{Duration: time.Second * 10}} var acc1 testutil.Accumulator err = m.Gather(&acc1) assert.Len(t, m.counters, 4) @@ -464,6 +578,181 @@ func TestGatherRefreshing(t *testing.T) { } +func TestGatherRefreshingWithoutExpansion(t *testing.T) { + var err error + if testing.Short() { + t.Skip("Skipping long taking test in short mode") + } + measurement := "test" + perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, 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}), + expandPaths: map[string][]string{ + "\\O(*)\\C1": {cps1[0], cps1[2]}, + "\\O(*)\\C2": {cps1[1], cps1[3]}, + }, + addEnglishSupported: true, + } + m := Win_PerfCounters{PrintValid: false, Object: perfObjects, UseWildcardsExpansion: false, query: fpm, CountersRefreshInterval: internal.Duration{Duration: time.Second * 10}} + var acc1 testutil.Accumulator + err = m.Gather(&acc1) + assert.Len(t, m.counters, 2) + require.NoError(t, err) + assert.Len(t, acc1.Metrics, 2) + + fields1 := map[string]interface{}{ + "C1": float32(1.1), + "C2": float32(1.2), + } + tags1 := map[string]string{ + "instance": "I1", + "objectname": "O", + } + acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) + + fields2 := map[string]interface{}{ + "C1": float32(1.3), + "C2": float32(1.4), + } + tags2 := map[string]string{ + "instance": "I2", + "objectname": "O", + } + acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2) + //test finding new instance + cps2 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2", "\\O(I3)\\C1", "\\O(I3)\\C2"} + fpm = &FakePerformanceQuery{ + counters: createCounterMap(append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps2...), []float64{0, 0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6}), + expandPaths: map[string][]string{ + "\\O(*)\\C1": {cps2[0], cps2[2], cps2[4]}, + "\\O(*)\\C2": {cps2[1], cps2[3], cps2[5]}, + }, + addEnglishSupported: true, + } + m.query = fpm + fpm.Open() + var acc2 testutil.Accumulator + + fields3 := map[string]interface{}{ + "C1": float32(1.5), + "C2": float32(1.6), + } + tags3 := map[string]string{ + "instance": "I3", + "objectname": "O", + } + + //test before elapsing CounterRefreshRate counters are not refreshed + err = m.Gather(&acc2) + require.NoError(t, err) + assert.Len(t, m.counters, 2) + assert.Len(t, acc2.Metrics, 3) + + acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1) + 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) + 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}), + expandPaths: map[string][]string{ + "\\O(*)\\C1": {cps3[0], cps3[3]}, + "\\O(*)\\C2": {cps3[1], cps3[4]}, + "\\O(*)\\C3": {cps3[2], cps3[5]}, + }, + addEnglishSupported: true, + } + m.query = fpm + m.Object = perfObjects + + fpm.Open() + + time.Sleep(m.CountersRefreshInterval.Duration) + + var acc3 testutil.Accumulator + err = m.Gather(&acc3) + require.NoError(t, err) + assert.Len(t, acc3.Metrics, 2) + fields4 := map[string]interface{}{ + "C1": float32(1.1), + "C2": float32(1.2), + "C3": float32(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), + } + tags5 := map[string]string{ + "instance": "I2", + "objectname": "O", + } + + acc3.AssertContainsTaggedFields(t, measurement, fields4, tags4) + acc3.AssertContainsTaggedFields(t, measurement, fields5, tags5) + +} + +func TestGatherTotalNoExpansion(t *testing.T) { + var err error + measurement := "m" + perfObjects := createPerfObject(measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, true) + cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"} + m := Win_PerfCounters{PrintValid: false, UseWildcardsExpansion: false, Object: perfObjects, query: &FakePerformanceQuery{ + counters: createCounterMap(append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps1...), []float64{0, 0, 1.1, 1.2, 1.3, 1.4}), + expandPaths: map[string][]string{ + "\\O(*)\\C1": {cps1[0], cps1[2]}, + "\\O(*)\\C2": {cps1[1], cps1[3]}, + }, + addEnglishSupported: 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": float32(1.1), + "C2": float32(1.2), + } + tags1 := map[string]string{ + "instance": "I1", + "objectname": "O", + } + acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) + + fields2 := map[string]interface{}{ + "C1": float32(1.3), + "C2": float32(1.4), + } + tags2 := map[string]string{ + "instance": "_Total", + "objectname": "O", + } + acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2) + + perfObjects[0].IncludeTotal = false + + m.counters = nil + m.lastRefreshed = time.Time{} + + var acc2 testutil.Accumulator + err = m.Gather(&acc2) + require.NoError(t, err) + assert.Len(t, m.counters, 2) + assert.Len(t, acc2.Metrics, 1) + + acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1) + + acc2.AssertDoesNotContainsTaggedFields(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} @@ -495,5 +784,4 @@ func TestUTF16ToStringArray(t *testing.T) { czechStrings := UTF16ToStringArray(unicodeStringListWithCzechChars) assert.True(t, assert.ObjectsAreEqual(czechStrings, stringArrayWithCzechChars), "Not equal czech arrays") - }