Fix wildcard and other issues with win_perf_counters (#4189)

This commit is contained in:
Vlasta Hajek 2018-05-25 03:25:06 +02:00 committed by Daniel Nelson
parent d18aa450d8
commit a4f511a64a
6 changed files with 1502 additions and 629 deletions

View File

@ -1,19 +1,24 @@
# win_perf_counters readme # win_perf_counters readme
The way this plugin works is that on load of Telegraf, Input plugin to read Performance Counters on Windows operating systems.
the plugin will be handed configuration from Telegraf.
This configuration is parsed and then tested for validity such as Configuration is parsed and then tested for validity such as
if the Object, Instance and Counter existing. whether the Object, Instance and Counter exist on Telegraf startup.
If it does not match at startup, it will not be fetched.
Exceptions to this are in cases where you query for all instances "*". Counter paths are refreshed periodically, see [CountersRefreshInterval](#countersrefreshinterval)
By default the plugin does not return _Total configuration parameter for more info.
when it is querying for all (*) as this is redundant.
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.
## Basics ## Basics
The examples contained in this file have been found on the internet The examples contained in this file have been found on the internet
as counters used when performance monitoring as counters used when performance monitoring
Active Directory and IIS in perticular. Active Directory and IIS in particular.
There are a lot other good objects to monitor, if you know what to look for. There are a lot other good objects to monitor, if you know what to look for.
This file is likely to be updated in the future with more examples for This file is likely to be updated in the future with more examples for
useful configurations for separate scenarios. useful configurations for separate scenarios.
@ -29,9 +34,23 @@ Bool, if set to `true` will print out all matching performance objects.
Example: Example:
`PrintValid=true` `PrintValid=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.
Setting `CountersRefreshInterval` too low (order of seconds) can cause Telegraf to create
a high CPU load.
Set to `0s` to disable periodic refreshing.
#### PreVistaSupport #### PreVistaSupport
Bool, if set to `true` will use the localized PerfCounter interface that is present before Vista for backwards compatability. _Deprecated. Necessary features on Windows Vista and newer are checked dynamically_
Bool, if set to `true` will use the localized PerfCounter interface that has been present since before Vista for backwards compatability.
It is recommended NOT to use this on OSes starting with Vista and newer because it requires more configuration to use this than the newer interface present since Vista. It is recommended NOT to use this on OSes starting with Vista and newer because it requires more configuration to use this than the newer interface present since Vista.
@ -45,10 +64,10 @@ See Entry below.
### Entry ### Entry
A new configuration entry consists of the TOML header to start with, A new configuration entry consists of the TOML header to start with,
`[[inputs.win_perf_counters.object]]`. `[[inputs.win_perf_counters.object]]`.
This must follow before other plugins configuration, This must follow before other plugin configurations,
beneath the main win_perf_counters entry, `[[inputs.win_perf_counters]]`. beneath the main win_perf_counters entry, `[[inputs.win_perf_counters]]`.
Following this is 3 required key/value pairs and the three optional parameters and their usage. Following this are 3 required key/value pairs and the three optional parameters and their usage.
#### ObjectName #### ObjectName
**Required** **Required**
@ -60,37 +79,39 @@ Example: `ObjectName = "LogicalDisk"`
#### Instances #### Instances
**Required** **Required**
Instances (this is an array) is the instances of a counter you would like returned, Instances key (this is an array) is the instances of a counter you would like returned,
it can be one or more values. it can be one or more values.
Example, `Instances = ["C:","D:","E:"]` will return only for the instances Example, `Instances = ["C:","D:","E:"]` will return only for the instances
C:, D: and E: where relevant. To get all instances of a Counter, use ["*"] only. C:, D: and E: where relevant. To get all instances of a Counter, use `["*"]` only.
By default any results containing _Total are stripped, 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.
Some Objects does not have instances to select from at all, It is also possible to set partial wildcards, eg. `["chrome*"]`
here only one option is valid if you want data back,
Some Objects do not have instances to select from at all.
Here only one option is valid if you want data back,
and that is to specify `Instances = ["------"]`. and that is to specify `Instances = ["------"]`.
#### Counters #### Counters
**Required** **Required**
Counters (this is an array) is the counters of the ObjectName 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,
it is not possible to ask for all counters in the ObjectName. or use `["*"]` for all the counters for object.
#### Measurement #### Measurement
*Optional* *Optional*
This key is optional, if it is not set it will be win_perf_counters. This key is optional, if it is not set it will be `win_perf_counters`.
In InfluxDB this is the key by which the returned data is stored underneath, In InfluxDB this is the key by which the returned data is stored underneath,
so for ordering your data in a good manner, so for ordering your data in a good manner,
this is a good key to set with where you want your IIS and Disk results stored, this is a good key to set with a value when you want your IIS and Disk results stored
separate from Processor results. separately from Processor results.
Example: `Measurement = "win_disk" Example: `Measurement = "win_disk"
@ -99,9 +120,9 @@ Example: `Measurement = "win_disk"
This key is optional, it is a simple bool. This key is optional, it is a simple bool.
If it is not set to true or included it is treated as false. If it is not set to true or included it is treated as false.
This key only has an effect if Instances is set to "*" This key only has an effect if the Instances key is set to `["*"]`
and you would also like all instances containg _Total returned, and you would also like all instances containing `_Total` returned,
like "_Total", "0,_Total" and so on where applicable like `_Total`, `0,_Total` and so on where applicable
(Processor Information is one example). (Processor Information is one example).
#### WarnOnMissing #### WarnOnMissing
@ -111,13 +132,13 @@ This key is optional, it is a simple bool.
If it is not set to true or included it is treated as false. If it is not set to true or included it is treated as false.
This only has an effect on the first execution of the plugin, This only has an effect on the first execution of the plugin,
it will print out any ObjectName/Instance/Counter combinations it will print out any ObjectName/Instance/Counter combinations
asked for that does not match. Useful when debugging new configurations. asked for that do not match. Useful when debugging new configurations.
#### FailOnMissing #### FailOnMissing
*Internal* *Internal*
This key should not be used, it is for testing purposes only. This key should not be used. It is for testing purposes only.
It is a simple bool, if it is not set to true or included this is treaded as false. It is a simple bool. If it is not set to true or included this is treated as false.
If this is set to true, the plugin will abort and end prematurely If this is set to true, the plugin will abort and end prematurely
if any of the combinations of ObjectName/Instances/Counters are invalid. if any of the combinations of ObjectName/Instances/Counters are invalid.
@ -337,10 +358,14 @@ if any of the combinations of ObjectName/Instances/Counters are invalid.
## Troubleshooting ## Troubleshooting
If you are getting an error about an invalid counter, use the `typeperf` command to check the counter path
on the command line.
E.g. `typeperf "Process(chrome*)\% Processor Time"`
If no metrics are emitted even with the default config, you may need to repair If no metrics are emitted even with the default config, you may need to repair
your performance counters. your performance counters.
1. Launch Command Prompt as Administrator (right click Runs As Administrator). 1. Launch the Command Prompt as Administrator (right click Runs As Administrator).
1. Drop into the C:\WINDOWS\System32 directory by typing `C:` then `cd \Windows\System32` 1. Drop into the C:\WINDOWS\System32 directory by typing `C:` then `cd \Windows\System32`
1. Rebuild your counter values, which may take a few moments so please be 1. Rebuild your counter values, which may take a few moments so please be
patient, by running: patient, by running:

View File

@ -198,6 +198,51 @@ type PDH_FMT_COUNTERVALUE_ITEM_LONG struct {
FmtValue PDH_FMT_COUNTERVALUE_LONG FmtValue PDH_FMT_COUNTERVALUE_LONG
} }
//PDH_COUNTER_INFO structure contains information describing the properties of a counter. This information also includes the counter path.
type PDH_COUNTER_INFO struct {
//Size of the structure, including the appended strings, in bytes.
DwLength uint32
//Counter type. For a list of counter types, see the Counter Types section of the <a "href=http://go.microsoft.com/fwlink/p/?linkid=84422">Windows Server 2003 Deployment Kit</a>.
//The counter type constants are defined in Winperf.h.
DwType uint32
//Counter version information. Not used.
CVersion uint32
//Counter status that indicates if the counter value is valid. For a list of possible values,
//see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa371894(v=vs.85).aspx">Checking PDH Interface Return Values</a>.
CStatus uint32
//Scale factor to use when computing the displayable value of the counter. The scale factor is a power of ten.
//The valid range of this parameter is PDH_MIN_SCALE (7) (the returned value is the actual value times 10⁷) to
//PDH_MAX_SCALE (+7) (the returned value is the actual value times 10⁺⁷). A value of zero will set the scale to one, so that the actual value is returned
LScale int32
//Default scale factor as suggested by the counter's provider.
LDefaultScale int32
//The value passed in the dwUserData parameter when calling PdhAddCounter.
DwUserData *uint32
//The value passed in the dwUserData parameter when calling PdhOpenQuery.
DwQueryUserData *uint32
//Null-terminated string that specifies the full counter path. The string follows this structure in memory.
SzFullPath *uint16 // pointer to a string
//Null-terminated string that contains the name of the computer specified in the counter path. Is NULL, if the path does not specify a computer.
//The string follows this structure in memory.
SzMachineName *uint16 // pointer to a string
//Null-terminated string that contains the name of the performance object specified in the counter path. The string follows this structure in memory.
SzObjectName *uint16 // pointer to a string
//Null-terminated string that contains the name of the object instance specified in the counter path. Is NULL, if the path does not specify an instance.
//The string follows this structure in memory.
SzInstanceName *uint16 // pointer to a string
//Null-terminated string that contains the name of the parent instance specified in the counter path. Is NULL, if the path does not specify a parent instance.
//The string follows this structure in memory.
SzParentInstance *uint16 // pointer to a string
//Instance index specified in the counter path. Is 0, if the path does not specify an instance index.
DwInstanceIndex uint32 // pointer to a string
//Null-terminated string that contains the counter name. The string follows this structure in memory.
SzCounterName *uint16 // pointer to a string
//Help text that describes the counter. Is NULL if the source is a log file.
SzExplainText *uint16 // pointer to a string
//Start of the string data that is appended to the structure.
DataBuffer *uint32 // pointer to an extra space
}
var ( var (
// Library // Library
libpdhDll *syscall.DLL libpdhDll *syscall.DLL
@ -211,6 +256,8 @@ var (
pdh_GetFormattedCounterArrayW *syscall.Proc pdh_GetFormattedCounterArrayW *syscall.Proc
pdh_OpenQuery *syscall.Proc pdh_OpenQuery *syscall.Proc
pdh_ValidatePathW *syscall.Proc pdh_ValidatePathW *syscall.Proc
pdh_ExpandWildCardPathW *syscall.Proc
pdh_GetCounterInfoW *syscall.Proc
) )
func init() { func init() {
@ -226,9 +273,11 @@ func init() {
pdh_GetFormattedCounterArrayW = libpdhDll.MustFindProc("PdhGetFormattedCounterArrayW") pdh_GetFormattedCounterArrayW = libpdhDll.MustFindProc("PdhGetFormattedCounterArrayW")
pdh_OpenQuery = libpdhDll.MustFindProc("PdhOpenQuery") pdh_OpenQuery = libpdhDll.MustFindProc("PdhOpenQuery")
pdh_ValidatePathW = libpdhDll.MustFindProc("PdhValidatePathW") pdh_ValidatePathW = libpdhDll.MustFindProc("PdhValidatePathW")
pdh_ExpandWildCardPathW = libpdhDll.MustFindProc("PdhExpandWildCardPathW")
pdh_GetCounterInfoW = libpdhDll.MustFindProc("PdhGetCounterInfoW")
} }
// Adds the specified counter to the query. This is the internationalized version. Preferably, use the // PdhAddCounter adds the specified counter to the query. This is the internationalized version. Preferably, use the
// function PdhAddEnglishCounter instead. hQuery is the query handle, which has been fetched by PdhOpenQuery. // function PdhAddEnglishCounter instead. hQuery is the query handle, which has been fetched by PdhOpenQuery.
// szFullCounterPath is a full, internationalized counter path (this will differ per Windows language version). // szFullCounterPath is a full, internationalized counter path (this will differ per Windows language version).
// dwUserData is a 'user-defined value', which becomes part of the counter information. To retrieve this value // dwUserData is a 'user-defined value', which becomes part of the counter information. To retrieve this value
@ -277,7 +326,14 @@ func PdhAddCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintp
return uint32(ret) return uint32(ret)
} }
// Adds the specified language-neutral counter to the query. See the PdhAddCounter function. This function only exists on // PdhAddEnglishCounterSupported returns true if PdhAddEnglishCounterW Win API function was found in pdh.dll.
// PdhAddEnglishCounterW function is not supported on pre-Windows Vista systems
func PdhAddEnglishCounterSupported() bool {
return pdh_AddEnglishCounterW != nil
}
// PdhAddEnglishCounter adds the specified language-neutral counter to the query. See the PdhAddCounter function. This function only exists on
// Windows versions higher than Vista. // Windows versions higher than Vista.
func PdhAddEnglishCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) uint32 { func PdhAddEnglishCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) uint32 {
if pdh_AddEnglishCounterW == nil { if pdh_AddEnglishCounterW == nil {
@ -294,7 +350,7 @@ func PdhAddEnglishCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserDat
return uint32(ret) return uint32(ret)
} }
// Closes all counters contained in the specified query, closes all handles related to the query, // PdhCloseQuery closes all counters contained in the specified query, closes all handles related to the query,
// and frees all memory associated with the query. // and frees all memory associated with the query.
func PdhCloseQuery(hQuery PDH_HQUERY) uint32 { func PdhCloseQuery(hQuery PDH_HQUERY) uint32 {
ret, _, _ := pdh_CloseQuery.Call(uintptr(hQuery)) ret, _, _ := pdh_CloseQuery.Call(uintptr(hQuery))
@ -329,7 +385,7 @@ func PdhCollectQueryData(hQuery PDH_HQUERY) uint32 {
return uint32(ret) return uint32(ret)
} }
// Formats the given hCounter using a 'double'. The result is set into the specialized union struct pValue. // PdhGetFormattedCounterValueDouble formats the given hCounter using a 'double'. The result is set into the specialized union struct pValue.
// This function does not directly translate to a Windows counterpart due to union specialization tricks. // This function does not directly translate to a Windows counterpart due to union specialization tricks.
func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32, pValue *PDH_FMT_COUNTERVALUE_DOUBLE) uint32 { func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32, pValue *PDH_FMT_COUNTERVALUE_DOUBLE) uint32 {
ret, _, _ := pdh_GetFormattedCounterValue.Call( ret, _, _ := pdh_GetFormattedCounterValue.Call(
@ -341,7 +397,7 @@ func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32,
return uint32(ret) return uint32(ret)
} }
// Returns an array of formatted counter values. Use this function when you want to format the counter values of a // PdhGetFormattedCounterArrayDouble returns an array of formatted counter values. Use this function when you want to format the counter values of a
// counter that contains a wildcard character for the instance name. The itemBuffer must a slice of type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE. // counter that contains a wildcard character for the instance name. The itemBuffer must a slice of type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE.
// An example of how this function can be used: // An example of how this function can be used:
// //
@ -389,7 +445,7 @@ func PdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *ui
return uint32(ret) return uint32(ret)
} }
// Creates a new query that is used to manage the collection of performance data. // PdhOpenQuery creates a new query that is used to manage the collection of performance data.
// szDataSource is a null terminated string that specifies the name of the log file from which to // szDataSource is a null terminated string that specifies the name of the log file from which to
// retrieve the performance data. If 0, performance data is collected from a real-time data source. // retrieve the performance data. If 0, performance data is collected from a real-time data source.
// dwUserData is a user-defined value to associate with this query. To retrieve the user data later, // dwUserData is a user-defined value to associate with this query. To retrieve the user data later,
@ -405,7 +461,51 @@ func PdhOpenQuery(szDataSource uintptr, dwUserData uintptr, phQuery *PDH_HQUERY)
return uint32(ret) return uint32(ret)
} }
// Validates a path. Will return ERROR_SUCCESS when ok, or PDH_CSTATUS_BAD_COUNTERNAME when the path is //PdhExpandWildCardPath examines the specified computer or log file and returns those counter paths that match the given counter path which contains wildcard characters.
//The general counter path format is as follows:
//
//\\computer\object(parent/instance#index)\counter
//
//The parent, instance, index, and counter components of the counter path may contain either a valid name or a wildcard character. The computer, parent, instance,
// and index components are not necessary for all counters.
//
//The following is a list of the possible formats:
//
//\\computer\object(parent/instance#index)\counter
//\\computer\object(parent/instance)\counter
//\\computer\object(instance#index)\counter
//\\computer\object(instance)\counter
//\\computer\object\counter
//\object(parent/instance#index)\counter
//\object(parent/instance)\counter
//\object(instance#index)\counter
//\object(instance)\counter
//\object\counter
//Use an asterisk (*) as the wildcard character, for example, \object(*)\counter.
//
//If a wildcard character is specified in the parent name, all instances of the specified object that match the specified instance and counter fields will be returned.
// For example, \object(*/instance)\counter.
//
//If a wildcard character is specified in the instance name, all instances of the specified object and parent object will be returned if all instance names
// corresponding to the specified index match the wildcard character. For example, \object(parent/*)\counter. If the object does not contain an instance, an error occurs.
//
//If a wildcard character is specified in the counter name, all counters of the specified object are returned.
//
//Partial counter path string matches (for example, "pro*") are supported.
func PdhExpandWildCardPath(szWildCardPath string, mszExpandedPathList *uint16, pcchPathListLength *uint32) uint32 {
ptxt, _ := syscall.UTF16PtrFromString(szWildCardPath)
flags := uint32(0) // expand instances and counters
ret, _, _ := pdh_ExpandWildCardPathW.Call(
uintptr(unsafe.Pointer(nil)), // search counters on local computer
uintptr(unsafe.Pointer(ptxt)),
uintptr(unsafe.Pointer(mszExpandedPathList)),
uintptr(unsafe.Pointer(pcchPathListLength)),
uintptr(unsafe.Pointer(&flags)))
return uint32(ret)
}
// PdhValidatePath validates a path. Will return ERROR_SUCCESS when ok, or PDH_CSTATUS_BAD_COUNTERNAME when the path is
// erroneous. // erroneous.
func PdhValidatePath(path string) uint32 { func PdhValidatePath(path string) uint32 {
ptxt, _ := syscall.UTF16PtrFromString(path) ptxt, _ := syscall.UTF16PtrFromString(path)
@ -414,13 +514,6 @@ func PdhValidatePath(path string) uint32 {
return uint32(ret) return uint32(ret)
} }
func UTF16PtrToString(s *uint16) string {
if s == nil {
return ""
}
return syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(s))[0:])
}
func PdhFormatError(msgId uint32) string { func PdhFormatError(msgId uint32) string {
var flags uint32 = windows.FORMAT_MESSAGE_FROM_HMODULE | windows.FORMAT_MESSAGE_ARGUMENT_ARRAY | windows.FORMAT_MESSAGE_IGNORE_INSERTS var flags uint32 = windows.FORMAT_MESSAGE_FROM_HMODULE | windows.FORMAT_MESSAGE_ARGUMENT_ARRAY | windows.FORMAT_MESSAGE_IGNORE_INSERTS
buf := make([]uint16, 300) buf := make([]uint16, 300)
@ -430,3 +523,25 @@ func PdhFormatError(msgId uint32) string {
} }
return fmt.Sprintf("(pdhErr=%d) %s", msgId, err.Error()) return fmt.Sprintf("(pdhErr=%d) %s", msgId, err.Error())
} }
//Retrieves information about a counter, such as data size, counter type, path, and user-supplied data values
//hCounter [in]
//Handle of the counter from which you want to retrieve information. The PdhAddCounter function returns this handle.
//
//bRetrieveExplainText [in]
//Determines whether explain text is retrieved. If you set this parameter to TRUE, the explain text for the counter is retrieved. If you set this parameter to FALSE, the field in the returned buffer is NULL.
//
//pdwBufferSize [in, out]
//Size of the lpBuffer 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.
//
//lpBuffer [out]
//Caller-allocated buffer that receives a PDH_COUNTER_INFO structure. The structure is variable-length, because the string data is appended to the end of the fixed-format portion of the structure. This is done so that all data is returned in a single buffer allocated by the caller. Set to NULL if pdwBufferSize is zero.
func PdhGetCounterInfo(hCounter PDH_HCOUNTER, bRetrieveExplainText int, pdwBufferSize *uint32, lpBuffer *byte) uint32 {
ret, _, _ := pdh_GetCounterInfoW.Call(
uintptr(hCounter),
uintptr(bRetrieveExplainText),
uintptr(unsafe.Pointer(pdwBufferSize)),
uintptr(unsafe.Pointer(lpBuffer)))
return uint32(ret)
}

