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

Perfmon ignore non existent counters #6432

Merged
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,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.

Choose a reason for hiding this comment

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

comment on exported type PerfmonConfig should be of the form "PerfmonConfig ..." (with optional leading article)

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