Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 62 additions & 5 deletions src/libmain/progress-bar.cc
Original file line number Diff line number Diff line change
Expand Up @@ -467,8 +467,6 @@ class ProgressBar : public Logger

std::string getStatus(State & state)
{
auto MiB = 1024.0 * 1024.0;

std::string res;

auto renderActivity =
Expand Down Expand Up @@ -516,6 +514,65 @@ class ProgressBar : public Logger
return s;
};

auto renderSizeActivity = [&](ActivityType type, const std::string & itemFmt = "%s") {
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::optional<SizeUnit> commonUnit;
std::string s;

if (running || done || expected || failed) {
if (running)
if (expected != 0) {
commonUnit = getCommonSizeUnit({(int64_t) running, (int64_t) done, (int64_t) expected});
s =
fmt(ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL "/%s",
commonUnit ? renderSizeWithoutUnit(running, *commonUnit) : renderSize(running),
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done),
commonUnit ? renderSizeWithoutUnit(expected, *commonUnit) : renderSize(expected));
} else {
commonUnit = getCommonSizeUnit({(int64_t) running, (int64_t) done});
s =
fmt(ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL,
commonUnit ? renderSizeWithoutUnit(running, *commonUnit) : renderSize(running),
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done));
}
else if (expected != done)
if (expected != 0) {
commonUnit = getCommonSizeUnit({(int64_t) done, (int64_t) expected});
s =
fmt(ANSI_GREEN "%s" ANSI_NORMAL "/%s",
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done),
commonUnit ? renderSizeWithoutUnit(expected, *commonUnit) : renderSize(expected));
} else {
commonUnit = getSizeUnit(done);
s = fmt(ANSI_GREEN "%s" ANSI_NORMAL, renderSizeWithoutUnit(done, *commonUnit));
}
else {
commonUnit = getSizeUnit(done);
s = fmt(done ? ANSI_GREEN "%s" ANSI_NORMAL : "%s", renderSizeWithoutUnit(done, *commonUnit));
}

if (commonUnit)
s = fmt("%s %siB", s, getSizeUnitSuffix(*commonUnit));

s = fmt(itemFmt, s);

if (failed)
s += fmt(" (" ANSI_RED "%s failed" ANSI_NORMAL ")", renderSize(failed));
}

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);
Expand All @@ -529,7 +586,7 @@ class ProgressBar : public Logger
showActivity(actBuilds, "%s built");

auto s1 = renderActivity(actCopyPaths, "%s copied");
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
auto s2 = renderSizeActivity(actCopyPath);

if (!s1.empty() || !s2.empty()) {
if (!res.empty())
Expand All @@ -545,12 +602,12 @@ class ProgressBar : public Logger
}
}

showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
renderSizeActivity(actFileTransfer, "%s DL");

