Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

container/libcontainer: Improve limits file parsing perf #3396

Merged
merged 2 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 36 additions & 35 deletions container/libcontainer/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import (
)

var (
whitelistedUlimits = [...]string{"max_open_files"}
referencedResetInterval = flag.Uint64("referenced_reset_interval", 0,
"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)")

Expand Down Expand Up @@ -205,51 +204,53 @@ func parseUlimit(value string) (int64, error) {
return num, nil
}

func isUlimitWhitelisted(name string) bool {
for _, whitelist := range whitelistedUlimits {
if name == whitelist {
return true
}
}
return false
}

func processLimitsFile(fileData string) []info.UlimitSpec {
const maxOpenFilesLinePrefix = "Max open files"

limits := strings.Split(fileData, "\n")
ulimits := make([]info.UlimitSpec, 0, len(limits))
for _, lim := range limits {
// Skip any headers/footers
if strings.HasPrefix(lim, "Max") {

// Line format: Max open files 16384 16384 files
fields := regexp.MustCompile(`[\s]{2,}`).Split(lim, -1)
name := strings.Replace(strings.ToLower(strings.TrimSpace(fields[0])), " ", "_", -1)

found := isUlimitWhitelisted(name)
if !found {
continue
}

soft := strings.TrimSpace(fields[1])
softNum, softErr := parseUlimit(soft)

hard := strings.TrimSpace(fields[2])
hardNum, hardErr := parseUlimit(hard)

// Omit metric if there were any parsing errors
if softErr == nil && hardErr == nil {
ulimitSpec := info.UlimitSpec{
Name: name,
SoftLimit: int64(softNum),
HardLimit: int64(hardNum),
}
ulimits = append(ulimits, ulimitSpec)
if strings.HasPrefix(lim, "Max open files") {
// Remove line prefix
ulimit, err := processMaxOpenFileLimitLine(
"max_open_files",
lim[len(maxOpenFilesLinePrefix):],
)
if err == nil {
ulimits = append(ulimits, ulimit)
}
}
}
return ulimits
}

// Any caller of processMaxOpenFileLimitLine must ensure that the name prefix is already removed from the limit line.
// with the "Max open files" prefix.
func processMaxOpenFileLimitLine(name, line string) (info.UlimitSpec, error) {
// Remove any leading whitespace
line = strings.TrimSpace(line)
// Split on whitespace
fields := strings.Fields(line)
if len(fields) != 3 {
return info.UlimitSpec{}, fmt.Errorf("unable to parse max open files line: %s", line)
}
// The first field is the soft limit, the second is the hard limit
soft, err := parseUlimit(fields[0])
if err != nil {
return info.UlimitSpec{}, err
}
hard, err := parseUlimit(fields[1])
if err != nil {
return info.UlimitSpec{}, err
}
return info.UlimitSpec{
Name: name,
SoftLimit: soft,
HardLimit: hard,
}, nil
}

func processRootProcUlimits(rootFs string, rootPid int) []info.UlimitSpec {
filePath := path.Join(rootFs, "/proc", strconv.Itoa(rootPid), "limits")
out, err := os.ReadFile(filePath)
Expand Down
25 changes: 25 additions & 0 deletions container/libcontainer/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,28 @@ func TestClearReferencedBytesWhenClearRefsMissing(t *testing.T) {
err := clearReferencedBytes(pids, 0, 1)
assert.Nil(t, err)
}

var ulimits []info.UlimitSpec

func BenchmarkProcessLimitsFile(b *testing.B) {
content, err := os.ReadFile("testdata/limits")
assert.Nil(b, err)

b.ResetTimer()
for i := 0; i < b.N; i++ {
ulimits = processLimitsFile(string(content))
}

// Ensure the compiler doesn't optimize away the benchmark
_ = ulimits
}

func TestProcessMaxOpenFileLimitLine(t *testing.T) {
line := " 1073741816 1073741816 files "

ulimit, err := processMaxOpenFileLimitLine("max_open_files", line)
assert.Nil(t, err)
assert.Equal(t, "max_open_files", ulimit.Name)
assert.Equal(t, int64(1073741816), ulimit.SoftLimit)
assert.Equal(t, int64(1073741816), ulimit.HardLimit)
}
17 changes: 17 additions & 0 deletions container/libcontainer/testdata/limits
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size unlimited unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 119958 119958 processes
Max open files 1073741816 1073741816 files
Max locked memory 3932852224 3932852224 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 119958 119958 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us