Skip to content

Commit eb6f5ec

Browse files
committed
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 <[email protected]>
1 parent 5b8711b commit eb6f5ec

File tree

7 files changed

+234
-39
lines changed

7 files changed

+234
-39
lines changed

features.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ var featuresCommand = cli.Command{
5656
Enabled: &t,
5757
},
5858
IntelRdt: &features.IntelRdt{
59-
Enabled: &t,
59+
Enabled: &t,
60+
Monitoring: &t,
6061
},
6162
MountExtensions: &features.MountExtensions{
6263
IDMap: &features.IDMap{

libcontainer/configs/intelrdt.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ type IntelRdt struct {
1313
// The unit of memory bandwidth is specified in "percentages" by
1414
// default, and in "MBps" if MBA Software Controller is enabled.
1515
MemBwSchema string `json:"memBwSchema,omitempty"`
16+
17+
// Create a monitoring group for the container.
18+
EnableMonitoring bool `json:"enableMonitoring,omitempty"`
1619
}

libcontainer/configs/validate/intelrdt_test.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,24 @@ func TestValidateIntelRdt(t *testing.T) {
1919
isErr bool
2020
}{
2121
{
22-
name: "rdt not supported, no config",
23-
rdtEnabled: false,
24-
config: nil,
25-
isErr: false,
22+
name: "rdt not supported, no config",
2623
},
2724
{
28-
name: "rdt not supported, with config",
29-
rdtEnabled: false,
30-
config: &configs.IntelRdt{},
31-
isErr: true,
25+
name: "rdt not supported, with config",
26+
config: &configs.IntelRdt{},
27+
isErr: true,
3228
},
3329
{
3430
name: "empty config",
3531
rdtEnabled: true,
3632
config: &configs.IntelRdt{},
37-
isErr: false,
3833
},
3934
{
4035
name: "root clos",
4136
rdtEnabled: true,
4237
config: &configs.IntelRdt{
4338
ClosID: "/",
4439
},
45-
isErr: false,
4640
},
4741
{
4842
name: "invalid ClosID (.)",
@@ -71,7 +65,6 @@ func TestValidateIntelRdt(t *testing.T) {
7165
{
7266
name: "cat not supported",
7367
rdtEnabled: true,
74-
catEnabled: false,
7568
config: &configs.IntelRdt{
7669
L3CacheSchema: "0=ff",
7770
},
@@ -80,7 +73,6 @@ func TestValidateIntelRdt(t *testing.T) {
8073
{
8174
name: "mba not supported",
8275
rdtEnabled: true,
83-
mbaEnabled: false,
8476
config: &configs.IntelRdt{
8577
MemBwSchema: "0=100",
8678
},
@@ -96,7 +88,6 @@ func TestValidateIntelRdt(t *testing.T) {
9688
L3CacheSchema: "0=ff",
9789
MemBwSchema: "0=100",
9890
},
99-
isErr: false,
10091
},
10192
}
10293
for _, tc := range testCases {

libcontainer/container_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ type State struct {
7373

7474
// Intel RDT "resource control" filesystem path.
7575
IntelRdtPath string `json:"intel_rdt_path,omitempty"`
76+
77+
// Path of the container specific monitoring group in resctrl filesystem.
78+
// Empty if the container does not have aindividual dedicated monitoring
79+
// group.
80+
IntelRdtMonPath string `json:"intel_rdt_mon_path,omitempty"`
7681
}
7782

7883
// ID returns the container's unique ID
@@ -942,8 +947,10 @@ func (c *Container) currentState() *State {
942947
}
943948

944949
intelRdtPath := ""
950+
intelRdtMonPath := ""
945951
if c.intelRdtManager != nil {
946952
intelRdtPath = c.intelRdtManager.GetPath()
953+
intelRdtMonPath = c.intelRdtManager.GetMonPath()
947954
}
948955
state := &State{
949956
BaseState: BaseState{
@@ -956,6 +963,7 @@ func (c *Container) currentState() *State {
956963
Rootless: c.config.RootlessEUID && c.config.RootlessCgroups,
957964
CgroupPaths: c.cgroupManager.GetPaths(),
958965
IntelRdtPath: intelRdtPath,
966+
IntelRdtMonPath: intelRdtMonPath,
959967
NamespacePaths: make(map[configs.NamespaceType]string),
960968
ExternalDescriptors: externalDescriptors,
961969
}

libcontainer/intelrdt/intelrdt.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,22 +478,41 @@ func (m *Manager) Apply(pid int) (err error) {
478478
return newLastCmdError(err)
479479
}
480480

481+
// Create MON group
482+
if monPath := m.GetMonPath(); monPath != "" {
483+
if err := os.Mkdir(monPath, 0o755); err != nil && !os.IsExist(err) {
484+
return newLastCmdError(err)
485+
}
486+
if err := WriteIntelRdtTasks(monPath, pid); err != nil {
487+
return newLastCmdError(err)
488+
}
489+
}
490+
481491
m.path = path
482492
return nil
483493
}
484494

485495
// Destroy destroys the Intel RDT container-specific container_id group.
486496
func (m *Manager) Destroy() error {
497+
if m.config.IntelRdt == nil {
498+
return nil
499+
}
487500
// Don't remove resctrl group if closid has been explicitly specified. The
488501
// group is likely externally managed, i.e. by some other entity than us.
489502
// There are probably other containers/tasks sharing the same group.
490-
if m.config.IntelRdt != nil && m.config.IntelRdt.ClosID == "" {
503+
if m.config.IntelRdt.ClosID == "" {
491504
m.mu.Lock()
492505
defer m.mu.Unlock()
493506
if err := os.Remove(m.GetPath()); err != nil && !os.IsNotExist(err) {
494507
return err
495508
}
496509
m.path = ""
510+
} else if monPath := m.GetMonPath(); monPath != "" {
511+
// If ClosID is not specified the possible monintoring group was
512+
// removed with the CLOS above.
513+
if err := os.Remove(monPath); err != nil && !os.IsNotExist(err) {
514+
return err
515+
}
497516
}
498517
return nil
499518
}
@@ -504,6 +523,21 @@ func (m *Manager) GetPath() string {
504523
return m.path
505524
}
506525

526+
// GetMonPath returns path of the monitoring group of the container. Returns an
527+
// empty string if the container does not have a individual dedicated
528+
// monitoring group.
529+
func (m *Manager) GetMonPath() string {
530+
if !m.config.IntelRdt.EnableMonitoring {
531+
return ""
532+
}
533+
closPath := m.GetPath()
534+
if closPath == "" {
535+
return ""
536+
}
537+
538+
return filepath.Join(closPath, "mon_groups", m.id)
539+
}
540+
507541
// GetStats returns statistics for Intel RDT.
508542
func (m *Manager) GetStats() (*Stats, error) {
509543
// If intelRdt is not specified in config
@@ -581,7 +615,16 @@ func (m *Manager) GetStats() (*Stats, error) {
581615
}
582616

583617
if IsMBMEnabled() || IsCMTEnabled() {
584-
err = getMonitoringStats(containerPath, stats)
618+
monPath := m.GetMonPath()
619+
if monPath == "" {
620+
// NOTE: If per-container monitoring is not enabled, the monitoring
621+
// data we get here might have little to do with this container as
622+
// there might be anything from this single container to the half
623+
// of the system assigned in the group. Should consider not
624+
// exposing stats in this case(?)
625+
monPath = containerPath
626+
}
627+
err = getMonitoringStats(monPath, stats)
585628
if err != nil {
586629
return nil, err
587630
}

libcontainer/intelrdt/intelrdt_test.go

Lines changed: 168 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package intelrdt
33
import (
44
"os"
55
"path/filepath"
6+
"strconv"
67
"strings"
78
"testing"
9+
10+
"github.com/opencontainers/runc/libcontainer/configs"
811
)
912

1013
func TestIntelRdtSetL3CacheSchema(t *testing.T) {
@@ -98,31 +101,176 @@ func TestIntelRdtSetMemBwScSchema(t *testing.T) {
98101
}
99102

100103
func TestApply(t *testing.T) {
101-
helper := NewIntelRdtTestUtil(t)
104+
const pid = 1234
105+
tests := []struct {
106+
name string
107+
config configs.IntelRdt
108+
precreateClos bool
109+
isError bool
110+
postApplyAssert func(*Manager)
111+
}{
112+
{
113+
name: "failure because non-pre-existing CLOS",
114+
config: configs.IntelRdt{
115+
ClosID: "non-existing-clos",
116+
},
117+
isError: true,
118+
postApplyAssert: func(m *Manager) {
119+
if _, err := os.Stat(m.path); err == nil {
120+
t.Fatal("closid dir should not exist")
121+
}
122+
},
123+
},
124+
{
125+
name: "CLOS dir should be created if some schema has been specified",
126+
config: configs.IntelRdt{
127+
ClosID: "clos-to-be-created",
128+
L3CacheSchema: "L3:0=f",
129+
},
130+
postApplyAssert: func(m *Manager) {
131+
pids, err := getIntelRdtParamString(m.path, "tasks")
132+
if err != nil {
133+
t.Fatalf("failed to read tasks file: %v", err)
134+
}
135+
if pids != strconv.Itoa(pid) {
136+
t.Fatalf("unexpected tasks file, expected '%d', got %q", pid, pids)
137+
}
138+
},
139+
},
140+
{
141+
name: "clos and monitoring group should be created if EnableMonitoring is true",
142+
config: configs.IntelRdt{
143+
EnableMonitoring: true,
144+
},
145+
precreateClos: true,
146+
postApplyAssert: func(m *Manager) {
147+
pids, err := getIntelRdtParamString(closPath, "tasks")
148+
if err != nil {
149+
t.Fatalf("failed to read tasks file: %v", err)
150+
}
151+
if pids != strconv.Itoa(pid) {
152+
t.Fatalf("unexpected tasks file, expected '%d', got %q", pid, pids)
153+
}
154+
},
155+
},
156+
}
102157

103-
const closID = "test-clos"
104-
closPath := filepath.Join(helper.IntelRdtPath, closID)
158+
for _, tt := range tests {
159+
t.Run(tt.name, func(t *testing.T) {
160+
NewIntelRdtTestUtil(t)
161+
id := "abcd-1234"
162+
closPath := filepath.Join(intelRdtRoot, id)
163+
if tt.config.ClosID != "" {
164+
closPath = filepath.Join(intelRdtRoot, tt.config.ClosID)
165+
}
105166

106-
helper.config.IntelRdt.ClosID = closID
107-
intelrdt := newManager(helper.config, "container-1", closPath)
108-
if err := intelrdt.Apply(1234); err == nil {
109-
t.Fatal("unexpected success when applying pid")
110-
}
111-
if _, err := os.Stat(closPath); err == nil {
112-
t.Fatal("closid dir should not exist")
167+
if tt.precreateClos {
168+
if err := os.MkdirAll(filepath.Join(closPath, "mon_groups"), 0o755); err != nil {
169+
t.Fatal(err)
170+
}
171+
}
172+
m := newManager(&configs.Config{IntelRdt: &tt.config}, id, closPath)
173+
err := m.Apply(pid)
174+
if tt.isError && err == nil {
175+
t.Fatal("expected error, got nil")
176+
} else if !tt.isError && err != nil {
177+
t.Fatalf("unexpected error: %v", err)
178+
}
179+
tt.postApplyAssert(m)
180+
})
113181
}
182+
}
114183

115-
// Dir should be created if some schema has been specified
116-
intelrdt.config.IntelRdt.L3CacheSchema = "L3:0=f"
117-
if err := intelrdt.Apply(1235); err != nil {
118-
t.Fatalf("Apply() failed: %v", err)
119-
}
184+
func TestDestroy(t *testing.T) {
185+
tests := []struct {
186+
name string
187+
config configs.IntelRdt
188+
testFunc func(*Manager)
189+
}{
190+
{
191+
name: "per-container CLOS dir should be removed",
192+
testFunc: func(m *Manager) {
193+
closPath := m.path
194+
if _, err := os.Stat(closPath); err != nil {
195+
t.Fatal("CLOS dir should exist")
196+
}
197+
// Need to delete the tasks file so that the dir is empty
198+
if err := os.Remove(filepath.Join(closPath, "tasks")); err != nil {
199+
t.Fatalf("failed to remove tasks file: %v", err)
200+
}
201+
if err := m.Destroy(); err != nil {
202+
t.Fatalf("Destroy() failed: %v", err)
203+
}
204+
if _, err := os.Stat(closPath); err == nil {
205+
t.Fatal("CLOS dir should not exist")
206+
}
207+
},
208+
},
209+
{
210+
name: "pre-existing CLOS should not be removed",
211+
config: configs.IntelRdt{
212+
ClosID: "pre-existing-clos",
213+
},
214+
testFunc: func(m *Manager) {
215+
closPath := m.path
120216

121-
pids, err := getIntelRdtParamString(intelrdt.GetPath(), "tasks")
122-
if err != nil {
123-
t.Fatalf("failed to read tasks file: %v", err)
217+
if _, err := os.Stat(closPath); err != nil {
218+
t.Fatal("CLOS dir should exist")
219+
}
220+
if err := m.Destroy(); err != nil {
221+
t.Fatalf("Destroy() failed: %v", err)
222+
}
223+
if _, err := os.Stat(closPath); err != nil {
224+
t.Fatal("CLOS dir should exist")
225+
}
226+
},
227+
},
228+
{
229+
name: "per-container MON dir in pre-existing CLOS should be removed",
230+
config: configs.IntelRdt{
231+
ClosID: "pre-existing-clos",
232+
EnableMonitoring: true,
233+
},
234+
testFunc: func(m *Manager) {
235+
closPath := m.path
236+
237+
monPath := filepath.Join(closPath, "mon_groups", m.id)
238+
if _, err := os.Stat(monPath); err != nil {
239+
t.Fatal("MON dir should exist")
240+
}
241+
// Need to delete the tasks file so that the dir is empty
242+
os.Remove(filepath.Join(monPath, "tasks"))
243+
if err := m.Destroy(); err != nil {
244+
t.Fatalf("Destroy() failed: %v", err)
245+
}
246+
if _, err := os.Stat(closPath); err != nil {
247+
t.Fatalf("CLOS dir should exist: %f", err)
248+
}
249+
if _, err := os.Stat(monPath); err == nil {
250+
t.Fatal("MON dir should not exist")
251+
}
252+
},
253+
},
124254
}
125-
if pids != "1235" {
126-
t.Fatalf("unexpected tasks file, expected '1235', got %q", pids)
255+
256+
for _, tt := range tests {
257+
t.Run(tt.name, func(t *testing.T) {
258+
NewIntelRdtTestUtil(t)
259+
260+
id := "abcd-1234"
261+
closPath := filepath.Join(intelRdtRoot, id)
262+
if tt.config.ClosID != "" {
263+
closPath = filepath.Join(intelRdtRoot, tt.config.ClosID)
264+
// Pre-create the CLOS directory
265+
if err := os.MkdirAll(filepath.Join(closPath, "mon_groups"), 0o755); err != nil {
266+
t.Fatal(err)
267+
}
268+
}
269+
m := newManager(&configs.Config{IntelRdt: &tt.config}, id, closPath)
270+
if err := m.Apply(1234); err != nil {
271+
t.Fatalf("Apply() failed: %v", err)
272+
}
273+
tt.testFunc(m)
274+
})
127275
}
128276
}

0 commit comments

Comments
 (0)