Skip to content

Commit

Permalink
Add ability to configure and scrape counters with instances (#1229)
Browse files Browse the repository at this point in the history
  • Loading branch information
james-bebbington authored Nov 9, 2020
1 parent da4f325 commit 76d1c75
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 39 deletions.
51 changes: 40 additions & 11 deletions receiver/windowsperfcountersreceiver/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# Windows Performance Counters Receiver

#### :warning: This receiver is still under construction. It currently only supports very basic functionality, i.e. performance counters with no 'Instance'.
#### :warning: This receiver is still under construction.

This receiver, for Windows only, captures the configured system, application, or
custom performance counter data from the Windows registry using the [PDH
interface](https://docs.microsoft.com/en-us/windows/win32/perfctrs/using-the-pdh-functions-to-consume-counter-data).
It is based on the [Telegraf Windows Performance Counters Input
Plugin](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/win_perf_counters).

Metrics will be generated with names and labels that match the performance
counter path, i.e.

- `Memory\Committed Bytes`
- `Processor\% Processor Time`, with a datapoint for each `Instance` label = (`_Total`, `1`, `2`, `3`, ... )

## Configuration

Expand All @@ -16,10 +24,23 @@ windowsperfcounters:
collection_interval: <duration> # default = "1m"
counters:
- object: <object name>
instances: [<instance name>]*
counters:
- <counter name>
```
*Note `instances` can have several special values depending on the type of
counter:

Value | Interpretation
-- | --
`""` (or not specified) | This is the only valid value if the counter has no instances
`"*"` | All instances
`"_Total"` | The "total" instance
`"instance1"` | A single instance
`["instance1", "instance2", ...]` | A set of instances
`["_Total", "instance1", "instance2", ...]` | A set of instances including the "total" instance

### Scraping at different frequencies

If you would like to scrape some counters at a different frequency than others,
Expand All @@ -31,27 +52,36 @@ receivers:
windowsperfcounters/memory:
collection_interval: 30s
counters:
- object: Memory
counters:
- Committed Bytes
- object: Memory
counters:
- Committed Bytes
windowsperfcounters/connections:
windowsperfcounters/processor:
collection_interval: 1m
counters:
- object: TCPv4
counters:
- Connections Established
- object: "Processor"
instances: "*"
counters:
- "% Processor Time"
- object: "Processor"
instances: [1, 2]
counters:
- "% Idle Time"
service:
pipelines:
metrics:
receivers: [windowsperfcounters/memory, windowsperfcounters/connections]
receivers: [windowsperfcounters/memory, windowsperfcounters/processor]
```

### Changing metric format

To report metrics in the desired output format, it's recommended you use this
receiver with the metrics transform processor, e.g.:
receiver with the [metrics transform
processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/master/processor/metricstransformprocessor).

e.g. To output the `Memory/Committed Bytes` counter as a metric with the name
`system.memory.usage`:

```yaml
receivers:
Expand All @@ -65,7 +95,6 @@ receivers:
processors:
metricstransformprocessor:
transforms:
# rename "Memory/Committed Bytes" -> system.memory.usage
- metric_name: "Memory/Committed Bytes"
action: update
new_name: system.memory.usage
Expand Down
17 changes: 14 additions & 3 deletions receiver/windowsperfcountersreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,24 @@ import (
"go.opentelemetry.io/collector/receiver/receiverhelper"
)

// Config defines configuration for HostMetrics receiver.
// Config defines configuration for WindowsPerfCounters receiver.
type Config struct {
configmodels.ReceiverSettings `mapstructure:",squash"`
receiverhelper.ScraperControllerSettings `mapstructure:",squash"`

PerfCounters []PerfCounterConfig `mapstructure:"perfcounters"`
}

// PerfCounterConfig defines configuration for a perf counter object.
type PerfCounterConfig struct {
Object string `mapstructure:"object"`
Counters []string `mapstructure:"counters"`
Object string `mapstructure:"object"`
Instances []string `mapstructure:"instances"`
Counters []string `mapstructure:"counters"`
}

func (c *Config) validate() error {
// TODO: consider validating duplicate configuration of counters

var errors []error

if c.CollectionInterval <= 0 {
Expand All @@ -53,6 +57,13 @@ func (c *Config) validate() error {
continue
}

for _, instance := range pc.Instances {
if instance == "" {
errors = append(errors, fmt.Errorf("perf counter for object %q includes an empty instance", pc.Object))
break
}
}

if len(pc.Counters) == 0 {
errors = append(errors, fmt.Errorf("perf counter for object %q does not specify any counters", pc.Object))
}
Expand Down
19 changes: 16 additions & 3 deletions receiver/windowsperfcountersreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func TestLoadConfig_Error(t *testing.T) {
noPerfCountersErr = "must specify at least one perf counter"
noObjectNameErr = "must specify object name for all perf counters"
noCountersErr = `perf counter for object "%s" does not specify any counters`
emptyInstanceErr = `perf counter for object "%s" includes an empty instance`
)

testCases := []testCase{
Expand All @@ -108,9 +109,21 @@ func TestLoadConfig_Error(t *testing.T) {
expectedErr: fmt.Sprintf("%s: %s", errorPrefix, fmt.Sprintf(noCountersErr, "object")),
},
{
name: "AllErrors",
cfgFile: "config-allerrors.yaml",
expectedErr: fmt.Sprintf("%s: [%s; %s; %s]", errorPrefix, negativeCollectionIntervalErr, fmt.Sprintf(noCountersErr, "object"), noObjectNameErr),
name: "EmptyInstance",
cfgFile: "config-emptyinstance.yaml",
expectedErr: fmt.Sprintf("%s: %s", errorPrefix, fmt.Sprintf(emptyInstanceErr, "object")),
},
{
name: "AllErrors",
cfgFile: "config-allerrors.yaml",
expectedErr: fmt.Sprintf(
"%s: [%s; %s; %s; %s]",
errorPrefix,
negativeCollectionIntervalErr,
fmt.Sprintf(emptyInstanceErr, "object"),
fmt.Sprintf(noCountersErr, "object"),
noObjectNameErr,
),
},
}

Expand Down
31 changes: 31 additions & 0 deletions receiver/windowsperfcountersreceiver/config_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build windows

package windowsperfcountersreceiver

func (pc *PerfCounterConfig) instances() []string {
if len(pc.Instances) == 0 {
return []string{""}
}

for _, instance := range pc.Instances {
if instance == "*" {
return []string{"*"}
}
}

return pc.Instances
}
8 changes: 8 additions & 0 deletions receiver/windowsperfcountersreceiver/example_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ receivers:
- object: "Memory"
counters:
- "Committed Bytes"
- object: "Processor"
instances: "*"
counters:
- "% Processor Time"
- object: "Processor"
instances: [1, 2]
counters:
- "% Idle Time"

exporters:
logging:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ receivers:
collection_interval: -1m
perfcounters:
-
- Object: "object"
- object: "object"
instances: [ "instance", "", "*" ]

processors:
exampleprocessor:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
receivers:
windowsperfcounters:
perfcounters:
- object: "object"
instances: [""]
counters:
- "counter"

processors:
exampleprocessor:

exporters:
exampleexporter:

service:
pipelines:
metrics:
receivers: [windowsperfcounters]
processors: [exampleprocessor]
exporters: [exampleexporter]
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,31 @@ func (s *scraper) initialize(ctx context.Context) error {
var errors []error

for _, perfCounterCfg := range s.cfg.PerfCounters {
for _, counterName := range perfCounterCfg.Counters {
c, err := pdh.NewPerfCounter(fmt.Sprintf("\\%s\\%s", perfCounterCfg.Object, counterName), true)
if err != nil {
errors = append(errors, err)
} else {
s.counters = append(s.counters, c)
for _, instance := range perfCounterCfg.instances() {
for _, counterName := range perfCounterCfg.Counters {
counterPath := counterPath(perfCounterCfg.Object, instance, counterName)

c, err := pdh.NewPerfCounter(counterPath, true)
if err != nil {
errors = append(errors, fmt.Errorf("error initializing counter %v: %w", counterPath, err))
} else {
s.counters = append(s.counters, c)
}
}
}
}

return componenterror.CombineErrors(errors)
}

func counterPath(object, instance, counterName string) string {
if instance != "" {
instance = fmt.Sprintf("(%s)", instance)
}

return fmt.Sprintf("\\%s%s\\%s", object, instance, counterName)
}

func (s *scraper) close(ctx context.Context) error {
var errors []error

Expand Down
Loading

0 comments on commit 76d1c75

Please sign in to comment.