mirror of
https://github.com/opencontainers/runc.git
synced 2025-12-24 11:50:58 +08:00
libcontainer/intelrdt: add support for EnableMonitoring field
The linux.intelRdt.enableMonitoring field enables the creation of a per-container monitoring group. The monitoring group is removed when the container is destroyed. Signed-off-by: Markus Lehtonen <markus.lehtonen@intel.com>
This commit is contained in:
@@ -56,8 +56,9 @@ var featuresCommand = cli.Command{
|
|||||||
Enabled: &t,
|
Enabled: &t,
|
||||||
},
|
},
|
||||||
IntelRdt: &features.IntelRdt{
|
IntelRdt: &features.IntelRdt{
|
||||||
Enabled: &t,
|
Enabled: &t,
|
||||||
Schemata: &t,
|
Schemata: &t,
|
||||||
|
Monitoring: &t,
|
||||||
},
|
},
|
||||||
MountExtensions: &features.MountExtensions{
|
MountExtensions: &features.MountExtensions{
|
||||||
IDMap: &features.IDMap{
|
IDMap: &features.IDMap{
|
||||||
|
|||||||
@@ -17,4 +17,7 @@ type IntelRdt struct {
|
|||||||
// The unit of memory bandwidth is specified in "percentages" by
|
// The unit of memory bandwidth is specified in "percentages" by
|
||||||
// default, and in "MBps" if MBA Software Controller is enabled.
|
// default, and in "MBps" if MBA Software Controller is enabled.
|
||||||
MemBwSchema string `json:"memBwSchema,omitempty"`
|
MemBwSchema string `json:"memBwSchema,omitempty"`
|
||||||
|
|
||||||
|
// Create a monitoring group for the container.
|
||||||
|
EnableMonitoring bool `json:"enableMonitoring,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,22 +19,17 @@ func TestValidateIntelRdt(t *testing.T) {
|
|||||||
isErr bool
|
isErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "rdt not supported, no config",
|
name: "rdt not supported, no config",
|
||||||
rdtEnabled: false,
|
|
||||||
config: nil,
|
|
||||||
isErr: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "rdt not supported, with config",
|
name: "rdt not supported, with config",
|
||||||
rdtEnabled: false,
|
config: &configs.IntelRdt{},
|
||||||
config: &configs.IntelRdt{},
|
isErr: true,
|
||||||
isErr: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty config",
|
name: "empty config",
|
||||||
rdtEnabled: true,
|
rdtEnabled: true,
|
||||||
config: &configs.IntelRdt{},
|
config: &configs.IntelRdt{},
|
||||||
isErr: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "root clos",
|
name: "root clos",
|
||||||
@@ -42,7 +37,6 @@ func TestValidateIntelRdt(t *testing.T) {
|
|||||||
config: &configs.IntelRdt{
|
config: &configs.IntelRdt{
|
||||||
ClosID: "/",
|
ClosID: "/",
|
||||||
},
|
},
|
||||||
isErr: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid ClosID (.)",
|
name: "invalid ClosID (.)",
|
||||||
@@ -71,7 +65,6 @@ func TestValidateIntelRdt(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "cat not supported",
|
name: "cat not supported",
|
||||||
rdtEnabled: true,
|
rdtEnabled: true,
|
||||||
catEnabled: false,
|
|
||||||
config: &configs.IntelRdt{
|
config: &configs.IntelRdt{
|
||||||
L3CacheSchema: "0=ff",
|
L3CacheSchema: "0=ff",
|
||||||
},
|
},
|
||||||
@@ -80,7 +73,6 @@ func TestValidateIntelRdt(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "mba not supported",
|
name: "mba not supported",
|
||||||
rdtEnabled: true,
|
rdtEnabled: true,
|
||||||
mbaEnabled: false,
|
|
||||||
config: &configs.IntelRdt{
|
config: &configs.IntelRdt{
|
||||||
MemBwSchema: "0=100",
|
MemBwSchema: "0=100",
|
||||||
},
|
},
|
||||||
@@ -96,7 +88,6 @@ func TestValidateIntelRdt(t *testing.T) {
|
|||||||
L3CacheSchema: "0=ff",
|
L3CacheSchema: "0=ff",
|
||||||
MemBwSchema: "0=100",
|
MemBwSchema: "0=100",
|
||||||
},
|
},
|
||||||
isErr: false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ type State struct {
|
|||||||
|
|
||||||
// Intel RDT "resource control" filesystem path.
|
// Intel RDT "resource control" filesystem path.
|
||||||
IntelRdtPath string `json:"intel_rdt_path,omitempty"`
|
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
|
// ID returns the container's unique ID
|
||||||
@@ -942,8 +947,10 @@ func (c *Container) currentState() *State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
intelRdtPath := ""
|
intelRdtPath := ""
|
||||||
|
intelRdtMonPath := ""
|
||||||
if c.intelRdtManager != nil {
|
if c.intelRdtManager != nil {
|
||||||
intelRdtPath = c.intelRdtManager.GetPath()
|
intelRdtPath = c.intelRdtManager.GetPath()
|
||||||
|
intelRdtMonPath = c.intelRdtManager.GetMonPath()
|
||||||
}
|
}
|
||||||
state := &State{
|
state := &State{
|
||||||
BaseState: BaseState{
|
BaseState: BaseState{
|
||||||
@@ -956,6 +963,7 @@ func (c *Container) currentState() *State {
|
|||||||
Rootless: c.config.RootlessEUID && c.config.RootlessCgroups,
|
Rootless: c.config.RootlessEUID && c.config.RootlessCgroups,
|
||||||
CgroupPaths: c.cgroupManager.GetPaths(),
|
CgroupPaths: c.cgroupManager.GetPaths(),
|
||||||
IntelRdtPath: intelRdtPath,
|
IntelRdtPath: intelRdtPath,
|
||||||
|
IntelRdtMonPath: intelRdtMonPath,
|
||||||
NamespacePaths: make(map[configs.NamespaceType]string),
|
NamespacePaths: make(map[configs.NamespaceType]string),
|
||||||
ExternalDescriptors: externalDescriptors,
|
ExternalDescriptors: externalDescriptors,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,22 +468,41 @@ func (m *Manager) Apply(pid int) (err error) {
|
|||||||
return newLastCmdError(err)
|
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
|
m.path = path
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy destroys the Intel RDT container-specific container_id group.
|
// Destroy destroys the Intel RDT container-specific container_id group.
|
||||||
func (m *Manager) Destroy() error {
|
func (m *Manager) Destroy() error {
|
||||||
|
if m.config.IntelRdt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// Don't remove resctrl group if closid has been explicitly specified. The
|
// 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.
|
// group is likely externally managed, i.e. by some other entity than us.
|
||||||
// There are probably other containers/tasks sharing the same group.
|
// There are probably other containers/tasks sharing the same group.
|
||||||
if m.config.IntelRdt != nil && m.config.IntelRdt.ClosID == "" {
|
if m.config.IntelRdt.ClosID == "" {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
if err := os.Remove(m.GetPath()); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(m.GetPath()); err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.path = ""
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -494,6 +513,21 @@ func (m *Manager) GetPath() string {
|
|||||||
return m.path
|
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 !m.config.IntelRdt.EnableMonitoring {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
closPath := m.GetPath()
|
||||||
|
if closPath == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(closPath, "mon_groups", m.id)
|
||||||
|
}
|
||||||
|
|
||||||
// GetStats returns statistics for Intel RDT.
|
// GetStats returns statistics for Intel RDT.
|
||||||
func (m *Manager) GetStats() (*Stats, error) {
|
func (m *Manager) GetStats() (*Stats, error) {
|
||||||
// If intelRdt is not specified in config
|
// If intelRdt is not specified in config
|
||||||
@@ -573,7 +607,16 @@ func (m *Manager) GetStats() (*Stats, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if IsMBMEnabled() || IsCMTEnabled() {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -126,31 +127,176 @@ func TestIntelRdtSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestApply(t *testing.T) {
|
func TestApply(t *testing.T) {
|
||||||
helper := NewIntelRdtTestUtil(t)
|
const pid = 1234
|
||||||
|
tests := []struct {
|
||||||
const closID = "test-clos"
|
name string
|
||||||
closPath := filepath.Join(helper.IntelRdtPath, closID)
|
config configs.IntelRdt
|
||||||
|
precreateClos bool
|
||||||
helper.config.IntelRdt.ClosID = closID
|
isError bool
|
||||||
intelrdt := newManager(helper.config, "container-1", closPath)
|
postApplyAssert func(*Manager)
|
||||||
if err := intelrdt.Apply(1234); err == nil {
|
}{
|
||||||
t.Fatal("unexpected success when applying pid")
|
{
|
||||||
}
|
name: "failure because non-pre-existing CLOS",
|
||||||
if _, err := os.Stat(closPath); err == nil {
|
config: configs.IntelRdt{
|
||||||
t.Fatal("closid dir should not exist")
|
ClosID: "non-existing-clos",
|
||||||
|
},
|
||||||
|
isError: true,
|
||||||
|
postApplyAssert: func(m *Manager) {
|
||||||
|
if _, err := os.Stat(m.path); err == nil {
|
||||||
|
t.Fatal("closid dir should not exist")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CLOS dir should be created if some schema has been specified",
|
||||||
|
config: configs.IntelRdt{
|
||||||
|
ClosID: "clos-to-be-created",
|
||||||
|
L3CacheSchema: "L3:0=f",
|
||||||
|
},
|
||||||
|
postApplyAssert: func(m *Manager) {
|
||||||
|
pids, err := getIntelRdtParamString(m.path, "tasks")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read tasks file: %v", err)
|
||||||
|
}
|
||||||
|
if pids != strconv.Itoa(pid) {
|
||||||
|
t.Fatalf("unexpected tasks file, expected '%d', got %q", pid, pids)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clos and monitoring group should be created if EnableMonitoring is true",
|
||||||
|
config: configs.IntelRdt{
|
||||||
|
EnableMonitoring: true,
|
||||||
|
},
|
||||||
|
precreateClos: true,
|
||||||
|
postApplyAssert: func(m *Manager) {
|
||||||
|
pids, err := getIntelRdtParamString(m.path, "tasks")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read tasks file: %v", err)
|
||||||
|
}
|
||||||
|
if pids != strconv.Itoa(pid) {
|
||||||
|
t.Fatalf("unexpected tasks file, expected '%d', got %q", pid, pids)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dir should be created if some schema has been specified
|
for _, tt := range tests {
|
||||||
intelrdt.config.IntelRdt.L3CacheSchema = "L3:0=f"
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := intelrdt.Apply(1235); err != nil {
|
NewIntelRdtTestUtil(t)
|
||||||
t.Fatalf("Apply() failed: %v", err)
|
id := "abcd-1234"
|
||||||
}
|
closPath := filepath.Join(intelRdtRoot, id)
|
||||||
|
if tt.config.ClosID != "" {
|
||||||
|
closPath = filepath.Join(intelRdtRoot, tt.config.ClosID)
|
||||||
|
}
|
||||||
|
|
||||||
pids, err := getIntelRdtParamString(intelrdt.GetPath(), "tasks")
|
if tt.precreateClos {
|
||||||
if err != nil {
|
if err := os.MkdirAll(filepath.Join(closPath, "mon_groups"), 0o755); err != nil {
|
||||||
t.Fatalf("failed to read tasks file: %v", err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if pids != "1235" {
|
}
|
||||||
t.Fatalf("unexpected tasks file, expected '1235', got %q", pids)
|
m := newManager(&configs.Config{IntelRdt: &tt.config}, id, closPath)
|
||||||
|
err := m.Apply(pid)
|
||||||
|
if tt.isError && err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
} else if !tt.isError && err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
tt.postApplyAssert(m)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestroy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config configs.IntelRdt
|
||||||
|
testFunc func(*Manager)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "per-container CLOS dir should be removed",
|
||||||
|
testFunc: func(m *Manager) {
|
||||||
|
closPath := m.path
|
||||||
|
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
|
||||||
|
if err := os.Remove(filepath.Join(closPath, "tasks")); err != nil {
|
||||||
|
t.Fatalf("failed to remove tasks file: %v", err)
|
||||||
|
}
|
||||||
|
if err := m.Destroy(); err != nil {
|
||||||
|
t.Fatalf("Destroy() failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(closPath); err == nil {
|
||||||
|
t.Fatal("CLOS dir should not exist")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre-existing CLOS should not be removed",
|
||||||
|
config: configs.IntelRdt{
|
||||||
|
ClosID: "pre-existing-clos",
|
||||||
|
},
|
||||||
|
testFunc: func(m *Manager) {
|
||||||
|
closPath := m.path
|
||||||
|
|
||||||
|
if _, err := os.Stat(closPath); err != nil {
|
||||||
|
t.Fatal("CLOS dir should exist")
|
||||||
|
}
|
||||||
|
if err := m.Destroy(); err != nil {
|
||||||
|
t.Fatalf("Destroy() failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(closPath); err != nil {
|
||||||
|
t.Fatal("CLOS dir should exist")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "per-container MON dir in pre-existing CLOS should be removed",
|
||||||
|
config: configs.IntelRdt{
|
||||||
|
ClosID: "pre-existing-clos",
|
||||||
|
EnableMonitoring: true,
|
||||||
|
},
|
||||||
|
testFunc: func(m *Manager) {
|
||||||
|
closPath := m.path
|
||||||
|
|
||||||
|
monPath := filepath.Join(closPath, "mon_groups", m.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 := m.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")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
NewIntelRdtTestUtil(t)
|
||||||
|
|
||||||
|
id := "abcd-1234"
|
||||||
|
closPath := filepath.Join(intelRdtRoot, id)
|
||||||
|
if tt.config.ClosID != "" {
|
||||||
|
closPath = filepath.Join(intelRdtRoot, tt.config.ClosID)
|
||||||
|
// Pre-create the CLOS directory
|
||||||
|
if err := os.MkdirAll(filepath.Join(closPath, "mon_groups"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m := newManager(&configs.Config{IntelRdt: &tt.config}, id, closPath)
|
||||||
|
if err := m.Apply(1234); err != nil {
|
||||||
|
t.Fatalf("Apply() failed: %v", err)
|
||||||
|
}
|
||||||
|
tt.testFunc(m)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,10 +462,11 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
|
|||||||
}
|
}
|
||||||
if spec.Linux.IntelRdt != nil {
|
if spec.Linux.IntelRdt != nil {
|
||||||
config.IntelRdt = &configs.IntelRdt{
|
config.IntelRdt = &configs.IntelRdt{
|
||||||
ClosID: spec.Linux.IntelRdt.ClosID,
|
ClosID: spec.Linux.IntelRdt.ClosID,
|
||||||
Schemata: spec.Linux.IntelRdt.Schemata,
|
Schemata: spec.Linux.IntelRdt.Schemata,
|
||||||
L3CacheSchema: spec.Linux.IntelRdt.L3CacheSchema,
|
L3CacheSchema: spec.Linux.IntelRdt.L3CacheSchema,
|
||||||
MemBwSchema: spec.Linux.IntelRdt.MemBwSchema,
|
MemBwSchema: spec.Linux.IntelRdt.MemBwSchema,
|
||||||
|
EnableMonitoring: spec.Linux.IntelRdt.EnableMonitoring,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if spec.Linux.Personality != nil {
|
if spec.Linux.Personality != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user