blob: ce9986f64f541175a59dbaa0fd0a188fbe4d155e [file] [log] [blame]
James Kuszmaul418fd062022-03-22 15:22:27 -07001#include "aos/util/top.h"
2
3#include <unistd.h>
4
5#include <array>
6#include <string>
7#include <thread>
8
Philipp Schrader790cb542023-07-05 21:06:52 -07009#include "gtest/gtest.h"
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060010#include <gmock/gmock.h>
Philipp Schrader790cb542023-07-05 21:06:52 -070011
James Kuszmaul418fd062022-03-22 15:22:27 -070012#include "aos/events/shm_event_loop.h"
13#include "aos/json_to_flatbuffer.h"
14#include "aos/testing/path.h"
15#include "aos/testing/tmpdir.h"
James Kuszmaul418fd062022-03-22 15:22:27 -070016
17namespace aos::util::testing {
18
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060019void SetThreadName(const std::string &name) {
20 pthread_setname_np(pthread_self(), name.c_str());
21}
22
23constexpr std::string_view kTestCPUConsumer = "TestCPUConsumer";
24
James Kuszmaul418fd062022-03-22 15:22:27 -070025class TopTest : public ::testing::Test {
26 protected:
27 TopTest()
28 : shm_dir_(aos::testing::TestTmpDir() + "/aos"),
29 cpu_consumer_([this]() {
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060030 SetThreadName(std::string(kTestCPUConsumer));
James Kuszmaul418fd062022-03-22 15:22:27 -070031 while (!stop_flag_.load()) {
32 }
33 }),
34 config_file_(
35 aos::testing::ArtifactPath("aos/events/pingpong_config.json")),
36 config_(aos::configuration::ReadConfig(config_file_)),
37 event_loop_(&config_.message()) {
38 FLAGS_shm_base = shm_dir_;
39
Austin Schuh60e77942022-05-16 17:48:24 -070040 // Nuke the shm dir, to ensure we aren't being affected by any preexisting
41 // tests.
James Kuszmaul418fd062022-03-22 15:22:27 -070042 aos::util::UnlinkRecursive(shm_dir_);
43 }
44 ~TopTest() {
45 stop_flag_ = true;
46 cpu_consumer_.join();
47 }
48
49 gflags::FlagSaver flag_saver_;
50 std::string shm_dir_;
51
52 std::thread cpu_consumer_;
53 std::atomic<bool> stop_flag_{false};
54 const std::string config_file_;
55 const aos::FlatbufferDetachedBuffer<aos::Configuration> config_;
56 aos::ShmEventLoop event_loop_;
57};
58
59TEST_F(TopTest, TestSelfStat) {
60 const pid_t pid = getpid();
61 std::optional<ProcStat> proc_stat = ReadProcStat(pid);
62 ASSERT_TRUE(proc_stat.has_value());
63 ASSERT_EQ(pid, proc_stat->pid);
64 ASSERT_EQ("top_test", proc_stat->name);
65 ASSERT_EQ('R', proc_stat->state);
66 ASSERT_LT(1, proc_stat->num_threads);
67}
68
69TEST_F(TopTest, QuerySingleProcess) {
70 const pid_t pid = getpid();
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060071 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
72 Top::TrackPerThreadInfoMode::kDisabled);
James Kuszmaul418fd062022-03-22 15:22:27 -070073 top.set_track_pids({pid});
74 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
Philipp Schradera6712522023-07-05 20:25:11 -070075 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
James Kuszmaul418fd062022-03-22 15:22:27 -070076 event_loop_.Run();
77 flatbuffers::FlatBufferBuilder fbb;
78 fbb.ForceDefaults(true);
79 fbb.Finish(top.InfoForProcess(&fbb, pid));
80 aos::FlatbufferDetachedBuffer<ProcessInfo> info = fbb.Release();
81 ASSERT_EQ(pid, info.message().pid());
82 ASSERT_TRUE(info.message().has_name());
83 ASSERT_EQ("top_test", info.message().name()->string_view());
84 // Check that we did indeed consume ~1 CPU core (because we're multi-threaded,
85 // we could've consumed a bit more; and on systems where we are competing with
86 // other processes for CPU time, we may not get a full 100% load).
87 ASSERT_LT(0.5, info.message().cpu_usage());
88 ASSERT_GT(1.1, info.message().cpu_usage());
89 // Sanity check memory usage.
90 ASSERT_LT(1000000, info.message().physical_memory());
91 ASSERT_GT(1000000000, info.message().physical_memory());
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060092
93 // Verify no per-thread information is included by default.
94 ASSERT_FALSE(info.message().has_threads());
95}
96
97TEST_F(TopTest, QuerySingleProcessWithThreads) {
98 const pid_t pid = getpid();
99 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
100 Top::TrackPerThreadInfoMode::kEnabled);
101 top.set_track_pids({pid});
102 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
103 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
104 event_loop_.Run();
105 flatbuffers::FlatBufferBuilder fbb;
106 fbb.ForceDefaults(true);
107 fbb.Finish(top.InfoForProcess(&fbb, pid));
108 aos::FlatbufferDetachedBuffer<ProcessInfo> info = fbb.Release();
109 ASSERT_EQ(pid, info.message().pid());
110 ASSERT_TRUE(info.message().has_name());
111 ASSERT_EQ("top_test", info.message().name()->string_view());
112 // Check that we did indeed consume ~1 CPU core (because we're multi-threaded,
113 // we could've consumed a bit more; and on systems where we are competing with
114 // other processes for CPU time, we may not get a full 100% load).
115 ASSERT_LT(0.5, info.message().cpu_usage());
116 ASSERT_GT(1.1, info.message().cpu_usage());
117 // Sanity check memory usage.
118 ASSERT_LT(1000000, info.message().physical_memory());
119 ASSERT_GT(1000000000, info.message().physical_memory());
120
121 // Validate that we have some per-thread information.
122 ASSERT_TRUE(info.message().has_threads());
123 ASSERT_GT(info.message().threads()->size(), 0);
124 std::set<std::string_view> thread_names;
125 double thread_cpu_usage = 0.0;
126 for (const ThreadInfo *thread_info : *info.message().threads()) {
127 thread_names.insert(thread_info->name()->str());
128 thread_cpu_usage += thread_info->cpu_usage();
129 ASSERT_TRUE(thread_info->has_state());
130 }
131 // Validate that at least one thread was named correctly.
132 ASSERT_THAT(thread_names, ::testing::Contains(kTestCPUConsumer));
133 // Validate that we consumed at some cpu one a thread.
134 ASSERT_GT(thread_cpu_usage, 0);
James Kuszmaul418fd062022-03-22 15:22:27 -0700135}
136
137TEST_F(TopTest, TopProcesses) {
138 // Make some dummy processes that will just spin and get killed off at the
139 // end, so that we actually have things to query.
140 constexpr int kNProcesses = 2;
141 std::vector<pid_t> children;
142 // This will create kNProcesses children + ourself, which means we have enough
143 // processes to test that we correctly exclude extras when requesting fewer
144 // processes than exist.
145 for (int ii = 0; ii < kNProcesses; ++ii) {
146 const pid_t pid = fork();
147 PCHECK(pid >= 0);
148 if (pid == 0) {
James Kuszmaul860a94c2022-04-06 16:35:07 -0700149 LOG(INFO) << "In child process.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700150 while (true) {
James Kuszmaul860a94c2022-04-06 16:35:07 -0700151 // This is a "please don't optimize me out" thing for the compiler.
152 // Otherwise, the entire if (pid == 0) block can get optimized away...
153 asm("");
154 continue;
James Kuszmaul418fd062022-03-22 15:22:27 -0700155 }
James Kuszmaul860a94c2022-04-06 16:35:07 -0700156 LOG(FATAL) << "This should be unreachable.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700157 } else {
James Kuszmaul860a94c2022-04-06 16:35:07 -0700158 CHECK_NE(0, pid) << "The compiler is messing with you.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700159 children.push_back(pid);
160 }
161 }
162
Maxwell Gumleyb27245f2024-04-11 15:46:22 -0600163 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
164 Top::TrackPerThreadInfoMode::kDisabled);
James Kuszmaul418fd062022-03-22 15:22:27 -0700165 top.set_track_top_processes(true);
166 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
Philipp Schradera6712522023-07-05 20:25:11 -0700167 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
James Kuszmaul418fd062022-03-22 15:22:27 -0700168 event_loop_.SkipTimingReport();
169 event_loop_.SkipAosLog();
170 event_loop_.Run();
171 flatbuffers::FlatBufferBuilder fbb;
172 fbb.ForceDefaults(true);
173 fbb.Finish(top.TopProcesses(&fbb, kNProcesses));
174 aos::FlatbufferDetachedBuffer<TopProcessesFbs> info = fbb.Release();
175 ASSERT_EQ(kNProcesses, info.message().processes()->size());
176 double last_cpu = std::numeric_limits<double>::infinity();
177 std::set<pid_t> observed_pids;
178 int process_index = 0;
179 for (const ProcessInfo *info : *info.message().processes()) {
180 SCOPED_TRACE(aos::FlatbufferToJson(info));
181 ASSERT_EQ(0, observed_pids.count(info->pid()));
182 observed_pids.insert(info->pid());
183 ASSERT_TRUE(info->has_name());
184 // Confirm that the top process has non-zero CPU usage, but allow the
185 // lower-down processes to have not been scheduled in the last measurement
186 // cycle.
187 if (process_index < 1) {
188 ASSERT_LT(0.0, info->cpu_usage());
189 } else {
190 ASSERT_LE(0.0, info->cpu_usage());
191 }
192 ++process_index;
193 ASSERT_GE(last_cpu, info->cpu_usage());
194 last_cpu = info->cpu_usage();
195 ASSERT_LT(0, info->physical_memory());
196 }
197
198 for (const pid_t child : children) {
199 kill(child, SIGINT);
200 }
201}
202
203// Test thgat if we request arbitrarily many processes that we only get back as
204// many processes as actually exist and that nothing breaks.
205TEST_F(TopTest, AllTopProcesses) {
206 constexpr int kNProcesses = 1000000;
207
Maxwell Gumleyb27245f2024-04-11 15:46:22 -0600208 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
209 Top::TrackPerThreadInfoMode::kDisabled);
James Kuszmaul418fd062022-03-22 15:22:27 -0700210 top.set_track_top_processes(true);
211 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
Philipp Schradera6712522023-07-05 20:25:11 -0700212 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
James Kuszmaul418fd062022-03-22 15:22:27 -0700213 event_loop_.Run();
214 flatbuffers::FlatBufferBuilder fbb;
215 fbb.ForceDefaults(true);
216 // There should only be at most 2-3 processes visible inside the bazel
217 // sandbox.
218 fbb.Finish(top.TopProcesses(&fbb, kNProcesses));
219 aos::FlatbufferDetachedBuffer<TopProcessesFbs> info = fbb.Release();
220 ASSERT_GT(kNProcesses, info.message().processes()->size());
221 double last_cpu = std::numeric_limits<double>::infinity();
222 std::set<pid_t> observed_pids;
223 for (const ProcessInfo *info : *info.message().processes()) {
224 SCOPED_TRACE(aos::FlatbufferToJson(info));
225 LOG(INFO) << aos::FlatbufferToJson(info);
226 ASSERT_EQ(0, observed_pids.count(info->pid()));
227 observed_pids.insert(info->pid());
228 ASSERT_TRUE(info->has_name());
229 ASSERT_LE(0.0, info->cpu_usage());
230 ASSERT_GE(last_cpu, info->cpu_usage());
231 last_cpu = info->cpu_usage();
232 ASSERT_LE(0, info->physical_memory());
233 }
234}
235
236} // namespace aos::util::testing