Skip to content

Commit b655c2e

Browse files
committed
Extract cvd monitor functionality into a library
Refactors the `cvd monitor` command to extract its core monitoring logic into a reusable library (`monitor`). This allows other commands to easily invoke log monitoring. Assisted-by: Jetski:Gemini Next Bug: b/519304405
1 parent 4f5b412 commit b655c2e

4 files changed

Lines changed: 212 additions & 135 deletions

File tree

base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/BUILD.bazel

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ cf_cc_library(
1818
srcs = ["command_handler.cc"],
1919
hdrs = ["command_handler.h"],
2020
deps = [
21-
":ansi_codes",
22-
":display",
23-
"//cuttlefish/common/libs/fs",
21+
":monitor",
2422
"//cuttlefish/flag_parser",
2523
"//cuttlefish/host/commands/cvd/cli:command_request",
2624
"//cuttlefish/host/commands/cvd/cli:types",
@@ -29,10 +27,8 @@ cf_cc_library(
2927
"//cuttlefish/host/commands/cvd/cli/selector",
3028
"//cuttlefish/host/commands/cvd/instances",
3129
"//cuttlefish/host/commands/cvd/instances:instance_manager",
32-
"//cuttlefish/host/libs/log_names",
3330
"//cuttlefish/result",
3431
"//libbase",
35-
"@abseil-cpp//absl/strings",
3632
],
3733
)
3834

@@ -142,6 +138,31 @@ cf_cc_library(
142138
],
143139
)
144140

141+
cf_cc_test(
142+
name = "logcat_test",
143+
srcs = ["logcat_test.cc"],
144+
deps = [
145+
":logcat",
146+
"//cuttlefish/result:result_matchers",
147+
],
148+
)
149+
150+
cf_cc_library(
151+
name = "monitor",
152+
srcs = ["monitor.cc"],
153+
hdrs = ["monitor.h"],
154+
deps = [
155+
":ansi_codes",
156+
":display",
157+
"//cuttlefish/common/libs/fs",
158+
"//cuttlefish/host/commands/cvd/cli:utils",
159+
"//cuttlefish/host/commands/cvd/instances",
160+
"//cuttlefish/host/libs/log_names",
161+
"//cuttlefish/result",
162+
"@abseil-cpp//absl/strings",
163+
],
164+
)
165+
145166
cf_cc_library(
146167
name = "truncate",
147168
srcs = ["truncate.cc"],
@@ -162,15 +183,6 @@ cf_cc_test(
162183
],
163184
)
164185

