Skip to content

Commit

Permalink
Fix wildcard and other issues with win_perf_counters (influxdata#4189)
Browse files Browse the repository at this point in the history
  • Loading branch information
vlastahajek authored and otherpirate committed Mar 15, 2019
1 parent 7f1dba5 commit d58595d
Show file tree
Hide file tree
Showing 6 changed files with 1,491 additions and 618 deletions.
85 changes: 55 additions & 30 deletions plugins/inputs/win_perf_counters/README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.

Expand All @@ -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**
Expand All @@ -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.

It is also possible to set partial wildcards, eg. `["chrome*"]`

Some Objects does not have instances to select from at all,
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 = ["------"]`.

#### 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"

Expand All @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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:
Expand Down
143 changes: 129 additions & 14 deletions plugins/inputs/win_perf_counters/pdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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(
Expand All @@ -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:
//
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
}
Loading

0 comments on commit d58595d

Please sign in to comment.