Skip to content

Commit 4c90b90

Browse files
committed
container/libcontainer: Improve limits file parsing perf
Only max open files is ever parsed from limits files, therefore this change optimizes for that case. Benchmark: ``` $ benchstat old.txt new.txt goos: linux goarch: amd64 pkg: github.com/google/cadvisor/container/libcontainer cpu: AMD Ryzen 5 3400GE with Radeon Vega Graphics │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ ProcessLimitsFile-8 85.012µ ± 1% 1.324µ ± 0% -98.44% (p=0.000 n=10) ``` On a GKE v1.27.4 production cluster, this code path used roughly 1.5% of the total kubelet CPU usage, and at 98.44% improvement this likely results in at least a 1.5% CPU reduction (perhaps even more since also less garbage is produced to be collected by the GC).
1 parent f76ba48 commit 4c90b90

File tree

2 files changed

+46
-35
lines changed

2 files changed

+46
-35
lines changed

container/libcontainer/handler.go

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import (
3939
)
4040

4141
var (
42-
whitelistedUlimits = [...]string{"max_open_files"}
4342
referencedResetInterval = flag.Uint64("referenced_reset_interval", 0,
4443
"Reset interval for referenced bytes (container_referenced_bytes metric), number of measurement cycles after which referenced bytes are cleared, if set to 0 referenced bytes are never cleared (default: 0)")
4544

@@ -205,51 +204,53 @@ func parseUlimit(value string) (int64, error) {
205204
return num, nil
206205
}
207206

208-
func isUlimitWhitelisted(name string) bool {
209-
for _, whitelist := range whitelistedUlimits {
210-
if name == whitelist {
211-
return true
212-
}
213-
}
214-
return false
215-
}
216-
217207
func processLimitsFile(fileData string) []info.UlimitSpec {
208+
const maxOpenFilesLinePrefix = "Max open files"
209+
218210
limits := strings.Split(fileData, "\n")
219211
ulimits := make([]info.UlimitSpec, 0, len(limits))
220212
for _, lim := range limits {
221213
// Skip any headers/footers
222-
if strings.HasPrefix(lim, "Max") {
223-
224-
// Line format: Max open files 16384 16384 files
225-
fields := regexp.MustCompile(`[\s]{2,}`).Split(lim, -1)
226-
name := strings.Replace(strings.ToLower(strings.TrimSpace(fields[0])), " ", "_", -1)
227-
228-
found := isUlimitWhitelisted(name)
229-
if !found {
230-
continue
231-
}
232-
233-
soft := strings.TrimSpace(fields[1])
234-
softNum, softErr := parseUlimit(soft)
235-
236-
hard := strings.TrimSpace(fields[2])
237-
hardNum, hardErr := parseUlimit(hard)
238-
239-
// Omit metric if there were any parsing errors
240-
if softErr == nil && hardErr == nil {
241-
ulimitSpec := info.UlimitSpec{
242-
Name: name,
243-
SoftLimit: int64(softNum),
244-
HardLimit: int64(hardNum),
245-
}
246-
ulimits = append(ulimits, ulimitSpec)
214+
if strings.HasPrefix(lim, "Max open files") {
215+
// Remove line prefix
216+
ulimit, err := processMaxOpenFileLimitLine(
217+
"max_open_files",
218+
lim[len(maxOpenFilesLinePrefix):],
219+
)
220+
if err == nil {
221+
ulimits = append(ulimits, ulimit)
247222
}
248223
}
249224
}
250225
return ulimits
251226
}
252227

228+
// Any caller of processMaxOpenFileLimitLine must ensure that the name prefix is already removed from the limit line.
229+
// with the "Max open files" prefix.
230+
func processMaxOpenFileLimitLine(name, line string) (info.UlimitSpec, error) {
231+
// Remove any leading whitespace
232+
line = strings.TrimSpace(line)
233+
// Split on whitespace
234+
fields := strings.Fields(line)
235+
if len(fields) != 3 {
236+
return info.UlimitSpec{}, fmt.Errorf("unable to parse max open files line: %s", line)
237+
}
238+
// The first field is the soft limit, the second is the hard limit
239+
soft, err := parseUlimit(fields[0])
240+
if err != nil {
241+
return info.UlimitSpec{}, err
242+
}
243+
hard, err := parseUlimit(fields[1])
244+
if err != nil {
245+
return info.UlimitSpec{}, err
246+
}
247+
return info.UlimitSpec{
248+
Name: name,
249+
SoftLimit: soft,
250+
HardLimit: hard,
251+
}, nil
252+
}
253+
253254
func processRootProcUlimits(rootFs string, rootPid int) []info.UlimitSpec {
254255
filePath := path.Join(rootFs, "/proc", strconv.Itoa(rootPid), "limits")
255256
out, err := os.ReadFile(filePath)

container/libcontainer/handler_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,13 @@ func BenchmarkProcessLimitsFile(b *testing.B) {
311311
// Ensure the compiler doesn't optimize away the benchmark
312312
_ = ulimits
313313
}
314+
315+
func TestProcessMaxOpenFileLimitLine(t *testing.T) {
316+
line := " 1073741816 1073741816 files "
317+
318+
ulimit, err := processMaxOpenFileLimitLine("max_open_files", line)
319+
assert.Nil(t, err)
320+
assert.Equal(t, "max_open_files", ulimit.Name)
321+
assert.Equal(t, int64(1073741816), ulimit.SoftLimit)
322+
assert.Equal(t, int64(1073741816), ulimit.HardLimit)
323+
}

0 commit comments

Comments
 (0)