165-
cf_cc_test(
166-
name = "logcat_test",
167-
srcs = ["logcat_test.cc"],
168-
deps = [
169-
":logcat",
170-
"//cuttlefish/result:result_matchers",
171-
],
172-
)
173-
174186
cf_cc_library(
175187
name = "verbosity",
176188
srcs = ["verbosity.cc"],

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

Lines changed: 2 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,19 @@
1616

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

19-
#include <errno.h>
20-
#include <fcntl.h>
21-
#include <poll.h>
22-
#include <sys/inotify.h>
23-
#include <sys/types.h>
2419
#include <unistd.h>
2520

26-
#include <chrono>
27-
#include <iostream>
2821
#include <memory>
2922
#include <string>
30-
#include <thread>
31-
#include <utility>
3223
#include <vector>
3324

34-
#include "absl/strings/str_cat.h"
35-
36-
#include "cuttlefish/common/libs/fs/shared_fd.h"
3725
#include "cuttlefish/flag_parser/flag.h"
3826
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
3927
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
40-
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/ansi_codes.h"
41-
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/display.h"
28+
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/monitor.h"
4229
#include "cuttlefish/host/commands/cvd/cli/selector/selector.h"
4330
#include "cuttlefish/host/commands/cvd/cli/types.h"
44-
#include "cuttlefish/host/commands/cvd/cli/utils.h"
4531
#include "cuttlefish/host/commands/cvd/instances/instance_manager.h"
46-
#include "cuttlefish/host/libs/log_names/log_names.h"
4732
#include "cuttlefish/result/result.h"
4833

4934
namespace cuttlefish {
@@ -67,23 +52,6 @@ It displays:
6752

6853
constexpr char kMonitorCmd[] = "monitor";
6954

70-
void ClearLastNLines(int n) {
71-
if (n > 0) {
72-
// Move cursor up N lines and clear to end of screen
73-
std::cout << AnsiCursorUp(n) << kAnsiClearScreenAfterCursor << std::flush;
74-
}
75-
}
76-
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-
8755
} // namespace
8856

8957
CvdMonitorCommandHandler::CvdMonitorCommandHandler(
@@ -100,94 +68,7 @@ Result<void> CvdMonitorCommandHandler::Handle(const CommandRequest& request) {
10068
CF_EXPECT(selector::SelectInstance(instance_manager_, request),
10169
"Unable to select an instance");
10270

103-
const std::string kernel_log =
104-
absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameKernel);
105-
const std::string launcher_log =
106-
absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameLauncher);
107-
const std::string logcat =
108-
absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameLogcat);
109-
110-
SharedFD kernel_fd;
111-
SharedFD launcher_fd;
112-
SharedFD logcat_fd;
113-
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-
130-
while (true) {
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);
134-
135-
const Result<TerminalSize> term_size_result = GetTerminalSize();
136-
int width = 79; // Default fallback width (80 - 1)
137-
if (term_size_result.ok()) {
138-
width = term_size_result->columns - 1;
139-
}
140-
LogMonitorDisplay display(width);
141-
142-
display.DrawFile(launcher_fd, kLogNameLauncher);
143-
display.DrawFile(kernel_fd, kLogNameKernel);
144-
display.DrawFile(logcat_fd, kLogNameLogcat);
145-
146-
const auto [output, total_lines_drawn] = display.Finalize();
147-
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-
}
169-
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-
}
187-
ClearLastNLines(total_lines_drawn);
188-
}
189-
190-
return {};
71+
return MonitorLogs(instance);
19172
}
19273

