mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
Merge pull request #4830 from marquiz/devel/rdt-schemata-field
libcontainer/intelrdt: add support for Schemata field
This commit is contained in:
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,6 +524,8 @@ func (m *Manager) GetStats() (*Stats, error) {
|
||||
}
|
||||
schemaStrings := strings.Split(tmpStrings, "\n")
|
||||
|
||||
stats.Schemata = schemaStrings
|
||||
|
||||
if IsCATEnabled() {
|
||||
// The read-only L3 cache information
|
||||
l3CacheInfo, err := getL3CacheInfo()
|
||||
@@ -637,35 +629,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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,97 +3,125 @@ package intelrdt
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
func TestIntelRdtSetL3CacheSchema(t *testing.T) {
|
||||
helper := NewIntelRdtTestUtil(t)
|
||||
|
||||
const (
|
||||
l3CacheSchemaBefore = "L3:0=f;1=f0"
|
||||
l3CacheSchemeAfter = "L3:0=f0;1=f"
|
||||
)
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"schemata": l3CacheSchemaBefore + "\n",
|
||||
})
|
||||
|
||||
helper.config.IntelRdt.L3CacheSchema = l3CacheSchemeAfter
|
||||
intelrdt := newManager(helper.config, "", helper.IntelRdtPath)
|
||||
if err := intelrdt.Set(helper.config); err != nil {
|
||||
t.Fatal(err)
|
||||
func TestIntelRdtSet(t *testing.T) {
|
||||
tcs := []struct {
|
||||
name string
|
||||
config *configs.IntelRdt
|
||||
schemataAfter []string
|
||||
}{
|
||||
{
|
||||
name: "L3",
|
||||
config: &configs.IntelRdt{
|
||||
L3CacheSchema: "L3:0=f0;1=f",
|
||||
},
|
||||
schemataAfter: []string{"L3:0=f0;1=f"},
|
||||
},
|
||||
{
|
||||
name: "MemBw",
|
||||
config: &configs.IntelRdt{
|
||||
MemBwSchema: "MB:0=70;1=20",
|
||||
},
|
||||
schemataAfter: []string{"MB:0=70;1=20"},
|
||||
},
|
||||
{
|
||||
name: "MemBwSc",
|
||||
config: &configs.IntelRdt{
|
||||
MemBwSchema: "MB:0=9000;1=4000",
|
||||
},
|
||||
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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tmpStrings, err := getIntelRdtParamString(helper.IntelRdtPath, "schemata")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse file 'schemata' - %s", err)
|
||||
}
|
||||
values := strings.Split(tmpStrings, "\n")
|
||||
value := values[0]
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
helper := NewIntelRdtTestUtil(t)
|
||||
helper.config.IntelRdt = tc.config
|
||||
|
||||
if value != l3CacheSchemeAfter {
|
||||
t.Fatal("Got the wrong value, set 'schemata' failed.")
|
||||
}
|
||||
}
|
||||
intelrdt := newManager(helper.config, "", helper.IntelRdtPath)
|
||||
if err := intelrdt.Set(helper.config); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
func TestIntelRdtSetMemBwSchema(t *testing.T) {
|
||||
helper := NewIntelRdtTestUtil(t)
|
||||
tmpStrings, err := getIntelRdtParamString(helper.IntelRdtPath, "schemata")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse file 'schemata' - %s", err)
|
||||
}
|
||||
values := strings.Split(tmpStrings, "\n")
|
||||
|
||||
const (
|
||||
memBwSchemaBefore = "MB:0=20;1=70"
|
||||
memBwSchemeAfter = "MB:0=70;1=20"
|
||||
)
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"schemata": memBwSchemaBefore + "\n",
|
||||
})
|
||||
|
||||
helper.config.IntelRdt.MemBwSchema = memBwSchemeAfter
|
||||
intelrdt := newManager(helper.config, "", helper.IntelRdtPath)
|
||||
if err := intelrdt.Set(helper.config); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpStrings, err := getIntelRdtParamString(helper.IntelRdtPath, "schemata")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse file 'schemata' - %s", err)
|
||||
}
|
||||
values := strings.Split(tmpStrings, "\n")
|
||||
value := values[0]
|
||||
|
||||
if value != memBwSchemeAfter {
|
||||
t.Fatal("Got the wrong value, set 'schemata' failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntelRdtSetMemBwScSchema(t *testing.T) {
|
||||
helper := NewIntelRdtTestUtil(t)
|
||||
|
||||
const (
|
||||
memBwScSchemaBefore = "MB:0=5000;1=7000"
|
||||
memBwScSchemeAfter = "MB:0=9000;1=4000"
|
||||
)
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"schemata": memBwScSchemaBefore + "\n",
|
||||
})
|
||||
|
||||
helper.config.IntelRdt.MemBwSchema = memBwScSchemeAfter
|
||||
intelrdt := newManager(helper.config, "", helper.IntelRdtPath)
|
||||
if err := intelrdt.Set(helper.config); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpStrings, err := getIntelRdtParamString(helper.IntelRdtPath, "schemata")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse file 'schemata' - %s", err)
|
||||
}
|
||||
values := strings.Split(tmpStrings, "\n")
|
||||
value := values[0]
|
||||
|
||||
if value != memBwScSchemeAfter {
|
||||
t.Fatal("Got the wrong value, set 'schemata' failed.")
|
||||
if slices.Compare(values, tc.schemataAfter) != 0 {
|
||||
t.Fatalf("Got the wrong value, expected %v, got %v", tc.schemataAfter, values)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ type Stats struct {
|
||||
// The memory bandwidth schema in 'container_id' group
|
||||
MemBwSchema string `json:"mem_bw_schema,omitempty"`
|
||||
|
||||
// Schemata contains the full schemata of the ClosID (resctrl group) that the container is assigned to.
|
||||
Schemata []string `json:"schemata,omitempty"`
|
||||
|
||||
// The memory bandwidth monitoring statistics from NUMA nodes in 'container_id' group
|
||||
MBMStats *[]MBMNumaNodeStats `json:"mbm_stats,omitempty"`
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user