From 1211e59a038379026496bbee4b203bbd66833b01 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Dec 2022 12:38:03 +0100 Subject: Move cgroup.{cc,hh} to libutil --- src/libstore/cgroup.cc | 131 ------------------------------------------------- src/libstore/cgroup.hh | 27 ---------- src/libutil/cgroup.cc | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ src/libutil/cgroup.hh | 27 ++++++++++ 4 files changed, 158 insertions(+), 158 deletions(-) delete mode 100644 src/libstore/cgroup.cc delete mode 100644 src/libstore/cgroup.hh create mode 100644 src/libutil/cgroup.cc create mode 100644 src/libutil/cgroup.hh (limited to 'src') diff --git a/src/libstore/cgroup.cc b/src/libstore/cgroup.cc deleted file mode 100644 index f693d77be..000000000 --- a/src/libstore/cgroup.cc +++ /dev/null @@ -1,131 +0,0 @@ -#if __linux__ - -#include "cgroup.hh" -#include "util.hh" - -#include -#include -#include -#include -#include - -#include - -namespace nix { - -// FIXME: obsolete, check for cgroup2 -std::map getCgroups(const Path & cgroupFile) -{ - std::map cgroups; - - for (auto & line : tokenizeString>(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); - - std::string name = hasPrefix(std::string(match[2]), "name=") ? std::string(match[2], 5) : match[2]; - cgroups.insert_or_assign(name, match[3]); - } - - return cgroups; -} - -static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats) -{ - if (!pathExists(cgroup)) return {}; - - auto procsFile = cgroup + "/cgroup.procs"; - - if (!pathExists(procsFile)) - throw Error("'%s' is not a cgroup", cgroup); - - /* 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. */ - for (auto & entry : readDirectory(cgroup)) { - if (entry.type != DT_DIR) continue; - destroyCgroup(cgroup + "/" + entry.name, false); - } - - int round = 1; - - std::unordered_set pidsShown; - - while (true) { - auto pids = tokenizeString>(readFile(procsFile)); - - if (pids.empty()) break; - - if (round > 20) - throw Error("cannot kill cgroup '%s'", cgroup); - - for (auto & pid_s : pids) { - pid_t pid; - if (auto o = string2Int(pid_s)) - pid = *o; - else - throw Error("invalid pid '%s'", pid); - 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, " "))); - } catch (SysError &) { - } - } - // 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))); - if (sleep.count() > 100) - printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup); - std::this_thread::sleep_for(sleep); - round++; - } - - CgroupStats stats; - - if (returnStats) { - auto cpustatPath = cgroup + "/cpu.stat"; - - if (pathExists(cpustatPath)) { - for (auto & line : tokenizeString>(readFile(cpustatPath), "\n")) { - std::string_view userPrefix = "user_usec "; - if (hasPrefix(line, userPrefix)) { - auto n = string2Int(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(line.substr(systemPrefix.size())); - if (n) stats.cpuSystem = std::chrono::microseconds(*n); - } - } - } - - } - - if (rmdir(cgroup.c_str()) == -1) - throw SysError("deleting cgroup '%s'", cgroup); - - return stats; -} - -CgroupStats destroyCgroup(const Path & cgroup) -{ - return destroyCgroup(cgroup, true); -} - -} - -#endif diff --git a/src/libstore/cgroup.hh b/src/libstore/cgroup.hh deleted file mode 100644 index 3ead4735f..000000000 --- a/src/libstore/cgroup.hh +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#if __linux__ - -#include -#include - -#include "types.hh" - -namespace nix { - -std::map getCgroups(const Path & cgroupFile); - -struct CgroupStats -{ - std::optional cpuUser, cpuSystem; -}; - -/* Destroy the cgroup denoted by 'path'. The postcondition is that - 'path' does not exist, and thus any processes in the cgroup have - been killed. Also return statistics from the cgroup just before - destruction. */ -CgroupStats destroyCgroup(const Path & cgroup); - -} - -#endif diff --git a/src/libutil/cgroup.cc b/src/libutil/cgroup.cc new file mode 100644 index 000000000..f693d77be --- /dev/null +++ b/src/libutil/cgroup.cc @@ -0,0 +1,131 @@ +#if __linux__ + +#include "cgroup.hh" +#include "util.hh" + +#include +#include +#include +#include +#include + +#include + +namespace nix { + +// FIXME: obsolete, check for cgroup2 +std::map getCgroups(const Path & cgroupFile) +{ + std::map cgroups; + + for (auto & line : tokenizeString>(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); + + std::string name = hasPrefix(std::string(match[2]), "name=") ? std::string(match[2], 5) : match[2]; + cgroups.insert_or_assign(name, match[3]); + } + + return cgroups; +} + +static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats) +{ + if (!pathExists(cgroup)) return {}; + + auto procsFile = cgroup + "/cgroup.procs"; + + if (!pathExists(procsFile)) + throw Error("'%s' is not a cgroup", cgroup); + + /* 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. */ + for (auto & entry : readDirectory(cgroup)) { + if (entry.type != DT_DIR) continue; + destroyCgroup(cgroup + "/" + entry.name, false); + } + + int round = 1; + + std::unordered_set pidsShown; + + while (true) { + auto pids = tokenizeString>(readFile(procsFile)); + + if (pids.empty()) break; + + if (round > 20) + throw Error("cannot kill cgroup '%s'", cgroup); + + for (auto & pid_s : pids) { + pid_t pid; + if (auto o = string2Int(pid_s)) + pid = *o; + else + throw Error("invalid pid '%s'", pid); + 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, " "))); + } catch (SysError &) { + } + } + // 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))); + if (sleep.count() > 100) + printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup); + std::this_thread::sleep_for(sleep); + round++; + } + + CgroupStats stats; + + if (returnStats) { + auto cpustatPath = cgroup + "/cpu.stat"; + + if (pathExists(cpustatPath)) { + for (auto & line : tokenizeString>(readFile(cpustatPath), "\n")) { + std::string_view userPrefix = "user_usec "; + if (hasPrefix(line, userPrefix)) { + auto n = string2Int(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(line.substr(systemPrefix.size())); + if (n) stats.cpuSystem = std::chrono::microseconds(*n); + } + } + } + + } + + if (rmdir(cgroup.c_str()) == -1) + throw SysError("deleting cgroup '%s'", cgroup); + + return stats; +} + +CgroupStats destroyCgroup(const Path & cgroup) +{ + return destroyCgroup(cgroup, true); +} + +} + +#endif diff --git a/src/libutil/cgroup.hh b/src/libutil/cgroup.hh new file mode 100644 index 000000000..3ead4735f --- /dev/null +++ b/src/libutil/cgroup.hh @@ -0,0 +1,27 @@ +#pragma once + +#if __linux__ + +#include +#include + +#include "types.hh" + +namespace nix { + +std::map getCgroups(const Path & cgroupFile); + +struct CgroupStats +{ + std::optional cpuUser, cpuSystem; +}; + +/* Destroy the cgroup denoted by 'path'. The postcondition is that + 'path' does not exist, and thus any processes in the cgroup have + been killed. Also return statistics from the cgroup just before + destruction. */ +CgroupStats destroyCgroup(const Path & cgroup); + +} + +#endif -- cgit v1.2.3 From 1e6a5d1ff6e8ef5bf340502f74c4d5039cedc67a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Dec 2022 12:57:41 +0100 Subject: Clean up cgroup handling in getMaxCPU() Also, don't assume in LocalDerivationGoal that cgroups are mounted on /sys/fs/cgroup. --- src/libstore/build/local-derivation-goal.cc | 6 +++- src/libutil/cgroup.cc | 17 ++++++++++ src/libutil/cgroup.hh | 2 ++ src/libutil/util.cc | 49 +++++++++-------------------- 4 files changed, 39 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index c9b7b24f3..d2798888b 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -409,12 +409,16 @@ void LocalDerivationGoal::startBuilder() #if __linux__ settings.requireExperimentalFeature(Xp::Cgroups); + auto cgroupFS = getCgroupFS(); + if (!cgroupFS) + throw Error("cannot determine the cgroups file system"); + auto ourCgroups = getCgroups("/proc/self/cgroup"); auto ourCgroup = ourCgroups[""]; if (ourCgroup == "") throw Error("cannot determine cgroup name from /proc/self/cgroup"); - auto ourCgroupPath = canonPath("/sys/fs/cgroup/" + ourCgroup); + auto ourCgroupPath = canonPath(*cgroupFS + "/" + ourCgroup); if (!pathExists(ourCgroupPath)) throw Error("expected cgroup directory '%s'", ourCgroupPath); diff --git a/src/libutil/cgroup.cc b/src/libutil/cgroup.cc index f693d77be..a008481ca 100644 --- a/src/libutil/cgroup.cc +++ b/src/libutil/cgroup.cc @@ -2,6 +2,7 @@ #include "cgroup.hh" #include "util.hh" +#include "finally.hh" #include #include @@ -10,9 +11,25 @@ #include #include +#include namespace nix { +std::optional getCgroupFS() +{ + static auto res = [&]() -> std::optional { + 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; +} + // FIXME: obsolete, check for cgroup2 std::map getCgroups(const Path & cgroupFile) { diff --git a/src/libutil/cgroup.hh b/src/libutil/cgroup.hh index 3ead4735f..d08c8ad29 100644 --- a/src/libutil/cgroup.hh +++ b/src/libutil/cgroup.hh @@ -9,6 +9,8 @@ namespace nix { +std::optional getCgroupFS(); + std::map getCgroups(const Path & cgroupFile); struct CgroupStats diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 623b74bdd..2c2aae82e 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -2,6 +2,7 @@ #include "sync.hh" #include "finally.hh" #include "serialise.hh" +#include "cgroup.hh" #include #include @@ -36,7 +37,6 @@ #include #include -#include #include #endif @@ -727,43 +727,24 @@ unsigned int getMaxCPU() { #if __linux__ try { - FILE *fp = fopen("/proc/mounts", "r"); - if (!fp) - return 0; + auto cgroupFS = getCgroupFS(); + if (!cgroupFS) return 0; - Strings cgPathParts; + if (!pathExists("/proc/self/cgroup")) return 0; - struct mntent *ent; - while ((ent = getmntent(fp))) { - std::string mountType, mountPath; + auto cgroups = getCgroups("/proc/self/cgroup"); + auto cgroup = cgroups[""]; + if (cgroup == "") return 0; - mountType = ent->mnt_type; - mountPath = ent->mnt_dir; + auto cpuFile = *cgroupFS + "/" + cgroup + "/cpu.max"; - if (mountType == "cgroup2") { - cgPathParts.push_back(mountPath); - break; - } - } - - fclose(fp); - - if (cgPathParts.size() > 0 && pathExists("/proc/self/cgroup")) { - std::string currentCgroup = readFile("/proc/self/cgroup"); - Strings cgValues = tokenizeString(currentCgroup, ":"); - cgPathParts.push_back(trim(cgValues.back(), "\n")); - cgPathParts.push_back("cpu.max"); - std::string fullCgPath = canonPath(concatStringsSep("/", cgPathParts)); - - if (pathExists(fullCgPath)) { - std::string cpuMax = readFile(fullCgPath); - std::vector cpuMaxParts = tokenizeString>(cpuMax, " "); - std::string quota = cpuMaxParts[0]; - std::string period = trim(cpuMaxParts[1], "\n"); - - if (quota != "max") - return std::ceil(std::stoi(quota) / std::stof(period)); - } + if (pathExists(cpuFile)) { + auto cpuMax = readFile(cpuFile); + auto cpuMaxParts = tokenizeString>(cpuMax, " \n"); + auto quota = cpuMaxParts[0]; + auto period = cpuMaxParts[1]; + if (quota != "max") + return std::ceil(std::stoi(quota) / std::stof(period)); } } catch (Error &) { ignoreException(); } #endif -- cgit v1.2.3 From fa99ef6a879e77024d60e73901a4773c6756c1bb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Dec 2022 15:03:40 +0100 Subject: getMaxCPU(): Lower verbosity level for ignored exceptions Fixes #7268. --- src/libutil/util.cc | 22 +++++++++------------- src/libutil/util.hh | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 2c2aae82e..a93ef1901 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -730,23 +730,19 @@ unsigned int getMaxCPU() auto cgroupFS = getCgroupFS(); if (!cgroupFS) return 0; - if (!pathExists("/proc/self/cgroup")) return 0; - - auto cgroups = getCgroups("/proc/self/cgroup"); + auto cgroups = getCgroups("/proc/self/cgroupp"); auto cgroup = cgroups[""]; if (cgroup == "") return 0; auto cpuFile = *cgroupFS + "/" + cgroup + "/cpu.max"; - if (pathExists(cpuFile)) { - auto cpuMax = readFile(cpuFile); - auto cpuMaxParts = tokenizeString>(cpuMax, " \n"); - auto quota = cpuMaxParts[0]; - auto period = cpuMaxParts[1]; - if (quota != "max") + auto cpuMax = readFile(cpuFile); + auto cpuMaxParts = tokenizeString>(cpuMax, " \n"); + auto quota = cpuMaxParts[0]; + auto period = cpuMaxParts[1]; + if (quota != "max") return std::ceil(std::stoi(quota) / std::stof(period)); - } - } catch (Error &) { ignoreException(); } + } catch (Error &) { ignoreException(lvlDebug); } #endif return 0; @@ -1408,7 +1404,7 @@ std::string shellEscape(const std::string_view s) } -void ignoreException() +void ignoreException(Verbosity lvl) { /* Make sure no exceptions leave this function. printError() also throws when remote is closed. */ @@ -1416,7 +1412,7 @@ void ignoreException() try { throw; } catch (std::exception & e) { - printError("error (ignored): %1%", e.what()); + printMsg(lvl, "error (ignored): %1%", e.what()); } } catch (...) { } } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index e5c678682..94d8cc555 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -528,7 +528,7 @@ std::string shellEscape(const std::string_view s); /* Exception handling in destructors: print an error message, then ignore the exception. */ -void ignoreException(); +void ignoreException(Verbosity lvl = lvlError); -- cgit v1.2.3