From 5fe29ea5da6fabfc03a19dd41d2ef56510dacd83 Mon Sep 17 00:00:00 2001 From: guodong Date: Mon, 16 May 2022 20:55:51 +0800 Subject: [PATCH] Support to handle more than 4 fields in cpuacct.stat Signed-off-by: Guodong Zhu --- cpuacct.go | 33 ++++++++++++++---------- cpuacct_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 cpuacct_test.go diff --git a/cpuacct.go b/cpuacct.go index e5fc864b..4fd5f1e6 100644 --- a/cpuacct.go +++ b/cpuacct.go @@ -17,8 +17,10 @@ package cgroups import ( + "bufio" "fmt" "io/ioutil" + "os" "path/filepath" "strconv" "strings" @@ -86,36 +88,41 @@ func (c *cpuacctController) percpuUsage(path string) ([]uint64, error) { func (c *cpuacctController) getUsage(path string) (user uint64, kernel uint64, err error) { statPath := filepath.Join(c.Path(path), "cpuacct.stat") - data, err := ioutil.ReadFile(statPath) + f, err := os.Open(statPath) if err != nil { return 0, 0, err } - fields := strings.Fields(string(data)) - if len(fields) != 4 { - return 0, 0, fmt.Errorf("%q is expected to have 4 fields", statPath) + defer f.Close() + var ( + raw = make(map[string]uint64) + sc = bufio.NewScanner(f) + ) + for sc.Scan() { + key, v, err := parseKV(sc.Text()) + if err != nil { + return 0, 0, err + } + raw[key] = v + } + if err := sc.Err(); err != nil { + return 0, 0, err } for _, t := range []struct { - index int name string value *uint64 }{ { - index: 0, name: "user", value: &user, }, { - index: 2, name: "system", value: &kernel, }, } { - if fields[t.index] != t.name { - return 0, 0, fmt.Errorf("expected field %q but found %q in %q", t.name, fields[t.index], statPath) - } - v, err := strconv.ParseUint(fields[t.index+1], 10, 64) - if err != nil { - return 0, 0, err + v, ok := raw[t.name] + if !ok { + return 0, 0, fmt.Errorf("expected field %q but not found in %q", t.name, statPath) } *t.value = v } diff --git a/cpuacct_test.go b/cpuacct_test.go new file mode 100644 index 00000000..2a7cdf9f --- /dev/null +++ b/cpuacct_test.go @@ -0,0 +1,67 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cgroups + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +const cpuacctStatData = `user 1 +system 2 +sched_delay 3 +` + +func TestGetUsage(t *testing.T) { + mock, err := newMock() + if err != nil { + t.Fatal(err) + } + defer mock.delete() + cpuacct := NewCpuacct(mock.root) + if cpuacct == nil { + t.Fatal("cpuacct is nil") + } + err = os.Mkdir(filepath.Join(mock.root, string(Cpuacct), "test"), defaultDirPerm) + if err != nil { + t.Fatal(err) + } + current := filepath.Join(mock.root, string(Cpuacct), "test", "cpuacct.stat") + if err = ioutil.WriteFile( + current, + []byte(cpuacctStatData), + defaultFilePerm, + ); err != nil { + t.Fatal(err) + } + user, kernel, err := cpuacct.getUsage("test") + if err != nil { + t.Fatalf("can't get usage: %v", err) + } + index := []uint64{ + user, + kernel, + } + for i, v := range index { + expected := ((uint64(i) + 1) * nanosecondsInSecond) / clockTicks + if v != expected { + t.Errorf("expected value at index %d to be %d but received %d", i, expected, v) + } + } +}