317 lines
8.2 KiB
Go
317 lines
8.2 KiB
Go
|
// +build windows
|
||
|
|
||
|
package wpc
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
"unsafe"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||
|
lxn "github.com/lxn/win"
|
||
|
)
|
||
|
|
||
|
var sampleConfig string = `
|
||
|
## A plugin to collect stats from Windows Performance Counters.
|
||
|
## If the system being polled for data does not have a particular Counter at startup
|
||
|
## of the Telegraf agent, it will not be gathered.
|
||
|
# Prints all matching performance counters (useful for debugging)
|
||
|
# PrintValid = false
|
||
|
|
||
|
[[inputs.wpc.template]]
|
||
|
# Processor usage, alternative to native.
|
||
|
Counters = [
|
||
|
[ "usage_idle", "\\Processor(*)\\%% Idle Time" ],
|
||
|
[ "usage_user", "\\Processor(*)\\%% User Time" ],
|
||
|
[ "usage_system", "\\Processor(*)\\%% Processor Time" ]
|
||
|
]
|
||
|
Measurement = "win_cpu"
|
||
|
# Print out when the performance counter is missing from object, counter or instance.
|
||
|
# WarnOnMissing = false
|
||
|
|
||
|
|
||
|
[[inputs.wpc.template]]
|
||
|
# Disk times and queues
|
||
|
Counters = [
|
||
|
[ "usage_idle", "\\LogicalDisk(*)\\%% Idle Time" ],
|
||
|
[ "usage_used", "\\LogicalDisk(*)\\%% Disk Time" ],
|
||
|
[ "usage_read", "\\LogicalDisk(*)\\%% Disk Read Time" ],
|
||
|
[ "usage_write", "\\LogicalDisk(*)\\%% Disk Write Time" ],
|
||
|
[ "usage_user", "\\LogicalDisk(*)\\%% User Time" ],
|
||
|
[ "qcur", "\\LogicalDisk(*)\\Current Disk Queue Length" ]
|
||
|
]
|
||
|
Measurement = "win_diskio"
|
||
|
# Print out when the performance counter is missing from object, counter or instance.
|
||
|
# WarnOnMissing = false
|
||
|
|
||
|
[[inputs.wpc.template]]
|
||
|
# System and memory details
|
||
|
Counters = [
|
||
|
[ "cs_rate", "\\System\\Context Switches/sec" ],
|
||
|
[ "syscall_rate", "\\System\\System Calls/sec" ],
|
||
|
[ "mem_available", "\\Memory\\Available Bytes" ]
|
||
|
]
|
||
|
Measurement = "win_system"
|
||
|
# Print out when the performance counter is missing from object, counter or instance.
|
||
|
# WarnOnMissing = false
|
||
|
`
|
||
|
|
||
|
type WindowsPerformanceCounter struct {
|
||
|
PrintValid bool
|
||
|
TestName string
|
||
|
PreVistaSupport bool
|
||
|
Template []template
|
||
|
}
|
||
|
|
||
|
type template struct {
|
||
|
Counters [][]string
|
||
|
Measurement string
|
||
|
WarnOnMissing bool
|
||
|
FailOnMissing bool
|
||
|
}
|
||
|
|
||
|
type task struct {
|
||
|
measurement string
|
||
|
fields map[string]*counter
|
||
|
}
|
||
|
|
||
|
type counter struct {
|
||
|
query string
|
||
|
handle lxn.PDH_HQUERY
|
||
|
counterHandle lxn.PDH_HCOUNTER
|
||
|
current map[string]float32
|
||
|
}
|
||
|
|
||
|
// Globals
|
||
|
var (
|
||
|
gConfigParsed bool
|
||
|
|
||
|
// Parsed configuration ends up here after it has been validated
|
||
|
gTaskList []*task
|
||
|
|
||
|
// Counter cache to avoid gathering the same counter more than once per Gather
|
||
|
gCounterCache = make(map[string]*counter)
|
||
|
|
||
|
// Various error messages
|
||
|
errBadConfig error = errors.New("inputs.wpc.series contains invalid configuration")
|
||
|
errObjectNotExist error = errors.New("Performance object does not exist")
|
||
|
errCounterNotExist error = errors.New("Counter in Performance object does not exist")
|
||
|
errInstanceNotExist error = errors.New("Instance in Performance object does not exist")
|
||
|
errBadQuery error = errors.New("Invalid query for Performance Counters")
|
||
|
|
||
|
// Used to cleanup instance names
|
||
|
sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec", " ", "_", "%", "Percent", `\`, "", ",", "_")
|
||
|
)
|
||
|
|
||
|
func (m *WindowsPerformanceCounter) Description() string {
|
||
|
return "Input plugin to query Performance Counters on Windows operating systems"
|
||
|
}
|
||
|
|
||
|
func (m *WindowsPerformanceCounter) SampleConfig() string {
|
||
|
return sampleConfig
|
||
|
}
|
||
|
|
||
|
func (m *WindowsPerformanceCounter) Gather(acc telegraf.Accumulator) error {
|
||
|
// We only need to parse the config during the init, it uses the global variable after.
|
||
|
if gConfigParsed == false {
|
||
|
err := m.parseConfig()
|
||
|
gConfigParsed = true
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sample counters
|
||
|
for _, c := range gCounterCache {
|
||
|
if ok := c.queryPerformanceCounter(); !ok {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type grouping struct {
|
||
|
fields map[string]interface{}
|
||
|
tags map[string]string
|
||
|
}
|
||
|
|
||
|
for _, t := range gTaskList {
|
||
|
groups := make(map[string]*grouping)
|
||
|
|
||
|
// Regroup samples by (measurement, instance) to minimize points generated.
|
||
|
for field, c := range t.fields {
|
||
|
for instance, f32 := range c.current {
|
||
|
g, ok := groups[instance]
|
||
|
if !ok {
|
||
|
g = &grouping{
|
||
|
tags: make(map[string]string),
|
||
|
fields: make(map[string]interface{})}
|
||
|
g.tags["instance"] = sanitizedChars.Replace(instance)
|
||
|
groups[instance] = g
|
||
|
}
|
||
|
|
||
|
g.fields[field] = f32
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, g := range groups {
|
||
|
acc.AddFields(t.measurement, g.fields, g.tags)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Samples (instance, value) tuples from the performance counter
|
||
|
func (c *counter) queryPerformanceCounter() (ok bool) {
|
||
|
if ret := lxn.PdhCollectQueryData(c.handle); ret != lxn.ERROR_SUCCESS {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
var bufSize uint32
|
||
|
var bufCount uint32
|
||
|
var size uint32 = uint32(unsafe.Sizeof(lxn.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{}))
|
||
|
var emptyBuf [1]lxn.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr.
|
||
|
|
||
|
// uses null ptr here according to MSDN.
|
||
|
if ret := lxn.PdhGetFormattedCounterArrayDouble(c.counterHandle, &bufSize, &bufCount, &emptyBuf[0]); ret != lxn.PDH_MORE_DATA || bufCount == 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
coll := make(map[string]float32)
|
||
|
data := make([]lxn.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size)
|
||
|
|
||
|
_ = lxn.PdhGetFormattedCounterArrayDouble(c.counterHandle, &bufSize, &bufCount, &data[0])
|
||
|
for i := 0; i < int(bufCount); i++ {
|
||
|
res := data[i]
|
||
|
instance := lxn.UTF16PtrToString(res.SzName)
|
||
|
value := float32(res.FmtValue.DoubleValue)
|
||
|
coll[instance] = value
|
||
|
}
|
||
|
|
||
|
c.current = coll
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (m *WindowsPerformanceCounter) validatePerformanceCounterPath(query string, onMissingWarn, onMissingFail bool) (ok bool, err error) {
|
||
|
const (
|
||
|
lxn_PDH_CSTATUS_NO_OBJECT uint32 = 3221228472
|
||
|
lxn_PDH_CSTATUS_NO_COUNTER uint32 = 3221228473
|
||
|
lxn_PDH_CSTATUS_NO_INSTANCE uint32 = 2147485649
|
||
|
)
|
||
|
|
||
|
exists := lxn.PdhValidatePath(query)
|
||
|
if exists == lxn.ERROR_SUCCESS {
|
||
|
if m.PrintValid {
|
||
|
fmt.Printf("Valid: %s\n", query)
|
||
|
}
|
||
|
|
||
|
return true, nil
|
||
|
} else if !onMissingWarn && !onMissingFail {
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
switch exists {
|
||
|
case lxn_PDH_CSTATUS_NO_OBJECT:
|
||
|
if onMissingFail {
|
||
|
return false, errObjectNotExist
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Performance Object does not exist in query: %s\n", query)
|
||
|
break
|
||
|
|
||
|
case lxn_PDH_CSTATUS_NO_COUNTER:
|
||
|
if onMissingFail {
|
||
|
return false, errCounterNotExist
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Counter does not exist in query: %s\n", query)
|
||
|
break
|
||
|
|
||
|
case lxn_PDH_CSTATUS_NO_INSTANCE:
|
||
|
if onMissingFail {
|
||
|
return false, errInstanceNotExist
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Instance does not exist in query: %s\n", query)
|
||
|
break
|
||
|
|
||
|
default:
|
||
|
fmt.Printf("Invalid result: %v, query: %s\n", exists, query)
|
||
|
if onMissingFail {
|
||
|
return false, errBadQuery
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
func (m *WindowsPerformanceCounter) openPerformanceCounter(query string) *counter {
|
||
|
var handle lxn.PDH_HQUERY
|
||
|
var counterHandle lxn.PDH_HCOUNTER
|
||
|
|
||
|
ret := lxn.PdhOpenQuery(0, 0, &handle)
|
||
|
if m.PreVistaSupport {
|
||
|
ret = lxn.PdhAddCounter(handle, query, 0, &counterHandle)
|
||
|
} else {
|
||
|
ret = lxn.PdhAddEnglishCounter(handle, query, 0, &counterHandle)
|
||
|
}
|
||
|
_ = ret
|
||
|
|
||
|
return &counter{query, handle, counterHandle, nil}
|
||
|
}
|
||
|
|
||
|
// Populates the global counter cache and task list.
|
||
|
func (m *WindowsPerformanceCounter) parseConfig() error {
|
||
|
if len(m.Template) == 0 {
|
||
|
err := errors.New("Nothing to do!")
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
for _, tmpl := range m.Template {
|
||
|
t := &task{
|
||
|
measurement: tmpl.Measurement,
|
||
|
fields: make(map[string]*counter),
|
||
|
}
|
||
|
|
||
|
for _, pair := range tmpl.Counters {
|
||
|
if len(pair) != 2 {
|
||
|
return errBadConfig
|
||
|
}
|
||
|
|
||
|
field := pair[0]
|
||
|
query := pair[1]
|
||
|
|
||
|
if _, ok := gCounterCache[query]; !ok {
|
||
|
if ok, err := m.validatePerformanceCounterPath(query, tmpl.WarnOnMissing, tmpl.FailOnMissing); !ok && err != nil {
|
||
|
return err
|
||
|
} else if !ok {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
gCounterCache[query] = m.openPerformanceCounter(query)
|
||
|
}
|
||
|
|
||
|
t.fields[field] = gCounterCache[query]
|
||
|
}
|
||
|
|
||
|
gTaskList = append(gTaskList, t)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// func (m *WindowsPerformanceCounter) cleanup(metrics *itemList) {
|
||
|
// // Cleanup
|
||
|
|
||
|
// for _, metric := range metrics.items {
|
||
|
// ret := lxn.PdhCloseQuery(metric.handle)
|
||
|
// _ = ret
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
func init() {
|
||
|
inputs.Add("wpc", func() telegraf.Input { return &WindowsPerformanceCounter{} })
|
||
|
}
|