Merge pull request #118865 from iholder101/kubelet/add-swap-to-summary-stats

Add swap to stats to Summary API and Prometheus endpoints (`/stats/summary` and `/metrics/resource`)
This commit is contained in:
Kubernetes Prow Robot 2023-07-17 19:49:18 -07:00 committed by GitHub
commit b4d793c450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1049 additions and 443 deletions

View File

@ -41,6 +41,13 @@ var (
metrics.ALPHA,
"")
nodeSwapUsageDesc = metrics.NewDesc("node_swap_usage_bytes",
"Current swap usage of the node in bytes. Reported only on non-windows systems",
nil,
nil,
metrics.ALPHA,
"")
containerCPUUsageDesc = metrics.NewDesc("container_cpu_usage_seconds_total",
"Cumulative cpu time consumed by the container in core-seconds",
[]string{"container", "pod", "namespace"},
@ -55,6 +62,13 @@ var (
metrics.ALPHA,
"")
containerSwapUsageDesc = metrics.NewDesc("container_swap_usage_bytes",
"Current amount of the container swap usage in bytes. Reported only on non-windows systems",
[]string{"container", "pod", "namespace"},
nil,
metrics.ALPHA,
"")
podCPUUsageDesc = metrics.NewDesc("pod_cpu_usage_seconds_total",
"Cumulative cpu time consumed by the pod in core-seconds",
[]string{"pod", "namespace"},
@ -69,6 +83,13 @@ var (
metrics.ALPHA,
"")
podSwapUsageDesc = metrics.NewDesc("pod_swap_usage_bytes",
"Current amount of the pod swap usage in bytes. Reported only on non-windows systems",
[]string{"pod", "namespace"},
nil,
metrics.ALPHA,
"")
resourceScrapeResultDesc = metrics.NewDesc("scrape_error",
"1 if there was an error while getting container metrics, 0 otherwise",
nil,
@ -104,11 +125,14 @@ var _ metrics.StableCollector = &resourceMetricsCollector{}
func (rc *resourceMetricsCollector) DescribeWithStability(ch chan<- *metrics.Desc) {
ch <- nodeCPUUsageDesc
ch <- nodeMemoryUsageDesc
ch <- nodeSwapUsageDesc
ch <- containerStartTimeDesc
ch <- containerCPUUsageDesc
ch <- containerMemoryUsageDesc
ch <- containerSwapUsageDesc
ch <- podCPUUsageDesc
ch <- podMemoryUsageDesc
ch <- podSwapUsageDesc
ch <- resourceScrapeResultDesc
}
@ -131,15 +155,18 @@ func (rc *resourceMetricsCollector) CollectWithStability(ch chan<- metrics.Metri
rc.collectNodeCPUMetrics(ch, statsSummary.Node)
rc.collectNodeMemoryMetrics(ch, statsSummary.Node)
rc.collectNodeSwapMetrics(ch, statsSummary.Node)
for _, pod := range statsSummary.Pods {
for _, container := range pod.Containers {
rc.collectContainerStartTime(ch, pod, container)
rc.collectContainerCPUMetrics(ch, pod, container)
rc.collectContainerMemoryMetrics(ch, pod, container)
rc.collectContainerSwapMetrics(ch, pod, container)
}
rc.collectPodCPUMetrics(ch, pod)
rc.collectPodMemoryMetrics(ch, pod)
rc.collectPodSwapMetrics(ch, pod)
}
}
@ -161,6 +188,15 @@ func (rc *resourceMetricsCollector) collectNodeMemoryMetrics(ch chan<- metrics.M
metrics.NewLazyConstMetric(nodeMemoryUsageDesc, metrics.GaugeValue, float64(*s.Memory.WorkingSetBytes)))
}
func (rc *resourceMetricsCollector) collectNodeSwapMetrics(ch chan<- metrics.Metric, s summary.NodeStats) {
if s.Swap == nil || s.Swap.SwapUsageBytes == nil {
return
}
ch <- metrics.NewLazyMetricWithTimestamp(s.Memory.Time.Time,
metrics.NewLazyConstMetric(nodeSwapUsageDesc, metrics.GaugeValue, float64(*s.Swap.SwapUsageBytes)))
}
func (rc *resourceMetricsCollector) collectContainerStartTime(ch chan<- metrics.Metric, pod summary.PodStats, s summary.ContainerStats) {
if s.StartTime.Unix() <= 0 {
return
@ -190,6 +226,16 @@ func (rc *resourceMetricsCollector) collectContainerMemoryMetrics(ch chan<- metr
float64(*s.Memory.WorkingSetBytes), s.Name, pod.PodRef.Name, pod.PodRef.Namespace))
}
func (rc *resourceMetricsCollector) collectContainerSwapMetrics(ch chan<- metrics.Metric, pod summary.PodStats, s summary.ContainerStats) {
if s.Swap == nil || s.Swap.SwapUsageBytes == nil {
return
}
ch <- metrics.NewLazyMetricWithTimestamp(s.Swap.Time.Time,
metrics.NewLazyConstMetric(containerSwapUsageDesc, metrics.GaugeValue,
float64(*s.Swap.SwapUsageBytes), s.Name, pod.PodRef.Name, pod.PodRef.Namespace))
}
func (rc *resourceMetricsCollector) collectPodCPUMetrics(ch chan<- metrics.Metric, pod summary.PodStats) {
if pod.CPU == nil || pod.CPU.UsageCoreNanoSeconds == nil {
return
@ -209,3 +255,13 @@ func (rc *resourceMetricsCollector) collectPodMemoryMetrics(ch chan<- metrics.Me
metrics.NewLazyConstMetric(podMemoryUsageDesc, metrics.GaugeValue,
float64(*pod.Memory.WorkingSetBytes), pod.PodRef.Name, pod.PodRef.Namespace))
}
func (rc *resourceMetricsCollector) collectPodSwapMetrics(ch chan<- metrics.Metric, pod summary.PodStats) {
if pod.Swap == nil || pod.Swap.SwapUsageBytes == nil {
return
}
ch <- metrics.NewLazyMetricWithTimestamp(pod.Swap.Time.Time,
metrics.NewLazyConstMetric(podSwapUsageDesc, metrics.GaugeValue,
float64(*pod.Swap.SwapUsageBytes), pod.PodRef.Name, pod.PodRef.Namespace))
}

View File

@ -38,11 +38,14 @@ func TestCollectResourceMetrics(t *testing.T) {
"scrape_error",
"node_cpu_usage_seconds_total",
"node_memory_working_set_bytes",
"node_swap_usage_bytes",
"container_cpu_usage_seconds_total",
"container_memory_working_set_bytes",
"container_swap_usage_bytes",
"container_start_time_seconds",
"pod_cpu_usage_seconds_total",
"pod_memory_working_set_bytes",
"pod_swap_usage_bytes",
}
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
@ -75,6 +78,10 @@ func TestCollectResourceMetrics(t *testing.T) {
Time: testTime,
WorkingSetBytes: uint64Ptr(1000),
},
Swap: &statsapi.SwapStats{
Time: testTime,
SwapUsageBytes: uint64Ptr(500),
},
},
},
summaryErr: nil,
@ -85,6 +92,9 @@ func TestCollectResourceMetrics(t *testing.T) {
# HELP node_memory_working_set_bytes [ALPHA] Current working set of the node in bytes
# TYPE node_memory_working_set_bytes gauge
node_memory_working_set_bytes 1000 1624396278302
# HELP node_swap_usage_bytes [ALPHA] Current swap usage of the node in bytes. Reported only on non-windows systems
# TYPE node_swap_usage_bytes gauge
node_swap_usage_bytes 500 1624396278302
# HELP scrape_error [ALPHA] 1 if there was an error while getting container metrics, 0 otherwise
# TYPE scrape_error gauge
scrape_error 0
@ -132,6 +142,10 @@ func TestCollectResourceMetrics(t *testing.T) {
Time: testTime,
WorkingSetBytes: uint64Ptr(1000),
},
Swap: &statsapi.SwapStats{
Time: testTime,
SwapUsageBytes: uint64Ptr(1000),
},
},
{
Name: "container_b",
@ -189,6 +203,9 @@ func TestCollectResourceMetrics(t *testing.T) {
container_start_time_seconds{container="container_a",namespace="namespace_a",pod="pod_a"} 1.6243962483020916e+09 1624396248302
container_start_time_seconds{container="container_a",namespace="namespace_b",pod="pod_b"} 1.6243956783020916e+09 1624395678302
container_start_time_seconds{container="container_b",namespace="namespace_a",pod="pod_a"} 1.6243961583020916e+09 1624396158302
# HELP container_swap_usage_bytes [ALPHA] Current amount of the container swap usage in bytes. Reported only on non-windows systems
# TYPE container_swap_usage_bytes gauge
container_swap_usage_bytes{container="container_a",namespace="namespace_a",pod="pod_a"} 1000 1624396278302
`,
},
{
@ -310,6 +327,10 @@ func TestCollectResourceMetrics(t *testing.T) {
Time: testTime,
WorkingSetBytes: uint64Ptr(1000),
},
Swap: &statsapi.SwapStats{
Time: testTime,
SwapUsageBytes: uint64Ptr(5000),
},
},
},
},
@ -324,6 +345,9 @@ func TestCollectResourceMetrics(t *testing.T) {
# HELP pod_memory_working_set_bytes [ALPHA] Current working set of the pod in bytes
# TYPE pod_memory_working_set_bytes gauge
pod_memory_working_set_bytes{namespace="namespace_a",pod="pod_a"} 1000 1624396278302
# HELP pod_swap_usage_bytes [ALPHA] Current amount of the pod swap usage in bytes. Reported only on non-windows systems
# TYPE pod_swap_usage_bytes gauge
pod_swap_usage_bytes{namespace="namespace_a",pod="pod_a"} 5000 1624396278302
`,
},
{

View File

@ -105,6 +105,7 @@ func (sp *summaryProviderImpl) Get(ctx context.Context, updateStats bool) (*stat
NodeName: node.Name,
CPU: rootStats.CPU,
Memory: rootStats.Memory,
Swap: rootStats.Swap,
Network: networkStats,
StartTime: sp.systemBootTime,
Fs: rootFsStats,
@ -141,6 +142,7 @@ func (sp *summaryProviderImpl) GetCPUAndMemoryStats(ctx context.Context) (*stats
NodeName: node.Name,
CPU: rootStats.CPU,
Memory: rootStats.Memory,
Swap: rootStats.Swap,
StartTime: rootStats.StartTime,
SystemContainers: sp.GetSystemContainersCPUAndMemoryStats(nodeConfig, podStats, false),
}

View File

@ -100,6 +100,7 @@ func TestSummaryProviderGetStats(t *testing.T) {
assert.Equal(summary.Node.StartTime, systemBootTime)
assert.Equal(summary.Node.CPU, cgroupStatsMap["/"].cs.CPU)
assert.Equal(summary.Node.Memory, cgroupStatsMap["/"].cs.Memory)
assert.Equal(summary.Node.Swap, cgroupStatsMap["/"].cs.Swap)
assert.Equal(summary.Node.Network, cgroupStatsMap["/"].ns)
assert.Equal(summary.Node.Fs, rootFsStats)
assert.Equal(summary.Node.Runtime, &statsapi.RuntimeStats{ImageFs: imageFsStats})
@ -112,6 +113,7 @@ func TestSummaryProviderGetStats(t *testing.T) {
Memory: cgroupStatsMap["/kubelet"].cs.Memory,
Accelerators: cgroupStatsMap["/kubelet"].cs.Accelerators,
UserDefinedMetrics: cgroupStatsMap["/kubelet"].cs.UserDefinedMetrics,
Swap: cgroupStatsMap["/kubelet"].cs.Swap,
})
assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{
Name: "misc",
@ -120,6 +122,7 @@ func TestSummaryProviderGetStats(t *testing.T) {
Memory: cgroupStatsMap["/misc"].cs.Memory,
Accelerators: cgroupStatsMap["/misc"].cs.Accelerators,
UserDefinedMetrics: cgroupStatsMap["/misc"].cs.UserDefinedMetrics,
Swap: cgroupStatsMap["/misc"].cs.Swap,
})
assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{
Name: "runtime",
@ -128,6 +131,7 @@ func TestSummaryProviderGetStats(t *testing.T) {
Memory: cgroupStatsMap["/runtime"].cs.Memory,
Accelerators: cgroupStatsMap["/runtime"].cs.Accelerators,
UserDefinedMetrics: cgroupStatsMap["/runtime"].cs.UserDefinedMetrics,
Swap: cgroupStatsMap["/runtime"].cs.Swap,
})
assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{
Name: "pods",
@ -136,6 +140,7 @@ func TestSummaryProviderGetStats(t *testing.T) {
Memory: cgroupStatsMap["/pods"].cs.Memory,
Accelerators: cgroupStatsMap["/pods"].cs.Accelerators,
UserDefinedMetrics: cgroupStatsMap["/pods"].cs.UserDefinedMetrics,
Swap: cgroupStatsMap["/pods"].cs.Swap,
})
assert.Equal(summary.Pods, podStats)
}

View File

@ -152,6 +152,7 @@ func (p *cadvisorStatsProvider) ListPodStats(_ context.Context) ([]statsapi.PodS
cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
podStats.CPU = cpu
podStats.Memory = memory
podStats.Swap = cadvisorInfoToSwapStats(podInfo)
podStats.ProcessStats = cadvisorInfoToProcessStats(podInfo)
}
@ -227,6 +228,7 @@ func (p *cadvisorStatsProvider) ListPodCPUAndMemoryStats(_ context.Context) ([]s
cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
podStats.CPU = cpu
podStats.Memory = memory
podStats.Swap = cadvisorInfoToSwapStats(podInfo)
}
result = append(result, *podStats)
}

View File

@ -294,11 +294,13 @@ func TestCadvisorListPodStats(t *testing.T) {
assert.EqualValues(t, testTime(creationTime, seedPod0Container0).Unix(), con.StartTime.Time.Unix())
checkCPUStats(t, "Pod0Container0", seedPod0Container0, con.CPU)
checkMemoryStats(t, "Pod0Conainer0", seedPod0Container0, infos["/pod0-c0"], con.Memory)
checkSwapStats(t, "Pod0Conainer0", seedPod0Container0, infos["/pod0-c0"], con.Swap)
con = indexCon[cName01]
assert.EqualValues(t, testTime(creationTime, seedPod0Container1).Unix(), con.StartTime.Time.Unix())
checkCPUStats(t, "Pod0Container1", seedPod0Container1, con.CPU)
checkMemoryStats(t, "Pod0Container1", seedPod0Container1, infos["/pod0-c1"], con.Memory)
checkSwapStats(t, "Pod0Container1", seedPod0Container1, infos["/pod0-c1"], con.Swap)
assert.EqualValues(t, p0Time.Unix(), ps.StartTime.Time.Unix())
checkNetworkStats(t, "Pod0", seedPod0Infra, ps.Network)
@ -309,6 +311,9 @@ func TestCadvisorListPodStats(t *testing.T) {
if ps.Memory != nil {
checkMemoryStats(t, "Pod0", seedPod0Infra, infos["/pod0-i"], ps.Memory)
}
if ps.Swap != nil {
checkSwapStats(t, "Pod0", seedPod0Infra, infos["/pod0-i"], ps.Swap)
}
// Validate Pod1 Results
ps, found = indexPods[prf1]
@ -318,6 +323,7 @@ func TestCadvisorListPodStats(t *testing.T) {
assert.Equal(t, cName10, con.Name)
checkCPUStats(t, "Pod1Container0", seedPod1Container, con.CPU)
checkMemoryStats(t, "Pod1Container0", seedPod1Container, infos["/pod1-c0"], con.Memory)
checkSwapStats(t, "Pod1Container0", seedPod1Container, infos["/pod1-c0"], con.Swap)
checkNetworkStats(t, "Pod1", seedPod1Infra, ps.Network)
// Validate Pod2 Results
@ -328,6 +334,7 @@ func TestCadvisorListPodStats(t *testing.T) {
assert.Equal(t, cName20, con.Name)
checkCPUStats(t, "Pod2Container0", seedPod2Container, con.CPU)
checkMemoryStats(t, "Pod2Container0", seedPod2Container, infos["/pod2-c0"], con.Memory)
checkSwapStats(t, "Pod2Container0", seedPod2Container, infos["/pod2-c0"], con.Swap)
checkNetworkStats(t, "Pod2", seedPod2Infra, ps.Network)
// Validate Pod3 Results
@ -344,6 +351,7 @@ func TestCadvisorListPodStats(t *testing.T) {
assert.Equal(t, cName31, con.Name)
checkCPUStats(t, "Pod3Container1", seedPod3Container1, con.CPU)
checkMemoryStats(t, "Pod3Container1", seedPod3Container1, infos["/pod3-c1"], con.Memory)
checkSwapStats(t, "Pod3Container1", seedPod3Container1, infos["/pod3-c1"], con.Swap)
}
func TestCadvisorListPodCPUAndMemoryStats(t *testing.T) {

View File

@ -206,6 +206,7 @@ func (p *criStatsProvider) listPodStatsPartiallyFromCRI(ctx context.Context, upd
cs := p.makeContainerStats(stats, container, rootFsInfo, fsIDtoInfo, podSandbox.GetMetadata(), updateCPUNanoCoreUsage)
p.addPodNetworkStats(ps, podSandboxID, caInfos, cs, containerNetworkStats[podSandboxID])
p.addPodCPUMemoryStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
p.addSwapStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
p.addProcessStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
// If cadvisor stats is available for the container, use it to populate
@ -548,6 +549,31 @@ func (p *criStatsProvider) addPodCPUMemoryStats(
}
}
func (p *criStatsProvider) addSwapStats(
ps *statsapi.PodStats,
podUID types.UID,
allInfos map[string]cadvisorapiv2.ContainerInfo,
cs *statsapi.ContainerStats,
) {
// try get cpu and memory stats from cadvisor first.
podCgroupInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
if podCgroupInfo != nil {
ps.Swap = cadvisorInfoToSwapStats(podCgroupInfo)
return
}
// Sum Pod cpu and memory stats from containers stats.
if cs.Swap != nil {
if ps.Swap == nil {
ps.Swap = &statsapi.SwapStats{Time: cs.Swap.Time}
}
swapAvailableBytes := getUint64Value(cs.Swap.SwapAvailableBytes) + getUint64Value(ps.Swap.SwapAvailableBytes)
swapUsageBytes := getUint64Value(cs.Swap.SwapUsageBytes) + getUint64Value(ps.Swap.SwapUsageBytes)
ps.Swap.SwapAvailableBytes = &swapAvailableBytes
ps.Swap.SwapUsageBytes = &swapUsageBytes
}
}
func (p *criStatsProvider) addProcessStats(
ps *statsapi.PodStats,
podUID types.UID,
@ -577,6 +603,7 @@ func (p *criStatsProvider) makeContainerStats(
CPU: &statsapi.CPUStats{},
Memory: &statsapi.MemoryStats{},
Rootfs: &statsapi.FsStats{},
Swap: &statsapi.SwapStats{},
// UserDefinedMetrics is not supported by CRI.
}
if stats.Cpu != nil {
@ -607,6 +634,19 @@ func (p *criStatsProvider) makeContainerStats(
result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
result.Memory.WorkingSetBytes = uint64Ptr(0)
}
if stats.Swap != nil {
result.Swap.Time = metav1.NewTime(time.Unix(0, stats.Swap.Timestamp))
if stats.Swap.SwapUsageBytes != nil {
result.Swap.SwapUsageBytes = &stats.Swap.SwapUsageBytes.Value
}
if stats.Swap.SwapAvailableBytes != nil {
result.Swap.SwapAvailableBytes = &stats.Swap.SwapAvailableBytes.Value
}
} else {
result.Swap.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
result.Swap.SwapUsageBytes = uint64Ptr(0)
result.Swap.SwapAvailableBytes = uint64Ptr(0)
}
if stats.WritableLayer != nil {
result.Rootfs.Time = metav1.NewTime(time.Unix(0, stats.WritableLayer.Timestamp))
if stats.WritableLayer.UsedBytes != nil {

View File

@ -282,6 +282,7 @@ func TestCRIListPodStats(t *testing.T) {
checkCRILogsStats(assert, c1, &rootFsInfo, containerLogStats1)
checkCRINetworkStats(assert, p0.Network, infos[sandbox0.PodSandboxStatus.Id].Stats[0].Network)
checkCRIPodCPUAndMemoryStats(assert, p0, infos[sandbox0Cgroup].Stats[0])
checkCRIPodSwapStats(assert, p0, infos[sandbox0Cgroup].Stats[0])
p1 := podStatsMap[statsapi.PodReference{Name: "sandbox1-name", UID: "sandbox1-uid", Namespace: "sandbox1-ns"}]
assert.Equal(sandbox1.CreatedAt, p1.StartTime.UnixNano())
@ -298,6 +299,7 @@ func TestCRIListPodStats(t *testing.T) {
checkCRILogsStats(assert, c2, &rootFsInfo, containerLogStats2)
checkCRINetworkStats(assert, p1.Network, infos[sandbox1.PodSandboxStatus.Id].Stats[0].Network)
checkCRIPodCPUAndMemoryStats(assert, p1, infos[sandbox1Cgroup].Stats[0])
checkCRIPodSwapStats(assert, p1, infos[sandbox1Cgroup].Stats[0])
p2 := podStatsMap[statsapi.PodReference{Name: "sandbox2-name", UID: "sandbox2-uid", Namespace: "sandbox2-ns"}]
assert.Equal(sandbox2.CreatedAt, p2.StartTime.UnixNano())
@ -316,6 +318,7 @@ func TestCRIListPodStats(t *testing.T) {
checkCRILogsStats(assert, c3, &rootFsInfo, containerLogStats4)
checkCRINetworkStats(assert, p2.Network, infos[sandbox2.PodSandboxStatus.Id].Stats[0].Network)
checkCRIPodCPUAndMemoryStats(assert, p2, infos[sandbox2Cgroup].Stats[0])
checkCRIPodSwapStats(assert, p2, infos[sandbox2Cgroup].Stats[0])
p3 := podStatsMap[statsapi.PodReference{Name: "sandbox3-name", UID: "sandbox3-uid", Namespace: "sandbox3-ns"}]
assert.Equal(sandbox3.CreatedAt, p3.StartTime.UnixNano())
@ -327,6 +330,7 @@ func TestCRIListPodStats(t *testing.T) {
assert.NotNil(c8.CPU.Time)
assert.NotNil(c8.Memory.Time)
checkCRIPodCPUAndMemoryStats(assert, p3, infos[sandbox3Cgroup].Stats[0])
checkCRIPodSwapStats(assert, p3, infos[sandbox3Cgroup].Stats[0])
}
func TestListPodStatsStrictlyFromCRI(t *testing.T) {
@ -1074,6 +1078,15 @@ func checkCRIPodCPUAndMemoryStats(assert *assert.Assertions, actual statsapi.Pod
assert.Equal(cs.Memory.ContainerData.Pgmajfault, *actual.Memory.MajorPageFaults)
}
func checkCRIPodSwapStats(assert *assert.Assertions, actual statsapi.PodStats, cs *cadvisorapiv2.ContainerStats) {
if runtime.GOOS != "linux" {
return
}
assert.Equal(cs.Timestamp.UnixNano(), actual.Swap.Time.UnixNano())
assert.Equal(cs.Memory.Swap, *actual.Swap.SwapUsageBytes)
}
func checkCRIPodCPUAndMemoryStatsStrictlyFromCRI(assert *assert.Assertions, actual statsapi.PodStats, excepted statsapi.PodStats) {
if runtime.GOOS != "linux" {
return

View File

@ -95,6 +95,7 @@ func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo
cpu, memory := cadvisorInfoToCPUandMemoryStats(info)
result.CPU = cpu
result.Memory = memory
result.Swap = cadvisorInfoToSwapStats(info)
// NOTE: if they can be found, log stats will be overwritten
// by the caller, as it knows more information about the pod,
@ -257,6 +258,29 @@ func cadvisorInfoToUserDefinedMetrics(info *cadvisorapiv2.ContainerInfo) []stats
return udm
}
func cadvisorInfoToSwapStats(info *cadvisorapiv2.ContainerInfo) *statsapi.SwapStats {
cstat, found := latestContainerStats(info)
if !found {
return nil
}
var swapStats *statsapi.SwapStats
if info.Spec.HasMemory && cstat.Memory != nil {
swapStats = &statsapi.SwapStats{
Time: metav1.NewTime(cstat.Timestamp),
SwapUsageBytes: &cstat.Memory.Swap,
}
if !isMemoryUnlimited(info.Spec.Memory.SwapLimit) {
swapAvailableBytes := info.Spec.Memory.SwapLimit - cstat.Memory.Swap
swapStats.SwapAvailableBytes = &swapAvailableBytes
}
}
return swapStats
}
// latestContainerStats returns the latest container stats from cadvisor, or nil if none exist
func latestContainerStats(info *cadvisorapiv2.ContainerInfo) (*cadvisorapiv2.ContainerStats, bool) {
stats := info.Stats

View File

@ -63,6 +63,7 @@ const (
offsetFsBaseUsageBytes
offsetFsInodeUsage
offsetAcceleratorDutyCycle
offsetMemSwapUsageBytes
)
var (
@ -101,6 +102,7 @@ func TestGetCgroupStats(t *testing.T) {
checkCPUStats(t, "", containerInfoSeed, cs.CPU)
checkMemoryStats(t, "", containerInfoSeed, containerInfo, cs.Memory)
checkNetworkStats(t, "", containerInfoSeed, ns)
checkSwapStats(t, "", containerInfoSeed, containerInfo, cs.Swap)
assert.Equal(cgroupName, cs.Name)
assert.Equal(metav1.NewTime(containerInfo.Spec.CreationTime), cs.StartTime)
@ -497,7 +499,8 @@ func getTestContainerInfo(seed int, podName string, podNamespace string, contain
HasNetwork: true,
Labels: labels,
Memory: cadvisorapiv2.MemorySpec{
Limit: unlimitedMemory,
Limit: unlimitedMemory,
SwapLimit: unlimitedMemory,
},
CustomMetrics: generateCustomMetricSpec(),
}
@ -518,6 +521,7 @@ func getTestContainerInfo(seed int, podName string, podNamespace string, contain
Pgfault: uint64(seed + offsetMemPageFaults),
Pgmajfault: uint64(seed + offsetMemMajorPageFaults),
},
Swap: uint64(seed + offsetMemSwapUsageBytes),
},
Network: &cadvisorapiv2.NetworkStats{
Interfaces: []cadvisorapiv1.InterfaceStats{{
@ -696,6 +700,20 @@ func checkMemoryStats(t *testing.T, label string, seed int, info cadvisorapiv2.C
}
}
func checkSwapStats(t *testing.T, label string, seed int, info cadvisorapiv2.ContainerInfo, stats *statsapi.SwapStats) {
label += ".Swap"
assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Time")
assert.EqualValues(t, seed+offsetMemSwapUsageBytes, *stats.SwapUsageBytes, label+".SwapUsageBytes")
if !info.Spec.HasMemory || isMemoryUnlimited(info.Spec.Memory.SwapLimit) {
assert.Nil(t, stats.SwapAvailableBytes, label+".SwapAvailableBytes")
} else {
expected := info.Spec.Memory.Limit - *stats.SwapUsageBytes
assert.EqualValues(t, expected, *stats.SwapAvailableBytes, label+".AvailableBytes")
}
}
func checkFsStats(t *testing.T, label string, seed int, stats *statsapi.FsStats) {
assert.EqualValues(t, seed+offsetFsCapacity, *stats.CapacityBytes, label+".CapacityBytes")
assert.EqualValues(t, seed+offsetFsAvailable, *stats.AvailableBytes, label+".AvailableBytes")

File diff suppressed because it is too large Load Diff

View File

@ -1639,6 +1639,8 @@ message ContainerStats {
MemoryUsage memory = 3;
// Usage of the writable layer.
FilesystemUsage writable_layer = 4;
// Swap usage gathered from the container.
SwapUsage swap = 5;
}
// WindowsContainerStats provides the resource usage statistics for a container specific for Windows
@ -1693,6 +1695,15 @@ message MemoryUsage {
UInt64Value major_page_faults = 7;
}
message SwapUsage {
// Timestamp in nanoseconds at which the information were collected. Must be > 0.
int64 timestamp = 1;
// Available swap for use. This is defined as the swap limit - swapUsageBytes.
UInt64Value swap_available_bytes = 2;
// Total memory in use. This includes all memory regardless of when it was accessed.
UInt64Value swap_usage_bytes = 3;
}
// WindowsMemoryUsage provides the memory usage information specific to Windows
message WindowsMemoryUsage {
// Timestamp in nanoseconds at which the information were collected. Must be > 0.

View File

@ -59,6 +59,9 @@ type NodeStats struct {
// Stats about the rlimit of system.
// +optional
Rlimit *RlimitStats `json:"rlimit,omitempty"`
// Stats pertaining to swap resources. This is reported to non-windows systems only.
// +optional
Swap *SwapStats `json:"swap,omitempty"`
}
// RlimitStats are stats rlimit of OS.
@ -131,6 +134,9 @@ type PodStats struct {
// ProcessStats pertaining to processes.
// +optional
ProcessStats *ProcessStats `json:"process_stats,omitempty"`
// Stats pertaining to swap resources. This is reported to non-windows systems only.
// +optional
Swap *SwapStats `json:"swap,omitempty"`
}
// ContainerStats holds container-level unprocessed sample stats.
@ -159,6 +165,9 @@ type ContainerStats struct {
// +patchMergeKey=name
// +patchStrategy=merge
UserDefinedMetrics []UserDefinedMetric `json:"userDefinedMetrics,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
// Stats pertaining to swap resources. This is reported to non-windows systems only.
// +optional
Swap *SwapStats `json:"swap,omitempty"`
}
// PodReference contains enough information to locate the referenced pod.
@ -237,6 +246,19 @@ type MemoryStats struct {
MajorPageFaults *uint64 `json:"majorPageFaults,omitempty"`
}
// SwapStats contains data about memory usage
type SwapStats struct {
// The time at which these stats were updated.
Time metav1.Time `json:"time"`
// Available swap memory for use. This is defined as the <swap-limit> - <current-swap-usage>.
// If swap limit is undefined, this value is omitted.
// +optional
SwapAvailableBytes *uint64 `json:"swapAvailableBytes,omitempty"`
// Total swap memory in use.
// +optional
SwapUsageBytes *uint64 `json:"swapUsageBytes,omitempty"`
}
// AcceleratorStats contains stats for accelerators attached to the container.
type AcceleratorStats struct {
// Make of the accelerator (nvidia, amd, google etc.)

View File

@ -112,6 +112,7 @@ var _ = SIGDescribe("Summary API [NodeConformance]", func() {
"PageFaults": bounded(1000, 1e9),
"MajorPageFaults": bounded(0, 100000),
}),
"Swap": swapExpectation(memoryLimit),
"Accelerators": gomega.BeEmpty(),
"Rootfs": gomega.BeNil(),
"Logs": gomega.BeNil(),
@ -183,6 +184,7 @@ var _ = SIGDescribe("Summary API [NodeConformance]", func() {
"PageFaults": bounded(100, expectedPageFaultsUpperBound),
"MajorPageFaults": bounded(0, expectedMajorPageFaultsUpperBound),
}),
"Swap": swapExpectation(memoryLimit),
"Accelerators": gomega.BeEmpty(),
"Rootfs": ptrMatchAllFields(gstruct.Fields{
"Time": recent(maxStatsAge),
@ -230,6 +232,7 @@ var _ = SIGDescribe("Summary API [NodeConformance]", func() {
"PageFaults": bounded(0, expectedPageFaultsUpperBound),
"MajorPageFaults": bounded(0, expectedMajorPageFaultsUpperBound),
}),
"Swap": swapExpectation(memoryLimit),
"VolumeStats": gstruct.MatchAllElements(summaryObjectID, gstruct.Elements{
"test-empty-dir": gstruct.MatchAllFields(gstruct.Fields{
"Name": gomega.Equal("test-empty-dir"),
@ -280,6 +283,7 @@ var _ = SIGDescribe("Summary API [NodeConformance]", func() {
"PageFaults": bounded(1000, 1e9),
"MajorPageFaults": bounded(0, 100000),
}),
"Swap": swapExpectation(memoryLimit),
// TODO(#28407): Handle non-eth0 network interface names.
"Network": ptrMatchAllFields(gstruct.Fields{
"Time": recent(maxStatsAge),
@ -410,6 +414,27 @@ func bounded(lower, upper interface{}) types.GomegaMatcher {
gomega.BeNumerically("<=", upper)))
}
func swapExpectation(upper interface{}) types.GomegaMatcher {
// Size after which we consider memory to be "unlimited". This is not
// MaxInt64 due to rounding by the kernel.
const maxMemorySize = uint64(1 << 62)
swapBytesMatcher := gomega.Or(
gomega.BeNil(),
bounded(0, upper),
gstruct.PointTo(gomega.BeNumerically(">=", maxMemorySize)),
)
return gomega.Or(
gomega.BeNil(),
ptrMatchAllFields(gstruct.Fields{
"Time": recent(maxStatsAge),
"SwapUsageBytes": swapBytesMatcher,
"SwapAvailableBytes": swapBytesMatcher,
}),
)
}
func recent(d time.Duration) types.GomegaMatcher {
return gomega.WithTransform(func(t metav1.Time) time.Time {
return t.Time