blob: 9dc9c8cceb8659e1f7fecd8dfc7aca21f2d8a8dc [file] [log] [blame]
James Kuszmaul418fd062022-03-22 15:22:27 -07001#include "aos/util/top.h"
2
Stephan Pleinesb1177672024-05-27 17:48:32 -07003#include <pthread.h>
4#include <signal.h>
James Kuszmaul418fd062022-03-22 15:22:27 -07005#include <unistd.h>
6
Stephan Pleinesb1177672024-05-27 17:48:32 -07007#include <algorithm>
8#include <atomic>
9#include <limits>
10#include <ostream>
James Kuszmaul418fd062022-03-22 15:22:27 -070011#include <string>
Stephan Pleinesb1177672024-05-27 17:48:32 -070012#include <string_view>
James Kuszmaul418fd062022-03-22 15:22:27 -070013#include <thread>
Stephan Pleinesb1177672024-05-27 17:48:32 -070014#include <vector>
James Kuszmaul418fd062022-03-22 15:22:27 -070015
Austin Schuh99f7c6a2024-06-25 22:07:44 -070016#include "absl/flags/flag.h"
17#include "absl/flags/reflection.h"
18#include "absl/log/check.h"
19#include "absl/log/log.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070020#include "flatbuffers/string.h"
21#include "flatbuffers/vector.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070022#include "gtest/gtest.h"
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060023#include <gmock/gmock.h>
Philipp Schrader790cb542023-07-05 21:06:52 -070024
Stephan Pleinesb1177672024-05-27 17:48:32 -070025#include "aos/configuration.h"
James Kuszmaul418fd062022-03-22 15:22:27 -070026#include "aos/events/shm_event_loop.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070027#include "aos/flatbuffers.h"
28#include "aos/ipc_lib/shm_base.h"
James Kuszmaul418fd062022-03-22 15:22:27 -070029#include "aos/json_to_flatbuffer.h"
30#include "aos/testing/path.h"
31#include "aos/testing/tmpdir.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070032#include "aos/util/file.h"
James Kuszmaul418fd062022-03-22 15:22:27 -070033
34namespace aos::util::testing {
35
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060036void SetThreadName(const std::string &name) {
37 pthread_setname_np(pthread_self(), name.c_str());
38}
39
40constexpr std::string_view kTestCPUConsumer = "TestCPUConsumer";
41
James Kuszmaul418fd062022-03-22 15:22:27 -070042class TopTest : public ::testing::Test {
43 protected:
44 TopTest()
Austin Schuh99f7c6a2024-06-25 22:07:44 -070045 : shm_dir_(aos::testing::TestTmpDir()),
James Kuszmaul418fd062022-03-22 15:22:27 -070046 cpu_consumer_([this]() {
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060047 SetThreadName(std::string(kTestCPUConsumer));
James Kuszmaul418fd062022-03-22 15:22:27 -070048 while (!stop_flag_.load()) {
49 }
50 }),
51 config_file_(
52 aos::testing::ArtifactPath("aos/events/pingpong_config.json")),
53 config_(aos::configuration::ReadConfig(config_file_)),
54 event_loop_(&config_.message()) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -070055 aos::testing::SetShmBase(shm_dir_);
James Kuszmaul418fd062022-03-22 15:22:27 -070056
Austin Schuh60e77942022-05-16 17:48:24 -070057 // Nuke the shm dir, to ensure we aren't being affected by any preexisting
58 // tests.
Austin Schuh99f7c6a2024-06-25 22:07:44 -070059 aos::util::UnlinkRecursive(shm_dir_ + "/aos");
James Kuszmaul418fd062022-03-22 15:22:27 -070060 }
61 ~TopTest() {
62 stop_flag_ = true;
63 cpu_consumer_.join();
64 }
65
Austin Schuh99f7c6a2024-06-25 22:07:44 -070066 absl::FlagSaver flag_saver_;
James Kuszmaul418fd062022-03-22 15:22:27 -070067 std::string shm_dir_;
68
69 std::thread cpu_consumer_;
70 std::atomic<bool> stop_flag_{false};
71 const std::string config_file_;
72 const aos::FlatbufferDetachedBuffer<aos::Configuration> config_;
73 aos::ShmEventLoop event_loop_;
74};
75
76TEST_F(TopTest, TestSelfStat) {
77 const pid_t pid = getpid();
78 std::optional<ProcStat> proc_stat = ReadProcStat(pid);
79 ASSERT_TRUE(proc_stat.has_value());
80 ASSERT_EQ(pid, proc_stat->pid);
81 ASSERT_EQ("top_test", proc_stat->name);
82 ASSERT_EQ('R', proc_stat->state);
83 ASSERT_LT(1, proc_stat->num_threads);
84}
85
86TEST_F(TopTest, QuerySingleProcess) {
87 const pid_t pid = getpid();
Maxwell Gumleyb27245f2024-04-11 15:46:22 -060088 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
89 Top::TrackPerThreadInfoMode::kDisabled);
James Kuszmaul418fd062022-03-22 15:22:27 -070090 top.set_track_pids({pid});
91 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
Philipp Schradera6712522023-07-05 20:25:11 -070092 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
James Kuszmaul418fd062022-03-22 15:22:27 -070093 event_loop_.Run();
94 flatbuffers::FlatBufferBuilder fbb;
95 fbb.ForceDefaults(true);
96 fbb.Finish(top.InfoForProcess(&fbb, pid));
97 aos::FlatbufferDetachedBuffer<ProcessInfo> info = fbb.Release();
98 ASSERT_EQ(pid, info.message().pid());
99 ASSERT_TRUE(info.message().has_name());
100 ASSERT_EQ("top_test", info.message().name()->string_view());
101 // Check that we did indeed consume ~1 CPU core (because we're multi-threaded,
102 // we could've consumed a bit more; and on systems where we are competing with
103 // other processes for CPU time, we may not get a full 100% load).
104 ASSERT_LT(0.5, info.message().cpu_usage());
105 ASSERT_GT(1.1, info.message().cpu_usage());
106 // Sanity check memory usage.
107 ASSERT_LT(1000000, info.message().physical_memory());
108 ASSERT_GT(1000000000, info.message().physical_memory());
Maxwell Gumleyb27245f2024-04-11 15:46:22 -0600109
110 // Verify no per-thread information is included by default.
111 ASSERT_FALSE(info.message().has_threads());
112}
113
114TEST_F(TopTest, QuerySingleProcessWithThreads) {
115 const pid_t pid = getpid();
116 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
117 Top::TrackPerThreadInfoMode::kEnabled);
118 top.set_track_pids({pid});
119 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
120 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
121 event_loop_.Run();
122 flatbuffers::FlatBufferBuilder fbb;
123 fbb.ForceDefaults(true);
124 fbb.Finish(top.InfoForProcess(&fbb, pid));
125 aos::FlatbufferDetachedBuffer<ProcessInfo> info = fbb.Release();
126 ASSERT_EQ(pid, info.message().pid());
127 ASSERT_TRUE(info.message().has_name());
128 ASSERT_EQ("top_test", info.message().name()->string_view());
129 // Check that we did indeed consume ~1 CPU core (because we're multi-threaded,
130 // we could've consumed a bit more; and on systems where we are competing with
131 // other processes for CPU time, we may not get a full 100% load).
132 ASSERT_LT(0.5, info.message().cpu_usage());
133 ASSERT_GT(1.1, info.message().cpu_usage());
134 // Sanity check memory usage.
135 ASSERT_LT(1000000, info.message().physical_memory());
136 ASSERT_GT(1000000000, info.message().physical_memory());
137
138 // Validate that we have some per-thread information.
139 ASSERT_TRUE(info.message().has_threads());
140 ASSERT_GT(info.message().threads()->size(), 0);
141 std::set<std::string_view> thread_names;
142 double thread_cpu_usage = 0.0;
143 for (const ThreadInfo *thread_info : *info.message().threads()) {
144 thread_names.insert(thread_info->name()->str());
145 thread_cpu_usage += thread_info->cpu_usage();
146 ASSERT_TRUE(thread_info->has_state());
147 }
148 // Validate that at least one thread was named correctly.
149 ASSERT_THAT(thread_names, ::testing::Contains(kTestCPUConsumer));
150 // Validate that we consumed at some cpu one a thread.
151 ASSERT_GT(thread_cpu_usage, 0);
James Kuszmaul418fd062022-03-22 15:22:27 -0700152}
153
154TEST_F(TopTest, TopProcesses) {
155 // Make some dummy processes that will just spin and get killed off at the
156 // end, so that we actually have things to query.
157 constexpr int kNProcesses = 2;
158 std::vector<pid_t> children;
159 // This will create kNProcesses children + ourself, which means we have enough
160 // processes to test that we correctly exclude extras when requesting fewer
161 // processes than exist.
162 for (int ii = 0; ii < kNProcesses; ++ii) {
163 const pid_t pid = fork();
164 PCHECK(pid >= 0);
165 if (pid == 0) {
James Kuszmaul860a94c2022-04-06 16:35:07 -0700166 LOG(INFO) << "In child process.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700167 while (true) {
James Kuszmaul860a94c2022-04-06 16:35:07 -0700168 // This is a "please don't optimize me out" thing for the compiler.
169 // Otherwise, the entire if (pid == 0) block can get optimized away...
170 asm("");
171 continue;
James Kuszmaul418fd062022-03-22 15:22:27 -0700172 }
James Kuszmaul860a94c2022-04-06 16:35:07 -0700173 LOG(FATAL) << "This should be unreachable.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700174 } else {
James Kuszmaul860a94c2022-04-06 16:35:07 -0700175 CHECK_NE(0, pid) << "The compiler is messing with you.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700176 children.push_back(pid);
177 }
178 }
179
Maxwell Gumleyb27245f2024-04-11 15:46:22 -0600180 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
181 Top::TrackPerThreadInfoMode::kDisabled);
James Kuszmaul418fd062022-03-22 15:22:27 -0700182 top.set_track_top_processes(true);
183 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
Philipp Schradera6712522023-07-05 20:25:11 -0700184 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
James Kuszmaul418fd062022-03-22 15:22:27 -0700185 event_loop_.SkipTimingReport();
186 event_loop_.SkipAosLog();
187 event_loop_.Run();
188 flatbuffers::FlatBufferBuilder fbb;
189 fbb.ForceDefaults(true);
190 fbb.Finish(top.TopProcesses(&fbb, kNProcesses));
191 aos::FlatbufferDetachedBuffer<TopProcessesFbs> info = fbb.Release();
192 ASSERT_EQ(kNProcesses, info.message().processes()->size());
193 double last_cpu = std::numeric_limits<double>::infinity();
194 std::set<pid_t> observed_pids;
195 int process_index = 0;
196 for (const ProcessInfo *info : *info.message().processes()) {
197 SCOPED_TRACE(aos::FlatbufferToJson(info));
198 ASSERT_EQ(0, observed_pids.count(info->pid()));
199 observed_pids.insert(info->pid());
200 ASSERT_TRUE(info->has_name());
201 // Confirm that the top process has non-zero CPU usage, but allow the
202 // lower-down processes to have not been scheduled in the last measurement
203 // cycle.
204 if (process_index < 1) {
205 ASSERT_LT(0.0, info->cpu_usage());
206 } else {
207 ASSERT_LE(0.0, info->cpu_usage());
208 }
209 ++process_index;
210 ASSERT_GE(last_cpu, info->cpu_usage());
211 last_cpu = info->cpu_usage();
212 ASSERT_LT(0, info->physical_memory());
213 }
214
215 for (const pid_t child : children) {
216 kill(child, SIGINT);
217 }
218}
219
220// Test thgat if we request arbitrarily many processes that we only get back as
221// many processes as actually exist and that nothing breaks.
222TEST_F(TopTest, AllTopProcesses) {
223 constexpr int kNProcesses = 1000000;
224
Maxwell Gumleyb27245f2024-04-11 15:46:22 -0600225 Top top(&event_loop_, Top::TrackThreadsMode::kDisabled,
226 Top::TrackPerThreadInfoMode::kDisabled);
James Kuszmaul418fd062022-03-22 15:22:27 -0700227 top.set_track_top_processes(true);
228 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
Philipp Schradera6712522023-07-05 20:25:11 -0700229 ->Schedule(event_loop_.monotonic_now() + std::chrono::seconds(2));
James Kuszmaul418fd062022-03-22 15:22:27 -0700230 event_loop_.Run();
231 flatbuffers::FlatBufferBuilder fbb;
232 fbb.ForceDefaults(true);
233 // There should only be at most 2-3 processes visible inside the bazel
234 // sandbox.
235 fbb.Finish(top.TopProcesses(&fbb, kNProcesses));
236 aos::FlatbufferDetachedBuffer<TopProcessesFbs> info = fbb.Release();
237 ASSERT_GT(kNProcesses, info.message().processes()->size());
238 double last_cpu = std::numeric_limits<double>::infinity();
239 std::set<pid_t> observed_pids;
240 for (const ProcessInfo *info : *info.message().processes()) {
241 SCOPED_TRACE(aos::FlatbufferToJson(info));
242 LOG(INFO) << aos::FlatbufferToJson(info);
243 ASSERT_EQ(0, observed_pids.count(info->pid()));
244 observed_pids.insert(info->pid());
245 ASSERT_TRUE(info->has_name());
246 ASSERT_LE(0.0, info->cpu_usage());
247 ASSERT_GE(last_cpu, info->cpu_usage());
248 last_cpu = info->cpu_usage();
249 ASSERT_LE(0, info->physical_memory());
250 }
251}
252
253} // namespace aos::util::testing