Skip to content

Commit 89dfebb

Browse files
authored
Merge pull request kubernetes#89359 from gongguan/process
eviction by process number
2 parents 0c9ba6b + e56d40d commit 89dfebb

File tree

9 files changed

+113
-7
lines changed

9 files changed

+113
-7
lines changed

pkg/kubelet/apis/stats/v1alpha1/types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ const (
9191
SystemContainerPods = "pods"
9292
)
9393

94+
// ProcessStats are stats pertaining to processes.
95+
type ProcessStats struct {
96+
// Number of processes
97+
// +optional
98+
ProcessCount *uint64 `json:"process_count,omitempty"`
99+
}
100+
94101
// PodStats holds pod-level unprocessed sample stats.
95102
type PodStats struct {
96103
// Reference to the measured Pod.
@@ -119,6 +126,9 @@ type PodStats struct {
119126
// EphemeralStorage reports the total filesystem usage for the containers and emptyDir-backed volumes in the measured Pod.
120127
// +optional
121128
EphemeralStorage *FsStats `json:"ephemeral-storage,omitempty"`
129+
// ProcessStats pertaining to processes.
130+
// +optional
131+
ProcessStats *ProcessStats `json:"process_stats,omitempty"`
122132
}
123133

124134
// ContainerStats holds container-level unprocessed sample stats.

pkg/kubelet/eviction/helpers.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,15 @@ func memoryUsage(memStats *statsapi.MemoryStats) *resource.Quantity {
328328
return resource.NewQuantity(usage, resource.BinarySI)
329329
}
330330

331+
// processUsage converts working set into a process count.
332+
func processUsage(processStats *statsapi.ProcessStats) uint64 {
333+
if processStats == nil || processStats.ProcessCount == nil {
334+
return 0
335+
}
336+
usage := uint64(*processStats.ProcessCount)
337+
return usage
338+
}
339+
331340
// localVolumeNames returns the set of volumes for the pod that are local
332341
// TODO: summary API should report what volumes consume local storage rather than hard-code here.
333342
func localVolumeNames(pod *v1.Pod) []string {
@@ -566,6 +575,23 @@ func memory(stats statsFunc) cmpFunc {
566575
}
567576
}
568577

578+
// process compares pods by largest consumer of process number relative to request.
579+
func process(stats statsFunc) cmpFunc {
580+
return func(p1, p2 *v1.Pod) int {
581+
p1Stats, p1Found := stats(p1)
582+
p2Stats, p2Found := stats(p2)
583+
if !p1Found || !p2Found {
584+
// prioritize evicting the pod for which no stats were found
585+
return cmpBool(!p1Found, !p2Found)
586+
}
587+
588+
p1Process := processUsage(p1Stats.ProcessStats)
589+
p2Process := processUsage(p2Stats.ProcessStats)
590+
// prioritize evicting the pod which has the larger consumption of process
591+
return int(p2Process - p1Process)
592+
}
593+
}
594+
569595
// exceedDiskRequests compares whether or not pods' disk usage exceeds their requests
570596
func exceedDiskRequests(stats statsFunc, fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) cmpFunc {
571597
return func(p1, p2 *v1.Pod) int {
@@ -640,7 +666,7 @@ func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) {
640666

641667
// rankPIDPressure orders the input pods by priority in response to PID pressure.
642668
func rankPIDPressure(pods []*v1.Pod, stats statsFunc) {
643-
orderedBy(priority).Sort(pods)
669+
orderedBy(priority, process(stats)).Sort(pods)
644670
}
645671

646672
// rankDiskPressureFunc returns a rankFunc that measures the specified fs stats.

pkg/kubelet/eviction/helpers_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,32 @@ func TestOrderedByPriorityMemory(t *testing.T) {
958958
}
959959
}
960960

961+
// TestOrderedByPriorityProcess ensures we order by priority and then process consumption relative to request.
962+
func TestOrderedByPriorityProcess(t *testing.T) {
963+
pod1 := newPod("low-priority-high-usage", lowPriority, nil, nil)
964+
pod2 := newPod("low-priority-low-usage", lowPriority, nil, nil)
965+
pod3 := newPod("high-priority-high-usage", highPriority, nil, nil)
966+
pod4 := newPod("high-priority-low-usage", highPriority, nil, nil)
967+
stats := map[*v1.Pod]statsapi.PodStats{
968+
pod1: newPodProcessStats(pod1, 20),
969+
pod2: newPodProcessStats(pod2, 6),
970+
pod3: newPodProcessStats(pod3, 20),
971+
pod4: newPodProcessStats(pod4, 5),
972+
}
973+
statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
974+
result, found := stats[pod]
975+
return result, found
976+
}
977+
pods := []*v1.Pod{pod4, pod3, pod2, pod1}
978+
expected := []*v1.Pod{pod1, pod2, pod3, pod4}
979+
orderedBy(priority, process(statsFn)).Sort(pods)
980+
for i := range expected {
981+
if pods[i] != expected[i] {
982+
t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
983+
}
984+
}
985+
}
986+
961987
func TestSortByEvictionPriority(t *testing.T) {
962988
for _, tc := range []struct {
963989
name string
@@ -1884,6 +1910,17 @@ func newPodMemoryStats(pod *v1.Pod, workingSet resource.Quantity) statsapi.PodSt
18841910
}
18851911
}
18861912

1913+
func newPodProcessStats(pod *v1.Pod, num uint64) statsapi.PodStats {
1914+
return statsapi.PodStats{
1915+
PodRef: statsapi.PodReference{
1916+
Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
1917+
},
1918+
ProcessStats: &statsapi.ProcessStats{
1919+
ProcessCount: &num,
1920+
},
1921+
}
1922+
}
1923+
18871924
func newResourceList(cpu, memory, disk string) v1.ResourceList {
18881925
res := v1.ResourceList{}
18891926
if cpu != "" {

pkg/kubelet/stats/cadvisor_stats_provider.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
120120
if containerName == leaky.PodInfraContainerName {
121121
// Special case for infrastructure container which is hidden from
122122
// the user and has network stats.
123-
podStats.Network = cadvisorInfoToNetworkStats("pod:"+ref.Namespace+"_"+ref.Name, &cinfo)
123+
podStats.Network = cadvisorInfoToNetworkStats(&cinfo)
124124
} else {
125125
podStats.Containers = append(podStats.Containers, *cadvisorInfoToContainerStats(containerName, &cinfo, &rootFsInfo, &imageFsInfo))
126126
}
@@ -144,6 +144,7 @@ func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
144144
cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
145145
podStats.CPU = cpu
146146
podStats.Memory = memory
147+
podStats.ProcessStats = cadvisorInfoToProcessStats(podInfo)
147148
}
148149

149150
status, found := p.statusProvider.GetPodStatus(podUID)

pkg/kubelet/stats/cri_stats_provider.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ func (p *criStatsProvider) listPodStats(updateCPUNanoCoreUsage bool) ([]statsapi
199199
cs := p.makeContainerStats(stats, container, &rootFsInfo, fsIDtoInfo, podSandbox.GetMetadata(), updateCPUNanoCoreUsage)
200200
p.addPodNetworkStats(ps, podSandboxID, caInfos, cs, containerNetworkStats[podSandboxID])
201201
p.addPodCPUMemoryStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
202+
p.addProcessStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
202203

203204
// If cadvisor stats is available for the container, use it to populate
204205
// container stats
@@ -425,7 +426,7 @@ func (p *criStatsProvider) addPodNetworkStats(
425426
caPodSandbox, found := caInfos[podSandboxID]
426427
// try get network stats from cadvisor first.
427428
if found {
428-
networkStats := cadvisorInfoToNetworkStats(ps.PodRef.Name, &caPodSandbox)
429+
networkStats := cadvisorInfoToNetworkStats(&caPodSandbox)
429430
if networkStats != nil {
430431
ps.Network = networkStats
431432
return
@@ -491,6 +492,20 @@ func (p *criStatsProvider) addPodCPUMemoryStats(
491492
}
492493
}
493494

495+
func (p *criStatsProvider) addProcessStats(
496+
ps *statsapi.PodStats,
497+
podUID types.UID,
498+
allInfos map[string]cadvisorapiv2.ContainerInfo,
499+
cs *statsapi.ContainerStats,
500+
) {
501+
// try get process stats from cadvisor only.
502+
info := getCadvisorPodInfoFromPodUID(podUID, allInfos)
503+
if info != nil {
504+
ps.ProcessStats = cadvisorInfoToProcessStats(info)
505+
return
506+
}
507+
}
508+
494509
func (p *criStatsProvider) makeContainerStats(
495510
stats *runtimeapi.ContainerStats,
496511
container *runtimeapi.Container,

pkg/kubelet/stats/helper.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,18 @@ func cadvisorInfoToContainerCPUAndMemoryStats(name string, info *cadvisorapiv2.C
153153
return result
154154
}
155155

156+
func cadvisorInfoToProcessStats(info *cadvisorapiv2.ContainerInfo) *statsapi.ProcessStats {
157+
cstat, found := latestContainerStats(info)
158+
if !found || cstat.Processes == nil {
159+
return nil
160+
}
161+
num := cstat.Processes.ProcessCount
162+
return &statsapi.ProcessStats{ProcessCount: uint64Ptr(num)}
163+
}
164+
156165
// cadvisorInfoToNetworkStats returns the statsapi.NetworkStats converted from
157166
// the container info from cadvisor.
158-
func cadvisorInfoToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo) *statsapi.NetworkStats {
167+
func cadvisorInfoToNetworkStats(info *cadvisorapiv2.ContainerInfo) *statsapi.NetworkStats {
159168
if !info.Spec.HasNetwork {
160169
return nil
161170
}

pkg/kubelet/stats/stats_provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (p *StatsProvider) GetCgroupStats(cgroupName string, updateStats bool) (*st
114114
}
115115
// Rootfs and imagefs doesn't make sense for raw cgroup.
116116
s := cadvisorInfoToContainerStats(cgroupName, info, nil, nil)
117-
n := cadvisorInfoToNetworkStats(cgroupName, info)
117+
n := cadvisorInfoToNetworkStats(info)
118118
return s, n, nil
119119
}
120120

test/e2e_node/eviction_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,15 +422,20 @@ var _ = framework.KubeDescribe("PriorityPidEvictionOrdering [Slow] [Serial] [Dis
422422
})
423423
specs := []podEvictSpec{
424424
{
425-
evictionPriority: 1,
426-
pod: pidConsumingPod("fork-bomb-container", 12000),
425+
evictionPriority: 2,
426+
pod: pidConsumingPod("fork-bomb-container-with-low-priority", 12000),
427427
},
428428
{
429429
evictionPriority: 0,
430430
pod: innocentPod(),
431431
},
432+
{
433+
evictionPriority: 1,
434+
pod: pidConsumingPod("fork-bomb-container-with-high-priority", 12000),
435+
},
432436
}
433437
specs[1].pod.Spec.PriorityClassName = highPriorityClassName
438+
specs[2].pod.Spec.PriorityClassName = highPriorityClassName
434439
runEvictionTest(f, pressureTimeout, expectedNodeCondition, expectedStarvedResource, logPidMetrics, specs)
435440
})
436441
})

test/e2e_node/summary_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ var _ = framework.KubeDescribe("Summary API [NodeConformance]", func() {
260260
"Inodes": bounded(1e4, 1e8),
261261
"InodesUsed": bounded(0, 1e8),
262262
}),
263+
"ProcessStats": ptrMatchAllFields(gstruct.Fields{
264+
"ProcessCount": bounded(0, 1e8),
265+
}),
263266
})
264267

265268
matchExpectations := ptrMatchAllFields(gstruct.Fields{

0 commit comments

Comments
 (0)