Add option to enable wildcard expansion (#4265)

This is needed because wildcard expansion causes counters to be localized.
This commit is contained in:
Vlasta Hajek 2018-06-11 20:10:53 +02:00 committed by Daniel Nelson
parent 87f711a19a
commit 1690f36b09
6 changed files with 471 additions and 56 deletions

View File

@ -8,9 +8,6 @@ whether the Object, Instance and Counter exist on Telegraf startup.
Counter paths are refreshed periodically, see [CountersRefreshInterval](#countersrefreshinterval) Counter paths are refreshed periodically, see [CountersRefreshInterval](#countersrefreshinterval)
configuration parameter for more info. 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` In case of query for all instances `["*"]`, the plugin does not return the instance `_Total`
by default. See [IncludeTotal](#includetotal) for more info. 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: Example:
`PrintValid=true` `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 #### CountersRefreshInterval
Configured counters are matched against available counters at the interval Configured counters are matched against available counters at the interval
specified by the `CountersRefreshInterval` parameter. Default value is `1m` (1 minute). 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 Setting `CountersRefreshInterval` too low (order of seconds) can cause Telegraf to create
a high CPU load. a high CPU load.
Set to `0s` to disable periodic refreshing. Set to `0s` to disable periodic refreshing.
Example:
`CountersRefreshInterval=1m`
#### PreVistaSupport #### PreVistaSupport
_Deprecated. Necessary features on Windows Vista and newer are checked dynamically_ _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. unless this is specified as the wanted instance.
Alternatively see the option `IncludeTotal` below. 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. Some Objects do not have instances to select from at all.
Here only one option is valid if you want data back, 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. you would like returned, it can also be one or more values.
Example: `Counters = ["% Idle Time", "% Disk Read Time", "% Disk Write Time"]` Example: `Counters = ["% Idle Time", "% Disk Read Time", "% Disk Write Time"]`
This must be specified for every counter you want the results of, This must be specified for every counter you want the results of, or use `["*"]` for all the counters for object,
or use `["*"]` for all the counters for object. if the `UseWildcardsExpansion` param is set to `true`
#### Measurement #### Measurement
*Optional* *Optional*

View File

@ -352,7 +352,7 @@ func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32,
// time.Sleep(2000 * time.Millisecond) // 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( ret, _, _ := pdh_GetFormattedCounterArrayW.Call(
uintptr(hCounter), uintptr(hCounter),
uintptr(PDH_FMT_DOUBLE|PDH_FMT_NOCAP100), uintptr(PDH_FMT_DOUBLE|PDH_FMT_NOCAP100),

View File

@ -9,6 +9,12 @@ import (
"unsafe" "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 //PerformanceQuery provides wrappers around Windows performance counters API for easy usage in GO
type PerformanceQuery interface { type PerformanceQuery interface {
Open() error Open() error
@ -18,6 +24,7 @@ type PerformanceQuery interface {
GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) GetCounterPath(counterHandle PDH_HCOUNTER) (string, error)
ExpandWildCardPath(counterPath string) ([]string, error) ExpandWildCardPath(counterPath string) ([]string, error)
GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error) GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error)
GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error)
CollectData() error CollectData() error
AddEnglishCounterSupported() bool 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 { func (m *PerformanceQueryImpl) CollectData() error {
if m.query == 0 { if m.query == 0 {
return errors.New("uninitialised query") return errors.New("uninitialised query")

View File

@ -22,6 +22,10 @@ var sampleConfig = `
## agent, it will not be gathered. ## agent, it will not be gathered.
## Settings: ## Settings:
# PrintValid = false # Print All matching performance counters # 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 # Period after which counters will be reread from configuration and wildcards in counter paths expanded
CountersRefreshInterval="1m" CountersRefreshInterval="1m"
@ -75,6 +79,7 @@ type Win_PerfCounters struct {
PreVistaSupport bool PreVistaSupport bool
Object []perfobject Object []perfobject
CountersRefreshInterval internal.Duration CountersRefreshInterval internal.Duration
UseWildcardsExpansion bool
lastRefreshed time.Time lastRefreshed time.Time
counters []*counter counters []*counter
@ -137,45 +142,59 @@ func (m *Win_PerfCounters) SampleConfig() string {
return sampleConfig 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() { if !m.query.AddEnglishCounterSupported() {
_, err := m.query.AddCounterToQuery(counterPath) counterHandle, err = m.query.AddCounterToQuery(counterPath)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
counterHandle, err := m.query.AddEnglishCounterToQuery(counterPath) counterHandle, err = m.query.AddEnglishCounterToQuery(counterPath)
if err != nil { if err != nil {
return err return err
} }
}
if m.UseWildcardsExpansion {
origInstance := instance
counterPath, err = m.query.GetCounterPath(counterHandle) counterPath, err = m.query.GetCounterPath(counterHandle)
if err != nil { if err != nil {
return err return err
} }
} counters, err := m.query.ExpandWildCardPath(counterPath)
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)
if err != nil { if err != nil {
return err return err
} }
if parsedInstance == "_Total" && instance == "*" && !includeTotal { for _, counterPath := range counters {
continue 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} includeTotal, counterHandle}
m.counters = append(m.counters, newItem) m.counters = append(m.counters, newItem)
if m.PrintValid { if m.PrintValid {
log.Printf("Valid: %s\n", counterPath) log.Printf("Valid: %s\n", counterPath)
} }
@ -199,7 +218,7 @@ func (m *Win_PerfCounters) ParseConfig() error {
counterPath = "\\" + objectname + "(" + instance + ")\\" + counter 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 err != nil {
if PerfObject.FailOnMissing || PerfObject.WarnOnMissing { if PerfObject.FailOnMissing || PerfObject.WarnOnMissing {
@ -225,7 +244,9 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
var err error var err error
if m.lastRefreshed.IsZero() || (m.CountersRefreshInterval.Duration.Nanoseconds() > 0 && m.lastRefreshed.Add(m.CountersRefreshInterval.Duration).Before(time.Now())) { 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() err = m.query.Open()
if err != nil { 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 iterate over the known metrics and get the samples.
for _, metric := range m.counters { for _, metric := range m.counters {
// collect // collect
value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle) if m.UseWildcardsExpansion {
if err == nil { value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle)
measurement := sanitizedChars.Replace(metric.measurement) if err == nil {
if measurement == "" { measurement := sanitizedChars.Replace(metric.measurement)
measurement = "win_perf_counters" if measurement == "" {
} measurement = "win_perf_counters"
}
var instance = InstanceGrouping{measurement, metric.instance, metric.objectName} var instance = InstanceGrouping{measurement, metric.instance, metric.objectName}
if collectFields[instance] == nil { if collectFields[instance] == nil {
collectFields[instance] = make(map[string]interface{}) 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 { } else {
//ignore invalid data from as some counters from process instances returns this sometimes counterValues, err := m.query.GetFormattedCounterArrayDouble(metric.counterHandle)
if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE { if err == nil {
return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err) 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)
}
}
} }
} }
} }

View File

@ -81,6 +81,29 @@ func TestWinPerformanceQueryImpl(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, paths) require.NotNil(t, paths)
assert.True(t, len(paths) > 1) 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) { func TestWinPerfcountersConfigGet1(t *testing.T) {
@ -573,7 +596,7 @@ func TestWinPerfcountersCollect2(t *testing.T) {
perfobjects[0] = PerfObject 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 var acc testutil.Accumulator
err := m.Gather(&acc) err := m.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)

View File

@ -25,6 +25,14 @@ type FakePerformanceQuery struct {
openCalled bool openCalled bool
} }
func (m *testCounter) ToCounterValue() *CounterValue {
_, inst, _, _ := extractObjectInstanceCounterFromQuery(m.path)
if inst == "" {
inst = "--"
}
return &CounterValue{inst, m.value}
}
func (m *FakePerformanceQuery) Open() error { func (m *FakePerformanceQuery) Open() error {
if m.openCalled { if m.openCalled {
err := m.Close() err := m.Close()
@ -102,6 +110,48 @@ func (m *FakePerformanceQuery) GetFormattedCounterValueDouble(counterHandle PDH_
} }
return 0, fmt.Errorf("GetFormattedCounterValueDouble: invalid handle: %d", counterHandle) 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 { func (m *FakePerformanceQuery) CollectData() error {
if !m.openCalled { if !m.openCalled {
@ -152,7 +202,7 @@ func TestAddItemSimple(t *testing.T) {
}} }}
err = m.query.Open() err = m.query.Open()
require.NoError(t, err) 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) require.NoError(t, err)
err = m.query.Close() err = m.query.Close()
require.NoError(t, err) require.NoError(t, err)
@ -161,7 +211,7 @@ func TestAddItemSimple(t *testing.T) {
func TestAddItemInvalidCountPath(t *testing.T) { func TestAddItemInvalidCountPath(t *testing.T) {
var err error var err error
cps1 := []string{"\\O\\C"} 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}), counters: createCounterMap(cps1, []float64{1.1}),
expandPaths: map[string][]string{ expandPaths: map[string][]string{
cps1[0]: {"\\O/C"}, cps1[0]: {"\\O/C"},
@ -170,7 +220,7 @@ func TestAddItemInvalidCountPath(t *testing.T) {
}} }}
err = m.query.Open() err = m.query.Open()
require.NoError(t, err) require.NoError(t, err)
err = m.AddItem("\\O\\C", "*", "test", false) err = m.AddItem("\\O\\C", "O", "------", "C", "test", false)
require.Error(t, err) require.Error(t, err)
err = m.query.Close() err = m.query.Close()
require.NoError(t, err) require.NoError(t, err)
@ -197,13 +247,24 @@ func TestParseConfigBasic(t *testing.T) {
assert.Len(t, m.counters, 4) assert.Len(t, m.counters, 4)
err = m.query.Close() err = m.query.Close()
require.NoError(t, err) 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) { func TestParseConfigNoInstance(t *testing.T) {
var err error var err error
perfObjects := createPerfObject("m", "O", []string{"------"}, []string{"C1", "C2"}, false, false) perfObjects := createPerfObject("m", "O", []string{"------"}, []string{"C1", "C2"}, false, false)
cps1 := []string{"\\O\\C1", "\\O\\C2"} 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}), counters: createCounterMap(cps1, []float64{1.1, 1.2}),
expandPaths: map[string][]string{ expandPaths: map[string][]string{
cps1[0]: {cps1[0]}, cps1[0]: {cps1[0]},
@ -218,6 +279,17 @@ func TestParseConfigNoInstance(t *testing.T) {
assert.Len(t, m.counters, 2) assert.Len(t, m.counters, 2)
err = m.query.Close() err = m.query.Close()
require.NoError(t, err) 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) { func TestParseConfigInvalidCounterError(t *testing.T) {
@ -239,6 +311,16 @@ func TestParseConfigInvalidCounterError(t *testing.T) {
require.Error(t, err) require.Error(t, err)
err = m.query.Close() err = m.query.Close()
require.NoError(t, err) 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) { func TestParseConfigInvalidCounterNoError(t *testing.T) {
@ -260,13 +342,24 @@ func TestParseConfigInvalidCounterNoError(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
err = m.query.Close() err = m.query.Close()
require.NoError(t, err) 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 var err error
perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true) perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"} 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}), counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{ expandPaths: map[string][]string{
"\\O(*)\\*": cps1, "\\O(*)\\*": cps1,
@ -283,7 +376,7 @@ func TestParseConfigTotal(t *testing.T) {
perfObjects[0].IncludeTotal = false 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}), counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{ expandPaths: map[string][]string{
"\\O(*)\\*": cps1, "\\O(*)\\*": cps1,
@ -303,7 +396,7 @@ func TestParseConfigExpand(t *testing.T) {
var err error var err error
perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, false, false) perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, false, false)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"} 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}), counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{ expandPaths: map[string][]string{
"\\O(*)\\*": cps1, "\\O(*)\\*": cps1,
@ -346,6 +439,17 @@ func TestSimpleGather(t *testing.T) {
"objectname": "O", "objectname": "O",
} }
acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) 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) { func TestGatherInvalidDataIgnore(t *testing.T) {
@ -377,15 +481,25 @@ func TestGatherInvalidDataIgnore(t *testing.T) {
"objectname": "O", "objectname": "O",
} }
acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) 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 var err error
if testing.Short() { if testing.Short() {
t.Skip("Skipping long taking test in short mode") t.Skip("Skipping long taking test in short mode")
} }
measurement := "test" 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"} cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
fpm := &FakePerformanceQuery{ fpm := &FakePerformanceQuery{
counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}), 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, 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 var acc1 testutil.Accumulator
err = m.Gather(&acc1) err = m.Gather(&acc1)
assert.Len(t, m.counters, 4) 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 // 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 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} 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) czechStrings := UTF16ToStringArray(unicodeStringListWithCzechChars)
assert.True(t, assert.ObjectsAreEqual(czechStrings, stringArrayWithCzechChars), "Not equal czech arrays") assert.True(t, assert.ObjectsAreEqual(czechStrings, stringArrayWithCzechChars), "Not equal czech arrays")
} }