diff --git a/receiver/kubeletstatsreceiver/internal/kubelet/metadata.go b/receiver/kubeletstatsreceiver/internal/kubelet/metadata.go index ab22853b22bc..0bf8b385ff34 100644 --- a/receiver/kubeletstatsreceiver/internal/kubelet/metadata.go +++ b/receiver/kubeletstatsreceiver/internal/kubelet/metadata.go @@ -50,15 +50,87 @@ type Metadata struct { Labels map[MetadataLabel]bool PodsMetadata *v1.PodList DetailedPVCResourceSetter func(rb *metadata.ResourceBuilder, volCacheID, volumeClaim, namespace string) error + podResources map[string]resources + containerResources map[string]resources +} + +type resources struct { + cpuRequest float64 + cpuLimit float64 + memoryRequest int64 + memoryLimit int64 +} + +func getContainerResources(r *v1.ResourceRequirements) resources { + if r == nil { + return resources{} + } + + return resources{ + cpuRequest: r.Requests.Cpu().AsApproximateFloat64(), + cpuLimit: r.Limits.Cpu().AsApproximateFloat64(), + memoryRequest: r.Requests.Memory().Value(), + memoryLimit: r.Limits.Memory().Value(), + } } func NewMetadata(labels []MetadataLabel, podsMetadata *v1.PodList, detailedPVCResourceSetter func(rb *metadata.ResourceBuilder, volCacheID, volumeClaim, namespace string) error) Metadata { - return Metadata{ + m := Metadata{ Labels: getLabelsMap(labels), PodsMetadata: podsMetadata, DetailedPVCResourceSetter: detailedPVCResourceSetter, + podResources: make(map[string]resources, 0), + containerResources: make(map[string]resources, 0), + } + + if podsMetadata != nil { + for _, pod := range podsMetadata.Items { + var podResource resources + allContainersCPULimitsDefined := true + allContainersCPURequestsDefined := true + allContainersMemoryLimitsDefined := true + allContainersMemoryRequestsDefined := true + for _, container := range pod.Spec.Containers { + containerResource := getContainerResources(&container.Resources) + + if allContainersCPULimitsDefined && containerResource.cpuLimit == 0 { + allContainersCPULimitsDefined = false + podResource.cpuLimit = 0 + } + if allContainersCPURequestsDefined && containerResource.cpuRequest == 0 { + allContainersCPURequestsDefined = false + podResource.cpuRequest = 0 + } + if allContainersMemoryLimitsDefined && containerResource.memoryLimit == 0 { + allContainersMemoryLimitsDefined = false + podResource.memoryLimit = 0 + } + if allContainersMemoryRequestsDefined && containerResource.memoryRequest == 0 { + allContainersMemoryRequestsDefined = false + podResource.memoryRequest = 0 + } + + if allContainersCPULimitsDefined { + podResource.cpuLimit += containerResource.cpuLimit + } + if allContainersCPURequestsDefined { + podResource.cpuRequest += containerResource.cpuRequest + } + if allContainersMemoryLimitsDefined { + podResource.memoryLimit += containerResource.memoryLimit + } + if allContainersMemoryRequestsDefined { + podResource.memoryRequest += containerResource.memoryRequest + } + + m.containerResources[string(pod.UID)+container.Name] = containerResource + } + m.podResources[string(pod.UID)] = podResource + } } + + return m } func getLabelsMap(metadataLabels []MetadataLabel) map[MetadataLabel]bool { diff --git a/receiver/kubeletstatsreceiver/internal/kubelet/metadata_test.go b/receiver/kubeletstatsreceiver/internal/kubelet/metadata_test.go index 396c4a32c73e..ae9309a375c0 100644 --- a/receiver/kubeletstatsreceiver/internal/kubelet/metadata_test.go +++ b/receiver/kubeletstatsreceiver/internal/kubelet/metadata_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + k8sresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1" @@ -386,3 +387,300 @@ func TestSetExtraLabelsForVolumeTypes(t *testing.T) { }) } } + +// Test happy paths for volume type metadata. +func TestCpuAndMemoryGetters(t *testing.T) { + + tests := []struct { + name string + metadata Metadata + podUID string + containerName string + wantPodCPULimit float64 + wantPodCPURequest float64 + wantContainerCPULimit float64 + wantContainerCPURequest float64 + wantPodMemoryLimit int64 + wantPodMemoryRequest int64 + wantContainerMemoryLimit int64 + wantContainerMemoryRequest int64 + }{ + { + name: "no metadata", + metadata: NewMetadata([]MetadataLabel{}, nil, nil), + }, + { + name: "pod happy path", + metadata: NewMetadata([]MetadataLabel{}, &v1.PodList{ + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: "uid-1234", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("100m"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("100m"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + { + Name: "container-2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("2"), + v1.ResourceMemory: k8sresource.MustParse("3G"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("2"), + v1.ResourceMemory: k8sresource.MustParse("3G"), + }, + }, + }, + }, + }, + }, + }, + }, nil), + podUID: "uid-1234", + containerName: "container-2", + wantPodCPULimit: 2.1, + wantPodCPURequest: 2.1, + wantContainerCPULimit: 2, + wantContainerCPURequest: 2, + wantPodMemoryLimit: 4000000000, + wantPodMemoryRequest: 4000000000, + wantContainerMemoryLimit: 3000000000, + wantContainerMemoryRequest: 3000000000, + }, + { + name: "unknown pod", + metadata: NewMetadata([]MetadataLabel{}, &v1.PodList{ + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: "uid-1234", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + { + Name: "container-2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("2"), + v1.ResourceMemory: k8sresource.MustParse("3G"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("2"), + v1.ResourceMemory: k8sresource.MustParse("3G"), + }, + }, + }, + }, + }, + }, + }, + }, nil), + podUID: "uid-12345", + }, + { + name: "unknown container", + metadata: NewMetadata([]MetadataLabel{}, &v1.PodList{ + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: "uid-1234", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("300m"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("300m"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + { + Name: "container-2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("400m"), + v1.ResourceMemory: k8sresource.MustParse("3G"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("400m"), + v1.ResourceMemory: k8sresource.MustParse("3G"), + }, + }, + }, + }, + }, + }, + }, + }, nil), + podUID: "uid-1234", + containerName: "container-3", + wantPodCPULimit: 0.7, + wantPodCPURequest: 0.7, + wantPodMemoryLimit: 4000000000, + wantPodMemoryRequest: 4000000000, + }, + { + name: "container limit not set", + metadata: NewMetadata([]MetadataLabel{}, &v1.PodList{ + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: "uid-1234", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + { + Name: "container-2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + }, + }, + }, + }, + }, nil), + podUID: "uid-1234", + containerName: "container-2", + wantPodCPURequest: 2, + wantContainerCPURequest: 1, + wantPodMemoryRequest: 2000000000, + wantContainerMemoryRequest: 1000000000, + }, + { + name: "container request not set", + metadata: NewMetadata([]MetadataLabel{}, &v1.PodList{ + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: "uid-1234", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + { + Name: "container-2", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + }, + }, + }, + }, + }, nil), + podUID: "uid-1234", + containerName: "container-2", + wantPodCPULimit: 2, + wantContainerCPULimit: 1, + wantPodMemoryLimit: 2000000000, + wantContainerMemoryLimit: 1000000000, + }, + { + name: "container limit not set but other is", + metadata: NewMetadata([]MetadataLabel{}, &v1.PodList{ + Items: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + UID: "uid-1234", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: k8sresource.MustParse("1"), + v1.ResourceMemory: k8sresource.MustParse("1G"), + }, + }, + }, + { + Name: "container-2", + }, + }, + }, + }, + }, + }, nil), + podUID: "uid-1234", + containerName: "container-1", + wantContainerCPULimit: 1, + wantContainerCPURequest: 1, + wantContainerMemoryLimit: 1000000000, + wantContainerMemoryRequest: 1000000000, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.wantPodCPULimit, tt.metadata.podResources[tt.podUID].cpuLimit) + require.Equal(t, tt.wantPodCPURequest, tt.metadata.podResources[tt.podUID].cpuRequest) + require.Equal(t, tt.wantContainerCPULimit, tt.metadata.containerResources[tt.podUID+tt.containerName].cpuLimit) + require.Equal(t, tt.wantContainerCPURequest, tt.metadata.containerResources[tt.podUID+tt.containerName].cpuRequest) + require.Equal(t, tt.wantPodMemoryLimit, tt.metadata.podResources[tt.podUID].memoryLimit) + require.Equal(t, tt.wantPodMemoryRequest, tt.metadata.podResources[tt.podUID].memoryRequest) + require.Equal(t, tt.wantContainerMemoryLimit, tt.metadata.containerResources[tt.podUID+tt.containerName].memoryLimit) + require.Equal(t, tt.wantContainerMemoryRequest, tt.metadata.containerResources[tt.podUID+tt.containerName].memoryRequest) + }) + } +}