Skip to content

Commit

Permalink
Use ebpf.PossibleCPU to determine number of possible CPUs
Browse files Browse the repository at this point in the history
Use the existing functionality provided by the cilium/ebpf library. This
also makes sure that errors parsing the sysfs file are checked and will
lead to cell startup failing, rather than just logging an error.
Moreover, the underlying sysfs will only be parsed once and the result
is cached using sync.OnceValues.

Signed-off-by: Tobias Klauser <tobias@cilium.io>
  • Loading branch information
tklauser committed May 3, 2024
1 parent 84f158a commit 8552def
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 92 deletions.
3 changes: 0 additions & 3 deletions pkg/common/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,4 @@ const (
// EndpointStateFileName is used as the file name for the JSON representation
// of endpoint state.
EndpointStateFileName = "ep_config.json"

// PossibleCPUSysfsPath is used to retrieve the number of CPUs for per-CPU maps.
PossibleCPUSysfsPath = "/sys/devices/system/cpu/possible"
)
51 changes: 0 additions & 51 deletions pkg/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@ package common

import (
"fmt"
"io"
"os"
"strconv"
"strings"

"github.com/sirupsen/logrus"

"github.com/cilium/cilium/pkg/safeio"
)

// C2GoArray transforms an hexadecimal string representation into a byte slice.
Expand Down Expand Up @@ -89,49 +84,3 @@ func MapStringStructToSlice(m map[string]struct{}) []string {
}
return s
}

// GetNumPossibleCPUs returns a total number of possible CPUS, i.e. CPUs that
// have been allocated resources and can be brought online if they are present.
// The number is retrieved by parsing /sys/devices/system/cpu/possible.
//
// See https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/cpumask.h?h=v4.19#n50
// for more details.
func GetNumPossibleCPUs(log logrus.FieldLogger) int {
f, err := os.Open(PossibleCPUSysfsPath)
if err != nil {
log.WithError(err).Errorf("unable to open %q", PossibleCPUSysfsPath)
return 0
}
defer f.Close()

return getNumPossibleCPUsFromReader(log, f)
}

func getNumPossibleCPUsFromReader(log logrus.FieldLogger, r io.Reader) int {
out, err := safeio.ReadAllLimit(r, safeio.KB)
if err != nil {
log.WithError(err).Errorf("unable to read %q to get CPU count", PossibleCPUSysfsPath)
return 0
}

var start, end int
count := 0
for _, s := range strings.Split(string(out), ",") {
// Go's scanf will return an error if a format cannot be fully matched.
// So, just ignore it, as a partial match (e.g. when there is only one
// CPU) is expected.
n, err := fmt.Sscanf(s, "%d-%d", &start, &end)

switch n {
case 0:
log.WithError(err).Errorf("failed to scan %q to retrieve number of possible CPUs!", s)
return 0
case 1:
count++
default:
count += (end - start + 1)
}
}

return count
}
24 changes: 0 additions & 24 deletions pkg/common/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@
package common

import (
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/cilium/cilium/pkg/logging"
"github.com/cilium/cilium/pkg/logging/logfields"
)

func TestC2GoArray(t *testing.T) {
Expand Down Expand Up @@ -52,23 +48,3 @@ func TestGoArray2C(t *testing.T) {
require.Equal(t, test.output, GoArray2C(test.input))
}
}

func TestGetNumPossibleCPUsFromReader(t *testing.T) {
log := logging.DefaultLogger.WithField(logfields.LogSubsys, "utils-test")
tests := []struct {
in string
expected int
}{
{"0", 1},
{"0-7", 8},
{"0,2-3", 3},
{"", 0},
{"foobar", 0},
}

for _, tt := range tests {
possibleCpus := getNumPossibleCPUsFromReader(log, strings.NewReader(tt.in))
require.Equal(t, tt.expected, possibleCpus)
}

}
9 changes: 7 additions & 2 deletions pkg/datapath/loader/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/sirupsen/logrus"

