-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Improved details for build and pull #40596
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
da330ff
07c7cfc
41d419a
9fb96f7
059c664
819e983
2e644a0
dab9f35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,9 +64,10 @@ void BuildImageCallback::CollapseWindow() | |
|
|
||
| m_lines.clear(); | ||
| m_pendingLine.clear(); | ||
| m_pullLines.clear(); | ||
| } | ||
|
|
||
| HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG /*current*/, ULONGLONG /*total*/) | ||
| HRESULT BuildImageCallback::OnProgress(LPCSTR status, LPCSTR id, ULONGLONG current, ULONGLONG total) | ||
| try | ||
| { | ||
| if (status == nullptr || *status == '\0') | ||
|
|
@@ -81,78 +82,103 @@ try | |
| return S_OK; | ||
| } | ||
|
|
||
| const std::string_view idView = (id != nullptr) ? id : std::string_view{}; | ||
| const bool isLog = (idView == "log"); | ||
| const bool isPullProgress = (!idView.empty() && total > 0 && !isLog); | ||
|
|
||
| if (m_verbose || !m_isConsole) | ||
| { | ||
| wprintf(L"%hs", status); | ||
| // Skip pull progress updates when output is redirected, show only major steps | ||
| if (!isPullProgress) | ||
| { | ||
| wprintf(L"%hs", status); | ||
| } | ||
| return S_OK; | ||
| } | ||
|
|
||
| // Match the specific "log" sentinel sent by WSLCSession::BuildImage rather than | ||
| // accepting any non-empty id, so future or unrelated id usage defaults to permanent. | ||
| const bool isLog = (id != nullptr && std::string_view{id} == "log"); | ||
|
|
||
| if (!isLog) | ||
| // Pull/download progress: update the per-entry map so Redraw can show each entry | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we are handling this correctly when m_isConsole is false. We should probably skip the whole callback stuff when non in an interactive terminal.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would break AIs who are scraping I think this is pretty reasonable and gives the right info on all the steps. |
||
| // on a single line that updates in place. | ||
|
craigloewen-msft marked this conversation as resolved.
|
||
| if (isPullProgress) | ||
| { | ||
| // Permanent line: collapse the scrolling window then print directly. | ||
| CollapseWindow(); | ||
| WriteTerminal(MultiByteToWide(status)); | ||
| m_pullLines[id] = status; | ||
|
|
||
| auto now = std::chrono::steady_clock::now(); | ||
| if (now - m_lastRedraw >= c_redrawInterval) | ||
| { | ||
| Redraw(); | ||
| m_lastRedraw = now; | ||
| } | ||
|
|
||
| return S_OK; | ||
| } | ||
|
craigloewen-msft marked this conversation as resolved.
|
||
|
|
||
| // Log line: add to the scrolling window. | ||
| for (const char* p = status; *p != '\0'; ++p) | ||
| if (isLog) | ||
| { | ||
| if (*p == '\n') | ||
| // Log line: add to the scrolling window. | ||
| for (const char* p = status; *p != '\0'; ++p) | ||
| { | ||
| // Store with the trailing newline so the byte count matches what is replayed. | ||
| // Cap retained log output to avoid unbounded growth on very long builds. | ||
| m_allLines.push_back(m_pendingLine + '\n'); | ||
| m_allLinesBytes += m_allLines.back().size(); | ||
| while (m_allLinesBytes > c_maxAllLinesBytes && !m_allLines.empty()) | ||
| if (*p == '\n') | ||
| { | ||
| m_allLinesBytes -= m_allLines.front().size(); | ||
| m_allLines.pop_front(); | ||
| } | ||
| // Store with the trailing newline so the byte count matches what is replayed. | ||
| // Cap retained log output to avoid unbounded growth on very long builds. | ||
| m_allLines.push_back(m_pendingLine + '\n'); | ||
| m_allLinesBytes += m_allLines.back().size(); | ||
| while (m_allLinesBytes > c_maxAllLinesBytes && !m_allLines.empty()) | ||
| { | ||
| m_allLinesBytes -= m_allLines.front().size(); | ||
| m_allLines.pop_front(); | ||
| } | ||
|
|
||
| m_lines.push_back(std::move(m_pendingLine)); | ||
| m_pendingLine.clear(); | ||
| if (m_lines.size() > c_maxDisplayLines) | ||
| { | ||
| m_lines.pop_front(); | ||
| m_lines.push_back(std::move(m_pendingLine)); | ||
| m_pendingLine.clear(); | ||
| if (m_lines.size() > c_maxDisplayLines) | ||
| { | ||
| m_lines.pop_front(); | ||
| } | ||
| } | ||
| } | ||
| else if (*p == '\r') | ||
| { | ||
| // \r\n is a line ending; standalone \r overwrites the current line. | ||
| if (*(p + 1) != '\n') | ||
| else if (*p == '\r') | ||
| { | ||
| // Flush a throttled redraw before clearing so \r-based progress | ||
| // updates are visible even when batched in a single OnProgress call. | ||
| auto now = std::chrono::steady_clock::now(); | ||
| if (!m_pendingLine.empty() && now - m_lastRedraw >= c_redrawInterval) | ||
| // \r\n is a line ending; standalone \r overwrites the current line. | ||
| if (*(p + 1) != '\n') | ||
| { | ||
| Redraw(); | ||
| m_lastRedraw = now; | ||
| // Flush a throttled redraw before clearing so \r-based progress | ||
| // updates are visible even when batched in a single OnProgress call. | ||
| auto now = std::chrono::steady_clock::now(); | ||
| if (!m_pendingLine.empty() && now - m_lastRedraw >= c_redrawInterval) | ||
| { | ||
| Redraw(); | ||
| m_lastRedraw = now; | ||
| } | ||
| m_pendingLine.clear(); | ||
| } | ||
| m_pendingLine.clear(); | ||
| } | ||
| else | ||
| { | ||
| m_pendingLine += *p; | ||
| } | ||
| } | ||
| else | ||
|
|
||
| // Throttle redraws to avoid blocking the server's IO loop with console writes | ||
| // during rapid output. Lines accumulate in the deque immediately; the display | ||
| // catches up at ~20fps. | ||
| auto now = std::chrono::steady_clock::now(); | ||
| if (now - m_lastRedraw >= c_redrawInterval) | ||
| { | ||
| m_pendingLine += *p; | ||
| Redraw(); | ||
| m_lastRedraw = now; | ||
| } | ||
| } | ||
|
|
||
| // Throttle redraws to avoid blocking the server's IO loop with console writes | ||
| // during rapid output. Lines accumulate in the deque immediately; the display | ||
| // catches up at ~20fps. | ||
| auto now = std::chrono::steady_clock::now(); | ||
| if (now - m_lastRedraw >= c_redrawInterval) | ||
| { | ||
| Redraw(); | ||
| m_lastRedraw = now; | ||
| return S_OK; | ||
| } | ||
|
|
||
| // Else is a build step | ||
| CollapseWindow(); | ||
| auto wide = MultiByteToWide(status); | ||
| const auto bodyLength = wide.find_last_not_of(L"\r\n") + 1; | ||
| const auto newlines = wide.substr(bodyLength); | ||
| wide.resize(bodyLength); | ||
|
|
||
| WriteTerminal(std::format(L"\033[92m{}\033[0m{}", wide, newlines)); | ||
| return S_OK; | ||
| } | ||
| CATCH_RETURN(); | ||
|
|
@@ -167,14 +193,16 @@ void BuildImageCallback::Redraw() | |
| // to std::wstring::resize). | ||
| const SHORT consoleWidth = std::max<SHORT>(0, info.srWindow.Right - info.srWindow.Left); | ||
|
|
||
| // Determine how many completed lines to show, leaving room for the pending line. | ||
| // Determine how many completed lines to show, leaving room for the pending line and pull progress. | ||
| const bool showPending = !m_pendingLine.empty(); | ||
| const SHORT pullCount = static_cast<SHORT>(m_pullLines.size()); | ||
| SHORT completedCount = static_cast<SHORT>(m_lines.size()); | ||
| if (showPending && completedCount >= c_maxDisplayLines) | ||
| const SHORT reservedLines = (showPending ? 1 : 0) + pullCount; | ||
| if (completedCount + reservedLines > c_maxDisplayLines) | ||
| { | ||
| completedCount = c_maxDisplayLines - 1; | ||
| completedCount = std::max<SHORT>(0, c_maxDisplayLines - reservedLines); | ||
| } | ||
| const SHORT displayCount = completedCount + (showPending ? 1 : 0); | ||
| const SHORT displayCount = completedCount + reservedLines; | ||
|
|
||
| // Build the entire frame in one buffer to minimize console writes. Hide the cursor | ||
| // during the redraw so the user doesn't see it bouncing through the cursor movement, | ||
|
|
@@ -217,6 +245,12 @@ void BuildImageCallback::Redraw() | |
| appendLine(m_pendingLine); | ||
| } | ||
|
|
||
| // Render per-entry pull progress (each entry updates in place via the map). | ||
| for (const auto& [key, line] : m_pullLines) | ||
|
craigloewen-msft marked this conversation as resolved.
|
||
| { | ||
| appendLine(line); | ||
| } | ||
|
|
||
| buffer += L"\033[22m\033[?25h"; | ||
|
|
||
| WriteTerminal(buffer); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if we could move this to
wsl::windows::common::stringand use the windowsStrFormatByteSizeEx. It does have a slightly different oputput. StrFormatByteSizeEx uses 1024-based math with the Windows-conventional labels (KB, MB, GB, TB) — i.e. 1.00 KB means 1024 bytes. The current helper is 1000-based. So switching will change the numbers users see during pulls which would diverge from docker's behavior. However, it does give localized decimal seperator. I could see this going either way.ALSO, if we are making a helper for this, we should probably get rid of the other FormatBytes method in ContainerTasks.cpp:37 and just use a common helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opportunity to centralize this, such as moving the one from ContainerTasks into stringshared.h
I'm not sure if Craig's PR is the right place to do that consolidation, but at the very least using the same method for it is appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to leave this PR as small as possible, so I will refrain for now from doing refactoring changes. If it blocks this PR from going in then I can do it!