k8s.io/kubernetes@v1.29.3/pkg/kubelet/cm/topologymanager/numa_info_test.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 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 topologymanager 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 "testing" 24 25 cadvisorapi "github.com/google/cadvisor/info/v1" 26 "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" 27 ) 28 29 func TestNUMAInfo(t *testing.T) { 30 tcases := []struct { 31 name string 32 topology []cadvisorapi.Node 33 expectedNUMAInfo *NUMAInfo 34 expectedErr error 35 opts PolicyOptions 36 }{ 37 { 38 name: "positive test 1 node", 39 topology: []cadvisorapi.Node{ 40 { 41 Id: 0, 42 }, 43 }, 44 expectedNUMAInfo: &NUMAInfo{ 45 Nodes: []int{0}, 46 NUMADistances: NUMADistances{ 47 0: nil, 48 }, 49 }, 50 opts: PolicyOptions{}, 51 }, 52 { 53 name: "positive test 1 node, with PreferClosestNUMA", 54 topology: []cadvisorapi.Node{ 55 { 56 Id: 0, 57 Distances: []uint64{ 58 10, 59 11, 60 12, 61 12, 62 }, 63 }, 64 }, 65 expectedNUMAInfo: &NUMAInfo{ 66 Nodes: []int{0}, 67 NUMADistances: NUMADistances{ 68 0: { 69 10, 70 11, 71 12, 72 12, 73 }, 74 }, 75 }, 76 opts: PolicyOptions{ 77 PreferClosestNUMA: true, 78 }, 79 }, 80 { 81 name: "positive test 2 nodes", 82 topology: []cadvisorapi.Node{ 83 { 84 Id: 0, 85 }, 86 { 87 Id: 1, 88 }, 89 }, 90 expectedNUMAInfo: &NUMAInfo{ 91 Nodes: []int{0, 1}, 92 NUMADistances: NUMADistances{ 93 0: nil, 94 1: nil, 95 }, 96 }, 97 }, 98 { 99 name: "positive test 2 nodes, with PreferClosestNUMA", 100 topology: []cadvisorapi.Node{ 101 { 102 Id: 0, 103 Distances: []uint64{ 104 10, 105 11, 106 12, 107 12, 108 }, 109 }, 110 { 111 Id: 1, 112 Distances: []uint64{ 113 11, 114 10, 115 12, 116 12, 117 }, 118 }, 119 }, 120 expectedNUMAInfo: &NUMAInfo{ 121 Nodes: []int{0, 1}, 122 NUMADistances: NUMADistances{ 123 0: { 124 10, 125 11, 126 12, 127 12, 128 }, 129 1: { 130 11, 131 10, 132 12, 133 12, 134 }, 135 }, 136 }, 137 opts: PolicyOptions{ 138 PreferClosestNUMA: true, 139 }, 140 }, 141 { 142 name: "positive test 3 nodes", 143 topology: []cadvisorapi.Node{ 144 { 145 Id: 0, 146 }, 147 { 148 Id: 1, 149 }, 150 { 151 Id: 2, 152 }, 153 }, 154 expectedNUMAInfo: &NUMAInfo{ 155 Nodes: []int{0, 1, 2}, 156 NUMADistances: NUMADistances{ 157 0: nil, 158 1: nil, 159 2: nil, 160 }, 161 }, 162 }, 163 { 164 name: "positive test 3 nodes, with PreferClosestNUMA", 165 topology: []cadvisorapi.Node{ 166 { 167 Id: 0, 168 Distances: []uint64{ 169 10, 170 11, 171 12, 172 12, 173 }, 174 }, 175 { 176 Id: 1, 177 Distances: []uint64{ 178 11, 179 10, 180 12, 181 12, 182 }, 183 }, 184 { 185 Id: 2, 186 Distances: []uint64{ 187 12, 188 12, 189 10, 190 11, 191 }, 192 }, 193 }, 194 expectedNUMAInfo: &NUMAInfo{ 195 Nodes: []int{0, 1, 2}, 196 NUMADistances: NUMADistances{ 197 0: { 198 10, 199 11, 200 12, 201 12, 202 }, 203 1: { 204 11, 205 10, 206 12, 207 12, 208 }, 209 2: { 210 12, 211 12, 212 10, 213 11, 214 }, 215 }, 216 }, 217 opts: PolicyOptions{ 218 PreferClosestNUMA: true, 219 }, 220 }, 221 { 222 name: "positive test 4 nodes", 223 topology: []cadvisorapi.Node{ 224 { 225 Id: 0, 226 }, 227 { 228 Id: 1, 229 }, 230 { 231 Id: 2, 232 }, 233 { 234 Id: 3, 235 }, 236 }, 237 expectedNUMAInfo: &NUMAInfo{ 238 Nodes: []int{0, 1, 2, 3}, 239 NUMADistances: NUMADistances{ 240 0: nil, 241 1: nil, 242 2: nil, 243 3: nil, 244 }, 245 }, 246 }, 247 { 248 name: "positive test 4 nodes, with PreferClosestNUMA", 249 topology: []cadvisorapi.Node{ 250 { 251 Id: 0, 252 Distances: []uint64{ 253 10, 254 11, 255 12, 256 12, 257 }, 258 }, 259 { 260 Id: 1, 261 Distances: []uint64{ 262 11, 263 10, 264 12, 265 12, 266 }, 267 }, 268 { 269 Id: 2, 270 Distances: []uint64{ 271 12, 272 12, 273 10, 274 11, 275 }, 276 }, 277 { 278 Id: 3, 279 Distances: []uint64{ 280 12, 281 12, 282 11, 283 10, 284 }, 285 }, 286 }, 287 expectedNUMAInfo: &NUMAInfo{ 288 Nodes: []int{0, 1, 2, 3}, 289 NUMADistances: NUMADistances{ 290 0: { 291 10, 292 11, 293 12, 294 12, 295 }, 296 1: { 297 11, 298 10, 299 12, 300 12, 301 }, 302 2: { 303 12, 304 12, 305 10, 306 11, 307 }, 308 3: { 309 12, 310 12, 311 11, 312 10, 313 }, 314 }, 315 }, 316 opts: PolicyOptions{ 317 PreferClosestNUMA: true, 318 }, 319 }, 320 { 321 name: "negative test 1 node, no distance file with PreferClosestNUMA", 322 topology: []cadvisorapi.Node{ 323 { 324 Id: 9, 325 }, 326 }, 327 expectedNUMAInfo: nil, 328 expectedErr: fmt.Errorf("error getting NUMA distances from cadvisor"), 329 opts: PolicyOptions{ 330 PreferClosestNUMA: true, 331 }, 332 }, 333 { 334 name: "one node and its id is 1", 335 topology: []cadvisorapi.Node{ 336 { 337 Id: 1, 338 }, 339 }, 340 expectedNUMAInfo: &NUMAInfo{ 341 Nodes: []int{1}, 342 NUMADistances: NUMADistances{ 343 1: nil, 344 }, 345 }, 346 }, 347 { 348 name: "one node and its id is 1, with PreferClosestNUMA", 349 topology: []cadvisorapi.Node{ 350 { 351 Id: 1, 352 Distances: []uint64{ 353 11, 354 10, 355 12, 356 12, 357 }, 358 }, 359 }, 360 expectedNUMAInfo: &NUMAInfo{ 361 Nodes: []int{1}, 362 NUMADistances: NUMADistances{ 363 1: { 364 11, 365 10, 366 12, 367 12, 368 }, 369 }, 370 }, 371 opts: PolicyOptions{ 372 PreferClosestNUMA: true, 373 }, 374 }, 375 { 376 name: "two nodes not sequential", 377 topology: []cadvisorapi.Node{ 378 { 379 Id: 0, 380 Distances: []uint64{ 381 10, 382 11, 383 12, 384 12, 385 }, 386 }, 387 { 388 Id: 2, 389 Distances: []uint64{ 390 12, 391 12, 392 10, 393 11, 394 }, 395 }, 396 }, 397 expectedNUMAInfo: &NUMAInfo{ 398 Nodes: []int{0, 2}, 399 NUMADistances: NUMADistances{ 400 0: nil, 401 2: nil, 402 }, 403 }, 404 }, 405 { 406 name: "two nodes not sequential, with PreferClosestNUMA", 407 topology: []cadvisorapi.Node{ 408 { 409 Id: 0, 410 Distances: []uint64{ 411 10, 412 11, 413 12, 414 12, 415 }, 416 }, 417 { 418 Id: 2, 419 Distances: []uint64{ 420 12, 421 12, 422 10, 423 11, 424 }, 425 }, 426 }, 427 expectedNUMAInfo: &NUMAInfo{ 428 Nodes: []int{0, 2}, 429 NUMADistances: NUMADistances{ 430 0: { 431 10, 432 11, 433 12, 434 12, 435 }, 436 2: { 437 12, 438 12, 439 10, 440 11, 441 }, 442 }, 443 }, 444 opts: PolicyOptions{ 445 PreferClosestNUMA: true, 446 }, 447 }, 448 } 449 450 for _, tcase := range tcases { 451 topology, err := NewNUMAInfo(tcase.topology, tcase.opts) 452 if tcase.expectedErr == nil && err != nil { 453 t.Fatalf("Expected err to equal nil, not %v", err) 454 } else if tcase.expectedErr != nil && err == nil { 455 t.Fatalf("Expected err to equal %v, not nil", tcase.expectedErr) 456 } else if tcase.expectedErr != nil { 457 if !strings.Contains(err.Error(), tcase.expectedErr.Error()) { 458 t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tcase.expectedErr.Error()) 459 } 460 } 461 462 if !reflect.DeepEqual(topology, tcase.expectedNUMAInfo) { 463 t.Fatalf("Expected topology to equal %v, not %v", tcase.expectedNUMAInfo, topology) 464 } 465 466 } 467 } 468 469 func TestCalculateAvgDistanceFor(t *testing.T) { 470 tcases := []struct { 471 name string 472 bm []int 473 distance NUMADistances 474 expectedAvg float64 475 }{ 476 { 477 name: "1 NUMA node", 478 bm: []int{ 479 0, 480 }, 481 distance: NUMADistances{ 482 0: { 483 10, 484 }, 485 }, 486 expectedAvg: 10, 487 }, 488 { 489 name: "2 NUMA node, 1 set in bitmask", 490 bm: []int{ 491 0, 492 }, 493 distance: NUMADistances{ 494 0: { 495 10, 496 11, 497 }, 498 1: { 499 11, 500 10, 501 }, 502 }, 503 expectedAvg: 10, 504 }, 505 { 506 name: "2 NUMA node, 2 set in bitmask", 507 bm: []int{ 508 0, 509 1, 510 }, 511 distance: NUMADistances{ 512 0: { 513 10, 514 11, 515 }, 516 1: { 517 11, 518 10, 519 }, 520 }, 521 expectedAvg: 10.5, 522 }, 523 { 524 name: "4 NUMA node, 2 set in bitmask", 525 bm: []int{ 526 0, 527 2, 528 }, 529 distance: NUMADistances{ 530 0: { 531 10, 532 11, 533 12, 534 12, 535 }, 536 1: { 537 11, 538 10, 539 12, 540 12, 541 }, 542 2: { 543 12, 544 12, 545 10, 546 11, 547 }, 548 3: { 549 12, 550 12, 551 11, 552 10, 553 }, 554 }, 555 expectedAvg: 11, 556 }, 557 { 558 name: "4 NUMA node, 3 set in bitmask", 559 bm: []int{ 560 0, 561 2, 562 3, 563 }, 564 distance: NUMADistances{ 565 0: { 566 10, 567 11, 568 12, 569 12, 570 }, 571 1: { 572 11, 573 10, 574 12, 575 12, 576 }, 577 2: { 578 12, 579 12, 580 10, 581 11, 582 }, 583 3: { 584 12, 585 12, 586 11, 587 10, 588 }, 589 }, 590 expectedAvg: 11.11111111111111, 591 }, 592 { 593 name: "0 NUMA node, 0 set in bitmask", 594 bm: []int{}, 595 distance: NUMADistances{}, 596 expectedAvg: 0, 597 }, 598 } 599 600 for _, tcase := range tcases { 601 bm, err := bitmask.NewBitMask(tcase.bm...) 602 if err != nil { 603 t.Errorf("no error expected got %v", err) 604 } 605 606 numaInfo := NUMAInfo{ 607 Nodes: tcase.bm, 608 NUMADistances: tcase.distance, 609 } 610 611 result := numaInfo.NUMADistances.CalculateAverageFor(bm) 612 if result != tcase.expectedAvg { 613 t.Errorf("Expected result to equal %g, not %g", tcase.expectedAvg, result) 614 } 615 } 616 617 } 618 619 func TestClosest(t *testing.T) { 620 tcases := []struct { 621 description string 622 current bitmask.BitMask 623 candidate bitmask.BitMask 624 expected string 625 numaInfo *NUMAInfo 626 }{ 627 { 628 description: "current and candidate length is not the same, current narrower", 629 current: NewTestBitMask(0), 630 candidate: NewTestBitMask(0, 2), 631 expected: "current", 632 numaInfo: &NUMAInfo{}, 633 }, 634 { 635 description: "current and candidate length is the same, distance is the same, current more lower bits set", 636 current: NewTestBitMask(0, 1), 637 candidate: NewTestBitMask(0, 2), 638 expected: "current", 639 numaInfo: &NUMAInfo{ 640 NUMADistances: NUMADistances{ 641 0: {10, 10, 10}, 642 1: {10, 10, 10}, 643 2: {10, 10, 10}, 644 }, 645 }, 646 }, 647 { 648 description: "current and candidate length is the same, distance is the same, candidate more lower bits set", 649 current: NewTestBitMask(0, 3), 650 candidate: NewTestBitMask(0, 2), 651 expected: "candidate", 652 numaInfo: &NUMAInfo{ 653 NUMADistances: NUMADistances{ 654 0: {10, 10, 10, 10}, 655 1: {10, 10, 10, 10}, 656 2: {10, 10, 10, 10}, 657 3: {10, 10, 10, 10}, 658 }, 659 }, 660 }, 661 { 662 description: "current and candidate length is the same, candidate average distance is smaller", 663 current: NewTestBitMask(0, 3), 664 candidate: NewTestBitMask(0, 1), 665 expected: "candidate", 666 numaInfo: &NUMAInfo{ 667 NUMADistances: NUMADistances{ 668 0: {10, 11, 12, 12}, 669 1: {11, 10, 12, 12}, 670 2: {12, 12, 10, 11}, 671 3: {12, 12, 11, 10}, 672 }, 673 }, 674 }, 675 { 676 description: "current and candidate length is the same, current average distance is smaller", 677 current: NewTestBitMask(2, 3), 678 candidate: NewTestBitMask(0, 3), 679 expected: "current", 680 numaInfo: &NUMAInfo{ 681 NUMADistances: NUMADistances{ 682 0: {10, 11, 12, 12}, 683 1: {11, 10, 12, 12}, 684 2: {12, 12, 10, 11}, 685 3: {12, 12, 11, 10}, 686 }, 687 }, 688 }, 689 } 690 691 for _, tc := range tcases { 692 t.Run(tc.description, func(t *testing.T) { 693 694 result := tc.numaInfo.Closest(tc.candidate, tc.current) 695 if result != tc.current && result != tc.candidate { 696 t.Errorf("Expected result to be either 'current' or 'candidate' hint") 697 } 698 if tc.expected == "current" && result != tc.current { 699 t.Errorf("Expected result to be %v, got %v", tc.current, result) 700 } 701 if tc.expected == "candidate" && result != tc.candidate { 702 t.Errorf("Expected result to be %v, got %v", tc.candidate, result) 703 } 704 }) 705 } 706 }