View File

@ -0,0 +1,189 @@
// Go API over pdh syscalls
// +build windows
package win_perf_counters
import (
"errors"
"syscall"
"unsafe"
)
//PerformanceQuery provides wrappers around Windows performance counters API for easy usage in GO
type PerformanceQuery interface {
Open() error
Close() error
AddCounterToQuery(counterPath string) (PDH_HCOUNTER, error)
AddEnglishCounterToQuery(counterPath string) (PDH_HCOUNTER, error)
GetCounterPath(counterHandle PDH_HCOUNTER) (string, error)
ExpandWildCardPath(counterPath string) ([]string, error)
GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error)
CollectData() error
AddEnglishCounterSupported() bool
}
//PdhError represents error returned from Performance Counters API
type PdhError struct {
ErrorCode uint32
errorText string
}
func (m *PdhError) Error() string {
return m.errorText
}
func NewPdhError(code uint32) error {
return &PdhError{
ErrorCode: code,
errorText: PdhFormatError(code),
}
}
//PerformanceQueryImpl is implementation of PerformanceQuery interface, which calls phd.dll functions
type PerformanceQueryImpl struct {
query PDH_HQUERY
}
// Open creates a new counterPath that is used to manage the collection of performance data.
// It returns counterPath handle used for subsequent calls for adding counters and querying data
func (m *PerformanceQueryImpl) Open() error {
if m.query != 0 {
err := m.Close()
if err != nil {
return err
}
}
var handle PDH_HQUERY
ret := PdhOpenQuery(0, 0, &handle)
if ret != ERROR_SUCCESS {
return NewPdhError(ret)
}
m.query = handle
return nil
}
// Close closes the counterPath, releases associated counter handles and frees resources
func (m *PerformanceQueryImpl) Close() error {
if m.query == 0 {
return errors.New("uninitialised query")
}
ret := PdhCloseQuery(m.query)
if ret != ERROR_SUCCESS {
return NewPdhError(ret)
}
m.query = 0
return nil
}
func (m *PerformanceQueryImpl) AddCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
var counterHandle PDH_HCOUNTER
if m.query == 0 {
return 0, errors.New("uninitialised query")
}
ret := PdhAddCounter(m.query, counterPath, 0, &counterHandle)
if ret != ERROR_SUCCESS {
return 0, NewPdhError(ret)
}
return counterHandle, nil
}
func (m *PerformanceQueryImpl) AddEnglishCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
var counterHandle PDH_HCOUNTER
if m.query == 0 {
return 0, errors.New("uninitialised query")
}
ret := PdhAddEnglishCounter(m.query, counterPath, 0, &counterHandle)
if ret != ERROR_SUCCESS {
return 0, NewPdhError(ret)
}
return counterHandle, nil
}
//GetCounterPath return counter information for given handle
func (m *PerformanceQueryImpl) GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) {
var bufSize uint32
var buff []byte
ret := PdhGetCounterInfo(counterHandle, 0, &bufSize, nil)
if ret == PDH_MORE_DATA {
buff = make([]byte, bufSize)
bufSize = uint32(len(buff))
ret = PdhGetCounterInfo(counterHandle, 0, &bufSize, &buff[0])
if ret == ERROR_SUCCESS {
ci := (*PDH_COUNTER_INFO)(unsafe.Pointer(&buff[0]))
return UTF16PtrToString(ci.SzFullPath), nil
}
}
return "", NewPdhError(ret)
}
// ExpandWildCardPath examines local computer and returns those counter paths that match the given counter path which contains wildcard characters.
func (m *PerformanceQueryImpl) ExpandWildCardPath(counterPath string) ([]string, error) {
var bufSize uint32
var buff []uint16
ret := PdhExpandWildCardPath(counterPath, nil, &bufSize)
if ret == PDH_MORE_DATA {
buff = make([]uint16, bufSize)
bufSize = uint32(len(buff))
ret = PdhExpandWildCardPath(counterPath, &buff[0], &bufSize)
if ret == ERROR_SUCCESS {
list := UTF16ToStringArray(buff)
return list, nil
}
}
return nil, NewPdhError(ret)
}
//GetFormattedCounterValueDouble computes a displayable value for the specified counter
func (m *PerformanceQueryImpl) GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error) {
var counterType uint32
var value PDH_FMT_COUNTERVALUE_DOUBLE
ret := PdhGetFormattedCounterValueDouble(hCounter, &counterType, &value)
if ret == ERROR_SUCCESS {
if value.CStatus == PDH_CSTATUS_VALID_DATA || value.CStatus == PDH_CSTATUS_NEW_DATA {
return value.DoubleValue, nil
} else {
return 0, NewPdhError(value.CStatus)
}
} else {
return 0, NewPdhError(ret)
}
}
func (m *PerformanceQueryImpl) CollectData() error {
if m.query == 0 {
return errors.New("uninitialised query")
}
ret := PdhCollectQueryData(m.query)
if ret != ERROR_SUCCESS {
return NewPdhError(ret)
}
return nil
}
func (m *PerformanceQueryImpl) AddEnglishCounterSupported() bool {
return PdhAddEnglishCounterSupported()
}
// UTF16PtrToString converts Windows API LPTSTR (pointer to string) to go string
func UTF16PtrToString(s *uint16) string {
if s == nil {
return ""
}
return syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(s))[0:])
}
// UTF16ToStringArray converts list of Windows API NULL terminated strings to go string array
func UTF16ToStringArray(buf []uint16) []string {
var strings []string
nextLineStart := 0
stringLine := UTF16PtrToString(&buf[0])
for stringLine != "" {
strings = append(strings, stringLine)
nextLineStart += len(stringLine) + 1
remainingBuf := buf[nextLineStart:]
stringLine = UTF16PtrToString(&remainingBuf[0])
}
return strings
}

