Skip to content

Commit

Permalink
feat: Implemented support for reading raw values, added tests and doc (
Browse files Browse the repository at this point in the history
  • Loading branch information
vlastahajek authored Feb 10, 2022
1 parent 437d3b0 commit 3ef1c73
Show file tree
Hide file tree
Showing 8 changed files with 542 additions and 111 deletions.
14 changes: 13 additions & 1 deletion plugins/inputs/win_perf_counters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,19 @@ So for ordering your data in a good manner,
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"``
Example: `Measurement = "win_disk"`

#### UseRawValues

(Optional)

This key is optional. It is a simple bool.
If set to `true`, counter values will be provided in the raw, integer, form. This is in contrast with the default behavior, where values are returned in a formatted, displayable, form
as seen in the Windows Performance Monitor.
A field representing raw counter value has the `_Raw` suffix. Raw values should be further used in a calculation, e.g. `100-(non_negative_derivative("Percent_Processor_Time_Raw",1s)/100000`
Note: Time based counters (i.e. _% Processor Time_) are reported in hundredths of nanoseconds.

Example: `UseRawValues = true`

#### IncludeTotal

Expand Down
51 changes: 51 additions & 0 deletions plugins/inputs/win_perf_counters/pdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ var (
pdh_ValidatePathW *syscall.Proc
pdh_ExpandWildCardPathW *syscall.Proc
pdh_GetCounterInfoW *syscall.Proc
pdh_GetRawCounterValue *syscall.Proc
pdh_GetRawCounterArrayW *syscall.Proc
)

func init() {
Expand All @@ -290,6 +292,8 @@ func init() {
pdh_ValidatePathW = libpdhDll.MustFindProc("PdhValidatePathW")
pdh_ExpandWildCardPathW = libpdhDll.MustFindProc("PdhExpandWildCardPathW")
pdh_GetCounterInfoW = libpdhDll.MustFindProc("PdhGetCounterInfoW")
pdh_GetRawCounterValue = libpdhDll.MustFindProc("PdhGetRawCounterValue")
pdh_GetRawCounterArrayW = libpdhDll.MustFindProc("PdhGetRawCounterArrayW")
}

// PdhAddCounter adds the specified counter to the query. This is the internationalized version. Preferably, use the
Expand Down Expand Up @@ -591,3 +595,50 @@ func PdhGetCounterInfo(hCounter PDH_HCOUNTER, bRetrieveExplainText int, pdwBuffe

return uint32(ret)
}

// Returns the current raw value of the counter.
// If the specified counter instance does not exist, this function will return ERROR_SUCCESS
// and the CStatus member of the PDH_RAW_COUNTER structure will contain PDH_CSTATUS_NO_INSTANCE.
//
// hCounter [in]
// Handle of the counter from which to retrieve the current raw value. The PdhAddCounter function returns this handle.
//
// lpdwType [out]
// Receives the counter type. For a list of counter types, see the Counter Types section of the Windows Server 2003 Deployment Kit.
// This parameter is optional.
//
// pValue [out]
// A PDH_RAW_COUNTER structure that receives the counter value.
func PdhGetRawCounterValue(hCounter PDH_HCOUNTER, lpdwType *uint32, pValue *PDH_RAW_COUNTER) uint32 {
ret, _, _ := pdh_GetRawCounterValue.Call(
uintptr(hCounter),
uintptr(unsafe.Pointer(lpdwType)),
uintptr(unsafe.Pointer(pValue)))

return uint32(ret)
}

// Returns an array of raw values from the specified counter. Use this function when you want to retrieve the raw counter values
// of a counter that contains a wildcard character for the instance name.
// hCounter
// Handle of the counter for whose current raw instance values you want to retrieve. The PdhAddCounter function returns this handle.
//
// lpdwBufferSize
// Size of the ItemBuffer 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.
//
// lpdwItemCount
// Number of raw counter values in the ItemBuffer buffer.
//
// ItemBuffer
// Caller-allocated buffer that receives the array of PDH_RAW_COUNTER_ITEM structures; the structures contain the raw instance counter values.
// Set to NULL if lpdwBufferSize is zero.
func PdhGetRawCounterArray(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *byte) uint32 {
ret, _, _ := pdh_GetRawCounterArrayW.Call(
uintptr(hCounter),
uintptr(unsafe.Pointer(lpdwBufferSize)),
uintptr(unsafe.Pointer(lpdwBufferCount)),
uintptr(unsafe.Pointer(itemBuffer)))
return uint32(ret)
}
23 changes: 23 additions & 0 deletions plugins/inputs/win_perf_counters/pdh_386.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,26 @@ type PDH_COUNTER_INFO struct {
//Start of the string data that is appended to the structure.
DataBuffer [1]uint32 // pointer to an extra space
}

// The PDH_RAW_COUNTER structure returns the data as it was collected from the counter provider. No translation, formatting, or other interpretation is performed on the data
type PDH_RAW_COUNTER struct {
// Counter status that indicates if the counter value is valid. Check this member before using the data in a calculation or displaying its value. For a list of possible values,
// see https://docs.microsoft.com/windows/desktop/PerfCtrs/checking-pdh-interface-return-values
CStatus uint32
// Local time for when the data was collected
TimeStamp FILETIME
// First raw counter value.
FirstValue int64
// Second raw counter value. Rate counters require two values in order to compute a displayable value.
SecondValue int64
// If the counter type contains the PERF_MULTI_COUNTER flag, this member contains the additional counter data used in the calculation.
// For example, the PERF_100NSEC_MULTI_TIMER counter type contains the PERF_MULTI_COUNTER flag.
MultiCount uint32
}

type PDH_RAW_COUNTER_ITEM struct {
// Pointer to a null-terminated string that specifies the instance name of the counter. The string is appended to the end of this structure.
SzName *uint16
//A PDH_RAW_COUNTER structure that contains the raw counter value of the instance
RawValue PDH_RAW_COUNTER
}
23 changes: 23 additions & 0 deletions plugins/inputs/win_perf_counters/pdh_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,26 @@ type PDH_COUNTER_INFO struct {
//Start of the string data that is appended to the structure.
DataBuffer [1]uint32 // pointer to an extra space
}

// The PDH_RAW_COUNTER structure returns the data as it was collected from the counter provider. No translation, formatting, or other interpretation is performed on the data
type PDH_RAW_COUNTER struct {
// Counter status that indicates if the counter value is valid. Check this member before using the data in a calculation or displaying its value. For a list of possible values,
// see https://docs.microsoft.com/windows/desktop/PerfCtrs/checking-pdh-interface-return-values
CStatus uint32
// Local time for when the data was collected
TimeStamp FILETIME
// First raw counter value.
FirstValue int64
// Second raw counter value. Rate counters require two values in order to compute a displayable value.
SecondValue int64
// If the counter type contains the PERF_MULTI_COUNTER flag, this member contains the additional counter data used in the calculation.
// For example, the PERF_100NSEC_MULTI_TIMER counter type contains the PERF_MULTI_COUNTER flag.
MultiCount uint32
}

type PDH_RAW_COUNTER_ITEM struct {
// Pointer to a null-terminated string that specifies the instance name of the counter. The string is appended to the end of this structure.
SzName *uint16
//A PDH_RAW_COUNTER structure that contains the raw counter value of the instance
RawValue PDH_RAW_COUNTER
}
48 changes: 47 additions & 1 deletion plugins/inputs/win_perf_counters/performance_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
//PerformanceQuery is abstraction for PDH_FMT_COUNTERVALUE_ITEM_DOUBLE
type CounterValue struct {
InstanceName string
Value float64
Value interface{}
}

//PerformanceQuery provides wrappers around Windows performance counters API for easy usage in GO
Expand All @@ -26,7 +26,9 @@ type PerformanceQuery interface {
GetCounterPath(counterHandle PDH_HCOUNTER) (string, error)
ExpandWildCardPath(counterPath string) ([]string, error)
GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error)
GetRawCounterValue(hCounter PDH_HCOUNTER) (int64, error)
GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error)
GetRawCounterArray(hCounter PDH_HCOUNTER) ([]CounterValue, error)
CollectData() error
CollectDataWithTime() (time.Time, error)
IsVistaOrNewer() bool
Expand Down Expand Up @@ -182,6 +184,29 @@ func (m *PerformanceQueryImpl) GetFormattedCounterArrayDouble(hCounter PDH_HCOUN
return nil, NewPdhError(ret)
}

func (m *PerformanceQueryImpl) GetRawCounterArray(hCounter PDH_HCOUNTER) ([]CounterValue, error) {
var buffSize uint32
var itemCount uint32
var ret uint32

if ret = PdhGetRawCounterArray(hCounter, &buffSize, &itemCount, nil); ret == PDH_MORE_DATA {
buff := make([]byte, buffSize)

if ret = PdhGetRawCounterArray(hCounter, &buffSize, &itemCount, &buff[0]); ret == ERROR_SUCCESS {
items := (*[1 << 20]PDH_RAW_COUNTER_ITEM)(unsafe.Pointer(&buff[0]))[:itemCount]
values := make([]CounterValue, 0, itemCount)
for _, item := range items {
if item.RawValue.CStatus == PDH_CSTATUS_VALID_DATA || item.RawValue.CStatus == PDH_CSTATUS_NEW_DATA {
val := CounterValue{UTF16PtrToString(item.SzName), item.RawValue.FirstValue}
values = append(values, val)
}
}
return values, nil
}
}
return nil, NewPdhError(ret)
}

func (m *PerformanceQueryImpl) CollectData() error {
var ret uint32
if m.query == 0 {
Expand Down Expand Up @@ -209,6 +234,27 @@ func (m *PerformanceQueryImpl) IsVistaOrNewer() bool {
return PdhAddEnglishCounterSupported()
}

func (m *PerformanceQueryImpl) GetRawCounterValue(hCounter PDH_HCOUNTER) (int64, error) {
if m.query == 0 {
return 0, errors.New("uninitialised query")
}

var counterType uint32
var value PDH_RAW_COUNTER
var ret uint32

if ret = PdhGetRawCounterValue(hCounter, &counterType, &value); ret == ERROR_SUCCESS {
if value.CStatus == PDH_CSTATUS_VALID_DATA || value.CStatus == PDH_CSTATUS_NEW_DATA {
return value.FirstValue, nil
} else {
return 0, NewPdhError(value.CStatus)
}
} else {
return 0, NewPdhError(ret)
}

}

// UTF16PtrToString converts Windows API LPTSTR (pointer to string) to go string
func UTF16PtrToString(s *uint16) string {
if s == nil {
Expand Down
89 changes: 64 additions & 25 deletions plugins/inputs/win_perf_counters/win_perf_counters.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ var sampleConfig = `
# IncludeTotal=false
# Print out when the performance counter is missing from object, counter or instance.
# WarnOnMissing = false
# Gather raw values instead of formatted. Raw value is stored in the field name with the "_Raw" suffix, e.g. "Disk_Read_Bytes_sec_Raw".
# UseRawValues = true
[[inputs.win_perf_counters.object]]
# Disk times and queues
Expand Down Expand Up @@ -174,6 +176,7 @@ type perfobject struct {
WarnOnMissing bool
FailOnMissing bool
IncludeTotal bool
UseRawValues bool
}

type counter struct {
Expand All @@ -183,6 +186,7 @@ type counter struct {
instance string
measurement string
includeTotal bool
useRawValue bool
counterHandle PDH_HCOUNTER
}

Expand Down Expand Up @@ -257,8 +261,20 @@ func (m *Win_PerfCounters) SampleConfig() string {
return sampleConfig
}

//objectName string, counter string, instance string, measurement string, include_total bool
func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instance string, counterName string, measurement string, includeTotal bool) error {
func newCounter(counterHandle PDH_HCOUNTER, counterPath string, objectName string, instance string, counterName string, measurement string, includeTotal bool, useRawValue bool) *counter {
measurementName := sanitizedChars.Replace(measurement)
if measurementName == "" {
measurementName = "win_perf_counters"
}
newCounterName := sanitizedChars.Replace(counterName)
if useRawValue {
newCounterName += "_Raw"
}
return &counter{counterPath, objectName, newCounterName, instance, measurementName,
includeTotal, useRawValue, counterHandle}
}

func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instance string, counterName string, measurement string, includeTotal bool, useRawValue bool) error {
origCounterPath := counterPath
var err error
var counterHandle PDH_HCOUNTER
Expand Down Expand Up @@ -315,20 +331,27 @@ func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instan
}
counterPath = formatPath(origObjectName, newInstance, origCounterName)
counterHandle, err = m.query.AddEnglishCounterToQuery(counterPath)
newItem = &counter{
newItem = newCounter(
counterHandle,
counterPath,
origObjectName, origCounterName,
instance, measurement,
includeTotal, counterHandle,
}
origObjectName, instance,
origCounterName,
measurement,
includeTotal,
useRawValue,
)
} else {
counterHandle, err = m.query.AddCounterToQuery(counterPath)
newItem = &counter{
newItem = newCounter(
counterHandle,
counterPath,
objectName, counterName,
instance, measurement,
includeTotal, counterHandle,
}
objectName,
instance,
counterName,
measurement,
includeTotal,
useRawValue,
)
}

if instance == "_Total" && origInstance == "*" && !includeTotal {
Expand All @@ -342,8 +365,16 @@ func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instan
}
}
} else {
newItem := &counter{counterPath, objectName, counterName, instance, measurement,
includeTotal, counterHandle}
newItem := newCounter(
counterHandle,
counterPath,
objectName,
instance,
counterName,
measurement,
includeTotal,
useRawValue,
)
m.counters = append(m.counters, newItem)
if m.PrintValid {
m.Log.Infof("Valid: %s", counterPath)
Expand All @@ -369,12 +400,15 @@ func (m *Win_PerfCounters) ParseConfig() error {
if len(m.Object) > 0 {
for _, PerfObject := range m.Object {
for _, counter := range PerfObject.Counters {
if len(PerfObject.Instances) == 0 {
m.Log.Warnf("Missing 'Instances' param for object '%s'\n", PerfObject.ObjectName)
}
for _, instance := range PerfObject.Instances {
objectname := PerfObject.ObjectName

counterPath = formatPath(objectname, instance, counter)

err := m.AddItem(counterPath, objectname, instance, counter, PerfObject.Measurement, PerfObject.IncludeTotal)
err := m.AddItem(counterPath, objectname, instance, counter, PerfObject.Measurement, PerfObject.IncludeTotal, PerfObject.UseRawValues)

if err != nil {
if PerfObject.FailOnMissing || PerfObject.WarnOnMissing {
Expand Down Expand Up @@ -447,12 +481,16 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
return err
}
}

var value interface{}
// For iterate over the known metrics and get the samples.
for _, metric := range m.counters {
// collect
if m.UseWildcardsExpansion {
value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle)
if metric.useRawValue {
value, err = m.query.GetRawCounterValue(metric.counterHandle)
} else {
value, err = m.query.GetFormattedCounterValueDouble(metric.counterHandle)
}
if err != nil {
//ignore invalid data as some counters from process instances returns this sometimes
if !isKnownCounterDataError(err) {
Expand All @@ -463,7 +501,12 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
}
addCounterMeasurement(metric, metric.instance, value, collectFields)
} else {
counterValues, err := m.query.GetFormattedCounterArrayDouble(metric.counterHandle)
var counterValues []CounterValue
if metric.useRawValue {
counterValues, err = m.query.GetRawCounterArray(metric.counterHandle)
} else {
counterValues, err = m.query.GetFormattedCounterArrayDouble(metric.counterHandle)
}
if err != nil {
//ignore invalid data as some counters from process instances returns this sometimes
if !isKnownCounterDataError(err) {
Expand Down Expand Up @@ -519,16 +562,12 @@ func shouldIncludeMetric(metric *counter, cValue CounterValue) bool {
return false
}

func addCounterMeasurement(metric *counter, instanceName string, value float64, collectFields map[instanceGrouping]map[string]interface{}) {
measurement := sanitizedChars.Replace(metric.measurement)
if measurement == "" {
measurement = "win_perf_counters"
}
var instance = instanceGrouping{measurement, instanceName, metric.objectName}
func addCounterMeasurement(metric *counter, instanceName string, value interface{}, collectFields map[instanceGrouping]map[string]interface{}) {
var instance = instanceGrouping{metric.measurement, instanceName, metric.objectName}
if collectFields[instance] == nil {
collectFields[instance] = make(map[string]interface{})
}
collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value)
collectFields[instance][sanitizedChars.Replace(metric.counter)] = value
}

func isKnownCounterDataError(err error) bool {
Expand Down
Loading

0 comments on commit 3ef1c73

Please sign in to comment.