From 52e1551154be4fe364aaba0f617e5665c9d78160 Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Wed, 10 Jun 2026 15:59:14 -0700 Subject: [PATCH] Update cvd monitor to use inotify This replaces the 50ms polling loop with an event-driven approach using inotify to wait for log file changes. It implements stack-based, non-blocking event coalescing and a 20 FPS rate-limiting throttle to protect terminal bandwidth and CPU during log storms. Additionally, this adds InotifyAddWatch retries on every frame if watches fail or log files are missing, refactors the retrying logic into a reusable mutating helper function, supervises the loop with SharedFD::Poll() and a single unified drain read using a dynamic 1-second fallback timeout, supervises read operations with CF_EXPECT / CF_EXPECTF to catch unexpected errors, and eliminates initial frame throttling delays by starting with a 0 epoch draw time. Assisted-by: Jetski:Gemini Next --- .../cli/commands/monitor/command_handler.cc | 94 +++++++++++++++---- 1 file changed, 77 insertions(+), 17 deletions(-) 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); }