Skip to content

Commit

Permalink
Add perfmon option to ignore non-existent counters (#6432)
Browse files Browse the repository at this point in the history
* Add option to ignore non existent counters

* Add changelog.

* FIx houndci

* Fix new config param

* Fix comment

* Remove unnecessary time.Sleep

* Remove reference to PerfmonConfig
  • Loading branch information
martinscholz83 authored and andrewkroh committed Mar 9, 2018
1 parent cefe024 commit 75228cc
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ https://github.com/elastic/beats/compare/v6.0.0-beta2...master[Check the HEAD di
- Fix the default configuration for Logstash to include the default port. {pull}6279[6279]
- Add filtering option by exact device names in system.diskio. `diskio.include_devices`. {pull}6085[6085]
- Fix dealing with new process status codes in Linux kernel 4.14+. {pull}6306[6306]
- Add config option for windows/perfmon metricset to ignore non existent counters. {pull}6432[6432]

*Packetbeat*

Expand Down
6 changes: 4 additions & 2 deletions metricbeat/module/windows/perfmon/_meta/docs.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ performance counters.

You must configure queries for the Windows performance counters that you wish
to collect. The example below collects processor time and disk writes.
With `format` you can set the output format for a specific counter. Possible values are
`float` and `long`. If nothing is selected the default value is `float`.
`ignore_non_existent_counters` ignores failures for non-existent counters without
to interrupt the service. With `format` you can set the output format for a specific counter.
Possible values are `float` and `long`. If nothing is selected the default value is `float`.
With `instance_name`, you can specify the name of the instance. Use this setting when:
- You want to use an instance name that is different from the computed name. For example, `Total` instead of `_Total`.
- You specify a counter that has no instance. For example, `\TCPIP Performance Diagnostics\IPv4 NBLs/sec indicated without prevalidation`.
Expand All @@ -19,6 +20,7 @@ For wildcard queries this setting has no effect.
- module: windows
metricsets: ["perfmon"]
period: 10s
perfmon.ignore_non_existent_counters: true
perfmon.counters:
- instance_label: "processor.name"
instance_name: "Total"
Expand Down
73 changes: 53 additions & 20 deletions metricbeat/module/windows/perfmon/pdh_integration_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,13 @@ func TestQuery(t *testing.T) {
}

func TestExistingCounter(t *testing.T) {
config := make([]CounterConfig, 1)
config[0].InstanceLabel = "processor.name"
config[0].MeasurementLabel = "processor.time.total.pct"
config[0].Query = processorTimeCounter
config[0].Format = "float"
config := Config{
CounterConfig: make([]CounterConfig, 1),
}
config.CounterConfig[0].InstanceLabel = "processor.name"
config.CounterConfig[0].MeasurementLabel = "processor.time.total.pct"
config.CounterConfig[0].Query = processorTimeCounter
config.CounterConfig[0].Format = "float"
handle, err := NewPerfmonReader(config)
if err != nil {
t.Fatal(err)
Expand All @@ -105,11 +107,13 @@ func TestExistingCounter(t *testing.T) {
}

func TestNonExistingCounter(t *testing.T) {
config := make([]CounterConfig, 1)
config[0].InstanceLabel = "processor.name"
config[0].MeasurementLabel = "processor.time.total.pct"
config[0].Query = "\\Processor Information(_Total)\\not existing counter"
config[0].Format = "float"
config := Config{
CounterConfig: make([]CounterConfig, 1),
}
config.CounterConfig[0].InstanceLabel = "processor.name"
config.CounterConfig[0].MeasurementLabel = "processor.time.total.pct"
config.CounterConfig[0].Query = "\\Processor Information(_Total)\\not existing counter"
config.CounterConfig[0].Format = "float"
handle, err := NewPerfmonReader(config)
if assert.Error(t, err) {
assert.EqualValues(t, PDH_CSTATUS_NO_COUNTER, errors.Cause(err))
Expand All @@ -121,12 +125,39 @@ func TestNonExistingCounter(t *testing.T) {
}
}

func TestIgnoreNonExistentCounter(t *testing.T) {
config := Config{
CounterConfig: make([]CounterConfig, 1),
IgnoreNECounters: true,
}
config.CounterConfig[0].InstanceLabel = "processor.name"
config.CounterConfig[0].MeasurementLabel = "processor.time.total.pct"
config.CounterConfig[0].Query = "\\Processor Information(_Total)\\not existing counter"
config.CounterConfig[0].Format = "float"
handle, err := NewPerfmonReader(config)

values, err := handle.Read()

if assert.Error(t, err) {
assert.EqualValues(t, PDH_NO_DATA, errors.Cause(err))
}

if handle != nil {
err = handle.query.Close()
assert.NoError(t, err)
}

t.Log(values)
}

func TestNonExistingObject(t *testing.T) {
config := make([]CounterConfig, 1)
config[0].InstanceLabel = "processor.name"
config[0].MeasurementLabel = "processor.time.total.pct"
config[0].Query = "\\non existing object\\% Processor Performance"
config[0].Format = "float"
config := Config{
CounterConfig: make([]CounterConfig, 1),
}
config.CounterConfig[0].InstanceLabel = "processor.name"
config.CounterConfig[0].MeasurementLabel = "processor.time.total.pct"
config.CounterConfig[0].Query = "\\non existing object\\% Processor Performance"
config.CounterConfig[0].Format = "float"
handle, err := NewPerfmonReader(config)
if assert.Error(t, err) {
assert.EqualValues(t, PDH_CSTATUS_NO_OBJECT, errors.Cause(err))
Expand Down Expand Up @@ -253,11 +284,13 @@ func TestRawValues(t *testing.T) {
}

func TestWildcardQuery(t *testing.T) {
config := make([]CounterConfig, 1)
config[0].InstanceLabel = "processor.name"
config[0].MeasurementLabel = "processor.time.pct"
config[0].Query = `\Processor Information(*)\% Processor Time`
config[0].Format = "float"
config := Config{
CounterConfig: make([]CounterConfig, 1),
}
config.CounterConfig[0].InstanceLabel = "processor.name"
config.CounterConfig[0].MeasurementLabel = "processor.time.pct"
config.CounterConfig[0].Query = `\Processor Information(*)\% Processor Time`
config.CounterConfig[0].Format = "float"
handle, err := NewPerfmonReader(config)
if err != nil {
t.Fatal(err)
Expand Down
17 changes: 13 additions & 4 deletions metricbeat/module/windows/perfmon/pdh_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"unicode/utf16"
"unsafe"

"github.com/elastic/beats/libbeat/logp"

"github.com/joeshaw/multierror"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
Expand Down Expand Up @@ -222,7 +224,7 @@ func (q *Query) AddCounter(counterPath string, format Format, instanceName strin

h, err := PdhAddCounter(q.handle, counterPath, 0)
if err != nil {
return errors.Wrapf(err, `failed to add counter (path="%v")`, counterPath)
return err
}

wildcard := wildcardRegexp.MatchString(counterPath)
Expand Down Expand Up @@ -314,7 +316,8 @@ type PerfmonReader struct {
executed bool // Indicates if the query has been executed.
}

func NewPerfmonReader(config []CounterConfig) (*PerfmonReader, error) {
// NewPerfmonReader creates a new instance of PerfmonReader.
func NewPerfmonReader(config Config) (*PerfmonReader, error) {
query, err := NewQuery("")
if err != nil {
return nil, err
Expand All @@ -326,7 +329,7 @@ func NewPerfmonReader(config []CounterConfig) (*PerfmonReader, error) {
measurement: map[string]string{},
}

for _, counter := range config {
for _, counter := range config.CounterConfig {
var format Format
switch counter.Format {
case "float":
Expand All @@ -335,8 +338,14 @@ func NewPerfmonReader(config []CounterConfig) (*PerfmonReader, error) {
format = LongFormat
}
if err := query.AddCounter(counter.Query, format, counter.InstanceName); err != nil {
if config.IgnoreNECounters {
if err == PDH_CSTATUS_NO_COUNTER {
logp.Info(`ignore non existent counter (path="%v")`, counter.Query)
continue
}
}
query.Close()
return nil, err
return nil, errors.Wrapf(err, `failed to add counter (path="%v")`, counter.Query)
}

r.instanceLabel[counter.Query] = counter.InstanceLabel
Expand Down
13 changes: 9 additions & 4 deletions metricbeat/module/windows/perfmon/perfmon.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/elastic/beats/metricbeat/mb"
)

// CounterConfig for perfmon counters.
type CounterConfig struct {
InstanceLabel string `config:"instance_label" validate:"required"`
InstanceName string `config:"instance_name"`
Expand All @@ -21,6 +22,12 @@ type CounterConfig struct {
Format string `config:"format"`
}

// Config for the windows perfmon metricset.
type Config struct {
IgnoreNECounters bool `config:"perfmon.ignore_non_existent_counters"`
CounterConfig []CounterConfig `config:"perfmon.counters" validate:"required"`
}

func init() {
if err := mb.Registry.AddMetricSet("windows", "perfmon", New); err != nil {
panic(err)
Expand All @@ -36,9 +43,7 @@ type MetricSet struct {
func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
cfgwarn.Beta("The perfmon metricset is beta")

config := struct {
CounterConfig []CounterConfig `config:"perfmon.counters" validate:"required"`
}{}
config := Config{}

if err := base.Module().UnpackConfig(&config); err != nil {
return nil, err
Expand All @@ -57,7 +62,7 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) {

}

reader, err := NewPerfmonReader(config.CounterConfig)
reader, err := NewPerfmonReader(config)
if err != nil {
return nil, errors.Wrap(err, "initialization failed")
}
Expand Down

0 comments on commit 75228cc

Please sign in to comment.