Skip to content

Commit db9a922

Browse files
committed
progress-bar: use dynamic size units
1 parent da637a0 commit db9a922

File tree

4 files changed

+204
-14
lines changed

4 files changed

+204
-14
lines changed

src/libmain/progress-bar.cc

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,6 @@ class ProgressBar : public Logger
467467

468468
std::string getStatus(State & state)
469469
{
470-
auto MiB = 1024.0 * 1024.0;
471-
472470
std::string res;
473471

474472
auto renderActivity =
@@ -516,6 +514,65 @@ class ProgressBar : public Logger
516514
return s;
517515
};
518516

517+
auto renderSizeActivity = [&](ActivityType type, const std::string & itemFmt = "%s") {
518+
auto & act = state.activitiesByType[type];
519+
uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
520+
for (auto & j : act.its) {
521+
done += j.second->done;
522+
expected += j.second->expected;
523+
running += j.second->running;
524+
failed += j.second->failed;
525+
}
526+
527+
expected = std::max(expected, act.expected);
528+
529+
std::optional<SizeUnit> commonUnit;
530+
std::string s;
531+
532+
if (running || done || expected || failed) {
533+
if (running)
534+
if (expected != 0) {
535+
commonUnit = getCommonSizeUnit({(int64_t) running, (int64_t) done, (int64_t) expected});
536+
s =
537+
fmt(ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL "/%s",
538+
commonUnit ? renderSizeWithoutUnit(running, *commonUnit) : renderSize(running),
539+
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done),
540+
commonUnit ? renderSizeWithoutUnit(expected, *commonUnit) : renderSize(expected));
541+
} else {
542+
commonUnit = getCommonSizeUnit({(int64_t) running, (int64_t) done});
543+
s =
544+
fmt(ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL,
545+
commonUnit ? renderSizeWithoutUnit(running, *commonUnit) : renderSize(running),
546+
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done));
547+
}
548+
else if (expected != done)
549+
if (expected != 0) {
550+
commonUnit = getCommonSizeUnit({(int64_t) done, (int64_t) expected});
551+
s =
552+
fmt(ANSI_GREEN "%s" ANSI_NORMAL "/%s",
553+
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done),
554+
commonUnit ? renderSizeWithoutUnit(expected, *commonUnit) : renderSize(expected));
555+
} else {
556+
commonUnit = getSizeUnit(done);
557+
s = fmt(ANSI_GREEN "%s" ANSI_NORMAL, renderSizeWithoutUnit(done, *commonUnit));
558+
}
559+
else {
560+
commonUnit = getSizeUnit(done);
561+
s = fmt(done ? ANSI_GREEN "%s" ANSI_NORMAL : "%s", renderSizeWithoutUnit(done, *commonUnit));
562+
}
563+
564+
if (commonUnit)
565+
s = fmt("%s %siB", s, getSizeUnitSuffix(*commonUnit));
566+
567+
s = fmt(itemFmt, s);
568+
569+
if (failed)
570+
s += fmt(" (" ANSI_RED "%s failed" ANSI_NORMAL ")", renderSize(failed));
571+
}
572+
573+
return s;
574+
};
575+
519576
auto showActivity =
520577
[&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
521578
auto s = renderActivity(type, itemFmt, numberFmt, unit);
@@ -529,7 +586,7 @@ class ProgressBar : public Logger
529586
showActivity(actBuilds, "%s built");
530587

531588
auto s1 = renderActivity(actCopyPaths, "%s copied");
532-
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
589+
auto s2 = renderSizeActivity(actCopyPath);
533590

534591
if (!s1.empty() || !s2.empty()) {
535592
if (!res.empty())
@@ -545,12 +602,12 @@ class ProgressBar : public Logger
545602
}
546603
}
547604

548-
showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
605+
renderSizeActivity(actFileTransfer, "%s DL");
549606

