github.com/intel/goresctrl@v0.5.0/pkg/rdt/rdt.go (about)

     1  /*
     2  Copyright 2019 Intel Corporation
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package rdt implements an API for managing IntelĀ® RDT technologies via the
    18  // resctrl pseudo-filesystem of the Linux kernel. It provides flexible
    19  // configuration with a hierarchical approach for easy management of exclusive
    20  // cache allocations.
    21  //
    22  // Goresctrl supports all available RDT technologies, i.e. L2 and L3 Cache
    23  // Allocation (CAT) with Code and Data Prioritization (CDP) and Memory
    24  // Bandwidth Allocation (MBA) plus Cache Monitoring (CMT) and Memory Bandwidth
    25  // Monitoring (MBM).
    26  //
    27  // Basic usage example:
    28  //
    29  //	rdt.SetLogger(logrus.New())
    30  //
    31  //	if err := rdt.Initialize(""); err != nil {
    32  //		return fmt.Errorf("RDT not supported: %v", err)
    33  //	}
    34  //
    35  //	if err := rdt.SetConfigFromFile("/path/to/rdt.conf.yaml", false); err != nil {
    36  //		return fmt.Errorf("RDT configuration failed: %v", err)
    37  //	}
    38  //
    39  //	if cls, ok := rdt.GetClass("my-class"); ok {
    40  //	   //  Set PIDs 12345 and 12346 to class "my-class"
    41  //		if err := cls.AddPids("12345", "12346"); err != nil {
    42  //			return fmt.Errorf("failed to add PIDs to RDT class: %v", err)
    43  //		}
    44  //	}
    45  package rdt
    46  
    47  import (
    48  	"errors"
    49  	"fmt"
    50  	stdlog "log"
    51  	"os"
    52  	"path/filepath"
    53  	"sort"
    54  	"strconv"
    55  	"strings"
    56  	"syscall"
    57  
    58  	"sigs.k8s.io/yaml"
    59  
    60  	grclog "github.com/intel/goresctrl/pkg/log"
    61  	"github.com/intel/goresctrl/pkg/utils"
    62  )
    63  
    64  const (
    65  	// RootClassName is the name we use in our config for the special class
    66  	// that configures the "root" resctrl group of the system
    67  	RootClassName = "system/default"
    68  	// RootClassAlias is an alternative name for the root class
    69  	RootClassAlias = ""
    70  )
    71  
    72  type control struct {
    73  	grclog.Logger
    74  
    75  	resctrlGroupPrefix string
    76  	conf               config
    77  	rawConf            Config
    78  	classes            map[string]*ctrlGroup
    79  }
    80  
    81  var log grclog.Logger = grclog.NewLoggerWrapper(stdlog.New(os.Stderr, "[ rdt ] ", 0))
    82  
    83  var info *resctrlInfo
    84  
    85  var rdt *control
    86  
    87  // Function for removing resctrl groups from the filesystem. This is
    88  // configurable because of unit tests.
    89  var groupRemoveFunc func(string) error = os.Remove
    90  
    91  // CtrlGroup defines the interface of one goresctrl managed RDT class. It maps
    92  // to one CTRL group directory in the goresctrl pseudo-filesystem.
    93  type CtrlGroup interface {
    94  	ResctrlGroup
    95  
    96  	// CreateMonGroup creates a new monitoring group under this CtrlGroup.
    97  	CreateMonGroup(name string, annotations map[string]string) (MonGroup, error)
    98  
    99  	// DeleteMonGroup deletes a monitoring group from this CtrlGroup.
   100  	DeleteMonGroup(name string) error
   101  
   102  	// DeleteMonGroups deletes all monitoring groups from this CtrlGroup.
   103  	DeleteMonGroups() error
   104  
   105  	// GetMonGroup returns a specific monitoring group under this CtrlGroup.
   106  	GetMonGroup(name string) (MonGroup, bool)
   107  
   108  	// GetMonGroups returns all monitoring groups under this CtrlGroup.
   109  	GetMonGroups() []MonGroup
   110  }
   111  
   112  // ResctrlGroup is the generic interface for resctrl CTRL and MON groups. It
   113  // maps to one CTRL or MON group directory in the goresctrl pseudo-filesystem.
   114  type ResctrlGroup interface {
   115  	// Name returns the name of the group.
   116  	Name() string
   117  
   118  	// GetPids returns the process ids assigned to the group.
   119  	GetPids() ([]string, error)
   120  
   121  	// AddPids assigns the given process ids to the group.
   122  	AddPids(pids ...string) error
   123  
   124  	// GetMonData retrieves the monitoring data of the group.
   125  	GetMonData() MonData
   126  }
   127  
   128  // MonGroup represents the interface to a RDT monitoring group. It maps to one
   129  // MON group in the goresctrl filesystem.
   130  type MonGroup interface {
   131  	ResctrlGroup
   132  
   133  	// Parent returns the CtrlGroup under which the monitoring group exists.
   134  	Parent() CtrlGroup
   135  
   136  	// GetAnnotations returns the annotations stored to the monitoring group.
   137  	GetAnnotations() map[string]string
   138  }
   139  
   140  // MonData contains monitoring stats of one monitoring group.
   141  type MonData struct {
   142  	L3 MonL3Data
   143  }
   144  
   145  // MonL3Data contains L3 monitoring stats of one monitoring group.
   146  type MonL3Data map[uint64]MonLeafData
   147  
   148  // MonLeafData represents the raw numerical stats from one RDT monitor data leaf.
   149  type MonLeafData map[string]uint64
   150  
   151  // MonResource is the type of RDT monitoring resource.
   152  type MonResource string
   153  
   154  const (
   155  	// MonResourceL3 is the RDT L3 cache monitor resource.
   156  	MonResourceL3 MonResource = "l3"
   157  )
   158  
   159  type ctrlGroup struct {
   160  	resctrlGroup
   161  
   162  	monPrefix string
   163  	monGroups map[string]*monGroup
   164  }
   165  
   166  type monGroup struct {
   167  	resctrlGroup
   168  
   169  	annotations map[string]string
   170  }
   171  
   172  type resctrlGroup struct {
   173  	prefix string
   174  	name   string
   175  	parent *ctrlGroup // parent for MON groups
   176  }
   177  
   178  // SetLogger sets the logger instance to be used by the package. This function
   179  // may be called even before Initialize().
   180  func SetLogger(l grclog.Logger) {
   181  	log = l
   182  	if rdt != nil {
   183  		rdt.setLogger(l)
   184  	}
   185  }
   186  
   187  // Initialize detects RDT from the system and initializes control interface of
   188  // the package.
   189  func Initialize(resctrlGroupPrefix string) error {
   190  	var err error
   191  
   192  	info = nil
   193  	rdt = nil
   194  
   195  	// Get info from the resctrl filesystem
   196  	info, err = getRdtInfo()
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	r := &control{Logger: log, resctrlGroupPrefix: resctrlGroupPrefix}
   202  
   203  	// NOTE: we lose monitoring group annotations (i.e. prometheus metrics
   204  	// labels) on re-init
   205  	if r.classes, err = r.classesFromResctrlFs(); err != nil {
   206  		return fmt.Errorf("failed to initialize classes from resctrl fs: %v", err)
   207  	}
   208  
   209  	rdt = r
   210  
   211  	return nil
   212  }
   213  
   214  // DiscoverClasses discovers existing classes from the resctrl filesystem.
   215  // Makes it possible to discover gropus with another prefix than was set with
   216  // Initialize(). The original prefix is still used for monitoring groups.
   217  func DiscoverClasses(resctrlGroupPrefix string) error {
   218  	if rdt != nil {
   219  		return rdt.discoverFromResctrl(resctrlGroupPrefix)
   220  	}
   221  	return fmt.Errorf("rdt not initialized")
   222  }
   223  
   224  // SetConfig  (re-)configures the resctrl filesystem according to the specified
   225  // configuration.
   226  func SetConfig(c *Config, force bool) error {
   227  	if rdt != nil {
   228  		return rdt.setConfig(c, force)
   229  	}
   230  	return fmt.Errorf("rdt not initialized")
   231  }
   232  
   233  // SetConfigFromData takes configuration as raw data, parses it and
   234  // reconfigures the resctrl filesystem.
   235  func SetConfigFromData(data []byte, force bool) error {
   236  	cfg := &Config{}
   237  	if err := yaml.Unmarshal(data, &cfg); err != nil {
   238  		return fmt.Errorf("failed to parse configuration data: %v", err)
   239  	}
   240  
   241  	return SetConfig(cfg, force)
   242  }
   243  
   244  // SetConfigFromFile reads configuration from the filesystem and reconfigures
   245  // the resctrl filesystem.
   246  func SetConfigFromFile(path string, force bool) error {
   247  	data, err := os.ReadFile(path)
   248  	if err != nil {
   249  		return fmt.Errorf("failed to read config file: %v", err)
   250  	}
   251  
   252  	if err := SetConfigFromData(data, force); err != nil {
   253  		return err
   254  	}
   255  
   256  	log.Infof("configuration successfully loaded from %q", path)
   257  	return nil
   258  }
   259  
   260  // GetClass returns one RDT class.
   261  func GetClass(name string) (CtrlGroup, bool) {
   262  	if rdt != nil {
   263  		return rdt.getClass(name)
   264  	}
   265  	return nil, false
   266  }
   267  
   268  // GetClasses returns all available RDT classes.
   269  func GetClasses() []CtrlGroup {
   270  	if rdt != nil {
   271  		return rdt.getClasses()
   272  	}
   273  	return []CtrlGroup{}
   274  }
   275  
   276  // MonSupported returns true if RDT monitoring features are available.
   277  func MonSupported() bool {
   278  	if rdt != nil {
   279  		return rdt.monSupported()
   280  	}
   281  	return false
   282  }
   283  
   284  // GetMonFeatures returns the available monitoring stats of each available
   285  // monitoring technology.
   286  func GetMonFeatures() map[MonResource][]string {
   287  	if rdt != nil {
   288  		return rdt.getMonFeatures()
   289  	}
   290  	return map[MonResource][]string{}
   291  }
   292  
   293  // IsQualifiedClassName returns true if given string qualifies as a class name
   294  func IsQualifiedClassName(name string) bool {
   295  	// Must be qualified as a file name
   296  	return name == RootClassName || (len(name) < 4096 && name != "." && name != ".." && !strings.ContainsAny(name, "/\n"))
   297  }
   298  
   299  func (c *control) getClass(name string) (CtrlGroup, bool) {
   300  	cls, ok := c.classes[unaliasClassName(name)]
   301  	return cls, ok
   302  }
   303  
   304  func (c *control) getClasses() []CtrlGroup {
   305  	ret := make([]CtrlGroup, 0, len(c.classes))
   306  
   307  	for _, v := range c.classes {
   308  		ret = append(ret, v)
   309  	}
   310  	sort.Slice(ret, func(i, j int) bool { return ret[i].Name() < ret[j].Name() })
   311  
   312  	return ret
   313  }
   314  
   315  func (c *control) monSupported() bool {
   316  	return info.l3mon.Supported()
   317  }
   318  
   319  func (c *control) getMonFeatures() map[MonResource][]string {
   320  	ret := make(map[MonResource][]string)
   321  	if info.l3mon.Supported() {
   322  		ret[MonResourceL3] = append([]string{}, info.l3mon.monFeatures...)
   323  	}
   324  
   325  	return ret
   326  }
   327  
   328  func (c *control) setLogger(l grclog.Logger) {
   329  	c.Logger = l
   330  }
   331  
   332  func (c *control) setConfig(newConfig *Config, force bool) error {
   333  	c.Infof("configuration update")
   334  
   335  	conf, err := (*newConfig).resolve()
   336  	if err != nil {
   337  		return fmt.Errorf("invalid configuration: %v", err)
   338  	}
   339  
   340  	err = c.configureResctrl(conf, force)
   341  	if err != nil {
   342  		return fmt.Errorf("resctrl configuration failed: %v", err)
   343  	}
   344  
   345  	c.conf = conf
   346  	// TODO: we'd better create a deep copy
   347  	c.rawConf = *newConfig
   348  	c.Infof("configuration finished")
   349  
   350  	return nil
   351  }
   352  
   353  func (c *control) configureResctrl(conf config, force bool) error {
   354  	grclog.DebugBlock(c, "applying resolved config:", "  ", "%s", utils.DumpJSON(conf))
   355  
   356  	// Remove stale resctrl groups
   357  	classesFromFs, err := c.classesFromResctrlFs()
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	for name, cls := range classesFromFs {
   363  		if _, ok := conf.Classes[cls.name]; !isRootClass(cls.name) && !ok {
   364  			if !force {
   365  				tasks, err := cls.GetPids()
   366  				if err != nil {
   367  					return fmt.Errorf("failed to get resctrl group tasks: %v", err)
   368  				}
   369  				if len(tasks) > 0 {
   370  					return fmt.Errorf("refusing to remove non-empty resctrl group %q", cls.relPath(""))
   371  				}
   372  			}
   373  			log.Debugf("removing existing resctrl group %q", cls.relPath(""))
   374  			err = groupRemoveFunc(cls.path(""))
   375  			if err != nil {
   376  				return fmt.Errorf("failed to remove resctrl group %q: %v", cls.relPath(""), err)
   377  			}
   378  
   379  			delete(c.classes, name)
   380  		}
   381  	}
   382  
   383  	for name, cls := range c.classes {
   384  		if _, ok := conf.Classes[cls.name]; !ok || cls.prefix != c.resctrlGroupPrefix {
   385  			if !isRootClass(cls.name) {
   386  				log.Debugf("dropping stale class %q (%q)", name, cls.path(""))
   387  				delete(c.classes, name)
   388  			}
   389  		}
   390  	}
   391  
   392  	if _, ok := c.classes[RootClassName]; !ok {
   393  		log.Warnf("root class missing from runtime data, re-adding...")
   394  		c.classes[RootClassName] = classesFromFs[RootClassName]
   395  	}
   396  
   397  	// Try to apply given configuration
   398  	for name, class := range conf.Classes {
   399  		if _, ok := c.classes[name]; !ok {
   400  			cg, err := newCtrlGroup(c.resctrlGroupPrefix, c.resctrlGroupPrefix, name)
   401  			if err != nil {
   402  				return err
   403  			}
   404  			c.classes[name] = cg
   405  		}
   406  		partition := conf.Partitions[class.Partition]
   407  		if err := c.classes[name].configure(name, class, partition, conf.Options); err != nil {
   408  			return err
   409  		}
   410  	}
   411  
   412  	if err := c.pruneMonGroups(); err != nil {
   413  		return err
   414  	}
   415  
   416  	return nil
   417  }
   418  
   419  func (c *control) discoverFromResctrl(prefix string) error {
   420  	c.Debugf("running class discovery from resctrl filesystem using prefix %q", prefix)
   421  
   422  	classesFromFs, err := c.classesFromResctrlFsPrefix(prefix)
   423  	if err != nil {
   424  		return err
   425  	}
   426  
   427  	// Drop stale classes
   428  	for name, cls := range c.classes {
   429  		if _, ok := classesFromFs[cls.name]; !ok || cls.prefix != prefix {
   430  			if !isRootClass(cls.name) {
   431  				log.Debugf("dropping stale class %q (%q)", name, cls.path(""))
   432  				delete(c.classes, name)
   433  			}
   434  		}
   435  	}
   436  
   437  	for name, cls := range classesFromFs {
   438  		if _, ok := c.classes[name]; !ok {
   439  			c.classes[name] = cls
   440  			log.Debugf("adding discovered class %q (%q)", name, cls.path(""))
   441  		}
   442  	}
   443  
   444  	if err := c.pruneMonGroups(); err != nil {
   445  		return err
   446  	}
   447  
   448  	return nil
   449  }
   450  
   451  func (c *control) classesFromResctrlFs() (map[string]*ctrlGroup, error) {
   452  	return c.classesFromResctrlFsPrefix(c.resctrlGroupPrefix)
   453  }
   454  
   455  func (c *control) classesFromResctrlFsPrefix(prefix string) (map[string]*ctrlGroup, error) {
   456  	names := []string{RootClassName}
   457  	if g, err := resctrlGroupsFromFs(prefix, info.resctrlPath); err != nil {
   458  		return nil, err
   459  	} else {
   460  		for _, n := range g {
   461  			if prefix != c.resctrlGroupPrefix &&
   462  				strings.HasPrefix(n, c.resctrlGroupPrefix) &&
   463  				strings.HasPrefix(c.resctrlGroupPrefix, prefix) {
   464  				// Skip groups in the standard namespace
   465  				continue
   466  			}
   467  			names = append(names, n[len(prefix):])
   468  		}
   469  	}
   470  
   471  	classes := make(map[string]*ctrlGroup, len(names)+1)
   472  	for _, name := range names {
   473  		g, err := newCtrlGroup(prefix, c.resctrlGroupPrefix, name)
   474  		if err != nil {
   475  			return nil, err
   476  		}
   477  		classes[name] = g
   478  	}
   479  
   480  	return classes, nil
   481  }
   482  
   483  func (c *control) pruneMonGroups() error {
   484  	for name, cls := range c.classes {
   485  		if err := cls.pruneMonGroups(); err != nil {
   486  			return fmt.Errorf("failed to prune stale monitoring groups of %q: %v", name, err)
   487  		}
   488  	}
   489  	return nil
   490  }
   491  
   492  func (c *control) readRdtFile(rdtPath string) ([]byte, error) {
   493  	return os.ReadFile(filepath.Join(info.resctrlPath, rdtPath))
   494  }
   495  
   496  func (c *control) writeRdtFile(rdtPath string, data []byte) error {
   497  	if err := os.WriteFile(filepath.Join(info.resctrlPath, rdtPath), data, 0644); err != nil {
   498  		return c.cmdError(err)
   499  	}
   500  	return nil
   501  }
   502  
   503  func (c *control) cmdError(origErr error) error {
   504  	errData, readErr := c.readRdtFile(filepath.Join("info", "last_cmd_status"))
   505  	if readErr != nil {
   506  		return origErr
   507  	}
   508  	cmdStatus := strings.TrimSpace(string(errData))
   509  	if len(cmdStatus) > 0 && cmdStatus != "ok" {
   510  		return fmt.Errorf("%s", cmdStatus)
   511  	}
   512  	return origErr
   513  }
   514  
   515  func newCtrlGroup(prefix, monPrefix, name string) (*ctrlGroup, error) {
   516  	cg := &ctrlGroup{
   517  		resctrlGroup: resctrlGroup{prefix: prefix, name: name},
   518  		monPrefix:    monPrefix,
   519  	}
   520  
   521  	if err := os.Mkdir(cg.path(""), 0755); err != nil && !os.IsExist(err) {
   522  		return nil, err
   523  	}
   524  
   525  	var err error
   526  	cg.monGroups, err = cg.monGroupsFromResctrlFs()
   527  	if err != nil {
   528  		return nil, fmt.Errorf("error when retrieving existing monitor groups: %v", err)
   529  	}
   530  
   531  	return cg, nil
   532  }
   533  
   534  func (c *ctrlGroup) CreateMonGroup(name string, annotations map[string]string) (MonGroup, error) {
   535  	if mg, ok := c.monGroups[name]; ok {
   536  		return mg, nil
   537  	}
   538  
   539  	log.Debugf("creating monitoring group %s/%s", c.name, name)
   540  	mg, err := newMonGroup(c.monPrefix, name, c, annotations)
   541  	if err != nil {
   542  		return nil, fmt.Errorf("failed to create new monitoring group %q: %v", name, err)
   543  	}
   544  
   545  	c.monGroups[name] = mg
   546  
   547  	return mg, err
   548  }
   549  
   550  func (c *ctrlGroup) DeleteMonGroup(name string) error {
   551  	mg, ok := c.monGroups[name]
   552  	if !ok {
   553  		log.Warnf("trying to delete non-existent mon group %s/%s", c.name, name)
   554  		return nil
   555  	}
   556  
   557  	log.Debugf("deleting monitoring group %s/%s", c.name, name)
   558  	if err := groupRemoveFunc(mg.path("")); err != nil {
   559  		return fmt.Errorf("failed to remove monitoring group %q: %v", mg.relPath(""), err)
   560  	}
   561  
   562  	delete(c.monGroups, name)
   563  
   564  	return nil
   565  }
   566  
   567  func (c *ctrlGroup) DeleteMonGroups() error {
   568  	for name := range c.monGroups {
   569  		if err := c.DeleteMonGroup(name); err != nil {
   570  			return err
   571  		}
   572  	}
   573  	return nil
   574  }
   575  
   576  func (c *ctrlGroup) GetMonGroup(name string) (MonGroup, bool) {
   577  	mg, ok := c.monGroups[name]
   578  	return mg, ok
   579  }
   580  
   581  func (c *ctrlGroup) GetMonGroups() []MonGroup {
   582  	ret := make([]MonGroup, 0, len(c.monGroups))
   583  
   584  	for _, v := range c.monGroups {
   585  		ret = append(ret, v)
   586  	}
   587  	sort.Slice(ret, func(i, j int) bool { return ret[i].Name() < ret[j].Name() })
   588  
   589  	return ret
   590  }
   591  
   592  func (c *ctrlGroup) configure(name string, class *classConfig,
   593  	partition *partitionConfig, options Options) error {
   594  	schemata := ""
   595  
   596  	// Handle cache allocation
   597  	for _, lvl := range []cacheLevel{L2, L3} {
   598  		switch {
   599  		case info.cat[lvl].unified.Supported():
   600  			schema, err := class.CATSchema[lvl].toStr(catSchemaTypeUnified, partition.CAT[lvl])
   601  			if err != nil {
   602  				return err
   603  			}
   604  			schemata += schema
   605  		case info.cat[lvl].data.Supported() || info.cat[lvl].code.Supported():
   606  			schema, err := class.CATSchema[lvl].toStr(catSchemaTypeCode, partition.CAT[lvl])
   607  			if err != nil {
   608  				return err
   609  			}
   610  			schemata += schema
   611  
   612  			schema, err = class.CATSchema[lvl].toStr(catSchemaTypeData, partition.CAT[lvl])
   613  			if err != nil {
   614  				return err
   615  			}
   616  			schemata += schema
   617  		default:
   618  			if class.CATSchema[lvl].Alloc != nil && !options.cat(lvl).Optional {
   619  				return fmt.Errorf("%s cache allocation for %q specified in configuration but not supported by system", lvl, name)
   620  			}
   621  		}
   622  	}
   623  
   624  	// Handle memory bandwidth allocation
   625  	switch {
   626  	case info.mb.Supported():
   627  		schemata += class.MBSchema.toStr(partition.MB)
   628  	default:
   629  		if class.MBSchema != nil && !options.MB.Optional {
   630  			return fmt.Errorf("memory bandwidth allocation for %q specified in configuration but not supported by system", name)
   631  		}
   632  	}
   633  
   634  	if len(schemata) > 0 {
   635  		log.Debugf("writing schemata %q to %q", schemata, c.relPath(""))
   636  		if err := rdt.writeRdtFile(c.relPath("schemata"), []byte(schemata)); err != nil {
   637  			return err
   638  		}
   639  	} else {
   640  		log.Debugf("empty schemata")
   641  	}
   642  
   643  	return nil
   644  }
   645  
   646  func (c *ctrlGroup) monGroupsFromResctrlFs() (map[string]*monGroup, error) {
   647  	names, err := resctrlGroupsFromFs(c.monPrefix, c.path("mon_groups"))
   648  	if err != nil && !os.IsNotExist(err) {
   649  		return nil, err
   650  	}
   651  
   652  	grps := make(map[string]*monGroup, len(names))
   653  	for _, name := range names {
   654  		name = name[len(c.monPrefix):]
   655  		mg, err := newMonGroup(c.monPrefix, name, c, nil)
   656  		if err != nil {
   657  			return nil, err
   658  		}
   659  		grps[name] = mg
   660  	}
   661  	return grps, nil
   662  }
   663  
   664  // Remove empty monitoring groups
   665  func (c *ctrlGroup) pruneMonGroups() error {
   666  	for name, mg := range c.monGroups {
   667  		pids, err := mg.GetPids()
   668  		if err != nil {
   669  			return fmt.Errorf("failed to get pids for monitoring group %q: %v", mg.relPath(""), err)
   670  		}
   671  		if len(pids) == 0 {
   672  			if err := c.DeleteMonGroup(name); err != nil {
   673  				return fmt.Errorf("failed to remove monitoring group %q: %v", mg.relPath(""), err)
   674  			}
   675  		}
   676  	}
   677  	return nil
   678  }
   679  
   680  func (r *resctrlGroup) Name() string {
   681  	return r.name
   682  }
   683  
   684  func (r *resctrlGroup) GetPids() ([]string, error) {
   685  	data, err := rdt.readRdtFile(r.relPath("tasks"))
   686  	if err != nil {
   687  		return []string{}, err
   688  	}
   689  	split := strings.Split(strings.TrimSpace(string(data)), "\n")
   690  	if len(split[0]) > 0 {
   691  		return split, nil
   692  	}
   693  	return []string{}, nil
   694  }
   695  
   696  func (r *resctrlGroup) AddPids(pids ...string) error {
   697  	f, err := os.OpenFile(r.path("tasks"), os.O_WRONLY, 0644)
   698  	if err != nil {
   699  		return err
   700  	}
   701  	defer f.Close()
   702  
   703  	for _, pid := range pids {
   704  		if _, err := f.WriteString(pid + "\n"); err != nil {
   705  			if errors.Is(err, syscall.ESRCH) {
   706  				log.Debugf("no task %s", pid)
   707  			} else {
   708  				return fmt.Errorf("failed to assign processes %v to class %q: %v", pids, r.name, rdt.cmdError(err))
   709  			}
   710  		}
   711  	}
   712  	return nil
   713  }
   714  
   715  func (r *resctrlGroup) GetMonData() MonData {
   716  	m := MonData{}
   717  
   718  	if info.l3mon.Supported() {
   719  		l3, err := r.getMonL3Data()
   720  		if err != nil {
   721  			log.Warnf("failed to retrieve L3 monitoring data: %v", err)
   722  		} else {
   723  			m.L3 = l3
   724  		}
   725  	}
   726  
   727  	return m
   728  }
   729  
   730  func (r *resctrlGroup) getMonL3Data() (MonL3Data, error) {
   731  	files, err := os.ReadDir(r.path("mon_data"))
   732  	if err != nil {
   733  		return nil, err
   734  	}
   735  
   736  	m := MonL3Data{}
   737  	for _, file := range files {
   738  		name := file.Name()
   739  		if strings.HasPrefix(name, "mon_L3_") {
   740  			// Parse cache id from the dirname
   741  			id, err := strconv.ParseUint(strings.TrimPrefix(name, "mon_L3_"), 10, 32)
   742  			if err != nil {
   743  				// Just print a warning, we try to retrieve as much info as possible
   744  				log.Warnf("error parsing L3 monitor data directory name %q: %v", name, err)
   745  				continue
   746  			}
   747  
   748  			data, err := r.getMonLeafData(filepath.Join("mon_data", name))
   749  			if err != nil {
   750  				log.Warnf("failed to read monitor data: %v", err)
   751  				continue
   752  			}
   753  
   754  			m[id] = data
   755  		}
   756  	}
   757  
   758  	return m, nil
   759  }
   760  
   761  func (r *resctrlGroup) getMonLeafData(path string) (MonLeafData, error) {
   762  	files, err := os.ReadDir(r.path(path))
   763  	if err != nil {
   764  		return nil, err
   765  	}
   766  
   767  	m := make(MonLeafData, len(files))
   768  
   769  	for _, file := range files {
   770  		name := file.Name()
   771  
   772  		// We expect that all the files in the dir are regular files
   773  		val, err := readFileUint64(r.path(path, name))
   774  		if err != nil {
   775  			// Just print a warning, we want to retrieve as much info as possible
   776  			log.Warnf("error reading data file: %v", err)
   777  			continue
   778  		}
   779  
   780  		m[name] = val
   781  	}
   782  	return m, nil
   783  }
   784  
   785  func (r *resctrlGroup) relPath(elem ...string) string {
   786  	if r.parent == nil {
   787  		if r.name == RootClassName {
   788  			return filepath.Join(elem...)
   789  		}
   790  		return filepath.Join(append([]string{r.prefix + r.name}, elem...)...)
   791  	}
   792  	// Parent is only intended for MON groups - non-root CTRL groups are considered
   793  	// as peers to the root CTRL group (as they are in HW) and do not have a parent
   794  	return r.parent.relPath(append([]string{"mon_groups", r.prefix + r.name}, elem...)...)
   795  }
   796  
   797  func (r *resctrlGroup) path(elem ...string) string {
   798  	return filepath.Join(info.resctrlPath, r.relPath(elem...))
   799  }
   800  
   801  func newMonGroup(prefix string, name string, parent *ctrlGroup, annotations map[string]string) (*monGroup, error) {
   802  	mg := &monGroup{
   803  		resctrlGroup: resctrlGroup{prefix: prefix, name: name, parent: parent},
   804  		annotations:  make(map[string]string, len(annotations))}
   805  
   806  	if err := os.Mkdir(mg.path(""), 0755); err != nil && !os.IsExist(err) {
   807  		return nil, err
   808  	}
   809  	for k, v := range annotations {
   810  		mg.annotations[k] = v
   811  	}
   812  
   813  	return mg, nil
   814  }
   815  
   816  func (m *monGroup) Parent() CtrlGroup {
   817  	return m.parent
   818  }
   819  
   820  func (m *monGroup) GetAnnotations() map[string]string {
   821  	a := make(map[string]string, len(m.annotations))
   822  	for k, v := range m.annotations {
   823  		a[k] = v
   824  	}
   825  	return a
   826  }
   827  
   828  func resctrlGroupsFromFs(prefix string, path string) ([]string, error) {
   829  	files, err := os.ReadDir(path)
   830  	if err != nil {
   831  		return nil, err
   832  	}
   833  
   834  	grps := make([]string, 0, len(files))
   835  	for _, file := range files {
   836  		filename := file.Name()
   837  		if strings.HasPrefix(filename, prefix) {
   838  			if s, err := os.Stat(filepath.Join(path, filename, "tasks")); err == nil && !s.IsDir() {
   839  				grps = append(grps, filename)
   840  			}
   841  		}
   842  	}
   843  	return grps, nil
   844  }
   845  
   846  func isRootClass(name string) bool {
   847  	return name == RootClassName || name == RootClassAlias
   848  }
   849  
   850  func unaliasClassName(name string) string {
   851  	if isRootClass(name) {
   852  		return RootClassName
   853  	}
   854  	return name
   855  }