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

     1  /*
     2  Copyright 2019-2021 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
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"math"
    23  	"math/bits"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  
    28  	grclog "github.com/intel/goresctrl/pkg/log"
    29  	"github.com/intel/goresctrl/pkg/utils"
    30  )
    31  
    32  // Config is the user-specified RDT configuration.
    33  type Config struct {
    34  	Options    Options `json:"options"`
    35  	Partitions map[string]struct {
    36  		L2Allocation CatConfig `json:"l2Allocation"`
    37  		L3Allocation CatConfig `json:"l3Allocation"`
    38  		MBAllocation MbaConfig `json:"mbAllocation"`
    39  		Classes      map[string]struct {
    40  			L2Allocation CatConfig         `json:"l2Allocation"`
    41  			L3Allocation CatConfig         `json:"l3Allocation"`
    42  			MBAllocation MbaConfig         `json:"mbAllocation"`
    43  			Kubernetes   KubernetesOptions `json:"kubernetes"`
    44  		} `json:"classes"`
    45  	} `json:"partitions"`
    46  }
    47  
    48  // CatConfig contains the L2 or L3 cache allocation configuration for one partition or class.
    49  type CatConfig map[string]CacheIdCatConfig
    50  
    51  // MbaConfig contains the memory bandwidth configuration for one partition or class.
    52  type MbaConfig map[string]CacheIdMbaConfig
    53  
    54  // CacheIdCatConfig is the cache allocation configuration for one cache id.
    55  // Code and Data represent an optional configuration for separate code and data
    56  // paths and only have effect when RDT CDP (Code and Data Prioritization) is
    57  // enabled in the system. Code and Data go in tandem so that both or neither
    58  // must be specified - only specifying the other is considered a configuration
    59  // error.
    60  type CacheIdCatConfig struct {
    61  	Unified CacheProportion
    62  	Code    CacheProportion
    63  	Data    CacheProportion
    64  }
    65  
    66  // CacheIdMbaConfig is the memory bandwidth configuration for one cache id.
    67  // It's an array of at most two values, specifying separate values to be used
    68  // for percentage based and MBps based memory bandwidth allocation. For
    69  // example, `{"80%", "1000MBps"}` would allocate 80% if percentage based
    70  // allocation is used by the Linux kernel, or 1000 MBps in case MBps based
    71  // allocation is in use.
    72  type CacheIdMbaConfig []MbProportion
    73  
    74  // MbProportion specifies a share of available memory bandwidth. It's an
    75  // integer value followed by a unit. Two units are supported:
    76  //
    77  // - percentage, e.g. `80%`
    78  // - MBps, e.g. `1000MBps`
    79  type MbProportion string
    80  
    81  // CacheProportion specifies a share of the available cache lines.
    82  // Supported formats:
    83  //
    84  // - percentage, e.g. `50%`
    85  // - percentage range, e.g. `50-60%`
    86  // - bit numbers, e.g. `0-5`, `2,3`, must contain one contiguous block of bits set
    87  // - hex bitmask, e.g. `0xff0`, must contain one contiguous block of bits set
    88  type CacheProportion string
    89  
    90  // CacheIdAll is a special cache id used to denote a default, used as a
    91  // fallback for all cache ids that are not explicitly specified.
    92  const CacheIdAll = "all"
    93  
    94  // config represents the final (parsed and resolved) runtime configuration of
    95  // RDT Control
    96  type config struct {
    97  	Options    Options
    98  	Partitions partitionSet
    99  	Classes    classSet
   100  }
   101  
   102  // partitionSet represents the pool of rdt partitions
   103  type partitionSet map[string]*partitionConfig
   104  
   105  // classSet represents the pool of rdt classes
   106  type classSet map[string]*classConfig
   107  
   108  // partitionConfig is the final configuration of one partition
   109  type partitionConfig struct {
   110  	CAT map[cacheLevel]catSchema
   111  	MB  mbSchema
   112  }
   113  
   114  // classConfig represents configuration of one class, i.e. one CTRL group in
   115  // the Linux resctrl interface
   116  type classConfig struct {
   117  	Partition  string
   118  	CATSchema  map[cacheLevel]catSchema
   119  	MBSchema   mbSchema
   120  	Kubernetes KubernetesOptions
   121  }
   122  
   123  // Options contains common settings.
   124  type Options struct {
   125  	L2 CatOptions `json:"l2"`
   126  	L3 CatOptions `json:"l3"`
   127  	MB MbOptions  `json:"mb"`
   128  }
   129  
   130  // CatOptions contains the common settings for cache allocation.
   131  type CatOptions struct {
   132  	Optional bool
   133  }
   134  
   135  // MbOptions contains the common settings for memory bandwidth allocation.
   136  type MbOptions struct {
   137  	Optional bool
   138  }
   139  
   140  // KubernetesOptions contains per-class settings for the Kubernetes-related functionality.
   141  type KubernetesOptions struct {
   142  	DenyPodAnnotation       bool `json:"denyPodAnnotation"`
   143  	DenyContainerAnnotation bool `json:"denyContainerAnnotation"`
   144  }
   145  
   146  // catSchema represents a cache part of the schemata of a class (i.e. resctrl group)
   147  type catSchema struct {
   148  	Lvl   cacheLevel
   149  	Alloc catSchemaRaw
   150  }
   151  
   152  // catSchemaRaw is the cache schemata without the information about cache level
   153  type catSchemaRaw map[uint64]catAllocation
   154  
   155  // mbSchema represents the MB part of the schemata of a class (i.e. resctrl group)
   156  type mbSchema map[uint64]uint64
   157  
   158  // catAllocation describes the allocation configuration for one cache id
   159  type catAllocation struct {
   160  	Unified cacheAllocation
   161  	Code    cacheAllocation `json:",omitempty"`
   162  	Data    cacheAllocation `json:",omitempty"`
   163  }
   164  
   165  // cacheAllocation is the basic interface for handling cache allocations of one
   166  // type (unified, code, data)
   167  type cacheAllocation interface {
   168  	Overlay(bitmask, uint64) (bitmask, error)
   169  }
   170  
   171  // catAbsoluteAllocation represents an explicitly specified cache allocation
   172  // bitmask
   173  type catAbsoluteAllocation bitmask
   174  
   175  // catPctAllocation represents a relative (percentage) share of the available
   176  // bitmask
   177  type catPctAllocation uint64
   178  
   179  // catPctRangeAllocation represents a percentage range of the available bitmask
   180  type catPctRangeAllocation struct {
   181  	lowPct  uint64
   182  	highPct uint64
   183  }
   184  
   185  // catSchemaType represents different L3 cache allocation schemes
   186  type catSchemaType string
   187  
   188  const (
   189  	// catSchemaTypeUnified is the schema type when CDP is not enabled
   190  	catSchemaTypeUnified catSchemaType = "unified"
   191  	// catSchemaTypeCode is the 'code' part of CDP schema
   192  	catSchemaTypeCode catSchemaType = "code"
   193  	// catSchemaTypeData is the 'data' part of CDP schema
   194  	catSchemaTypeData catSchemaType = "data"
   195  )
   196  
   197  // cat returns CAT options for the specified cache level.
   198  func (o Options) cat(lvl cacheLevel) CatOptions {
   199  	switch lvl {
   200  	case L2:
   201  		return o.L2
   202  	case L3:
   203  		return o.L3
   204  	}
   205  	return CatOptions{}
   206  }
   207  
   208  func (t catSchemaType) toResctrlStr() string {
   209  	if t == catSchemaTypeUnified {
   210  		return ""
   211  	}
   212  	return strings.ToUpper(string(t))
   213  }
   214  
   215  const (
   216  	mbSuffixPct  = "%"
   217  	mbSuffixMbps = "MBps"
   218  )
   219  
   220  func newCatSchema(typ cacheLevel) catSchema {
   221  	return catSchema{
   222  		Lvl:   typ,
   223  		Alloc: make(map[uint64]catAllocation),
   224  	}
   225  }
   226  
   227  // toStr returns the CAT schema in a format accepted by the Linux kernel
   228  // resctrl (schemata) interface
   229  func (s catSchema) toStr(typ catSchemaType, baseSchema catSchema) (string, error) {
   230  	schema := string(s.Lvl) + typ.toResctrlStr() + ":"
   231  	sep := ""
   232  
   233  	// Get a sorted slice of cache ids for deterministic output
   234  	ids := append([]uint64{}, info.cat[s.Lvl].cacheIds...)
   235  	utils.SortUint64s(ids)
   236  
   237  	minBits := info.cat[s.Lvl].minCbmBits()
   238  	for _, id := range ids {
   239  		// Default to 100%
   240  		bmask := info.cat[s.Lvl].cbmMask()
   241  
   242  		if base, ok := baseSchema.Alloc[id]; ok {
   243  			baseMask, ok := base.getEffective(typ).(catAbsoluteAllocation)
   244  			if !ok {
   245  				return "", fmt.Errorf("BUG: basemask not of type catAbsoluteAllocation")
   246  			}
   247  			bmask = bitmask(baseMask)
   248  		}
   249  
   250  		if s.Alloc != nil {
   251  			var err error
   252  
   253  			masks := s.Alloc[id]
   254  			overlayMask := masks.getEffective(typ)
   255  
   256  			bmask, err = overlayMask.Overlay(bmask, minBits)
   257  			if err != nil {
   258  				return "", err
   259  			}
   260  		}
   261  		schema += fmt.Sprintf("%s%d=%x", sep, id, bmask)
   262  		sep = ";"
   263  	}
   264  
   265  	return schema + "\n", nil
   266  }
   267  
   268  func (a catAllocation) get(typ catSchemaType) cacheAllocation {
   269  	switch typ {
   270  	case catSchemaTypeCode:
   271  		return a.Code
   272  	case catSchemaTypeData:
   273  		return a.Data
   274  	}
   275  	return a.Unified
   276  }
   277  
   278  func (a catAllocation) set(typ catSchemaType, v cacheAllocation) catAllocation {
   279  	switch typ {
   280  	case catSchemaTypeCode:
   281  		a.Code = v
   282  	case catSchemaTypeData:
   283  		a.Data = v
   284  	default:
   285  		a.Unified = v
   286  	}
   287  
   288  	return a
   289  }
   290  
   291  func (a catAllocation) getEffective(typ catSchemaType) cacheAllocation {
   292  	switch typ {
   293  	case catSchemaTypeCode:
   294  		if a.Code != nil {
   295  			return a.Code
   296  		}
   297  	case catSchemaTypeData:
   298  		if a.Data != nil {
   299  			return a.Data
   300  		}
   301  	}
   302  	// Use Unified as the default/fallback for Code and Data
   303  	return a.Unified
   304  }
   305  
   306  // Overlay function of the cacheAllocation interface
   307  func (a catAbsoluteAllocation) Overlay(baseMask bitmask, minBits uint64) (bitmask, error) {
   308  	if err := verifyCatBaseMask(baseMask, minBits); err != nil {
   309  		return 0, err
   310  	}
   311  
   312  	shiftWidth := baseMask.lsbOne()
   313  
   314  	// Treat our bitmask relative to the basemask
   315  	bmask := bitmask(a) << shiftWidth
   316  
   317  	// Do bounds checking that we're "inside" the base mask
   318  	if bmask|baseMask != baseMask {
   319  		return 0, fmt.Errorf("bitmask %#x (%#x << %d) does not fit basemask %#x", bmask, a, shiftWidth, baseMask)
   320  	}
   321  
   322  	return bmask, nil
   323  }
   324  
   325  // MarshalJSON implements the Marshaler interface of "encoding/json"
   326  func (a catAbsoluteAllocation) MarshalJSON() ([]byte, error) {
   327  	return []byte(fmt.Sprintf("\"%#x\"", a)), nil
   328  }
   329  
   330  // Overlay function of the cacheAllocation interface
   331  func (a catPctAllocation) Overlay(baseMask bitmask, minBits uint64) (bitmask, error) {
   332  	return catPctRangeAllocation{highPct: uint64(a)}.Overlay(baseMask, minBits)
   333  }
   334  
   335  // Overlay function of the cacheAllocation interface
   336  func (a catPctRangeAllocation) Overlay(baseMask bitmask, minBits uint64) (bitmask, error) {
   337  	if err := verifyCatBaseMask(baseMask, minBits); err != nil {
   338  		return 0, err
   339  	}
   340  
   341  	baseMaskMsb := uint64(baseMask.msbOne())
   342  	baseMaskLsb := uint64(baseMask.lsbOne())
   343  	baseMaskNumBits := baseMaskMsb - baseMaskLsb + 1
   344  
   345  	low, high := a.lowPct, a.highPct
   346  	if low == 0 {
   347  		low = 1
   348  	}
   349  	if low > high || low > 100 || high > 100 {
   350  		return 0, fmt.Errorf("invalid percentage range in %v", a)
   351  	}
   352  
   353  	// Convert percentage limits to bit numbers
   354  	// Our effective range is 1%-100%, use substraction (-1) because of
   355  	// arithmetics, so that we don't overflow on 100%
   356  	lsb := (low - 1) * baseMaskNumBits / 100
   357  	msb := (high - 1) * baseMaskNumBits / 100
   358  
   359  	// Make sure the number of bits set satisfies the minimum requirement
   360  	numBits := msb - lsb + 1
   361  	if numBits < minBits {
   362  		gap := minBits - numBits
   363  
   364  		// First, widen the mask from the "lsb end"
   365  		if gap <= lsb {
   366  			lsb -= gap
   367  			gap = 0
   368  		} else {
   369  			gap -= lsb
   370  			lsb = 0
   371  		}
   372  		// If needed, widen the mask from the "msb end"
   373  		msbAvailable := baseMaskNumBits - msb - 1
   374  		if gap <= msbAvailable {
   375  			msb += gap
   376  		} else {
   377  			return 0, fmt.Errorf("BUG: not enough bits available for cache bitmask (%v applied on basemask %#x)", a, baseMask)
   378  		}
   379  	}
   380  
   381  	value := ((1 << (msb - lsb + 1)) - 1) << (lsb + baseMaskLsb)
   382  
   383  	return bitmask(value), nil
   384  }
   385  
   386  func verifyCatBaseMask(baseMask bitmask, minBits uint64) error {
   387  	if baseMask == 0 {
   388  		return fmt.Errorf("empty basemask not allowed")
   389  	}
   390  
   391  	// Check that the basemask contains one (and only one) contiguous block of
   392  	// (enough) bits set
   393  	baseMaskWidth := baseMask.msbOne() - baseMask.lsbOne() + 1
   394  	if bits.OnesCount64(uint64(baseMask)) != baseMaskWidth {
   395  		return fmt.Errorf("invalid basemask %#x: more than one block of bits set", baseMask)
   396  	}
   397  	if uint64(bits.OnesCount64(uint64(baseMask))) < minBits {
   398  		return fmt.Errorf("invalid basemask %#x: fewer than %d bits set", baseMask, minBits)
   399  	}
   400  
   401  	return nil
   402  }
   403  
   404  // MarshalJSON implements the Marshaler interface of "encoding/json"
   405  func (a catPctAllocation) MarshalJSON() ([]byte, error) {
   406  	return []byte(fmt.Sprintf("\"%d%%\"", a)), nil
   407  }
   408  
   409  // MarshalJSON implements the Marshaler interface of "encoding/json"
   410  func (a catPctRangeAllocation) MarshalJSON() ([]byte, error) {
   411  	return []byte(fmt.Sprintf("\"%d-%d%%\"", a.lowPct, a.highPct)), nil
   412  }
   413  
   414  // toStr returns the MB schema in a format accepted by the Linux kernel
   415  // resctrl (schemata) interface
   416  func (s mbSchema) toStr(base map[uint64]uint64) string {
   417  	schema := "MB:"
   418  	sep := ""
   419  
   420  	// Get a sorted slice of cache ids for deterministic output
   421  	ids := append([]uint64{}, info.mb.cacheIds...)
   422  	utils.SortUint64s(ids)
   423  
   424  	for _, id := range ids {
   425  		baseAllocation, ok := base[id]
   426  		if !ok {
   427  			if info.mb.mbpsEnabled {
   428  				baseAllocation = math.MaxUint32
   429  			} else {
   430  				baseAllocation = 100
   431  			}
   432  		}
   433  
   434  		value := uint64(0)
   435  		if info.mb.mbpsEnabled {
   436  			value = math.MaxUint32
   437  			if s != nil {
   438  				value = s[id]
   439  			}
   440  			// Limit to given base value
   441  			if value > baseAllocation {
   442  				value = baseAllocation
   443  			}
   444  		} else {
   445  			allocation := uint64(100)
   446  			if s != nil {
   447  				allocation = s[id]
   448  			}
   449  			value = allocation * baseAllocation / 100
   450  			// Guarantee minimum bw so that writing out the schemata does not fail
   451  			if value < info.mb.minBandwidth {
   452  				value = info.mb.minBandwidth
   453  			}
   454  		}
   455  
   456  		schema += fmt.Sprintf("%s%d=%d", sep, id, value)
   457  		sep = ";"
   458  	}
   459  
   460  	return schema + "\n"
   461  }
   462  
   463  // listStrToArray parses a string containing a human-readable list of numbers
   464  // into an integer array
   465  func listStrToArray(str string) ([]int, error) {
   466  	a := []int{}
   467  
   468  	// Empty list
   469  	if len(str) == 0 {
   470  		return a, nil
   471  	}
   472  
   473  	ranges := strings.Split(str, ",")
   474  	for _, ran := range ranges {
   475  		split := strings.SplitN(ran, "-", 2)
   476  
   477  		// We limit to 8 bits in order to avoid accidental super long slices
   478  		num, err := strconv.ParseInt(split[0], 10, 8)
   479  		if err != nil {
   480  			return a, fmt.Errorf("invalid integer %q: %v", str, err)
   481  		}
   482  
   483  		if len(split) == 1 {
   484  			a = append(a, int(num))
   485  		} else {
   486  			endNum, err := strconv.ParseInt(split[1], 10, 8)
   487  			if err != nil {
   488  				return a, fmt.Errorf("invalid integer in range %q: %v", str, err)
   489  			}
   490  			if endNum <= num {
   491  				return a, fmt.Errorf("invalid integer range %q in %q", ran, str)
   492  			}
   493  			for i := num; i <= endNum; i++ {
   494  				a = append(a, int(i))
   495  			}
   496  		}
   497  	}
   498  	sort.Ints(a)
   499  	return a, nil
   500  }
   501  
   502  // resolve tries to resolve the requested configuration into a working
   503  // configuration
   504  func (c *Config) resolve() (config, error) {
   505  	var err error
   506  	conf := config{Options: c.Options}
   507  
   508  	grclog.DebugBlock(log, "resolving configuration:", "  ", "%s", utils.DumpJSON(c))
   509  
   510  	conf.Partitions, err = c.resolvePartitions()
   511  	if err != nil {
   512  		return conf, err
   513  	}
   514  
   515  	conf.Classes, err = c.resolveClasses()
   516  
   517  	return conf, err
   518  }
   519  
   520  // resolvePartitions tries to resolve the requested resource allocations of
   521  // partitions
   522  func (c *Config) resolvePartitions() (partitionSet, error) {
   523  	// Initialize empty partition configuration
   524  	conf := make(partitionSet, len(c.Partitions))
   525  	for name := range c.Partitions {
   526  		conf[name] = &partitionConfig{
   527  			CAT: map[cacheLevel]catSchema{
   528  				L2: newCatSchema(L2),
   529  				L3: newCatSchema(L3),
   530  			},
   531  			MB: make(mbSchema, len(info.mb.cacheIds))}
   532  	}
   533  
   534  	// Resolve L2 partition allocations
   535  	err := c.resolveCatPartitions(L2, conf)
   536  	if err != nil {
   537  		return nil, err
   538  	}
   539  
   540  	// Try to resolve L3 partition allocations
   541  	err = c.resolveCatPartitions(L3, conf)
   542  	if err != nil {
   543  		return nil, err
   544  	}
   545  
   546  	// Try to resolve MB partition allocations
   547  	err = c.resolveMBPartitions(conf)
   548  	if err != nil {
   549  		return nil, err
   550  	}
   551  
   552  	return conf, nil
   553  }
   554  
   555  // resolveCatPartitions tries to resolve requested cache allocations between partitions
   556  func (c *Config) resolveCatPartitions(lvl cacheLevel, conf partitionSet) error {
   557  	if len(c.Partitions) == 0 {
   558  		return nil
   559  	}
   560  
   561  	// Resolve partitions in sorted order for reproducibility
   562  	names := make([]string, 0, len(c.Partitions))
   563  	for name := range c.Partitions {
   564  		names = append(names, name)
   565  	}
   566  	sort.Strings(names)
   567  
   568  	resolver := newCacheResolver(lvl, names)
   569  
   570  	// Parse requested allocations from user config and load the resolver
   571  	for _, name := range names {
   572  		var allocations catSchema
   573  		var err error
   574  		switch lvl {
   575  		case L2:
   576  			allocations, err = c.Partitions[name].L2Allocation.toSchema(L2)
   577  		case L3:
   578  			allocations, err = c.Partitions[name].L3Allocation.toSchema(L3)
   579  		}
   580  
   581  		if err != nil {
   582  			return fmt.Errorf("failed to parse %s allocation request for partition %q: %v", lvl, name, err)
   583  		}
   584  
   585  		resolver.requests[name] = allocations.Alloc
   586  	}
   587  
   588  	// Run resolver fo partition allocations
   589  	grants, err := resolver.resolve()
   590  	if err != nil {
   591  		return err
   592  	}
   593  	if grants == nil {
   594  		log.Debugf("%s allocation disabled for all partitions", lvl)
   595  		return nil
   596  	}
   597  
   598  	for name, grant := range grants {
   599  		conf[name].CAT[lvl] = grant
   600  	}
   601  
   602  	heading := fmt.Sprintf("actual (and requested) %s allocations per partition and cache id:", lvl)
   603  	infoStr := ""
   604  	for name, partition := range resolver.requests {
   605  		infoStr += name + "\n"
   606  		for _, id := range resolver.ids {
   607  			infoStr += fmt.Sprintf("  %2d: ", id)
   608  			allocationReq := partition[id]
   609  			for _, typ := range []catSchemaType{catSchemaTypeUnified, catSchemaTypeCode, catSchemaTypeData} {
   610  				infoStr += string(typ) + " "
   611  				requested := allocationReq.get(typ)
   612  				switch v := requested.(type) {
   613  				case catAbsoluteAllocation:
   614  					infoStr += fmt.Sprintf("<absolute %#x>  ", v)
   615  				case catPctAllocation:
   616  					granted := grants[name].Alloc[id].get(typ).(catAbsoluteAllocation)
   617  					requestedPct := fmt.Sprintf("(%d%%)", v)
   618  					truePct := float64(bits.OnesCount64(uint64(granted))) * 100 / float64(resolver.bitsTotal)
   619  					infoStr += fmt.Sprintf("%5.1f%% %-6s ", truePct, requestedPct)
   620  				case nil:
   621  					infoStr += "<not specified>  "
   622  				}
   623  			}
   624  			infoStr += "\n"
   625  		}
   626  	}
   627  	grclog.DebugBlock(log, heading, "  ", "%s", infoStr)
   628  
   629  	return nil
   630  }
   631  
   632  // cacheResolver is a helper for resolving exclusive (partition) cache // allocation requests
   633  type cacheResolver struct {
   634  	lvl        cacheLevel
   635  	ids        []uint64
   636  	minBits    uint64
   637  	bitsTotal  uint64
   638  	partitions []string
   639  	requests   map[string]catSchemaRaw
   640  	grants     map[string]catSchema
   641  }
   642  
   643  func newCacheResolver(lvl cacheLevel, partitions []string) *cacheResolver {
   644  	r := &cacheResolver{
   645  		lvl:        lvl,
   646  		ids:        info.cat[lvl].cacheIds,
   647  		minBits:    info.cat[lvl].minCbmBits(),
   648  		bitsTotal:  uint64(info.cat[lvl].cbmMask().lsbZero()),
   649  		partitions: partitions,
   650  		requests:   make(map[string]catSchemaRaw, len(partitions)),
   651  		grants:     make(map[string]catSchema, len(partitions))}
   652  
   653  	for _, p := range partitions {
   654  		r.grants[p] = catSchema{Lvl: lvl, Alloc: make(catSchemaRaw, len(r.ids))}
   655  	}
   656  
   657  	return r
   658  }
   659  
   660  func (r *cacheResolver) resolve() (map[string]catSchema, error) {
   661  	for _, id := range r.ids {
   662  		err := r.resolveID(id)
   663  		if err != nil {
   664  			return nil, err
   665  		}
   666  	}
   667  	return r.grants, nil
   668  }
   669  
   670  // resolveCacheID resolves the partition allocations for one cache id
   671  func (r *cacheResolver) resolveID(id uint64) error {
   672  	for _, typ := range []catSchemaType{catSchemaTypeUnified, catSchemaTypeCode, catSchemaTypeData} {
   673  		log.Debugf("resolving partitions for %q schema for cache id %d", typ, id)
   674  		err := r.resolveType(id, typ)
   675  		if err != nil {
   676  			return err
   677  		}
   678  	}
   679  	return nil
   680  }
   681  
   682  // resolveType resolve one schema type for one cache id
   683  func (r *cacheResolver) resolveType(id uint64, typ catSchemaType) error {
   684  	// Sanity check: if any partition has l3 allocation of this schema type
   685  	// configured check that all other partitions have it, too
   686  	nils := []string{}
   687  	for _, partition := range r.partitions {
   688  		if r.requests[partition][id].get(typ) == nil {
   689  			nils = append(nils, partition)
   690  		}
   691  	}
   692  	if len(nils) > 0 && len(nils) != len(r.partitions) {
   693  		return fmt.Errorf("some partitions (%s) missing %s %q allocation request for cache id %d",
   694  			strings.Join(nils, ", "), r.lvl, typ, id)
   695  	}
   696  
   697  	// Act depending on the type of the first request in the list
   698  	a := r.requests[r.partitions[0]][id].get(typ)
   699  	switch a.(type) {
   700  	case catAbsoluteAllocation:
   701  		return r.resolveAbsolute(id, typ)
   702  	case nil:
   703  	default:
   704  		return r.resolveRelative(id, typ)
   705  	}
   706  	return nil
   707  }
   708  
   709  func (r *cacheResolver) resolveRelative(id uint64, typ catSchemaType) error {
   710  	type reqHelper struct {
   711  		name string
   712  		req  uint64
   713  	}
   714  
   715  	// Sanity check:
   716  	// 1. allocation requests are of the same type (relative)
   717  	// 2. total allocation requested for this cache id does not exceed 100 percent
   718  	// Additionally fill a helper structure for sorting partitions
   719  	percentageTotal := uint64(0)
   720  	reqs := make([]reqHelper, 0, len(r.partitions))
   721  	for _, partition := range r.partitions {
   722  		switch a := r.requests[partition][id].get(typ).(type) {
   723  		case catPctAllocation:
   724  			percentageTotal += uint64(a)
   725  			reqs = append(reqs, reqHelper{name: partition, req: uint64(a)})
   726  		case catAbsoluteAllocation:
   727  			return fmt.Errorf("error resolving %s allocation for cache id %d: mixing "+
   728  				"relative and absolute allocations between partitions not supported", r.lvl, id)
   729  		case catPctRangeAllocation:
   730  			return fmt.Errorf("percentage ranges in partition allocation not supported")
   731  		default:
   732  			return fmt.Errorf("BUG: unknown cacheAllocation type %T", a)
   733  		}
   734  	}
   735  	if percentageTotal < 100 {
   736  		log.Infof("requested total %s %q partition allocation for cache id %d <100%% (%d%%)", r.lvl, typ, id, percentageTotal)
   737  	} else if percentageTotal > 100 {
   738  		return fmt.Errorf("accumulated %s %q partition allocation requests for cache id %d exceeds 100%% (%d%%)", r.lvl, typ, id, percentageTotal)
   739  	}
   740  
   741  	// Sort partition allocations. We want to resolve smallest allocations
   742  	// first in order to try to ensure that all allocations can be satisfied
   743  	// because small percentages might need to be rounded up
   744  	sort.Slice(reqs, func(i, j int) bool {
   745  		return reqs[i].req < reqs[j].req
   746  	})
   747  
   748  	// Calculate number of bits granted to each partition.
   749  	grants := make(map[string]uint64, len(r.partitions))
   750  	bitsTotal := percentageTotal * uint64(r.bitsTotal) / 100
   751  	bitsAvailable := bitsTotal
   752  	for i, req := range reqs {
   753  		percentageAvailable := bitsAvailable * percentageTotal / bitsTotal
   754  
   755  		// This might happen e.g. if number of partitions would be greater
   756  		// than the total number of bits
   757  		if bitsAvailable < r.minBits {
   758  			return fmt.Errorf("unable to resolve %s allocation for cache id %d, not enough exlusive bits available", r.lvl, id)
   759  		}
   760  
   761  		// Use integer arithmetics, effectively always rounding down
   762  		// fractional allocations i.e. trying to avoid over-allocation
   763  		numBits := req.req * bitsAvailable / percentageAvailable
   764  
   765  		// Guarantee a non-zero allocation
   766  		if numBits < r.minBits {
   767  			numBits = r.minBits
   768  		}
   769  		// Don't overflow, allocate all remaining bits to the last partition
   770  		if numBits > bitsAvailable || i == len(reqs)-1 {
   771  			numBits = bitsAvailable
   772  		}
   773  
   774  		grants[req.name] = numBits
   775  		bitsAvailable -= numBits
   776  	}
   777  
   778  	// Construct the actual bitmasks for each partition
   779  	lsbID := uint64(0)
   780  	for _, partition := range r.partitions {
   781  		// Compose the actual bitmask
   782  		v := r.grants[partition].Alloc[id].set(typ, catAbsoluteAllocation(bitmask(((1<<grants[partition])-1)<<lsbID)))
   783  		r.grants[partition].Alloc[id] = v
   784  
   785  		lsbID += grants[partition]
   786  	}
   787  
   788  	return nil
   789  }
   790  
   791  func (r *cacheResolver) resolveAbsolute(id uint64, typ catSchemaType) error {
   792  	// Just sanity check:
   793  	// 1. allocation requests of the correct type (absolute)
   794  	// 2. allocations do not overlap
   795  	mask := bitmask(0)
   796  	for _, partition := range r.partitions {
   797  		a, ok := r.requests[partition][id].get(typ).(catAbsoluteAllocation)
   798  		if !ok {
   799  			return fmt.Errorf("error resolving %s allocation for cache id %d: mixing absolute and relative allocations between partitions not supported", r.lvl, id)
   800  		}
   801  		if bitmask(a)&mask > 0 {
   802  			return fmt.Errorf("overlapping %s partition allocation requests for cache id %d", r.lvl, id)
   803  		}
   804  		mask |= bitmask(a)
   805  
   806  		r.grants[partition].Alloc[id] = r.grants[partition].Alloc[id].set(typ, a)
   807  	}
   808  
   809  	return nil
   810  }
   811  
   812  // resolveMBPartitions tries to resolve requested MB allocations between partitions
   813  func (c *Config) resolveMBPartitions(conf partitionSet) error {
   814  	// We use percentage values directly from the user conf
   815  	for name, partition := range c.Partitions {
   816  		allocations, err := partition.MBAllocation.toSchema()
   817  		if err != nil {
   818  			return fmt.Errorf("failed to resolve MB allocation for partition %q: %v", name, err)
   819  		}
   820  		for id, allocation := range allocations {
   821  			conf[name].MB[id] = allocation
   822  			// Check that we don't go under the minimum allowed bandwidth setting
   823  			if !info.mb.mbpsEnabled && allocation < info.mb.minBandwidth {
   824  				conf[name].MB[id] = info.mb.minBandwidth
   825  			}
   826  		}
   827  	}
   828  
   829  	return nil
   830  }
   831  
   832  // resolveClasses tries to resolve class allocations of all partitions
   833  func (c *Config) resolveClasses() (classSet, error) {
   834  	classes := make(classSet)
   835  
   836  	for bname, partition := range c.Partitions {
   837  		for gname, class := range partition.Classes {
   838  			gname = unaliasClassName(gname)
   839  
   840  			if !IsQualifiedClassName(gname) {
   841  				return classes, fmt.Errorf("unqualified class name %q (must not be '.' or '..' and must not contain '/' or newline)", gname)
   842  			}
   843  			if _, ok := classes[gname]; ok {
   844  				return classes, fmt.Errorf("class names must be unique, %q defined multiple times", gname)
   845  			}
   846  
   847  			var err error
   848  			gc := &classConfig{Partition: bname,
   849  				CATSchema:  make(map[cacheLevel]catSchema),
   850  				Kubernetes: class.Kubernetes}
   851  
   852  			gc.CATSchema[L2], err = class.L2Allocation.toSchema(L2)
   853  			if err != nil {
   854  				return classes, fmt.Errorf("failed to resolve L2 allocation for class %q: %v", gname, err)
   855  			}
   856  			if gc.CATSchema[L2].Alloc != nil && partition.L2Allocation == nil {
   857  				return classes, fmt.Errorf("L2 allocation missing from partition %q but class %q specifies L2 schema", bname, gname)
   858  			}
   859  
   860  			gc.CATSchema[L3], err = class.L3Allocation.toSchema(L3)
   861  			if err != nil {
   862  				return classes, fmt.Errorf("failed to resolve L3 allocation for class %q: %v", gname, err)
   863  			}
   864  			if gc.CATSchema[L3].Alloc != nil && partition.L3Allocation == nil {
   865  				return classes, fmt.Errorf("L3 allocation missing from partition %q but class %q specifies L3 schema", bname, gname)
   866  			}
   867  
   868  			gc.MBSchema, err = class.MBAllocation.toSchema()
   869  			if err != nil {
   870  				return classes, fmt.Errorf("failed to resolve MB allocation for class %q: %v", gname, err)
   871  			}
   872  			if gc.MBSchema != nil && partition.MBAllocation == nil {
   873  				return classes, fmt.Errorf("MB allocation missing from partition %q but class %q specifies MB schema", bname, gname)
   874  			}
   875  
   876  			classes[gname] = gc
   877  		}
   878  	}
   879  
   880  	return classes, nil
   881  }
   882  
   883  // toSchema converts a cache allocation config to effective allocation schema covering all cache IDs
   884  func (c CatConfig) toSchema(lvl cacheLevel) (catSchema, error) {
   885  	if c == nil {
   886  		return catSchema{Lvl: lvl}, nil
   887  	}
   888  
   889  	allocations := newCatSchema(lvl)
   890  	minBits := info.cat[lvl].minCbmBits()
   891  
   892  	d, ok := c[CacheIdAll]
   893  	if !ok {
   894  		d = CacheIdCatConfig{Unified: "100%"}
   895  	}
   896  	defaultVal, err := d.parse(minBits)
   897  	if err != nil {
   898  		return allocations, err
   899  	}
   900  
   901  	// Pre-fill with defaults
   902  	for _, i := range info.cat[lvl].cacheIds {
   903  		allocations.Alloc[i] = defaultVal
   904  	}
   905  
   906  	for key, val := range c {
   907  		if key == CacheIdAll {
   908  			continue
   909  		}
   910  
   911  		ids, err := listStrToArray(key)
   912  		if err != nil {
   913  			return allocations, err
   914  		}
   915  
   916  		schemaVal, err := val.parse(minBits)
   917  		if err != nil {
   918  			return allocations, err
   919  		}
   920  
   921  		for _, id := range ids {
   922  			if _, ok := allocations.Alloc[uint64(id)]; ok {
   923  				allocations.Alloc[uint64(id)] = schemaVal
   924  			}
   925  		}
   926  	}
   927  
   928  	return allocations, nil
   929  }
   930  
   931  // catConfig is a helper for unmarshalling CatConfig
   932  type catConfig CatConfig
   933  
   934  // UnmarshalJSON implements the Unmarshaler interface of "encoding/json"
   935  func (c *CatConfig) UnmarshalJSON(data []byte) error {
   936  	raw := new(interface{})
   937  
   938  	err := json.Unmarshal(data, raw)
   939  	if err != nil {
   940  		return err
   941  	}
   942  
   943  	conf := CatConfig{}
   944  	switch v := (*raw).(type) {
   945  	case string:
   946  		conf[CacheIdAll] = CacheIdCatConfig{Unified: CacheProportion(v)}
   947  	default:
   948  		// Use the helper type to avoid infinite recursion
   949  		helper := catConfig{}
   950  		if err := json.Unmarshal(data, &helper); err != nil {
   951  			return err
   952  		}
   953  		for k, v := range helper {
   954  			conf[k] = v
   955  		}
   956  	}
   957  	*c = conf
   958  	return nil
   959  }
   960  
   961  // toSchema converts an MB allocation config to effective allocation schema covering all cache IDs
   962  func (c MbaConfig) toSchema() (mbSchema, error) {
   963  	if c == nil {
   964  		return nil, nil
   965  	}
   966  
   967  	d, ok := c[CacheIdAll]
   968  	if !ok {
   969  		d = CacheIdMbaConfig{"100" + mbSuffixPct, "4294967295" + mbSuffixMbps}
   970  	}
   971  	defaultVal, err := d.parse()
   972  	if err != nil {
   973  		return nil, err
   974  	}
   975  
   976  	allocations := make(mbSchema, len(info.mb.cacheIds))
   977  	// Pre-fill with defaults
   978  	for _, i := range info.mb.cacheIds {
   979  		allocations[i] = defaultVal
   980  	}
   981  
   982  	for key, val := range c {
   983  		if key == CacheIdAll {
   984  			continue
   985  		}
   986  
   987  		ids, err := listStrToArray(key)
   988  		if err != nil {
   989  			return nil, err
   990  		}
   991  
   992  		schemaVal, err := val.parse()
   993  		if err != nil {
   994  			return nil, err
   995  		}
   996  
   997  		for _, id := range ids {
   998  			if _, ok := allocations[uint64(id)]; ok {
   999  				allocations[uint64(id)] = schemaVal
  1000  			}
  1001  		}
  1002  	}
  1003  
  1004  	return allocations, nil
  1005  }
  1006  
  1007  // mbaConfig is a helper for unmarshalling MbaConfig
  1008  type mbaConfig MbaConfig
  1009  
  1010  // UnmarshalJSON implements the Unmarshaler interface of "encoding/json"
  1011  func (c *MbaConfig) UnmarshalJSON(data []byte) error {
  1012  	raw := new(interface{})
  1013  
  1014  	err := json.Unmarshal(data, raw)
  1015  	if err != nil {
  1016  		return err
  1017  	}
  1018  
  1019  	conf := MbaConfig{}
  1020  	switch (*raw).(type) {
  1021  	case []interface{}:
  1022  		helper := CacheIdMbaConfig{}
  1023  		if err := json.Unmarshal(data, &helper); err != nil {
  1024  			return err
  1025  		}
  1026  		conf[CacheIdAll] = helper
  1027  	default:
  1028  		// Use the helper type to avoid infinite recursion
  1029  		helper := mbaConfig{}
  1030  		if err := json.Unmarshal(data, &helper); err != nil {
  1031  			return err
  1032  		}
  1033  		for k, v := range helper {
  1034  			conf[k] = v
  1035  		}
  1036  	}
  1037  	*c = conf
  1038  	return nil
  1039  }
  1040  
  1041  // parse per cache-id CAT configuration into an effective allocation to be used
  1042  // in the CAT schema
  1043  func (c *CacheIdCatConfig) parse(minBits uint64) (catAllocation, error) {
  1044  	var err error
  1045  	allocation := catAllocation{}
  1046  
  1047  	allocation.Unified, err = c.Unified.parse(minBits)
  1048  	if err != nil {
  1049  		return allocation, err
  1050  	}
  1051  	allocation.Code, err = c.Code.parse(minBits)
  1052  	if err != nil {
  1053  		return allocation, err
  1054  	}
  1055  	allocation.Data, err = c.Data.parse(minBits)
  1056  	if err != nil {
  1057  		return allocation, err
  1058  	}
  1059  
  1060  	// Sanity check for the configuration
  1061  	if allocation.Unified == nil {
  1062  		return allocation, fmt.Errorf("'unified' not specified in cache schema %s", *c)
  1063  	}
  1064  	if allocation.Code != nil && allocation.Data == nil {
  1065  		return allocation, fmt.Errorf("'code' specified but missing 'data' from cache schema %s", *c)
  1066  	}
  1067  	if allocation.Code == nil && allocation.Data != nil {
  1068  		return allocation, fmt.Errorf("'data' specified but missing 'code' from cache schema %s", *c)
  1069  	}
  1070  
  1071  	return allocation, nil
  1072  }
  1073  
  1074  // cacheIdCatConfig is a helper for unmarshalling CacheIdCatConfig
  1075  type cacheIdCatConfig CacheIdCatConfig
  1076  
  1077  // UnmarshalJSON implements the Unmarshaler interface of "encoding/json"
  1078  func (c *CacheIdCatConfig) UnmarshalJSON(data []byte) error {
  1079  	raw := new(interface{})
  1080  
  1081  	err := json.Unmarshal(data, raw)
  1082  	if err != nil {
  1083  		return err
  1084  	}
  1085  
  1086  	conf := CacheIdCatConfig{}
  1087  	switch v := (*raw).(type) {
  1088  	case string:
  1089  		conf.Unified = CacheProportion(v)
  1090  	default:
  1091  		// Use the helper type to avoid infinite recursion
  1092  		helper := cacheIdCatConfig{}
  1093  		if err := json.Unmarshal(data, &helper); err != nil {
  1094  			return err
  1095  		}
  1096  		conf.Unified = helper.Unified
  1097  		conf.Code = helper.Code
  1098  		conf.Data = helper.Data
  1099  	}
  1100  	*c = conf
  1101  	return nil
  1102  }
  1103  
  1104  // parse converts a per cache-id MBA configuration into effective value
  1105  // to be used in the MBA schema
  1106  func (c *CacheIdMbaConfig) parse() (uint64, error) {
  1107  	for _, v := range *c {
  1108  		str := string(v)
  1109  		if strings.HasSuffix(str, mbSuffixPct) {
  1110  			if !info.mb.mbpsEnabled {
  1111  				value, err := strconv.ParseUint(strings.TrimSuffix(str, mbSuffixPct), 10, 7)
  1112  				if err != nil {
  1113  					return 0, err
  1114  				}
  1115  				return value, nil
  1116  			}
  1117  		} else if strings.HasSuffix(str, mbSuffixMbps) {
  1118  			if info.mb.mbpsEnabled {
  1119  				value, err := strconv.ParseUint(strings.TrimSuffix(str, mbSuffixMbps), 10, 32)
  1120  				if err != nil {
  1121  					return 0, err
  1122  				}
  1123  				return value, nil
  1124  			}
  1125  		} else {
  1126  			log.Warnf("unrecognized MBA allocation unit in %q", str)
  1127  		}
  1128  	}
  1129  
  1130  	// No value for the active mode was specified
  1131  	if info.mb.mbpsEnabled {
  1132  		return 0, fmt.Errorf("missing 'MBps' value from mbSchema; required because 'mba_MBps' is enabled in the system")
  1133  	}
  1134  	return 0, fmt.Errorf("missing '%%' value from mbSchema; required because percentage-based MBA allocation is enabled in the system")
  1135  }
  1136  
  1137  // parse converts a string value into cacheAllocation type
  1138  func (c CacheProportion) parse(minBits uint64) (cacheAllocation, error) {
  1139  	if c == "" {
  1140  		return nil, nil
  1141  	}
  1142  
  1143  	if c[len(c)-1] == '%' {
  1144  		// Percentages of the max number of bits
  1145  		split := strings.SplitN(string(c)[0:len(c)-1], "-", 2)
  1146  		var allocation cacheAllocation
  1147  
  1148  		if len(split) == 1 {
  1149  			pct, err := strconv.ParseUint(split[0], 10, 7)
  1150  			if err != nil {
  1151  				return allocation, err
  1152  			}
  1153  			if pct > 100 {
  1154  				return allocation, fmt.Errorf("invalid percentage value %q", c)
  1155  			}
  1156  			allocation = catPctAllocation(pct)
  1157  		} else {
  1158  			low, err := strconv.ParseUint(split[0], 10, 7)
  1159  			if err != nil {
  1160  				return allocation, err
  1161  			}
  1162  			high, err := strconv.ParseUint(split[1], 10, 7)
  1163  			if err != nil {
  1164  				return allocation, err
  1165  			}
  1166  			if low > high || low > 100 || high > 100 {
  1167  				return allocation, fmt.Errorf("invalid percentage range %q", c)
  1168  			}
  1169  			allocation = catPctRangeAllocation{lowPct: low, highPct: high}
  1170  		}
  1171  
  1172  		return allocation, nil
  1173  	}
  1174  
  1175  	// Absolute allocation
  1176  	var value uint64
  1177  	var err error
  1178  	if strings.HasPrefix(string(c), "0x") {
  1179  		// Hex value
  1180  		value, err = strconv.ParseUint(string(c[2:]), 16, 64)
  1181  		if err != nil {
  1182  			return nil, err
  1183  		}
  1184  	} else {
  1185  		// Last, try "list" format (i.e. smthg like 0,2,5-9,...)
  1186  		tmp, err := listStrToBitmask(string(c))
  1187  		value = uint64(tmp)
  1188  		if err != nil {
  1189  			return nil, err
  1190  		}
  1191  	}
  1192  
  1193  	// Sanity check of absolute allocation: bitmask must (only) contain one
  1194  	// contiguous block of ones wide enough
  1195  	numOnes := bits.OnesCount64(value)
  1196  	if numOnes != bits.Len64(value)-bits.TrailingZeros64(value) {
  1197  		return nil, fmt.Errorf("invalid cache bitmask %q: more than one continuous block of ones", c)
  1198  	}
  1199  	if uint64(numOnes) < minBits {
  1200  		return nil, fmt.Errorf("invalid cache bitmask %q: number of bits less than %d", c, minBits)
  1201  	}
  1202  
  1203  	return catAbsoluteAllocation(value), nil
  1204  }