Skip to content

Commit

Permalink
[metricbeat] Add a new 'match_by_parent_instance' option to 'perfmon'…
Browse files Browse the repository at this point in the history
… module (#43002)

* Add a new 'match_by_parent_instance' option to 'perfmon' module

* Add type assertion check

(cherry picked from commit 180bd96)
  • Loading branch information
marc-gr authored and mergify[bot] committed Mar 6, 2025
1 parent 072afe5 commit 07341d7
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]
- Add new metricset network for the vSphere module. {pull}40559[40559]
- Add new metricset resourcepool for the vSphere module. {pull}40456[40456]
- Log every 401 response from Kubernetes API Server {pull}42714[42714]
- Add a new `match_by_parent_instance` option to `perfmon` module. {pull}43002[43002]

*Metricbeat*

Expand Down
7 changes: 7 additions & 0 deletions metricbeat/module/windows/perfmon/_meta/docs.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ The default behaviour is for all measurements to be sent as separate events.

*`refresh_wildcard_counters`*:: A boolean option to refresh the counter list at each fetch. By default, the counter list will be retrieved at the starting time, to refresh the list at each fetch, users will have to enable this setting.

*`match_by_parent_instance`*:: A boolean option that causes all instances
of the same parent to have the same instance value. In the above example,
this will cause metrics for `svchost`, `svchost#1`, etc. to have an `instance`
value of `svchost`. If set to `false` they will keep the original values
(`svchost`, `svchost#1`, etc.).
It defaults to `true`.


[float]
==== Query Configuration
Expand Down
5 changes: 5 additions & 0 deletions metricbeat/module/windows/perfmon/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Config struct {
RefreshWildcardCounters bool `config:"perfmon.refresh_wildcard_counters"`
Queries []Query `config:"perfmon.queries"`
GroupAllCountersTo string `config:"perfmon.group_all_counter"`
MatchByParentInstance *bool `config:"perfmon.match_by_parent_instance"`
}

// QueryConfig for perfmon queries. This will be used as the new configuration format
Expand Down Expand Up @@ -77,6 +78,10 @@ func (conf *Config) Validate() error {
return nil
}

func (conf *Config) ShouldMatchByParentInstance() bool {
return conf.MatchByParentInstance == nil || *conf.MatchByParentInstance
}

func isValidFormat(format string) bool {
for _, form := range allowedFormats {
if form == format {
Expand Down
20 changes: 11 additions & 9 deletions metricbeat/module/windows/perfmon/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ func (re *Reader) groupToEvents(counters map[string][]pdh.CounterValue) []mb.Eve
eventMap[eventKey].Error = fmt.Errorf("failed on query=%v: %w", counterPath, val.Err.Error)
}
if val.Instance != "" {
// will ignore instance index
if ok, match := matchesParentProcess(val.Instance); ok {
eventMap[eventKey].MetricSetFields.Put(counter.InstanceField, match)
} else {
eventMap[eventKey].MetricSetFields.Put(counter.InstanceField, val.Instance)
instanceName := val.Instance
if re.config.ShouldMatchByParentInstance() {
if ok, match := matchesParentProcess(val.Instance); ok {
// will ignore instance index
instanceName = match
}
}
eventMap[eventKey].MetricSetFields.Put(counter.InstanceField, instanceName)
}
}

Expand Down Expand Up @@ -126,13 +128,13 @@ func (re *Reader) groupToSingleEvent(counters map[string][]pdh.CounterValue) mb.
continue
}
var counterVal float64
switch val.Measurement.(type) {
switch m := val.Measurement.(type) {
case int64:
counterVal = float64(val.Measurement.(int64))
counterVal = float64(m)
case int:
counterVal = float64(val.Measurement.(int))
counterVal = float64(m)
default:
counterVal = val.Measurement.(float64)
counterVal, _ = val.Measurement.(float64)
}
if _, ok := measurements[readerCounter.QueryField]; !ok {
measurements[readerCounter.QueryField] = counterVal
Expand Down
76 changes: 76 additions & 0 deletions metricbeat/module/windows/perfmon/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,82 @@ func TestGroupToSingleEvent(t *testing.T) {
assert.Equal(t, val, mapstr.M{"processor_count": float64(2)})
}

func TestMatchByParentInstance(t *testing.T) {
_true := true
_false := false
reader := Reader{
query: pdh.Query{},
log: nil,
config: Config{
MatchByParentInstance: &_true,
},
counters: []PerfCounter{
{
QueryField: "%_processor_time",
QueryName: `\Processor Information(*)\% Processor Time`,
Format: "float",
ObjectName: "Processor Information",
ObjectField: "object",
InstanceName: "*",
InstanceField: "instance",
ChildQueries: []string{`\Processor Information(processor)\% Processor Time`, `\Processor Information(processor#1)\% Processor Time`},
},
},
}

counters := map[string][]pdh.CounterValue{
`\Processor Information(processor)\% Processor Time`: {
{
Instance: "processor",
Measurement: 23,
},
},
`\Processor Information(processor#1)\% Processor Time`: {
{
Instance: "processor#1",
Measurement: 21,
},
},
}

{
events := reader.groupToEvents(counters)
assert.NotNil(t, events)
assert.Equal(t, 2, len(events))
ok, err := events[0].MetricSetFields.HasKey("instance")
assert.NoError(t, err)
assert.True(t, ok)
ok, err = events[1].MetricSetFields.HasKey("instance")
assert.NoError(t, err)
assert.True(t, ok)
val1, err := events[0].MetricSetFields.GetValue("instance")
assert.NoError(t, err)
assert.Equal(t, val1, "processor")
val2, err := events[1].MetricSetFields.GetValue("instance")
assert.NoError(t, err)
assert.Equal(t, val2, "processor")
}

reader.config.MatchByParentInstance = &_false
{
events := reader.groupToEvents(counters)
assert.NotNil(t, events)
assert.Equal(t, 2, len(events))
ok, err := events[0].MetricSetFields.HasKey("instance")
assert.NoError(t, err)
assert.True(t, ok)
ok, err = events[1].MetricSetFields.HasKey("instance")
assert.NoError(t, err)
assert.True(t, ok)
val1, err := events[0].MetricSetFields.GetValue("instance")
assert.NoError(t, err)
assert.Equal(t, val1, "processor")
val2, err := events[1].MetricSetFields.GetValue("instance")
assert.NoError(t, err)
assert.Equal(t, val2, "processor#1")
}
}

func TestMatchesParentProcess(t *testing.T) {
ok, val := matchesParentProcess("svchost")
assert.True(t, ok)
Expand Down

0 comments on commit 07341d7

Please sign in to comment.