{
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
if (s != "") {
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
s += fmt(", %s / %d inodes freed", renderSize(state.bytesLinked), state.filesLinked);
if (!res.empty())
res += ", ";
res += s;
Expand Down
53 changes: 53 additions & 0 deletions src/libutil-tests/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,59 @@ TEST(string2Int, trivialConversions)
ASSERT_EQ(string2Int<int>("-100"), -100);
}

/* ----------------------------------------------------------------------------
* getSizeUnit
* --------------------------------------------------------------------------*/

TEST(getSizeUnit, misc)
{
ASSERT_EQ(getSizeUnit(0), SizeUnit::Base);
ASSERT_EQ(getSizeUnit(100), SizeUnit::Base);
ASSERT_EQ(getSizeUnit(100), SizeUnit::Base);
ASSERT_EQ(getSizeUnit(972), SizeUnit::Base);
ASSERT_EQ(getSizeUnit(973), SizeUnit::Base); // FIXME: should round down
ASSERT_EQ(getSizeUnit(1024), SizeUnit::Base);
ASSERT_EQ(getSizeUnit(-1024), SizeUnit::Base);
ASSERT_EQ(getSizeUnit(1024 * 1024), SizeUnit::Kilo);
ASSERT_EQ(getSizeUnit(1100 * 1024), SizeUnit::Mega);
ASSERT_EQ(getSizeUnit(2ULL * 1024 * 1024 * 1024), SizeUnit::Giga);
ASSERT_EQ(getSizeUnit(2100ULL * 1024 * 1024 * 1024), SizeUnit::Tera);
}

/* ----------------------------------------------------------------------------
* getCommonSizeUnit
* --------------------------------------------------------------------------*/

TEST(getCommonSizeUnit, misc)
{
ASSERT_EQ(getCommonSizeUnit({0}), SizeUnit::Base);
ASSERT_EQ(getCommonSizeUnit({0, 100}), SizeUnit::Base);
ASSERT_EQ(getCommonSizeUnit({100, 0}), SizeUnit::Base);
ASSERT_EQ(getCommonSizeUnit({100, 1024 * 1024}), std::nullopt);
ASSERT_EQ(getCommonSizeUnit({1024 * 1024, 100}), std::nullopt);
ASSERT_EQ(getCommonSizeUnit({1024 * 1024, 1024 * 1024}), SizeUnit::Kilo);
ASSERT_EQ(getCommonSizeUnit({2100ULL * 1024 * 1024 * 1024, 2100ULL * 1024 * 1024 * 1024}), SizeUnit::Tera);
}

/* ----------------------------------------------------------------------------
* renderSizeWithoutUnit
* --------------------------------------------------------------------------*/

TEST(renderSizeWithoutUnit, misc)
{
ASSERT_EQ(renderSizeWithoutUnit(0, SizeUnit::Base, true), " 0.0");
ASSERT_EQ(renderSizeWithoutUnit(100, SizeUnit::Base, true), " 0.1");
ASSERT_EQ(renderSizeWithoutUnit(100, SizeUnit::Base), "0.1");
ASSERT_EQ(renderSizeWithoutUnit(972, SizeUnit::Base, true), " 0.9");
ASSERT_EQ(renderSizeWithoutUnit(973, SizeUnit::Base, true), " 1.0"); // FIXME: should round down
ASSERT_EQ(renderSizeWithoutUnit(1024, SizeUnit::Base, true), " 1.0");
ASSERT_EQ(renderSizeWithoutUnit(-1024, SizeUnit::Base, true), " -1.0");
ASSERT_EQ(renderSizeWithoutUnit(1024 * 1024, SizeUnit::Kilo, true), "1024.0");
ASSERT_EQ(renderSizeWithoutUnit(1100 * 1024, SizeUnit::Mega, true), " 1.1");
ASSERT_EQ(renderSizeWithoutUnit(2ULL * 1024 * 1024 * 1024, SizeUnit::Giga, true), " 2.0");
ASSERT_EQ(renderSizeWithoutUnit(2100ULL * 1024 * 1024 * 1024, SizeUnit::Tera, true), " 2.1");
}

/* ----------------------------------------------------------------------------
* renderSize
* --------------------------------------------------------------------------*/
Expand Down
36 changes: 36 additions & 0 deletions src/libutil/include/nix/util/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,42 @@ N string2IntWithUnitPrefix(std::string_view s)
throw UsageError("'%s' is not an integer", s);
}

// Base also uses 'K', because it should also displayed as KiB => 100 Bytes => 0.1 KiB
#define NIX_UTIL_SIZE_UNITS \
NIX_UTIL_DEFINE_SIZE_UNIT(Base, 'K') \
NIX_UTIL_DEFINE_SIZE_UNIT(Kilo, 'K') \
NIX_UTIL_DEFINE_SIZE_UNIT(Mega, 'M') \
NIX_UTIL_DEFINE_SIZE_UNIT(Giga, 'G') \
NIX_UTIL_DEFINE_SIZE_UNIT(Tera, 'T') \
NIX_UTIL_DEFINE_SIZE_UNIT(Peta, 'P') \
NIX_UTIL_DEFINE_SIZE_UNIT(Exa, 'E') \
NIX_UTIL_DEFINE_SIZE_UNIT(Zetta, 'Z') \
NIX_UTIL_DEFINE_SIZE_UNIT(Yotta, 'Y')

