Skip to content

Commit 3765b1a

Browse files
authored
Merge pull request #282 from DeterminateSystems/nix-ps
Add `nix ps` command
2 parents bb6c8a1 + 383aec2 commit 3765b1a

29 files changed

+973
-65
lines changed

src/libfetchers/git-utils.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
359359

360360
ThreadPool pool;
361361

362-
auto process = [&done, &pool, &repoPool](this const auto & process, const git_oid & oid) -> void {
362+
auto process = [&done, &pool, &repoPool](this auto const & process, const git_oid & oid) -> void {
363363
auto repo(repoPool.get());
364364

365365
auto _commit = lookupObject(*repo, oid, GIT_OBJECT_COMMIT);

src/libmain/progress-bar.cc

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,7 @@ class ProgressBar : public Logger
470470
}
471471
}
472472

473-
auto width = getWindowSize().second;
474-
if (width <= 0)
475-
width = std::numeric_limits<decltype(width)>::max();
476-
477-
redraw("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
473+
redraw("\r" + filterANSIEscapes(line, false, getWindowWidth()) + ANSI_NORMAL + "\e[K");
478474

479475
return nextWakeup;
480476
}

src/libstore/active-builds.cc

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include "nix/store/active-builds.hh"
2+
#include "nix/util/json-utils.hh"
3+
4+
#include <nlohmann/json.hpp>
5+
6+
#ifndef _WIN32
7+
# include <pwd.h>
8+
#endif
9+
10+
namespace nix {
11+
12+
UserInfo UserInfo::fromUid(uid_t uid)
13+
{
14+
UserInfo info;
15+
info.uid = uid;
16+
17+
#ifndef _WIN32
18+
// Look up the user name for the UID (thread-safe)
19+
struct passwd pwd;
20+
struct passwd * result;
21+
std::vector<char> buf(16384);
22+
if (getpwuid_r(uid, &pwd, buf.data(), buf.size(), &result) == 0 && result)
23+
info.name = result->pw_name;
24+
#endif
25+
26+
return info;
27+
}
28+
29+
} // namespace nix
30+
31+
namespace nlohmann {
32+
33+
using namespace nix;
34+
35+
UserInfo adl_serializer<UserInfo>::from_json(const json & j)
36+
{
37+
return UserInfo{
38+
.uid = j.at("uid").get<uid_t>(),
39+
.name = j.contains("name") && !j.at("name").is_null()
40+
? std::optional<std::string>(j.at("name").get<std::string>())
41+
: std::nullopt,
42+
};
43+
}
44+
45+
void adl_serializer<UserInfo>::to_json(json & j, const UserInfo & info)
46+
{
47+
j = nlohmann::json{
48+
{"uid", info.uid},
49+
{"name", info.name},
50+
};
51+
}
52+
53+
// Durations are serialized as floats representing seconds.
54+
static std::optional<std::chrono::microseconds> parseDuration(const json & j, const char * key)
55+
{
56+
if (j.contains(key) && !j.at(key).is_null())
57+
return std::chrono::duration_cast<std::chrono::microseconds>(
58+
std::chrono::duration<float, std::chrono::seconds::period>(j.at(key).get<double>()));
59+
else
60+
return std::nullopt;
61+
}
62+
63+
static nlohmann::json printDuration(const std::optional<std::chrono::microseconds> & duration)
64+
{
65+
return duration
66+
? nlohmann::json(
67+
std::chrono::duration_cast<std::chrono::duration<float, std::chrono::seconds::period>>(*duration)
68+
.count())
69+
: nullptr;
70+
}
71+
72+
ActiveBuildInfo::ProcessInfo adl_serializer<ActiveBuildInfo::ProcessInfo>::from_json(const json & j)
73+
{
74+
return ActiveBuildInfo::ProcessInfo{
75+
.pid = j.at("pid").get<pid_t>(),
76+
.parentPid = j.at("parentPid").get<pid_t>(),
77+
.user = j.at("user").get<UserInfo>(),
78+
.argv = j.at("argv").get<std::vector<std::string>>(),
79+
.utime = parseDuration(j, "utime"),
80+
.stime = parseDuration(j, "stime"),
81+
.cutime = parseDuration(j, "cutime"),
82+
.cstime = parseDuration(j, "cstime"),
83+
};
84+
}
85+
86+
void adl_serializer<ActiveBuildInfo::ProcessInfo>::to_json(json & j, const ActiveBuildInfo::ProcessInfo & process)
87+
{
88+
j = nlohmann::json{
89+
{"pid", process.pid},
90+
{"parentPid", process.parentPid},
91+
{"user", process.user},
92+
{"argv", process.argv},
93+
{"utime", printDuration(process.utime)},
94+
{"stime", printDuration(process.stime)},
95+
{"cutime", printDuration(process.cutime)},
96+
{"cstime", printDuration(process.cstime)},
97+
};
98+
}
99+
100+
ActiveBuild adl_serializer<ActiveBuild>::from_json(const json & j)
101+
{
102+
return ActiveBuild{
103+
.nixPid = j.at("nixPid").get<pid_t>(),
104+
.clientPid = j.at("clientPid").get<std::optional<pid_t>>(),
105+
.clientUid = j.at("clientUid").get<std::optional<uid_t>>(),
106+
.mainPid = j.at("mainPid").get<pid_t>(),
107+
.mainUser = j.at("mainUser").get<UserInfo>(),
108+
.cgroup = j.at("cgroup").get<std::optional<Path>>(),
109+
.startTime = (time_t) j.at("startTime").get<double>(),
110+
.derivation = StorePath{getString(j.at("derivation"))},
111+
};
112+
}
113+
114+
void adl_serializer<ActiveBuild>::to_json(json & j, const ActiveBuild & build)
115+
{
116+
j = nlohmann::json{
117+
{"nixPid", build.nixPid},
118+
{"clientPid", build.clientPid},
119+
{"clientUid", build.clientUid},
120+
{"mainPid", build.mainPid},
121+
{"mainUser", build.mainUser},
122+
{"cgroup", build.cgroup},
123+
{"startTime", (double) build.startTime},
124+
{"derivation", build.derivation.to_string()},
125+
};
126+
}
127+
128+
ActiveBuildInfo adl_serializer<ActiveBuildInfo>::from_json(const json & j)
129+
{
130+
ActiveBuildInfo info(adl_serializer<ActiveBuild>::from_json(j));
131+
info.processes = j.at("processes").get<std::vector<ActiveBuildInfo::ProcessInfo>>();
132+
info.utime = parseDuration(j, "utime");
133+
info.stime = parseDuration(j, "stime");
134+
return info;
135+
}
136+
137+
void adl_serializer<ActiveBuildInfo>::to_json(json & j, const ActiveBuildInfo & build)
138+
{
139+
adl_serializer<ActiveBuild>::to_json(j, build);
140+
j["processes"] = build.processes;
141+
j["utime"] = printDuration(build.utime);
142+
j["stime"] = printDuration(build.stime);
143+
}
144+
145+
} // namespace nlohmann

