Skip to content

Commit 6b39b6d

Browse files
Improved details for build and pull (#40596)
* Improved details for build and pull
1 parent 0b402cd commit 6b39b6d

6 files changed

Lines changed: 189 additions & 71 deletions

File tree

src/shared/inc/stringshared.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,30 @@ struct CaseInsensitiveCompare
825825
}
826826
};
827827

828+
inline std::wstring FormatBytes(uint64_t bytes)
829+
{
830+
constexpr double c_kB = 1000.0;
831+
constexpr double c_MB = 1000.0 * 1000.0;
832+
constexpr double c_GB = 1000.0 * 1000.0 * 1000.0;
833+
834+
if (bytes >= static_cast<uint64_t>(c_GB))
835+
{
836+
return std::format(L"{:.2f} GB", bytes / c_GB);
837+
}
838+
else if (bytes >= static_cast<uint64_t>(c_MB))
839+
{
840+
return std::format(L"{:.2f} MB", bytes / c_MB);
841+
}
842+
else if (bytes >= static_cast<uint64_t>(c_kB))
843+
{
844+
return std::format(L"{:.2f} KB", bytes / c_kB);
845+
}
846+
else
847+
{
848+
return std::format(L"{} B", bytes);
849+
}
850+
}
851+
828852
} // namespace wsl::shared::string
829853

830854
template <>

src/windows/inc/docker_schema.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,8 +584,10 @@ struct BuildKitStatus
584584
{
585585
std::string id;
586586
std::string vertex;
587+
int64_t current{};
588+
int64_t total{};
587589

588-
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(BuildKitStatus, id, vertex);
590+
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(BuildKitStatus, id, vertex, current, total);
589591
};
590592

591593
struct BuildKitLog

src/windows/wslc/services/BuildImageCallback.cpp

Lines changed: 100 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ namespace wsl::windows::wslc::services {
1919

2020
using wsl::windows::common::string::MultiByteToWide;
2121

22+
namespace {
23+
constexpr std::wstring_view c_escapeMoveCursorUpAndClear = L"\033[{}A\033[J";
24+
constexpr std::wstring_view c_escapeBrightGreen = L"\033[92m";
25+
constexpr std::wstring_view c_escapeResetAttributes = L"\033[0m";
26+
constexpr std::wstring_view c_escapeHideCursorDim = L"\033[?25l\033[2m";
27+
constexpr std::wstring_view c_escapeClearLineAndNewline = L"\033[K\n";
28+
constexpr std::wstring_view c_escapeUndimShowCursor = L"\033[22m\033[?25h";
29+
} // namespace
30+
2231
BuildImageCallback::~BuildImageCallback()
2332
try
2433
{
@@ -58,15 +67,26 @@ void BuildImageCallback::CollapseWindow()
5867
{
5968
if (m_displayedLines > 0)
6069
{
61-
WriteTerminal(std::format(L"\033[{}A\033[J", m_displayedLines));
70+
WriteTerminal(std::format(c_escapeMoveCursorUpAndClear, m_displayedLines));
6271
m_displayedLines = 0;
6372
}
6473

6574
m_lines.clear();
6675
m_pendingLine.clear();
76+
m_pullLines.clear();
6777
}
6878

69-
HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG /*current*/, ULONGLONG /*total*/)
79+
void BuildImageCallback::RedrawIfNeeded()
80+
{
81+
auto now = std::chrono::steady_clock::now();
82+
if (now - m_lastRedraw >= c_redrawInterval)
83+
{
84+
Redraw();
85+
m_lastRedraw = now;
86+
}
87+
}
88+
89+
HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total)
7090
try
7191
{
7292
if (status == nullptr || *status == '\0')
@@ -81,78 +101,90 @@ try
81101
return S_OK;
82102
}
83103