19374
cvd_common::Args CvdMonitorCommandHandler::CmdList() const {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright (C) 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/monitor.h"
18+
19+
#include <errno.h>
20+
#include <fcntl.h>
21+
#include <poll.h>
22+
#include <sys/inotify.h>
23+
#include <sys/types.h>
24+
#include <unistd.h>
25+
26+
#include <chrono>
27+
#include <iostream>
28+
#include <memory>
29+
#include <string>
30+
#include <thread>
31+
32+
#include "absl/strings/str_cat.h"
33+
34+
#include "cuttlefish/common/libs/fs/shared_fd.h"
35+
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/ansi_codes.h"
36+
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/display.h"
37+
#include "cuttlefish/host/commands/cvd/cli/utils.h"
38+
#include "cuttlefish/host/commands/cvd/instances/local_instance.h"
39+
#include "cuttlefish/host/libs/log_names/log_names.h"
40+
#include "cuttlefish/result/result.h"
41+
42+
namespace cuttlefish {
43+
44+
namespace {
45+
46+
void ClearLastNLines(int n) {
47+
if (n > 0) {
48+
// Move cursor up N lines and clear to end of screen
49+
std::cout << AnsiCursorUp(n) << kAnsiClearScreenAfterCursor << std::flush;
50+
}
51+
}
52+
53+
void UpdateFileAndWatch(const SharedFD& inotify_fd, const std::string& path,
54+
SharedFD& fd, int& watch) {
55+
if (!fd->IsOpen()) {
56+
fd = SharedFD::Open(path, O_RDONLY);
57+
}
58+
if (fd->IsOpen() && watch == -1) {
59+
watch = inotify_fd->InotifyAddWatch(path, IN_MODIFY);
60+
}
61+
}
62+
63+
} // namespace
64+
65+
Result<void> MonitorLogs(const LocalInstance& instance) {
66+
CF_EXPECT(isatty(0), "The monitor command requires an interactive terminal.");
67+
68+
const std::string kernel_log =
69+
absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameKernel);
70+
const std::string launcher_log =
71+
absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameLauncher);
72+
const std::string logcat =
73+
absl::StrCat(instance.InstanceDirectory(), "/logs/", kLogNameLogcat);
74+
75+
SharedFD kernel_fd;
76+
SharedFD launcher_fd;
77+
SharedFD logcat_fd;
78+
79+
const SharedFD inotify_fd = SharedFD::InotifyFd();
80+
CF_EXPECT(inotify_fd->IsOpen(), "Failed to create inotify fd");
81+
const int flags = inotify_fd->Fcntl(F_GETFL, 0);
82+
CF_EXPECT(inotify_fd->Fcntl(F_SETFL, flags | O_NONBLOCK) != -1,
83+
"Failed to set inotify fd to non-blocking");
84+
85+
const std::string logs_dir =
86+
absl::StrCat(instance.InstanceDirectory(), "/logs");
87+
const int dir_watch = inotify_fd->InotifyAddWatch(logs_dir, IN_CREATE);
88+
89+
int kernel_watch = -1;
90+
int launcher_watch = -1;
91+
int logcat_watch = -1;
92+
std::chrono::steady_clock::time_point last_draw_time =
93+
std::chrono::steady_clock::time_point();
94+
95+
while (true) {
96+
UpdateFileAndWatch(inotify_fd, kernel_log, kernel_fd, kernel_watch);
97+
UpdateFileAndWatch(inotify_fd, launcher_log, launcher_fd, launcher_watch);
98+
UpdateFileAndWatch(inotify_fd, logcat, logcat_fd, logcat_watch);
99+
100+
const Result<TerminalSize> term_size_result = GetTerminalSize();
101+
int width = 79; // Default fallback width (80 - 1)
102+
if (term_size_result.ok()) {
103+
width = term_size_result->columns - 1;
104+
}
105+
LogMonitorDisplay display(width);
106+
107+
display.DrawFile(launcher_fd, kLogNameLauncher);
108+
display.DrawFile(kernel_fd, kLogNameKernel);
109+
display.DrawFile(logcat_fd, kLogNameLogcat);
110+
111+
const auto [output, total_lines_drawn] = display.Finalize();
112+
std::cout << output << std::flush;
113+
// Enforce a maximum framerate (max 20 FPS / min 50ms between draws)
114+
// so we don't saturate SSH bandwidth or CPU during heavy, continuous
115+
// logging.
116+
const std::chrono::steady_clock::time_point now =
117+
std::chrono::steady_clock::now();
118+
const std::chrono::steady_clock::duration elapsed = now - last_draw_time;
119+
constexpr std::chrono::milliseconds min_frame_time(50);
120+
if (elapsed < min_frame_time) {
121+
std::this_thread::sleep_for(min_frame_time - elapsed);
122+
}
123+
last_draw_time = std::chrono::steady_clock::now();
124+
125+
// Block until file changes occur. If any watch failed or is missing, use
126+
// a 1-second fallback timeout to awake and retry.
127+
// NOLINTNEXTLINE(misc-include-cleaner)
128+
PollSharedFd poll_fd = {inotify_fd, POLLIN, 0};
129+
int timeout_ms = -1;
130+
if (dir_watch == -1 || kernel_watch == -1 || launcher_watch == -1 ||
131+
logcat_watch == -1) {
132+
timeout_ms = 1000;
133+
}
134+
135+
// NOLINTNEXTLINE(misc-include-cleaner)
136+
if (SharedFD::Poll(&poll_fd, 1, timeout_ms) > 0 &&
137+
(poll_fd.revents & POLLIN)) {
138+
// Exhaustively drain all available events from the non-blocking
139+
// descriptor to coalesce rapid file modifications.
140+
char buf[4096]
141+
__attribute__((aligned(__alignof__(struct inotify_event))));
142+
ssize_t read_res = 0;
143+
while ((read_res = inotify_fd->Read(buf, sizeof(buf))) > 0) {
144+
}
145+
CF_EXPECT(read_res != 0,
146+
"Unexpected End-of-File reading inotify descriptor");
147+
const int err = inotify_fd->GetErrno();
148+
CF_EXPECTF(err == EAGAIN || err == EWOULDBLOCK,
149+
"Unexpected error reading inotify descriptor: {} ({})",
150+
inotify_fd->StrError(), err);
151+
}
152+
ClearLastNLines(total_lines_drawn);
153+
}
154+
155+
return {};
156+
}
157+
158+
} // namespace cuttlefish

0 commit comments

Comments
 (0)