Skip to content

Commit 52e1551

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. 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
1 parent b2eb0a4 commit 52e1551

1 file changed

Lines changed: 77 additions & 17 deletions

File tree

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

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
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

7589
CvdMonitorCommandHandler::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

Comments
 (0)