mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-17 15:50:10 +00:00
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:
commit
b4d793c450
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -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.
|
||||
|
@ -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.)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user