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 ce3b367dac
commit 010e4f5b0b
6 changed files with 1502 additions and 629 deletions

View File

@ -1,19 +1,24 @@
# win_perf_counters readme
The way this plugin works is that on load of Telegraf,
the plugin will be handed configuration from Telegraf.
This configuration is parsed and then tested for validity such as
if the Object, Instance and Counter existing.
If it does not match at startup, it will not be fetched.
Exceptions to this are in cases where you query for all instances "*".
By default the plugin does not return _Total
when it is querying for all (*) as this is redundant.
Input plugin to read Performance Counters on Windows operating systems.
Configuration is parsed and then tested for validity such as
whether the Object, Instance and Counter exist on Telegraf startup.
Counter paths are refreshed periodically, see [CountersRefreshInterval](#countersrefreshinterval)
configuration parameter for more info.
Wildcards can be used in instance and counter names. Partial wildcards are supported only
in instance names on Windows Vista and newer.
In case of query for all instances `["*"]`, the plugin does not return the instance `_Total`
by default. See [IncludeTotal](#includetotal) for more info.
## Basics
The examples contained in this file have been found on the internet
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.
This file is likely to be updated in the future with more examples for
useful configurations for separate scenarios.
@ -29,9 +34,23 @@ Bool, if set to `true` will print out all matching performance objects.
Example:
`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
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.
@ -45,10 +64,10 @@ See Entry below.
### Entry
A new configuration entry consists of the TOML header to start with,
`[[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]]`.
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
**Required**
@ -60,37 +79,39 @@ Example: `ObjectName = "LogicalDisk"`
#### Instances
**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.
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.
By default any results containing _Total are stripped,
C:, D: and E: where relevant. To get all instances of a Counter, use `["*"]` only.
By default any results containing `_Total` are stripped,
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,
here only one option is valid if you want data back,
It is also possible to set partial wildcards, eg. `["chrome*"]`
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 = ["------"]`.
#### Counters
**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.
Example: `Counters = ["% Idle Time", "% Disk Read Time", "% Disk Write Time"]`
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
*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,
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,
separate from Processor results.
this is a good key to set with a value when you want your IIS and Disk results stored
separately from Processor results.
Example: `Measurement = "win_disk"
@ -99,9 +120,9 @@ Example: `Measurement = "win_disk"
This key is optional, it is a simple bool.
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 "*"
and you would also like all instances containg _Total returned,
like "_Total", "0,_Total" and so on where applicable
This key only has an effect if the Instances key is set to `["*"]`
and you would also like all instances containing `_Total` returned,
like `_Total`, `0,_Total` and so on where applicable
(Processor Information is one example).
#### 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.
This only has an effect on the first execution of the plugin,
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
*Internal*
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.
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 treated as false.
If this is set to true, the plugin will abort and end prematurely
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
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
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. Rebuild your counter values, which may take a few moments so please be
patient, by running:

View File

@ -198,6 +198,51 @@ type PDH_FMT_COUNTERVALUE_ITEM_LONG struct {
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 (
// Library
libpdhDll *syscall.DLL
@ -211,6 +256,8 @@ var (
pdh_GetFormattedCounterArrayW *syscall.Proc
pdh_OpenQuery *syscall.Proc
pdh_ValidatePathW *syscall.Proc
pdh_ExpandWildCardPathW *syscall.Proc
pdh_GetCounterInfoW *syscall.Proc
)
func init() {
@ -226,9 +273,11 @@ func init() {
pdh_GetFormattedCounterArrayW = libpdhDll.MustFindProc("PdhGetFormattedCounterArrayW")
pdh_OpenQuery = libpdhDll.MustFindProc("PdhOpenQuery")
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.
// 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
@ -277,7 +326,14 @@ func PdhAddCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintp
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.
func PdhAddEnglishCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) uint32 {
if pdh_AddEnglishCounterW == nil {
@ -294,7 +350,7 @@ func PdhAddEnglishCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserDat
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.
func PdhCloseQuery(hQuery PDH_HQUERY) uint32 {
ret, _, _ := pdh_CloseQuery.Call(uintptr(hQuery))
@ -329,7 +385,7 @@ func PdhCollectQueryData(hQuery PDH_HQUERY) uint32 {
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.
func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32, pValue *PDH_FMT_COUNTERVALUE_DOUBLE) uint32 {
ret, _, _ := pdh_GetFormattedCounterValue.Call(
@ -341,7 +397,7 @@ func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32,
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.
// An example of how this function can be used:
//
@ -389,7 +445,7 @@ func PdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *ui
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
// 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,
@ -405,7 +461,51 @@ func PdhOpenQuery(szDataSource uintptr, dwUserData uintptr, phQuery *PDH_HQUERY)
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.
func PdhValidatePath(path string) uint32 {
ptxt, _ := syscall.UTF16PtrFromString(path)
@ -414,13 +514,6 @@ func PdhValidatePath(path string) uint32 {
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 {
var flags uint32 = windows.FORMAT_MESSAGE_FROM_HMODULE | windows.FORMAT_MESSAGE_ARGUMENT_ARRAY | windows.FORMAT_MESSAGE_IGNORE_INSERTS
buf := make([]uint16, 300)
@ -430,3 +523,25 @@ func PdhFormatError(msgId uint32) string {
}
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 (
"errors"
"fmt"
"strings"
"unsafe"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
"log"
"regexp"
"strings"
"time"
)
var sampleConfig = `
@ -20,6 +22,8 @@ var sampleConfig = `
## agent, it will not be gathered.
## Settings:
# 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]]
# Processor usage, alternative to native, reports on a per core.
@ -53,7 +57,7 @@ var sampleConfig = `
Measurement = "win_system"
[[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.
ObjectName = "Memory"
Counters = [
@ -61,17 +65,20 @@ var sampleConfig = `
"Page Faults/sec", "Pages/sec", "Transition Faults/sec",
"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"
`
type Win_PerfCounters struct {
PrintValid bool
PreVistaSupport bool
Object []perfobject
PrintValid bool
//deprecated: determined dynamically
PreVistaSupport bool
Object []perfobject
CountersRefreshInterval internal.Duration
configParsed bool
itemCache []*item
lastRefreshed time.Time
counters []*counter
query PerformanceQuery
}
type perfobject struct {
@ -84,56 +91,101 @@ type perfobject struct {
IncludeTotal bool
}
type item struct {
query string
type counter struct {
counterPath string
objectName string
counter string
instance string
measurement string
include_total bool
handle PDH_HQUERY
includeTotal bool
counterHandle PDH_HCOUNTER
}
var sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec",
" ", "_", "%", "Percent", `\`, "")
func (m *Win_PerfCounters) AddItem(query string, objectName string, counter string, instance string,
measurement string, include_total bool) error {
//General Counter path pattern is: \\computer\object(parent/instance#index)\counter
//parent/instance#index part is skipped in single instance objects (e.g. Memory): \\computer\object\counter
var handle PDH_HQUERY
var counterHandle PDH_HCOUNTER
ret := PdhOpenQuery(0, 0, &handle)
if m.PreVistaSupport {
ret = PdhAddCounter(handle, query, 0, &counterHandle)
var counterPathRE = regexp.MustCompile(`.*\\(.*)\\(.*)`)
var objectInstanceRE = regexp.MustCompile(`(.*)\((.*)\)`)
//extractObjectInstanceCounterFromQuery gets object name, instance name (if available) and counter name from counter path
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 {
ret = PdhAddEnglishCounter(handle, query, 0, &counterHandle)
object = objectInstanceParts[0][1]
instance = objectInstanceParts[0][2]
}
// 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
return
}
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 {
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 {
var query string
var counterPath string
if len(m.Object) > 0 {
for _, PerfObject := range m.Object {
@ -142,21 +194,16 @@ func (m *Win_PerfCounters) ParseConfig() error {
objectname := PerfObject.ObjectName
if instance == "------" {
query = "\\" + objectname + "\\" + counter
counterPath = "\\" + objectname + "\\" + counter
} else {
query = "\\" + objectname + "(" + instance + ")\\" + counter
counterPath = "\\" + objectname + "(" + instance + ")\\" + counter
}
err := m.AddItem(query, objectname, counter, instance,
PerfObject.Measurement, PerfObject.IncludeTotal)
err := m.AddItem(counterPath, instance, PerfObject.Measurement, PerfObject.IncludeTotal)
if err == nil {
if m.PrintValid {
fmt.Printf("Valid: %s\n", query)
}
} else {
if err != nil {
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 {
return err
@ -165,32 +212,39 @@ func (m *Win_PerfCounters) ParseConfig() error {
}
}
}
return nil
} else {
err := errors.New("No performance objects configured!")
err := errors.New("no performance objects configured")
return err
}
}
func (m *Win_PerfCounters) GetParsedItemsForTesting() []*item {
return m.itemCache
}
func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
// Parse the config once
if !m.configParsed {
err := m.ParseConfig()
m.configParsed = true
var err error
if m.lastRefreshed.IsZero() || (m.CountersRefreshInterval.Duration.Nanoseconds() > 0 && m.lastRefreshed.Add(m.CountersRefreshInterval.Duration).Before(time.Now())) {
m.counters = m.counters[:0]
err = m.query.Open()
if err != nil {
return err
}
}
var bufSize uint32
var bufCount uint32
var size uint32 = uint32(unsafe.Sizeof(PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{}))
var emptyBuf [1]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr.
err = m.ParseConfig()
if err != nil {
return err
}
//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 {
name string
@ -200,78 +254,40 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
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 _, metric := range m.itemCache {
for _, metric := range m.counters {
// collect
ret := PdhCollectQueryData(metric.handle)
if ret == ERROR_SUCCESS {
ret = PdhGetFormattedCounterArrayDouble(metric.counterHandle, &bufSize,
&bufCount, &emptyBuf[0]) // uses null ptr here according to MSDN.
if ret == PDH_MORE_DATA {
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)
value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle)
if err == nil {
measurement := sanitizedChars.Replace(metric.measurement)
if measurement == "" {
measurement = "win_perf_counters"
}
var add bool
if metric.include_total {
// If IncludeTotal is set, include all.
add = true
} else if metric.instance == "*" && !strings.Contains(s, "_Total") {
// Catch if set to * and that it is not a '*_Total*' instance.
add = true
} else if metric.instance == s {
// 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
var instance = InstanceGrouping{measurement, metric.instance, metric.objectName}
if collectFields[instance] == nil {
collectFields[instance] = make(map[string]interface{})
}
collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value)
} else {
//ignore invalid data from as some counters from process instances returns this sometimes
if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE {
return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err)
}
}
}
for instance, fields := range collectFields {
var tags = map[string]string{
"instance": instance.instance,
"objectname": instance.objectname,
}
if len(instance.instance) > 0 {
tags["instance"] = instance.instance
}
acc.AddFields(instance.name, fields, tags)
}
@ -279,5 +295,7 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
}
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 (
"errors"
"fmt"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
func TestWinPerfcountersConfigGet1(t *testing.T) {
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 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)
type testCounter struct {
handle PDH_HCOUNTER
path string
value float64
}
type FakePerformanceQuery struct {
counters map[string]testCounter
addEnglishSupported bool
expandPaths map[string][]string
openCalled bool
}
func TestWinPerfcountersConfigGet2(t *testing.T) {
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 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,
func (m *FakePerformanceQuery) Open() error {
if m.openCalled {
err := m.Close()
if err != nil {
return err
}
}
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}
err := m.ParseConfig()
require.NoError(t, err)
var parsedItems = m.GetParsedItemsForTesting()
if len(parsedItems) == 1 {
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 (m *FakePerformanceQuery) AddCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
if !m.openCalled {
return 0, errors.New("AddCounterToQuery: uninitialised query")
}
if c, ok := m.counters[counterPath]; ok {
return c.handle, nil
} else {
return 0, errors.New(fmt.Sprintf("AddCounterToQuery: invalid counter path: %s", counterPath))
}
}
func TestWinPerfcountersConfigGet3(t *testing.T) {
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 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,
func (m *FakePerformanceQuery) AddEnglishCounterToQuery(counterPath string) (PDH_HCOUNTER, error) {
if !m.openCalled {
return 0, errors.New("AddEnglishCounterToQuery: uninitialised query")
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
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)
if c, ok := m.counters[counterPath]; ok {
return c.handle, nil
} else {
return 0, fmt.Errorf("AddEnglishCounterToQuery: invalid counter path: %s", counterPath)
}
}
func TestWinPerfcountersConfigGet4(t *testing.T) {
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 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,
func (m *FakePerformanceQuery) GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) {
for _, counter := range m.counters {
if counter.handle == counterHandle {
return counter.path, nil
}
}
return "", fmt.Errorf("GetCounterPath: invalid handle: %d", counterHandle)
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
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 (m *FakePerformanceQuery) ExpandWildCardPath(counterPath string) ([]string, error) {
if e, ok := m.expandPaths[counterPath]; ok {
return e, nil
} else {
return []string{}, fmt.Errorf("ExpandWildCardPath: invalid counter path: %s", counterPath)
}
}
func TestWinPerfcountersConfigGet5(t *testing.T) {
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 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,
func (m *FakePerformanceQuery) GetFormattedCounterValueDouble(counterHandle PDH_HCOUNTER) (float64, error) {
if !m.openCalled {
return 0, errors.New("GetFormattedCounterValueDouble: uninitialised query")
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
err := m.ParseConfig()
require.NoError(t, err)
var parsedItems = m.GetParsedItemsForTesting()
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)
for _, counter := range m.counters {
if counter.handle == counterHandle {
if counter.value > 0 {
return counter.value, nil
} else {
if counter.value == 0 {
return 0, NewPdhError(PDH_INVALID_DATA)
} else {
return 0, NewPdhError(PDH_CALC_NEGATIVE_VALUE)
}
}
}
}
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)
var counters = make([]string, 1)
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 (m *FakePerformanceQuery) AddEnglishCounterSupported() bool {
return m.addEnglishSupported
}
func createPerfObject(measurement string, object string, instances []string, counters []string, failOnMissing bool, includeTotal bool) []perfobject {
PerfObject := perfobject{
ObjectName: objectname,
ObjectName: object,
Instances: instances,
Counters: counters,
Measurement: measurement,
WarnOnMissing: warnonmissing,
FailOnMissing: failonmissing,
IncludeTotal: includetotal,
WarnOnMissing: false,
FailOnMissing: failOnMissing,
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}
err := m.ParseConfig()
func TestAddItemSimple(t *testing.T) {
var err error
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)
}
func TestWinPerfcountersConfigGet7(t *testing.T) {
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 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()
func TestAddItemInvalidCountPath(t *testing.T) {
var err error
cps1 := []string{"\\O\\C"}
m := Win_PerfCounters{PrintValid: false, Object: nil, query: &FakePerformanceQuery{
counters: createCounterMap(cps1, []float64{1.1}),
expandPaths: map[string][]string{
cps1[0]: {"\\O/C"},
},
addEnglishSupported: true,
}}
err = m.query.Open()
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 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()
err = m.AddItem("\\O\\C", "*", "test", false)
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)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
func TestParseConfigNoInstance(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"------"}, []string{"C1", "C2"}, false, false)
cps1 := []string{"\\O\\C1", "\\O\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
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"
instances[0] = "SuperERROR"
counters[0] = "% C1 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()
func TestParseConfigInvalidCounterError(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, true, false)
cps1 := []string{"\\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}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.Error(t, err)
err = m.query.Close()
require.NoError(t, err)
}
func TestWinPerfcountersConfigError3(t *testing.T) {
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 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)
func TestParseConfigInvalidCounterNoError(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false)
cps1 := []string{"\\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}),
expandPaths: map[string][]string{
cps1[0]: {cps1[0]},
cps1[1]: {cps1[1]},
cps1[2]: {cps1[2]},
},
addEnglishSupported: true,
}}
err = m.query.Open()
require.NoError(t, err)
err = m.ParseConfig()
require.NoError(t, err)
err = m.query.Close()
require.NoError(t, err)
}
func TestWinPerfcountersCollect1(t *testing.T) {
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 string = "Parking_Status"
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}
var acc testutil.Accumulator
err := m.Gather(&acc)
func TestParseConfigTotal(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, true, true)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
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)
time.Sleep(2000 * time.Millisecond)
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)
perfObjects[0].IncludeTotal = false
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)
var counters = make([]string, 1)
var perfobjects = make([]perfobject, 1)
func TestParseConfigExpand(t *testing.T) {
var err error
perfObjects := createPerfObject("m", "O", []string{"*"}, []string{"*"}, false, false)
cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
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"
instances[0] = "_Total"
instances[1] = "0,0"
counters[0] = "Performance Limit Flags"
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,
func TestSimpleGather(t *testing.T) {
var err error
if testing.Short() {
t.Skip("Skipping long taking test in short mode")
}
perfobjects[0] = PerfObject
m := Win_PerfCounters{PrintValid: false, Object: perfobjects}
var acc testutil.Accumulator
err := m.Gather(&acc)
measurement := "test"
perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false)
cp1 := "\\O(I)\\C"
m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{
counters: createCounterMap([]string{cp1}, []float64{1.2}),
expandPaths: map[string][]string{
cp1: {cp1},
},
addEnglishSupported: false,
}}
var acc1 testutil.Accumulator
err = m.Gather(&acc1)
require.NoError(t, err)
time.Sleep(2000 * time.Millisecond)
err = m.Gather(&acc)
tags := map[string]string{
"instance": instances[0],
"objectname": objectname,
fields1 := map[string]interface{}{
"C": float32(1.2),
}
fields := map[string]interface{}{
expectedCounter: float32(0),
tags1 := map[string]string{
"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)
tags = map[string]string{
"instance": instances[1],
"objectname": objectname,
}
fields = map[string]interface{}{
expectedCounter: float32(0),
}
acc.AssertContainsTaggedFields(t, measurement, fields, tags)
//test before elapsing CounterRefreshRate counters are not refreshed
err = m.Gather(&acc2)
require.NoError(t, err)
assert.Len(t, m.counters, 4)
assert.Len(t, acc2.Metrics, 2)
acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1)
acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2)
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)
}