Skip to content

Commit 031f875

Browse files
committed
Switch k8s.pod and k8s.container metrics to use pdata.
1 parent b719459 commit 031f875

24 files changed

+1916
-274
lines changed

.chloggen/switchk8spod.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
5+
component: k8sclusterreceiver
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Switch k8s.pod and k8s.container metrics to use pdata.
9+
10+
# One or more tracking issues related to the change
11+
issues: [23441]

receiver/k8sclusterreceiver/e2e_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ const testKubeConfig = "/tmp/kube-config-otelcol-e2e-testing"
3939
// make docker-otelcontribcol
4040
// KUBECONFIG=/tmp/kube-config-otelcol-e2e-testing kind load docker-image otelcontribcol:latest
4141
func TestE2E(t *testing.T) {
42+
var expected pmetric.Metrics
43+
expectedFile := filepath.Join("testdata", "e2e", "expected.yaml")
44+
expected, err := golden.ReadMetrics(expectedFile)
45+
require.NoError(t, err)
4246
kubeConfig, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
4347
require.NoError(t, err)
4448
dynamicClient, err := dynamic.NewForConfig(kubeConfig)
@@ -57,11 +61,6 @@ func TestE2E(t *testing.T) {
5761
wantEntries := 10 // Minimal number of metrics to wait for.
5862
waitForData(t, wantEntries, metricsConsumer)
5963

60-
var expected pmetric.Metrics
61-
expectedFile := filepath.Join("testdata", "e2e", "expected.yaml")
62-
expected, err = golden.ReadMetrics(expectedFile)
63-
require.NoError(t, err)
64-
require.NoError(t, err)
6564
replaceWithStar := func(string) string { return "*" }
6665
shortenNames := func(value string) string {
6766
if strings.HasPrefix(value, "kube-proxy") {
@@ -82,7 +81,7 @@ func TestE2E(t *testing.T) {
8281
return value
8382
}
8483
containerImageShorten := func(value string) string {
85-
return value[strings.LastIndex(value, "/"):]
84+
return value[(strings.LastIndex(value, "/") + 1):]
8685
}
8786
require.NoError(t, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1],
8887
pmetrictest.IgnoreTimestamp(),

receiver/k8sclusterreceiver/internal/collection/collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func (dc *DataCollector) SyncMetrics(obj interface{}) {
104104

105105
switch o := obj.(type) {
106106
case *corev1.Pod:
107-
md = ocsToMetrics(pod.GetMetrics(o, dc.settings.TelemetrySettings.Logger))
107+
md = pod.GetMetrics(dc.settings, o)
108108
case *corev1.Node:
109109
md = ocsToMetrics(node.GetMetrics(o, dc.nodeConditionsToReport, dc.allocatableTypesToReport, dc.settings.TelemetrySettings.Logger))
110110
case *corev1.Namespace:

receiver/k8sclusterreceiver/internal/container/containers.go

Lines changed: 45 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@
44
package container // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/container"
55

66
import (
7-
"fmt"
7+
"time"
88

9-
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
10-
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
9+
"go.opentelemetry.io/collector/pdata/pcommon"
10+
"go.opentelemetry.io/collector/pdata/pmetric"
11+
"go.opentelemetry.io/collector/receiver"
1112
conventions "go.opentelemetry.io/collector/semconv/v1.6.1"
12-
"go.uber.org/zap"
1313
corev1 "k8s.io/api/core/v1"
1414

1515
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/docker"
16-
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/maps"
1716
metadataPkg "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/experimentalmetricmetadata"
18-
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/constants"
17+
imetadata "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/container/internal/metadata"
1918
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/metadata"
2019
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/utils"
2120
)
@@ -31,127 +30,47 @@ const (
3130
containerStatusTerminated = "terminated"
3231
)
3332

34-
var containerRestartMetric = &metricspb.MetricDescriptor{
35-
Name: "k8s.container.restarts",
36-
Description: "How many times the container has restarted in the recent past. " +
37-
"This value is pulled directly from the K8s API and the value can go indefinitely high" +
38-
" and be reset to 0 at any time depending on how your kubelet is configured to prune" +
39-
" dead containers. It is best to not depend too much on the exact value but rather look" +
40-
" at it as either == 0, in which case you can conclude there were no restarts in the recent" +
41-
" past, or > 0, in which case you can conclude there were restarts in the recent past, and" +
42-
" not try and analyze the value beyond that.",
43-
Unit: "1",
44-
Type: metricspb.MetricDescriptor_GAUGE_INT64,
45-
}
46-
47-
var containerReadyMetric = &metricspb.MetricDescriptor{
48-
Name: "k8s.container.ready",
49-
Description: "Whether a container has passed its readiness probe (0 for no, 1 for yes)",
50-
Type: metricspb.MetricDescriptor_GAUGE_INT64,
51-
}
52-
53-
// GetStatusMetrics returns metrics about the status of the container.
54-
func GetStatusMetrics(cs corev1.ContainerStatus) []*metricspb.Metric {
55-
metrics := []*metricspb.Metric{
56-
{
57-
MetricDescriptor: containerRestartMetric,
58-
Timeseries: []*metricspb.TimeSeries{
59-
utils.GetInt64TimeSeries(int64(cs.RestartCount)),
60-
},
61-
},
62-
{
63-
MetricDescriptor: containerReadyMetric,
64-
Timeseries: []*metricspb.TimeSeries{
65-
utils.GetInt64TimeSeries(boolToInt64(cs.Ready)),
66-
},
67-
},
68-
}
69-
70-
return metrics
71-
}
72-
73-
func boolToInt64(b bool) int64 {
74-
if b {
75-
return 1
76-
}
77-
return 0
78-
}
79-
8033
// GetSpecMetrics metricizes values from the container spec.
8134
// This includes values like resource requests and limits.
82-
func GetSpecMetrics(c corev1.Container) []*metricspb.Metric {
83-
var metrics []*metricspb.Metric
84-
85-
for _, t := range []struct {
86-
typ string
87-
description string
88-
rl corev1.ResourceList
89-
}{
90-
{
91-
"request",
92-
"Resource requested for the container. " +
93-
"See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details",
94-
c.Resources.Requests,
95-
},
96-
{
97-
"limit",
98-
"Maximum resource limit set for the container. " +
99-
"See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details",
100-
c.Resources.Limits,
101-
},
102-
} {
103-
for k, v := range t.rl {
104-
val := utils.GetInt64TimeSeries(v.Value())
105-
valType := metricspb.MetricDescriptor_GAUGE_INT64
106-
if k == corev1.ResourceCPU {
107-
// cpu metrics must be of the double type to adhere to opentelemetry system.cpu metric specifications
108-
valType = metricspb.MetricDescriptor_GAUGE_DOUBLE
109-
val = utils.GetDoubleTimeSeries(float64(v.MilliValue()) / 1000.0)
110-
}
111-
metrics = append(metrics,
112-
&metricspb.Metric{
113-
MetricDescriptor: &metricspb.MetricDescriptor{
114-
Name: fmt.Sprintf("k8s.container.%s_%s", k, t.typ),
115-
Description: t.description,
116-
Type: valType,
117-
},
118-
Timeseries: []*metricspb.TimeSeries{
119-
val,
120-
},
121-
},
122-
)
35+
func GetSpecMetrics(set receiver.CreateSettings, c corev1.Container, pod *corev1.Pod) pmetric.Metrics {
36+
mb := imetadata.NewMetricsBuilder(imetadata.DefaultMetricsBuilderConfig(), set)
37+
ts := pcommon.NewTimestampFromTime(time.Now())
38+
mb.RecordK8sContainerCPURequestDataPoint(ts, float64(c.Resources.Requests.Cpu().MilliValue())/1000.0)
39+
mb.RecordK8sContainerCPULimitDataPoint(ts, float64(c.Resources.Limits.Cpu().MilliValue())/1000.0)
40+
for _, cs := range pod.Status.ContainerStatuses {
41+
if cs.Name == c.Name {
42+
mb.RecordK8sContainerRestartsDataPoint(ts, int64(cs.RestartCount))
43+
mb.RecordK8sContainerReadyDataPoint(ts, boolToInt64(cs.Ready))
44+
break
12345
}
12446
}
12547

126-
return metrics
127-
}
128-
129-
// GetResource returns a proto representation of the pod.
130-
func GetResource(labels map[string]string) *resourcepb.Resource {
131-
return &resourcepb.Resource{
132-
Type: constants.ContainerType,
133-
Labels: labels,
48+
var containerID string
49+
for _, cs := range pod.Status.ContainerStatuses {
50+
if cs.Name == c.Name {
51+
containerID = cs.ContainerID
52+
}
13453
}
135-
}
136-
137-
// GetAllLabels returns all container labels, including ones from
138-
// the pod in which the container is running.
139-
func GetAllLabels(cs corev1.ContainerStatus,
140-
dims map[string]string, logger *zap.Logger) map[string]string {
141-
142-
image, err := docker.ParseImageName(cs.Image)
54+
resourceOptions := []imetadata.ResourceMetricsOption{
55+
imetadata.WithK8sPodUID(string(pod.UID)),
56+
imetadata.WithK8sPodName(pod.Name),
57+
imetadata.WithK8sNodeName(pod.Spec.NodeName),
58+
imetadata.WithK8sNamespaceName(pod.Namespace),
59+
imetadata.WithOpencensusResourcetype("container"),
60+
imetadata.WithContainerID(utils.StripContainerID(containerID)),
61+
imetadata.WithK8sContainerName(c.Name),
62+
}
63+
image, err := docker.ParseImageName(c.Image)
14364
if err != nil {
144-
docker.LogParseError(err, cs.Image, logger)
65+
docker.LogParseError(err, c.Image, set.Logger)
66+
} else {
67+
resourceOptions = append(resourceOptions,
68+
imetadata.WithContainerImageName(image.Repository),
69+
imetadata.WithContainerImageTag(image.Tag))
14570
}
146-
147-
out := maps.CloneStringMap(dims)
148-
149-
out[conventions.AttributeContainerID] = utils.StripContainerID(cs.ContainerID)
150-
out[conventions.AttributeK8SContainerName] = cs.Name
151-
out[conventions.AttributeContainerImageName] = image.Repository
152-
out[conventions.AttributeContainerImageTag] = image.Tag
153-
154-
return out
71+
return mb.Emit(
72+
resourceOptions...,
73+
)
15574
}
15675

15776
func GetMetadata(cs corev1.ContainerStatus) *metadata.KubernetesMetadata {
@@ -177,3 +96,10 @@ func GetMetadata(cs corev1.ContainerStatus) *metadata.KubernetesMetadata {
17796
Metadata: mdata,
17897
}
17998
}
99+
100+
func boolToInt64(b bool) int64 {
101+
if b {
102+
return 1
103+
}
104+
return 0
105+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//go:generate mdatagen metadata.yaml
5+
6+
package container // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/container"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
2+
3+
# k8s/container
4+
5+
## Default Metrics
6+
7+
The following metrics are emitted by default. Each of them can be disabled by applying the following configuration:
8+
9+
```yaml
10+
metrics:
11+
<metric_name>:
12+
enabled: false
13+
```
14+
15+
### k8s.container.cpu_limit
16+
17+
Maximum resource limit set for the container. See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details
18+
19+
| Unit | Metric Type | Value Type |
20+
| ---- | ----------- | ---------- |
21+
| 1 | Gauge | Double |
22+
23+
### k8s.container.cpu_request
24+
25+
Resource requested for the container. See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details
26+
27+
| Unit | Metric Type | Value Type |
28+
| ---- | ----------- | ---------- |
29+
| 1 | Gauge | Double |
30+
31+
### k8s.container.ready
32+
33+
Whether a container has passed its readiness probe (0 for no, 1 for yes)
34+
35+
| Unit | Metric Type | Value Type |
36+
| ---- | ----------- | ---------- |
37+
| 1 | Gauge | Int |
38+
39+
### k8s.container.restarts
40+
41+
How many times the container has restarted in the recent past. This value is pulled directly from the K8s API and the value can go indefinitely high and be reset to 0 at any time depending on how your kubelet is configured to prune dead containers. It is best to not depend too much on the exact value but rather look at it as either == 0, in which case you can conclude there were no restarts in the recent past, or > 0, in which case you can conclude there were restarts in the recent past, and not try and analyze the value beyond that.
42+
43+
| Unit | Metric Type | Value Type |
44+
| ---- | ----------- | ---------- |
45+
| 1 | Gauge | Int |
46+
47+
## Resource Attributes
48+
49+
| Name | Description | Values | Enabled |
50+
| ---- | ----------- | ------ | ------- |
51+
| container.id | The container id. | Any Str | true |
52+
| container.image.name | The container image name | Any Str | true |
53+
| container.image.tag | The container image tag | Any Str | true |
54+
| k8s.container.name | The k8s container name | Any Str | true |
55+
| k8s.namespace.name | The k8s namespace name | Any Str | true |
56+
| k8s.node.name | The k8s node name | Any Str | true |
57+
| k8s.pod.name | The k8s pod name | Any Str | true |
58+
| k8s.pod.uid | The k8s pod uid | Any Str | true |
59+
| opencensus.resourcetype | The OpenCensus resource type. | Any Str | true |

0 commit comments

Comments
 (0)