Skip to content
70 changes: 70 additions & 0 deletions metric/system/cgroup/bytesutil/bytesutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 bytesutil

import (
"iter"
)

var asciiSpace = [256]bool{'\t': true, '\n': true, '\v': true, '\f': true, '\r': true, ' ': true}

// Fields returns an iterator that yields the fields of byte array b split around each single
// ASCII white space character (space, tab, newline, vertical tab, form feed, carriage return).
// It can be used with range loops like this:
//
// for i, field := range stringutil.Fields(b) {
// fmt.Printf("Field %d: %v\n", i, field)
// }
func Fields(b []byte) iter.Seq2[int, []byte] {
return func(yield func(int, []byte) bool) {
for i, bi := 0, 0; bi < len(b); i++ {
fieldStart := bi
// Find the end of the field
for bi < len(b) && !asciiSpace[b[bi]] {
bi++
}
if !yield(i, b[fieldStart:bi]) {
return
}
bi++
}
}
}

// Split returns an iterator that yields the fields of byte array b split around
// the given character.
// It can be used with range loops like this:
//
// for i, field := range stringutil.Split(b, ',') {
// fmt.Printf("Field %d: %v\n", i, field)
// }
func Split(b []byte, sep byte) iter.Seq2[int, []byte] {
return func(yield func(int, []byte) bool) {
for i, bi := 0, 0; bi < len(b); i++ {
fieldStart := bi
// Find the end of the field
for bi < len(b) && b[bi] != sep {
bi++
}
if !yield(i, b[fieldStart:bi]) {
return
}
bi++
}
}
}
96 changes: 96 additions & 0 deletions metric/system/cgroup/bytesutil/bytesutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 bytesutil

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestFields(t *testing.T) {
tests := []struct {
name string
input []byte
want []string
}{
{
name: "empty array",
input: []byte(""),
want: []string{},
},
{
name: "single white space",
input: []byte(" "),
want: []string{"", ""},
},
{
name: "single word",
input: []byte("hello"),
want: []string{"hello"},
},
{
name: "multiple words",
input: []byte("hello world this is a test"),
want: []string{"hello", "world", "this", "is", "a", "test"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for i, f := range Fields(tt.input) {
require.Equal(t, tt.want[i], string(f), "Fields() mismatch at index %d", i)
}
})
}
}

func TestSplit(t *testing.T) {
tests := []struct {
name string
input []byte
want []string
}{
{
name: "empty array",
input: []byte(""),
want: []string{},
},
{
name: "single separator",
input: []byte(","),
want: []string{"", ""},
},
{
name: "single word",
input: []byte("hello"),
want: []string{"hello"},
},
{
name: "multiple words",
input: []byte("hello,world,this,is,a,test"),
want: []string{"hello", "world", "this", "is", "a", "test"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for i, f := range Split(tt.input, ',') {
require.Equal(t, tt.want[i], string(f), "Split() mismatch at index %d", i)
}
})
}
}
8 changes: 8 additions & 0 deletions metric/system/cgroup/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"time"

"github.com/elastic/elastic-agent-libs/logp"

"github.com/elastic/elastic-agent-system-metrics/metric/system/cgroup/cgv1"
"github.com/elastic/elastic-agent-system-metrics/metric/system/cgroup/cgv2"
"github.com/elastic/elastic-agent-system-metrics/metric/system/resolve"
Expand Down Expand Up @@ -361,6 +362,7 @@ func (r *Reader) readControllerList(cgroupsFile string) ([]string, error) {
controllers := strings.Split(cgroupsFile, "\n")
var cgpath string
for _, controller := range controllers {
logp.L().Infof("controller: %s", controller)
if strings.Contains(controller, "0::/") {
fields := strings.Split(controller, ":")
cgpath = fields[2]
Expand All @@ -370,16 +372,22 @@ func (r *Reader) readControllerList(cgroupsFile string) ([]string, error) {
if cgpath == "" {
return []string{}, nil
}
cgpath = filepath.Clean(cgpath) // The path may have a relative prefix like "/../..`, which effectively is "/".
cgFilePath := filepath.Join(r.cgroupMountpoints.V2Loc, cgpath, "cgroup.controllers")
if cgroupNSStateFetch() && r.rootfsMountpoint.IsSet() {
logp.L().Infof("a) V2Loc: %s, ContainerizedRootMount %s", r.cgroupMountpoints.V2Loc, r.cgroupMountpoints.ContainerizedRootMount)
cgFilePath = filepath.Join(r.cgroupMountpoints.V2Loc, r.cgroupMountpoints.ContainerizedRootMount, cgpath, "cgroup.controllers")
} else {
logp.L().Infof("b) V2Loc: %s, ContainerizedRootMount %s", r.cgroupMountpoints.V2Loc, r.cgroupMountpoints.ContainerizedRootMount)
}

logp.L().Infof("reading %s", cgFilePath)
controllersRaw, err := os.ReadFile(cgFilePath)
if err != nil {
return nil, fmt.Errorf("error reading cgroup '%s': file %s: %w", cgpath, cgFilePath, err)
}

logp.L().Infof("controllersRaw: %s", controllersRaw)
if len(controllersRaw) == 0 {
return []string{}, nil
}
Expand Down
Loading
Loading