Skip to content

Add container metrics receiver to collect CPU & Memory Usage #1064

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

Merged
merged 18 commits into from
Apr 29, 2025
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
2 changes: 2 additions & 0 deletions .testcoverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ exclude:
- ^test/.*$
- app.go # app.go and main.go should be tested by integration tests.
- main.go
# ignore metadata generated files
- metadata/generated_.*\.go
# ignore wrappers around gopsutil
- internal/datasource/host
- internal/watcher/process
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ require (
go.opentelemetry.io/collector/receiver/receivertest v0.124.0
go.opentelemetry.io/collector/scraper v0.124.0
go.opentelemetry.io/collector/scraper/scraperhelper v0.124.0
go.opentelemetry.io/collector/scraper/scrapertest v0.124.0
go.opentelemetry.io/otel v1.35.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.0
Expand Down
18 changes: 18 additions & 0 deletions internal/collector/containermetricsreceiver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Container Metrics Receiver

The Container Metrics receiver generates metrics about the container scraped from the cgroup files.

## Configuration

### Receiver Config

The following settings are optional:
- `collection_interval` (default = `10s`): This receiver collects metrics on an interval. This value must be a string readable by Golang's [time.ParseDuration](https://pkg.go.dev/time#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`.
- `initial_delay` (default = `1s`): defines how long this receiver waits before starting.

Example:
```yaml
containermetrics:
collection_interval: <duration> # default = 1m
initial_delay: <duration> # default = 1s
```
8 changes: 8 additions & 0 deletions internal/collector/containermetricsreceiver/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

//go:generate mdatagen metadata.yaml

package containermetricsreceiver
33 changes: 33 additions & 0 deletions internal/collector/containermetricsreceiver/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)

# containermetrics

## Default Metrics

The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:

```yaml
metrics:
<metric_name>:
enabled: false
```

### system.memory.usage

Bytes of memory in use.

| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic |
| ---- | ----------- | ---------- | ----------------------- | --------- |
| By | Sum | Int | Cumulative | false |

#### Attributes

| Name | Description | Values |
| ---- | ----------- | ------ |
| state | Breakdown of memory usage by type. | Str: ``buffered``, ``cached``, ``inactive``, ``free``, ``slab_reclaimable``, ``slab_unreclaimable``, ``used`` |

## Resource Attributes

| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| resource.id | The resource id. | Any Str | false |
55 changes: 55 additions & 0 deletions internal/collector/containermetricsreceiver/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

package containermetricsreceiver

import (
"context"
"errors"

"github.com/nginx/agent/v3/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper"

"github.com/nginx/agent/v3/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper/scraperhelper"

"github.com/nginx/agent/v3/internal/collector/containermetricsreceiver/internal/config"
"github.com/nginx/agent/v3/internal/collector/containermetricsreceiver/internal/metadata"
)

// nolint: ireturn
func NewFactory() receiver.Factory {
return receiver.NewFactory(
metadata.Type,
config.CreateDefaultConfig,
receiver.WithMetrics(
createMetricsReceiver,
metadata.MetricsStability,
),
)
}

// nolint: ireturn
func createMetricsReceiver(
_ context.Context,
params receiver.Settings,
rConf component.Config,
cons consumer.Metrics,
) (receiver.Metrics, error) {
cfg, ok := rConf.(*config.Config)
if !ok {
return nil, errors.New("cast to metrics receiver config failed")
}

return scraperhelper.NewMetricsController(
&cfg.ControllerConfig,
params,
cons,
scraperhelper.AddFactoryWithConfig(cpuscraper.NewFactory(), cpuscraper.NewConfig(cfg)),
scraperhelper.AddFactoryWithConfig(memoryscraper.NewFactory(), memoryscraper.NewConfig(cfg)),
)
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

package config

import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/scraper/scraperhelper"
)

type Config struct {
scraperhelper.ControllerConfig `mapstructure:",squash"`
}

// nolint: ireturn
func CreateDefaultConfig() component.Config {
cfg := scraperhelper.NewDefaultControllerConfig()
return &Config{
ControllerConfig: cfg,
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

package cpuscraper

import (
"github.com/nginx/agent/v3/internal/collector/containermetricsreceiver/internal/config"
"go.opentelemetry.io/collector/scraper/scraperhelper"

"github.com/nginx/agent/v3/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata"
)

type Config struct {
MetricsBuilderConfig metadata.MetricsBuilderConfig `mapstructure:",squash"`
scraperhelper.ControllerConfig `mapstructure:",squash"`
}

func NewConfig(cfg *config.Config) *Config {
return &Config{
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
ControllerConfig: scraperhelper.ControllerConfig{
CollectionInterval: cfg.CollectionInterval,
InitialDelay: cfg.InitialDelay,
Timeout: cfg.Timeout,
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

//go:generate mdatagen metadata.yaml

package cpuscraper
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)

# cpu

## Default Metrics

The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:

```yaml
metrics:
<metric_name>:
enabled: false
```

### system.cpu.logical.count

Number of available logical CPUs.

| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic |
| ---- | ----------- | ---------- | ----------------------- | --------- |
| {cpu} | Sum | Int | Cumulative | false |

### system.cpu.utilization

Difference in system.cpu.time since the last measurement per logical CPU, divided by the elapsed time (value in interval [0,1]).

| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| 1 | Gauge | Double |

#### Attributes

| Name | Description | Values |
| ---- | ----------- | ------ |
| state | CPU usage type. | Str: ``idle``, ``interrupt``, ``nice``, ``softirq``, ``steal``, ``system``, ``user``, ``wait`` |

## Resource Attributes

| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| resource.id | The resource id. | Any Str | false |
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

package cpuscraper

import (
"context"
"errors"

"github.com/nginx/agent/v3/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/scraper"
)

// NewFactory for CPU scraper.
// nolint: ireturn
func NewFactory() scraper.Factory {
return scraper.NewFactory(
metadata.Type,
createDefaultConfig,
scraper.WithMetrics(createMetricsScraper, metadata.MetricsStability),
)
}

// createDefaultConfig creates the default configuration for the Scraper.
// nolint: ireturn
func createDefaultConfig() component.Config {
return &Config{
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
}
}

// createMetricsScraper creates a scraper based on provided config.
// nolint: ireturn
func createMetricsScraper(
ctx context.Context,
settings scraper.Settings,
config component.Config,
) (scraper.Metrics, error) {
cfg, ok := config.(*Config)
if !ok {
return nil, errors.New("cast to metrics scraper config")
}

s := NewScraper(ctx, settings, cfg)

return scraper.NewMetrics(
s.Scrape,
scraper.WithStart(s.Start),
)
}
Loading