gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/util/cgroup_util.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 #include "test/util/cgroup_util.h" 16 17 #include <sys/syscall.h> 18 #include <unistd.h> 19 20 #include "absl/strings/str_cat.h" 21 #include "absl/strings/str_split.h" 22 #include "test/util/fs_util.h" 23 #include "test/util/mount_util.h" 24 25 namespace gvisor { 26 namespace testing { 27 28 Cgroup::Cgroup(absl::string_view path, absl::string_view mountpoint) 29 : cgroup_path_(path), mountpoint_(mountpoint) { 30 id_ = ++Cgroup::next_id_; 31 std::cerr << absl::StreamFormat("[cg#%d] <= %s", id_, cgroup_path_) 32 << std::endl; 33 } 34 35 PosixError Cgroup::Delete() { return Rmdir(cgroup_path_); } 36 37 PosixErrorOr<Cgroup> Cgroup::CreateChild(absl::string_view name) const { 38 std::string path = JoinPath(Path(), name); 39 RETURN_IF_ERRNO(Mkdir(path)); 40 return Cgroup(path, mountpoint_); 41 } 42 43 PosixErrorOr<std::string> Cgroup::ReadControlFile( 44 absl::string_view name) const { 45 std::string buf; 46 RETURN_IF_ERRNO(GetContents(Relpath(name), &buf)); 47 48 const std::string alias_path = absl::StrFormat("[cg#%d]/%s", id_, name); 49 std::cerr << absl::StreamFormat("<contents of %s>", alias_path) << std::endl; 50 std::cerr << buf; 51 std::cerr << absl::StreamFormat("<end of %s>", alias_path) << std::endl; 52 53 return buf; 54 } 55 56 PosixErrorOr<int64_t> Cgroup::ReadIntegerControlFile( 57 absl::string_view name) const { 58 ASSIGN_OR_RETURN_ERRNO(const std::string buf, ReadControlFile(name)); 59 ASSIGN_OR_RETURN_ERRNO(const int64_t val, Atoi<int64_t>(buf)); 60 return val; 61 } 62 63 PosixError Cgroup::WriteControlFile(absl::string_view name, 64 const std::string& value) const { 65 ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(Relpath(name), O_WRONLY)); 66 RETURN_ERROR_IF_SYSCALL_FAIL(WriteFd(fd.get(), value.c_str(), value.size())); 67 const std::string alias_path = absl::StrFormat("[cg#%d]/%s", id_, name); 68 std::cerr << absl::StreamFormat("echo '%s' > %s", value, alias_path) 69 << std::endl; 70 return NoError(); 71 } 72 73 PosixError Cgroup::WriteIntegerControlFile(absl::string_view name, 74 int64_t value) const { 75 return WriteControlFile(name, absl::StrCat(value)); 76 } 77 78 PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::Procs() const { 79 ASSIGN_OR_RETURN_ERRNO(std::string buf, ReadControlFile("cgroup.procs")); 80 return ParsePIDList(buf); 81 } 82 83 PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::Tasks() const { 84 ASSIGN_OR_RETURN_ERRNO(std::string buf, ReadControlFile("tasks")); 85 return ParsePIDList(buf); 86 } 87 88 PosixError Cgroup::PollControlFileForChange(absl::string_view name, 89 absl::Duration timeout) const { 90 return PollControlFileForChangeAfter(name, timeout, []() {}); 91 } 92 93 PosixError Cgroup::PollControlFileForChangeAfter( 94 absl::string_view name, absl::Duration timeout, 95 std::function<void()> body) const { 96 const absl::Time deadline = absl::Now() + timeout; 97 const std::string alias_path = absl::StrFormat("[cg#%d]/%s", id_, name); 98 99 ASSIGN_OR_RETURN_ERRNO(const int64_t initial_value, 100 ReadIntegerControlFile(name)); 101 102 body(); 103 104 // The loop below iterates quickly and results in too many save-restore 105 // cycles. This can prevent tasks from being scheduled and incurring the 106 // resource usage this function is often waiting for, resulting in timeouts. 107 const DisableSave ds; 108 109 std::cerr << absl::StreamFormat( 110 "Waiting for control file '%s' to change from '%d'...", 111 alias_path, initial_value) 112 << std::endl; 113 while (true) { 114 ASSIGN_OR_RETURN_ERRNO(const int64_t current_value, 115 ReadIntegerControlFile(name)); 116 if (current_value != initial_value) { 117 std::cerr << absl::StreamFormat( 118 "Control file '%s' changed from '%d' to '%d'", 119 alias_path, initial_value, current_value) 120 << std::endl; 121 return NoError(); 122 } 123 if (absl::Now() >= deadline) { 124 return PosixError(ETIME, absl::StrCat(alias_path, " didn't change in ", 125 absl::FormatDuration(timeout))); 126 } 127 } 128 } 129 130 PosixError Cgroup::ContainsCallingProcess() const { 131 ASSIGN_OR_RETURN_ERRNO(const absl::flat_hash_set<pid_t> procs, Procs()); 132 ASSIGN_OR_RETURN_ERRNO(const absl::flat_hash_set<pid_t> tasks, Tasks()); 133 const pid_t pid = getpid(); 134 const pid_t tid = syscall(SYS_gettid); 135 if (!procs.contains(pid)) { 136 return PosixError( 137 ENOENT, absl::StrFormat("Cgroup doesn't contain process %d", pid)); 138 } 139 if (!tasks.contains(tid)) { 140 return PosixError(ENOENT, 141 absl::StrFormat("Cgroup doesn't contain task %d", tid)); 142 } 143 return NoError(); 144 } 145 146 PosixError Cgroup::ContainsCallingThread() const { 147 ASSIGN_OR_RETURN_ERRNO(const absl::flat_hash_set<pid_t> tasks, Tasks()); 148 const pid_t tid = syscall(SYS_gettid); 149 if (!tasks.contains(tid)) { 150 return PosixError(ENOENT, 151 absl::StrFormat("Cgroup doesn't contain task %d", tid)); 152 } 153 return NoError(); 154 } 155 156 PosixError Cgroup::Enter(pid_t pid) const { 157 return WriteIntegerControlFile("cgroup.procs", static_cast<int64_t>(pid)); 158 } 159 160 PosixError Cgroup::EnterThread(pid_t pid) const { 161 return WriteIntegerControlFile("tasks", static_cast<int64_t>(pid)); 162 } 163 164 PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::ParsePIDList( 165 absl::string_view data) const { 166 absl::flat_hash_set<pid_t> res; 167 std::vector<absl::string_view> lines = absl::StrSplit(data, '\n'); 168 for (const absl::string_view& line : lines) { 169 if (line.empty()) { 170 continue; 171 } 172 ASSIGN_OR_RETURN_ERRNO(const int32_t pid, Atoi<int32_t>(line)); 173 res.insert(static_cast<pid_t>(pid)); 174 } 175 return res; 176 } 177 178 int64_t Cgroup::next_id_ = 0; 179 180 PosixErrorOr<Cgroup> Mounter::MountCgroupfs(std::string mopts) { 181 ASSIGN_OR_RETURN_ERRNO(TempPath mountpoint, 182 TempPath::CreateDirIn(root_.path())); 183 ASSIGN_OR_RETURN_ERRNO( 184 Cleanup mount, Mount("none", mountpoint.path(), "cgroup", 0, mopts, 0)); 185 const std::string mountpath = mountpoint.path(); 186 std::cerr << absl::StreamFormat( 187 "Mount(\"none\", \"%s\", \"cgroup\", 0, \"%s\", 0) => OK", 188 mountpath, mopts) 189 << std::endl; 190 Cgroup cg = Cgroup::RootCgroup(mountpath); 191 mountpoints_[cg.id()] = std::move(mountpoint); 192 mounts_[cg.id()] = std::move(mount); 193 return cg; 194 } 195 196 PosixError Mounter::Unmount(const Cgroup& c) { 197 auto mount = mounts_.find(c.id()); 198 auto mountpoint = mountpoints_.find(c.id()); 199 200 if (mount == mounts_.end() || mountpoint == mountpoints_.end()) { 201 return PosixError( 202 ESRCH, absl::StrFormat("No mount found for cgroupfs containing cg#%d", 203 c.id())); 204 } 205 206 std::cerr << absl::StreamFormat("Unmount([cg#%d])", c.id()) << std::endl; 207 208 // Simply delete the entries, their destructors will unmount and delete the 209 // mountpoint. Note the order is important to avoid errors: mount then 210 // mountpoint. 211 mounts_.erase(mount); 212 mountpoints_.erase(mountpoint); 213 214 return NoError(); 215 } 216 217 void Mounter::release(const Cgroup& c) { 218 auto mp = mountpoints_.find(c.id()); 219 if (mp != mountpoints_.end()) { 220 mp->second.release(); 221 mountpoints_.erase(mp); 222 } 223 224 auto m = mounts_.find(c.id()); 225 if (m != mounts_.end()) { 226 m->second.Release(); 227 mounts_.erase(m); 228 } 229 } 230 231 constexpr char kProcCgroupsHeader[] = 232 "#subsys_name\thierarchy\tnum_cgroups\tenabled"; 233 234 PosixErrorOr<absl::flat_hash_map<std::string, CgroupsEntry>> 235 ProcCgroupsEntries() { 236 std::string content; 237 RETURN_IF_ERRNO(GetContents("/proc/cgroups", &content)); 238 239 bool found_header = false; 240 absl::flat_hash_map<std::string, CgroupsEntry> entries; 241 std::vector<std::string> lines = absl::StrSplit(content, '\n'); 242 std::cerr << "<contents of /proc/cgroups>" << std::endl; 243 for (const std::string& line : lines) { 244 std::cerr << line << std::endl; 245 246 if (!found_header) { 247 EXPECT_EQ(line, kProcCgroupsHeader); 248 found_header = true; 249 continue; 250 } 251 if (line.empty()) { 252 continue; 253 } 254 255 // Parse a single entry from /proc/cgroups. 256 // 257 // Example entries, fields are tab separated in the real file: 258 // 259 // #subsys_name hierarchy num_cgroups enabled 260 // cpuset 12 35 1 261 // cpu 3 222 1 262 // ^ ^ ^ ^ 263 // 0 1 2 3 264 265 CgroupsEntry entry; 266 std::vector<std::string> fields = 267 StrSplit(line, absl::ByAnyChar(": \t"), absl::SkipEmpty()); 268 269 entry.subsys_name = fields[0]; 270 ASSIGN_OR_RETURN_ERRNO(entry.hierarchy, Atoi<uint32_t>(fields[1])); 271 ASSIGN_OR_RETURN_ERRNO(entry.num_cgroups, Atoi<uint64_t>(fields[2])); 272 ASSIGN_OR_RETURN_ERRNO(const int enabled, Atoi<int>(fields[3])); 273 entry.enabled = enabled != 0; 274 275 entries[entry.subsys_name] = entry; 276 } 277 std::cerr << "<end of /proc/cgroups>" << std::endl; 278 279 return entries; 280 } 281 282 PosixErrorOr<absl::flat_hash_map<std::string, PIDCgroupEntry>> 283 ProcPIDCgroupEntries(pid_t pid) { 284 const std::string path = absl::StrFormat("/proc/%d/cgroup", pid); 285 std::string content; 286 RETURN_IF_ERRNO(GetContents(path, &content)); 287 288 absl::flat_hash_map<std::string, PIDCgroupEntry> entries; 289 std::vector<std::string> lines = absl::StrSplit(content, '\n'); 290 291 std::cerr << absl::StreamFormat("<contents of %s>", path) << std::endl; 292 for (const std::string& line : lines) { 293 std::cerr << line << std::endl; 294 295 if (line.empty()) { 296 continue; 297 } 298 299 // Parse a single entry from /proc/<pid>/cgroup. 300 // 301 // Example entries: 302 // 303 // 2:cpu:/path/to/cgroup 304 // 1:memory:/ 305 306 PIDCgroupEntry entry; 307 std::vector<std::string> fields = 308 absl::StrSplit(line, absl::ByChar(':'), absl::SkipEmpty()); 309 310 ASSIGN_OR_RETURN_ERRNO(entry.hierarchy, Atoi<uint32_t>(fields[0])); 311 entry.controllers = fields[1]; 312 entry.path = fields[2]; 313 314 entries[entry.controllers] = entry; 315 } 316 std::cerr << absl::StreamFormat("<end of %s>", path) << std::endl; 317 318 return entries; 319 } 320 321 } // namespace testing 322 } // namespace gvisor