Skip to content

Commit 9a22e3e

Browse files
committed
container/libcontainer: fix memory hog and stats
The logic of the existing code of schedulerStatsFromProcs is to provide a cumulative stats for all the processes inside a container. Once the process is dead, its stat entry is no longer updated, but still used in totals calculation. This creates two problems: - pidsMetricsCache map is ever growing -- in case of many short-lived processes in containers this can impact kubelet memory usage a lot; - in case a new process with the same PID appears (as a result of PID reuse), the stats from the old one are overwritten, resulting in wrong totals (e.g. they can be less than previous, which should not ever be the case). To kill these two birds with one stone, let's accumulate stats from dead processes in pidsMetricsSaved, and remove them from the pidsMetricsCache. Signed-off-by: Kir Kolyshkin <[email protected]>
1 parent 2b607f0 commit 9a22e3e

File tree

1 file changed

+16
-2
lines changed

1 file changed

+16
-2
lines changed

container/libcontainer/handler.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ type Handler struct {
5454
rootFs string
5555
pid int
5656
includedMetrics container.MetricSet
57+
// pidMetricsCache holds CPU scheduler stats for existing processes (map key is PID) between calls to schedulerStatsFromProcs.
5758
pidMetricsCache map[int]*info.CpuSchedstat
59+
// pidMetricsSaved holds accumulated CPU scheduler stats for processes that no longer exist.
60+
pidMetricsSaved info.CpuSchedstat
5861
cycles uint64
5962
}
6063

@@ -314,6 +317,7 @@ func (h *Handler) schedulerStatsFromProcs() (info.CpuSchedstat, error) {
314317
if err != nil {
315318
return info.CpuSchedstat{}, fmt.Errorf("Could not get PIDs for container %d: %w", h.pid, err)
316319
}
320+
alivePids := make(map[int]struct{}, len(pids))
317321
for _, pid := range pids {
318322
f, err := os.Open(path.Join(h.rootFs, "proc", strconv.Itoa(pid), "schedstat"))
319323
if err != nil {
@@ -324,6 +328,7 @@ func (h *Handler) schedulerStatsFromProcs() (info.CpuSchedstat, error) {
324328
if err != nil {
325329
return info.CpuSchedstat{}, fmt.Errorf("couldn't read scheduler statistics for process %d: %v", pid, err)
326330
}
331+
alivePids[pid] = struct{}{}
327332
rawMetrics := bytes.Split(bytes.TrimRight(contents, "\n"), []byte(" "))
328333
if len(rawMetrics) != 3 {
329334
return info.CpuSchedstat{}, fmt.Errorf("unexpected number of metrics in schedstat file for process %d", pid)
@@ -348,11 +353,20 @@ func (h *Handler) schedulerStatsFromProcs() (info.CpuSchedstat, error) {
348353
}
349354
}
350355
}
351-
schedstats := info.CpuSchedstat{}
352-
for _, v := range h.pidMetricsCache {
356+
schedstats := h.pidMetricsSaved // copy
357+
for p, v := range h.pidMetricsCache {
353358
schedstats.RunPeriods += v.RunPeriods
354359
schedstats.RunqueueTime += v.RunqueueTime
355360
schedstats.RunTime += v.RunTime
361+
if _, alive := alivePids[p]; !alive {
362+
// PID p is gone: accumulate its stats ...
363+
h.pidMetricsSaved.RunPeriods += v.RunPeriods
364+
h.pidMetricsSaved.RunqueueTime += v.RunqueueTime
365+
h.pidMetricsSaved.RunTime += v.RunTime
366+
// ... and remove its cache entry, to prevent
367+
// pidMetricsCache from growing.
368+
delete(h.pidMetricsCache, p)
369+
}
356370
}
357371
return schedstats, nil
358372
}

0 commit comments

Comments
 (0)