"github.com/cilium/cilium/pkg/command/exec"
"github.com/cilium/cilium/pkg/common"
"github.com/cilium/cilium/pkg/datapath/linux/probes"
"github.com/cilium/cilium/pkg/datapath/types"
"github.com/cilium/cilium/pkg/lock"
Expand Down Expand Up @@ -98,7 +97,7 @@ type directoryInfo struct {

var (
standardCFlags = []string{"-O2", "--target=bpf", "-std=gnu89",
"-nostdinc", fmt.Sprintf("-D__NR_CPUS__=%d", common.GetNumPossibleCPUs(log)),
"-nostdinc",
"-Wall", "-Wextra", "-Werror", "-Wshadow",
"-Wno-address-of-packed-member",
"-Wno-unknown-warning-option",
Expand Down Expand Up @@ -165,6 +164,11 @@ func pidFromProcess(proc *os.Process) string {
//
// May output assembly or source code after prepocessing.
func compile(ctx context.Context, prog *progInfo, dir *directoryInfo) (string, error) {
possibleCPUs, err := ebpf.PossibleCPU()
if err != nil {
return "", fmt.Errorf("failed to get number of possible CPUs: %w", err)
}

compileArgs := append(testIncludes,
fmt.Sprintf("-I%s", path.Join(dir.Runtime, "globals")),
fmt.Sprintf("-I%s", dir.State),
Expand All @@ -180,6 +184,7 @@ func compile(ctx context.Context, prog *progInfo, dir *directoryInfo) (string, e
}

compileArgs = append(compileArgs, standardCFlags...)
compileArgs = append(compileArgs, fmt.Sprintf("-D__NR_CPUS__=%d", possibleCPUs))
compileArgs = append(compileArgs, "-mcpu="+getBPFCPU())
compileArgs = append(compileArgs, prog.Options...)
compileArgs = append(compileArgs,
Expand Down
12 changes: 7 additions & 5 deletions pkg/maps/eventsmap/cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ package eventsmap
import (
"fmt"

"github.com/cilium/ebpf"
"github.com/cilium/hive/cell"
"github.com/sirupsen/logrus"

"github.com/cilium/cilium/pkg/bpf"
"github.com/cilium/cilium/pkg/common"
)

// Cell provides eventsmap.Map, which is the hive representation of the cilium
Expand All @@ -28,13 +27,16 @@ var (

type Map interface{}

func newEventsMap(log logrus.FieldLogger, lifecycle cell.Lifecycle) bpf.MapOut[Map] {
func newEventsMap(lifecycle cell.Lifecycle) bpf.MapOut[Map] {
eventsMap := &eventsMap{}

lifecycle.Append(cell.Hook{
OnStart: func(context cell.HookContext) error {
cpus := common.GetNumPossibleCPUs(log)
err := eventsMap.init(cpus)
cpus, err := ebpf.PossibleCPU()
if err != nil {
return fmt.Errorf("failed to get number of possible CPUs: %w", err)
}
err = eventsMap.init(cpus)
if err != nil {
return fmt.Errorf("initializing events map: %w", err)
}
Expand Down
14 changes: 9 additions & 5 deletions pkg/maps/signalmap/cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
package signalmap

import (
"fmt"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/perf"
"github.com/cilium/hive/cell"
"github.com/sirupsen/logrus"

"github.com/cilium/cilium/pkg/bpf"
"github.com/cilium/cilium/pkg/common"
)

// Cell initializes and manages the config map.
Expand All @@ -34,8 +35,11 @@ type Map interface {
MapName() string
}

func newMap(log logrus.FieldLogger, lifecycle cell.Lifecycle) bpf.MapOut[Map] {
possibleCPUs := common.GetNumPossibleCPUs(log)
func newMap(lifecycle cell.Lifecycle) (bpf.MapOut[Map], error) {
possibleCPUs, err := ebpf.PossibleCPU()
if err != nil {
return bpf.MapOut[Map]{}, fmt.Errorf("failed to get number of possible CPUs: %w", err)
}
signalmap := initMap(possibleCPUs)

lifecycle.Append(cell.Hook{
Expand All @@ -47,5 +51,5 @@ func newMap(log logrus.FieldLogger, lifecycle cell.Lifecycle) bpf.MapOut[Map] {
},
})

return bpf.NewMapOut(Map(signalmap))
return bpf.NewMapOut(Map(signalmap)), nil
}
9 changes: 7 additions & 2 deletions pkg/monitor/agent/cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"context"
"fmt"

"github.com/cilium/ebpf"
"github.com/cilium/hive/cell"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"

"github.com/cilium/cilium/pkg/common"
"github.com/cilium/cilium/pkg/defaults"
"github.com/cilium/cilium/pkg/maps/eventsmap"
)
Expand Down Expand Up @@ -74,7 +74,12 @@ func newMonitorAgent(params agentParams) Agent {
if params.Config.EnableMonitor {
queueSize := params.Config.MonitorQueueSize
if queueSize == 0 {
queueSize = common.GetNumPossibleCPUs(log) * defaults.MonitorQueueSizePerCPU
possibleCPUs, err := ebpf.PossibleCPU()
if err != nil {
log.WithError(err).Error("failed to get number of possible CPUs")
return fmt.Errorf("failed to get number of possible CPUs: %w", err)
}
queueSize = possibleCPUs * defaults.MonitorQueueSizePerCPU
if queueSize > defaults.MonitorQueueSizePerCPUMaximum {
queueSize = defaults.MonitorQueueSizePerCPUMaximum
}
Expand Down

0 comments on commit 8552def

Please sign in to comment.