1616
1717#include " cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.h"
1818
19+ #include < errno.h>
1920#include < fcntl.h>
21+ #include < poll.h>
22+ #include < sys/inotify.h>
23+ #include < sys/types.h>
2024#include < unistd.h>
2125
2226#include < chrono>
@@ -70,6 +74,16 @@ void ClearLastNLines(int n) {
7074 }
7175}
7276
77+ void UpdateFileAndWatch (const SharedFD& inotify_fd, const std::string& path,
78+ SharedFD& fd, int & watch) {
79+ if (!fd->IsOpen ()) {
80+ fd = SharedFD::Open (path, O_RDONLY );
81+ }
82+ if (fd->IsOpen () && watch == -1 ) {
83+ watch = inotify_fd->InotifyAddWatch (path, IN_MODIFY );
84+ }
85+ }
86+
7387} // namespace
7488
7589CvdMonitorCommandHandler::CvdMonitorCommandHandler (
@@ -82,33 +96,43 @@ Result<void> CvdMonitorCommandHandler::Handle(const CommandRequest& request) {
8296 std::vector<std::string> args = request.SubcommandArguments ();
8397 CF_EXPECT (ConsumeFlags ({}, args, {.fail_on_unexpected_argument = true }));
8498
85- auto [instance, unused] =
99+ const auto [instance, unused] =
86100 CF_EXPECT (selector::SelectInstance (instance_manager_, request),
87101 " Unable to select an instance" );
88102
89- std::string kernel_log =
103+ const std::string kernel_log =
90104 absl::StrCat (instance.InstanceDirectory (), " /logs/" , kLogNameKernel );
91- std::string launcher_log =
105+ const std::string launcher_log =
92106 absl::StrCat (instance.InstanceDirectory (), " /logs/" , kLogNameLauncher );
93- std::string logcat =
107+ const std::string logcat =
94108 absl::StrCat (instance.InstanceDirectory (), " /logs/" , kLogNameLogcat );
95109
96110 SharedFD kernel_fd;
97111 SharedFD launcher_fd;
98112 SharedFD logcat_fd;
99113
114+ const SharedFD inotify_fd = SharedFD::InotifyFd ();
115+ CF_EXPECT (inotify_fd->IsOpen (), " Failed to create inotify fd" );
116+ const int flags = inotify_fd->Fcntl (F_GETFL , 0 );
117+ CF_EXPECT (inotify_fd->Fcntl (F_SETFL , flags | O_NONBLOCK ) != -1 ,
118+ " Failed to set inotify fd to non-blocking" );
119+
120+ const std::string logs_dir =
121+ absl::StrCat (instance.InstanceDirectory (), " /logs" );
122+ const int dir_watch = inotify_fd->InotifyAddWatch (logs_dir, IN_CREATE );
123+
124+ int kernel_watch = -1 ;
125+ int launcher_watch = -1 ;
126+ int logcat_watch = -1 ;
127+ std::chrono::steady_clock::time_point last_draw_time =
128+ std::chrono::steady_clock::time_point ();
129+
100130 while (true ) {
101- if (!kernel_fd->IsOpen ()) {
102- kernel_fd = SharedFD::Open (kernel_log, O_RDONLY );
103- }
104- if (!launcher_fd->IsOpen ()) {
105- launcher_fd = SharedFD::Open (launcher_log, O_RDONLY );
106- }
107- if (!logcat_fd->IsOpen ()) {
108- logcat_fd = SharedFD::Open (logcat, O_RDONLY );
109- }
131+ UpdateFileAndWatch (inotify_fd, kernel_log, kernel_fd, kernel_watch);
132+ UpdateFileAndWatch (inotify_fd, launcher_log, launcher_fd, launcher_watch);
133+ UpdateFileAndWatch (inotify_fd, logcat, logcat_fd, logcat_watch);
110134
111- Result<TerminalSize> term_size_result = GetTerminalSize ();
135+ const Result<TerminalSize> term_size_result = GetTerminalSize ();
112136 int width = 79 ; // Default fallback width (80 - 1)
113137 if (term_size_result.ok ()) {
114138 width = term_size_result->columns - 1 ;
@@ -119,11 +143,47 @@ Result<void> CvdMonitorCommandHandler::Handle(const CommandRequest& request) {
119143 display.DrawFile (kernel_fd, kLogNameKernel );
120144 display.DrawFile (logcat_fd, kLogNameLogcat );
121145
122- auto [output, total_lines_drawn] = display.Finalize ();
146+ const auto [output, total_lines_drawn] = display.Finalize ();
123147 std::cout << output << std::flush;
148+ // Enforce a maximum framerate (max 20 FPS / min 50ms between draws)
149+ // so we don't saturate SSH bandwidth or CPU during heavy, continuous
150+ // logging.
151+ const std::chrono::steady_clock::time_point now =
152+ std::chrono::steady_clock::now ();
153+ const std::chrono::steady_clock::duration elapsed = now - last_draw_time;
154+ constexpr std::chrono::milliseconds min_frame_time (50 );
155+ if (elapsed < min_frame_time) {
156+ std::this_thread::sleep_for (min_frame_time - elapsed);
157+ }
158+ last_draw_time = std::chrono::steady_clock::now ();
159+
160+ // Block until file changes occur. If any watch failed or is missing, use
161+ // a 1-second fallback timeout to awake and retry.
162+ // NOLINTNEXTLINE(misc-include-cleaner)
163+ PollSharedFd poll_fd = {inotify_fd, POLLIN , 0 };
164+ int timeout_ms = -1 ;
165+ if (dir_watch == -1 || kernel_watch == -1 || launcher_watch == -1 ||
166+ logcat_watch == -1 ) {
167+ timeout_ms = 1000 ;
168+ }
124169
125- // Wait a bit before clearing and redrawing
126- std::this_thread::sleep_for (std::chrono::milliseconds (50 ));
170+ // NOLINTNEXTLINE(misc-include-cleaner)
171+ if (SharedFD::Poll (&poll_fd, 1 , timeout_ms) > 0 &&
172+ (poll_fd.revents & POLLIN )) {
173+ // Exhaustively drain all available events from the non-blocking
174+ // descriptor to coalesce rapid file modifications.
175+ char buf[4096 ]
176+ __attribute__ ((aligned (__alignof__ (struct inotify_event ))));
177+ ssize_t read_res = 0 ;
178+ while ((read_res = inotify_fd->Read (buf, sizeof (buf))) > 0 ) {
179+ }
180+ CF_EXPECT (read_res != 0 ,
181+ " Unexpected End-of-File reading inotify descriptor" );
182+ const int err = inotify_fd->GetErrno ();
183+ CF_EXPECTF (err == EAGAIN || err == EWOULDBLOCK ,
184+ " Unexpected error reading inotify descriptor: {} ({})" ,
185+ inotify_fd->StrError (), err);
186+ }
127187 ClearLastNLines (total_lines_drawn);
128188 }
129189
0 commit comments