Get CPU stats from the cgroup

This commit is contained in:
Eelco Dolstra 2022-11-18 13:40:59 +01:00
parent 20f66c6889
commit fa68eb367e
6 changed files with 71 additions and 11 deletions

View file

@ -5,7 +5,7 @@
#include <string> #include <string>
#include <chrono> #include <chrono>
#include <optional>
namespace nix { namespace nix {
@ -78,6 +78,9 @@ struct BuildResult
was repeated). */ was repeated). */
time_t startTime = 0, stopTime = 0; time_t startTime = 0, stopTime = 0;
/* User and system CPU time the build took. */
std::optional<std::chrono::microseconds> cpuUser, cpuSystem;
bool success() bool success()
{ {
return status == Built || status == Substituted || status == AlreadyValid || status == ResolvesToAlreadyValid; return status == Built || status == Substituted || status == AlreadyValid || status == ResolvesToAlreadyValid;

View file

@ -869,6 +869,14 @@ void DerivationGoal::buildDone()
cleanupPostChildKill(); cleanupPostChildKill();
if (buildResult.cpuUser && buildResult.cpuSystem) {
debug("builder for '%s' terminated with status %d, user CPU %.3fs, system CPU %.3fs",
worker.store.printStorePath(drvPath),
status,
((double) buildResult.cpuUser->count()) / 1000000,
((double) buildResult.cpuSystem->count()) / 1000000);
}
bool diskFull = false; bool diskFull = false;
try { try {

View file

@ -137,7 +137,7 @@ void LocalDerivationGoal::killChild()
also send a conventional kill to the child. */ also send a conventional kill to the child. */
::kill(-pid, SIGKILL); /* ignore the result */ ::kill(-pid, SIGKILL); /* ignore the result */
killSandbox(); killSandbox(true);
pid.wait(); pid.wait();
} }
@ -146,10 +146,14 @@ void LocalDerivationGoal::killChild()
} }
void LocalDerivationGoal::killSandbox() void LocalDerivationGoal::killSandbox(bool getStats)
{ {
if (cgroup) { if (cgroup) {
destroyCgroup(*cgroup); auto stats = destroyCgroup(*cgroup);
if (getStats) {
buildResult.cpuUser = stats.cpuUser;
buildResult.cpuSystem = stats.cpuSystem;
}
} }
else if (buildUser) { else if (buildUser) {
@ -270,7 +274,7 @@ void LocalDerivationGoal::cleanupPostChildKill()
malicious user from leaving behind a process that keeps files malicious user from leaving behind a process that keeps files
open and modifies them after they have been chown'ed to open and modifies them after they have been chown'ed to
root. */ root. */
killSandbox(); killSandbox(true);
/* Terminate the recursive Nix daemon. */ /* Terminate the recursive Nix daemon. */
stopDaemon(); stopDaemon();
@ -410,7 +414,7 @@ void LocalDerivationGoal::startBuilder()
/* Make sure that no other processes are executing under the /* Make sure that no other processes are executing under the
sandbox uids. This must be done before any chownToBuilder() sandbox uids. This must be done before any chownToBuilder()
calls. */ calls. */
killSandbox(); killSandbox(false);
/* Right platform? */ /* Right platform? */
if (!parsedDrv->canBuildLocally(worker.store)) if (!parsedDrv->canBuildLocally(worker.store))

View file

@ -202,7 +202,7 @@ struct LocalDerivationGoal : public DerivationGoal
/* Kill any processes running under the build user UID or in the /* Kill any processes running under the build user UID or in the
cgroup of the build. */ cgroup of the build. */
void killSandbox(); void killSandbox(bool getStats);
/* Create alternative path calculated from but distinct from the /* Create alternative path calculated from but distinct from the
input, so we can avoid overwriting outputs (or other store paths) input, so we can avoid overwriting outputs (or other store paths)

View file

@ -31,13 +31,16 @@ std::map<std::string, std::string> getCgroups(const Path & cgroupFile)
return cgroups; return cgroups;
} }
void destroyCgroup(const Path & cgroup) static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats)
{ {
if (!pathExists(cgroup)) return; if (!pathExists(cgroup)) return {};
if (!pathExists(cgroup + "/cgroup.procs"))
throw Error("'%s' is not a cgroup", cgroup);
for (auto & entry : readDirectory(cgroup)) { for (auto & entry : readDirectory(cgroup)) {
if (entry.type != DT_DIR) continue; if (entry.type != DT_DIR) continue;
destroyCgroup(cgroup + "/" + entry.name); destroyCgroup(cgroup + "/" + entry.name, false);
} }
int round = 1; int round = 1;
@ -79,8 +82,38 @@ void destroyCgroup(const Path & cgroup)
round++; round++;
} }
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);
}
}
}
}
if (rmdir(cgroup.c_str()) == -1) if (rmdir(cgroup.c_str()) == -1)
throw SysError("deleting cgroup '%s'", cgroup); throw SysError("deleting cgroup '%s'", cgroup);
return stats;
}
CgroupStats destroyCgroup(const Path & cgroup)
{
return destroyCgroup(cgroup, true);
} }
} }

View file

@ -2,13 +2,25 @@
#if __linux__ #if __linux__
#include <chrono>
#include <optional>
#include "types.hh" #include "types.hh"
namespace nix { namespace nix {
std::map<std::string, std::string> getCgroups(const Path & cgroupFile); std::map<std::string, std::string> getCgroups(const Path & cgroupFile);
void destroyCgroup(const Path & cgroup); struct CgroupStats
{
std::optional<std::chrono::microseconds> 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);
} }