libcontainer/intelrdt: add support for Schemata field

Implement support for the linux.intelRdt.schemata field of the spec.
This allows management of the "schemata" file in the resctrl group in a
generic way.

Signed-off-by: Markus Lehtonen <markus.lehtonen@intel.com>
This commit is contained in:
Markus Lehtonen
2025-07-30 16:02:23 +03:00
parent 3867f826da
commit 41553216ee
6 changed files with 82 additions and 49 deletions

View File

@@ -56,7 +56,8 @@ var featuresCommand = cli.Command{
Enabled: &t,
},
IntelRdt: &features.IntelRdt{
Enabled: &t,
Enabled: &t,
Schemata: &t,
},
MountExtensions: &features.MountExtensions{
IDMap: &features.IDMap{

View File

@@ -4,6 +4,10 @@ type IntelRdt struct {
// The identity for RDT Class of Service
ClosID string `json:"closID,omitempty"`
// Schemata is a generic field to specify schemata file in the resctrl
// filesystem. Each element represents one line written to the schemata file.
Schemata []string `json:"schemata,omitempty"`
// The schema for L3 cache id and capacity bitmask (CBM)
// Format: "L3:<cache_id0>=<cbm0>;<cache_id1>=<cbm1>;..."
L3CacheSchema string `json:"l3_cache_schema,omitempty"`

View File

@@ -326,16 +326,6 @@ func getIntelRdtParamString(path, file string) (string, error) {
return string(bytes.TrimSpace(contents)), nil
}
func writeFile(dir, file, data string) error {
if dir == "" {
return fmt.Errorf("no such directory for %s", file)
}
if err := os.WriteFile(filepath.Join(dir, file), []byte(data+"\n"), 0o600); err != nil {
return newLastCmdError(fmt.Errorf("intelrdt: unable to write %v: %w", data, err))
}
return nil
}
// Get the read-only L3 cache information
func getL3CacheInfo() (*L3CacheInfo, error) {
l3CacheInfo := &L3CacheInfo{}
@@ -462,11 +452,11 @@ func (m *Manager) Apply(pid int) (err error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.config.IntelRdt.ClosID != "" && m.config.IntelRdt.L3CacheSchema == "" && m.config.IntelRdt.MemBwSchema == "" {
if m.config.IntelRdt.ClosID != "" && m.config.IntelRdt.L3CacheSchema == "" && m.config.IntelRdt.MemBwSchema == "" && len(m.config.IntelRdt.Schemata) == 0 {
// Check that the CLOS exists, i.e. it has been pre-configured to
// conform with the runtime spec
if _, err := os.Stat(path); err != nil {
return fmt.Errorf("clos dir not accessible (must be pre-created when l3CacheSchema and memBwSchema are empty): %w", err)
return fmt.Errorf("clos dir not accessible (must be pre-created when schemata, l3CacheSchema and memBwSchema are empty): %w", err)
}
}
@@ -637,35 +627,24 @@ func (m *Manager) Set(container *configs.Config) error {
// For example, on a two-socket machine, the schema line could be
// "MB:0=5000;1=7000" which means 5000 MBps memory bandwidth limit on
// socket 0 and 7000 MBps memory bandwidth limit on socket 1.
if container.IntelRdt != nil {
path := m.GetPath()
l3CacheSchema := container.IntelRdt.L3CacheSchema
memBwSchema := container.IntelRdt.MemBwSchema
if r := container.IntelRdt; r != nil {
// TODO: verify that l3CacheSchema and/or memBwSchema match the
// existing schemata if ClosID has been specified. This is a more
// involved than reading the file and doing plain string comparison as
// the value written in does not necessarily match what gets read out
// (leading zeros, cache id ordering etc).
// Write a single joint schema string to schemata file
if l3CacheSchema != "" && memBwSchema != "" {
if err := writeFile(path, "schemata", l3CacheSchema+"\n"+memBwSchema); err != nil {
return err
var schemata strings.Builder
for _, s := range append([]string{r.L3CacheSchema, r.MemBwSchema}, r.Schemata...) {
if s != "" {
schemata.WriteString(s)
schemata.WriteString("\n")
}
}
// Write only L3 cache schema string to schemata file
if l3CacheSchema != "" && memBwSchema == "" {
if err := writeFile(path, "schemata", l3CacheSchema); err != nil {
return err
}
}
// Write only memory bandwidth schema string to schemata file
if l3CacheSchema == "" && memBwSchema != "" {
if err := writeFile(path, "schemata", memBwSchema); err != nil {
return err
if schemata.Len() > 0 {
path := filepath.Join(m.GetPath(), "schemata")
if err := os.WriteFile(path, []byte(schemata.String()), 0o600); err != nil {
return newLastCmdError(fmt.Errorf("intelrdt: unable to write %q: %w", schemata.String(), err))
}
}
}

View File

@@ -37,6 +37,69 @@ func TestIntelRdtSet(t *testing.T) {
},
schemataAfter: []string{"MB:0=9000;1=4000"},
},
{
name: "L3 and MemBw",
config: &configs.IntelRdt{
L3CacheSchema: "L3:0=f0;1=f",
MemBwSchema: "MB:0=9000;1=4000",
},
schemataAfter: []string{
"L3:0=f0;1=f",
"MB:0=9000;1=4000",
},
},
{
name: "Schemata",
config: &configs.IntelRdt{
Schemata: []string{
"L3CODE:0=ff;1=ff",
"L3DATA:0=f;1=f0",
},
},
schemataAfter: []string{
"L3CODE:0=ff;1=ff",
"L3DATA:0=f;1=f0",
},
},
{
name: "Schemata and L3",
config: &configs.IntelRdt{
L3CacheSchema: "L3:0=f0;1=f",
Schemata: []string{"L2:0=ff00;1=ff"},
},
schemataAfter: []string{
"L3:0=f0;1=f",
"L2:0=ff00;1=ff",
},
},
{
name: "Schemata and MemBw",
config: &configs.IntelRdt{
MemBwSchema: "MB:0=2000;1=4000",
Schemata: []string{"L3:0=ff;1=ff"},
},
schemataAfter: []string{
"MB:0=2000;1=4000",
"L3:0=ff;1=ff",
},
},
{
name: "Schemata, L3 and MemBw",
config: &configs.IntelRdt{
L3CacheSchema: "L3:0=80;1=7f",
MemBwSchema: "MB:0=2000;1=4000",
Schemata: []string{
"L2:0=ff00;1=ff",
"L3:0=c0;1=3f",
},
},
schemataAfter: []string{
"L3:0=80;1=7f",
"MB:0=2000;1=4000",
"L2:0=ff00;1=ff",
"L3:0=c0;1=3f",
},
},
}
for _, tc := range tcs {
@@ -44,11 +107,6 @@ func TestIntelRdtSet(t *testing.T) {
helper := NewIntelRdtTestUtil(t)
helper.config.IntelRdt = tc.config
helper.writeFileContents(map[string]string{
/* Common initial value for all test cases */
"schemata": "MB:0=100\nL3:0=ffff\nL2:0=ffffffff\n",
})
intelrdt := newManager(helper.config, "", helper.IntelRdtPath)
if err := intelrdt.Set(helper.config); err != nil {
t.Fatal(err)

View File

@@ -40,13 +40,3 @@ func NewIntelRdtTestUtil(t *testing.T) *intelRdtTestUtil {
}
return &intelRdtTestUtil{config: config, IntelRdtPath: testIntelRdtPath, t: t}
}
// Write the specified contents on the mock of the specified Intel RDT "resource control" files
func (c *intelRdtTestUtil) writeFileContents(fileContents map[string]string) {
for file, contents := range fileContents {
err := writeFile(c.IntelRdtPath, file, contents)
if err != nil {
c.t.Fatal(err)
}
}
}

View File

@@ -463,6 +463,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
if spec.Linux.IntelRdt != nil {
config.IntelRdt = &configs.IntelRdt{
ClosID: spec.Linux.IntelRdt.ClosID,
Schemata: spec.Linux.IntelRdt.Schemata,
L3CacheSchema: spec.Linux.IntelRdt.L3CacheSchema,
MemBwSchema: spec.Linux.IntelRdt.MemBwSchema,
}