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