Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit f700ba7

Browse files
fix: correct remaining time (#1669)
* fix: correct remaining time * chore: add unit tests * fix: use constexpr --------- Co-authored-by: vansangpfiev <sang@jan.ai>
1 parent ac9c113 commit f700ba7

File tree

4 files changed

+104
-11
lines changed

4 files changed

+104
-11
lines changed

engine/cli/utils/download_progress.cc

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ bool DownloadProgress::Handle(const DownloadType& event_type) {
5555

5656
std::vector<std::unique_ptr<indicators::ProgressBar>> items;
5757
indicators::show_console_cursor(false);
58-
auto handle_message = [this, &bars, &items,
59-
event_type](const std::string& message) {
58+
auto start = std::chrono::steady_clock::now();
59+
auto handle_message = [this, &bars, &items, event_type,
60+
start](const std::string& message) {
6061
CTL_INF(message);
6162

6263
auto pad_string = [](const std::string& str,
@@ -80,7 +81,7 @@ bool DownloadProgress::Handle(const DownloadType& event_type) {
8081
if (ev.download_task_.type != event_type) {
8182
return;
8283
}
83-
84+
auto now = std::chrono::steady_clock::now();
8485
if (!bars) {
8586
bars = std::make_unique<
8687
indicators::DynamicProgress<indicators::ProgressBar>>();
@@ -91,7 +92,7 @@ bool DownloadProgress::Handle(const DownloadType& event_type) {
9192
indicators::option::End{"]"},
9293
indicators::option::PrefixText{pad_string(Repo2Engine(i.id))},
9394
indicators::option::ForegroundColor{indicators::Color::white},
94-
indicators::option::ShowRemainingTime{true}));
95+
indicators::option::ShowRemainingTime{false}));
9596
bars->push_back(*(items.back()));
9697
}
9798
}
@@ -101,22 +102,34 @@ bool DownloadProgress::Handle(const DownloadType& event_type) {
101102
uint64_t downloaded = it.downloadedBytes.value_or(0u);
102103
uint64_t total =
103104
it.bytes.value_or(std::numeric_limits<uint64_t>::max());
105+
auto d = std::chrono::duration_cast<std::chrono::seconds>(now - start)
106+
.count();
107+
uint64_t bytes_per_sec = downloaded / (d + 1);
108+
std::string time_remaining;
109+
if (downloaded == total || bytes_per_sec == 0) {
110+
time_remaining = "00m:00s";
111+
} else {
112+
time_remaining = format_utils::TimeDownloadFormat(
113+
(total - downloaded) / bytes_per_sec);
114+
}
115+
104116
(*bars)[i].set_option(indicators::option::PrefixText{
105117
pad_string(Repo2Engine(it.id)) +
106118
std::to_string(int(static_cast<double>(downloaded) / total * 100)) +
107119
'%'});
108120
(*bars)[i].set_progress(
109121
int(static_cast<double>(downloaded) / total * 100));
110122
(*bars)[i].set_option(indicators::option::PostfixText{
123+
time_remaining + " " +
111124
format_utils::BytesToHumanReadable(downloaded) + "/" +
112125
format_utils::BytesToHumanReadable(total)});
113126
} else if (ev.type_ == DownloadStatus::DownloadSuccess) {
114127
uint64_t total =
115128
it.bytes.value_or(std::numeric_limits<uint64_t>::max());
116129
(*bars)[i].set_progress(100);
117130
auto total_str = format_utils::BytesToHumanReadable(total);
118-
(*bars)[i].set_option(
119-
indicators::option::PostfixText{total_str + "/" + total_str});
131+
(*bars)[i].set_option(indicators::option::PostfixText{
132+
"00m:00s " + total_str + "/" + total_str});
120133
(*bars)[i].set_option(indicators::option::PrefixText{
121134
pad_string(Repo2Engine(it.id)) + "100%"});
122135
(*bars)[i].set_progress(100);

engine/test/components/test_format_utils.cc

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class FormatUtilsTest : public ::testing::Test {};
88
TEST_F(FormatUtilsTest, WriteKeyValue) {
99
{
1010
YAML::Node node;
11-
std::string result = format_utils::writeKeyValue("key", node["does_not_exist"]);
11+
std::string result =
12+
format_utils::writeKeyValue("key", node["does_not_exist"]);
1213
EXPECT_EQ(result, "");
1314
}
1415

@@ -109,4 +110,45 @@ TEST_F(FormatUtilsTest, PrintFloat) {
109110
result =
110111
format_utils::print_float("key", std::numeric_limits<float>::quiet_NaN());
111112
EXPECT_EQ(result, "");
113+
}
114+
115+
TEST_F(FormatUtilsTest, TimeDownloadFormat_ZeroSeconds) {
116+
EXPECT_EQ(format_utils::TimeDownloadFormat(0), "00m:00s");
117+
}
118+
119+
TEST_F(FormatUtilsTest, TimeDownloadFormat_LessThanOneMinute) {
120+
EXPECT_EQ(format_utils::TimeDownloadFormat(30), "00m:30s");
121+
}
122+
123+
TEST_F(FormatUtilsTest, TimeDownloadFormat_ExactlyOneMinute) {
124+
EXPECT_EQ(format_utils::TimeDownloadFormat(60), "01m:00s");
125+
}
126+
127+
TEST_F(FormatUtilsTest, TimeDownloadFormat_LessThanOneHour) {
128+
EXPECT_EQ(format_utils::TimeDownloadFormat(125), "02m:05s");
129+
}
130+
131+
TEST_F(FormatUtilsTest, TimeDownloadFormat_ExactlyOneHour) {
132+
EXPECT_EQ(format_utils::TimeDownloadFormat(3600), "01h:00m:00s");
133+
}
134+
135+
TEST_F(FormatUtilsTest, TimeDownloadFormat_MoreThanOneHour) {
136+
EXPECT_EQ(format_utils::TimeDownloadFormat(3661), "01h:01m:01s");
137+
}
138+
139+
TEST_F(FormatUtilsTest, TimeDownloadFormat_LessThanOneDay) {
140+
EXPECT_EQ(format_utils::TimeDownloadFormat(86399),
141+
"23h:59m:59s"); // 1 second less than a day
142+
}
143+
144+
TEST_F(FormatUtilsTest, TimeDownloadFormat_ExactlyOneDay) {
145+
EXPECT_EQ(format_utils::TimeDownloadFormat(86400), "01d:00h:00m:00s");
146+
}
147+
148+
TEST_F(FormatUtilsTest, TimeDownloadFormat_MoreThanOneDay) {
149+
EXPECT_EQ(format_utils::TimeDownloadFormat(90061), "01d:01h:01m:01s");
150+
}
151+
152+
TEST_F(FormatUtilsTest, TimeDownloadFormat_LargeNumberOfSeconds) {
153+
EXPECT_EQ(format_utils::TimeDownloadFormat(1000000), "11d:13h:46m:40s");
112154
}

engine/test/components/test_string_utils.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,5 @@ TEST_F(StringUtilsTestSuite, LargeInputPerformance) {
287287
// and doesn't crash with large inputs
288288
EXPECT_EQ(RemoveSubstring(large_input, to_remove), "");
289289
}
290+
291+

engine/utils/format_utils.h

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ inline std::string writeKeyValue(const std::string& key,
8282
};
8383

8484
inline std::string BytesToHumanReadable(uint64_t bytes) {
85-
const uint64_t KB = 1024;
86-
const uint64_t MB = KB * 1024;
87-
const uint64_t GB = MB * 1024;
88-
const uint64_t TB = GB * 1024;
85+
constexpr const uint64_t KB = 1024;
86+
constexpr const uint64_t MB = KB * 1024;
87+
constexpr const uint64_t GB = MB * 1024;
88+
constexpr const uint64_t TB = GB * 1024;
8989

9090
double result;
9191
std::string unit;
@@ -112,4 +112,40 @@ inline std::string BytesToHumanReadable(uint64_t bytes) {
112112
out << std::fixed << std::setprecision(2) << result << " " << unit;
113113
return out.str();
114114
}
115+
116+
inline std::string TimeDownloadFormat(int seconds) {
117+
// Constants for time units
118+
constexpr const uint64_t kSecondsInMinute = 60;
119+
constexpr const uint64_t kSecondsInHour = kSecondsInMinute * 60;
120+
constexpr const uint64_t kSecondsInDay = kSecondsInHour * 24;
121+
122+
uint64_t days = seconds / kSecondsInDay;
123+
seconds %= kSecondsInDay;
124+
125+
uint64_t hours = seconds / kSecondsInHour;
126+
seconds %= kSecondsInHour;
127+
128+
uint64_t minutes = seconds / kSecondsInMinute;
129+
seconds %= kSecondsInMinute;
130+
131+
std::ostringstream oss;
132+
133+
auto pad = [](const std::string& v) -> std::string {
134+
if (v.size() == 1)
135+
return "0" + v;
136+
return v;
137+
};
138+
139+
if (days > 0) {
140+
oss << pad(std::to_string(days)) << "d:";
141+
}
142+
if (hours > 0 || days > 0) {
143+
oss << pad(std::to_string(hours)) << "h:";
144+
}
145+
oss << pad(std::to_string(minutes)) << "m:";
146+
147+
oss << pad(std::to_string(seconds)) << "s";
148+
149+
return oss.str();
150+
};
115151
} // namespace format_utils

0 commit comments

Comments
 (0)