Skip to content

Commit

Permalink
[cgroups2] Add an interface to read and assign processes.
Browse files Browse the repository at this point in the history
`cgroup.procs` lists all of the processes inside of a cgroup, one per line.
There may be duplicates. A single process can be moved into a cgroup by
writing its process ID into `cgroup.procs`.

Here we introduce:
- Reading the processes inside of a cgroup, and
- Assigning a process to a cgroup.

This closes apache#532
  • Loading branch information
Devin Leamy authored and bmahler committed Mar 22, 2024
1 parent 0aa65cd commit 60283df
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 0 deletions.
41 changes: 41 additions & 0 deletions src/linux/cgroups2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,47 @@ Try<string> cgroup(pid_t pid)
}


Try<set<pid_t>> processes(const string& cgroup)
{
if (!cgroups2::exists(cgroup)) {
return Error("Cgroup '" + cgroup + "' does not exist");
}

Try<string> contents = cgroups2::read<string>(cgroup, control::PROCESSES);
if (contents.isError()) {
return Error(
"Failed to read cgroup.procs in '" + cgroup + "': " + contents.error());
}

string trimmed = strings::trim(*contents);
if (trimmed.empty()) {
return set<pid_t>();
}

set<pid_t> pids;
foreach (const string& _pid, strings::split(strings::trim(*contents), "\n")) {
Try<pid_t> pid = numify<pid_t>(strings::trim(_pid));
if (pid.isError()) {
return Error("Failed to parse pid: " + pid.error());
}

pids.insert(*pid);
}

return pids;
}


Try<Nothing> assign(const string& cgroup, pid_t pid)
{
if (!cgroups2::exists(cgroup)) {
return Error("Cgroup '" + cgroup + "' does not exist");
}

return cgroups2::write(cgroup, control::PROCESSES, stringify(pid));
}


string path(const string& cgroup)
{
return path::join(cgroups2::MOUNT_POINT, cgroup);
Expand Down
9 changes: 9 additions & 0 deletions src/linux/cgroups2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ Try<Nothing> move_process(const std::string& cgroup, pid_t pid);
Try<std::string> cgroup(pid_t pid);


// Get the processes inside of a cgroup.
Try<std::set<pid_t>> processes(const std::string& cgroup);


// Assign a process to a cgroup, by PID. This removes the process from its
// current cgroup.
Try<Nothing> assign(const std::string& cgroup, pid_t pid);


// Get the absolute of a cgroup. The cgroup provided should not start with '/'.
std::string path(const std::string& cgroup);

Expand Down
37 changes: 37 additions & 0 deletions src/tests/containerizer/cgroups2_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,43 @@ TEST_F(Cgroups2Test, CGROUPS2_Path)
EXPECT_EQ("/sys/fs/cgroup/foo/bar", cgroups2::path("foo/bar"));
}


TEST_F(Cgroups2Test, CGROUPS_Path)
{
Try<set<pid_t>> pids = cgroups2::processes(cgroups2::ROOT_CGROUP);

EXPECT_SOME(pids);
EXPECT_TRUE(pids->size() > 0);

ASSERT_SOME(cgroups2::create(TEST_CGROUP));

pid_t pid = ::fork();
ASSERT_NE(-1, pid);

if (pid == 0) {
// In child process, wait for kill signal.
while (true) { sleep(1); }

SAFE_EXIT(
EXIT_FAILURE, "Error, child should be killed before reaching here");
}

pids = cgroups2::processes(TEST_CGROUP);
EXPECT_SOME(pids);
EXPECT_EQ(0u, pids->size());

EXPECT_SOME(cgroups2::assign(TEST_CGROUP, pid));
pids = cgroups2::processes(TEST_CGROUP);

EXPECT_SOME(pids);
EXPECT_EQ(1u, pids->size());
EXPECT_EQ(pid, *pids->begin());

// Kill the child process.
ASSERT_NE(-1, ::kill(pid, SIGKILL));
AWAIT_EXPECT_WTERMSIG_EQ(SIGKILL, process::reap(pid));
}

} // namespace tests {

} // namespace internal {
Expand Down

0 comments on commit 60283df

Please sign in to comment.