diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.cc b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.cc index 3fff82c5a13..4f508a566e5 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.cc +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.cc @@ -16,7 +16,11 @@ #include "cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.h" +#include #include +#include +#include +#include #include #include @@ -70,6 +74,16 @@ void ClearLastNLines(int n) { } } +void UpdateFileAndWatch(const SharedFD& inotify_fd, const std::string& path, + SharedFD& fd, int& watch) { + if (!fd->IsOpen()) { + fd = SharedFD::Open(path, O_RDONLY); + } + if (fd->IsOpen() && watch == -1) { + watch = inotify_fd->InotifyAddWatch(path, IN_MODIFY); + } +} + } // namespace CvdMonitorCommandHandler::CvdMonitorCommandHandler( @@ -82,33 +96,43 @@ Result CvdMonitorCommandHandler::Handle(const CommandRequest& request) { std::vector args = request.SubcommandArguments(); CF_EXPECT(ConsumeFlags({}, args, {.fail_on_unexpected_argument = true})); - auto [instance, unused] = + const auto [instance, unused] = CF_EXPECT(selector::SelectInstance(instance_manager_, request), "Unable to select an instance"); - std::string kernel_log = + const std::string kernel_log = absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameKernel); - std::string launcher_log = + const std::string launcher_log = absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameLauncher); - std::string logcat = + const std::string logcat = absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameLogcat); SharedFD kernel_fd; SharedFD launcher_fd; SharedFD logcat_fd; + const SharedFD inotify_fd = SharedFD::InotifyFd(); + CF_EXPECT(inotify_fd->IsOpen(), "Failed to create inotify fd"); + const int flags = inotify_fd->Fcntl(F_GETFL, 0); + CF_EXPECT(inotify_fd->Fcntl(F_SETFL, flags | O_NONBLOCK) != -1, + "Failed to set inotify fd to non-blocking"); + + const std::string logs_dir = + absl::StrCat(instance.InstanceDirectory(), "/logs"); + const int dir_watch = inotify_fd->InotifyAddWatch(logs_dir, IN_CREATE); + + int kernel_watch = -1; + int launcher_watch = -1; + int logcat_watch = -1; + std::chrono::steady_clock::time_point last_draw_time = + std::chrono::steady_clock::time_point(); + while (true) { - if (!kernel_fd->IsOpen()) { - kernel_fd = SharedFD::Open(kernel_log, O_RDONLY); - } - if (!launcher_fd->IsOpen()) { - launcher_fd = SharedFD::Open(launcher_log, O_RDONLY); - } - if (!logcat_fd->IsOpen()) { - logcat_fd = SharedFD::Open(logcat, O_RDONLY); - } + UpdateFileAndWatch(inotify_fd, kernel_log, kernel_fd, kernel_watch); + UpdateFileAndWatch(inotify_fd, launcher_log, launcher_fd, launcher_watch); + UpdateFileAndWatch(inotify_fd, logcat, logcat_fd, logcat_watch); - Result term_size_result = GetTerminalSize(); + const Result term_size_result = GetTerminalSize(); int width = 79; // Default fallback width (80 - 1) if (term_size_result.ok()) { width = term_size_result->columns - 1; @@ -119,11 +143,47 @@ Result CvdMonitorCommandHandler::Handle(const CommandRequest& request) { display.DrawFile(kernel_fd, kLogNameKernel); display.DrawFile(logcat_fd, kLogNameLogcat); - auto [output, total_lines_drawn] = display.Finalize(); + const auto [output, total_lines_drawn] = display.Finalize(); std::cout << output << std::flush; + // Enforce a maximum framerate (max 20 FPS / min 50ms between draws) + // so we don't saturate SSH bandwidth or CPU during heavy, continuous + // logging. + const std::chrono::steady_clock::time_point now = + std::chrono::steady_clock::now(); + const std::chrono::steady_clock::duration elapsed = now - last_draw_time; + constexpr std::chrono::milliseconds min_frame_time(50); + if (elapsed < min_frame_time) { + std::this_thread::sleep_for(min_frame_time - elapsed); + } + last_draw_time = std::chrono::steady_clock::now(); + + // Block until file changes occur. If any watch failed or is missing, use + // a 1-second fallback timeout to awake and retry. + // NOLINTNEXTLINE(misc-include-cleaner) + PollSharedFd poll_fd = {inotify_fd, POLLIN, 0}; + int timeout_ms = -1; + if (dir_watch == -1 || kernel_watch == -1 || launcher_watch == -1 || + logcat_watch == -1) { + timeout_ms = 1000; + } - // Wait a bit before clearing and redrawing - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // NOLINTNEXTLINE(misc-include-cleaner) + if (SharedFD::Poll(&poll_fd, 1, timeout_ms) > 0 && + (poll_fd.revents & POLLIN)) { + // Exhaustively drain all available events from the non-blocking + // descriptor to coalesce rapid file modifications. + char buf[4096] + __attribute__((aligned(__alignof__(struct inotify_event)))); + ssize_t read_res = 0; + while ((read_res = inotify_fd->Read(buf, sizeof(buf))) > 0) { + } + CF_EXPECT(read_res != 0, + "Unexpected End-of-File reading inotify descriptor"); + const int err = inotify_fd->GetErrno(); + CF_EXPECTF(err == EAGAIN || err == EWOULDBLOCK, + "Unexpected error reading inotify descriptor: {} ({})", + inotify_fd->StrError(), err); + } ClearLastNLines(total_lines_drawn); }