550607
{
551608
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
552609
if (s != "") {
553-
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
610+
s += fmt(", %s / %d inodes freed", renderSize(state.bytesLinked), state.filesLinked);
554611
if (!res.empty())
555612
res += ", ";
556613
res += s;

src/libutil-tests/util.cc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,59 @@ TEST(string2Int, trivialConversions)
146146
ASSERT_EQ(string2Int<int>("-100"), -100);
147147
}
148148

149+
/* ----------------------------------------------------------------------------
150+
* getSizeUnit
151+
* --------------------------------------------------------------------------*/
152+
153+
TEST(getSizeUnit, misc)
154+
{
155+
ASSERT_EQ(getSizeUnit(0), SizeUnit::Base);
156+
ASSERT_EQ(getSizeUnit(100), SizeUnit::Base);
157+
ASSERT_EQ(getSizeUnit(100), SizeUnit::Base);
158+
ASSERT_EQ(getSizeUnit(972), SizeUnit::Base);
159+
ASSERT_EQ(getSizeUnit(973), SizeUnit::Base); // FIXME: should round down
160+
ASSERT_EQ(getSizeUnit(1024), SizeUnit::Base);
161+
ASSERT_EQ(getSizeUnit(-1024), SizeUnit::Base);
162+
ASSERT_EQ(getSizeUnit(1024 * 1024), SizeUnit::Kilo);
163+
ASSERT_EQ(getSizeUnit(1100 * 1024), SizeUnit::Mega);
164+
ASSERT_EQ(getSizeUnit(2ULL * 1024 * 1024 * 1024), SizeUnit::Giga);
165+
ASSERT_EQ(getSizeUnit(2100ULL * 1024 * 1024 * 1024), SizeUnit::Tera);
166+
}
167+
168+
/* ----------------------------------------------------------------------------
169+
* getCommonSizeUnit
170+
* --------------------------------------------------------------------------*/
171+
172+
TEST(getCommonSizeUnit, misc)
173+
{
174+
ASSERT_EQ(*getCommonSizeUnit({0}), SizeUnit::Base);
175+
ASSERT_EQ(*getCommonSizeUnit({0, 100}), SizeUnit::Base);
176+
ASSERT_EQ(*getCommonSizeUnit({100, 0}), SizeUnit::Base);
177+
ASSERT_EQ(getCommonSizeUnit({100, 1024 * 1024}), std::nullopt);
178+
ASSERT_EQ(getCommonSizeUnit({1024 * 1024, 100}), std::nullopt);
179+
ASSERT_EQ(*getCommonSizeUnit({1024 * 1024, 1024 * 1024}), SizeUnit::Kilo);
180+
ASSERT_EQ(*getCommonSizeUnit({2100ULL * 1024 * 1024 * 1024, 2100ULL * 1024 * 1024 * 1024}), SizeUnit::Tera);
181+
}
182+
183+
/* ----------------------------------------------------------------------------
184+
* renderSizeWithoutUnit
185+
* --------------------------------------------------------------------------*/
186+
187+
TEST(renderSizeWithoutUnit, misc)
188+
{
189+
ASSERT_EQ(renderSizeWithoutUnit(0, SizeUnit::Base, true), " 0.0");
190+
ASSERT_EQ(renderSizeWithoutUnit(100, SizeUnit::Base, true), " 0.1");
191+
ASSERT_EQ(renderSizeWithoutUnit(100, SizeUnit::Base), "0.1");
192+
ASSERT_EQ(renderSizeWithoutUnit(972, SizeUnit::Base, true), " 0.9");
193+
ASSERT_EQ(renderSizeWithoutUnit(973, SizeUnit::Base, true), " 1.0"); // FIXME: should round down
194+
ASSERT_EQ(renderSizeWithoutUnit(1024, SizeUnit::Base, true), " 1.0");
195+
ASSERT_EQ(renderSizeWithoutUnit(-1024, SizeUnit::Base, true), " -1.0");
196+
ASSERT_EQ(renderSizeWithoutUnit(1024 * 1024, SizeUnit::Kilo, true), "1024.0");
197+
ASSERT_EQ(renderSizeWithoutUnit(1100 * 1024, SizeUnit::Mega, true), " 1.1");
198+
ASSERT_EQ(renderSizeWithoutUnit(2ULL * 1024 * 1024 * 1024, SizeUnit::Giga, true), " 2.0");
199+
ASSERT_EQ(renderSizeWithoutUnit(2100ULL * 1024 * 1024 * 1024, SizeUnit::Tera, true), " 2.1");
200+
}
201+
149202
/* ----------------------------------------------------------------------------
150203
* renderSize
151204
* --------------------------------------------------------------------------*/

src/libutil/include/nix/util/util.hh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,42 @@ N string2IntWithUnitPrefix(std::string_view s)
9999
throw UsageError("'%s' is not an integer", s);
100100
}
101101

