Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

win_perf_counters: New option "Expand" expands wildcards in counter definitions. #2336

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions plugins/inputs/win_perf_counters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ It is a simple bool, if it is not set to true or included this is treaded as fal
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
*Optional*

This key is optional, it is a simple bool.
If it is not set to true or included it is treated as false.
When the option is set to true Telegraf will expand wildcards "*"
used in defintions of ObjectName/Instance/Counter.
Read more about wildcards in https://msdn.microsoft.com/en-us/library/windows/desktop/aa372605(v=vs.85).aspx

## Examples

### Generic Queries
Expand Down
28 changes: 28 additions & 0 deletions plugins/inputs/win_perf_counters/pdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ var (
pdh_GetFormattedCounterArrayW *syscall.Proc
pdh_OpenQuery *syscall.Proc
pdh_ValidatePathW *syscall.Proc
pdh_ExpandWildCardPathW *syscall.Proc
)

func init() {
Expand All @@ -223,6 +224,7 @@ func init() {
pdh_GetFormattedCounterArrayW = libpdhDll.MustFindProc("PdhGetFormattedCounterArrayW")
pdh_OpenQuery = libpdhDll.MustFindProc("PdhOpenQuery")
pdh_ValidatePathW = libpdhDll.MustFindProc("PdhValidatePathW")
pdh_ExpandWildCardPathW = libpdhDll.MustFindProc("PdhExpandWildCardPathW")
}

// Adds the specified counter to the query. This is the internationalized version. Preferably, use the
Expand Down Expand Up @@ -402,6 +404,19 @@ func PdhOpenQuery(szDataSource uintptr, dwUserData uintptr, phQuery *PDH_HQUERY)
return uint32(ret)
}

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)
}

// Validates a path. Will return ERROR_SUCCESS when ok, or PDH_CSTATUS_BAD_COUNTERNAME when the path is
// erroneous.
func PdhValidatePath(path string) uint32 {
Expand All @@ -417,3 +432,16 @@ func UTF16PtrToString(s *uint16) string {
}
return syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(s))[0:])
}

func UTF16ToStringArray(buf []uint16) []string {
var strings []string
nextLineStart := 0
stringLine := UTF16PtrToString(&buf[0])
for stringLine != "" {
strings = append(strings, stringLine)
nextLineStart += len(stringLine) + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure that +1 should not be here.
If your string has a length of 1 character, the next string is one bytes later, not 1+1 bytes later :)
And this is very likely my issue with panic: runtime error

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PierreF The strings are NULL terminated, so that +1 is for hopping over that one I guess.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very fishy to me, the length of stringLine tells us nothing about the offset into buf, because buf contains utf16 while stringLine contains utf8.

remainingBuf := buf[nextLineStart:]
stringLine = UTF16PtrToString(&remainingBuf[0])
}
return strings
}
88 changes: 65 additions & 23 deletions plugins/inputs/win_perf_counters/win_perf_counters.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/influxdata/telegraf/plugins/inputs"
)

var sampleConfig string = `
var sampleConfig = `
## By default this plugin returns basic CPU and Disk statistics.
## See the README file for more examples.
## Uncomment examples below or write your own as you see fit. If the system
Expand Down Expand Up @@ -87,6 +87,7 @@ type perfobject struct {
WarnOnMissing bool
FailOnMissing bool
IncludeTotal bool
Expand bool
}

