github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/syscalls/linux/cgroup.cc (about) 1 // Copyright 2021 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // All tests in this file rely on being about to mount and unmount cgroupfs, 16 // which isn't expected to work, or be safe on a general linux system. 17 18 #include <limits.h> 19 #include <sys/mount.h> 20 #include <unistd.h> 21 22 #include "gtest/gtest.h" 23 #include "absl/container/flat_hash_map.h" 24 #include "absl/container/flat_hash_set.h" 25 #include "absl/strings/str_split.h" 26 #include "test/util/capability_util.h" 27 #include "test/util/cgroup_util.h" 28 #include "test/util/cleanup.h" 29 #include "test/util/mount_util.h" 30 #include "test/util/temp_path.h" 31 #include "test/util/test_util.h" 32 #include "test/util/thread_util.h" 33 34 namespace gvisor { 35 namespace testing { 36 namespace { 37 38 using ::testing::_; 39 using ::testing::Contains; 40 using ::testing::Ge; 41 using ::testing::Gt; 42 using ::testing::Key; 43 using ::testing::Not; 44 45 std::vector<std::string> known_controllers = { 46 "cpu", "cpuset", "cpuacct", "job", "memory", 47 }; 48 49 bool CgroupsAvailable() { 50 return IsRunningOnGvisor() && !IsRunningWithVFS1() && 51 TEST_CHECK_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)); 52 } 53 54 TEST(Cgroup, MountSucceeds) { 55 SKIP_IF(!CgroupsAvailable()); 56 57 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 58 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); 59 EXPECT_NO_ERRNO(c.ContainsCallingProcess()); 60 } 61 62 TEST(Cgroup, SeparateMounts) { 63 SKIP_IF(!CgroupsAvailable()); 64 65 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 66 67 for (const auto& ctl : known_controllers) { 68 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(ctl)); 69 EXPECT_NO_ERRNO(c.ContainsCallingProcess()); 70 } 71 } 72 73 TEST(Cgroup, AllControllersImplicit) { 74 SKIP_IF(!CgroupsAvailable()); 75 76 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 77 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); 78 79 absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries = 80 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 81 for (const auto& ctl : known_controllers) { 82 EXPECT_TRUE(cgroups_entries.contains(ctl)) 83 << absl::StreamFormat("ctl=%s", ctl); 84 } 85 EXPECT_EQ(cgroups_entries.size(), known_controllers.size()); 86 } 87 88 TEST(Cgroup, AllControllersExplicit) { 89 SKIP_IF(!CgroupsAvailable()); 90 91 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 92 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("all")); 93 94 absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries = 95 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 96 for (const auto& ctl : known_controllers) { 97 EXPECT_TRUE(cgroups_entries.contains(ctl)) 98 << absl::StreamFormat("ctl=%s", ctl); 99 } 100 EXPECT_EQ(cgroups_entries.size(), known_controllers.size()); 101 } 102 103 TEST(Cgroup, ProcsAndTasks) { 104 SKIP_IF(!CgroupsAvailable()); 105 106 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 107 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); 108 absl::flat_hash_set<pid_t> pids = ASSERT_NO_ERRNO_AND_VALUE(c.Procs()); 109 absl::flat_hash_set<pid_t> tids = ASSERT_NO_ERRNO_AND_VALUE(c.Tasks()); 110 111 EXPECT_GE(tids.size(), pids.size()) << "Found more processes than threads"; 112 113 // Pids should be a strict subset of tids. 114 for (auto it = pids.begin(); it != pids.end(); ++it) { 115 EXPECT_TRUE(tids.contains(*it)) 116 << absl::StreamFormat("Have pid %d, but no such tid", *it); 117 } 118 } 119 120 TEST(Cgroup, ControllersMustBeInUniqueHierarchy) { 121 SKIP_IF(!CgroupsAvailable()); 122 123 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 124 // Hierarchy #1: all controllers. 125 Cgroup all = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); 126 // Hierarchy #2: memory. 127 // 128 // This should conflict since memory is already in hierarchy #1, and the two 129 // hierarchies have different sets of controllers, so this mount can't be a 130 // view into hierarchy #1. 131 EXPECT_THAT(m.MountCgroupfs("memory"), PosixErrorIs(EBUSY, _)) 132 << "Memory controller mounted on two hierarchies"; 133 EXPECT_THAT(m.MountCgroupfs("cpu"), PosixErrorIs(EBUSY, _)) 134 << "CPU controller mounted on two hierarchies"; 135 } 136 137 TEST(Cgroup, UnmountFreesControllers) { 138 SKIP_IF(!CgroupsAvailable()); 139 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 140 Cgroup all = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); 141 // All controllers are now attached to all's hierarchy. Attempting new mount 142 // with any individual controller should fail. 143 EXPECT_THAT(m.MountCgroupfs("memory"), PosixErrorIs(EBUSY, _)) 144 << "Memory controller mounted on two hierarchies"; 145 146 // Unmount the "all" hierarchy. This should enable any controller to be 147 // mounted on a new hierarchy again. 148 ASSERT_NO_ERRNO(m.Unmount(all)); 149 EXPECT_NO_ERRNO(m.MountCgroupfs("memory")); 150 EXPECT_NO_ERRNO(m.MountCgroupfs("cpu")); 151 } 152 153 TEST(Cgroup, OnlyContainsControllerSpecificFiles) { 154 SKIP_IF(!CgroupsAvailable()); 155 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 156 Cgroup mem = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory")); 157 EXPECT_THAT(Exists(mem.Relpath("memory.usage_in_bytes")), 158 IsPosixErrorOkAndHolds(true)); 159 // CPU files shouldn't exist in memory cgroups. 160 EXPECT_THAT(Exists(mem.Relpath("cpu.cfs_period_us")), 161 IsPosixErrorOkAndHolds(false)); 162 EXPECT_THAT(Exists(mem.Relpath("cpu.cfs_quota_us")), 163 IsPosixErrorOkAndHolds(false)); 164 EXPECT_THAT(Exists(mem.Relpath("cpu.shares")), IsPosixErrorOkAndHolds(false)); 165 166 Cgroup cpu = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu")); 167 EXPECT_THAT(Exists(cpu.Relpath("cpu.cfs_period_us")), 168 IsPosixErrorOkAndHolds(true)); 169 EXPECT_THAT(Exists(cpu.Relpath("cpu.cfs_quota_us")), 170 IsPosixErrorOkAndHolds(true)); 171 EXPECT_THAT(Exists(cpu.Relpath("cpu.shares")), IsPosixErrorOkAndHolds(true)); 172 // Memory files shouldn't exist in cpu cgroups. 173 EXPECT_THAT(Exists(cpu.Relpath("memory.usage_in_bytes")), 174 IsPosixErrorOkAndHolds(false)); 175 } 176 177 TEST(Cgroup, InvalidController) { 178 SKIP_IF(!CgroupsAvailable()); 179 180 TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 181 std::string mopts = "this-controller-is-invalid"; 182 EXPECT_THAT( 183 mount("none", mountpoint.path().c_str(), "cgroup", 0, mopts.c_str()), 184 SyscallFailsWithErrno(EINVAL)); 185 } 186 187 TEST(Cgroup, MoptAllMustBeExclusive) { 188 SKIP_IF(!CgroupsAvailable()); 189 190 TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 191 std::string mopts = "all,cpu"; 192 EXPECT_THAT( 193 mount("none", mountpoint.path().c_str(), "cgroup", 0, mopts.c_str()), 194 SyscallFailsWithErrno(EINVAL)); 195 } 196 197 TEST(Cgroup, MountRace) { 198 SKIP_IF(!CgroupsAvailable()); 199 200 TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 201 202 const DisableSave ds; // Too many syscalls. 203 204 auto mount_thread = [&mountpoint]() { 205 for (int i = 0; i < 100; ++i) { 206 mount("none", mountpoint.path().c_str(), "cgroup", 0, 0); 207 } 208 }; 209 std::list<ScopedThread> threads; 210 for (int i = 0; i < 10; ++i) { 211 threads.emplace_back(mount_thread); 212 } 213 for (auto& t : threads) { 214 t.Join(); 215 } 216 217 auto cleanup = Cleanup([&mountpoint] { 218 // We need 1 umount call per successful mount. If some of the mount calls 219 // were unsuccessful, their corresponding umount will silently fail. 220 for (int i = 0; i < (10 * 100) + 1; ++i) { 221 umount(mountpoint.path().c_str()); 222 } 223 }); 224 225 Cgroup c = Cgroup(mountpoint.path()); 226 // c should be a valid cgroup. 227 EXPECT_NO_ERRNO(c.ContainsCallingProcess()); 228 } 229 230 TEST(Cgroup, MountUnmountRace) { 231 SKIP_IF(!CgroupsAvailable()); 232 233 TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 234 235 const DisableSave ds; // Too many syscalls. 236 237 auto mount_thread = [&mountpoint]() { 238 for (int i = 0; i < 100; ++i) { 239 mount("none", mountpoint.path().c_str(), "cgroup", 0, 0); 240 } 241 }; 242 auto unmount_thread = [&mountpoint]() { 243 for (int i = 0; i < 100; ++i) { 244 umount(mountpoint.path().c_str()); 245 } 246 }; 247 std::list<ScopedThread> threads; 248 for (int i = 0; i < 10; ++i) { 249 threads.emplace_back(mount_thread); 250 } 251 for (int i = 0; i < 10; ++i) { 252 threads.emplace_back(unmount_thread); 253 } 254 for (auto& t : threads) { 255 t.Join(); 256 } 257 258 // We don't know how many mount refs are remaining, since the count depends on 259 // the ordering of mount and umount calls. Keep calling unmount until it 260 // returns an error. 261 while (umount(mountpoint.path().c_str()) == 0) { 262 } 263 } 264 265 TEST(Cgroup, UnmountRepeated) { 266 SKIP_IF(!CgroupsAvailable()); 267 268 const DisableSave ds; // Too many syscalls. 269 270 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 271 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); 272 273 // First unmount should succeed. 274 EXPECT_THAT(umount(c.Path().c_str()), SyscallSucceeds()); 275 276 // We just manually unmounted, so release managed resources. 277 m.release(c); 278 279 EXPECT_THAT(umount(c.Path().c_str()), SyscallFailsWithErrno(EINVAL)); 280 } 281 282 TEST(MemoryCgroup, MemoryUsageInBytes) { 283 SKIP_IF(!CgroupsAvailable()); 284 285 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 286 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory")); 287 EXPECT_THAT(c.ReadIntegerControlFile("memory.usage_in_bytes"), 288 IsPosixErrorOkAndHolds(Gt(0))); 289 } 290 291 TEST(CPUCgroup, ControlFilesHaveDefaultValues) { 292 SKIP_IF(!CgroupsAvailable()); 293 294 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 295 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu")); 296 EXPECT_THAT(c.ReadIntegerControlFile("cpu.cfs_quota_us"), 297 IsPosixErrorOkAndHolds(-1)); 298 EXPECT_THAT(c.ReadIntegerControlFile("cpu.cfs_period_us"), 299 IsPosixErrorOkAndHolds(100000)); 300 EXPECT_THAT(c.ReadIntegerControlFile("cpu.shares"), 301 IsPosixErrorOkAndHolds(1024)); 302 } 303 304 TEST(CPUAcctCgroup, CPUAcctUsage) { 305 SKIP_IF(!CgroupsAvailable()); 306 307 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 308 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct")); 309 310 const int64_t usage = 311 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage")); 312 const int64_t usage_user = 313 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage_user")); 314 const int64_t usage_sys = 315 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage_sys")); 316 317 EXPECT_GE(usage, 0); 318 EXPECT_GE(usage_user, 0); 319 EXPECT_GE(usage_sys, 0); 320 321 EXPECT_GE(usage_user + usage_sys, usage); 322 } 323 324 TEST(CPUAcctCgroup, CPUAcctStat) { 325 SKIP_IF(!CgroupsAvailable()); 326 327 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 328 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct")); 329 330 std::string stat = 331 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuacct.stat")); 332 333 // We're expecting the contents of "cpuacct.stat" to look similar to this: 334 // 335 // user 377986 336 // system 220662 337 338 std::vector<absl::string_view> lines = 339 absl::StrSplit(stat, '\n', absl::SkipEmpty()); 340 ASSERT_EQ(lines.size(), 2); 341 342 std::vector<absl::string_view> user_tokens = 343 StrSplit(lines[0], absl::ByChar(' ')); 344 EXPECT_EQ(user_tokens[0], "user"); 345 EXPECT_THAT(Atoi<int64_t>(user_tokens[1]), IsPosixErrorOkAndHolds(Ge(0))); 346 347 std::vector<absl::string_view> sys_tokens = 348 StrSplit(lines[1], absl::ByChar(' ')); 349 EXPECT_EQ(sys_tokens[0], "system"); 350 EXPECT_THAT(Atoi<int64_t>(sys_tokens[1]), IsPosixErrorOkAndHolds(Ge(0))); 351 } 352 353 // WriteAndVerifyControlValue attempts to write val to a cgroup file at path, 354 // and verify the value by reading it afterwards. 355 PosixError WriteAndVerifyControlValue(const Cgroup& c, std::string_view path, 356 int64_t val) { 357 RETURN_IF_ERRNO(c.WriteIntegerControlFile(path, val)); 358 ASSIGN_OR_RETURN_ERRNO(int64_t newval, c.ReadIntegerControlFile(path)); 359 if (newval != val) { 360 return PosixError( 361 EINVAL, 362 absl::StrFormat( 363 "Unexpected value for control file '%s': expected %d, got %d", path, 364 val, newval)); 365 } 366 return NoError(); 367 } 368 369 TEST(JobCgroup, ReadWriteRead) { 370 SKIP_IF(!CgroupsAvailable()); 371 372 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 373 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("job")); 374 375 EXPECT_THAT(c.ReadIntegerControlFile("job.id"), IsPosixErrorOkAndHolds(0)); 376 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", 1234)); 377 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", -1)); 378 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MIN)); 379 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MAX)); 380 } 381 382 TEST(ProcCgroups, Empty) { 383 SKIP_IF(!CgroupsAvailable()); 384 385 absl::flat_hash_map<std::string, CgroupsEntry> entries = 386 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 387 // No cgroups mounted yet, we should have no entries. 388 EXPECT_TRUE(entries.empty()); 389 } 390 391 TEST(ProcCgroups, ProcCgroupsEntries) { 392 SKIP_IF(!CgroupsAvailable()); 393 394 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 395 396 Cgroup mem = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory")); 397 absl::flat_hash_map<std::string, CgroupsEntry> entries = 398 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 399 EXPECT_EQ(entries.size(), 1); 400 ASSERT_TRUE(entries.contains("memory")); 401 CgroupsEntry mem_e = entries["memory"]; 402 EXPECT_EQ(mem_e.subsys_name, "memory"); 403 EXPECT_GE(mem_e.hierarchy, 1); 404 // Expect a single root cgroup. 405 EXPECT_EQ(mem_e.num_cgroups, 1); 406 // Cgroups are currently always enabled when mounted. 407 EXPECT_TRUE(mem_e.enabled); 408 409 // Add a second cgroup, and check for new entry. 410 411 Cgroup cpu = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu")); 412 entries = ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 413 EXPECT_EQ(entries.size(), 2); 414 EXPECT_TRUE(entries.contains("memory")); // Still have memory entry. 415 ASSERT_TRUE(entries.contains("cpu")); 416 CgroupsEntry cpu_e = entries["cpu"]; 417 EXPECT_EQ(cpu_e.subsys_name, "cpu"); 418 EXPECT_GE(cpu_e.hierarchy, 1); 419 EXPECT_EQ(cpu_e.num_cgroups, 1); 420 EXPECT_TRUE(cpu_e.enabled); 421 422 // Separate hierarchies, since controllers were mounted separately. 423 EXPECT_NE(mem_e.hierarchy, cpu_e.hierarchy); 424 } 425 426 TEST(ProcCgroups, UnmountRemovesEntries) { 427 SKIP_IF(!CgroupsAvailable()); 428 429 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 430 Cgroup cg = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu,memory")); 431 absl::flat_hash_map<std::string, CgroupsEntry> entries = 432 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 433 EXPECT_EQ(entries.size(), 2); 434 435 ASSERT_NO_ERRNO(m.Unmount(cg)); 436 437 entries = ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 438 EXPECT_TRUE(entries.empty()); 439 } 440 441 TEST(ProcPIDCgroup, Empty) { 442 SKIP_IF(!CgroupsAvailable()); 443 444 absl::flat_hash_map<std::string, PIDCgroupEntry> entries = 445 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 446 EXPECT_TRUE(entries.empty()); 447 } 448 449 TEST(ProcPIDCgroup, Entries) { 450 SKIP_IF(!CgroupsAvailable()); 451 452 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 453 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory")); 454 455 absl::flat_hash_map<std::string, PIDCgroupEntry> entries = 456 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 457 EXPECT_EQ(entries.size(), 1); 458 PIDCgroupEntry mem_e = entries["memory"]; 459 EXPECT_GE(mem_e.hierarchy, 1); 460 EXPECT_EQ(mem_e.controllers, "memory"); 461 EXPECT_EQ(mem_e.path, "/"); 462 463 Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu")); 464 entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 465 EXPECT_EQ(entries.size(), 2); 466 EXPECT_TRUE(entries.contains("memory")); // Still have memory entry. 467 PIDCgroupEntry cpu_e = entries["cpu"]; 468 EXPECT_GE(cpu_e.hierarchy, 1); 469 EXPECT_EQ(cpu_e.controllers, "cpu"); 470 EXPECT_EQ(cpu_e.path, "/"); 471 472 // Separate hierarchies, since controllers were mounted separately. 473 EXPECT_NE(mem_e.hierarchy, cpu_e.hierarchy); 474 } 475 476 TEST(ProcPIDCgroup, UnmountRemovesEntries) { 477 SKIP_IF(!CgroupsAvailable()); 478 479 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 480 Cgroup all = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); 481 482 absl::flat_hash_map<std::string, PIDCgroupEntry> entries = 483 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 484 EXPECT_GT(entries.size(), 0); 485 486 ASSERT_NO_ERRNO(m.Unmount(all)); 487 488 entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 489 EXPECT_TRUE(entries.empty()); 490 } 491 492 TEST(ProcCgroup, PIDCgroupMatchesCgroups) { 493 SKIP_IF(!CgroupsAvailable()); 494 495 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 496 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory")); 497 Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu")); 498 499 absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries = 500 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 501 absl::flat_hash_map<std::string, PIDCgroupEntry> pid_entries = 502 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 503 504 CgroupsEntry cgroup_mem = cgroups_entries["memory"]; 505 PIDCgroupEntry pid_mem = pid_entries["memory"]; 506 507 EXPECT_EQ(cgroup_mem.hierarchy, pid_mem.hierarchy); 508 509 CgroupsEntry cgroup_cpu = cgroups_entries["cpu"]; 510 PIDCgroupEntry pid_cpu = pid_entries["cpu"]; 511 512 EXPECT_EQ(cgroup_cpu.hierarchy, pid_cpu.hierarchy); 513 EXPECT_NE(cgroup_mem.hierarchy, cgroup_cpu.hierarchy); 514 EXPECT_NE(pid_mem.hierarchy, pid_cpu.hierarchy); 515 } 516 517 TEST(ProcCgroup, MultiControllerHierarchy) { 518 SKIP_IF(!CgroupsAvailable()); 519 520 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 521 Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory,cpu")); 522 523 absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries = 524 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 525 526 CgroupsEntry mem_e = cgroups_entries["memory"]; 527 CgroupsEntry cpu_e = cgroups_entries["cpu"]; 528 529 // Both controllers should have the same hierarchy ID. 530 EXPECT_EQ(mem_e.hierarchy, cpu_e.hierarchy); 531 532 absl::flat_hash_map<std::string, PIDCgroupEntry> pid_entries = 533 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 534 535 // Expecting an entry listing both controllers, that matches the previous 536 // hierarchy ID. Note that the controllers are listed in alphabetical order. 537 PIDCgroupEntry pid_e = pid_entries["cpu,memory"]; 538 EXPECT_EQ(pid_e.hierarchy, mem_e.hierarchy); 539 } 540 541 TEST(ProcCgroup, ProcfsReportsCgroupfsMountOptions) { 542 SKIP_IF(!CgroupsAvailable()); 543 544 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 545 // Hierarchy with multiple controllers. 546 Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory,cpu")); 547 // Hierarchy with a single controller. 548 Cgroup c2 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct")); 549 550 const std::vector<ProcMountsEntry> mounts = 551 ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); 552 553 for (auto const& e : mounts) { 554 if (e.mount_point == c1.Path()) { 555 auto mopts = ParseMountOptions(e.mount_opts); 556 EXPECT_THAT(mopts, Contains(Key("memory"))); 557 EXPECT_THAT(mopts, Contains(Key("cpu"))); 558 EXPECT_THAT(mopts, Not(Contains(Key("cpuacct")))); 559 } 560 561 if (e.mount_point == c2.Path()) { 562 auto mopts = ParseMountOptions(e.mount_opts); 563 EXPECT_THAT(mopts, Contains(Key("cpuacct"))); 564 EXPECT_THAT(mopts, Not(Contains(Key("cpu")))); 565 EXPECT_THAT(mopts, Not(Contains(Key("memory")))); 566 } 567 } 568 569 const std::vector<ProcMountInfoEntry> mountinfo = 570 ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); 571 572 for (auto const& e : mountinfo) { 573 if (e.mount_point == c1.Path()) { 574 auto mopts = ParseMountOptions(e.super_opts); 575 EXPECT_THAT(mopts, Contains(Key("memory"))); 576 EXPECT_THAT(mopts, Contains(Key("cpu"))); 577 EXPECT_THAT(mopts, Not(Contains(Key("cpuacct")))); 578 } 579 580 if (e.mount_point == c2.Path()) { 581 auto mopts = ParseMountOptions(e.super_opts); 582 EXPECT_THAT(mopts, Contains(Key("cpuacct"))); 583 EXPECT_THAT(mopts, Not(Contains(Key("cpu")))); 584 EXPECT_THAT(mopts, Not(Contains(Key("memory")))); 585 } 586 } 587 } 588 589 } // namespace 590 } // namespace testing 591 } // namespace gvisor