102+
// Base also uses 'K', because it should also displayed as KiB => 100 Bytes => 0.1 KiB
103+
#define SIZE_UNITS \
104+
X(Base, 'K') \
105+
X(Kilo, 'K') \
106+
X(Mega, 'M') \
107+
X(Giga, 'G') \
108+
X(Tera, 'T') \
109+
X(Peta, 'P') \
110+
X(Exa, 'E') \
111+
X(Zetta, 'Z') \
112+
X(Yotta, 'Y')
113+
114+
enum class SizeUnit {
115+
#define X(name, suffix) name,
116+
SIZE_UNITS
117+
#undef X
118+
};
119+
120+
constexpr std::array<SizeUnit, 9> sizeUnits{{
121+
#define X(name, suffix) SizeUnit::name,
122+
SIZE_UNITS
123+
#undef X
124+
}};
125+
126+
SizeUnit getSizeUnit(int64_t value);
127+
128+
/**
129+
* Returns the unit if all values would be rendered using the same unit
130+
* otherwise returns `std::nullopt`.
131+
*/
132+
std::optional<SizeUnit> getCommonSizeUnit(std::initializer_list<int64_t> values);
133+
134+
std::string renderSizeWithoutUnit(int64_t value, SizeUnit unit, bool align = false);
135+
136+
char getSizeUnitSuffix(SizeUnit unit);
137+
102138
/**
103139
* Pretty-print a byte value, e.g. 12433615056 is rendered as `11.6
104140
* GiB`. If `align` is set, the number will be right-justified by

src/libutil/util.cc

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,61 @@ std::optional<N> string2Float(const std::string_view s)
132132
template std::optional<double> string2Float<double>(const std::string_view s);
133133
template std::optional<float> string2Float<float>(const std::string_view s);
134134

135-
std::string renderSize(int64_t value, bool align)
135+
static const int64_t conversionNumber = 1024;
136+
137+
SizeUnit getSizeUnit(int64_t value)
138+
{
139+
auto unit = sizeUnits.begin();
140+
uint64_t absValue = std::abs(value);
141+
while (absValue > conversionNumber && unit < sizeUnits.end()) {
142+
unit++;
143+
absValue /= conversionNumber;
144+
}
145+
return *unit;
146+
}
147+
148+
std::optional<SizeUnit> getCommonSizeUnit(std::initializer_list<int64_t> values)
149+
{
150+
assert(values.size() > 0);
151+
152+
auto it = values.begin();
153+
SizeUnit unit = getSizeUnit(*it);
154+
it++;
155+
156+
for (; it != values.end(); it++) {
157+
if (unit != getSizeUnit(*it)) {
158+
return std::nullopt;
159+
}
160+
}
161+
162+
return unit;
163+
}
164+
165+
std::string renderSizeWithoutUnit(int64_t value, SizeUnit unit, bool align)
166+
{
167+
size_t unitIdx = std::find(sizeUnits.begin(), sizeUnits.end(), unit) - sizeUnits.begin();
168+
// bytes should also displayed as KiB => 100 Bytes => 0.1 KiB
169+
double result = (double) value / std::pow(conversionNumber, std::max((size_t) 1, unitIdx));
170+
return fmt(align ? "%6.1f" : "%.1f", result);
171+
}
172+
173+
char getSizeUnitSuffix(SizeUnit unit)
136174
{
137-
static const std::array<char, 9> prefixes{{'K', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}};
138-
size_t power = 0;
139-
double abs_value = std::abs(value);
140-
while (abs_value > 1024 && power < prefixes.size()) {
141-
++power;
142-
abs_value /= 1024;
175+
switch (unit) {
176+
#define X(name, suffix) \
177+
case SizeUnit::name: \
178+
return suffix;
179+
SIZE_UNITS
180+
#undef X
143181
}
144-
double res = (double) value / std::pow(1024.0, power);
145-
return fmt(align ? "%6.1f %ciB" : "%.1f %ciB", power == 0 ? res / 1024 : res, prefixes.at(power));
182+
183+
assert(false);
184+
}
185+
186+
std::string renderSize(int64_t value, bool align)
187+
{
188+
SizeUnit unit = getSizeUnit(value);
189+
return fmt("%s %ciB", renderSizeWithoutUnit(value, unit, align), getSizeUnitSuffix(unit));
146190
}
147191

148192
bool hasPrefix(std::string_view s, std::string_view prefix)

0 commit comments

Comments
 (0)