Skip to content

Commit 88ac450

Browse files
authored
receiver/dockerstatsreceiver: add container.uptime metric (open-telemetry#22851)
* receiver/dockerstatsreceiver: add container.uptime metric This change adds the `container.uptime` metric which indicates time elapsed (in seconds) since the container started. * Add scraper error for invalid time format * Disable by default * empty
1 parent 6947357 commit 88ac450

File tree

15 files changed

+231
-3
lines changed

15 files changed

+231
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Use this changelog template to create an entry for release notes.
2+
# If your change doesn't affect end users, such as a test fix or a tooling change,
3+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
4+
5+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
6+
change_type: enhancement
7+
8+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
9+
component: dockerstatsreceiver
10+
11+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
12+
note: Add container.uptime metric, indicating time elapsed since the start of the container.
13+
14+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
15+
issues: [22037]
16+
17+
# (Optional) One or more lines of additional information to render under the primary note.
18+
# These lines will be padded with 2 spaces and then inserted directly into the document.
19+
# Use pipe (|) for multiline entries.
20+
subtext:

receiver/dockerstatsreceiver/documentation.md

+8
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,14 @@ It requires docker API 1.23 or higher and kernel version >= 4.3 with pids cgroup
686686
| ---- | ----------- | ---------- | ----------------------- | --------- |
687687
| {pids} | Sum | Int | Cumulative | false |
688688

689+
### container.uptime
690+
691+
Time elapsed since container start time.
692+
693+
| Unit | Metric Type | Value Type |
694+
| ---- | ----------- | ---------- |
695+
| s | Gauge | Double |
696+
689697
## Resource Attributes
690698

691699
| Name | Description | Values | Enabled |

receiver/dockerstatsreceiver/internal/metadata/generated_config.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

receiver/dockerstatsreceiver/internal/metadata/generated_config_test.go

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

receiver/dockerstatsreceiver/internal/metadata/generated_metrics.go

+57
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

receiver/dockerstatsreceiver/internal/metadata/generated_metrics_test.go

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

receiver/dockerstatsreceiver/internal/metadata/testdata/config.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ all_set:
129129
enabled: true
130130
container.pids.limit:
131131
enabled: true
132+
container.uptime:
133+
enabled: true
132134
resource_attributes:
133135
container.hostname:
134136
enabled: true
@@ -270,6 +272,8 @@ none_set:
270272
enabled: false
271273
container.pids.limit:
272274
enabled: false
275+
container.uptime:
276+
enabled: false
273277
resource_attributes:
274278
container.hostname:
275279
enabled: false

receiver/dockerstatsreceiver/metadata.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -649,3 +649,11 @@ metrics:
649649
value_type: int
650650
aggregation: cumulative
651651
monotonic: false
652+
653+
# Base
654+
container.uptime:
655+
enabled: false
656+
description: "Time elapsed since container start time."
657+
unit: s
658+
gauge:
659+
value_type: double

receiver/dockerstatsreceiver/receiver.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"sync"
1212
"time"
1313

14+
"github.com/docker/docker/api/types"
1415
dtypes "github.com/docker/docker/api/types"
1516
"go.opentelemetry.io/collector/component"
1617
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -104,18 +105,24 @@ func (r *receiver) scrapeV2(ctx context.Context) (pmetric.Metrics, error) {
104105
errs = multierr.Append(errs, scrapererror.NewPartialScrapeError(res.err, 0))
105106
continue
106107
}
107-
r.recordContainerStats(now, res.stats, res.container)
108+
if err := r.recordContainerStats(now, res.stats, res.container); err != nil {
109+
errs = multierr.Append(errs, err)
110+
}
108111
}
109112

110113
return r.mb.Emit(), errs
111114
}
112115

113-
func (r *receiver) recordContainerStats(now pcommon.Timestamp, containerStats *dtypes.StatsJSON, container *docker.Container) {
116+
func (r *receiver) recordContainerStats(now pcommon.Timestamp, containerStats *dtypes.StatsJSON, container *docker.Container) error {
117+
var errs error
114118
r.recordCPUMetrics(now, &containerStats.CPUStats, &containerStats.PreCPUStats)
115119
r.recordMemoryMetrics(now, &containerStats.MemoryStats)
116120
r.recordBlkioMetrics(now, &containerStats.BlkioStats)
117121
r.recordNetworkMetrics(now, &containerStats.Networks)
118122
r.recordPidsMetrics(now, &containerStats.PidsStats)
123+
if err := r.recordBaseMetrics(now, container.ContainerJSONBase); err != nil {
124+
errs = multierr.Append(errs, err)
125+
}
119126

120127
// Always-present resource attrs + the user-configured resource attrs
121128
resourceCapacity := defaultResourcesLen + len(r.config.EnvVarsToMetricLabels) + len(r.config.ContainerLabelsToMetricLabels)
@@ -145,6 +152,7 @@ func (r *receiver) recordContainerStats(now pcommon.Timestamp, containerStats *d
145152
}
146153

147154
r.mb.EmitForResource(resourceMetricsOptions...)
155+
return errs
148156
}
149157

150158
func (r *receiver) recordMemoryMetrics(now pcommon.Timestamp, memoryStats *dtypes.MemoryStats) {
@@ -265,3 +273,15 @@ func (r *receiver) recordPidsMetrics(now pcommon.Timestamp, pidsStats *dtypes.Pi
265273
}
266274
}
267275
}
276+
277+
func (r *receiver) recordBaseMetrics(now pcommon.Timestamp, base *types.ContainerJSONBase) error {
278+
t, err := time.Parse(time.RFC3339, base.State.StartedAt)
279+
if err != nil {
280+
// value not available or invalid
281+
return scrapererror.NewPartialScrapeError(fmt.Errorf("error retrieving container.uptime from Container.State.StartedAt: %w", err), 1)
282+
}
283+
if v := now.AsTime().Sub(t); v > 0 {
284+
r.mb.RecordContainerUptimeDataPoint(now, v.Seconds())
285+
}
286+
return nil
287+
}

