From 2ad7a1241827ef1bc4f964fe8a5248b073f2db82 Mon Sep 17 00:00:00 2001 From: TomerLev Date: Mon, 10 Nov 2025 16:06:22 +0200 Subject: [PATCH] hugetlb: correctly parse hugetlb..events files Signed-off-by: Tomer Lev --- cgroup2/utils.go | 27 +++++++++++- cgroup2/utils_test.go | 98 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/cgroup2/utils.go b/cgroup2/utils.go index 4bdeac90..a48213d9 100644 --- a/cgroup2/utils.go +++ b/cgroup2/utils.go @@ -293,6 +293,31 @@ func getStatFileContentUint64(filePath string) uint64 { return res } +// getKVStatsFileContentUint64 gets uint64 parsed content of key-value cgroup stat file +func getKVStatsFileContentUint64(filePath string, propertyName string) uint64 { + f, err := os.Open(filePath) + if err != nil { + return 0 + } + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + name, value, err := parseKV(s.Text()) + if name == propertyName { + if err != nil { + log.L.WithError(err).Errorf("unable to parse %q as a uint from Cgroup file %q", propertyName, filePath) + return 0 + } + return value + } + } + if err = s.Err(); err != nil { + log.L.WithError(err).Errorf("error reading Cgroup file %q for property %q", filePath, propertyName) + } + return 0 +} + func readIoStats(path string) []*stats.IOEntry { // more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt var usage []*stats.IOEntry @@ -423,7 +448,7 @@ func readHugeTlbStats(path string) []*stats.HugeTlbStat { Max: getStatFileContentUint64(filepath.Join(path, "hugetlb."+pagesize+".max")), Current: getStatFileContentUint64(filepath.Join(path, "hugetlb."+pagesize+".current")), Pagesize: pagesize, - Failcnt: getStatFileContentUint64(filepath.Join(path, "hugetlb."+pagesize+".events")), + Failcnt: getKVStatsFileContentUint64(filepath.Join(path, "hugetlb."+pagesize+".events"), "max"), } } return usage diff --git a/cgroup2/utils_test.go b/cgroup2/utils_test.go index f481fb4c..0a2028c5 100644 --- a/cgroup2/utils_test.go +++ b/cgroup2/utils_test.go @@ -139,3 +139,101 @@ func BenchmarkGetStatFileContentUint64(b *testing.B) { _ = getStatFileContentUint64("/proc/self/loginuid") } } + +func TestGetKVStatsFileContentUint64(t *testing.T) { + testCases := []struct { + name string + fileContent string + propertyName string + expected uint64 + }{ + { + name: "valid property found", + fileContent: `some_key 123 +another_key 456 +third_key 789`, + propertyName: "another_key", + expected: 456, + }, + { + name: "property at beginning", + fileContent: `first_key 999 +second_key 888`, + propertyName: "first_key", + expected: 999, + }, + { + name: "property at end", + fileContent: `first_key 111 +second_key 222 +third_key 333`, + propertyName: "third_key", + expected: 333, + }, + { + name: "property not found", + fileContent: `key1 100 +key2 200`, + propertyName: "missing_key", + expected: 0, + }, + { + name: "invalid format - single field", + fileContent: `invalid_line +valid_key 42`, + propertyName: "valid_key", + expected: 42, + }, + { + name: "invalid format - three fields", + fileContent: `invalid key 1 2 +valid_key 100`, + propertyName: "valid_key", + expected: 100, + }, + { + name: "invalid number format", + fileContent: `key1 not_a_number +key2 500`, + propertyName: "key1", + expected: 0, + }, + { + name: "empty file", + fileContent: "", + propertyName: "any_key", + expected: 0, + }, + { + name: "large number", + fileContent: `max 18446744073709551615`, + propertyName: "max", + expected: 18446744073709551615, + }, + { + name: "zero value", + fileContent: `zero 0 +non_zero 1`, + propertyName: "zero", + expected: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "test.stat") + + err := os.WriteFile(filePath, []byte(tc.fileContent), 0o644) + assert.NoError(t, err) + + result := getKVStatsFileContentUint64(filePath, tc.propertyName) + assert.Equal(t, tc.expected, result) + }) + } + + t.Run("file not found", func(t *testing.T) { + result := getKVStatsFileContentUint64("/nonexistent/path/file.stat", "any_key") + assert.Equal(t, uint64(0), result) + }) +}