From 2e8f8b1122a03e5f12225f3664de686bde5a46f8 Mon Sep 17 00:00:00 2001 From: Kevin Fairise <132568982+KevinFairise2@users.noreply.github.com> Date: Fri, 18 Aug 2023 20:26:26 +0200 Subject: [PATCH] Revert "Optimize pkg/util/cgroup code to allocate less frequently (#18816)" (#18895) This reverts commit 2397c7c0132e79b22ba945935f2e712810683454. --- pkg/util/cgroups/cgroup_shared.go | 28 ++--- pkg/util/cgroups/cgroupv1_cpu.go | 24 ++--- pkg/util/cgroups/cgroupv1_cpu_test.go | 23 ---- pkg/util/cgroups/cgroupv1_memory.go | 6 +- pkg/util/cgroups/cgroupv1_memory_test.go | 26 ----- pkg/util/cgroups/cgroupv2_cpu.go | 18 ++-- pkg/util/cgroups/cgroupv2_cpu_test.go | 4 +- pkg/util/cgroups/cgroupv2_io_test.go | 116 +++++++++++++++++++++ pkg/util/cgroups/cgroupv2_memory.go | 11 +- pkg/util/cgroups/errors.go | 7 ++ pkg/util/cgroups/file.go | 127 ++++++----------------- pkg/util/cgroups/file_test.go | 58 ----------- pkg/util/cgroups/pid_mapper.go | 14 +-- pkg/util/cgroups/readerv2.go | 4 +- pkg/util/cgroups/readerv2_test.go | 2 - 15 files changed, 194 insertions(+), 274 deletions(-) create mode 100644 pkg/util/cgroups/cgroupv2_io_test.go delete mode 100644 pkg/util/cgroups/file_test.go diff --git a/pkg/util/cgroups/cgroup_shared.go b/pkg/util/cgroups/cgroup_shared.go index 24928028382c7..2737cbcdbdd2b 100644 --- a/pkg/util/cgroups/cgroup_shared.go +++ b/pkg/util/cgroups/cgroup_shared.go @@ -8,8 +8,8 @@ package cgroups import ( - "bytes" "strconv" + "strings" ) const ( @@ -22,28 +22,20 @@ const ( // So "0,1,5-8" represents processors 0, 1, 5, 6, 7, 8. // The function returns the count of CPUs, in this case 6. func ParseCPUSetFormat(line string) uint64 { - lineRaw := []byte(line) var numCPUs uint64 - var currentSegment []byte - for len(lineRaw) != 0 { - nextStart := bytes.IndexByte(lineRaw, ',') - if nextStart == -1 { - currentSegment = lineRaw - lineRaw = nil - } else { - currentSegment = lineRaw[:nextStart] - lineRaw = lineRaw[nextStart+1:] - } - - if split := bytes.IndexByte(currentSegment, '-'); split != -1 { - p0, _ := strconv.Atoi(string(currentSegment[:split])) - p1, _ := strconv.Atoi(string(currentSegment[split+1:])) + lineSlice := strings.Split(line, ",") + for _, l := range lineSlice { + lineParts := strings.Split(l, "-") + if len(lineParts) == 2 { + p0, _ := strconv.Atoi(lineParts[0]) + p1, _ := strconv.Atoi(lineParts[1]) numCPUs += uint64(p1 - p0 + 1) - } else { - numCPUs += 1 + } else if len(lineParts) == 1 { + numCPUs++ } } + return numCPUs } diff --git a/pkg/util/cgroups/cgroupv1_cpu.go b/pkg/util/cgroups/cgroupv1_cpu.go index 8827b29b0c61b..9f9ea02ffb63e 100644 --- a/pkg/util/cgroups/cgroupv1_cpu.go +++ b/pkg/util/cgroups/cgroupv1_cpu.go @@ -42,12 +42,7 @@ func (c *cgroupV1) parseCPUController(stats *CPUStats) { reportError(err) } - if err := parse2ColumnStats(c.fr, c.pathFor("cpu", "cpu.stat"), 0, 1, func(keyRaw, valueRaw []byte) error { - - // the go compiler will avoid a copy here - key := string(keyRaw) - value := string(valueRaw) - + if err := parse2ColumnStats(c.fr, c.pathFor("cpu", "cpu.stat"), 0, 1, func(key, value string) error { intVal, err := strconv.ParseUint(value, 10, 64) if err != nil { reportError(newValueError(value, err)) @@ -100,8 +95,8 @@ func (c *cgroupV1) parseCPUAcctController(stats *CPUStats) { func (c *cgroupV1) parseCPUSetController(stats *CPUStats) { // Normally there's only one line, but as the parser works line by line anyway, we do support multiple lines var cpuCount uint64 - err := parseFile(c.fr, c.pathFor("cpuset", "cpuset.cpus"), func(lineRaw []byte) error { - cpuCount += ParseCPUSetFormat(string(lineRaw)) + err := parseFile(c.fr, c.pathFor("cpuset", "cpuset.cpus"), func(line string) error { + cpuCount += ParseCPUSetFormat(line) return nil }) @@ -112,16 +107,11 @@ func (c *cgroupV1) parseCPUSetController(stats *CPUStats) { } } -func parseV1CPUAcctStatFn(stats *CPUStats) func(keyRaw, valRaw []byte) error { - return func(keyRaw, valRaw []byte) error { - - // the go compiler will avoid a copy here - key := string(keyRaw) - valString := string(valRaw) - - intVal, err := strconv.ParseUint(valString, 10, 64) +func parseV1CPUAcctStatFn(stats *CPUStats) func(key, val string) error { + return func(key, val string) error { + intVal, err := strconv.ParseUint(val, 10, 64) if err != nil { - reportError(newValueError(valString, err)) + reportError(newValueError(val, err)) return nil } diff --git a/pkg/util/cgroups/cgroupv1_cpu_test.go b/pkg/util/cgroups/cgroupv1_cpu_test.go index 55b77c8da2331..2a8afd303b5ac 100644 --- a/pkg/util/cgroups/cgroupv1_cpu_test.go +++ b/pkg/util/cgroups/cgroupv1_cpu_test.go @@ -103,26 +103,3 @@ func TestCgroupV1CPUStats(t *testing.T) { CPUCount: pointer.Ptr(uint64(8)), }, *stats)) } - -func BenchmarkGetCPUStatsV1(b *testing.B) { - cfs := newCgroupMemoryFS("/test/fs/cgroup") - cfs.enableControllers("cpu") - cfs.enableControllers("cpuacct", "cpuset") - - stats := &CPUStats{} - // Test failure if controller missing (cpu is missing) - cgFoo1 := cfs.createCgroupV1("foo1", containerCgroupKubePod(false)) - - // Test reading files in CPU controllers, all files present - createCgroupV1FakeCPUFiles(cfs, cgFoo1) - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - err := cgFoo1.GetCPUStats(stats) - if err != nil { - b.Errorf("error in GetCPUStats: %v", err) - b.FailNow() - } - } -} diff --git a/pkg/util/cgroups/cgroupv1_memory.go b/pkg/util/cgroups/cgroupv1_memory.go index 5cbcb3ead753e..ba206cc3e55af 100644 --- a/pkg/util/cgroups/cgroupv1_memory.go +++ b/pkg/util/cgroups/cgroupv1_memory.go @@ -26,11 +26,7 @@ func (c *cgroupV1) GetMemoryStats(stats *MemoryStats) error { return &ControllerNotFoundError{Controller: "memory"} } - if err := parse2ColumnStats(c.fr, c.pathFor("memory", "memory.stat"), 0, 1, func(keyRaw, valueRaw []byte) error { - // the go compiler is smart enough to not turn this into a copy - key := string(keyRaw) - value := string(valueRaw) - + if err := parse2ColumnStats(c.fr, c.pathFor("memory", "memory.stat"), 0, 1, func(key, value string) error { intVal, err := strconv.ParseUint(value, 10, 64) if err != nil { reportError(newValueError(value, err)) diff --git a/pkg/util/cgroups/cgroupv1_memory_test.go b/pkg/util/cgroups/cgroupv1_memory_test.go index 8ce91cdf34095..22ebc1650a9e7 100644 --- a/pkg/util/cgroups/cgroupv1_memory_test.go +++ b/pkg/util/cgroups/cgroupv1_memory_test.go @@ -117,29 +117,3 @@ func TestCgroupV1MemoryStats(t *testing.T) { Peak: pointer.Ptr(uint64(400000000)), }, *stats)) } - -func BenchmarkMemory(b *testing.B) { - cfs := newCgroupMemoryFS("/test/fs/cgroup") - - var err error - stats := &MemoryStats{} - - // Test failure if controller missing (memory is missing) - cgFoo1 := cfs.createCgroupV1("foo1", containerCgroupKubePod(false)) - - // Test reading files in memory controller, all files missing (memsw not counted as considered optional) - tr.reset() - cfs.enableControllers("memory") - - // Test reading files in memory controller, all files present - createCgroupV1FakeMemoryFiles(cfs, cgFoo1) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - err = cgFoo1.GetMemoryStats(stats) - if err != nil { - b.Errorf("error getting memory stats: %v", err) - } - } -} diff --git a/pkg/util/cgroups/cgroupv2_cpu.go b/pkg/util/cgroups/cgroupv2_cpu.go index a456e7b52d2ff..4ff39f47e406e 100644 --- a/pkg/util/cgroups/cgroupv2_cpu.go +++ b/pkg/util/cgroups/cgroupv2_cpu.go @@ -54,8 +54,8 @@ func (c *cgroupV2) parseCPUController(stats *CPUStats) { func (c *cgroupV2) parseCPUSetController(stats *CPUStats) { // Normally there's only one line, but as the parser works line by line anyway, we do support multiple lines var cpuCount uint64 - err := parseFile(c.fr, c.pathFor("cpuset.cpus.effective"), func(lineRaw []byte) error { - cpuCount += ParseCPUSetFormat(string(lineRaw)) + err := parseFile(c.fr, c.pathFor("cpuset.cpus.effective"), func(line string) error { + cpuCount += ParseCPUSetFormat(line) return nil }) @@ -66,11 +66,8 @@ func (c *cgroupV2) parseCPUSetController(stats *CPUStats) { } } -func parseV2CPUStat(stats *CPUStats) func(keyRaw, valueRaw []byte) error { - return func(keyRaw, valueRaw []byte) error { - key := string(keyRaw) - value := string(valueRaw) - +func parseV2CPUStat(stats *CPUStats) func(key, value string) error { + return func(key, value string) error { // Do not stop parsing the file if we cannot parse a single value intVal, err := strconv.ParseUint(value, 10, 64) if err != nil { @@ -101,11 +98,8 @@ func parseV2CPUStat(stats *CPUStats) func(keyRaw, valueRaw []byte) error { } } -func parseV2CPUMax(stats *CPUStats) func(keyRaw, valueRaw []byte) error { - return func(limitRaw, periodRaw []byte) error { - period := string(periodRaw) - limit := string(limitRaw) - +func parseV2CPUMax(stats *CPUStats) func(key, value string) error { + return func(limit, period string) error { periodVal, err := strconv.ParseUint(period, 10, 64) if err == nil { stats.SchedulerPeriod = pointer.Ptr(periodVal * uint64(time.Microsecond)) diff --git a/pkg/util/cgroups/cgroupv2_cpu_test.go b/pkg/util/cgroups/cgroupv2_cpu_test.go index 08e56b27f8a45..7d8fee8615bca 100644 --- a/pkg/util/cgroups/cgroupv2_cpu_test.go +++ b/pkg/util/cgroups/cgroupv2_cpu_test.go @@ -146,7 +146,7 @@ func TestParseV2CPUStat(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stats := CPUStats{} - err := parseV2CPUStat(&stats)([]byte(tt.inputKey), []byte(tt.inputVal)) + err := parseV2CPUStat(&stats)(tt.inputKey, tt.inputVal) assert.ErrorIs(t, err, tt.wantErr) assert.Empty(t, cmp.Diff(tt.want, &stats)) }) @@ -191,7 +191,7 @@ func TestParseV2CPUMax(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stats := CPUStats{} - err := parseV2CPUMax(&stats)([]byte(tt.inputKey), []byte(tt.inputVal)) + err := parseV2CPUMax(&stats)(tt.inputKey, tt.inputVal) assert.ErrorIs(t, err, tt.wantErr) assert.Empty(t, cmp.Diff(tt.want, &stats)) }) diff --git a/pkg/util/cgroups/cgroupv2_io_test.go b/pkg/util/cgroups/cgroupv2_io_test.go new file mode 100644 index 0000000000000..a115d56c92f60 --- /dev/null +++ b/pkg/util/cgroups/cgroupv2_io_test.go @@ -0,0 +1,116 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build linux + +package cgroups + +import ( + "testing" + + "github.com/DataDog/datadog-agent/pkg/util/pointer" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" +) + +const ( + sampleCgroupV2IOStat = `259:0 rbytes=278528 wbytes=11623899136 rios=6 wios=2744940 dbytes=0 dios=0 +8:16 rbytes=278528 wbytes=11623899136 rios=6 wios=2744940 dbytes=0 dios=0` + sampleCgroupV2IOMax = "8:16 rbps=2097152 wbps=max riops=max wiops=120" + sampleCroupV2IOPressure = `some avg10=0.00 avg60=0.00 avg300=0.00 total=0 +full avg10=0.00 avg60=0.00 avg300=0.00 total=0` +) + +func createCgroupV2FakeIOFiles(cfs *cgroupMemoryFS, cg *cgroupV2) { + cfs.setCgroupV2File(cg, "io.stat", sampleCgroupV2IOStat) + cfs.setCgroupV2File(cg, "io.max", sampleCgroupV2IOMax) + cfs.setCgroupV2File(cg, "io.pressure", sampleCroupV2IOPressure) +} + +func TestCgroupV2IOStats(t *testing.T) { + cfs := newCgroupMemoryFS("/test/fs/cgroup") + + var err error + stats := &IOStats{} + + // Test failure if controller missing (io is missing) + tr.reset() + cgFoo1 := cfs.createCgroupV2("foo1", containerCgroupKubePod(true)) + err = cgFoo1.GetIOStats(stats) + assert.ErrorIs(t, err, &ControllerNotFoundError{Controller: "io"}) + + // Test reading files in io controller, all files missing + tr.reset() + cfs.enableControllers("io") + err = cgFoo1.GetIOStats(stats) + assert.NoError(t, err) + assert.Equal(t, 3, len(tr.errors)) + assert.Empty(t, cmp.Diff(IOStats{}, *stats)) + + // Test reading files in io controller, all files present + tr.reset() + createCgroupV2FakeIOFiles(cfs, cgFoo1) + err = cgFoo1.GetIOStats(stats) + assert.NoError(t, err) + assert.ElementsMatch(t, []error{}, tr.errors) + assert.Empty(t, cmp.Diff(IOStats{ + ReadBytes: pointer.Ptr(uint64(557056)), + WriteBytes: pointer.Ptr(uint64(23247798272)), + ReadOperations: pointer.Ptr(uint64(12)), + WriteOperations: pointer.Ptr(uint64(5489880)), + Devices: map[string]DeviceIOStats{ + "259:0": { + ReadBytes: pointer.Ptr(uint64(278528)), + WriteBytes: pointer.Ptr(uint64(11623899136)), + ReadOperations: pointer.Ptr(uint64(6)), + WriteOperations: pointer.Ptr(uint64(2744940)), + }, + "8:16": { + ReadBytes: pointer.Ptr(uint64(278528)), + WriteBytes: pointer.Ptr(uint64(11623899136)), + ReadOperations: pointer.Ptr(uint64(6)), + WriteOperations: pointer.Ptr(uint64(2744940)), + ReadBytesLimit: pointer.Ptr(uint64(2097152)), + WriteOperationsLimit: pointer.Ptr(uint64(120)), + }, + }, + PSISome: PSIStats{ + Avg10: pointer.Ptr(0.0), + Avg60: pointer.Ptr(0.0), + Avg300: pointer.Ptr(0.0), + Total: pointer.Ptr(uint64(0)), + }, + PSIFull: PSIStats{ + Avg10: pointer.Ptr(0.0), + Avg60: pointer.Ptr(0.0), + Avg300: pointer.Ptr(0.0), + Total: pointer.Ptr(uint64(0)), + }, + }, *stats)) + + // Test reading empty file + tr.reset() + stats = &IOStats{} + cfs.setCgroupV2File(cgFoo1, "io.stat", "") + cfs.setCgroupV2File(cgFoo1, "io.max", "") + err = cgFoo1.GetIOStats(stats) + assert.NoError(t, err) + assert.ElementsMatch(t, []error{}, tr.errors) + assert.Empty(t, cmp.Diff(IOStats{ + PSISome: PSIStats{ + Avg10: pointer.Ptr(0.0), + Avg60: pointer.Ptr(0.0), + Avg300: pointer.Ptr(0.0), + Total: pointer.Ptr(uint64(0)), + }, + PSIFull: PSIStats{ + Avg10: pointer.Ptr(0.0), + Avg60: pointer.Ptr(0.0), + Avg300: pointer.Ptr(0.0), + Total: pointer.Ptr(uint64(0)), + }, + }, *stats)) +} diff --git a/pkg/util/cgroups/cgroupv2_memory.go b/pkg/util/cgroups/cgroupv2_memory.go index 587e4b80f0c1e..1077946456ee1 100644 --- a/pkg/util/cgroups/cgroupv2_memory.go +++ b/pkg/util/cgroups/cgroupv2_memory.go @@ -24,10 +24,7 @@ func (c *cgroupV2) GetMemoryStats(stats *MemoryStats) error { var kernelStack, slab *uint64 - if err := parse2ColumnStats(c.fr, c.pathFor("memory.stat"), 0, 1, func(keyRaw, valueRaw []byte) error { - key := string(keyRaw) - value := string(valueRaw) - + if err := parse2ColumnStats(c.fr, c.pathFor("memory.stat"), 0, 1, func(key, value string) error { intVal, err := strconv.ParseUint(value, 10, 64) if err != nil { reportError(newValueError(value, err)) @@ -116,11 +113,7 @@ func (c *cgroupV2) GetMemoryStats(stats *MemoryStats) error { } nilIfZero(&stats.SwapLimit) - if err := parse2ColumnStats(c.fr, c.pathFor("memory.events"), 0, 1, func(keyRaw, valueRaw []byte) error { - // the go compiler will avoid a copy here - key := string(keyRaw) - value := string(valueRaw) - + if err := parse2ColumnStats(c.fr, c.pathFor("memory.events"), 0, 1, func(key, value string) error { intVal, err := strconv.ParseUint(value, 10, 64) if err != nil { reportError(newValueError(value, err)) diff --git a/pkg/util/cgroups/errors.go b/pkg/util/cgroups/errors.go index dd78db6fbbd03..455d84d0c77b6 100644 --- a/pkg/util/cgroups/errors.go +++ b/pkg/util/cgroups/errors.go @@ -42,6 +42,13 @@ type FileSystemError struct { Err error } +func newFileSystemError(path string, err error) *FileSystemError { + return &FileSystemError{ + FilePath: path, + Err: err, + } +} + func (e *FileSystemError) Error() string { return fmt.Sprintf("fs error, path: %s, err: %s", e.FilePath, e.Err.Error()) } diff --git a/pkg/util/cgroups/file.go b/pkg/util/cgroups/file.go index e0d5dbea77f71..ee16c2b132d9a 100644 --- a/pkg/util/cgroups/file.go +++ b/pkg/util/cgroups/file.go @@ -9,16 +9,16 @@ package cgroups import ( "bufio" - "bytes" "errors" "fmt" "io" "os" "strconv" "strings" - "sync" - "unicode" - "unicode/utf8" +) + +const ( + spaceSeparator = " " ) var defaultFileReader = &osFileReader{} @@ -47,64 +47,41 @@ func (e *stopParsingError) Error() string { // returning an error will stop parsing and return the error // with the exception of stopParsingError that will return without error -// -// the input slice will get overwritten, and should be copied if it needs to escape the scope of the parser. -type parser func([]byte) error - -var readerPool = sync.Pool{New: func() any { return bufio.NewReader(nil) }} +type parser func(string) error func parseFile(fr fileReader, path string, p parser) error { - file, err := fr.open(path) + f, err := fr.open(path) if err != nil { - return err + return newFileSystemError(path, err) } - defer file.Close() - return readFile(file, p) -} - -func readFile(file io.Reader, p parser) error { - reader := readerPool.Get().(*bufio.Reader) - reader.Reset(file) - defer readerPool.Put(reader) - - for { - line, isPrefix, err := reader.ReadLine() - if err == io.EOF { - break - } + defer f.Close() - // less efficient path, only if the line length exceeds the readers buffer - if isPrefix { - var accum []byte - accum = append(accum, line...) - for isPrefix { - line, isPrefix, _ = reader.ReadLine() - accum = append(accum, line...) - } - line = accum - } - - err = p(line) + s := bufio.NewScanner(f) + for s.Scan() { + line := s.Text() + err := p(line) if err != nil { if errors.Is(err, &stopParsingError{}) { return nil } + return err } } + return nil } func parseSingleSignedStat(fr fileReader, path string, val **int64) error { - return parseFile(fr, path, func(line []byte) error { + return parseFile(fr, path, func(line string) error { // handle cgroupv2 max value, we usually consider max == no value (limit) - if bytes.Equal(line, []byte("max")) { + if line == "max" { return &stopParsingError{} } - value, err := strconv.ParseInt(string(line), 10, 64) + value, err := strconv.ParseInt(line, 10, 64) if err != nil { - return newValueError(string(line), err) + return newValueError(line, err) } *val = &value return &stopParsingError{} @@ -112,8 +89,7 @@ func parseSingleSignedStat(fr fileReader, path string, val **int64) error { } func parseSingleUnsignedStat(fr fileReader, path string, val **uint64) error { - return parseFile(fr, path, func(lineRaw []byte) error { - line := string(lineRaw) + return parseFile(fr, path, func(line string) error { // handle cgroupv2 max value, we usually consider max == no value (limit) if line == "max" { return &stopParsingError{} @@ -129,8 +105,8 @@ func parseSingleUnsignedStat(fr fileReader, path string, val **uint64) error { } func parseColumnStats(fr fileReader, path string, valueParser func([]string) error) error { - err := parseFile(fr, path, func(lineRaw []byte) error { - splits := strings.Fields(string(lineRaw)) + err := parseFile(fr, path, func(line string) error { + splits := strings.Fields(line) return valueParser(splits) }) @@ -138,27 +114,21 @@ func parseColumnStats(fr fileReader, path string, valueParser func([]string) err } // columns are 0-indexed, we skip malformed lines -func parse2ColumnStats(fr fileReader, path string, keyColumn, valueColumn int, valueParser func([]byte, []byte) error) error { - err := parseFile(fr, path, func(lineRaw []byte) error { - - var ( - i int - key []byte - value []byte - token []byte - ) - for len(lineRaw) != 0 { - token, lineRaw = munchWhitespace(lineRaw) - if i == keyColumn { - key = token - } - if i == valueColumn { - value = token - } - i++ +func parse2ColumnStats(fr fileReader, path string, keyColumn, valueColumn int, valueParser func(string, string) error) error { + lastIdx := valueColumn + if keyColumn > lastIdx { + lastIdx = keyColumn + } + + err := parseFile(fr, path, func(line string) error { + splits := strings.SplitN(line, spaceSeparator, lastIdx+1) + if len(splits) <= lastIdx { + return nil } - return valueParser(key, value) + + return valueParser(splits[keyColumn], splits[valueColumn]) }) + return err } @@ -219,32 +189,3 @@ func parsePSI(fr fileReader, path string, somePsi, fullPsi *PSIStats) error { return nil }) } - -// munchWhitespace reads the first token out of a unicode string, and returns a unicode string where the next call to munchWhitespace should pick up. -// `in` is expected to be a valid unicode string (represented as a []byte to allow for zero-copy). `token` and `rest` are slices aliasing the same memory -// as `in`. -// -// tok, rest := munchWhitespace("lorem ipsum dolor sit amet") => "lorem", "ipsum dolor sit amet" -// tok, rest := munchWhitespace("ipsum dolor sit amet", "dolor sit amet") -// ... -func munchWhitespace(in []byte) (token, rest []byte) { - - var cut int - for cut < len(in) { - r, size := utf8.DecodeRune(in[cut:]) - if unicode.IsSpace(r) { - break - } - cut += size - } - - cut2 := cut - for cut2 < len(in) { - r, size := utf8.DecodeRune(in[cut2:]) - if !unicode.IsSpace(r) { - break - } - cut2 += size - } - return in[:cut], in[cut2:] -} diff --git a/pkg/util/cgroups/file_test.go b/pkg/util/cgroups/file_test.go deleted file mode 100644 index 7c0de94bfae4a..0000000000000 --- a/pkg/util/cgroups/file_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2023-present Datadog, Inc. - -//go:build linux - -package cgroups - -import ( - "github.com/stretchr/testify/assert" - "strings" - "testing" -) - -func TestMunch(t *testing.T) { - - r := []byte("hello world it's me the clown🤡 ") - tok, rest := munchWhitespace(r) - assert.Equal(t, "hello", string(tok)) - - tok, rest = munchWhitespace(rest) - assert.Equal(t, "world", string(tok)) - - tok, rest = munchWhitespace(rest) - assert.Equal(t, "it's", string(tok)) - - tok, rest = munchWhitespace(rest) - assert.Equal(t, "me", string(tok)) - - tok, rest = munchWhitespace(rest) - assert.Equal(t, "the", string(tok)) - - tok, _ = munchWhitespace(rest) - assert.Equal(t, "clown🤡", string(tok)) -} - -func TestParseFile(t *testing.T) { - - bigString := &strings.Builder{} - for line := 0; line < 100; line++ { - // this string is wide enough to hit the initial buffer size in bufio.Reader - for char := 0; char < 1000; char++ { - bigString.WriteByte('X') - } - bigString.WriteRune('\n') - } - expected := bigString.String() - - actualString := &strings.Builder{} - readFile(strings.NewReader(expected), func(bytes []byte) error { - actualString.Write(bytes) - actualString.WriteRune('\n') - return nil - }) - - assert.Equal(t, expected, actualString.String()) -} diff --git a/pkg/util/cgroups/pid_mapper.go b/pkg/util/cgroups/pid_mapper.go index e208cb0f85f1d..f315d31100ea7 100644 --- a/pkg/util/cgroups/pid_mapper.go +++ b/pkg/util/cgroups/pid_mapper.go @@ -22,10 +22,10 @@ import ( func IdentiferFromCgroupReferences(procPath, pid, baseCgroupController string, filter ReaderFilter) (string, error) { var identifier string - err := parseFile(defaultFileReader, filepath.Join(procPath, pid, procCgroupFile), func(s []byte) error { + err := parseFile(defaultFileReader, filepath.Join(procPath, pid, procCgroupFile), func(s string) error { var err error - parts := strings.SplitN(string(s), ":", 3) + parts := strings.SplitN(s, ":", 3) // Skip potentially malformed lines if len(parts) != 3 { return nil @@ -62,8 +62,8 @@ func getPidMapper(procPath, cgroupRoot, baseController string, filter ReaderFilt // In cgroupv2, the file contains 0 values, filtering for that cgroupProcsTestFilePath := filepath.Join(cgroupRoot, cgroupProcsFile) cgroupProcsUsable := false - err := parseFile(defaultFileReader, cgroupProcsTestFilePath, func(s []byte) error { - if string(s) != "" && string(s) != "0" { + err := parseFile(defaultFileReader, cgroupProcsTestFilePath, func(s string) error { + if s != "" && s != "0" { cgroupProcsUsable = true } @@ -114,10 +114,10 @@ type cgroupProcsPidMapper struct { func (pm *cgroupProcsPidMapper) getPIDsForCgroup(identifier, relativeCgroupPath string, cacheValidity time.Duration) []int { var pids []int - if err := parseFile(pm.fr, pm.cgroupProcsFilePathBuilder(relativeCgroupPath), func(s []byte) error { - pid, err := strconv.Atoi(string(s)) + if err := parseFile(pm.fr, pm.cgroupProcsFilePathBuilder(relativeCgroupPath), func(s string) error { + pid, err := strconv.Atoi(s) if err != nil { - reportError(newValueError(string(s), err)) + reportError(newValueError(s, err)) return nil } diff --git a/pkg/util/cgroups/readerv2.go b/pkg/util/cgroups/readerv2.go index 54ed2e94c7cf0..31fcd7cb4a992 100644 --- a/pkg/util/cgroups/readerv2.go +++ b/pkg/util/cgroups/readerv2.go @@ -75,8 +75,8 @@ func (r *readerV2) parseCgroups() (map[string]Cgroup, error) { func readCgroupControllers(cgroupRoot string) (map[string]struct{}, error) { controllersMap := make(map[string]struct{}) - err := parseFile(defaultFileReader, path.Join(cgroupRoot, controllersFile), func(s []byte) error { - controllers := strings.Fields(string(s)) + err := parseFile(defaultFileReader, path.Join(cgroupRoot, controllersFile), func(s string) error { + controllers := strings.Fields(s) for _, c := range controllers { controllersMap[c] = struct{}{} } diff --git a/pkg/util/cgroups/readerv2_test.go b/pkg/util/cgroups/readerv2_test.go index a902b7343fbb4..f22e65185ba64 100644 --- a/pkg/util/cgroups/readerv2_test.go +++ b/pkg/util/cgroups/readerv2_test.go @@ -8,7 +8,6 @@ package cgroups import ( - "github.com/stretchr/testify/require" "os" "path/filepath" "testing" @@ -42,7 +41,6 @@ func TestReaderV2(t *testing.T) { } r, err := newReaderV2("", fakeFsPath, ContainerFilter) - require.NoError(t, err) r.pidMapper = nil assert.NoError(t, err) assert.NotNil(t, r)