github.com/intel/goresctrl@v0.5.0/pkg/rdt/rdt_test.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 stdlog "log" 21 "os" 22 "os/exec" 23 "path/filepath" 24 "regexp" 25 "sort" 26 "strings" 27 "testing" 28 29 "sigs.k8s.io/yaml" 30 31 "github.com/google/go-cmp/cmp" 32 33 grclog "github.com/intel/goresctrl/pkg/log" 34 "github.com/intel/goresctrl/pkg/testutils" 35 "github.com/intel/goresctrl/pkg/utils" 36 testdata "github.com/intel/goresctrl/test/data" 37 ) 38 39 const mockGroupPrefix string = "goresctrl." 40 41 type mockResctrlFs struct { 42 t *testing.T 43 44 origDir string 45 baseDir string 46 } 47 48 func newMockResctrlFs(t *testing.T, name, mountOpts string) (*mockResctrlFs, error) { 49 var err error 50 m := &mockResctrlFs{t: t} 51 52 m.origDir = testdata.Path(name) 53 m.baseDir, err = os.MkdirTemp("", "goresctrl.test.") 54 if err != nil { 55 return nil, err 56 } 57 58 // Create resctrl filesystem mock 59 m.copyFromOrig("", "") 60 61 // Create mountinfo mock 62 mountInfoPath = filepath.Join(m.baseDir, "mounts") 63 resctrlPath := filepath.Join(m.baseDir, "resctrl") 64 data := "resctrl " + resctrlPath + " resctrl " + mountOpts + " 0 0\n" 65 if err := os.WriteFile(mountInfoPath, []byte(data), 0644); err != nil { 66 m.delete() 67 return nil, err 68 } 69 return m, nil 70 } 71 72 func (m *mockResctrlFs) delete() { 73 if err := os.RemoveAll(m.baseDir); err != nil { 74 m.t.Fatalf("failed to delete mock resctrl fs: %v", err) 75 } 76 } 77 78 func (m *mockResctrlFs) initMockMonGroup(class, name string) { 79 m.copyFromOrig(filepath.Join("mon_groups", "example"), filepath.Join(mockGroupPrefix+class, "mon_groups", mockGroupPrefix+name)) 80 } 81 82 func (m *mockResctrlFs) copyFromOrig(relSrc, relDst string) { 83 absSrc := filepath.Join(m.origDir, relSrc) 84 if s, err := os.Stat(absSrc); err != nil { 85 m.t.Fatalf("%v", err) 86 } else if s.IsDir() { 87 absSrc = filepath.Join(absSrc, ".") 88 } 89 90 absDst := filepath.Join(m.baseDir, "resctrl", relDst) 91 cmd := exec.Command("cp", "-r", absSrc, absDst) 92 if err := cmd.Run(); err != nil { 93 m.t.Fatalf("failed to copy mock data %q -> %q: %v", absSrc, absDst, err) 94 } 95 } 96 97 func (m *mockResctrlFs) verifyTextFile(relPath, content string) { 98 verifyTextFile(m.t, filepath.Join(m.baseDir, "resctrl", relPath), content) 99 } 100 101 func verifyTextFile(t *testing.T, path, content string) { 102 data, err := os.ReadFile(path) 103 if err != nil { 104 t.Fatalf("failed to read %q: %v", path, err) 105 } 106 if string(data) != content { 107 t.Fatalf("unexpected content in %q\nexpected:\n %q\nfound:\n %q", path, content, data) 108 } 109 } 110 111 func parseTestConfig(t *testing.T, data string) *Config { 112 c := &Config{} 113 if err := yaml.Unmarshal([]byte(data), c); err != nil { 114 t.Fatalf("failed to parse rdt config: %v", err) 115 } 116 return c 117 } 118 119 // TestRdt tests the rdt public API, i.e. exported functionality of the package 120 func TestRdt(t *testing.T) { 121 const rdtTestConfig string = ` 122 partitions: 123 priority: 124 l3Allocation: 125 all: 60% 126 mbAllocation: 127 all: [100%] 128 classes: 129 Guaranteed: 130 l3Allocation: 131 all: 100% 132 default: 133 l3Allocation: 134 all: 40% 135 mbAllocation: 136 all: [100%] 137 classes: 138 Burstable: 139 l3Allocation: 140 all: 100% 141 mbAllocation: 142 all: [66%] 143 BestEffort: 144 l3Allocation: 145 all: 66% 146 mbAllocation: 147 all: [33%] 148 kubernetes: 149 denyPodAnnotation: true 150 kubernetes: 151 allowedPodAnnotationClasses: [bar, foo] 152 ` 153 154 verifyGroupNames := func(a interface{}, b []string) { 155 var names []string 156 157 switch v := a.(type) { 158 case []CtrlGroup: 159 for _, g := range v { 160 names = append(names, g.Name()) 161 } 162 case []MonGroup: 163 for _, g := range v { 164 names = append(names, g.Name()) 165 } 166 default: 167 t.Errorf("Invalid type '%T' in verifyGroupNames()", a) 168 return 169 } 170 if len(b) == 0 && len(names) == 0 { 171 return 172 } 173 sort.Strings(names) 174 sort.Strings(b) 175 if !cmp.Equal(names, b) { 176 t.Errorf("unexpected class/group names: expected %s got %s", b, names) 177 } 178 } 179 180 // Set group remove function so that mock groups can be removed 181 groupRemoveFunc = os.RemoveAll 182 183 // 184 // 1. test uninitialized interface 185 // 186 rdt = nil 187 SetLogger(grclog.NewLoggerWrapper(stdlog.New(os.Stderr, "[ rdt-test-1 ] ", 0))) 188 189 if err := SetConfig(&Config{}, false); err == nil { 190 t.Errorf("setting config on uninitialized rdt succeeded unexpectedly") 191 192 } 193 if classes := GetClasses(); len(classes) != 0 { 194 t.Errorf("uninitialized rdt contains classes %s", classes) 195 } 196 if _, ok := GetClass(""); ok { 197 t.Errorf("expected to not get a class with empty name") 198 } 199 if MonSupported() { 200 t.Errorf("unitialized rdt claims monitoring to be supported") 201 } 202 if features := GetMonFeatures(); len(features) != 0 { 203 t.Errorf("uninitialized rdt returned monitoring features %s", features) 204 } 205 206 // 207 // 2. Test setting up RDT with L3 L3_MON and MB support 208 // 209 mockFs, err := newMockResctrlFs(t, "resctrl.full", "") 210 if err != nil { 211 t.Fatalf("failed to set up mock resctrl fs: %v", err) 212 } 213 defer mockFs.delete() 214 215 if err := Initialize(mockGroupPrefix); err != nil { 216 t.Fatalf("rdt initialization failed: %v", err) 217 } 218 219 // Check that existing groups were read correctly on init 220 classes := GetClasses() 221 verifyGroupNames(classes, []string{"Guaranteed", "Stale", RootClassName}) 222 223 cls, _ := GetClass(RootClassName) 224 verifyGroupNames(cls.GetMonGroups(), []string{}) 225 cls, _ = GetClass("Guaranteed") 226 verifyGroupNames(cls.GetMonGroups(), []string{"predefined_group_empty", "predefined_group_live"}) 227 cls, _ = GetClass("Stale") 228 if err := cls.AddPids("99"); err != nil { 229 t.Fatalf("AddPids() failed: %v", err) 230 } 231 232 // Invalid test config content should cause an error 233 if err := SetConfigFromData([]byte("partitions: foo"), true); err == nil { 234 t.Fatalf("rdt configuration with invalid file succeeded unexpetedly") 235 } 236 // Non-existent configuration file should cause an error 237 if err := SetConfigFromFile("non-existent-config-file", true); err == nil { 238 t.Fatalf("rdt configuration with non-existent file succeeded unexpetedly") 239 } 240 // Configuration should fail as "Stale" class has pids assigned to it 241 testConfigFile := testutils.CreateTempFile(t, rdtTestConfig) 242 defer os.Remove(testConfigFile) 243 if err := SetConfigFromFile(testConfigFile, false); err == nil { 244 t.Fatalf("rdt configuration succeeded unexpetedly") 245 } 246 // Forced configuration should succeed 247 if err := SetConfigFromFile(testConfigFile, true); err != nil { 248 t.Fatalf("rdt forced configuration failed: %v", err) 249 } 250 251 // Check that KubernetesOptions of classes are parsed and propagated correctly 252 if !rdt.conf.Classes["BestEffort"].Kubernetes.DenyPodAnnotation { 253 t.Fatal("DenyPodAnnotation of class BestEffort should be 'true'") 254 } 255 256 // Empty mon group(s) should be pruned after configuration 257 cls, _ = GetClass("Guaranteed") 258 verifyGroupNames(cls.GetMonGroups(), []string{"predefined_group_live"}) 259 260 // Check that SetLogger() takes effect in the control interface, too 261 l := grclog.NewLoggerWrapper(stdlog.New(os.Stderr, "[ rdt-test-2 ] ", 0)) 262 SetLogger(l) 263 if l != rdt.Logger { 264 t.Errorf("unexpected logger implementation") 265 } 266 267 // Check that the path() and relPath() methods work correctly 268 if p := rdt.classes["Guaranteed"].path("foo"); p != filepath.Join(mockFs.baseDir, "resctrl", "goresctrl.Guaranteed", "foo") { 269 t.Errorf("path() returned wrong path %q", p) 270 } 271 if p := rdt.classes["Guaranteed"].relPath("foo"); p != filepath.Join("goresctrl.Guaranteed", "foo") { 272 t.Errorf("relPath() returned wrong path %q", p) 273 } 274 275 // Verify that ctrl groups are correctly configured 276 mockFs.verifyTextFile(rdt.classes["BestEffort"].relPath("schemata"), 277 "L3:0=3f;1=3f;2=3f;3=3f\nMB:0=33;1=33;2=33;3=33\n") 278 mockFs.verifyTextFile(rdt.classes["Burstable"].relPath("schemata"), 279 "L3:0=ff;1=ff;2=ff;3=ff\nMB:0=66;1=66;2=66;3=66\n") 280 mockFs.verifyTextFile(rdt.classes["Guaranteed"].relPath("schemata"), 281 "L3:0=fff00;1=fff00;2=fff00;3=fff00\nMB:0=100;1=100;2=100;3=100\n") 282 283 // Verify that existing goresctrl monitor groups were removed 284 for _, cls := range []string{RootClassName, "Guaranteed"} { 285 files, _ := os.ReadDir(rdt.classes[cls].path("mon_groups")) 286 for _, f := range files { 287 if strings.HasPrefix(mockGroupPrefix, f.Name()) { 288 t.Errorf("unexpected monitor group found %q", f.Name()) 289 } 290 } 291 } 292 293 // Verify GetClasses 294 classes = GetClasses() 295 verifyGroupNames(classes, []string{"BestEffort", "Burstable", "Guaranteed", RootClassName}) 296 297 // Verify assigning pids to classes (ctrl groups) 298 cls, _ = GetClass("Guaranteed") 299 if n := cls.Name(); n != "Guaranteed" { 300 t.Errorf("CtrlGroup.Name() returned %q, expected %q", n, "Guaranteed") 301 } 302 303 pids := []string{"10", "11", "12"} 304 if err := cls.AddPids(pids...); err != nil { 305 t.Errorf("AddPids() failed: %v", err) 306 } 307 if p, err := cls.GetPids(); err != nil { 308 t.Errorf("GetPids() failed: %v", err) 309 } else if !cmp.Equal(p, pids) { 310 t.Errorf("GetPids() returned %s, expected %s", p, pids) 311 } 312 313 mockFs.verifyTextFile(rdt.classes["Guaranteed"].relPath("tasks"), "10\n11\n12\n") 314 315 // Verify MonSupported and GetMonFeatures 316 if !MonSupported() { 317 t.Errorf("MonSupported() returned false, expected true") 318 } 319 expectedMonFeatures := map[MonResource][]string{MonResourceL3: []string{"llc_occupancy", "mbm_local_bytes", "mbm_total_bytes"}} 320 if features := GetMonFeatures(); !cmp.Equal(features, expectedMonFeatures) { 321 t.Fatalf("GetMonFeatures() returned %v, expected %v", features, expectedMonFeatures) 322 } 323 324 // Test creating monitoring groups 325 cls, _ = GetClass("Guaranteed") 326 mgName := "test_group" 327 mgAnnotations := map[string]string{"a_key": "a_value"} 328 mg, err := cls.CreateMonGroup(mgName, mgAnnotations) 329 if err != nil { 330 t.Fatalf("creating mon group failed: %v", err) 331 } 332 if n := mg.Name(); n != mgName { 333 t.Errorf("MonGroup.Name() returned %q, expected %q", n, mgName) 334 } 335 if a := mg.GetAnnotations(); !cmp.Equal(a, mgAnnotations) { 336 t.Errorf("MonGroup.GetAnnotations() returned %s, expected %s", a, mgAnnotations) 337 } 338 if n := mg.Parent().Name(); n != "Guaranteed" { 339 t.Errorf("MonGroup.Parent().Name() returned %q, expected %q", n, "Guaranteed") 340 } 341 342 if _, ok := cls.GetMonGroup("non-existing-group"); ok { 343 t.Errorf("unexpected success when querying non-existing group") 344 } 345 if _, ok := cls.GetMonGroup(mgName); !ok { 346 t.Errorf("unexpected error when querying mon group: %v", err) 347 } 348 349 verifyGroupNames(cls.GetMonGroups(), []string{"predefined_group_live", mgName}) 350 351 mgPath := rdt.classes["Guaranteed"].path("mon_groups", "goresctrl."+mgName) 352 if _, err := os.Stat(mgPath); err != nil { 353 t.Errorf("mon group directory not found: %v", err) 354 } 355 356 // Check that the monGroup.path() and relPath() methods work correctly 357 mgi := rdt.classes["Guaranteed"].monGroups[mgName] 358 if p := mgi.path("foo"); p != filepath.Join(mockFs.baseDir, "resctrl", "goresctrl.Guaranteed", "mon_groups", "goresctrl."+mgName, "foo") { 359 t.Errorf("path() returned wrong path %q", p) 360 } 361 if p := mgi.relPath("foo"); p != filepath.Join("goresctrl.Guaranteed", "mon_groups", "goresctrl."+mgName, "foo") { 362 t.Errorf("relPath() returned wrong path %q", p) 363 } 364 365 // Test deleting monitoring groups 366 if err := cls.DeleteMonGroup(mgName); err != nil { 367 t.Errorf("unexpected error when deleting mon group: %v", err) 368 } 369 if _, ok := cls.GetMonGroup("non-existing-group"); ok { 370 t.Errorf("unexpected success when querying deleted group") 371 } 372 if _, err := os.Stat(mgPath); !os.IsNotExist(err) { 373 t.Errorf("unexpected error when checking directory of deleted mon group: %v", err) 374 } 375 376 for _, n := range []string{"foo", "bar", "baz"} { 377 if _, err := cls.CreateMonGroup(n, map[string]string{}); err != nil { 378 t.Errorf("creating mon group failed: %v", err) 379 } 380 } 381 if err := cls.DeleteMonGroups(); err != nil { 382 t.Errorf("unexpected error when deleting all mon groups: %v", err) 383 } 384 if mgs := cls.GetMonGroups(); len(mgs) != 0 { 385 t.Errorf("unexpected mon groups exist: %v", mgs) 386 } 387 388 // Verify assigning pids to monitor group 389 mgName = "test_group_2" 390 mockFs.initMockMonGroup("Guaranteed", mgName) 391 cls, _ = GetClass("Guaranteed") 392 mg, _ = cls.CreateMonGroup(mgName, nil) 393 394 pids = []string{"10"} 395 if err := mg.AddPids(pids...); err != nil { 396 t.Errorf("MonGroup.AddPids() failed: %v", err) 397 } 398 if p, err := mg.GetPids(); err != nil { 399 t.Errorf("MonGroup.GetPids() failed: %v", err) 400 } else if !cmp.Equal(p, pids) { 401 t.Errorf("MonGroup.GetPids() returned %s, expected %s", p, pids) 402 } 403 mockFs.verifyTextFile(rdt.classes["Guaranteed"].monGroups[mgName].relPath("tasks"), "10\n") 404 405 // Verify monitoring functionality 406 expected := MonData{ 407 L3: MonL3Data{ 408 0: MonLeafData{ 409 "llc_occupancy": 1, 410 "mbm_local_bytes": 2, 411 "mbm_total_bytes": 3, 412 }, 413 1: MonLeafData{ 414 "llc_occupancy": 11, 415 "mbm_local_bytes": 12, 416 "mbm_total_bytes": 13, 417 }, 418 2: MonLeafData{ 419 "llc_occupancy": 21, 420 "mbm_local_bytes": 22, 421 "mbm_total_bytes": 23, 422 }, 423 3: MonLeafData{ 424 "llc_occupancy": 31, 425 "mbm_local_bytes": 32, 426 "mbm_total_bytes": 33, 427 }, 428 }, 429 } 430 md := mg.GetMonData() 431 if !cmp.Equal(md, expected) { 432 t.Errorf("unexcpected monitoring data\nexpected:\n%s\nreceived:\n%s", utils.DumpJSON(expected), utils.DumpJSON(md)) 433 } 434 435 // 436 // 3. Test discovery 437 // 438 if err := DiscoverClasses(""); err != nil { 439 t.Fatalf("DiscoverClasses() failed unexpectedly") 440 } 441 classes = GetClasses() 442 verifyGroupNames(classes, []string{"Guaranteed", "non_goresctrl.Group", RootClassName}) 443 444 if err := DiscoverClasses("non_goresctrl."); err != nil { 445 t.Fatalf("DiscoverClasses() failed unexpectedly") 446 } 447 classes = GetClasses() 448 verifyGroupNames(classes, []string{"Group", RootClassName}) 449 450 if err := DiscoverClasses("non-existing-prefix"); err != nil { 451 t.Fatalf("DiscoverClasses() failed unexpectedly") 452 } 453 classes = GetClasses() 454 verifyGroupNames(classes, []string{RootClassName}) 455 } 456 457 // TestConfig tests configuration parsing and resolving 458 func TestConfig(t *testing.T) { 459 type Schemata struct { 460 l2 string 461 l2code string 462 l2data string 463 l3 string 464 l3code string 465 l3data string 466 mb string 467 } 468 469 type TC struct { 470 name string 471 fs string 472 fsMountOpts string 473 config string 474 configErrRe string 475 schemata map[string]Schemata 476 } 477 478 tcs := []TC{ 479 // Testcase 480 TC{ 481 name: "Empty config", 482 fs: "resctrl.full", 483 config: "", 484 schemata: map[string]Schemata{ 485 "system/default": Schemata{ 486 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 487 mb: "0=100;1=100;2=100;3=100", 488 }, 489 }, 490 }, 491 // Testcase 492 TC{ 493 name: "Complex config", 494 fs: "resctrl.full", 495 config: ` 496 partitions: 497 part-1: 498 l3Allocation: 499 all: 60% 500 1: "0xff000" 501 2: "9-15" 502 mbAllocation: 503 all: [100%] 504 classes: 505 class-1: 506 l3Allocation: 100% 507 class-2: 508 l3Allocation: 509 all: 100% 510 0-1: 10% 511 2: "0x70" 512 mbAllocation: 513 all: [40%] 514 3: [10%] 515 part-2: 516 l3Allocation: 517 all: 39% 518 1: "0-10" 519 2: "0-6" 520 mbAllocation: 521 all: [50%] 522 1: [80%] 523 2: [100%] 524 classes: 525 class-3: 526 l3Allocation: 100% 527 mbAllocation: 528 all: [40%] 529 0: [80%] 530 class-4: 531 l3Allocation: 50% 532 mbAllocation: [100%] 533 system/default: 534 l3Allocation: 60% 535 mbAllocation: [60%] 536 part-3: 537 l3Allocation: 538 all: 1% 539 1: "0x800" 540 2: "7,8" 541 mbAllocation: [20%] 542 classes: 543 class-5: 544 l3Allocation: 100% 545 mbAllocation: 546 all: [100%] 547 0: [1%] 548 `, 549 schemata: map[string]Schemata{ 550 "class-1": Schemata{ 551 l3: "0=fff;1=ff000;2=fe00;3=fff", 552 mb: "0=100;1=100;2=100;3=100", 553 }, 554 "class-2": Schemata{ 555 l3: "0=3;1=1000;2=e000;3=fff", 556 mb: "0=40;1=40;2=40;3=10", 557 }, 558 "class-3": Schemata{ 559 l3: "0=7f000;1=7ff;2=7f;3=7f000", 560 mb: "0=40;1=32;2=40;3=20", 561 }, 562 "class-4": Schemata{ 563 l3: "0=f000;1=3f;2=f;3=f000", 564 mb: "0=50;1=80;2=100;3=50", 565 }, 566 "system/default": Schemata{ 567 l3: "0=1f000;1=7f;2=1f;3=1f000", 568 mb: "0=30;1=48;2=60;3=30", 569 }, 570 "class-5": Schemata{ 571 l3: "0=80000;1=800;2=180;3=80000", 572 mb: "0=10;1=20;2=20;3=20", 573 }, 574 }, 575 }, 576 // Testcase 577 TC{ 578 name: "L3 CDP disabled", 579 fs: "resctrl.nomb", 580 config: ` 581 partitions: 582 part-1: 583 l3Allocation: 584 0,1: 585 unified: 60% 586 code: 70% 587 data: 50% 588 2,3: 40% 589 classes: 590 class-1: 591 part-2: 592 l3Allocation: 593 0,1: 594 unified: 40% 595 code: 30% 596 data: 50% 597 2,3: 60% 598 classes: 599 class-2: 600 system/default: 601 l3Allocation: 602 all: 100% 603 3: 604 unified: 80% 605 code: 60% 606 data: 90% 607 608 `, 609 schemata: map[string]Schemata{ 610 "class-1": Schemata{ 611 l3: "0=fff;1=fff;2=ff;3=ff", 612 }, 613 "class-2": Schemata{ 614 l3: "0=ff000;1=ff000;2=fff00;3=fff00", 615 }, 616 "system/default": Schemata{ 617 l3: "0=ff000;1=ff000;2=fff00;3=3ff00", 618 }, 619 }, 620 }, 621 // Testcase 622 TC{ 623 name: "L3 CDP enabled", 624 fs: "resctrl.nomb.cdp", 625 config: ` 626 partitions: 627 part-1: 628 l3Allocation: 629 0,1: 630 unified: 60% 631 code: 70% 632 data: 50% 633 2,3: 40% 634 classes: 635 class-1: 636 part-2: 637 l3Allocation: 638 0,1: 639 unified: 40% 640 code: 30% 641 data: 50% 642 2,3: 60% 643 classes: 644 class-2: 645 "": 646 l3Allocation: 647 all: 100% 648 3: 649 unified: 80% 650 code: 60% 651 data: 90% 652 653 `, 654 schemata: map[string]Schemata{ 655 "class-1": Schemata{ 656 l3code: "0=3fff;1=3fff;2=ff;3=ff", 657 l3data: "0=3ff;1=3ff;2=ff;3=ff", 658 }, 659 "class-2": Schemata{ 660 l3code: "0=fc000;1=fc000;2=fff00;3=fff00", 661 l3data: "0=ffc00;1=ffc00;2=fff00;3=fff00", 662 }, 663 "system/default": Schemata{ 664 l3code: "0=fc000;1=fc000;2=fff00;3=ff00", 665 l3data: "0=ffc00;1=ffc00;2=fff00;3=7ff00", 666 }, 667 }, 668 }, 669 // Testcase 670 TC{ 671 name: "L3 optional", 672 fs: "resctrl.nol3", 673 config: ` 674 options: 675 l3: 676 optional: true 677 partitions: 678 part-1: 679 l3Allocation: 100% 680 mbAllocation: [100%] 681 classes: 682 class-1: 683 l3Allocation: 20% 684 mbAllocation: [50%] 685 `, 686 schemata: map[string]Schemata{ 687 "class-1": Schemata{ 688 mb: "0=50;1=50;2=50;3=50", 689 }, 690 "system/default": Schemata{ 691 mb: "0=100;1=100;2=100;3=100", 692 }, 693 }, 694 }, 695 // Testcase 696 TC{ 697 name: "Default L3 CAT", 698 fs: "resctrl.full", 699 config: ` 700 options: 701 partitions: 702 part-1: 703 mbAllocation: [100%] 704 classes: 705 class-1: 706 mbAllocation: [50%] 707 `, 708 schemata: map[string]Schemata{ 709 "class-1": Schemata{ 710 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 711 mb: "0=50;1=50;2=50;3=50", 712 }, 713 "system/default": Schemata{ 714 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 715 mb: "0=100;1=100;2=100;3=100", 716 }, 717 }, 718 }, 719 // Testcase 720 TC{ 721 name: "Default MBA", 722 fs: "resctrl.full", 723 config: ` 724 options: 725 partitions: 726 part-1: 727 l3Allocation: 100% 728 classes: 729 class-1: 730 l3Allocation: 50% 731 `, 732 schemata: map[string]Schemata{ 733 "class-1": Schemata{ 734 l3: "0=3ff;1=3ff;2=3ff;3=3ff", 735 mb: "0=100;1=100;2=100;3=100", 736 }, 737 "system/default": Schemata{ 738 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 739 mb: "0=100;1=100;2=100;3=100", 740 }, 741 }, 742 }, 743 // Testcase 744 TC{ 745 name: "duplicate class names (fail)", 746 fs: "resctrl.nomb", 747 configErrRe: `"class-1" defined multiple times`, 748 config: ` 749 partitions: 750 part-1: 751 classes: 752 class-1: 753 part-2: 754 classes: 755 class-1: 756 `, 757 }, 758 // Testcase 759 TC{ 760 name: "duplicate root class (fail)", 761 fs: "resctrl.nomb", 762 configErrRe: `"system/default" defined multiple times`, 763 config: ` 764 partitions: 765 part-1: 766 classes: 767 "": 768 part-2: 769 classes: 770 system/default: 771 `, 772 }, 773 // Testcase 774 TC{ 775 name: "invalid class name", 776 fs: "resctrl.nomb", 777 configErrRe: `unqualified class name`, 778 config: ` 779 partitions: 780 part-1: 781 classes: 782 "..": 783 `, 784 }, 785 // Testcase 786 TC{ 787 name: "Invalid cache ids (fail)", 788 fs: "resctrl.nomb", 789 configErrRe: `failed to parse L3 allocation request for partition "part-1": invalid integer "a"`, 790 config: ` 791 partitions: 792 part-1: 793 l3Allocation: 794 a: 100% 795 `, 796 }, 797 // Testcase 798 TC{ 799 name: "L3 invalid allocation schema #3, missing unified (fail)", 800 fs: "resctrl.nomb", 801 configErrRe: `failed to parse L3 allocation request for partition "part-1": 'unified' not specified in cache schema`, 802 config: ` 803 partitions: 804 part-1: 805 l3Allocation: 806 all: 807 data: 100% 808 `, 809 }, 810 // Testcase 811 TC{ 812 name: "L3 invalid allocation schema #4, missing code (fail)", 813 fs: "resctrl.nomb", 814 configErrRe: `failed to parse L3 allocation request for partition "part-1": 'data' specified but missing 'code' from cache schema`, 815 config: ` 816 partitions: 817 part-1: 818 l3Allocation: 819 all: 820 unified: 100% 821 data: 100% 822 `, 823 }, 824 // Testcase 825 TC{ 826 name: "L3 invalid allocation schema #5, missing data (fail)", 827 fs: "resctrl.nomb", 828 configErrRe: `failed to parse L3 allocation request for partition "part-1": 'code' specified but missing 'data' from cache schema`, 829 config: ` 830 partitions: 831 part-1: 832 l3Allocation: 833 all: 834 unified: 100% 835 code: 100% 836 `, 837 }, 838 // Testcase 839 TC{ 840 name: "L3 required (fail)", 841 fs: "resctrl.nol3", 842 configErrRe: `L3 cache allocation for "class-1" specified in configuration but not supported by system`, 843 config: ` 844 partitions: 845 part-1: 846 l3Allocation: 100% 847 classes: 848 class-1: 849 l3Allocation: 20% 850 `, 851 }, 852 // Testcase 853 TC{ 854 name: "MB optional", 855 fs: "resctrl.nomb", 856 config: ` 857 options: 858 mb: 859 optional: true 860 partitions: 861 part-1: 862 l3Allocation: 100% 863 mbAllocation: [100%] 864 classes: 865 class-1: 866 l3Allocation: 0-7 867 mbAllocation: [50%] 868 `, 869 schemata: map[string]Schemata{ 870 "class-1": Schemata{ 871 l3: "0=ff;1=ff;2=ff;3=ff", 872 }, 873 "system/default": Schemata{ 874 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 875 }, 876 }, 877 }, 878 // Testcase 879 TC{ 880 name: "MB required (fail)", 881 fs: "resctrl.nomb", 882 configErrRe: `memory bandwidth allocation for "class-1" specified in configuration but not supported by system`, 883 config: ` 884 partitions: 885 part-1: 886 mbAllocation: [100%] 887 classes: 888 class-1: 889 mbAllocation: [50%] 890 `, 891 }, 892 // Testcase 893 TC{ 894 name: "L3 mix rel and abs allocation in partition (fail)", 895 fs: "resctrl.full", 896 configErrRe: "error resolving L3 allocation for cache id 0: mixing absolute and relative allocations between partitions not supported", 897 config: ` 898 partitions: 899 part-1: 900 l3Allocation: "0xff" 901 part-2: 902 l3Allocation: 50% 903 `, 904 }, 905 // Testcase 906 TC{ 907 name: "L3 mix rel and abs allocation in partition #2 (fail)", 908 fs: "resctrl.full", 909 configErrRe: "error resolving L3 allocation for cache id 0: mixing relative and absolute allocations between partitions not supported", 910 config: ` 911 partitions: 912 part-1: 913 l3Allocation: 50% 914 part-2: 915 l3Allocation: "0xff" 916 `, 917 }, 918 // Testcase 919 TC{ 920 name: "L3 mix rel and abs allocation in classes", 921 fs: "resctrl.nomb", 922 config: ` 923 partitions: 924 part-1: 925 l3Allocation: 100% 926 classes: 927 class-1: 928 l3Allocation: 929 all: 100% 930 1: 50% 931 class-2: 932 l3Allocation: 933 all: 50% 934 1: "0x7" 935 2: "1-2" 936 `, 937 schemata: map[string]Schemata{ 938 "class-1": Schemata{ 939 l3: "0=fffff;1=3ff;2=fffff;3=fffff", 940 }, 941 "class-2": Schemata{ 942 l3: "0=3ff;1=7;2=6;3=3ff", 943 }, 944 "system/default": Schemata{ 945 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 946 }, 947 }, 948 }, 949 // Testcase 950 TC{ 951 name: "L3 partial allocation", 952 fs: "resctrl.nomb", 953 config: ` 954 partitions: 955 part-1: 956 l3Allocation: 957 all: "21%" 958 1: "42%" 959 2: "63%" 960 3: "89%" 961 classes: 962 class-1: 963 part-2: 964 l3Allocation: 965 all: "29%" 966 1: "8%" 967 2: "19%" 968 3: "11%" 969 classes: 970 class-2: 971 `, 972 schemata: map[string]Schemata{ 973 "class-1": Schemata{ 974 l3: "0=f;1=ff;2=1fff;3=3ffff", 975 }, 976 "class-2": Schemata{ 977 l3: "0=3f0;1=300;2=e000;3=c0000", 978 }, 979 "system/default": Schemata{ 980 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 981 }, 982 }, 983 }, 984 // Testcase 985 TC{ 986 name: "L3 partition non-contiguous bitmask (fail)", 987 fs: "resctrl.nomb", 988 configErrRe: `failed to parse L3 allocation request for partition "part-1": invalid cache bitmask "0x2f": more than one continuous block of ones`, 989 config: ` 990 partitions: 991 part-1: 992 l3Allocation: 993 all: "100%" 994 1: "0x2f" 995 `, 996 }, 997 // Testcase 998 TC{ 999 name: "L3 overlapping partitions (fail)", 1000 fs: "resctrl.nomb", 1001 configErrRe: `overlapping L3 partition allocation requests for cache id 2`, 1002 config: ` 1003 partitions: 1004 part-1: 1005 l3Allocation: "0xff" 1006 part-2: 1007 l3Allocation: 1008 all: "0xff00" 1009 2: "0xff80" 1010 `, 1011 }, 1012 // Testcase 1013 TC{ 1014 name: "L3 nan percentage in partition (fail)", 1015 fs: "resctrl.nomb", 1016 configErrRe: `failed to parse L3 allocation request for partition "part-1": strconv.ParseUint: parsing "1f": invalid syntax`, 1017 config: ` 1018 partitions: 1019 part-1: 1020 l3Allocation: "1f%" 1021 `, 1022 }, 1023 // Testcase 1024 TC{ 1025 name: "L3 percentage range in partition (fail)", 1026 fs: "resctrl.nomb", 1027 configErrRe: `invalid configuration: percentage ranges in partition allocation not supported`, 1028 config: ` 1029 partitions: 1030 part-1: 1031 l3Allocation: "50-100%" 1032 `, 1033 }, 1034 // Testcase 1035 TC{ 1036 name: "L3 missing for one partition (fail)", 1037 fs: "resctrl.full", 1038 configErrRe: `invalid configuration: some partitions \(part-2\) missing L3 "unified" allocation request`, 1039 config: ` 1040 partitions: 1041 part-1: 1042 l3Allocation: "50%" 1043 mbAllocation: ["100%"] 1044 part-2: 1045 mbAllocation: ["100%"] 1046 `, 1047 }, 1048 // Testcase 1049 TC{ 1050 name: "L3 percentage over 100 in partition (fail)", 1051 fs: "resctrl.nomb", 1052 configErrRe: `failed to parse L3 allocation request for partition "part-1": invalid percentage value "101%"`, 1053 config: ` 1054 partitions: 1055 part-1: 1056 l3Allocation: "101%" 1057 `, 1058 }, 1059 // Testcase 1060 TC{ 1061 name: "L3 missing cdp (fail)", 1062 fs: "resctrl.nomb", 1063 configErrRe: `some partitions \(part-2\) missing L3 "code" allocation request for cache id [0-3]`, 1064 config: ` 1065 partitions: 1066 part-1: 1067 l3Allocation: 1068 all: 1069 unified: "50%" 1070 code: "40%" 1071 data: "60%" 1072 part-2: 1073 l3Allocation: "50%" 1074 `, 1075 }, 1076 // Testcase 1077 TC{ 1078 name: "L3 total percentage over 100 (fail)", 1079 fs: "resctrl.nomb", 1080 configErrRe: `accumulated L3 "data" partition allocation requests for cache id [0-3] exceeds 100%`, 1081 config: ` 1082 partitions: 1083 part-1: 1084 l3Allocation: 1085 all: 1086 unified: "50%" 1087 code: "40%" 1088 data: "60%" 1089 part-2: 1090 l3Allocation: 1091 all: 1092 unified: "50%" 1093 code: "40%" 1094 data: "60%" 1095 `, 1096 }, 1097 // Testcase 1098 TC{ 1099 name: "L3 class allocation does not fit partition (fail)", 1100 fs: "resctrl.nomb", 1101 configErrRe: `bitmask 0x1ff00 \(0x1ff << 8\) does not fit basemask 0xff00`, 1102 config: ` 1103 partitions: 1104 part-1: 1105 l3Allocation: "0xff00" 1106 classes: 1107 class-1: 1108 l3Allocation: "0x1ff" 1109 `, 1110 }, 1111 // Testcase 1112 TC{ 1113 name: "L3 min cbm bits is respected", 1114 fs: "resctrl.nomb", 1115 config: ` 1116 partitions: 1117 part-1: 1118 l3Allocation: "100%" 1119 classes: 1120 class-1: 1121 l3Allocation: 1122 all: "1%" 1123 1-2: "99-100%" 1124 `, 1125 schemata: map[string]Schemata{ 1126 "class-1": Schemata{ 1127 l3: "0=3;1=c0000;2=c0000;3=3", 1128 }, 1129 "system/default": Schemata{ 1130 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 1131 }, 1132 }, 1133 }, 1134 // Testcase 1135 TC{ 1136 name: "L3 too few bits (fail)", 1137 fs: "resctrl.nomb", 1138 configErrRe: `bitmask 0x1ff00 \(0x1ff << 8\) does not fit basemask 0xff00`, 1139 config: ` 1140 partitions: 1141 part-1: 1142 l3Allocation: "0xff00" 1143 classes: 1144 class-1: 1145 l3Allocation: "0x1ff" 1146 `, 1147 }, 1148 // Testcase 1149 TC{ 1150 name: "L3 invalid percentage range in class (fail)", 1151 fs: "resctrl.nomb", 1152 configErrRe: `invalid configuration: failed to resolve L3 allocation for class "class-1": invalid percentage range`, 1153 config: ` 1154 partitions: 1155 part-1: 1156 l3Allocation: "100%" 1157 classes: 1158 class-1: 1159 l3Allocation: "0-101%" 1160 `, 1161 }, 1162 // Testcase 1163 TC{ 1164 name: "L3 missing from partition (fail)", 1165 fs: "resctrl.nomb", 1166 configErrRe: `L3 allocation missing from partition "part-1"`, 1167 config: ` 1168 partitions: 1169 part-1: 1170 classes: 1171 class-1: 1172 l3Allocation: "100%" 1173 `, 1174 }, 1175 // Testcase 1176 TC{ 1177 name: "MB allocation under minimum", 1178 fs: "resctrl.nol3", 1179 config: ` 1180 partitions: 1181 part-1: 1182 mbAllocation: ["1%"] 1183 classes: 1184 class-1: 1185 mbAllocation: ["100%"] 1186 `, 1187 schemata: map[string]Schemata{ 1188 "class-1": Schemata{ 1189 mb: "0=10;1=10;2=10;3=10", 1190 }, 1191 "system/default": Schemata{ 1192 mb: "0=100;1=100;2=100;3=100", 1193 }, 1194 }, 1195 }, 1196 // Testcase 1197 TC{ 1198 name: "L2, partial allocation", 1199 fs: "resctrl.l2", 1200 config: ` 1201 partitions: 1202 part-1: 1203 l2Allocation: 1204 all: 30% 1205 1: 75% 1206 classes: 1207 class-1: 1208 part-2: 1209 l2Allocation: 1210 0: 30% 1211 1: 1212 unified: 20% 1213 classes: 1214 class-2: 1215 part-3: 1216 l2Allocation: 1217 0: 40% 1218 1: 5% 1219 classes: 1220 system/default: 1221 `, 1222 schemata: map[string]Schemata{ 1223 "class-1": Schemata{ 1224 l2: "0=3;1=3f", 1225 }, 1226 "class-2": Schemata{ 1227 l2: "0=c;1=40", 1228 }, 1229 "system/default": Schemata{ 1230 l2: "0=f0;1=80", 1231 }, 1232 }, 1233 }, 1234 // Testcase 1235 TC{ 1236 name: "L2 CDP", 1237 fs: "resctrl.l2cdp", 1238 config: ` 1239 partitions: 1240 part-1: 1241 l2Allocation: 1242 all: 42% 1243 2: 1244 unified: 30% 1245 code: 20% 1246 data: 50% 1247 3: 1248 unified: 30% 1249 code: 40% 1250 data: 50% 1251 l3Allocation: 30% 1252 classes: 1253 class-1: 1254 part-2: 1255 l2Allocation: 1256 all: 43% 1257 2: 1258 unified: 70% 1259 code: 40% 1260 data: 30% 1261 3: 1262 unified: 30% 1263 code: 60% 1264 data: 50% 1265 l3Allocation: 50% 1266 classes: 1267 class-2: 1268 l2Allocation: 1269 all: 80% 1270 2: 1271 unified: 80% 1272 code: 60% 1273 data: 90% 1274 system/default: 1275 l3Allocation: 60% 1276 1277 `, 1278 schemata: map[string]Schemata{ 1279 "class-1": Schemata{ 1280 l2code: "0=ff;1=ff;2=f;3=ff", 1281 l2data: "0=ff;1=ff;2=3ff;3=3ff", 1282 l3: "0=7", 1283 }, 1284 "class-2": Schemata{ 1285 l2code: "0=ff00;1=ff00;2=1f0;3=3ff00", 1286 l2data: "0=ff00;1=ff00;2=fc00;3=3fc00", 1287 l3: "0=1f8", 1288 }, 1289 "system/default": Schemata{ 1290 l2code: "0=1ff00;1=1ff00;2=ff0;3=fff00", 1291 l2data: "0=1ff00;1=1ff00;2=fc00;3=ffc00", 1292 l3: "0=78", 1293 }, 1294 }, 1295 }, 1296 // Testcase 1297 TC{ 1298 name: "L2 optional", 1299 fs: "resctrl.nomb", 1300 config: ` 1301 options: 1302 l2: 1303 optional: true 1304 partitions: 1305 part-1: 1306 l2Allocation: 50% 1307 l3Allocation: 50% 1308 classes: 1309 class-1: 1310 l2Allocation: 20% 1311 `, 1312 schemata: map[string]Schemata{ 1313 "class-1": Schemata{ 1314 l3: "0=3ff;1=3ff;2=3ff;3=3ff", 1315 }, 1316 "system/default": Schemata{ 1317 l3: "0=fffff;1=fffff;2=fffff;3=fffff", 1318 }, 1319 }, 1320 }, 1321 // Testcase 1322 TC{ 1323 name: "MB nan percentage value in partition (fail)", 1324 fs: "resctrl.nol3", 1325 configErrRe: `failed to resolve MB allocation for partition "part-1": strconv.ParseUint: parsing "xyz"`, 1326 config: ` 1327 partitions: 1328 part-1: 1329 mbAllocation: ["xyz%"] 1330 `, 1331 }, 1332 // Testcase 1333 TC{ 1334 name: "MB invalid percentage value in class (fail)", 1335 fs: "resctrl.nol3", 1336 configErrRe: `failed to resolve MB allocation for class "class-1":.*invalid syntax`, 1337 config: ` 1338 partitions: 1339 part-1: 1340 mbAllocation: ["100%"] 1341 classes: 1342 class-1: 1343 mbAllocation: ["1a%"] 1344 `, 1345 }, 1346 // Testcase 1347 TC{ 1348 name: "MB missing percentage value (fail)", 1349 fs: "resctrl.nol3", 1350 configErrRe: `missing '%' value from mbSchema`, 1351 config: ` 1352 partitions: 1353 part-1: 1354 mbAllocation: ["100MBps"] 1355 `, 1356 }, 1357 // Testcase 1358 TC{ 1359 name: "MB missing from partition (fail)", 1360 fs: "resctrl.nol3", 1361 configErrRe: `MB allocation missing from partition "part-1"`, 1362 config: ` 1363 partitions: 1364 part-1: 1365 classes: 1366 class-1: 1367 mbAllocation: ["100%"] 1368 `, 1369 }, 1370 // Testcase 1371 TC{ 1372 name: "MB MBps", 1373 fs: "resctrl.nol3.mbps", 1374 fsMountOpts: "mba_MBps", 1375 config: ` 1376 partitions: 1377 part-1: 1378 mbAllocation: ["50%", "1000MBps"] 1379 classes: 1380 class-1: 1381 mbAllocation: ["100%", "1500MBps"] 1382 part-2: 1383 mbAllocation: 1384 all: ["1000MBps"] 1385 # Unsupported values should just be ignored 1386 0,1: [50, "1GBps", "500MBps"] 1387 classes: 1388 class-2: 1389 mbAllocation: ["750MBps"] 1390 `, 1391 schemata: map[string]Schemata{ 1392 "class-1": Schemata{ 1393 mb: "0=1000;1=1000;2=1000;3=1000", 1394 }, 1395 "class-2": Schemata{ 1396 mb: "0=500;1=500;2=750;3=750", 1397 }, 1398 "system/default": Schemata{ 1399 mb: "0=4294967295;1=4294967295;2=4294967295;3=4294967295", 1400 }, 1401 }, 1402 }, 1403 // Testcase 1404 TC{ 1405 name: "MB nan MBps value (fail)", 1406 fs: "resctrl.nol3.mbps", 1407 fsMountOpts: "mba_MBps", 1408 configErrRe: `failed to resolve MB allocation for partition "part-1":.* invalid syntax`, 1409 config: ` 1410 partitions: 1411 part-1: 1412 mbAllocation: ["0xffMBps"] 1413 `, 1414 }, 1415 // Testcase 1416 TC{ 1417 name: "MB missing MBps value (fail)", 1418 fs: "resctrl.nol3.mbps", 1419 fsMountOpts: "mba_MBps", 1420 configErrRe: `missing 'MBps' value from mbSchema`, 1421 config: ` 1422 partitions: 1423 part-1: 1424 mbAllocation: ["100%"] 1425 `, 1426 }, 1427 } 1428 1429 verifySchemata := func(tc *TC) { 1430 for n, s := range tc.schemata { 1431 expected := "" 1432 if s.l2 != "" { 1433 expected += "L2:" + s.l2 + "\n" 1434 } 1435 if s.l2code != "" { 1436 expected += "L2CODE:" + s.l2code + "\n" 1437 } 1438 if s.l2data != "" { 1439 expected += "L2DATA:" + s.l2data + "\n" 1440 } 1441 if s.l3 != "" { 1442 expected += "L3:" + s.l3 + "\n" 1443 } 1444 if s.l3code != "" { 1445 expected += "L3CODE:" + s.l3code + "\n" 1446 } 1447 if s.l3data != "" { 1448 expected += "L3DATA:" + s.l3data + "\n" 1449 } 1450 if s.mb != "" { 1451 expected += "MB:" + s.mb + "\n" 1452 } 1453 if c, ok := rdt.classes[n]; !ok { 1454 t.Fatalf("verifySchemata: class %q does not exists in %v", n, rdt.classes) 1455 } else { 1456 verifyTextFile(t, c.path("schemata"), expected) 1457 } 1458 } 1459 1460 if len(tc.schemata) != len(rdt.classes) { 1461 var a, b []string 1462 for n := range tc.schemata { 1463 a = append(a, n) 1464 } 1465 for n := range rdt.classes { 1466 b = append(b, n) 1467 } 1468 t.Fatalf("unexpected set of classes: expected %v, got %v", a, b) 1469 } 1470 } 1471 1472 // Set group remove function so that mock groups can be removed 1473 groupRemoveFunc = os.RemoveAll 1474 1475 for _, tc := range tcs { 1476 t.Logf("Running test case %q", tc.name) 1477 1478 mockFs, err := newMockResctrlFs(t, tc.fs, tc.fsMountOpts) 1479 if err != nil { 1480 t.Fatalf("failed to set up mock resctrl fs: %v", err) 1481 } 1482 defer mockFs.delete() 1483 1484 conf := parseTestConfig(t, tc.config) 1485 confDataOld, err := yaml.Marshal(conf) 1486 if err != nil { 1487 t.Fatalf("marshalling config failed: %v", err) 1488 } 1489 1490 if err := Initialize(mockGroupPrefix); err != nil { 1491 t.Fatalf("resctrl initialization failed: %v", err) 1492 } 1493 1494 err = SetConfig(conf, false) 1495 if tc.configErrRe != "" { 1496 if err == nil { 1497 t.Fatalf("resctrl configuration succeeded unexpectedly") 1498 } else { 1499 m, e := regexp.MatchString(tc.configErrRe, err.Error()) 1500 if e != nil { 1501 t.Fatalf("error in regexp matching: %v", e) 1502 } 1503 if !m { 1504 t.Fatalf("unexpected error message:\n %q\n does NOT match regexp\n %q", err.Error(), tc.configErrRe) 1505 } 1506 } 1507 } else { 1508 if err != nil { 1509 t.Fatalf("resctrl configuration failed: %v", err) 1510 } 1511 verifySchemata(&tc) 1512 } 1513 1514 if confDataNew, err := yaml.Marshal(conf); err != nil { 1515 t.Fatalf("marshalling config failed: %v", err) 1516 } else if !cmp.Equal(confDataNew, confDataOld) { 1517 t.Fatalf("SetConfig altered config data:\n%s\nVS.\n%s", confDataOld, confDataNew) 1518 } 1519 } 1520 } 1521 1522 func TestBitMap(t *testing.T) { 1523 // Test ListStr() 1524 testSet := map[bitmask]string{ 1525 0x0: "", 1526 0x1: "0", 1527 0x2: "1", 1528 0xf: "0-3", 1529 0x555: "0,2,4,6,8,10", 1530 0xaaa: "1,3,5,7,9,11", 1531 0x1d1a: "1,3-4,8,10-12", 1532 0xffffffffffffffff: "0-63", 1533 } 1534 for i, s := range testSet { 1535 // Test conversion to string 1536 listStr := i.listStr() 1537 if listStr != s { 1538 t.Errorf("from %#x expected %q, got %q", i, s, listStr) 1539 } 1540 1541 // Test conversion from string 1542 b, err := listStrToBitmask(s) 1543 if err != nil { 1544 t.Errorf("unexpected err when converting %q: %v", s, err) 1545 } 1546 if b != i { 1547 t.Errorf("from %q expected %#x, got %#x", s, i, b) 1548 } 1549 } 1550 1551 // Negative tests for ListStrToBitmask 1552 negTestSet := []string{ 1553 ",", 1554 "-", 1555 "1,", 1556 ",12", 1557 "-4", 1558 "0-", 1559 "13-13", 1560 "14-13", 1561 "a-2", 1562 "b", 1563 "3-c", 1564 "64", 1565 "1,2,,3", 1566 "1,2,3-", 1567 } 1568 for _, s := range negTestSet { 1569 b, err := listStrToBitmask(s) 1570 if err == nil { 1571 t.Errorf("expected err but got %#x when converting %q", b, s) 1572 } 1573 } 1574 1575 // Test MarshalJSON 1576 if s, err := bitmask(10).MarshalJSON(); err != nil { 1577 } else if string(s) != `"0xa"` { 1578 t.Errorf(`expected "0xa" but returned %s`, s) 1579 } 1580 } 1581 1582 func TestListStrToArray(t *testing.T) { 1583 testSet := map[string][]int{ 1584 "": {}, 1585 "0": {0}, 1586 "1": {1}, 1587 "0-3": {0, 1, 2, 3}, 1588 "4,2,0,6,10,8": {0, 2, 4, 6, 8, 10}, 1589 "1,3,5,7,9,11": {1, 3, 5, 7, 9, 11}, 1590 "1,3-4,10-12,8": {1, 3, 4, 8, 10, 11, 12}, 1591 } 1592 for s, expected := range testSet { 1593 // Test conversion from string to list of integers 1594 a, err := listStrToArray(s) 1595 if err != nil { 1596 t.Errorf("unexpected error when converting %q: %v", s, err) 1597 } 1598 if !cmp.Equal(a, expected) { 1599 t.Errorf("from %q expected %v, got %v", s, expected, a) 1600 } 1601 } 1602 1603 // Negative test cases 1604 negTestSet := []string{ 1605 ",", 1606 "-", 1607 "1,", 1608 "256", 1609 "256-257", 1610 "0-256", 1611 ",12", 1612 "-4", 1613 "0-", 1614 "13-13", 1615 "14-13", 1616 "a-2", 1617 "b", 1618 "3-c", 1619 "1,2,,3", 1620 "1,2,3-", 1621 } 1622 for _, s := range negTestSet { 1623 a, err := listStrToArray(s) 1624 if err == nil { 1625 t.Errorf("expected err but got %v when converting %q", a, s) 1626 } 1627 } 1628 } 1629 1630 // TestCacheAllocation tests the types implementing cacheAllocation interface 1631 func TestCacheAllocation(t *testing.T) { 1632 // Need to setup resctrl and initialize because pct allocations need 1633 // the "info" structure 1634 mockFs, err := newMockResctrlFs(t, "resctrl.nomb", "") 1635 if err != nil { 1636 t.Fatalf("failed to set up mock resctrl fs: %v", err) 1637 } 1638 defer mockFs.delete() 1639 1640 if err := Initialize(mockGroupPrefix); err != nil { 1641 t.Fatalf("resctrl initialization failed: %v", err) 1642 } 1643 1644 // Test absolute allocation 1645 abs := catAbsoluteAllocation(0x7) 1646 if res, err := abs.Overlay(0xf00, 1); err != nil { 1647 t.Errorf("unexpected error when overlaying catAbsoluteAllocation: %v", err) 1648 } else if res != 0x700 { 1649 t.Errorf("expected 0x700 but got %#x when overlaying catAbsoluteAllocation", res) 1650 } 1651 1652 if _, err := abs.Overlay(0, 1); err == nil { 1653 t.Errorf("unexpected success when overlaying catAbsoluteAllocation with empty basemask") 1654 } 1655 1656 if _, err := abs.Overlay(0x30, 1); err == nil { 1657 t.Errorf("unexpected success when overlaying too wide catAbsoluteAllocation") 1658 } 1659 1660 if _, err := abs.Overlay(0xf0f, 1); err == nil { 1661 t.Errorf("unexpected success when overlaying catAbsoluteAllocation with non-contiguous basemask") 1662 } 1663 1664 if _, err := catAbsoluteAllocation(0x1).Overlay(0x10, 2); err == nil { 1665 t.Errorf("unexpected success when overlaying catAbsoluteAllocation with too small basemask") 1666 } 1667 1668 // Test percentage allocation 1669 if res, err := (catPctRangeAllocation{lowPct: 0, highPct: 100}).Overlay(0xff00, 4); err != nil { 1670 t.Errorf("unexpected error when overlaying catPctAllocation: %v", err) 1671 } else if res != 0xff00 { 1672 t.Errorf("expected 0xff00 but got %#x when overlaying catPctAllocation", res) 1673 } 1674 if res, err := (catPctRangeAllocation{lowPct: 99, highPct: 100}).Overlay(0xff00, 4); err != nil { 1675 t.Errorf("unexpected error when overlaying catPctAllocation: %v", err) 1676 } else if res != 0xf000 { 1677 t.Errorf("expected 0xf000 but got %#x when overlaying catPctAllocation", res) 1678 } 1679 if res, err := (catPctRangeAllocation{lowPct: 0, highPct: 1}).Overlay(0xff00, 4); err != nil { 1680 t.Errorf("unexpected error when overlaying catPctAllocation: %v", err) 1681 } else if res != 0xf00 { 1682 t.Errorf("expected 0xf00 but got %#x when overlaying catPctAllocation", res) 1683 } 1684 if res, err := (catPctRangeAllocation{lowPct: 20, highPct: 30}).Overlay(0x3ff00, 4); err != nil { 1685 t.Errorf("unexpected error when overlaying catPctAllocation: %v", err) 1686 } else if res != 0xf00 { 1687 t.Errorf("expected 0xf00 but got %#x when overlaying catPctAllocation", res) 1688 } 1689 if res, err := (catPctRangeAllocation{lowPct: 30, highPct: 60}).Overlay(0xf00, 4); err != nil { 1690 t.Errorf("unexpected error when overlaying catPctAllocation: %v", err) 1691 } else if res != 0xf00 { 1692 t.Errorf("expected 0xf00 but got %#x when overlaying catPctAllocation", res) 1693 } 1694 if _, err := (catPctRangeAllocation{lowPct: 20, highPct: 10}).Overlay(0xff00, 4); err == nil { 1695 t.Errorf("unexpected success when overlaying catPctAllocation of invalid percentage range") 1696 } 1697 if _, err := (catPctRangeAllocation{lowPct: 0, highPct: 100}).Overlay(0, 4); err == nil { 1698 t.Errorf("unexpected success when overlaying catPctAllocation of invalid percentage range") 1699 } 1700 } 1701 1702 func TestCacheProportion(t *testing.T) { 1703 // Test percentage 1704 if a, err := CacheProportion("10%").parse(2); err != nil { 1705 t.Errorf("unexpected error when parsing cache allocation: %v", err) 1706 } else if a != catPctAllocation(10) { 1707 t.Errorf("expected 10%% but got %d%%", a) 1708 } 1709 if _, err := CacheProportion("1a%").parse(2); err == nil { 1710 t.Errorf("unexpected success when parsing percentage cache allocation") 1711 } 1712 if _, err := CacheProportion("101%").parse(2); err == nil { 1713 t.Errorf("unexpected success when parsing percentage cache allocation") 1714 } 1715 1716 // Test percentage ranges 1717 if a, err := CacheProportion("10-20%").parse(2); err != nil { 1718 t.Errorf("unexpected error when parsing cache allocation: %v", err) 1719 } else if a != (catPctRangeAllocation{lowPct: 10, highPct: 20}) { 1720 t.Errorf("expected {10 20} but got %v", a) 1721 } 1722 if _, err := CacheProportion("a-100%").parse(2); err == nil { 1723 t.Errorf("unexpected success when parsing percentage range cache allocation") 1724 } 1725 if _, err := CacheProportion("0-1f%").parse(2); err == nil { 1726 t.Errorf("unexpected success when parsing percentage range cache allocation") 1727 } 1728 if _, err := CacheProportion("20-10%").parse(2); err == nil { 1729 t.Errorf("unexpected success when parsing percentage range cache allocation") 1730 } 1731 if _, err := CacheProportion("20-101%").parse(2); err == nil { 1732 t.Errorf("unexpected success when parsing percentage range cache allocation") 1733 } 1734 1735 // Test bitmask 1736 if a, err := CacheProportion("0xf0").parse(2); err != nil { 1737 t.Errorf("unexpected error when parsing cache allocation: %v", err) 1738 } else if a != catAbsoluteAllocation(0xf0) { 1739 t.Errorf("expected 0xf0 but got %#x", a) 1740 } 1741 if _, err := CacheProportion("0x40").parse(2); err == nil { 1742 t.Errorf("unexpected success when parsing bitmask cache allocation") 1743 } 1744 if _, err := CacheProportion("0x11").parse(2); err == nil { 1745 t.Errorf("unexpected success when parsing bitmask cache allocation") 1746 } 1747 if _, err := CacheProportion("0xg").parse(2); err == nil { 1748 t.Errorf("unexpected success when parsing bitmask cache allocation") 1749 } 1750 1751 // Test bit numbers 1752 if a, err := CacheProportion("3,4,5-7,8").parse(2); err != nil { 1753 t.Errorf("unexpected error when parsing cache allocation: %v", err) 1754 } else if a != catAbsoluteAllocation(0x1f8) { 1755 t.Errorf("expected 0x1f8 but got %#x", a) 1756 } 1757 if _, err := CacheProportion("3,5").parse(2); err == nil { 1758 t.Errorf("unexpected success when parsing bitmask cache allocation") 1759 } 1760 if _, err := CacheProportion("1").parse(2); err == nil { 1761 t.Errorf("unexpected success when parsing bitmask cache allocation") 1762 } 1763 if _, err := CacheProportion("3-x").parse(2); err == nil { 1764 t.Errorf("unexpected success when parsing bitmask cache allocation") 1765 } 1766 } 1767 1768 func TestIsQualifiedClassName(t *testing.T) { 1769 tcs := map[string]bool{ 1770 "foo": true, 1771 RootClassName: true, 1772 RootClassAlias: true, 1773 ".": false, 1774 "..": false, 1775 "foo/bar": false, 1776 "foo\n": false, 1777 } 1778 1779 for name, expected := range tcs { 1780 if r := IsQualifiedClassName(name); r != expected { 1781 t.Errorf("IsQualifiedClassName(%q) returned %v (expected %v)", name, r, expected) 1782 } 1783 } 1784 }