104+
const std::string_view idView = (id != nullptr) ? id : std::string_view{};
105+
const bool isLog = (idView == "log");
106+
const bool isPullProgress = (!idView.empty() && total > 0 && !isLog);
107+
84108
if (m_verbose || !m_isConsole)
85109
{
86-
wprintf(L"%hs", status);
110+
// Skip pull progress updates when output is redirected, show only major steps
111+
if (!isPullProgress)
112+
{
113+
wprintf(L"%hs", status);
114+
}
87115
return S_OK;
88116
}
89117

90-
// Match the specific "log" sentinel sent by WSLCSession::BuildImage rather than
91-
// accepting any non-empty id, so future or unrelated id usage defaults to permanent.
92-
const bool isLog = (id != nullptr && std::string_view{id} == "log");
93-
94-
if (!isLog)
118+
// Pull/download progress: update the per-entry map so Redraw can show each entry
119+
// on a single line that updates in place.
120+
if (isPullProgress)
95121
{
96-
// Permanent line: collapse the scrolling window then print directly.
97-
CollapseWindow();
98-
WriteTerminal(MultiByteToWide(status));
122+
m_pullLines[id] = status;
123+
RedrawIfNeeded();
124+
99125
return S_OK;
100126
}
101127

102-
// Log line: add to the scrolling window.
103-
for (const char* p = status; *p != '\0'; ++p)
128+
if (isLog)
104129
{
105-
if (*p == '\n')
130+
// Log line: add to the scrolling window.
131+
for (const char* p = status; *p != '\0'; ++p)
106132
{
107-
// Store with the trailing newline so the byte count matches what is replayed.
108-
// Cap retained log output to avoid unbounded growth on very long builds.
109-
m_allLines.push_back(m_pendingLine + '\n');
110-
m_allLinesBytes += m_allLines.back().size();
111-
while (m_allLinesBytes > c_maxAllLinesBytes && !m_allLines.empty())
133+
if (*p == '\n')
112134
{
113-
m_allLinesBytes -= m_allLines.front().size();
114-
m_allLines.pop_front();
115-
}
135+
// Store with the trailing newline so the byte count matches what is replayed.
136+
// Cap retained log output to avoid unbounded growth on very long builds.
137+
m_allLines.push_back(m_pendingLine + '\n');
138+
m_allLinesBytes += m_allLines.back().size();
139+
while (m_allLinesBytes > c_maxAllLinesBytes && !m_allLines.empty())
140+
{
141+
m_allLinesBytes -= m_allLines.front().size();
142+
m_allLines.pop_front();
143+
}
116144

117-
m_lines.push_back(std::move(m_pendingLine));
118-
m_pendingLine.clear();
119-
if (m_lines.size() > c_maxDisplayLines)
120-
{
121-
m_lines.pop_front();
145+
m_lines.push_back(std::move(m_pendingLine));
146+
m_pendingLine.clear();
147+
if (m_lines.size() > c_maxDisplayLines)
148+
{
149+
m_lines.pop_front();
150+
}
122151
}
123-
}
124-
else if (*p == '\r')
125-
{
126-
// \r\n is a line ending; standalone \r overwrites the current line.
127-
if (*(p + 1) != '\n')
152+
else if (*p == '\r')
128153
{
129-
// Flush a throttled redraw before clearing so \r-based progress
130-
// updates are visible even when batched in a single OnProgress call.
131-
auto now = std::chrono::steady_clock::now();
132-
if (!m_pendingLine.empty() && now - m_lastRedraw >= c_redrawInterval)
154+
// \r\n is a line ending; standalone \r overwrites the current line.
155+
if (*(p + 1) != '\n')
133156
{
134-
Redraw();
135-
m_lastRedraw = now;
157+
// Flush a throttled redraw before clearing so \r-based progress
158+
// updates are visible even when batched in a single OnProgress call.
159+
if (!m_pendingLine.empty())
160+
{
161+
RedrawIfNeeded();
162+
}
163+
m_pendingLine.clear();
136164
}
137-
m_pendingLine.clear();
165+
}
166+
else
167+
{
168+
m_pendingLine += *p;
138169
}
139170
}
140-
else
141-
{
142-
m_pendingLine += *p;
143-
}
144-
}
145171

