mirror of
https://github.com/NixOS/nix.git
synced 2025-11-27 04:30:59 +01:00
Result's from Mic92's framework 13th Gen Intel Core i7-1360P:
Before: 3595.92s user 183.01s system 1360% cpu 4:37.74 total
After: 3486.07s user 168.93s system 1354% cpu 4:29.79 total
I saw that boost/lexical_cast was costing about 100s in CPU time on our
compiles. We can fix this trivially by doing explicit template
instantiation in exactly one place and eliminating all other includes of
it, which is a code improvement anyway by hiding the boost.
Before:
```
lix/lix2 » ClangBuildAnalyzer --analyze buildtimeold.bin
Analyzing build trace from 'buildtimeold.bin'...
**** Time summary:
Compilation (551 times):
Parsing (frontend): 1465.3 s
Codegen & opts (backend): 1110.9 s
<snip>
**** Expensive headers:
178153 ms: ../src/libcmd/installable-value.hh (included 52 times, avg 3426 ms), included via:
40x: command.hh
5x: command-installable-value.hh
3x: installable-flake.hh
2x: <direct include>
2x: installable-attr-path.hh
176217 ms: ../src/libutil/error.hh (included 246 times, avg 716 ms), included via:
36x: command.hh installable-value.hh installables.hh derived-path.hh config.hh experimental-features.hh
12x: globals.hh config.hh experimental-features.hh
11x: file-system.hh file-descriptor.hh
6x: serialise.hh strings.hh
6x: <direct include>
6x: archive.hh serialise.hh strings.hh
...
173243 ms: ../src/libstore/store-api.hh (included 152 times, avg 1139 ms), included via:
55x: <direct include>
39x: command.hh installable-value.hh installables.hh
7x: libexpr.hh
4x: local-store.hh
4x: command-installable-value.hh installable-value.hh installables.hh
3x: binary-cache-store.hh
...
170482 ms: ../src/libutil/serialise.hh (included 201 times, avg 848 ms), included via:
37x: command.hh installable-value.hh installables.hh built-path.hh realisation.hh hash.hh
14x: store-api.hh nar-info.hh hash.hh
11x: <direct include>
7x: primops.hh eval.hh attr-set.hh nixexpr.hh value.hh source-path.hh archive.hh
7x: libexpr.hh value.hh source-path.hh archive.hh
6x: fetchers.hh hash.hh
...
169397 ms: ../src/libcmd/installables.hh (included 53 times, avg 3196 ms), included via:
40x: command.hh installable-value.hh
5x: command-installable-value.hh installable-value.hh
3x: installable-flake.hh installable-value.hh
2x: <direct include>
1x: installable-derived-path.hh
1x: installable-value.hh
...
159740 ms: ../src/libutil/strings.hh (included 221 times, avg 722 ms), included via:
37x: command.hh installable-value.hh installables.hh built-path.hh realisation.hh hash.hh serialise.hh
19x: <direct include>
14x: store-api.hh nar-info.hh hash.hh serialise.hh
11x: serialise.hh
7x: primops.hh eval.hh attr-set.hh nixexpr.hh value.hh source-path.hh archive.hh serialise.hh
7x: libexpr.hh value.hh source-path.hh archive.hh serialise.hh
...
156796 ms: ../src/libcmd/command.hh (included 51 times, avg 3074 ms), included via:
42x: <direct include>
7x: command-installable-value.hh
2x: installable-attr-path.hh
150392 ms: ../src/libutil/types.hh (included 251 times, avg 599 ms), included via:
36x: command.hh installable-value.hh installables.hh path.hh
11x: file-system.hh
10x: globals.hh
6x: fetchers.hh
6x: serialise.hh strings.hh error.hh
5x: archive.hh
...
133101 ms: /nix/store/644b90j1vms44nr18yw3520pzkrg4dd1-boost-1.81.0-dev/include/boost/lexical_cast.hpp (included 226 times, avg 588 ms), included via
:
37x: command.hh installable-value.hh installables.hh built-path.hh realisation.hh hash.hh serialise.hh strings.hh
19x: file-system.hh
11x: store-api.hh nar-info.hh hash.hh serialise.hh strings.hh
7x: primops.hh eval.hh attr-set.hh nixexpr.hh value.hh source-path.hh archive.hh serialise.hh strings.hh
7x: libexpr.hh value.hh source-path.hh archive.hh serialise.hh strings.hh
6x: eval.hh attr-set.hh nixexpr.hh value.hh source-path.hh archive.hh serialise.hh strings.hh
...
132887 ms: /nix/store/h2abv2l8irqj942i5rq9wbrj42kbsh5y-gcc-12.3.0/include/c++/12.3.0/memory (included 262 times, avg 507 ms), included via:
36x: command.hh installable-value.hh installables.hh path.hh types.hh ref.hh
16x: gtest.h
11x: file-system.hh types.hh ref.hh
10x: globals.hh types.hh ref.hh
10x: json.hpp
6x: serialise.hh
...
done in 0.6s.
```
After:
```
lix/lix2 » maintainers/buildtime_report.sh build
Processing all files and saving to '/home/jade/lix/lix2/maintainers/../buildtime.bin'...
done in 0.6s. Run 'ClangBuildAnalyzer --analyze /home/jade/lix/lix2/maintainers/../buildtime.bin' to analyze it.
Analyzing build trace from '/home/jade/lix/lix2/maintainers/../buildtime.bin'...
**** Time summary:
Compilation (551 times):
Parsing (frontend): 1302.1 s
Codegen & opts (backend): 956.3 s
<snip>
**** Expensive headers:
178145 ms: ../src/libutil/error.hh (included 246 times, avg 724 ms), included via:
36x: command.hh installable-value.hh installables.hh derived-path.hh config.hh experimental-features.hh
12x: globals.hh config.hh experimental-features.hh
11x: file-system.hh file-descriptor.hh
6x: <direct include>
6x: serialise.hh strings.hh
6x: fetchers.hh hash.hh serialise.hh strings.hh
...
154043 ms: ../src/libcmd/installable-value.hh (included 52 times, avg 2962 ms), included via:
40x: command.hh
5x: command-installable-value.hh
3x: installable-flake.hh
2x: <direct include>
2x: installable-attr-path.hh
153593 ms: ../src/libstore/store-api.hh (included 152 times, avg 1010 ms), included via:
55x: <direct include>
39x: command.hh installable-value.hh installables.hh
7x: libexpr.hh
4x: local-store.hh
4x: command-installable-value.hh installable-value.hh installables.hh
3x: binary-cache-store.hh
...
149948 ms: ../src/libutil/types.hh (included 251 times, avg 597 ms), included via:
36x: command.hh installable-value.hh installables.hh path.hh
11x: file-system.hh
10x: globals.hh
6x: fetchers.hh
6x: serialise.hh strings.hh error.hh
5x: archive.hh
...
144560 ms: ../src/libcmd/installables.hh (included 53 times, avg 2727 ms), included via:
40x: command.hh installable-value.hh
5x: command-installable-value.hh installable-value.hh
3x: installable-flake.hh installable-value.hh
2x: <direct include>
1x: installable-value.hh
1x: installable-derived-path.hh
...
136585 ms: ../src/libcmd/command.hh (included 51 times, avg 2678 ms), included via:
42x: <direct include>
7x: command-installable-value.hh
2x: installable-attr-path.hh
133394 ms: /nix/store/h2abv2l8irqj942i5rq9wbrj42kbsh5y-gcc-12.3.0/include/c++/12.3.0/memory (included 262 times, avg 509 ms), included via:
36x: command.hh installable-value.hh installables.hh path.hh types.hh ref.hh
16x: gtest.h
11x: file-system.hh types.hh ref.hh
10x: globals.hh types.hh ref.hh
10x: json.hpp
6x: serialise.hh
...
89315 ms: ../src/libstore/derived-path.hh (included 178 times, avg 501 ms), included via:
37x: command.hh installable-value.hh installables.hh
25x: store-api.hh realisation.hh
7x: primops.hh eval.hh attr-set.hh nixexpr.hh value.hh context.hh
6x: eval.hh attr-set.hh nixexpr.hh value.hh context.hh
6x: libexpr.hh value.hh context.hh
6x: shared.hh
...
87347 ms: /nix/store/h2abv2l8irqj942i5rq9wbrj42kbsh5y-gcc-12.3.0/include/c++/12.3.0/ostream (included 273 times, avg 319 ms), included via:
35x: command.hh installable-value.hh installables.hh path.hh types.hh ref.hh memory unique_ptr.h
12x: regex sstream istream
10x: file-system.hh types.hh ref.hh memory unique_ptr.h
10x: gtest.h memory unique_ptr.h
10x: globals.hh types.hh ref.hh memory unique_ptr.h
6x: fetchers.hh types.hh ref.hh memory unique_ptr.h
...
85249 ms: ../src/libutil/config.hh (included 213 times, avg 400 ms), included via:
37x: command.hh installable-value.hh installables.hh derived-path.hh
20x: globals.hh
20x: logging.hh
16x: store-api.hh logging.hh
6x: <direct include>
6x: eval.hh attr-set.hh nixexpr.hh value.hh context.hh derived-path.hh
...
done in 0.5s.
```
Adapated from 18aa3e1d57
556 lines
17 KiB
C++
556 lines
17 KiB
C++
#include "progress-bar.hh"
|
|
#include "terminal.hh"
|
|
#include "sync.hh"
|
|
#include "store-api.hh"
|
|
#include "names.hh"
|
|
|
|
#include <atomic>
|
|
#include <map>
|
|
#include <thread>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <chrono>
|
|
|
|
namespace nix {
|
|
|
|
static std::string_view getS(const std::vector<Logger::Field> & fields, size_t n)
|
|
{
|
|
assert(n < fields.size());
|
|
assert(fields[n].type == Logger::Field::tString);
|
|
return fields[n].s;
|
|
}
|
|
|
|
static uint64_t getI(const std::vector<Logger::Field> & fields, size_t n)
|
|
{
|
|
assert(n < fields.size());
|
|
assert(fields[n].type == Logger::Field::tInt);
|
|
return fields[n].i;
|
|
}
|
|
|
|
static std::string_view storePathToName(std::string_view path)
|
|
{
|
|
auto base = baseNameOf(path);
|
|
auto i = base.find('-');
|
|
return i == std::string::npos ? base.substr(0, 0) : base.substr(i + 1);
|
|
}
|
|
|
|
class ProgressBar : public Logger
|
|
{
|
|
private:
|
|
|
|
struct ActInfo
|
|
{
|
|
std::string s, lastLine, phase;
|
|
ActivityType type = actUnknown;
|
|
uint64_t done = 0;
|
|
uint64_t expected = 0;
|
|
uint64_t running = 0;
|
|
uint64_t failed = 0;
|
|
std::map<ActivityType, uint64_t> expectedByType;
|
|
bool visible = true;
|
|
ActivityId parent;
|
|
std::optional<std::string> name;
|
|
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
|
};
|
|
|
|
struct ActivitiesByType
|
|
{
|
|
std::map<ActivityId, std::list<ActInfo>::iterator> its;
|
|
uint64_t done = 0;
|
|
uint64_t expected = 0;
|
|
uint64_t failed = 0;
|
|
};
|
|
|
|
struct State
|
|
{
|
|
std::list<ActInfo> activities;
|
|
std::map<ActivityId, std::list<ActInfo>::iterator> its;
|
|
|
|
std::map<ActivityType, ActivitiesByType> activitiesByType;
|
|
|
|
uint64_t filesLinked = 0, bytesLinked = 0;
|
|
|
|
uint64_t corruptedPaths = 0, untrustedPaths = 0;
|
|
|
|
bool active = true;
|
|
bool paused = false;
|
|
bool haveUpdate = true;
|
|
};
|
|
|
|
Sync<State> state_;
|
|
|
|
std::thread updateThread;
|
|
|
|
std::condition_variable quitCV, updateCV;
|
|
|
|
bool printBuildLogs = false;
|
|
bool isTTY;
|
|
|
|
public:
|
|
|
|
ProgressBar(bool isTTY)
|
|
: isTTY(isTTY)
|
|
{
|
|
state_.lock()->active = isTTY;
|
|
updateThread = std::thread([&]() {
|
|
auto state(state_.lock());
|
|
auto nextWakeup = std::chrono::milliseconds::max();
|
|
while (state->active) {
|
|
if (!state->haveUpdate)
|
|
state.wait_for(updateCV, nextWakeup);
|
|
nextWakeup = draw(*state);
|
|
state.wait_for(quitCV, std::chrono::milliseconds(50));
|
|
}
|
|
});
|
|
}
|
|
|
|
~ProgressBar()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
/* Called by destructor, can't be overridden */
|
|
void stop() override final
|
|
{
|
|
{
|
|
auto state(state_.lock());
|
|
if (!state->active) return;
|
|
state->active = false;
|
|
writeToStderr("\r\e[K");
|
|
updateCV.notify_one();
|
|
quitCV.notify_one();
|
|
}
|
|
updateThread.join();
|
|
}
|
|
|
|
void pause() override {
|
|
auto state (state_.lock());
|
|
state->paused = true;
|
|
if (state->active)
|
|
writeToStderr("\r\e[K");
|
|
}
|
|
|
|
void resume() override {
|
|
auto state (state_.lock());
|
|
state->paused = false;
|
|
if (state->active)
|
|
writeToStderr("\r\e[K");
|
|
state->haveUpdate = true;
|
|
updateCV.notify_one();
|
|
}
|
|
|
|
bool isVerbose() override
|
|
{
|
|
return printBuildLogs;
|
|
}
|
|
|
|
void log(Verbosity lvl, std::string_view s) override
|
|
{
|
|
if (lvl > verbosity) return;
|
|
auto state(state_.lock());
|
|
log(*state, lvl, s);
|
|
}
|
|
|
|
void logEI(const ErrorInfo & ei) override
|
|
{
|
|
auto state(state_.lock());
|
|
|
|
std::stringstream oss;
|
|
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
|
|
|
|
log(*state, ei.level, oss.str());
|
|
}
|
|
|
|
void log(State & state, Verbosity lvl, std::string_view s)
|
|
{
|
|
if (state.active) {
|
|
writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n");
|
|
draw(state);
|
|
} else {
|
|
writeToStderr(filterANSIEscapes(s, !isTTY) + "\n");
|
|
}
|
|
}
|
|
|
|
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
|
const std::string & s, const Fields & fields, ActivityId parent) override
|
|
{
|
|
auto state(state_.lock());
|
|
|
|
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
|
|
log(*state, lvl, s + "...");
|
|
|
|
state->activities.emplace_back(ActInfo {
|
|
.s = s,
|
|
.type = type,
|
|
.parent = parent,
|
|
.startTime = std::chrono::steady_clock::now()
|
|
});
|
|
auto i = std::prev(state->activities.end());
|
|
state->its.emplace(act, i);
|
|
state->activitiesByType[type].its.emplace(act, i);
|
|
|
|
if (type == actBuild) {
|
|
std::string name(storePathToName(getS(fields, 0)));
|
|
if (hasSuffix(name, ".drv"))
|
|
name = name.substr(0, name.size() - 4);
|
|
i->s = fmt("building " ANSI_BOLD "%s" ANSI_NORMAL, name);
|
|
auto machineName = getS(fields, 1);
|
|
if (machineName != "")
|
|
i->s += fmt(" on " ANSI_BOLD "%s" ANSI_NORMAL, machineName);
|
|
|
|
// Used to be curRound and nrRounds, but the
|
|
// implementation was broken for a long time.
|
|
if (getI(fields, 2) != 1 || getI(fields, 3) != 1) {
|
|
throw Error("log message indicated repeating builds, but this is not currently implemented");
|
|
}
|
|
i->name = DrvName(name).name;
|
|
}
|
|
|
|
if (type == actSubstitute) {
|
|
auto name = storePathToName(getS(fields, 0));
|
|
auto sub = getS(fields, 1);
|
|
i->s = fmt(
|
|
hasPrefix(sub, "local")
|
|
? "copying " ANSI_BOLD "%s" ANSI_NORMAL " from %s"
|
|
: "fetching " ANSI_BOLD "%s" ANSI_NORMAL " from %s",
|
|
name, sub);
|
|
}
|
|
|
|
if (type == actPostBuildHook) {
|
|
auto name = storePathToName(getS(fields, 0));
|
|
if (hasSuffix(name, ".drv"))
|
|
name = name.substr(0, name.size() - 4);
|
|
i->s = fmt("post-build " ANSI_BOLD "%s" ANSI_NORMAL, name);
|
|
i->name = DrvName(name).name;
|
|
}
|
|
|
|
if (type == actQueryPathInfo) {
|
|
auto name = storePathToName(getS(fields, 0));
|
|
i->s = fmt("querying " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
|
|
}
|
|
|
|
if ((type == actFileTransfer && hasAncestor(*state, actCopyPath, parent))
|
|
|| (type == actFileTransfer && hasAncestor(*state, actQueryPathInfo, parent))
|
|
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
|
|
i->visible = false;
|
|
|
|
update(*state);
|
|
}
|
|
|
|
/* Check whether an activity has an ancestore with the specified
|
|
type. */
|
|
bool hasAncestor(State & state, ActivityType type, ActivityId act)
|
|
{
|
|
while (act != 0) {
|
|
auto i = state.its.find(act);
|
|
if (i == state.its.end()) break;
|
|
if (i->second->type == type) return true;
|
|
act = i->second->parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void stopActivity(ActivityId act) override
|
|
{
|
|
auto state(state_.lock());
|
|
|
|
auto i = state->its.find(act);
|
|
if (i != state->its.end()) {
|
|
|
|
auto & actByType = state->activitiesByType[i->second->type];
|
|
actByType.done += i->second->done;
|
|
actByType.failed += i->second->failed;
|
|
|
|
for (auto & j : i->second->expectedByType)
|
|
state->activitiesByType[j.first].expected -= j.second;
|
|
|
|
actByType.its.erase(act);
|
|
state->activities.erase(i->second);
|
|
state->its.erase(i);
|
|
}
|
|
|
|
update(*state);
|
|
}
|
|
|
|
void result(ActivityId act, ResultType type, const std::vector<Field> & fields) override
|
|
{
|
|
auto state(state_.lock());
|
|
|
|
if (type == resFileLinked) {
|
|
state->filesLinked++;
|
|
state->bytesLinked += getI(fields, 0);
|
|
update(*state);
|
|
}
|
|
|
|
else if (type == resBuildLogLine || type == resPostBuildLogLine) {
|
|
auto lastLine = chomp(getS(fields, 0));
|
|
if (!lastLine.empty()) {
|
|
auto i = state->its.find(act);
|
|
assert(i != state->its.end());
|
|
ActInfo info = *i->second;
|
|
if (printBuildLogs) {
|
|
auto suffix = "> ";
|
|
if (type == resPostBuildLogLine) {
|
|
suffix = " (post)> ";
|
|
}
|
|
log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
|
|
} else {
|
|
state->activities.erase(i->second);
|
|
info.lastLine = lastLine;
|
|
state->activities.emplace_back(info);
|
|
i->second = std::prev(state->activities.end());
|
|
update(*state);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (type == resUntrustedPath) {
|
|
state->untrustedPaths++;
|
|
update(*state);
|
|
}
|
|
|
|
else if (type == resCorruptedPath) {
|
|
state->corruptedPaths++;
|
|
update(*state);
|
|
}
|
|
|
|
else if (type == resSetPhase) {
|
|
auto i = state->its.find(act);
|
|
assert(i != state->its.end());
|
|
i->second->phase = getS(fields, 0);
|
|
update(*state);
|
|
}
|
|
|
|
else if (type == resProgress) {
|
|
auto i = state->its.find(act);
|
|
assert(i != state->its.end());
|
|
ActInfo & actInfo = *i->second;
|
|
actInfo.done = getI(fields, 0);
|
|
actInfo.expected = getI(fields, 1);
|
|
actInfo.running = getI(fields, 2);
|
|
actInfo.failed = getI(fields, 3);
|
|
update(*state);
|
|
}
|
|
|
|
else if (type == resSetExpected) {
|
|
auto i = state->its.find(act);
|
|
assert(i != state->its.end());
|
|
ActInfo & actInfo = *i->second;
|
|
auto type = (ActivityType) getI(fields, 0);
|
|
auto & j = actInfo.expectedByType[type];
|
|
state->activitiesByType[type].expected -= j;
|
|
j = getI(fields, 1);
|
|
state->activitiesByType[type].expected += j;
|
|
update(*state);
|
|
}
|
|
|
|
else if (type == resFetchStatus) {
|
|
auto i = state->its.find(act);
|
|
assert(i != state->its.end());
|
|
ActInfo & actInfo = *i->second;
|
|
actInfo.lastLine = getS(fields, 0);
|
|
update(*state);
|
|
}
|
|
}
|
|
|
|
void update(State & state)
|
|
{
|
|
state.haveUpdate = true;
|
|
updateCV.notify_one();
|
|
}
|
|
|
|
std::chrono::milliseconds draw(State & state)
|
|
{
|
|
auto nextWakeup = std::chrono::milliseconds::max();
|
|
|
|
state.haveUpdate = false;
|
|
if (state.paused || !state.active) return nextWakeup;
|
|
|
|
std::string line;
|
|
|
|
std::string status = getStatus(state);
|
|
if (!status.empty()) {
|
|
line += '[';
|
|
line += status;
|
|
line += "]";
|
|
}
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
if (!state.activities.empty()) {
|
|
if (!status.empty()) line += " ";
|
|
auto i = state.activities.rbegin();
|
|
|
|
while (i != state.activities.rend()) {
|
|
if (i->visible && (!i->s.empty() || !i->lastLine.empty())) {
|
|
/* Don't show activities until some time has
|
|
passed, to avoid displaying very short
|
|
activities. */
|
|
auto delay = std::chrono::milliseconds(10);
|
|
if (i->startTime + delay < now)
|
|
break;
|
|
else
|
|
nextWakeup = std::min(nextWakeup, std::chrono::duration_cast<std::chrono::milliseconds>(delay - (now - i->startTime)));
|
|
}
|
|
++i;
|
|
}
|
|
|
|
if (i != state.activities.rend()) {
|
|
line += i->s;
|
|
if (!i->phase.empty()) {
|
|
line += " (";
|
|
line += i->phase;
|
|
line += ")";
|
|
}
|
|
if (!i->lastLine.empty()) {
|
|
if (!i->s.empty()) line += ": ";
|
|
line += i->lastLine;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto width = getWindowSize().second;
|
|
if (width <= 0) width = std::numeric_limits<decltype(width)>::max();
|
|
|
|
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
|
|
|
|
return nextWakeup;
|
|
}
|
|
|
|
std::string getStatus(State & state)
|
|
{
|
|
auto MiB = 1024.0 * 1024.0;
|
|
|
|
std::string res;
|
|
|
|
auto renderActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
|
|
auto & act = state.activitiesByType[type];
|
|
uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
|
|
for (auto & j : act.its) {
|
|
done += j.second->done;
|
|
expected += j.second->expected;
|
|
running += j.second->running;
|
|
failed += j.second->failed;
|
|
}
|
|
|
|
expected = std::max(expected, act.expected);
|
|
|
|
std::string s;
|
|
|
|
if (running || done || expected || failed) {
|
|
if (running)
|
|
if (expected != 0)
|
|
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
|
|
running / unit, done / unit, expected / unit);
|
|
else
|
|
s = fmt(ANSI_BLUE + numberFmt + ANSI_NORMAL "/" ANSI_GREEN + numberFmt + ANSI_NORMAL,
|
|
running / unit, done / unit);
|
|
else if (expected != done)
|
|
if (expected != 0)
|
|
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL "/" + numberFmt,
|
|
done / unit, expected / unit);
|
|
else
|
|
s = fmt(ANSI_GREEN + numberFmt + ANSI_NORMAL, done / unit);
|
|
else
|
|
s = fmt(done ? ANSI_GREEN + numberFmt + ANSI_NORMAL : numberFmt, done / unit);
|
|
s = fmt(itemFmt, s);
|
|
|
|
if (failed)
|
|
s += fmt(" (" ANSI_RED "%d failed" ANSI_NORMAL ")", failed / unit);
|
|
}
|
|
|
|
return s;
|
|
};
|
|
|
|
auto showActivity = [&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
|
|
auto s = renderActivity(type, itemFmt, numberFmt, unit);
|
|
if (s.empty()) return;
|
|
if (!res.empty()) res += ", ";
|
|
res += s;
|
|
};
|
|
|
|
showActivity(actBuilds, "%s built");
|
|
|
|
auto s1 = renderActivity(actCopyPaths, "%s copied");
|
|
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
|
|
|
|
if (!s1.empty() || !s2.empty()) {
|
|
if (!res.empty()) res += ", ";
|
|
if (s1.empty()) res += "0 copied"; else res += s1;
|
|
if (!s2.empty()) { res += " ("; res += s2; res += ')'; }
|
|
}
|
|
|
|
showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
|
|
|
|
{
|
|
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
|
|
if (s != "") {
|
|
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
|
|
if (!res.empty()) res += ", ";
|
|
res += s;
|
|
}
|
|
}
|
|
|
|
// FIXME: don't show "done" paths in green.
|
|
showActivity(actVerifyPaths, "%s paths verified");
|
|
|
|
if (state.corruptedPaths) {
|
|
if (!res.empty()) res += ", ";
|
|
res += fmt(ANSI_RED "%d corrupted" ANSI_NORMAL, state.corruptedPaths);
|
|
}
|
|
|
|
if (state.untrustedPaths) {
|
|
if (!res.empty()) res += ", ";
|
|
res += fmt(ANSI_RED "%d untrusted" ANSI_NORMAL, state.untrustedPaths);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void writeToStdout(std::string_view s) override
|
|
{
|
|
auto state(state_.lock());
|
|
if (state->active) {
|
|
std::cerr << "\r\e[K";
|
|
Logger::writeToStdout(s);
|
|
draw(*state);
|
|
} else {
|
|
Logger::writeToStdout(s);
|
|
}
|
|
}
|
|
|
|
std::optional<char> ask(std::string_view msg) override
|
|
{
|
|
auto state(state_.lock());
|
|
if (!state->active) return {};
|
|
std::cerr << fmt("\r\e[K%s ", msg);
|
|
auto s = trim(readLine(STDIN_FILENO));
|
|
if (s.size() != 1) return {};
|
|
draw(*state);
|
|
return s[0];
|
|
}
|
|
|
|
void setPrintBuildLogs(bool printBuildLogs) override
|
|
{
|
|
this->printBuildLogs = printBuildLogs;
|
|
}
|
|
};
|
|
|
|
Logger * makeProgressBar()
|
|
{
|
|
return new ProgressBar(isTTY());
|
|
}
|
|
|
|
void startProgressBar()
|
|
{
|
|
logger = makeProgressBar();
|
|
}
|
|
|
|
void stopProgressBar()
|
|
{
|
|
auto progressBar = dynamic_cast<ProgressBar *>(logger);
|
|
if (progressBar) progressBar->stop();
|
|
|
|
}
|
|
|
|
}
|