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{