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 }