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