| // Copyright 2024 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "status_table.h" |
| |
| #include <algorithm> |
| |
| #include "assert.h" |
| |
| #ifdef _WIN32 |
| #undef min // make std::min() work on Windows. |
| #endif |
| |
| StatusTable::StatusTable(const Config& config, AsyncLoop& async_loop) |
| : config_(config), async_loop_(async_loop) {} |
| |
| StatusTable::~StatusTable() = default; |
| |
| void StatusTable::BuildStarted() { |
| start_build_time_ms_ = async_loop_.NowMs(); |
| |
| if (config_.refresh_timeout_ms > 0) { |
| if (!timer_) { |
| timer_ = AsyncTimer::CreateWithDuration( |
| config_.refresh_timeout_ms, async_loop_, [this] { |
| int64_t cur_time_ms = async_loop_.NowMs() - start_build_time_ms_; |
| Refresh(cur_time_ms); |
| timer_.SetDurationMs(config_.refresh_timeout_ms); |
| }); |
| } |
| EnableTimer(); |
| } |
| } |
| |
| void StatusTable::BuildEnded() { |
| if (timer_) { |
| timer_.Close(); |
| } |
| start_build_time_ms_ = 0; |
| last_update_time_ms_ = -1; |
| ClearTable(); |
| } |
| |
| void StatusTable::CommandStarted(CommandPointer command) { |
| pending_commands_[command] = async_loop_.NowMs() - start_build_time_ms_; |
| } |
| |
| void StatusTable::CommandEnded(CommandPointer command) { |
| auto it = pending_commands_.find(command); |
| assert(it != pending_commands_.end()); |
| pending_commands_.erase(it); |
| } |
| |
| void StatusTable::SetStatus(const std::string& status) { |
| last_status_ = status; |
| } |
| |
| void StatusTable::UpdateTable() { |
| Refresh(async_loop_.NowMs()); |
| } |
| |
| void StatusTable::EnableTimer() { |
| if (timer_) |
| timer_.SetDurationMs(config_.refresh_timeout_ms); |
| } |
| |
| void StatusTable::DisableTimer() { |
| if (timer_) |
| timer_.Cancel(); |
| } |
| |
| void StatusTable::Refresh(int64_t current_time_ms) { |
| if (last_update_time_ms_ >= 0) { |
| int64_t since_last_ms = current_time_ms - last_update_time_ms_; |
| if (since_last_ms < config_.refresh_timeout_ms) { |
| // No need to update more than necessary when tasks complete |
| // really really fast. |
| return; |
| } |
| } |
| last_update_time_ms_ = current_time_ms; |
| PrintPending(current_time_ms); |
| } |
| |
| void StatusTable::PrintPending(int64_t cur_time_millis) { |
| if (!config_.max_commands) |
| return; |
| |
| // Find the N-th older running edges, where N is max_height_. |
| // Reuse the sorted_pending_edges_ vector between calls. |
| auto& sorted_commands = sorted_pending_commands_; |
| sorted_commands.assign(pending_commands_.begin(), pending_commands_.end()); |
| |
| auto less = [](const CommandInfo& a, const CommandInfo& b) -> bool { |
| return a.second < b.second; |
| }; |
| size_t count = std::min(sorted_commands.size(), config_.max_commands); |
| |
| std::partial_sort(sorted_commands.begin(), sorted_commands.begin() + count, |
| sorted_commands.end(), less); |
| |
| std::string pending_line; |
| for (size_t n = 0; n < count; ++n) { |
| CommandInfo& pair = sorted_commands[n]; |
| |
| // Format the elapsed time in a human friendly format. |
| char elapsed_buffer[16]; |
| int64_t elapsed_ms = cur_time_millis - pair.second; |
| if (elapsed_ms < 0) { |
| snprintf(elapsed_buffer, sizeof(elapsed_buffer), "??????"); |
| } else { |
| if (elapsed_ms < 60000) { |
| snprintf(elapsed_buffer, sizeof(elapsed_buffer), "%d.%ds", |
| static_cast<int>((elapsed_ms / 1000)), |
| static_cast<int>((elapsed_ms % 1000) / 100)); |
| } else { |
| snprintf(elapsed_buffer, sizeof(elapsed_buffer), "%dm%ds", |
| static_cast<int>((elapsed_ms / 60000)), |
| static_cast<int>((elapsed_ms % 60000) / 1000)); |
| } |
| } |
| |
| // Get edge description or command. |
| std::string description = GetCommandDescription(pair.first); |
| |
| // Format '<elapsed> | <description>' where <elapsed> is |
| // right-justified. |
| size_t justification_width = 6; |
| size_t elapsed_width = strlen(elapsed_buffer); |
| size_t justified_elapsed_width = |
| std::min(justification_width, elapsed_width); |
| size_t needed_capacity = justified_elapsed_width + 3 + description.size(); |
| if (needed_capacity > pending_line.capacity()) |
| pending_line.reserve(needed_capacity); |
| if (elapsed_width < justification_width) { |
| pending_line.assign(justification_width - elapsed_width, ' '); |
| } else { |
| pending_line.clear(); |
| } |
| pending_line.append(elapsed_buffer, elapsed_width); |
| pending_line.append(" | ", 3); |
| pending_line.append(description); |
| |
| PrintOnNextLine(pending_line); |
| } |
| |
| // Clear previous lines that are not needed anymore. |
| size_t next_height = count; |
| for (; count < last_command_count_; ++count) { |
| ClearNextLine(); |
| } |
| |
| if (count > 0) { |
| // Move up to the top status line. Then print the status |
| // again to reposition the cursor to the right position. |
| // Note that using ASCII sequences to save/restore the |
| // cursor position does not work reliably in all terminals |
| // (and terminal emulators like mosh or asciinema). |
| MoveUp(count); |
| PrintOnCurrentLine(last_status_); |
| } |
| Flush(); |
| |
| last_command_count_ = next_height; |
| } |
| |
| std::string StatusTable::GetCommandDescription( |
| StatusTable::CommandPointer command) const { |
| return "command $" + std::to_string(reinterpret_cast<uintptr_t>(command)); |
| } |
| |
| void StatusTable::PrintOnCurrentLine(const std::string& line) { |
| printf("%s\x1B[0K", line.c_str()); |
| } |
| |
| void StatusTable::PrintOnNextLine(const std::string& line) { |
| printf("\n"); |
| PrintOnCurrentLine(line); |
| } |
| |
| void StatusTable::ClearNextLine() { |
| printf("\x1B[1B\x1B[2K"); |
| } |
| |
| void StatusTable::MoveUp(size_t lines_count) { |
| printf("\x1B[%dA", static_cast<int>(lines_count)); |
| } |
| |
| void StatusTable::Flush() { |
| fflush(stdout); |
| } |
| |
| void StatusTable::ClearTable() { |
| if (last_command_count_ == 0) |
| return; |
| |
| // repeat "go down 1 line; erase whole line" |last_height_| times. |
| for (size_t n = 0; n < last_command_count_; ++n) |
| ClearNextLine(); |
| |
| // move up |last_height_| lines. |
| MoveUp(last_command_count_); |
| Flush(); |
| |
| last_command_count_ = 0; |
| } |