Skip to content

Commit 0e4f9fc

Browse files
committed
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, implementing FIONREAD event coalescing and a 20 FPS rate-limiting throttle to protect terminal bandwidth during log storms. Assisted-by: Jetski:Gemini Next
1 parent 0fb33ee commit 0e4f9fc

1 file changed

Lines changed: 50 additions & 2 deletions

File tree

base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.cc

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.h"
1818

1919
#include <fcntl.h>
20+
#include <sys/inotify.h>
2021
#include <unistd.h>
2122

2223
#include <chrono>
@@ -97,15 +98,36 @@ Result<void> CvdMonitorCommandHandler::Handle(const CommandRequest& request) {
9798
SharedFD launcher_fd;
9899
SharedFD logcat_fd;
99100

101+
SharedFD inotify_fd = SharedFD::InotifyFd();
102+
CF_EXPECT(inotify_fd->IsOpen(), "Failed to create inotify fd");
103+
104+
std::string logs_dir = absl::StrCat(instance.InstanceDirectory(), "/logs");
105+
inotify_fd->InotifyAddWatch(logs_dir, IN_CREATE);
106+
107+
int kernel_watch = -1;
108+
int launcher_watch = -1;
109+
int logcat_watch = -1;
110+
std::chrono::steady_clock::time_point last_draw_time =
111+
std::chrono::steady_clock::now();
112+
100113
while (true) {
101114
if (!kernel_fd->IsOpen()) {
102115
kernel_fd = SharedFD::Open(kernel_log, O_RDONLY);
116+
if (kernel_fd->IsOpen() && kernel_watch == -1) {
117+
kernel_watch = inotify_fd->InotifyAddWatch(kernel_log, IN_MODIFY);
118+
}
103119
}
104120
if (!launcher_fd->IsOpen()) {
105121
launcher_fd = SharedFD::Open(launcher_log, O_RDONLY);
122+
if (launcher_fd->IsOpen() && launcher_watch == -1) {
123+
launcher_watch = inotify_fd->InotifyAddWatch(launcher_log, IN_MODIFY);
124+
}
106125
}
107126
if (!logcat_fd->IsOpen()) {
108127
logcat_fd = SharedFD::Open(logcat, O_RDONLY);
128+
if (logcat_fd->IsOpen() && logcat_watch == -1) {
129+
logcat_watch = inotify_fd->InotifyAddWatch(logcat, IN_MODIFY);
130+
}
109131
}
110132

111133
Result<TerminalSize> term_size_result = GetTerminalSize();
@@ -121,9 +143,35 @@ Result<void> CvdMonitorCommandHandler::Handle(const CommandRequest& request) {
121143

122144
auto [output, total_lines_drawn] = display.Finalize();
123145
std::cout << output << std::flush;
146+
last_draw_time = std::chrono::steady_clock::now();
147+
148+
// Block until file changes occur (wait for the first event).
149+
// An oversized buffer is required because IN_CREATE events for the logs/
150+
// directory append the variable-length newly created filename to the
151+
// end of the struct inotify_event. A buffer that is too small to fit the
152+
// event plus the name will cause read() to fail with EINVAL.
153+
char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
154+
inotify_fd->Read(buf, sizeof(buf));
155+
156+
// Enforce a maximum framerate (max 20 FPS / min 50ms between draws)
157+
// so we don't saturate SSH bandwidth or CPU during heavy, continuous
158+
// logging.
159+
std::chrono::steady_clock::time_point now =
160+
std::chrono::steady_clock::now();
161+
std::chrono::steady_clock::duration elapsed = now - last_draw_time;
162+
std::chrono::milliseconds min_frame_time(50);
163+
if (elapsed < min_frame_time) {
164+
std::this_thread::sleep_for(min_frame_time - elapsed);
165+
}
124166

125-
// Wait a bit before clearing and redrawing
126-
std::this_thread::sleep_for(std::chrono::milliseconds(50));
167+
// Drain any additional events that queued up in the meantime
168+
int bytes_available = 0;
169+
// NOLINTNEXTLINE(misc-include-cleaner)
170+
if (inotify_fd->Ioctl(FIONREAD, &bytes_available) == 0 &&
171+
bytes_available > 0) {
172+
std::vector<char> drain_buf(bytes_available);
173+
inotify_fd->Read(drain_buf.data(), bytes_available);
174+
}
127175
ClearLastNLines(total_lines_drawn);
128176
}
129177

0 commit comments

Comments
 (0)