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