receiver/dockerstatsreceiver/receiver_test.go

+49-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import (
1717
"testing"
1818
"time"
1919

20+
"github.com/docker/docker/api/types"
2021
"github.com/stretchr/testify/assert"
2122
"github.com/stretchr/testify/require"
2223
"go.opentelemetry.io/collector/component/componenttest"
24+
"go.opentelemetry.io/collector/pdata/pcommon"
2325
"go.opentelemetry.io/collector/receiver/receivertest"
2426
"go.opentelemetry.io/collector/receiver/scraperhelper"
2527

@@ -96,6 +98,7 @@ var (
9698
ContainerNetworkIoUsageTxPackets: metricEnabled,
9799
ContainerPidsCount: metricEnabled,
98100
ContainerPidsLimit: metricEnabled,
101+
ContainerUptime: metricEnabled,
99102
}
100103
)
101104

@@ -254,11 +257,56 @@ func TestScrapeV2(t *testing.T) {
254257
assert.NoError(t, err)
255258
assert.NoError(t, pmetrictest.CompareMetrics(expectedMetrics, actualMetrics,
256259
pmetrictest.IgnoreMetricDataPointsOrder(),
257-
pmetrictest.IgnoreResourceMetricsOrder(), pmetrictest.IgnoreStartTimestamp(), pmetrictest.IgnoreTimestamp()))
260+
pmetrictest.IgnoreResourceMetricsOrder(),
261+
pmetrictest.IgnoreStartTimestamp(),
262+
pmetrictest.IgnoreTimestamp(),
263+
pmetrictest.IgnoreMetricValues(
264+
"container.uptime", // value depends on time.Now(), making it unpredictable as far as tests go
265+
),
266+
))
258267
})
259268
}
260269
}
261270

271+
func TestRecordBaseMetrics(t *testing.T) {
272+
cfg := createDefaultConfig().(*Config)
273+
cfg.MetricsBuilderConfig.Metrics = metadata.MetricsConfig{
274+
ContainerUptime: metricEnabled,
275+
}
276+
r := newReceiver(receivertest.NewNopCreateSettings(), cfg)
277+
now := time.Now()
278+
started := now.Add(-2 * time.Second).Format(time.RFC3339)
279+
280+
t.Run("ok", func(t *testing.T) {
281+
err := r.recordBaseMetrics(
282+
pcommon.NewTimestampFromTime(now),
283+
&types.ContainerJSONBase{
284+
State: &types.ContainerState{
285+
StartedAt: started,
286+
},
287+
},
288+
)
289+
require.NoError(t, err)
290+
m := r.mb.Emit().ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0)
291+
assert.Equal(t, "container.uptime", m.Name())
292+
dp := m.Gauge().DataPoints()
293+
assert.Equal(t, 1, dp.Len())
294+
assert.Equal(t, 2, int(dp.At(0).DoubleValue()))
295+
})
296+
297+
t.Run("error", func(t *testing.T) {
298+
err := r.recordBaseMetrics(
299+
pcommon.NewTimestampFromTime(now),
300+
&types.ContainerJSONBase{
301+
State: &types.ContainerState{
302+
StartedAt: "bad date",
303+
},
304+
},
305+
)
306+
require.Error(t, err)
307+
})
308+
}
309+
262310
func dockerMockServer(urlToFile *map[string]string) (*httptest.Server, error) {
263311
urlToFileContents := make(map[string][]byte, len(*urlToFile))
264312
for urlPath, filePath := range *urlToFile {

receiver/dockerstatsreceiver/testdata/mock/cgroups_v2/expected_metrics.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,13 @@ resourceMetrics:
360360
startTimeUnixNano: "1682426015940992000"
361361
timeUnixNano: "1682426015943175000"
362362
unit: '{pids}'
363+
- description: Time elapsed since container start time.
364+
name: container.uptime
365+
gauge:
366+
dataPoints:
367+
- asDouble: 0.0002888012543185477
368+
timeUnixNano: "1657771705535206000"
369+
unit: 's'
363370
scope:
364371
name: otelcol/dockerstatsreceiver
365372
version: latest

receiver/dockerstatsreceiver/testdata/mock/no_pids_stats/expected_metrics.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,13 @@ resourceMetrics:
779779
timeUnixNano: "1683723817613281000"
780780
isMonotonic: true
781781
unit: '{packets}'
782+
- description: Time elapsed since container start time.
783+
name: container.uptime
784+
gauge:
785+
dataPoints:
786+
- asDouble: 0.0002888012543185477
787+
timeUnixNano: "1657771705535206000"
788+
unit: 's'
782789
scope:
783790
name: otelcol/dockerstatsreceiver
784791
version: latest

receiver/dockerstatsreceiver/testdata/mock/pids_stats_max/expected_metrics.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,13 @@ resourceMetrics:
412412
startTimeUnixNano: "1683723781127718000"
413413
timeUnixNano: "1683723781130612000"
414414
unit: '{pids}'
415+
- description: Time elapsed since container start time.
416+
name: container.uptime
417+
gauge:
418+
dataPoints:
419+
- asDouble: 0.0002888012543185477
420+
timeUnixNano: "1657771705535206000"
421+
unit: 's'
415422
scope:
416423
name: otelcol/dockerstatsreceiver
417424
version: latest

0 commit comments

Comments
 (0)