src/libstore/daemon.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "nix/util/git.hh"
1818
#include "nix/util/logging.hh"
1919
#include "nix/store/globals.hh"
20+
#include "nix/store/active-builds.hh"
2021

2122
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
2223
# include "nix/util/monitor-fd.hh"
@@ -1014,6 +1015,15 @@ static void performOp(
10141015
case WorkerProto::Op::ClearFailedPaths:
10151016
throw Error("Removed operation %1%", op);
10161017

1018+
case WorkerProto::Op::QueryActiveBuilds: {
1019+
logger->startWork();
1020+
auto & activeBuildsStore = require<QueryActiveBuildsStore>(*store);
1021+
auto activeBuilds = activeBuildsStore.queryActiveBuilds();
1022+
logger->stopWork();
1023+
conn.to << nlohmann::json(activeBuilds).dump();
1024+
break;
1025+
}
1026+
10171027
default:
10181028
throw Error("invalid operation %1%", op);
10191029
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#pragma once
2+
3+
#include "nix/util/util.hh"
4+
#include "nix/util/json-impls.hh"
5+
#include "nix/store/path.hh"
6+
7+
#include <chrono>
8+
#include <sys/types.h>
9+
10+
namespace nix {
11+
12+
/**
13+
* A uid and optional corresponding user name.
14+
*/
15+
struct UserInfo
16+
{
17+
uid_t uid = -1;
18+
std::optional<std::string> name;
19+
20+
/**
21+
* Create a UserInfo from a UID, looking up the username if possible.
22+
*/
23+
static UserInfo fromUid(uid_t uid);
24+
};
25+
26+
struct ActiveBuild
27+
{
28+
pid_t nixPid;
29+
30+
std::optional<pid_t> clientPid;
31+
std::optional<uid_t> clientUid;
32+
33+
pid_t mainPid;
34+
UserInfo mainUser;
35+
std::optional<Path> cgroup;
36+
37+
time_t startTime;
38+
39+
StorePath derivation;
40+
};
41+
42+
struct ActiveBuildInfo : ActiveBuild
43+
{
44+
struct ProcessInfo
45+
{
46+
pid_t pid = 0;
47+
pid_t parentPid = 0;
48+
UserInfo user;
49+
std::vector<std::string> argv;
50+
std::optional<std::chrono::microseconds> utime, stime, cutime, cstime;
51+
};
52+
53+
// User/system CPU time for the entire cgroup, if available.
54+
std::optional<std::chrono::microseconds> utime, stime;
55+
56+
std::vector<ProcessInfo> processes;
57+
};
58+
59+
struct TrackActiveBuildsStore
60+
{
61+
struct BuildHandle
62+
{
63+
TrackActiveBuildsStore & tracker;
64+
uint64_t id;
65+
66+
BuildHandle(TrackActiveBuildsStore & tracker, uint64_t id)
67+
: tracker(tracker)
68+
, id(id)
69+
{
70+
}
71+
72+
BuildHandle(BuildHandle && other) noexcept
73+
: tracker(other.tracker)
74+
, id(other.id)
75+
{
76+
other.id = 0;
77+
}
78+
79+
~BuildHandle()
80+
{
81+
if (id) {
82+
try {
83+
tracker.buildFinished(*this);
84+
} catch (...) {
85+
ignoreExceptionInDestructor();
86+
}
87+
}
88+
}
89+
};
90+
91+
virtual BuildHandle buildStarted(const ActiveBuild & build) = 0;
92+
93+
virtual void buildFinished(const BuildHandle & handle) = 0;
94+
};
95+
96+
struct QueryActiveBuildsStore
97+
{
98+
inline static std::string operationName = "Querying active builds";
99+
100+
virtual std::vector<ActiveBuildInfo> queryActiveBuilds() = 0;
101+
};
102+
103+
} // namespace nix
104+
105+
JSON_IMPL(UserInfo)
106+
JSON_IMPL(ActiveBuild)
107+
JSON_IMPL(ActiveBuildInfo)
108+
JSON_IMPL(ActiveBuildInfo::ProcessInfo)

src/libstore/include/nix/store/local-store.hh

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "nix/store/pathlocks.hh"
77
#include "nix/store/store-api.hh"
88
#include "nix/store/indirect-root-store.hh"
9+
#include "nix/store/active-builds.hh"
910
#include "nix/util/sync.hh"
1011

1112
#include <chrono>
@@ -125,7 +126,10 @@ public:
125126
StoreReference getReference() const override;
126127
};
127128

