From 57770532a8b67ff0d8be35402cc34ddb709cfe67 Mon Sep 17 00:00:00 2001 From: lifubang Date: Fri, 17 Oct 2025 06:13:00 +0000 Subject: [PATCH] fs: improve cpuacct.usage_all parsing First, we can reasonably expect the first three fields of cpuacct.usage_all to be "cpu user system", even for future kernels, and if we see something different, it doesn't make sense to continue. So check that the header is as expected, and error out otherwise. Second, if we have more than 3 values, and we've checked that the first 3 are as expected ("cpu user system"), we can safely ignore extra columns. This fixes an issue on a custom kernel from Tencent, which adds a few extra columns (see [1]), as reported in issue 46. Add tests for both cases. Fixes issue 46. [1]: https://github.com/OpenCloudOS/TencentOS-kernel-0/commit/0b667819c3aaa9c8ac904c6b03256b86be93fc05 Reported-by: vimiix Signed-off-by: Kir Kolyshkin --- fs/cpuacct.go | 12 ++++++++---- fs/cpuacct_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/fs/cpuacct.go b/fs/cpuacct.go index 391a023..bde25b0 100644 --- a/fs/cpuacct.go +++ b/fs/cpuacct.go @@ -129,12 +129,16 @@ func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) { defer fd.Close() scanner := bufio.NewScanner(fd) - scanner.Scan() // skipping header line + scanner.Scan() // Read header line. + const want = "cpu user system" + if hdr := scanner.Text(); !strings.HasPrefix(hdr, want) { + return nil, nil, malformedLine(path, file, hdr) + } for scanner.Scan() { - // Each line is: cpu user system - fields := strings.SplitN(scanner.Text(), " ", 3) - if len(fields) != 3 { + // Each line is: cpu user system. Keep N at 4 to ignore extra fields. + fields := strings.SplitN(scanner.Text(), " ", 4) + if len(fields) < 3 { continue } diff --git a/fs/cpuacct_test.go b/fs/cpuacct_test.go index c0c9543..56ffad4 100644 --- a/fs/cpuacct_test.go +++ b/fs/cpuacct_test.go @@ -2,6 +2,7 @@ package fs import ( "reflect" + "slices" "testing" "github.com/opencontainers/cgroups" @@ -96,6 +97,54 @@ func TestCpuacctStatsWithoutUsageAll(t *testing.T) { } } +// TestCpuacctUsageAllExtra checks that if there are extra columns +// in cpuacct.usage_all, yet the first three are as expected, we +// can still parse it successfully. +func TestCpuacctUsageAllExtra(t *testing.T) { + path := tempDir(t, "cpuacct") + // These extra columns come from the custom Tencent kernel, + // see https://github.com/OpenCloudOS/TencentOS-kernel-0/commit/0b667819c3aaa9c8ac904c6b03256b86be93fc05 + err := cgroups.WriteFile(path, "cpuacct.usage_all", + `cpu user system bt_user bt_system +0 962250696038415 637727786389114 0 0 +1 981956408513304 638197595421064 0 0 +`) + if err != nil { + t.Fatal(err) + } + + system, user, err := getPercpuUsageInModes(path) + if err != nil { + t.Fatal(err) + } + + expUser := []uint64{962250696038415, 981956408513304} + expSystem := []uint64{637727786389114, 638197595421064} + if !slices.Equal(user, expUser) { + t.Fatalf("unexpected user data (want %+v, got %+v", expUser, user) + } + if !slices.Equal(system, expSystem) { + t.Fatalf("unexpected system data (want %+v, got +%v)", expSystem, system) + } +} + +func TestCpuacctUsageAllBad(t *testing.T) { + path := tempDir(t, "cpuacct") + err := cgroups.WriteFile(path, "cpuacct.usage_all", + `cpu bad data fields +0 1 2 +`) + if err != nil { + t.Fatal(err) + } + + _, _, err = getPercpuUsageInModes(path) + t.Log(err) + if err == nil { + t.Fatal("want error, got nil") + } +} + func BenchmarkGetCpuUsageBreakdown(b *testing.B) { path := tempDir(b, "cpuacct") writeFileContents(b, path, map[string]string{