View File

@ -5,11 +5,13 @@ package win_perf_counters
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings"
"unsafe"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
"log"
"regexp"
"strings"
"time"
) )
var sampleConfig = ` var sampleConfig = `
@ -20,6 +22,8 @@ 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
# Period after which counters will be reread from configuration and wildcards in counter paths expanded
CountersRefreshInterval="1m"
[[inputs.win_perf_counters.object]] [[inputs.win_perf_counters.object]]
# Processor usage, alternative to native, reports on a per core. # Processor usage, alternative to native, reports on a per core.
@ -53,7 +57,7 @@ var sampleConfig = `
Measurement = "win_system" Measurement = "win_system"
[[inputs.win_perf_counters.object]] [[inputs.win_perf_counters.object]]
# Example query where the Instance portion must be removed to get data back, # Example counterPath where the Instance portion must be removed to get data back,
# such as from the Memory object. # such as from the Memory object.
ObjectName = "Memory" ObjectName = "Memory"
Counters = [ Counters = [
@ -61,17 +65,20 @@ var sampleConfig = `
"Page Faults/sec", "Pages/sec", "Transition Faults/sec", "Page Faults/sec", "Pages/sec", "Transition Faults/sec",
"Pool Nonpaged Bytes", "Pool Paged Bytes" "Pool Nonpaged Bytes", "Pool Paged Bytes"
] ]
Instances = ["------"] # Use 6 x - to remove the Instance bit from the query. Instances = ["------"] # Use 6 x - to remove the Instance bit from the counterPath.
Measurement = "win_mem" Measurement = "win_mem"
` `
type Win_PerfCounters struct { type Win_PerfCounters struct {
PrintValid bool PrintValid bool
PreVistaSupport bool //deprecated: determined dynamically
Object []perfobject PreVistaSupport bool
Object []perfobject
CountersRefreshInterval internal.Duration
configParsed bool lastRefreshed time.Time
itemCache []*item counters []*counter
query PerformanceQuery
} }
type perfobject struct { type perfobject struct {
@ -84,56 +91,101 @@ type perfobject struct {
IncludeTotal bool IncludeTotal bool
} }
type item struct { type counter struct {
query string counterPath string
objectName string objectName string
counter string counter string
instance string instance string
measurement string measurement string
include_total bool includeTotal bool
handle PDH_HQUERY
counterHandle PDH_HCOUNTER counterHandle PDH_HCOUNTER
} }
var sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec", var sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec",
" ", "_", "%", "Percent", `\`, "") " ", "_", "%", "Percent", `\`, "")
func (m *Win_PerfCounters) AddItem(query string, objectName string, counter string, instance string, //General Counter path pattern is: \\computer\object(parent/instance#index)\counter
measurement string, include_total bool) error { //parent/instance#index part is skipped in single instance objects (e.g. Memory): \\computer\object\counter
var handle PDH_HQUERY var counterPathRE = regexp.MustCompile(`.*\\(.*)\\(.*)`)
var counterHandle PDH_HCOUNTER var objectInstanceRE = regexp.MustCompile(`(.*)\((.*)\)`)
ret := PdhOpenQuery(0, 0, &handle)
if m.PreVistaSupport { //extractObjectInstanceCounterFromQuery gets object name, instance name (if available) and counter name from counter path
ret = PdhAddCounter(handle, query, 0, &counterHandle) func extractObjectInstanceCounterFromQuery(query string) (object string, instance string, counter string, err error) {
pathParts := counterPathRE.FindAllStringSubmatch(query, -1)
if pathParts == nil || len(pathParts[0]) != 3 {
err = errors.New("Could not extract counter info from: " + query)
return
}
counter = pathParts[0][2]
//try to get instance name
objectInstanceParts := objectInstanceRE.FindAllStringSubmatch(pathParts[0][1], -1)
if objectInstanceParts == nil || len(objectInstanceParts[0]) != 3 {
object = pathParts[0][1]
} else { } else {
ret = PdhAddEnglishCounter(handle, query, 0, &counterHandle) object = objectInstanceParts[0][1]
instance = objectInstanceParts[0][2]
} }
return
// Call PdhCollectQueryData one time to check existence of the counter
ret = PdhCollectQueryData(handle)
if ret != ERROR_SUCCESS {
PdhCloseQuery(handle)
return errors.New(PdhFormatError(ret))
}
newItem := &item{query, objectName, counter, instance, measurement,
include_total, handle, counterHandle}
m.itemCache = append(m.itemCache, newItem)
return nil
} }
func (m *Win_PerfCounters) Description() string { func (m *Win_PerfCounters) Description() string {
return "Input plugin to query Performance Counters on Windows operating systems" return "Input plugin to counterPath Performance Counters on Windows operating systems"
} }
func (m *Win_PerfCounters) SampleConfig() string { func (m *Win_PerfCounters) SampleConfig() string {
return sampleConfig return sampleConfig
} }
func (m *Win_PerfCounters) AddItem(counterPath string, instance string, measurement string, includeTotal bool) error {
if !m.query.AddEnglishCounterSupported() {
_, err := m.query.AddCounterToQuery(counterPath)
if err != nil {
return err
}
} else {
counterHandle, err := m.query.AddEnglishCounterToQuery(counterPath)
if err != nil {
return err
}
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)
if err != nil {
return err
}
if parsedInstance == "_Total" && instance == "*" && !includeTotal {
continue
}
newItem := &counter{counterPath, parsedObjectName, parsedCounter, parsedInstance, measurement,
includeTotal, counterHandle}
m.counters = append(m.counters, newItem)
if m.PrintValid {
log.Printf("Valid: %s\n", counterPath)
}
}
return nil
}
func (m *Win_PerfCounters) ParseConfig() error { func (m *Win_PerfCounters) ParseConfig() error {
var query string var counterPath string
if len(m.Object) > 0 { if len(m.Object) > 0 {
for _, PerfObject := range m.Object { for _, PerfObject := range m.Object {
@ -142,21 +194,16 @@ func (m *Win_PerfCounters) ParseConfig() error {
objectname := PerfObject.ObjectName objectname := PerfObject.ObjectName
if instance == "------" { if instance == "------" {
query = "\\" + objectname + "\\" + counter counterPath = "\\" + objectname + "\\" + counter
} else { } else {
query = "\\" + objectname + "(" + instance + ")\\" + counter counterPath = "\\" + objectname + "(" + instance + ")\\" + counter
} }
err := m.AddItem(query, objectname, counter, instance, err := m.AddItem(counterPath, instance, PerfObject.Measurement, PerfObject.IncludeTotal)
PerfObject.Measurement, PerfObject.IncludeTotal)
if err == nil { if err != nil {
if m.PrintValid {
fmt.Printf("Valid: %s\n", query)
}
} else {
if PerfObject.FailOnMissing || PerfObject.WarnOnMissing { if PerfObject.FailOnMissing || PerfObject.WarnOnMissing {
fmt.Printf("Invalid query: '%s'. Error: %s", query, err.Error()) log.Printf("Invalid counterPath: '%s'. Error: %s\n", counterPath, err.Error())
} }
if PerfObject.FailOnMissing { if PerfObject.FailOnMissing {
return err return err
@ -165,32 +212,39 @@ func (m *Win_PerfCounters) ParseConfig() error {
} }
} }
} }
return nil return nil
} else { } else {
err := errors.New("No performance objects configured!") err := errors.New("no performance objects configured")
return err return err
} }
}
func (m *Win_PerfCounters) GetParsedItemsForTesting() []*item {
return m.itemCache
} }
func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
// Parse the config once // Parse the config once
if !m.configParsed { var err error
err := m.ParseConfig()
m.configParsed = true if m.lastRefreshed.IsZero() || (m.CountersRefreshInterval.Duration.Nanoseconds() > 0 && m.lastRefreshed.Add(m.CountersRefreshInterval.Duration).Before(time.Now())) {
m.counters = m.counters[:0]
err = m.query.Open()
if err != nil { if err != nil {
return err return err
} }
}
var bufSize uint32 err = m.ParseConfig()
var bufCount uint32 if err != nil {
var size uint32 = uint32(unsafe.Sizeof(PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) return err
var emptyBuf [1]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. }
//some counters need two data samples before computing a value
err = m.query.CollectData()
if err != nil {
return err
}
m.lastRefreshed = time.Now()
time.Sleep(time.Second)
}
type InstanceGrouping struct { type InstanceGrouping struct {
name string name string
@ -200,78 +254,40 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
var collectFields = make(map[InstanceGrouping]map[string]interface{}) var collectFields = make(map[InstanceGrouping]map[string]interface{})
err = m.query.CollectData()
if err != nil {
return err
}
// For iterate over the known metrics and get the samples. // For iterate over the known metrics and get the samples.
for _, metric := range m.itemCache { for _, metric := range m.counters {
// collect // collect
ret := PdhCollectQueryData(metric.handle) value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle)
if ret == ERROR_SUCCESS { if err == nil {
ret = PdhGetFormattedCounterArrayDouble(metric.counterHandle, &bufSize, measurement := sanitizedChars.Replace(metric.measurement)
&bufCount, &emptyBuf[0]) // uses null ptr here according to MSDN. if measurement == "" {
if ret == PDH_MORE_DATA { measurement = "win_perf_counters"
filledBuf := make([]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) }
if len(filledBuf) == 0 {
continue
}
ret = PdhGetFormattedCounterArrayDouble(metric.counterHandle,
&bufSize, &bufCount, &filledBuf[0])
for i := 0; i < int(bufCount); i++ {
c := filledBuf[i]
var s string = UTF16PtrToString(c.SzName)
var add bool var instance = InstanceGrouping{measurement, metric.instance, metric.objectName}
if collectFields[instance] == nil {
if metric.include_total { collectFields[instance] = make(map[string]interface{})
// If IncludeTotal is set, include all. }
add = true collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value)
} else if metric.instance == "*" && !strings.Contains(s, "_Total") { } else {
// Catch if set to * and that it is not a '*_Total*' instance. //ignore invalid data from as some counters from process instances returns this sometimes
add = true if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE {
} else if metric.instance == s { return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err)
// Catch if we set it to total or some form of it
add = true
} else if strings.Contains(metric.instance, "#") && strings.HasPrefix(metric.instance, s) {
// 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
s = metric.instance
} else if metric.instance == "------" {
add = true
}
if add {
tags := make(map[string]string)
if s != "" {
tags["instance"] = s
}
tags["objectname"] = metric.objectName
measurement := sanitizedChars.Replace(metric.measurement)
if measurement == "" {
measurement = "win_perf_counters"
}
var instance = InstanceGrouping{measurement, s, metric.objectName}
if collectFields[instance] == nil {
collectFields[instance] = make(map[string]interface{})
}
collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(c.FmtValue.DoubleValue)
}
}
filledBuf = nil
// Need to at least set bufSize to zero, because if not, the function will not
// return PDH_MORE_DATA and will not set the bufSize.
bufCount = 0
bufSize = 0
} }
} }
} }
for instance, fields := range collectFields { for instance, fields := range collectFields {
var tags = map[string]string{ var tags = map[string]string{
"instance": instance.instance,
"objectname": instance.objectname, "objectname": instance.objectname,
} }
if len(instance.instance) > 0 {
tags["instance"] = instance.instance
}
acc.AddFields(instance.name, fields, tags) acc.AddFields(instance.name, fields, tags)
} }
@ -279,5 +295,7 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
} }
func init() { func init() {
inputs.Add("win_perf_counters", func() telegraf.Input { return &Win_PerfCounters{} }) inputs.Add("win_perf_counters", func() telegraf.Input {
return &Win_PerfCounters{query: &PerformanceQueryImpl{}, CountersRefreshInterval: internal.Duration{Duration: time.Second * 60}}
})
} }

View File

@ -0,0 +1,592 @@
// +build windows
package win_perf_counters
import (
"errors"
"testing"
"time"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"strings"
)
func TestWinPerformanceQueryImpl(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var query PerformanceQuery
var hCounter PDH_HCOUNTER
var err error
query = &PerformanceQueryImpl{}
err = query.Close()
require.Error(t, err, "uninitialized query must return errors")
_, err = query.AddCounterToQuery("")
require.Error(t, err, "uninitialized query must return errors")
assert.True(t, strings.Contains(err.Error(), "uninitialised"))
_, err = query.AddEnglishCounterToQuery("")
require.Error(t, err, "uninitialized query must return errors")
assert.True(t, strings.Contains(err.Error(), "uninitialised"))
err = query.CollectData()
require.Error(t, err, "uninitialized query must return errors")
assert.True(t, strings.Contains(err.Error(), "uninitialised"))
err = query.Open()
require.NoError(t, err)
counterPath := "\\Processor Information(_Total)\\% Processor Time"
hCounter, err = query.AddCounterToQuery(counterPath)
require.NoError(t, err)
assert.NotEqual(t, 0, hCounter)
err = query.Close()
require.NoError(t, err)
err = query.Open()
require.NoError(t, err)
hCounter, err = query.AddEnglishCounterToQuery(counterPath)
require.NoError(t, err)
assert.NotEqual(t, 0, hCounter)
cp, err := query.GetCounterPath(hCounter)
require.NoError(t, err)
assert.True(t, strings.HasSuffix(cp, counterPath))
err = query.CollectData()
require.NoError(t, err)
time.Sleep(time.Second)
err = query.CollectData()
require.NoError(t, err)
_, err = query.GetFormattedCounterValueDouble(hCounter)
require.NoError(t, err)
counterPath = "\\Process(*)\\% Processor Time"
paths, err := query.ExpandWildCardPath(counterPath)
require.NoError(t, err)
require.NotNil(t, paths)
assert.True(t, len(paths) > 1)
counterPath = "\\Process(_Total)\\*"
paths, err = query.ExpandWildCardPath(counterPath)
require.NoError(t, err)
require.NotNil(t, paths)
assert.True(t, len(paths) > 1)
}
func TestWinPerfcountersConfigGet1(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 Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
}
func TestWinPerfcountersConfigGet2(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 Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 1 {
require.NoError(t, nil)
} else if len(m.counters) == 0 {
var errorstring1 = "No results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 1 {
var errorstring1 = "Too many results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet3(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 2 {
require.NoError(t, nil)
} else if len(m.counters) < 2 {
var errorstring1 = "Too few results returned from the counterPath. " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 2 {
var errorstring1 = "Too many results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet4(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 2)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 2 {
require.NoError(t, nil)
} else if len(m.counters) < 2 {
var errorstring1 = "Too few results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 2 {
var errorstring1 = "Too many results returned from the counterPath: " + string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet5(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 2)
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 4 {
require.NoError(t, nil)
} else if len(m.counters) < 4 {
var errorstring1 = "Too few results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 4 {
var errorstring1 = "Too many results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigGet6(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 := "System"
instances[0] = "------"
counters[0] = "Context Switches/sec"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
}
func TestWinPerfcountersConfigGet7(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 1)
var counters = make([]string, 3)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
counters[1] = "% Processor TimeERROR"
counters[2] = "% Idle Time"
var measurement = "test"
PerfObject := perfobject{
objectname,
counters,
instances,
measurement,
false,
false,
false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.NoError(t, err)
if len(m.counters) == 2 {
require.NoError(t, nil)
} else if len(m.counters) < 2 {
var errorstring1 = "Too few results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(m.counters) > 2 {
var errorstring1 = "Too many results returned from the counterPath: " +
string(len(m.counters))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigError1(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 InformationERROR"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.Error(t, err)
}
func TestWinPerfcountersConfigError2(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] = "SuperERROR"
counters[0] = "% C1 Time"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
var acc testutil.Accumulator
err = m.Gather(&acc)
require.Error(t, err)
}
func TestWinPerfcountersConfigError3(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 Information"
instances[0] = "_Total"
counters[0] = "% Processor TimeERROR"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
m.query.Open()
err := m.ParseConfig()
require.Error(t, err)
}
func TestWinPerfcountersCollect1(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 Information"
instances[0] = "_Total"
counters[0] = "Parking Status"
var expectedCounter = "Parking_Status"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
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)
assert.Len(t, acc.Metrics, 2)
for _, metric := range acc.Metrics {
_, ok := metric.Fields[expectedCounter]
assert.True(t, ok)
}
}
func TestWinPerfcountersCollect2(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
var instances = make([]string, 2)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,0"
counters[0] = "Performance Limit Flags"
var expectedCounter = "Performance_Limit_Flags"
var measurement = "test"
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: false,
FailOnMissing: true,
IncludeTotal: false,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}}
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)
assert.Len(t, acc.Metrics, 4)
for _, metric := range acc.Metrics {
_, ok := metric.Fields[expectedCounter]
assert.True(t, ok)
}
}

View File

@ -4,528 +4,462 @@ package win_perf_counters
import ( import (
"errors" "errors"
"fmt"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing" "testing"
"time" "time"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
) )
func TestWinPerfcountersConfigGet1(t *testing.T) { type testCounter struct {
handle PDH_HCOUNTER
var instances = make([]string, 1) path string
var counters = make([]string, 1) value float64
var perfobjects = make([]perfobject, 1) }
type FakePerformanceQuery struct {
objectname := "Processor Information" counters map[string]testCounter
instances[0] = "_Total" addEnglishSupported bool
counters[0] = "% Processor Time" expandPaths map[string][]string
openCalled bool
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
} }
func TestWinPerfcountersConfigGet2(t *testing.T) { func (m *FakePerformanceQuery) Open() error {
if m.openCalled {
var instances = make([]string, 1) err := m.Close()
var counters = make([]string, 1) if err != nil {
var perfobjects = make([]perfobject, 1) return err
}
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
} }
m.openCalled = true
return nil
}
perfobjects[0] = PerfObject func (m *FakePerformanceQuery) Close() error {
if !m.openCalled {
return errors.New("CloSe: uninitialised query")
}
m.openCalled = false
return nil
}
m := Win_PerfCounters{PrintValid: false, Object: perfobjects} func (m *FakePerformanceQuery) AddCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
if !m.openCalled {
err := m.ParseConfig() return 0, errors.New("AddCounterToQuery: uninitialised query")
require.NoError(t, err) }
if c, ok := m.counters[counterPath]; ok {
var parsedItems = m.GetParsedItemsForTesting() return c.handle, nil
} else {
if len(parsedItems) == 1 { return 0, errors.New(fmt.Sprintf("AddCounterToQuery: invalid counter path: %s", counterPath))
require.NoError(t, nil)
} else if len(parsedItems) == 0 {
var errorstring1 string = "No results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 1 {
var errorstring1 string = "Too many results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} }
} }
func TestWinPerfcountersConfigGet3(t *testing.T) { func (m *FakePerformanceQuery) AddEnglishCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
if !m.openCalled {
var instances = make([]string, 1) return 0, errors.New("AddEnglishCounterToQuery: uninitialised query")
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
} }
if c, ok := m.counters[counterPath]; ok {
perfobjects[0] = PerfObject return c.handle, nil
} else {
m := Win_PerfCounters{PrintValid: false, Object: perfobjects} return 0, fmt.Errorf("AddEnglishCounterToQuery: invalid counter path: %s", counterPath)
err := m.ParseConfig()
require.NoError(t, err)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 2 {
require.NoError(t, nil)
} else if len(parsedItems) < 2 {
var errorstring1 string = "Too few results returned from the query. " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 2 {
var errorstring1 string = "Too many results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} }
} }
func TestWinPerfcountersConfigGet4(t *testing.T) { func (m *FakePerformanceQuery) GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) {
for _, counter := range m.counters {
var instances = make([]string, 2) if counter.handle == counterHandle {
var counters = make([]string, 1) return counter.path, nil
var perfobjects = make([]perfobject, 1) }
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
} }
return "", fmt.Errorf("GetCounterPath: invalid handle: %d", counterHandle)
}
perfobjects[0] = PerfObject func (m *FakePerformanceQuery) ExpandWildCardPath(counterPath string) ([]string, error) {
if e, ok := m.expandPaths[counterPath]; ok {
m := Win_PerfCounters{PrintValid: false, Object: perfobjects} return e, nil
} else {
err := m.ParseConfig() return []string{}, fmt.Errorf("ExpandWildCardPath: invalid counter path: %s", counterPath)
require.NoError(t, err)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 2 {
require.NoError(t, nil)
} else if len(parsedItems) < 2 {
var errorstring1 string = "Too few results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 2 {
var errorstring1 string = "Too many results returned from the query: " + string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} }
} }
func TestWinPerfcountersConfigGet5(t *testing.T) { func (m *FakePerformanceQuery) GetFormattedCounterValueDouble(counterHandle PDH_HCOUNTER) (float64, error) {
if !m.openCalled {
var instances = make([]string, 2) return 0, errors.New("GetFormattedCounterValueDouble: uninitialised query")
var counters = make([]string, 2)
var perfobjects = make([]perfobject, 1)
objectname := "Processor Information"
instances[0] = "_Total"
instances[1] = "0,1"
counters[0] = "% Processor Time"
counters[1] = "% Idle Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
} }
for _, counter := range m.counters {
perfobjects[0] = PerfObject if counter.handle == counterHandle {
if counter.value > 0 {
m := Win_PerfCounters{PrintValid: false, Object: perfobjects} return counter.value, nil
} else {
err := m.ParseConfig() if counter.value == 0 {
require.NoError(t, err) return 0, NewPdhError(PDH_INVALID_DATA)
} else {
var parsedItems = m.GetParsedItemsForTesting() return 0, NewPdhError(PDH_CALC_NEGATIVE_VALUE)
}
if len(parsedItems) == 4 { }
require.NoError(t, nil) }
} else if len(parsedItems) < 4 {
var errorstring1 string = "Too few results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 4 {
var errorstring1 string = "Too many results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} }
return 0, fmt.Errorf("GetFormattedCounterValueDouble: invalid handle: %d", counterHandle)
} }
func TestWinPerfcountersConfigGet6(t *testing.T) { func (m *FakePerformanceQuery) CollectData() error {
if !m.openCalled {
return errors.New("CollectData: uninitialised query")
}
return nil
}
var instances = make([]string, 1) func (m *FakePerformanceQuery) AddEnglishCounterSupported() bool {
var counters = make([]string, 1) return m.addEnglishSupported
var perfobjects = make([]perfobject, 1) }
objectname := "System"
instances[0] = "------"
counters[0] = "Context Switches/sec"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
func createPerfObject(measurement string, object string, instances []string, counters []string, failOnMissing bool, includeTotal bool) []perfobject {
PerfObject := perfobject{ PerfObject := perfobject{
ObjectName: objectname, ObjectName: object,
Instances: instances, Instances: instances,
Counters: counters, Counters: counters,
Measurement: measurement, Measurement: measurement,
WarnOnMissing: warnonmissing, WarnOnMissing: false,
FailOnMissing: failonmissing, FailOnMissing: failOnMissing,
IncludeTotal: includetotal, IncludeTotal: includeTotal,
} }
perfobjects := []perfobject{PerfObject}
return perfobjects
}
perfobjects[0] = PerfObject func createCounterMap(counterPaths []string, values []float64) map[string]testCounter {
counters := make(map[string]testCounter)
for i, cp := range counterPaths {
counters[cp] = testCounter{
PDH_HCOUNTER(i),
cp,
values[i],
}
}
return counters
}
m := Win_PerfCounters{PrintValid: false, Object: perfobjects} func TestAddItemSimple(t *testing.T) {
var err error
err := m.ParseConfig() cps1 := []string{"\\O(I)\\C"}
m := Win_PerfCounters{PrintValid: false, Object: nil, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1}),
expandPaths: map[string][]string{
cps1[0]: cps1,
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.AddItem(cps1[0], "I", "test", false)
require.NoError(t, err)
err = m.query.Close()
require.NoError(t, err) require.NoError(t, err)
} }
func TestWinPerfcountersConfigGet7(t *testing.T) { func TestAddItemInvalidCountPath(t *testing.T) {
var err error
var instances = make([]string, 1) cps1 := []string{"\\O\\C"}
var counters = make([]string, 3) m := Win_PerfCounters{PrintValid: false, Object: nil, query: &FakePerformanceQuery{
var perfobjects = make([]perfobject, 1) counters: createCounterMap(cps1, []float64{1.1}),
expandPaths: map[string][]string{
objectname := "Processor Information" cps1[0]: {"\\O/C"},
instances[0] = "_Total" },
counters[0] = "% Processor Time" addEnglishSupported: true,
counters[1] = "% Processor TimeERROR" }}
counters[2] = "% Idle Time" err = m.query.Open()
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = false
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err) require.NoError(t, err)
err = m.AddItem("\\O\\C", "*", "test", false)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 2 {
require.NoError(t, nil)
} else if len(parsedItems) < 2 {
var errorstring1 string = "Too few results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
} else if len(parsedItems) > 2 {
var errorstring1 string = "Too many results returned from the query: " +
string(len(parsedItems))
err2 := errors.New(errorstring1)
require.NoError(t, err2)
}
}
func TestWinPerfcountersConfigError1(t *testing.T) {
var instances = make([]string, 1)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
objectname := "Processor InformationERROR"
instances[0] = "_Total"
counters[0] = "% Processor Time"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.Error(t, err) require.Error(t, err)
err = m.query.Close()
require.NoError(t, err)
} }
func TestWinPerfcountersConfigError2(t *testing.T) { func TestParseConfigBasic(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, 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{
counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3, 1.4}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
cps1[3]: {cps1[3]},
},
addEnglishSupported: true,
}}
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)
}
var instances = make([]string, 1) func TestParseConfigNoInstance(t *testing.T) {
var counters = make([]string, 1) var err error
var perfobjects = make([]perfobject, 1) 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{
counters: createCounterMap(cps1, []float64{1.1, 1.2}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
},
addEnglishSupported: true,
}}
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)
}
objectname := "Processor" func TestParseConfigInvalidCounterError(t *testing.T) {
instances[0] = "SuperERROR" var err error
counters[0] = "% C1 Time" perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, true, false)
cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
var measurement string = "test" m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
var warnonmissing bool = false counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3}),
var failonmissing bool = true expandPaths: map[string][]string{
var includetotal bool = false cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
PerfObject := perfobject{ cps1[2]: {cps1[2]},
ObjectName: objectname, },
Instances: instances, addEnglishSupported: true,
Counters: counters, }}
Measurement: measurement, err = m.query.Open()
WarnOnMissing: warnonmissing, require.NoError(t, err)
FailOnMissing: failonmissing, err = m.ParseConfig()
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.Error(t, err) require.Error(t, err)
err = m.query.Close()
require.NoError(t, err)
} }
func TestWinPerfcountersConfigError3(t *testing.T) { func TestParseConfigInvalidCounterNoError(t *testing.T) {
var err error
var instances = make([]string, 1) perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false)
var counters = make([]string, 1) cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
var perfobjects = make([]perfobject, 1) m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3}),
objectname := "Processor Information" expandPaths: map[string][]string{
instances[0] = "_Total" cps1[0]: {cps1[0]},
counters[0] = "% Processor TimeERROR" cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
var measurement string = "test" },
var warnonmissing bool = false addEnglishSupported: true,
var failonmissing bool = true }}
var includetotal bool = false err = m.query.Open()
require.NoError(t, err)
PerfObject := perfobject{ err = m.ParseConfig()
ObjectName: objectname, require.NoError(t, err)
Instances: instances, err = m.query.Close()
Counters: counters, require.NoError(t, err)
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.Error(t, err)
} }
func TestWinPerfcountersCollect1(t *testing.T) { func TestParseConfigTotal(t *testing.T) {
var err error
var instances = make([]string, 1) perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true)
var counters = make([]string, 1) cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"}
var perfobjects = make([]perfobject, 1) m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
objectname := "Processor Information" expandPaths: map[string][]string{
instances[0] = "_Total" "\\O(*)\\*": cps1,
counters[0] = "Parking Status" },
addEnglishSupported: true,
var expectedCounter string = "Parking_Status" }}
err = m.query.Open()
var measurement string = "test" require.NoError(t, err)
var warnonmissing bool = false err = m.ParseConfig()
var failonmissing bool = true require.NoError(t, err)
var includetotal bool = false assert.Len(t, m.counters, 4)
err = m.query.Close()
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
var acc testutil.Accumulator
err := m.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
time.Sleep(2000 * time.Millisecond) perfObjects[0].IncludeTotal = false
err = m.Gather(&acc)
tags := map[string]string{
"instance": instances[0],
"objectname": objectname,
}
fields := map[string]interface{}{
expectedCounter: float32(0),
}
acc.AssertContainsTaggedFields(t, measurement, fields, tags)
m = Win_PerfCounters{PrintValid: false, 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,
},
addEnglishSupported: true,
}}
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 TestWinPerfcountersCollect2(t *testing.T) {
var instances = make([]string, 2) func TestParseConfigExpand(t *testing.T) {
var counters = make([]string, 1) var err error
var perfobjects = make([]perfobject, 1) 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{
counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps1,
},
addEnglishSupported: true,
}}
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)
}
objectname := "Processor Information" func TestSimpleGather(t *testing.T) {
instances[0] = "_Total" var err error
instances[1] = "0,0" if testing.Short() {
counters[0] = "Performance Limit Flags" t.Skip("Skipping long taking test in short mode")
var expectedCounter string = "Performance_Limit_Flags"
var measurement string = "test"
var warnonmissing bool = false
var failonmissing bool = true
var includetotal bool = false
PerfObject := perfobject{
ObjectName: objectname,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
} }
measurement := "test"
perfobjects[0] = PerfObject perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false)
cp1 := "\\O(I)\\C"
m := Win_PerfCounters{PrintValid: false, Object: perfobjects} m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
var acc testutil.Accumulator counters: createCounterMap([]string{cp1}, []float64{1.2}),
err := m.Gather(&acc) expandPaths: map[string][]string{
cp1: {cp1},
},
addEnglishSupported: false,
}}
var acc1 testutil.Accumulator
err = m.Gather(&acc1)
require.NoError(t, err) require.NoError(t, err)
time.Sleep(2000 * time.Millisecond) fields1 := map[string]interface{}{
err = m.Gather(&acc) "C": float32(1.2),
tags := map[string]string{
"instance": instances[0],
"objectname": objectname,
} }
fields := map[string]interface{}{ tags1 := map[string]string{
expectedCounter: float32(0), "instance": "I",
"objectname": "O",
}
acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
}
func TestGatherInvalidDataIgnore(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{"I"}, []string{"C1", "C2", "C3"}, false, false)
cps1 := []string{"\\O(I)\\C1", "\\O(I)\\C2", "\\O(I)\\C3"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.2, -1, 0}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
},
addEnglishSupported: false,
}}
var acc1 testutil.Accumulator
err = m.Gather(&acc1)
require.NoError(t, err)
fields1 := map[string]interface{}{
"C1": float32(1.2),
}
tags1 := map[string]string{
"instance": "I",
"objectname": "O",
}
acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
}
func TestGatherRefreshing(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)
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}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps1,
},
addEnglishSupported: true,
}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: fpm, CountersRefreshInterval: internal.Duration{Duration: time.Second * 10}}
var acc1 testutil.Accumulator
err = m.Gather(&acc1)
assert.Len(t, m.counters, 4)
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)
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(cps2, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 0}),
expandPaths: map[string][]string{
"\\O(*)\\*": cps2,
},
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",
} }
acc.AssertContainsTaggedFields(t, measurement, fields, tags) //test before elapsing CounterRefreshRate counters are not refreshed
tags = map[string]string{ err = m.Gather(&acc2)
"instance": instances[1], require.NoError(t, err)
"objectname": objectname, assert.Len(t, m.counters, 4)
} assert.Len(t, acc2.Metrics, 2)
fields = map[string]interface{}{
expectedCounter: float32(0), acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1)
} acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2)
acc.AssertContainsTaggedFields(t, measurement, fields, tags) acc2.AssertDoesNotContainsTaggedFields(t, measurement, fields3, tags3)
time.Sleep(m.CountersRefreshInterval.Duration)
var acc3 testutil.Accumulator
err = m.Gather(&acc3)
require.NoError(t, err)
assert.Len(t, acc3.Metrics, 3)
acc3.AssertContainsTaggedFields(t, measurement, fields1, tags1)
acc3.AssertContainsTaggedFields(t, measurement, fields2, tags2)
acc3.AssertContainsTaggedFields(t, measurement, fields3, tags3)
} }