128-
class LocalStore : public virtual IndirectRootStore, public virtual GcStore
129+
class LocalStore : public virtual IndirectRootStore,
130+
public virtual GcStore,
131+
public virtual TrackActiveBuildsStore,
132+
public virtual QueryActiveBuildsStore
129133
{
130134
public:
131135

@@ -457,6 +461,24 @@ private:
457461

458462
friend struct PathSubstitutionGoal;
459463
friend struct DerivationGoal;
464+
465+
private:
466+
467+
std::filesystem::path activeBuildsDir;
468+
469+
struct ActiveBuildFile
470+
{
471+
AutoCloseFD fd;
472+
AutoDelete del;
473+
};
474+
475+
Sync<std::unordered_map<uint64_t, ActiveBuildFile>> activeBuilds;
476+
477+
std::vector<ActiveBuildInfo> queryActiveBuilds() override;
478+
479+
BuildHandle buildStarted(const ActiveBuild & build) override;
480+
481+
void buildFinished(const BuildHandle & handle) override;
460482
};
461483

462484
} // namespace nix

src/libstore/include/nix/store/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ config_pub_h = configure_file(
1010
)
1111

1212
headers = [ config_pub_h ] + files(
13+
'active-builds.hh',
1314
'async-path-writer.hh',
1415
'aws-creds.hh',
1516
'binary-cache-store.hh',

src/libstore/include/nix/store/remote-store.hh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "nix/store/store-api.hh"
88
#include "nix/store/gc-store.hh"
99
#include "nix/store/log-store.hh"
10+
#include "nix/store/active-builds.hh"
1011

1112
namespace nix {
1213

@@ -36,7 +37,10 @@ struct RemoteStoreConfig : virtual StoreConfig
3637
* \todo RemoteStore is a misnomer - should be something like
3738
* DaemonStore.
3839
*/
39-
struct RemoteStore : public virtual Store, public virtual GcStore, public virtual LogStore
40+
struct RemoteStore : public virtual Store,
41+
public virtual GcStore,
42+
public virtual LogStore,
43+
public virtual QueryActiveBuildsStore
4044
{
4145
using Config = RemoteStoreConfig;
4246

@@ -143,6 +147,8 @@ struct RemoteStore : public virtual Store, public virtual GcStore, public virtua
143147

144148
void addBuildLog(const StorePath & drvPath, std::string_view log) override;
145149

150+
std::vector<ActiveBuildInfo> queryActiveBuilds() override;
151+
146152
std::optional<std::string> getVersion() override;
147153

148154
void connect() override;

src/libstore/include/nix/store/worker-protocol.hh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ struct WorkerProto
138138
using Feature = std::string;
139139
using FeatureSet = std::set<Feature, std::less<>>;
140140

141+
static constexpr std::string_view featureQueryActiveBuilds{"queryActiveBuilds"};
142+
141143
static const FeatureSet allFeatures;
142144
};
143145

@@ -186,6 +188,7 @@ enum struct WorkerProto::Op : uint64_t {
186188
AddBuildLog = 45,
187189
BuildPathsWithResults = 46,
188190
AddPermRoot = 47,
191+
QueryActiveBuilds = 48,
189192
};
190193

191194
struct WorkerProto::ClientHandshakeInfo

0 commit comments

Comments
 (0)