2020-05-16 22:09:48 +03:00
|
|
|
#if __linux__
|
|
|
|
|
|
|
|
#include "cgroup.hh"
|
|
|
|
#include "util.hh"
|
2023-10-25 07:43:36 +03:00
|
|
|
#include "file-system.hh"
|
2022-12-02 13:57:41 +02:00
|
|
|
#include "finally.hh"
|
2020-05-16 22:09:48 +03:00
|
|
|
|
|
|
|
#include <chrono>
|
2020-10-17 20:25:17 +03:00
|
|
|
#include <cmath>
|
|
|
|
#include <regex>
|
2020-05-20 00:25:44 +03:00
|
|
|
#include <unordered_set>
|
2020-10-17 20:25:17 +03:00
|
|
|
#include <thread>
|
2020-05-16 22:09:48 +03:00
|
|
|
|
|
|
|
#include <dirent.h>
|
2022-12-02 13:57:41 +02:00
|
|
|
#include <mntent.h>
|
2020-05-16 22:09:48 +03:00
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
2022-12-02 13:57:41 +02:00
|
|
|
std::optional<Path> getCgroupFS()
|
|
|
|
{
|
|
|
|
static auto res = [&]() -> std::optional<Path> {
|
|
|
|
auto fp = fopen("/proc/mounts", "r");
|
|
|
|
if (!fp) return std::nullopt;
|
|
|
|
Finally delFP = [&]() { fclose(fp); };
|
|
|
|
while (auto ent = getmntent(fp))
|
|
|
|
if (std::string_view(ent->mnt_type) == "cgroup2")
|
|
|
|
return ent->mnt_dir;
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}();
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2022-11-08 17:03:42 +02:00
|
|
|
// FIXME: obsolete, check for cgroup2
|
2020-05-16 22:21:41 +03:00
|
|
|
std::map<std::string, std::string> getCgroups(const Path & cgroupFile)
|
|
|
|
{
|
|
|
|
std::map<std::string, std::string> cgroups;
|
|
|
|
|
|
|
|
for (auto & line : tokenizeString<std::vector<std::string>>(readFile(cgroupFile), "\n")) {
|
|
|
|
static std::regex regex("([0-9]+):([^:]*):(.*)");
|
|
|
|
std::smatch match;
|
|
|
|
if (!std::regex_match(line, match, regex))
|
|
|
|
throw Error("invalid line '%s' in '%s'", line, cgroupFile);
|
|
|
|
|
2020-05-20 00:25:44 +03:00
|
|
|
std::string name = hasPrefix(std::string(match[2]), "name=") ? std::string(match[2], 5) : match[2];
|
2020-05-16 22:21:41 +03:00
|
|
|
cgroups.insert_or_assign(name, match[3]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return cgroups;
|
|
|
|
}
|
|
|
|
|
2022-11-18 14:40:59 +02:00
|
|
|
static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats)
|
2020-05-16 22:09:48 +03:00
|
|
|
{
|
2022-11-18 14:40:59 +02:00
|
|
|
if (!pathExists(cgroup)) return {};
|
|
|
|
|
2022-11-18 17:59:36 +02:00
|
|
|
auto procsFile = cgroup + "/cgroup.procs";
|
|
|
|
|
|
|
|
if (!pathExists(procsFile))
|
2022-11-18 14:40:59 +02:00
|
|
|
throw Error("'%s' is not a cgroup", cgroup);
|
2020-05-20 00:25:44 +03:00
|
|
|
|
2022-11-18 17:59:36 +02:00
|
|
|
/* Use the fast way to kill every process in a cgroup, if
|
|
|
|
available. */
|
|
|
|
auto killFile = cgroup + "/cgroup.kill";
|
|
|
|
if (pathExists(killFile))
|
|
|
|
writeFile(killFile, "1");
|
|
|
|
|
|
|
|
/* Otherwise, manually kill every process in the subcgroups and
|
|
|
|
this cgroup. */
|
2020-05-16 22:09:48 +03:00
|
|
|
for (auto & entry : readDirectory(cgroup)) {
|
|
|
|
if (entry.type != DT_DIR) continue;
|
2022-11-18 14:40:59 +02:00
|
|
|
destroyCgroup(cgroup + "/" + entry.name, false);
|
2020-05-16 22:09:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int round = 1;
|
|
|
|
|
2020-05-20 00:25:44 +03:00
|
|
|
std::unordered_set<pid_t> pidsShown;
|
|
|
|
|
2020-05-16 22:09:48 +03:00
|
|
|
while (true) {
|
2022-11-18 17:59:36 +02:00
|
|
|
auto pids = tokenizeString<std::vector<std::string>>(readFile(procsFile));
|
2020-05-16 22:09:48 +03:00
|
|
|
|
|
|
|
if (pids.empty()) break;
|
|
|
|
|
|
|
|
if (round > 20)
|
|
|
|
throw Error("cannot kill cgroup '%s'", cgroup);
|
|
|
|
|
|
|
|
for (auto & pid_s : pids) {
|
|
|
|
pid_t pid;
|
2021-04-27 22:06:58 +03:00
|
|
|
if (auto o = string2Int<pid_t>(pid_s))
|
|
|
|
pid = *o;
|
|
|
|
else
|
|
|
|
throw Error("invalid pid '%s'", pid);
|
2020-05-20 00:25:44 +03:00
|
|
|
if (pidsShown.insert(pid).second) {
|
|
|
|
try {
|
|
|
|
auto cmdline = readFile(fmt("/proc/%d/cmdline", pid));
|
|
|
|
using namespace std::string_literals;
|
|
|
|
warn("killing stray builder process %d (%s)...",
|
|
|
|
pid, trim(replaceStrings(cmdline, "\0"s, " ")));
|
2023-12-02 00:03:28 +02:00
|
|
|
} catch (SystemError &) {
|
2020-05-20 00:25:44 +03:00
|
|
|
}
|
|
|
|
}
|
2020-05-16 22:09:48 +03:00
|
|
|
// FIXME: pid wraparound
|
|
|
|
if (kill(pid, SIGKILL) == -1 && errno != ESRCH)
|
|
|
|
throw SysError("killing member %d of cgroup '%s'", pid, cgroup);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto sleep = std::chrono::milliseconds((int) std::pow(2.0, std::min(round, 10)));
|
2020-05-20 00:25:44 +03:00
|
|
|
if (sleep.count() > 100)
|
|
|
|
printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup);
|
2020-05-16 22:09:48 +03:00
|
|
|
std::this_thread::sleep_for(sleep);
|
|
|
|
round++;
|
|
|
|
}
|
|
|
|
|
2022-11-18 14:40:59 +02:00
|
|
|
CgroupStats stats;
|
|
|
|
|
|
|
|
if (returnStats) {
|
|
|
|
auto cpustatPath = cgroup + "/cpu.stat";
|
|
|
|
|
|
|
|
if (pathExists(cpustatPath)) {
|
|
|
|
for (auto & line : tokenizeString<std::vector<std::string>>(readFile(cpustatPath), "\n")) {
|
|
|
|
std::string_view userPrefix = "user_usec ";
|
|
|
|
if (hasPrefix(line, userPrefix)) {
|
|
|
|
auto n = string2Int<uint64_t>(line.substr(userPrefix.size()));
|
|
|
|
if (n) stats.cpuUser = std::chrono::microseconds(*n);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string_view systemPrefix = "system_usec ";
|
|
|
|
if (hasPrefix(line, systemPrefix)) {
|
|
|
|
auto n = string2Int<uint64_t>(line.substr(systemPrefix.size()));
|
|
|
|
if (n) stats.cpuSystem = std::chrono::microseconds(*n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-16 22:09:48 +03:00
|
|
|
if (rmdir(cgroup.c_str()) == -1)
|
|
|
|
throw SysError("deleting cgroup '%s'", cgroup);
|
2022-11-18 14:40:59 +02:00
|
|
|
|
|
|
|
return stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
CgroupStats destroyCgroup(const Path & cgroup)
|
|
|
|
{
|
|
|
|
return destroyCgroup(cgroup, true);
|
2020-05-16 22:09:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|