telegraf/plugins/inputs/win_perf_counters/win_perf_counters.go

473 lines
14 KiB
Go

// +build windows
package win_perf_counters
import (
"errors"
"fmt"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
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
## being polled for data does not have the Object at startup of the Telegraf
## agent, it will not be gathered.
## Settings:
# PrintValid = false # Print All matching performance counters
# Whether request a timestamp along with the PerfCounter data or just use current time
# UsePerfCounterTime=true
# If UseWildcardsExpansion params is set to true, wildcards (partial wildcards in instance names and wildcards in counters names) in configured counter paths will be expanded
# and in case of localized Windows, counter paths will be also localized. It also returns instance indexes in instance names.
# If false, wildcards (not partial) in instance names will still be expanded, but instance indexes will not be returned in instance names.
#UseWildcardsExpansion = false
# 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.
ObjectName = "Processor"
Instances = ["*"]
Counters = [
"% Idle Time",
"% Interrupt Time",
"% Privileged Time",
"% User Time",
"% Processor Time",
"% DPC Time",
]
Measurement = "win_cpu"
# Set to true to include _Total instance when querying for all (*).
# IncludeTotal=false
# Print out when the performance counter is missing from object, counter or instance.
# WarnOnMissing = false
[[inputs.win_perf_counters.object]]
# Disk times and queues
ObjectName = "LogicalDisk"
Instances = ["*"]
Counters = [
"% Idle Time",
"% Disk Time",
"% Disk Read Time",
"% Disk Write Time",
"% User Time",
"% Free Space",
"Current Disk Queue Length",
"Free Megabytes",
]
Measurement = "win_disk"
[[inputs.win_perf_counters.object]]
ObjectName = "PhysicalDisk"
Instances = ["*"]
Counters = [
"Disk Read Bytes/sec",
"Disk Write Bytes/sec",
"Current Disk Queue Length",
"Disk Reads/sec",
"Disk Writes/sec",
"% Disk Time",
"% Disk Read Time",
"% Disk Write Time",
]
Measurement = "win_diskio"
[[inputs.win_perf_counters.object]]
ObjectName = "Network Interface"
Instances = ["*"]
Counters = [
"Bytes Received/sec",
"Bytes Sent/sec",
"Packets Received/sec",
"Packets Sent/sec",
"Packets Received Discarded",
"Packets Outbound Discarded",
"Packets Received Errors",
"Packets Outbound Errors",
]
Measurement = "win_net"
[[inputs.win_perf_counters.object]]
ObjectName = "System"
Counters = [
"Context Switches/sec",
"System Calls/sec",
"Processor Queue Length",
"System Up Time",
]
Instances = ["------"]
Measurement = "win_system"
[[inputs.win_perf_counters.object]]
# Example counterPath where the Instance portion must be removed to get data back,
# such as from the Memory object.
ObjectName = "Memory"
Counters = [
"Available Bytes",
"Cache Faults/sec",
"Demand Zero Faults/sec",
"Page Faults/sec",
"Pages/sec",
"Transition Faults/sec",
"Pool Nonpaged Bytes",
"Pool Paged Bytes",
"Standby Cache Reserve Bytes",
"Standby Cache Normal Priority Bytes",
"Standby Cache Core Bytes",
]
Instances = ["------"] # Use 6 x - to remove the Instance bit from the counterPath.
Measurement = "win_mem"
[[inputs.win_perf_counters.object]]
# Example query where the Instance portion must be removed to get data back,
# such as from the Paging File object.
ObjectName = "Paging File"
Counters = [
"% Usage",
]
Instances = ["_Total"]
Measurement = "win_swap"
`
type Win_PerfCounters struct {
PrintValid bool
//deprecated: determined dynamically
PreVistaSupport bool
UsePerfCounterTime bool
Object []perfobject
CountersRefreshInterval internal.Duration
UseWildcardsExpansion bool
Log telegraf.Logger
lastRefreshed time.Time
counters []*counter
query PerformanceQuery
}
type perfobject struct {
ObjectName string
Counters []string
Instances []string
Measurement string
WarnOnMissing bool
FailOnMissing bool
IncludeTotal bool
}
type counter struct {
counterPath string
objectName string
counter string
instance string
measurement string
includeTotal bool
counterHandle PDH_HCOUNTER
}
type instanceGrouping struct {
name string
instance string
objectname string
}
var sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec",
" ", "_", "%", "Percent", `\`, "")
// extractCounterInfoFromCounterPath gets object name, instance name (if available) and counter name from counter path
// 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
func extractCounterInfoFromCounterPath(counterPath string) (object string, instance string, counter string, err error) {
rightObjectBorderIndex := -1
leftObjectBorderIndex := -1
leftCounterBorderIndex := -1
rightInstanceBorderIndex := -1
leftInstanceBorderIndex := -1
bracketLevel := 0
for i := len(counterPath) - 1; i >= 0; i-- {
switch counterPath[i] {
case '\\':
if bracketLevel == 0 {
if leftCounterBorderIndex == -1 {
leftCounterBorderIndex = i
} else if leftObjectBorderIndex == -1 {
leftObjectBorderIndex = i
}
}
case '(':
bracketLevel--
if leftInstanceBorderIndex == -1 && bracketLevel == 0 && leftObjectBorderIndex == -1 && leftCounterBorderIndex > -1 {
leftInstanceBorderIndex = i
rightObjectBorderIndex = i
}
case ')':
if rightInstanceBorderIndex == -1 && bracketLevel == 0 && leftCounterBorderIndex > -1 {
rightInstanceBorderIndex = i
}
bracketLevel++
}
}
if rightObjectBorderIndex == -1 {
rightObjectBorderIndex = leftCounterBorderIndex
}
if rightObjectBorderIndex == -1 || leftObjectBorderIndex == -1 {
err = errors.New("cannot parse object from: " + counterPath)
return
}
if leftInstanceBorderIndex > -1 && rightInstanceBorderIndex > -1 {
instance = counterPath[leftInstanceBorderIndex+1 : rightInstanceBorderIndex]
} else if (leftInstanceBorderIndex == -1 && rightInstanceBorderIndex > -1) || (leftInstanceBorderIndex > -1 && rightInstanceBorderIndex == -1) {
err = errors.New("cannot parse instance from: " + counterPath)
return
}
object = counterPath[leftObjectBorderIndex+1 : rightObjectBorderIndex]
counter = counterPath[leftCounterBorderIndex+1:]
return
}
func (m *Win_PerfCounters) Description() string {
return "Input plugin to counterPath Performance Counters on Windows operating systems"
}
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 {
var err error
var counterHandle PDH_HCOUNTER
if !m.query.IsVistaOrNewer() {
counterHandle, err = m.query.AddCounterToQuery(counterPath)
if err != nil {
return err
}
} else {
counterHandle, err = m.query.AddEnglishCounterToQuery(counterPath)
if err != nil {
return err
}
}
if m.UseWildcardsExpansion {
origInstance := instance
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)
objectName, instance, counterName, err = extractCounterInfoFromCounterPath(counterPath)
if err != nil {
return err
}
if instance == "_Total" && origInstance == "*" && !includeTotal {
continue
}
newItem := &counter{counterPath, objectName, counterName, instance, measurement,
includeTotal, counterHandle}
m.counters = append(m.counters, newItem)
if m.PrintValid {
m.Log.Infof("Valid: %s", counterPath)
}
}
} else {
newItem := &counter{counterPath, objectName, counterName, instance, measurement,
includeTotal, counterHandle}
m.counters = append(m.counters, newItem)
if m.PrintValid {
m.Log.Infof("Valid: %s", counterPath)
}
}
return nil
}
func (m *Win_PerfCounters) ParseConfig() error {
var counterPath string
if len(m.Object) > 0 {
for _, PerfObject := range m.Object {
for _, counter := range PerfObject.Counters {
for _, instance := range PerfObject.Instances {
objectname := PerfObject.ObjectName
if instance == "------" {
counterPath = "\\" + objectname + "\\" + counter
} else {
counterPath = "\\" + objectname + "(" + instance + ")\\" + counter
}
err := m.AddItem(counterPath, objectname, instance, counter, PerfObject.Measurement, PerfObject.IncludeTotal)
if err != nil {
if PerfObject.FailOnMissing || PerfObject.WarnOnMissing {
m.Log.Errorf("Invalid counterPath: '%s'. Error: %s\n", counterPath, err.Error())
}
if PerfObject.FailOnMissing {
return err
}
}
}
}
}
return nil
} else {
err := errors.New("no performance objects configured")
return err
}
}
func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {
// Parse the config once
var err error
if m.lastRefreshed.IsZero() || (m.CountersRefreshInterval.Duration.Nanoseconds() > 0 && m.lastRefreshed.Add(m.CountersRefreshInterval.Duration).Before(time.Now())) {
if m.counters != nil {
m.counters = m.counters[:0]
}
if err = m.query.Open(); err != nil {
return err
}
if err = m.ParseConfig(); err != nil {
return err
}
//some counters need two data samples before computing a value
if err = m.query.CollectData(); err != nil {
return err
}
m.lastRefreshed = time.Now()
time.Sleep(time.Second)
}
var collectFields = make(map[instanceGrouping]map[string]interface{})
var timestamp time.Time
if m.UsePerfCounterTime && m.query.IsVistaOrNewer() {
timestamp, err = m.query.CollectDataWithTime()
if err != nil {
return err
}
} else {
timestamp = time.Now()
if err = m.query.CollectData(); err != nil {
return err
}
}
// 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 err == nil {
addCounterMeasurement(metric, metric.instance, value, collectFields)
} else {
//ignore invalid data as some counters from process instances returns this sometimes
if !isKnownCounterDataError(err) {
return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err)
}
}
} else {
counterValues, err := m.query.GetFormattedCounterArrayDouble(metric.counterHandle)
if err == nil {
for _, cValue := range counterValues {
var add bool
if metric.includeTotal {
// If IncludeTotal is set, include all.
add = true
} else if metric.instance == "*" && !strings.Contains(cValue.InstanceName, "_Total") {
// Catch if set to * and that it is not a '*_Total*' instance.
add = true
} else if metric.instance == cValue.InstanceName {
// Catch if we set it to total or some form of it
add = true
} else if strings.Contains(metric.instance, "#") && strings.HasPrefix(metric.instance, cValue.InstanceName) {
// 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
cValue.InstanceName = metric.instance
} else if metric.instance == "------" {
add = true
}
if add {
addCounterMeasurement(metric, cValue.InstanceName, cValue.Value, collectFields)
}
}
} else {
//ignore invalid data as some counters from process instances returns this sometimes
if !isKnownCounterDataError(err) {
return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err)
}
}
}
}
for instance, fields := range collectFields {
var tags = map[string]string{
"objectname": instance.objectname,
}
if len(instance.instance) > 0 {
tags["instance"] = instance.instance
}
acc.AddFields(instance.name, fields, tags, timestamp)
}
return nil
}
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}
if collectFields[instance] == nil {
collectFields[instance] = make(map[string]interface{})
}
collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value)
}
func isKnownCounterDataError(err error) bool {
if pdhErr, ok := err.(*PdhError); ok && (pdhErr.ErrorCode == PDH_INVALID_DATA ||
pdhErr.ErrorCode == PDH_CALC_NEGATIVE_VALUE ||
pdhErr.ErrorCode == PDH_CSTATUS_INVALID_DATA ||
pdhErr.ErrorCode == PDH_NO_DATA) {
return true
}
return false
}
func init() {
inputs.Add("win_perf_counters", func() telegraf.Input {
return &Win_PerfCounters{query: &PerformanceQueryImpl{}, CountersRefreshInterval: internal.Duration{Duration: time.Second * 60}}
})
}