146-
// Throttle redraws to avoid blocking the server's IO loop with console writes
147-
// during rapid output. Lines accumulate in the deque immediately; the display
148-
// catches up at ~20fps.
149-
auto now = std::chrono::steady_clock::now();
150-
if (now - m_lastRedraw >= c_redrawInterval)
151-
{
152-
Redraw();
153-
m_lastRedraw = now;
172+
// Throttle redraws to avoid blocking the server's IO loop with console writes
173+
// during rapid output. Lines accumulate in the deque immediately; the display
174+
// catches up at ~20fps.
175+
RedrawIfNeeded();
176+
177+
return S_OK;
154178
}
155179

180+
// Else is a build step
181+
CollapseWindow();
182+
auto wide = MultiByteToWide(status);
183+
const auto bodyLength = wide.find_last_not_of(L"\r\n") + 1;
184+
const auto newlines = wide.substr(bodyLength);
185+
wide.resize(bodyLength);
186+
187+
WriteTerminal(std::format(L"{}{}{}{}", c_escapeBrightGreen, wide, c_escapeResetAttributes, newlines));
156188
return S_OK;
157189
}
158190
CATCH_RETURN();
@@ -167,27 +199,29 @@ void BuildImageCallback::Redraw()
167199
// to std::wstring::resize).
168200
const SHORT consoleWidth = std::max<SHORT>(0, info.srWindow.Right - info.srWindow.Left);
169201

170-
// Determine how many completed lines to show, leaving room for the pending line.
202+
// Determine how many completed lines to show, leaving room for the pending line and pull progress.
171203
const bool showPending = !m_pendingLine.empty();
204+
const SHORT pullCount = static_cast<SHORT>(m_pullLines.size());
172205
SHORT completedCount = static_cast<SHORT>(m_lines.size());
173-
if (showPending && completedCount >= c_maxDisplayLines)
206+
const SHORT reservedLines = (showPending ? 1 : 0) + pullCount;
207+
if (completedCount + reservedLines > c_maxDisplayLines)
174208
{
175-
completedCount = c_maxDisplayLines - 1;
209+
completedCount = std::max<SHORT>(0, c_maxDisplayLines - reservedLines);
176210
}
177-
const SHORT displayCount = completedCount + (showPending ? 1 : 0);
211+
const SHORT displayCount = completedCount + reservedLines;
178212

179213
// Build the entire frame in one buffer to minimize console writes. Hide the cursor
180214
// during the redraw so the user doesn't see it bouncing through the cursor movement,
181215
// then show it again at the final position. The dim attribute (\033[2m) renders the
182216
// scrolling lines de-emphasized regardless of the user's theme.
183-
std::wstring buffer = L"\033[?25l\033[2m";
217+
std::wstring buffer{c_escapeHideCursorDim};
184218

185219
// Move cursor to the start of the display area and erase from there to the end of
186220
// the screen. \033[J handles the case where the new display is shorter than the
187221
// previous one (e.g. when \r clears the pending line without a replacement).
188222
if (m_displayedLines > 0)
189223
{
190-
buffer += std::format(L"\033[{}A\033[J", m_displayedLines);
224+
buffer += std::format(c_escapeMoveCursorUpAndClear, m_displayedLines);
191225
}
192226

193227
auto appendLine = [&](const std::string& line) {
@@ -197,7 +231,7 @@ void BuildImageCallback::Redraw()
197231
wline.resize(consoleWidth);
198232
}
199233
buffer += wline;
200-
buffer += L"\033[K\n";
234+
buffer += c_escapeClearLineAndNewline;
201235
};
202236

203237
// Print completed lines (skip older ones if we need room for the pending line).
@@ -217,7 +251,13 @@ void BuildImageCallback::Redraw()
217251
appendLine(m_pendingLine);
218252
}
219253

220-
buffer += L"\033[22m\033[?25h";
254+
// Render per-entry pull progress (each entry updates in place via the map).
255+
for (const auto& [key, line] : m_pullLines)
256+
{
257+
appendLine(line);
258+
}
259+
260+
buffer += c_escapeUndimShowCursor;
221261

