github.com/google/cadvisor@v0.49.1/resctrl/utils_test.go (about) 1 //go:build linux 2 // +build linux 3 4 // Copyright 2021 Google Inc. All Rights Reserved. 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 // Utilities tests. 19 // 20 // Mocked environment: 21 // - "container" first container with {1, 2, 3} processes. 22 // - "another" second container with {5, 6} processes. 23 package resctrl 24 25 import ( 26 "fmt" 27 "os" 28 "path/filepath" 29 "testing" 30 31 "github.com/opencontainers/runc/libcontainer/cgroups" 32 "github.com/opencontainers/runc/libcontainer/intelrdt" 33 34 "github.com/stretchr/testify/assert" 35 ) 36 37 func init() { 38 // All the test cases in this file uses "fake" cgroups (not the real 39 // cgroupfs). This setting relaxes filesystem type check in cgroups 40 // package so it can work with fake cgroups. 41 cgroups.TestMode = true 42 } 43 44 func mockAllGetContainerPids() ([]string, error) { 45 return []string{"1", "2", "3", "5", "6"}, nil 46 } 47 48 func mockGetContainerPids() ([]string, error) { 49 return []string{"1", "2", "3"}, nil 50 } 51 52 func mockAnotherGetContainerPids() ([]string, error) { 53 return []string{"5", "6"}, nil 54 } 55 56 func touch(path string) error { 57 file, err := os.OpenFile(path, os.O_CREATE, os.ModePerm) 58 if err != nil { 59 return err 60 } 61 return file.Close() 62 } 63 64 func touchDir(path string) error { 65 err := os.MkdirAll(path, os.ModePerm) 66 if err != nil { 67 return err 68 } 69 return nil 70 } 71 72 func fillPids(path string, pids []int) error { 73 f, err := os.OpenFile(path, os.O_WRONLY, os.ModePerm) 74 if err != nil { 75 return err 76 } 77 defer f.Close() 78 for _, pid := range pids { 79 _, err := fmt.Fprintln(f, pid) 80 if err != nil { 81 return err 82 } 83 } 84 return nil 85 } 86 87 func mockResctrl() string { 88 path, _ := os.MkdirTemp("", "resctrl") 89 90 var files = []struct { 91 path string 92 touch func(string) error 93 }{ 94 // Mock root files. 95 { 96 filepath.Join(path, cpusFileName), 97 touch, 98 }, 99 { 100 filepath.Join(path, cpusListFileName), 101 touch, 102 }, 103 { 104 filepath.Join(path, infoDirName), 105 touchDir, 106 }, 107 { 108 filepath.Join(path, monDataDirName), 109 touchDir, 110 }, 111 { 112 filepath.Join(path, monGroupsDirName), 113 touchDir, 114 }, 115 { 116 filepath.Join(path, schemataFileName), 117 touch, 118 }, 119 { 120 filepath.Join(path, modeFileName), 121 touch, 122 }, 123 { 124 filepath.Join(path, sizeFileName), 125 touch, 126 }, 127 { 128 filepath.Join(path, tasksFileName), 129 touch, 130 }, 131 // Create custom CLOSID "m1". 132 { 133 filepath.Join(path, "m1"), 134 touchDir, 135 }, 136 { 137 filepath.Join(path, "m1", cpusFileName), 138 touch, 139 }, 140 { 141 filepath.Join(path, "m1", cpusListFileName), 142 touch, 143 }, 144 { 145 filepath.Join(path, "m1", monDataDirName), 146 touchDir, 147 }, 148 { 149 filepath.Join(path, "m1", monGroupsDirName), 150 touchDir, 151 }, 152 { 153 filepath.Join(path, "m1", schemataFileName), 154 touch, 155 }, 156 { 157 filepath.Join(path, "m1", tasksFileName), 158 touch, 159 }, 160 { 161 filepath.Join(path, "m1", monGroupsDirName, "test"), 162 touchDir, 163 }, 164 { 165 filepath.Join(path, "m1", monGroupsDirName, "test", tasksFileName), 166 touch, 167 }, 168 } 169 for _, file := range files { 170 err := file.touch(file.path) 171 if err != nil { 172 return "" 173 } 174 } 175 176 // Mock root group task file. 177 err := fillPids(filepath.Join(path, tasksFileName), []int{1, 2, 3, 4}) 178 if err != nil { 179 return "" 180 } 181 182 // Mock custom CLOSID "m1" task file. 183 err = fillPids(filepath.Join(path, "m1", tasksFileName), []int{5, 6, 7, 8, 9, 10}) 184 if err != nil { 185 return "" 186 } 187 // Mock custom mon group "test" task file. 188 err = fillPids(filepath.Join(path, "m1", monGroupsDirName, "test", tasksFileName), []int{7, 8}) 189 if err != nil { 190 return "" 191 } 192 193 return path 194 } 195 196 func mockResctrlMonData(path string) { 197 198 _ = touchDir(filepath.Join(path, monDataDirName, "mon_L3_00")) 199 _ = touchDir(filepath.Join(path, monDataDirName, "mon_L3_01")) 200 201 var files = []struct { 202 path string 203 value string 204 }{ 205 { 206 filepath.Join(path, monDataDirName, "mon_L3_00", llcOccupancyFileName), 207 "1111", 208 }, 209 { 210 filepath.Join(path, monDataDirName, "mon_L3_00", mbmLocalBytesFileName), 211 "2222", 212 }, 213 { 214 filepath.Join(path, monDataDirName, "mon_L3_00", mbmTotalBytesFileName), 215 "3333", 216 }, 217 { 218 filepath.Join(path, monDataDirName, "mon_L3_01", llcOccupancyFileName), 219 "3333", 220 }, 221 { 222 filepath.Join(path, monDataDirName, "mon_L3_01", mbmLocalBytesFileName), 223 "1111", 224 }, 225 { 226 filepath.Join(path, monDataDirName, "mon_L3_01", mbmTotalBytesFileName), 227 "3333", 228 }, 229 } 230 231 for _, file := range files { 232 _ = touch(file.path) 233 _ = os.WriteFile(file.path, []byte(file.value), os.ModePerm) 234 } 235 } 236 237 func mockContainersPids() string { 238 path, _ := os.MkdirTemp("", "cgroup") 239 // container 240 _ = touchDir(filepath.Join(path, "container")) 241 _ = touch(filepath.Join(path, "container", cgroups.CgroupProcesses)) 242 err := fillPids(filepath.Join(path, "container", cgroups.CgroupProcesses), []int{1, 2, 3}) 243 if err != nil { 244 return "" 245 } 246 // another 247 _ = touchDir(filepath.Join(path, "another")) 248 _ = touch(filepath.Join(path, "another", cgroups.CgroupProcesses)) 249 err = fillPids(filepath.Join(path, "another", cgroups.CgroupProcesses), []int{5}) 250 if err != nil { 251 return "" 252 } 253 254 return path 255 } 256 257 func mockProcFs() string { 258 path, _ := os.MkdirTemp("", "proc") 259 260 var files = []struct { 261 path string 262 touch func(string) error 263 }{ 264 // container 265 { 266 filepath.Join(path, "1", processTask, "1"), 267 touchDir, 268 }, 269 { 270 filepath.Join(path, "2", processTask, "2"), 271 touchDir, 272 }, 273 { 274 filepath.Join(path, "3", processTask, "3"), 275 touchDir, 276 }, 277 { 278 filepath.Join(path, "4", processTask, "4"), 279 touchDir, 280 }, 281 // another 282 { 283 filepath.Join(path, "5", processTask, "5"), 284 touchDir, 285 }, 286 { 287 filepath.Join(path, "6", processTask, "6"), 288 touchDir, 289 }, 290 } 291 292 for _, file := range files { 293 _ = file.touch(file.path) 294 } 295 296 return path 297 } 298 299 func checkError(t *testing.T, err error, expected string) { 300 if expected != "" { 301 assert.EqualError(t, err, expected) 302 } else { 303 assert.NoError(t, err) 304 } 305 } 306 307 func TestPrepareMonitoringGroup(t *testing.T) { 308 rootResctrl = mockResctrl() 309 defer os.RemoveAll(rootResctrl) 310 311 pidsPath = mockContainersPids() 312 defer os.RemoveAll(pidsPath) 313 314 processPath = mockProcFs() 315 defer os.RemoveAll(processPath) 316 317 var testCases = []struct { 318 container string 319 getContainerPids func() ([]string, error) 320 expected string 321 err string 322 }{ 323 { 324 "container", 325 mockGetContainerPids, 326 filepath.Join(rootResctrl, monGroupsDirName, "cadvisor-container"), 327 "", 328 }, 329 { 330 "another", 331 mockAnotherGetContainerPids, 332 filepath.Join(rootResctrl, "m1", monGroupsDirName, "cadvisor-another"), 333 "", 334 }, 335 { 336 "/", 337 mockAllGetContainerPids, 338 rootResctrl, 339 "", 340 }, 341 } 342 343 for _, test := range testCases { 344 actual, err := prepareMonitoringGroup(test.container, test.getContainerPids, true) 345 assert.Equal(t, test.expected, actual) 346 checkError(t, err, test.err) 347 } 348 } 349 350 func TestGetPids(t *testing.T) { 351 pidsPath = mockContainersPids() 352 defer os.RemoveAll(pidsPath) 353 354 var testCases = []struct { 355 container string 356 expected []int 357 err string 358 }{ 359 { 360 "", 361 nil, 362 noContainerNameError, 363 }, 364 { 365 "container", 366 []int{1, 2, 3}, 367 "", 368 }, 369 { 370 "no_container", 371 nil, 372 fmt.Sprintf("couldn't obtain pids for \"no_container\" container: lstat %v: no such file or directory", filepath.Join(pidsPath, "no_container")), 373 }, 374 } 375 376 for _, test := range testCases { 377 actual, err := getPids(test.container) 378 assert.Equal(t, test.expected, actual) 379 checkError(t, err, test.err) 380 } 381 } 382 383 func TestGetAllProcessThreads(t *testing.T) { 384 mockProcFs := func() string { 385 path, _ := os.MkdirTemp("", "proc") 386 387 var files = []struct { 388 path string 389 touch func(string) error 390 }{ 391 // correct 392 { 393 filepath.Join(path, "4215", processTask, "4215"), 394 touchDir, 395 }, 396 { 397 filepath.Join(path, "4215", processTask, "4216"), 398 touchDir, 399 }, 400 { 401 filepath.Join(path, "4215", processTask, "4217"), 402 touchDir, 403 }, 404 { 405 filepath.Join(path, "4215", processTask, "4218"), 406 touchDir, 407 }, 408 // invalid 409 { 410 filepath.Join(path, "301", processTask, "301"), 411 touchDir, 412 }, 413 { 414 filepath.Join(path, "301", processTask, "incorrect"), 415 touchDir, 416 }, 417 } 418 419 for _, file := range files { 420 _ = file.touch(file.path) 421 } 422 423 return path 424 } 425 426 mockedProcFs := mockProcFs() 427 defer os.RemoveAll(mockedProcFs) 428 429 var testCases = []struct { 430 path string 431 expected []int 432 err string 433 }{ 434 { 435 filepath.Join(mockedProcFs, "4215", processTask), 436 []int{4215, 4216, 4217, 4218}, 437 "", 438 }, 439 { 440 filepath.Join(mockedProcFs, "301", processTask), 441 nil, 442 "couldn't parse \"incorrect\" dir: strconv.Atoi: parsing \"incorrect\": invalid syntax", 443 }, 444 } 445 446 for _, test := range testCases { 447 actual, err := getAllProcessThreads(test.path) 448 assert.Equal(t, test.expected, actual) 449 checkError(t, err, test.err) 450 } 451 } 452 453 func TestFindGroup(t *testing.T) { 454 rootResctrl = mockResctrl() 455 defer os.RemoveAll(rootResctrl) 456 457 var testCases = []struct { 458 path string 459 pids []string 460 includeGroup bool 461 exclusive bool 462 expected string 463 err string 464 }{ 465 { 466 rootResctrl, 467 []string{"1", "2", "3", "4"}, 468 true, 469 false, 470 rootResctrl, 471 "", 472 }, 473 { 474 rootResctrl, 475 []string{}, 476 true, 477 false, 478 "", 479 "there are no pids passed", 480 }, 481 { 482 rootResctrl, 483 []string{"5", "6"}, 484 true, 485 false, 486 filepath.Join(rootResctrl, "m1"), 487 "", 488 }, 489 { 490 rootResctrl, 491 []string{"11", "12"}, 492 true, 493 false, 494 "", 495 "", 496 }, 497 { 498 filepath.Join(rootResctrl, "m1", monGroupsDirName), 499 []string{"5", "6"}, 500 false, 501 true, 502 "", 503 "", 504 }, 505 { 506 filepath.Join(rootResctrl, "m1", monGroupsDirName), 507 []string{"7", "8"}, 508 false, 509 true, 510 filepath.Join(rootResctrl, "m1", monGroupsDirName, "test"), 511 "", 512 }, 513 { 514 filepath.Join(rootResctrl, "m1", monGroupsDirName), 515 []string{"7"}, 516 false, 517 true, 518 "", 519 "group should have container pids only", 520 }, 521 } 522 for _, test := range testCases { 523 actual, err := findGroup(test.path, test.pids, test.includeGroup, test.exclusive) 524 assert.Equal(t, test.expected, actual) 525 checkError(t, err, test.err) 526 } 527 } 528 529 func TestArePIDsInGroup(t *testing.T) { 530 rootResctrl = mockResctrl() 531 defer os.RemoveAll(rootResctrl) 532 533 var testCases = []struct { 534 expected bool 535 err string 536 path string 537 pids []string 538 exclusive bool 539 }{ 540 { 541 true, 542 "", 543 rootResctrl, 544 []string{"1", "2"}, 545 false, 546 }, 547 { 548 false, 549 "there should be all pids in group", 550 rootResctrl, 551 []string{"4", "5"}, 552 false, 553 }, 554 { 555 false, 556 "", 557 filepath.Join(rootResctrl, "m1"), 558 []string{"1"}, 559 false, 560 }, 561 { 562 false, 563 fmt.Sprintf("couldn't read tasks file from %q path: open %s: no such file or directory", filepath.Join(rootResctrl, monitoringGroupDir, tasksFileName), filepath.Join(rootResctrl, monitoringGroupDir, tasksFileName)), 564 filepath.Join(rootResctrl, monitoringGroupDir), 565 []string{"1", "2"}, 566 false, 567 }, 568 { 569 false, 570 fmt.Sprintf("couldn't obtain pids from %q path: %v", rootResctrl, noPidsPassedError), 571 rootResctrl, 572 nil, 573 false, 574 }, 575 } 576 577 for _, test := range testCases { 578 actual, err := arePIDsInGroup(test.path, test.pids, test.exclusive) 579 assert.Equal(t, test.expected, actual) 580 checkError(t, err, test.err) 581 } 582 } 583 584 func TestGetStats(t *testing.T) { 585 rootResctrl = mockResctrl() 586 defer os.RemoveAll(rootResctrl) 587 588 pidsPath = mockContainersPids() 589 defer os.RemoveAll(pidsPath) 590 591 processPath = mockProcFs() 592 defer os.RemoveAll(processPath) 593 594 enabledCMT, enabledMBM = true, true 595 596 var testCases = []struct { 597 container string 598 expected intelrdt.Stats 599 err string 600 }{ 601 { 602 "container", 603 intelrdt.Stats{ 604 MBMStats: &[]intelrdt.MBMNumaNodeStats{ 605 { 606 MBMTotalBytes: 3333, 607 MBMLocalBytes: 2222, 608 }, 609 { 610 MBMTotalBytes: 3333, 611 MBMLocalBytes: 1111, 612 }, 613 }, 614 CMTStats: &[]intelrdt.CMTNumaNodeStats{ 615 { 616 LLCOccupancy: 1111, 617 }, 618 { 619 LLCOccupancy: 3333, 620 }, 621 }, 622 }, 623 "", 624 }, 625 { 626 "another", 627 intelrdt.Stats{ 628 MBMStats: &[]intelrdt.MBMNumaNodeStats{ 629 { 630 MBMTotalBytes: 3333, 631 MBMLocalBytes: 2222, 632 }, 633 { 634 MBMTotalBytes: 3333, 635 MBMLocalBytes: 1111, 636 }, 637 }, 638 CMTStats: &[]intelrdt.CMTNumaNodeStats{ 639 { 640 LLCOccupancy: 1111, 641 }, 642 { 643 LLCOccupancy: 3333, 644 }, 645 }, 646 }, 647 "", 648 }, 649 { 650 "/", 651 intelrdt.Stats{ 652 MBMStats: &[]intelrdt.MBMNumaNodeStats{ 653 { 654 MBMTotalBytes: 3333, 655 MBMLocalBytes: 2222, 656 }, 657 { 658 MBMTotalBytes: 3333, 659 MBMLocalBytes: 1111, 660 }, 661 }, 662 CMTStats: &[]intelrdt.CMTNumaNodeStats{ 663 { 664 LLCOccupancy: 1111, 665 }, 666 { 667 LLCOccupancy: 3333, 668 }, 669 }, 670 }, 671 "", 672 }, 673 } 674 675 for _, test := range testCases { 676 containerPath, _ := prepareMonitoringGroup(test.container, mockGetContainerPids, true) 677 mockResctrlMonData(containerPath) 678 actual, err := getIntelRDTStatsFrom(containerPath, "") 679 checkError(t, err, test.err) 680 assert.Equal(t, test.expected.CMTStats, actual.CMTStats) 681 assert.Equal(t, test.expected.MBMStats, actual.MBMStats) 682 } 683 } 684 685 func TestReadTasksFile(t *testing.T) { 686 var testCases = []struct { 687 tasksFile string 688 expected map[string]struct{} 689 err string 690 }{ 691 {"testing/tasks_two", 692 map[string]struct{}{ 693 "12": {}, 694 "77": {}, 695 }, 696 "", 697 }, 698 {"testing/tasks_one", 699 map[string]struct{}{ 700 "2": {}, 701 }, 702 "", 703 }, 704 {"testing/tasks_empty", 705 map[string]struct{}{}, 706 "", 707 }, 708 } 709 710 for _, test := range testCases { 711 actual, err := readTasksFile(test.tasksFile) 712 assert.Equal(t, test.expected, actual) 713 checkError(t, err, test.err) 714 } 715 }