Fix wildcard and other issues with win_perf_counters (#4189)
This commit is contained in:
committed by
Daniel Nelson
parent
ce3b367dac
commit
010e4f5b0b
@@ -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}}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user