// Parsed configuration ends up here after it has been validated for valid
Expand All @@ -109,8 +110,14 @@ type item struct {
var sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec",
" ", "_", "%", "Percent", `\`, "")

func (m *Win_PerfCounters) AddItem(metrics *itemList, query string, objectName string, counter string, instance string,
measurement string, include_total bool) error {
func (m *Win_PerfCounters) AddItem(
metrics *itemList,
query string,
objectName string,
counter string,
instance string,
measurement string,
includeTotal bool) error {

var handle PDH_HQUERY
var counterHandle PDH_HCOUNTER
Expand All @@ -129,7 +136,7 @@ func (m *Win_PerfCounters) AddItem(metrics *itemList, query string, objectName s
}

temp := &item{query, objectName, counter, instance, measurement,
include_total, handle, counterHandle}
includeTotal, handle, counterHandle}
index := len(gItemList)
gItemList[index] = temp

Expand All @@ -148,34 +155,69 @@ func (m *Win_PerfCounters) SampleConfig() string {
return sampleConfig
}

func (m *Win_PerfCounters) ParseConfig(metrics *itemList) error {
var query string
func expandCounterQuery(query string) ([]string, error) {
var bufSize uint32
var buf []uint16
ret := PdhExpandWildCardPath(query, nil, &bufSize)
for ret == PDH_MORE_DATA {
buf = make([]uint16, bufSize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code does not seem defensive enough to me. Can bufSize be zero here, if so then the next line will panic. What if ret is not PDH_MORE_DATA at least once, then we will try to index it in UTF16ToStringArray and panic. In general we need to check anything that crosses an application boundary before we can assume anything about it.

ret = PdhExpandWildCardPath(query, &buf[0], &bufSize)
}
if ret == ERROR_SUCCESS {
return UTF16ToStringArray(buf), nil
}
return nil, fmt.Errorf("Failed to expand query: '%s', err(%d)", query, ret)
}

func formatCounterQuery(objectname string, instance string, counter string) string {
if instance == "------" {
return "\\" + objectname + "\\" + counter
}
return "\\" + objectname + "(" + instance + ")\\" + counter
}

func (m *Win_PerfCounters) ProcessQueryStatus(query string, err error, perfObject perfobject) {
if err == nil {
if m.PrintValid {
fmt.Printf("Valid: %s\n", query)
}
} else {
if perfObject.FailOnMissing || perfObject.WarnOnMissing {
fmt.Printf("Invalid query: %s\n", query)
}
}
}

func expandQuery(query string, expand bool) ([]string, error) {
if expand {
return expandCounterQuery(query)
}
return []string{query}, nil
}

func (m *Win_PerfCounters) ParseConfig(metrics *itemList) error {
configParsed = true

if len(m.Object) > 0 {
for _, PerfObject := range m.Object {
for _, counter := range PerfObject.Counters {
for _, instance := range PerfObject.Instances {
objectname := PerfObject.ObjectName
query := formatCounterQuery(objectname, instance, counter)
expandedQueries, err := expandQuery(query, PerfObject.Expand)

if instance == "------" {
query = "\\" + objectname + "\\" + counter
} else {
query = "\\" + objectname + "(" + instance + ")\\" + counter
if err != nil {
m.ProcessQueryStatus(query, err, PerfObject)
if PerfObject.FailOnMissing {
return err
}
continue
}

err := m.AddItem(metrics, query, objectname, counter, instance,
PerfObject.Measurement, PerfObject.IncludeTotal)

if err == nil {
if m.PrintValid {
fmt.Printf("Valid: %s\n", query)
}
} else {
if PerfObject.FailOnMissing || PerfObject.WarnOnMissing {
fmt.Printf("Invalid query: %s\n", query)
}
for _, expandedQuery := range expandedQueries {
err = m.AddItem(metrics, expandedQuery, objectname, counter, instance,
PerfObject.Measurement, PerfObject.IncludeTotal)
m.ProcessQueryStatus(expandedQuery, err, PerfObject)
if PerfObject.FailOnMissing {
return err
}
Expand Down Expand Up @@ -233,8 +275,8 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {

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.
size := uint32(unsafe.Sizeof(PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{}))

// For iterate over the known metrics and get the samples.
for _, metric := range gItemList {
Expand All @@ -252,7 +294,7 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
&bufSize, &bufCount, &filledBuf[0])
for i := 0; i < int(bufCount); i++ {
c := filledBuf[i]
var s string = UTF16PtrToString(c.SzName)
s := UTF16PtrToString(c.SzName)

var add bool

Expand Down