Skip to content

libcontainer/intelrdt: add support for EnableMonitoring field #4832

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion features.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ var featuresCommand = cli.Command{
Enabled: &t,
},
IntelRdt: &features.IntelRdt{
Enabled: &t,
Enabled: &t,
Monitoring: &t,
},
MountExtensions: &features.MountExtensions{
IDMap: &features.IDMap{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/moby/sys/userns v0.1.0
github.com/mrunalp/fileutils v0.5.1
github.com/opencontainers/cgroups v0.0.4
github.com/opencontainers/runtime-spec v1.2.2-0.20250401095657-e935f995dd67
github.com/opencontainers/runtime-spec v1.2.2-0.20250818071321-383cadbf08c0
github.com/opencontainers/selinux v1.12.0
github.com/seccomp/libseccomp-golang v0.11.1
github.com/sirupsen/logrus v1.9.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm
github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/opencontainers/cgroups v0.0.4 h1:XVj8P/IHVms/j+7eh8ggdkTLAxjz84ZzuFyGoE28DR4=
github.com/opencontainers/cgroups v0.0.4/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs=
github.com/opencontainers/runtime-spec v1.2.2-0.20250401095657-e935f995dd67 h1:Q+KewUGTMamIe6Q39xCD/T1NC1POmaTlWnhjikCrZHA=
github.com/opencontainers/runtime-spec v1.2.2-0.20250401095657-e935f995dd67/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.2.2-0.20250818071321-383cadbf08c0 h1:RLn0YfUWkiqPGtgUANvJrcjIkCHGRl3jcz/c557M28M=
github.com/opencontainers/runtime-spec v1.2.2-0.20250818071321-383cadbf08c0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
3 changes: 3 additions & 0 deletions libcontainer/configs/intelrdt.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ type IntelRdt struct {
// The unit of memory bandwidth is specified in "percentages" by
// default, and in "MBps" if MBA Software Controller is enabled.
MemBwSchema string `json:"memBwSchema,omitempty"`

// Create a monitoring group for the container.
EnableMonitoring bool `json:"enableMonitoring,omitempty"`
}
8 changes: 8 additions & 0 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ type State struct {

// Intel RDT "resource control" filesystem path.
IntelRdtPath string `json:"intel_rdt_path,omitempty"`

// Path of the container specific monitoring group in resctrl filesystem.
// Empty if the container does not have aindividual dedicated monitoring
// group.
IntelRdtMonPath string `json:"intel_rdt_mon_path,omitempty"`
}

// ID returns the container's unique ID
Expand Down Expand Up @@ -938,8 +943,10 @@ func (c *Container) currentState() *State {
}

intelRdtPath := ""
intelRdtMonPath := ""
if c.intelRdtManager != nil {
intelRdtPath = c.intelRdtManager.GetPath()
intelRdtMonPath = c.intelRdtManager.GetMonPath()
}
state := &State{
BaseState: BaseState{
Expand All @@ -952,6 +959,7 @@ func (c *Container) currentState() *State {
Rootless: c.config.RootlessEUID && c.config.RootlessCgroups,
CgroupPaths: c.cgroupManager.GetPaths(),
IntelRdtPath: intelRdtPath,
IntelRdtMonPath: intelRdtMonPath,
NamespacePaths: make(map[configs.NamespaceType]string),
ExternalDescriptors: externalDescriptors,
}
Expand Down
56 changes: 49 additions & 7 deletions libcontainer/intelrdt/intelrdt.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"sync"

securejoin "github.com/cyphar/filepath-securejoin"
"github.com/moby/sys/mountinfo"
"golang.org/x/sys/unix"

Expand Down Expand Up @@ -480,6 +481,16 @@ func (m *Manager) Apply(pid int) (err error) {
return newLastCmdError(err)
}

// Create MON group
if monPath := m.GetMonPath(); monPath != "" {
if err := os.Mkdir(monPath, 0o755); err != nil && !os.IsExist(err) {
return newLastCmdError(err)
}
if err := WriteIntelRdtTasks(monPath, pid); err != nil {
return newLastCmdError(err)
}
}

m.path = path
return nil
}
Expand All @@ -489,13 +500,21 @@ func (m *Manager) Destroy() error {
// Don't remove resctrl group if closid has been explicitly specified. The
// group is likely externally managed, i.e. by some other entity than us.
// There are probably other containers/tasks sharing the same group.
if m.config.IntelRdt != nil && m.config.IntelRdt.ClosID == "" {
m.mu.Lock()
defer m.mu.Unlock()
if err := os.Remove(m.GetPath()); err != nil && !os.IsNotExist(err) {
return err
if m.config.IntelRdt != nil {
if m.config.IntelRdt.ClosID == "" {
m.mu.Lock()
defer m.mu.Unlock()
if err := os.Remove(m.GetPath()); err != nil && !os.IsNotExist(err) {
return err
}
m.path = ""
} else if monPath := m.GetMonPath(); monPath != "" {
// If ClosID is not specified the possible monintoring group was
// removed with the CLOS above.
if err := os.Remove(monPath); err != nil && !os.IsNotExist(err) {
return err
}
}
m.path = ""
}
return nil
}
Expand All @@ -509,6 +528,20 @@ func (m *Manager) GetPath() string {
return m.path
}

// GetMonPath returns path of the monitoring group of the container. Returns an
// empty string if the container does not have a individual dedicated
// monitoring group.
func (m *Manager) GetMonPath() string {
if closPath := m.GetPath(); closPath != "" && m.config.IntelRdt.EnableMonitoring {
path, err := securejoin.SecureJoin(filepath.Join(closPath, "mon_groups"), m.id)
if err != nil {
return ""
}
return path
}
return ""
}

// GetStats returns statistics for Intel RDT.
func (m *Manager) GetStats() (*Stats, error) {
// If intelRdt is not specified in config
Expand Down Expand Up @@ -586,7 +619,16 @@ func (m *Manager) GetStats() (*Stats, error) {
}

if IsMBMEnabled() || IsCMTEnabled() {
err = getMonitoringStats(containerPath, stats)
monPath := m.GetMonPath()
if monPath == "" {
// NOTE: If per-container monitoring is not enabled, the monitoring
// data we get here might have little to do with this container as
// there might be anything from this single container to the half
// of the system assigned in the group. Should consider not
// exposing stats in this case(?)
monPath = containerPath
}
err = getMonitoringStats(monPath, stats)
if err != nil {
return nil, err
}
Expand Down
163 changes: 146 additions & 17 deletions libcontainer/intelrdt/intelrdt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"path/filepath"
"strings"
"testing"

"github.com/opencontainers/runc/libcontainer/configs"
)

func TestIntelRdtSetL3CacheSchema(t *testing.T) {
Expand Down Expand Up @@ -98,30 +100,157 @@ func TestIntelRdtSetMemBwScSchema(t *testing.T) {
}

func TestApply(t *testing.T) {
helper := NewIntelRdtTestUtil(t)

const closID = "test-clos"
// TC-1: failure because non-pre-existing CLOS
{
helper := NewIntelRdtTestUtil(t)
helper.config.IntelRdt = &configs.IntelRdt{
ClosID: closID,
}

helper.config.IntelRdt.ClosID = closID
intelrdt := newManager(helper.config, "", helper.IntelRdtPath)
if err := intelrdt.Apply(1234); err == nil {
t.Fatal("unexpected success when applying pid")
intelrdt := newManager(helper.config, "", "")
if err := intelrdt.Apply(1234); err == nil {
t.Fatal("unexpected success when applying pid")
}
closPath := filepath.Join(intelRdtRoot, closID)
if _, err := os.Stat(closPath); err == nil {
t.Fatal("closid dir should not exist")
}
}
if _, err := os.Stat(filepath.Join(helper.IntelRdtPath, closID)); err == nil {
t.Fatal("closid dir should not exist")
// TC-2: CLOS dir should be created if some schema has been specified
{
helper := NewIntelRdtTestUtil(t)
helper.config.IntelRdt = &configs.IntelRdt{
ClosID: closID,
L3CacheSchema: "L3:0=f",
}

intelrdt := newManager(helper.config, "", "")
if err := intelrdt.Apply(1235); err != nil {
t.Fatalf("Apply() failed: %v", err)
}

closPath := filepath.Join(intelRdtRoot, closID)
pids, err := getIntelRdtParamString(closPath, "tasks")
if err != nil {
t.Fatalf("failed to read tasks file: %v", err)
}
if pids != "1235" {
t.Fatalf("unexpected tasks file, expected '1235', got %q", pids)
}
}
// TC-3: clos and monitoring group should be created if EnableMonitoring is true
{
helper := NewIntelRdtTestUtil(t)
helper.config.IntelRdt = &configs.IntelRdt{
EnableMonitoring: true,
}
id := "aaaa-bbbb"

intelrdt := newManager(helper.config, id, "")
// We need to pre-create the CLOS/mon_groups directory
closPath := filepath.Join(intelRdtRoot, id)
if err := os.MkdirAll(filepath.Join(closPath, "mon_groups"), 0o755); err != nil {
t.Fatal(err)
}

if err := intelrdt.Apply(1236); err != nil {
t.Fatalf("Apply() failed: %v", err)
}

pids, err := getIntelRdtParamString(closPath, "tasks")
if err != nil {
t.Fatalf("failed to read tasks file: %v", err)
}
if pids != "1236" {
t.Fatalf("unexpected tasks file, expected '1236', got %q", pids)
}
}
}

func TestDestroy(t *testing.T) {
const closID = "test-clos"

// TC-1: per-container CLOS dir should be removed
{
helper := NewIntelRdtTestUtil(t)
id := "abcd-efgh"

// Dir should be created if some schema has been specified
intelrdt.config.IntelRdt.L3CacheSchema = "L3:0=f"
if err := intelrdt.Apply(1235); err != nil {
t.Fatalf("Apply() failed: %v", err)
intelrdt := newManager(helper.config, id, "")
if err := intelrdt.Apply(1234); err != nil {
t.Fatalf("Apply() failed: %v", err)
}
closPath := filepath.Join(intelRdtRoot, id)
if _, err := os.Stat(closPath); err != nil {
t.Fatal("CLOS dir should exist")
}
// Need to delete the tasks file so that the dir is empty
os.Remove(filepath.Join(closPath, "tasks"))
if err := intelrdt.Destroy(); err != nil {
t.Fatalf("Destroy() failed: %v", err)
}
if _, err := os.Stat(closPath); err == nil {
t.Fatal("CLOS dir should not exist")
}
}
// TC-2: pre-existing CLOS should not be removed
{
helper := NewIntelRdtTestUtil(t)
helper.config.IntelRdt = &configs.IntelRdt{
ClosID: closID,
}

pids, err := getIntelRdtParamString(intelrdt.GetPath(), "tasks")
if err != nil {
t.Fatalf("failed to read tasks file: %v", err)
closPath := filepath.Join(intelRdtRoot, closID)
if err := os.MkdirAll(closPath, 0o755); err != nil {
t.Fatal(err)
}

intelrdt := newManager(helper.config, "", "")
if err := intelrdt.Apply(1234); err != nil {
t.Fatalf("Apply() failed: %v", err)
}
if _, err := os.Stat(closPath); err != nil {
t.Fatal("CLOS dir should exist")
}
if err := intelrdt.Destroy(); err != nil {
t.Fatalf("Destroy() failed: %v", err)
}
if _, err := os.Stat(closPath); err != nil {
t.Fatal("CLOS dir should exist")
}
}
if pids != "1235" {
t.Fatalf("unexpected tasks file, expected '1235', got %q", pids)
// TC-3: per-container MON dir in pre-existing CLOS should be removed
{
helper := NewIntelRdtTestUtil(t)
helper.config.IntelRdt = &configs.IntelRdt{
ClosID: closID,
EnableMonitoring: true,
}
id := "abcd-efgh"

closPath := filepath.Join(intelRdtRoot, closID)
if err := os.MkdirAll(filepath.Join(closPath, "mon_groups"), 0o755); err != nil {
t.Fatal(err)
}

intelrdt := newManager(helper.config, id, "")
if err := intelrdt.Apply(1234); err != nil {
t.Fatalf("Apply() failed: %v", err)
}
monPath := filepath.Join(closPath, "mon_groups", id)
if _, err := os.Stat(monPath); err != nil {
t.Fatal("MON dir should exist")
}
// Need to delete the tasks file so that the dir is empty
os.Remove(filepath.Join(monPath, "tasks"))
if err := intelrdt.Destroy(); err != nil {
t.Fatalf("Destroy() failed: %v", err)
}
if _, err := os.Stat(closPath); err != nil {
t.Fatalf("CLOS dir should exist: %f", err)
}
if _, err := os.Stat(monPath); err == nil {
t.Fatal("MON dir should not exist")
}
}
}
7 changes: 4 additions & 3 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,9 +462,10 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
}
if spec.Linux.IntelRdt != nil {
config.IntelRdt = &configs.IntelRdt{
ClosID: spec.Linux.IntelRdt.ClosID,
L3CacheSchema: spec.Linux.IntelRdt.L3CacheSchema,
MemBwSchema: spec.Linux.IntelRdt.MemBwSchema,
ClosID: spec.Linux.IntelRdt.ClosID,
L3CacheSchema: spec.Linux.IntelRdt.L3CacheSchema,
MemBwSchema: spec.Linux.IntelRdt.MemBwSchema,
EnableMonitoring: spec.Linux.IntelRdt.EnableMonitoring,
}
}
if spec.Linux.Personality != nil {
Expand Down
Loading
Loading