222262
WriteTerminal(buffer);
223263
m_displayedLines = displayCount;

src/windows/wslc/services/BuildImageCallback.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Module Name:
1515
#include "ChangeTerminalMode.h"
1616
#include "SessionService.h"
1717
#include <deque>
18+
#include <map>
1819

1920
namespace wsl::windows::wslc::services {
2021
class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback
@@ -35,6 +36,7 @@ class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback
3536

3637
void CollapseWindow();
3738
void Redraw();
39+
void RedrawIfNeeded();
3840
// Use WriteConsoleW directly rather than wprintf: wprintf is noticeably slower for
3941
// the per-redraw scrolling display and produces visible flicker.
4042
void WriteTerminal(std::wstring_view content) const;
@@ -54,6 +56,8 @@ class DECLSPEC_UUID("3EDD5DBF-CA6C-4CF7-923A-AD94B6A732E5") BuildImageCallback
5456
std::string m_pendingLine;
5557
SHORT m_displayedLines = 0;
5658
std::chrono::steady_clock::time_point m_lastRedraw{};
59+
// Per-entry pull progress lines, keyed by entry id. Updated in place by Redraw. std::map so order is consistent.
60+
std::map<std::string, std::string> m_pullLines;
5761
// Captured at construction so the destructor can detect destruction during exception unwinding.
5862
int m_uncaughtExceptions = std::uncaught_exceptions();
5963
};

src/windows/wslc/services/ImageProgressCallback.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,58 @@ std::wstring ImageProgressCallback::GenerateStatusLine(LPCSTR status, LPCSTR id,
8484
std::wstring line;
8585
if (total != 0)
8686
{
87-
line = std::format(L"{} '{}': {}%", status, id, current * 100 / total);
87+
constexpr int c_progressBarWidth = 30;
88+
89+
int filled = 0;
90+
if (current >= total)
91+
{
92+
filled = c_progressBarWidth;
93+
}
94+
else
95+
{
96+
auto ratio = static_cast<long double>(current) / static_cast<long double>(total);
97+
filled = static_cast<int>(ratio * c_progressBarWidth);
98+
}
99+
100+
filled = std::clamp(filled, 0, c_progressBarWidth);
101+
102+
std::wstring bar;
103+
bar.reserve(c_progressBarWidth);
104+
bar.append(filled, L'=');
105+
bar.append(L">");
106+
bar.resize(c_progressBarWidth, L' ');
107+
108+
line = std::format(
109+
L"{}: {} [{}] {}/{}", id, status, bar, wsl::shared::string::FormatBytes(current), wsl::shared::string::FormatBytes(total));
88110
}
89111
else if (current != 0)
90112
{
91-
line = std::format(L"{} '{}': {}s", status, id, current);
113+
line = std::format(L"{}: {} {}s", id, status, current);
92114
}
93115
else
94116
{
95-
line = std::format(L"{} '{}'", status, id);
117+
line = std::format(L"{}: {}", id, status);
96118
}
97119

98-
// Erase any previously written char on that line.
99-
while (line.size() < info.dwSize.X)
120+
// Use the visible window width (not the buffer width) to prevent wrapping.
121+
const auto visibleWidth = std::max<SHORT>(0, info.srWindow.Right - info.srWindow.Left + 1);
122+
123+
// Truncate to console width to prevent wrapping that would break cursor repositioning.
124+
if (line.size() > static_cast<size_t>(visibleWidth))
100125
{
101-
line += L' ';
126+
line.resize(visibleWidth);
127+
128+
// Avoid splitting a surrogate pair — if the last code unit is a high surrogate,
129+
// drop it so we don't emit an invalid UTF-16 sequence.
130+
if (!line.empty() && IS_HIGH_SURROGATE(line.back()))
131+
{
132+
line.pop_back();
133+
}
102134
}
103135

136+
// Erase any previously written char on that line.
137+
line.resize(visibleWidth, L' ');
138+
104139
return line;
105140
}
106141
} // namespace wsl::windows::wslc::services

0 commit comments

Comments
 (0)