enum class SizeUnit {
#define NIX_UTIL_DEFINE_SIZE_UNIT(name, suffix) name,
NIX_UTIL_SIZE_UNITS
#undef NIX_UTIL_DEFINE_SIZE_UNIT
};

constexpr inline auto sizeUnits = std::to_array<SizeUnit>({
#define NIX_UTIL_DEFINE_SIZE_UNIT(name, suffix) SizeUnit::name,
NIX_UTIL_SIZE_UNITS
#undef NIX_UTIL_DEFINE_SIZE_UNIT
});

SizeUnit getSizeUnit(int64_t value);

/**
* Returns the unit if all values would be rendered using the same unit
* otherwise returns `std::nullopt`.
*/
std::optional<SizeUnit> getCommonSizeUnit(std::initializer_list<int64_t> values);

std::string renderSizeWithoutUnit(int64_t value, SizeUnit unit, bool align = false);

char getSizeUnitSuffix(SizeUnit unit);

/**
* Pretty-print a byte value, e.g. 12433615056 is rendered as `11.6
* GiB`. If `align` is set, the number will be right-justified by
Expand Down
63 changes: 54 additions & 9 deletions src/libutil/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,62 @@ std::optional<N> string2Float(const std::string_view s)
template std::optional<double> string2Float<double>(const std::string_view s);
template std::optional<float> string2Float<float>(const std::string_view s);

std::string renderSize(int64_t value, bool align)
static const int64_t conversionNumber = 1024;

SizeUnit getSizeUnit(int64_t value)
{
auto unit = sizeUnits.begin();
uint64_t absValue = std::abs(value);
while (absValue > conversionNumber && unit < sizeUnits.end()) {
unit++;
absValue /= conversionNumber;
}
return *unit;
}

std::optional<SizeUnit> getCommonSizeUnit(std::initializer_list<int64_t> values)
{
assert(values.size() > 0);

auto it = values.begin();
SizeUnit unit = getSizeUnit(*it);
it++;

for (; it != values.end(); it++) {
if (unit != getSizeUnit(*it)) {
return std::nullopt;
}
}

return unit;
}

std::string renderSizeWithoutUnit(int64_t value, SizeUnit unit, bool align)
{
// bytes should also displayed as KiB => 100 Bytes => 0.1 KiB
auto power = std::max<std::underlying_type_t<SizeUnit>>(1, std::to_underlying(unit));
double denominator = std::pow(conversionNumber, power);
double result = (double) value / denominator;
return fmt(align ? "%6.1f" : "%.1f", result);
}

char getSizeUnitSuffix(SizeUnit unit)
{
static const std::array<char, 9> prefixes{{'K', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}};
size_t power = 0;
double abs_value = std::abs(value);
while (abs_value > 1024 && power < prefixes.size()) {
++power;
abs_value /= 1024;
switch (unit) {
#define NIX_UTIL_DEFINE_SIZE_UNIT(name, suffix) \
case SizeUnit::name: \
return suffix;
NIX_UTIL_SIZE_UNITS
#undef NIX_UTIL_DEFINE_SIZE_UNIT
}
double res = (double) value / std::pow(1024.0, power);
return fmt(align ? "%6.1f %ciB" : "%.1f %ciB", power == 0 ? res / 1024 : res, prefixes.at(power));

assert(false);
}

std::string renderSize(int64_t value, bool align)
{
SizeUnit unit = getSizeUnit(value);
return fmt("%s %ciB", renderSizeWithoutUnit(value, unit, align), getSizeUnitSuffix(unit));
}

bool hasPrefix(std::string_view s, std::string_view prefix)
Expand Down
Loading