Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

#include "cuttlefish/host/commands/cvd/cli/commands/monitor/command_handler.h"

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <unistd.h>

#include <chrono>
Expand Down Expand Up @@ -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(
Expand All @@ -82,33 +96,43 @@ Result<void> CvdMonitorCommandHandler::Handle(const CommandRequest& request) {
std::vector<std::string> 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<TerminalSize> term_size_result = GetTerminalSize();
const Result<TerminalSize> term_size_result = GetTerminalSize();
int width = 79; // Default fallback width (80 - 1)
if (term_size_result.ok()) {
width = term_size_result->columns - 1;
Expand All @@ -119,11 +143,47 @@ Result<void> 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);
Comment thread
Databean marked this conversation as resolved.
}
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);
}

Expand Down
Loading