Skip to content

Commit

Permalink
Switch k8s.pod and k8s.container metrics to use pdata.
Browse files Browse the repository at this point in the history
  • Loading branch information
atoulme committed Jun 19, 2023
1 parent b719459 commit 031f875
Show file tree
Hide file tree
Showing 24 changed files with 1,916 additions and 274 deletions.
11 changes: 11 additions & 0 deletions .chloggen/switchk8spod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: k8sclusterreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Switch k8s.pod and k8s.container metrics to use pdata.

# One or more tracking issues related to the change
issues: [23441]
11 changes: 5 additions & 6 deletions receiver/k8sclusterreceiver/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const testKubeConfig = "/tmp/kube-config-otelcol-e2e-testing"
// make docker-otelcontribcol
// KUBECONFIG=/tmp/kube-config-otelcol-e2e-testing kind load docker-image otelcontribcol:latest
func TestE2E(t *testing.T) {
var expected pmetric.Metrics
expectedFile := filepath.Join("testdata", "e2e", "expected.yaml")
expected, err := golden.ReadMetrics(expectedFile)
require.NoError(t, err)
kubeConfig, err := clientcmd.BuildConfigFromFlags("", testKubeConfig)
require.NoError(t, err)
dynamicClient, err := dynamic.NewForConfig(kubeConfig)
Expand All @@ -57,11 +61,6 @@ func TestE2E(t *testing.T) {
wantEntries := 10 // Minimal number of metrics to wait for.
waitForData(t, wantEntries, metricsConsumer)

var expected pmetric.Metrics
expectedFile := filepath.Join("testdata", "e2e", "expected.yaml")
expected, err = golden.ReadMetrics(expectedFile)
require.NoError(t, err)
require.NoError(t, err)
replaceWithStar := func(string) string { return "*" }
shortenNames := func(value string) string {
if strings.HasPrefix(value, "kube-proxy") {
Expand All @@ -82,7 +81,7 @@ func TestE2E(t *testing.T) {
return value
}
containerImageShorten := func(value string) string {
return value[strings.LastIndex(value, "/"):]
return value[(strings.LastIndex(value, "/") + 1):]
}
require.NoError(t, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1],
pmetrictest.IgnoreTimestamp(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (dc *DataCollector) SyncMetrics(obj interface{}) {

switch o := obj.(type) {
case *corev1.Pod:
md = ocsToMetrics(pod.GetMetrics(o, dc.settings.TelemetrySettings.Logger))
md = pod.GetMetrics(dc.settings, o)
case *corev1.Node:
md = ocsToMetrics(node.GetMetrics(o, dc.nodeConditionsToReport, dc.allocatableTypesToReport, dc.settings.TelemetrySettings.Logger))
case *corev1.Namespace:
Expand Down
164 changes: 45 additions & 119 deletions receiver/k8sclusterreceiver/internal/container/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
package container // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/container"

import (
"fmt"
"time"

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

"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/docker"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/maps"
metadataPkg "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/experimentalmetricmetadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/constants"
imetadata "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/container/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/utils"
)
Expand All @@ -31,127 +30,47 @@ const (
containerStatusTerminated = "terminated"
)

var containerRestartMetric = &metricspb.MetricDescriptor{
Name: "k8s.container.restarts",
Description: "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.",
Unit: "1",
Type: metricspb.MetricDescriptor_GAUGE_INT64,
}

var containerReadyMetric = &metricspb.MetricDescriptor{
Name: "k8s.container.ready",
Description: "Whether a container has passed its readiness probe (0 for no, 1 for yes)",
Type: metricspb.MetricDescriptor_GAUGE_INT64,
}

// GetStatusMetrics returns metrics about the status of the container.
func GetStatusMetrics(cs corev1.ContainerStatus) []*metricspb.Metric {
metrics := []*metricspb.Metric{
{
MetricDescriptor: containerRestartMetric,
Timeseries: []*metricspb.TimeSeries{
utils.GetInt64TimeSeries(int64(cs.RestartCount)),
},
},
{
MetricDescriptor: containerReadyMetric,
Timeseries: []*metricspb.TimeSeries{
utils.GetInt64TimeSeries(boolToInt64(cs.Ready)),
},
},
}

return metrics
}

func boolToInt64(b bool) int64 {
if b {
return 1
}
return 0
}

// GetSpecMetrics metricizes values from the container spec.
// This includes values like resource requests and limits.
func GetSpecMetrics(c corev1.Container) []*metricspb.Metric {
var metrics []*metricspb.Metric

for _, t := range []struct {
typ string
description string
rl corev1.ResourceList
}{
{
"request",
"Resource requested for the container. " +
"See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details",
c.Resources.Requests,
},
{
"limit",
"Maximum resource limit set for the container. " +
"See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details",
c.Resources.Limits,
},
} {
for k, v := range t.rl {
val := utils.GetInt64TimeSeries(v.Value())
valType := metricspb.MetricDescriptor_GAUGE_INT64
if k == corev1.ResourceCPU {
// cpu metrics must be of the double type to adhere to opentelemetry system.cpu metric specifications
valType = metricspb.MetricDescriptor_GAUGE_DOUBLE
val = utils.GetDoubleTimeSeries(float64(v.MilliValue()) / 1000.0)
}
metrics = append(metrics,
&metricspb.Metric{
MetricDescriptor: &metricspb.MetricDescriptor{
Name: fmt.Sprintf("k8s.container.%s_%s", k, t.typ),
Description: t.description,
Type: valType,
},
Timeseries: []*metricspb.TimeSeries{
val,
},
},
)
func GetSpecMetrics(set receiver.CreateSettings, c corev1.Container, pod *corev1.Pod) pmetric.Metrics {
mb := imetadata.NewMetricsBuilder(imetadata.DefaultMetricsBuilderConfig(), set)
ts := pcommon.NewTimestampFromTime(time.Now())
mb.RecordK8sContainerCPURequestDataPoint(ts, float64(c.Resources.Requests.Cpu().MilliValue())/1000.0)
mb.RecordK8sContainerCPULimitDataPoint(ts, float64(c.Resources.Limits.Cpu().MilliValue())/1000.0)
for _, cs := range pod.Status.ContainerStatuses {
if cs.Name == c.Name {
mb.RecordK8sContainerRestartsDataPoint(ts, int64(cs.RestartCount))
mb.RecordK8sContainerReadyDataPoint(ts, boolToInt64(cs.Ready))
break
}
}

return metrics
}

// GetResource returns a proto representation of the pod.
func GetResource(labels map[string]string) *resourcepb.Resource {
return &resourcepb.Resource{
Type: constants.ContainerType,
Labels: labels,
var containerID string
for _, cs := range pod.Status.ContainerStatuses {
if cs.Name == c.Name {
containerID = cs.ContainerID
}
}
}

// GetAllLabels returns all container labels, including ones from
// the pod in which the container is running.
func GetAllLabels(cs corev1.ContainerStatus,
dims map[string]string, logger *zap.Logger) map[string]string {

image, err := docker.ParseImageName(cs.Image)
resourceOptions := []imetadata.ResourceMetricsOption{
imetadata.WithK8sPodUID(string(pod.UID)),
imetadata.WithK8sPodName(pod.Name),
imetadata.WithK8sNodeName(pod.Spec.NodeName),
imetadata.WithK8sNamespaceName(pod.Namespace),
imetadata.WithOpencensusResourcetype("container"),
imetadata.WithContainerID(utils.StripContainerID(containerID)),
imetadata.WithK8sContainerName(c.Name),
}
image, err := docker.ParseImageName(c.Image)
if err != nil {
docker.LogParseError(err, cs.Image, logger)
docker.LogParseError(err, c.Image, set.Logger)
} else {
resourceOptions = append(resourceOptions,
imetadata.WithContainerImageName(image.Repository),
imetadata.WithContainerImageTag(image.Tag))
}

out := maps.CloneStringMap(dims)

out[conventions.AttributeContainerID] = utils.StripContainerID(cs.ContainerID)
out[conventions.AttributeK8SContainerName] = cs.Name
out[conventions.AttributeContainerImageName] = image.Repository
out[conventions.AttributeContainerImageTag] = image.Tag

return out
return mb.Emit(
resourceOptions...,
)
}

func GetMetadata(cs corev1.ContainerStatus) *metadata.KubernetesMetadata {
Expand All @@ -177,3 +96,10 @@ func GetMetadata(cs corev1.ContainerStatus) *metadata.KubernetesMetadata {
Metadata: mdata,
}
}

func boolToInt64(b bool) int64 {
if b {
return 1
}
return 0
}
6 changes: 6 additions & 0 deletions receiver/k8sclusterreceiver/internal/container/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:generate mdatagen metadata.yaml

package container // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8sclusterreceiver/internal/container"
59 changes: 59 additions & 0 deletions receiver/k8sclusterreceiver/internal/container/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)

# k8s/container

## 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
```
### k8s.container.cpu_limit
Maximum resource limit set for the container. See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| 1 | Gauge | Double |
### k8s.container.cpu_request
Resource requested for the container. See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#resourcerequirements-v1-core for details
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| 1 | Gauge | Double |
### k8s.container.ready
Whether a container has passed its readiness probe (0 for no, 1 for yes)
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| 1 | Gauge | Int |
### k8s.container.restarts
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.
| Unit | Metric Type | Value Type |
| ---- | ----------- | ---------- |
| 1 | Gauge | Int |
## Resource Attributes
| Name | Description | Values | Enabled |
| ---- | ----------- | ------ | ------- |
| container.id | The container id. | Any Str | true |
| container.image.name | The container image name | Any Str | true |
| container.image.tag | The container image tag | Any Str | true |
| k8s.container.name | The k8s container name | Any Str | true |
| k8s.namespace.name | The k8s namespace name | Any Str | true |
| k8s.node.name | The k8s node name | Any Str | true |
| k8s.pod.name | The k8s pod name | Any Str | true |
| k8s.pod.uid | The k8s pod uid | Any Str | true |
| opencensus.resourcetype | The OpenCensus resource type. | Any Str | true |
Loading

0 comments on commit 031f875

Please sign in to comment.