blob: 32929278e5ff0f5c15efa44c9513d04441ebe38e [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
9#include "aos/events/shm_event_loop.h"
10#include "aos/json_to_flatbuffer.h"
11#include "aos/testing/path.h"
12#include "aos/testing/tmpdir.h"
13#include "gtest/gtest.h"
14
15namespace aos::util::testing {
16
17class TopTest : public ::testing::Test {
18 protected:
19 TopTest()
20 : shm_dir_(aos::testing::TestTmpDir() + "/aos"),
21 cpu_consumer_([this]() {
22 while (!stop_flag_.load()) {
23 }
24 }),
25 config_file_(
26 aos::testing::ArtifactPath("aos/events/pingpong_config.json")),
27 config_(aos::configuration::ReadConfig(config_file_)),
28 event_loop_(&config_.message()) {
29 FLAGS_shm_base = shm_dir_;
30
Austin Schuh60e77942022-05-16 17:48:24 -070031 // Nuke the shm dir, to ensure we aren't being affected by any preexisting
32 // tests.
James Kuszmaul418fd062022-03-22 15:22:27 -070033 aos::util::UnlinkRecursive(shm_dir_);
34 }
35 ~TopTest() {
36 stop_flag_ = true;
37 cpu_consumer_.join();
38 }
39
40 gflags::FlagSaver flag_saver_;
41 std::string shm_dir_;
42
43 std::thread cpu_consumer_;
44 std::atomic<bool> stop_flag_{false};
45 const std::string config_file_;
46 const aos::FlatbufferDetachedBuffer<aos::Configuration> config_;
47 aos::ShmEventLoop event_loop_;
48};
49
50TEST_F(TopTest, TestSelfStat) {
51 const pid_t pid = getpid();
52 std::optional<ProcStat> proc_stat = ReadProcStat(pid);
53 ASSERT_TRUE(proc_stat.has_value());
54 ASSERT_EQ(pid, proc_stat->pid);
55 ASSERT_EQ("top_test", proc_stat->name);
56 ASSERT_EQ('R', proc_stat->state);
57 ASSERT_LT(1, proc_stat->num_threads);
58}
59
60TEST_F(TopTest, QuerySingleProcess) {
61 const pid_t pid = getpid();
62 Top top(&event_loop_);
63 top.set_track_pids({pid});
64 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
65 ->Setup(event_loop_.monotonic_now() + std::chrono::seconds(2));
66 event_loop_.Run();
67 flatbuffers::FlatBufferBuilder fbb;
68 fbb.ForceDefaults(true);
69 fbb.Finish(top.InfoForProcess(&fbb, pid));
70 aos::FlatbufferDetachedBuffer<ProcessInfo> info = fbb.Release();
71 ASSERT_EQ(pid, info.message().pid());
72 ASSERT_TRUE(info.message().has_name());
73 ASSERT_EQ("top_test", info.message().name()->string_view());
74 // Check that we did indeed consume ~1 CPU core (because we're multi-threaded,
75 // we could've consumed a bit more; and on systems where we are competing with
76 // other processes for CPU time, we may not get a full 100% load).
77 ASSERT_LT(0.5, info.message().cpu_usage());
78 ASSERT_GT(1.1, info.message().cpu_usage());
79 // Sanity check memory usage.
80 ASSERT_LT(1000000, info.message().physical_memory());
81 ASSERT_GT(1000000000, info.message().physical_memory());
82}
83
84TEST_F(TopTest, TopProcesses) {
85 // Make some dummy processes that will just spin and get killed off at the
86 // end, so that we actually have things to query.
87 constexpr int kNProcesses = 2;
88 std::vector<pid_t> children;
89 // This will create kNProcesses children + ourself, which means we have enough
90 // processes to test that we correctly exclude extras when requesting fewer
91 // processes than exist.
92 for (int ii = 0; ii < kNProcesses; ++ii) {
93 const pid_t pid = fork();
94 PCHECK(pid >= 0);
95 if (pid == 0) {
James Kuszmaul860a94c2022-04-06 16:35:07 -070096 LOG(INFO) << "In child process.";
James Kuszmaul418fd062022-03-22 15:22:27 -070097 while (true) {
James Kuszmaul860a94c2022-04-06 16:35:07 -070098 // This is a "please don't optimize me out" thing for the compiler.
99 // Otherwise, the entire if (pid == 0) block can get optimized away...
100 asm("");
101 continue;
James Kuszmaul418fd062022-03-22 15:22:27 -0700102 }
James Kuszmaul860a94c2022-04-06 16:35:07 -0700103 LOG(FATAL) << "This should be unreachable.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700104 } else {
James Kuszmaul860a94c2022-04-06 16:35:07 -0700105 CHECK_NE(0, pid) << "The compiler is messing with you.";
James Kuszmaul418fd062022-03-22 15:22:27 -0700106 children.push_back(pid);
107 }
108 }
109
110 Top top(&event_loop_);
111 top.set_track_top_processes(true);
112 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
113 ->Setup(event_loop_.monotonic_now() + std::chrono::seconds(2));
114 event_loop_.SkipTimingReport();
115 event_loop_.SkipAosLog();
116 event_loop_.Run();
117 flatbuffers::FlatBufferBuilder fbb;
118 fbb.ForceDefaults(true);
119 fbb.Finish(top.TopProcesses(&fbb, kNProcesses));
120 aos::FlatbufferDetachedBuffer<TopProcessesFbs> info = fbb.Release();
121 ASSERT_EQ(kNProcesses, info.message().processes()->size());
122 double last_cpu = std::numeric_limits<double>::infinity();
123 std::set<pid_t> observed_pids;
124 int process_index = 0;
125 for (const ProcessInfo *info : *info.message().processes()) {
126 SCOPED_TRACE(aos::FlatbufferToJson(info));
127 ASSERT_EQ(0, observed_pids.count(info->pid()));
128 observed_pids.insert(info->pid());
129 ASSERT_TRUE(info->has_name());
130 // Confirm that the top process has non-zero CPU usage, but allow the
131 // lower-down processes to have not been scheduled in the last measurement
132 // cycle.
133 if (process_index < 1) {
134 ASSERT_LT(0.0, info->cpu_usage());
135 } else {
136 ASSERT_LE(0.0, info->cpu_usage());
137 }
138 ++process_index;
139 ASSERT_GE(last_cpu, info->cpu_usage());
140 last_cpu = info->cpu_usage();
141 ASSERT_LT(0, info->physical_memory());
142 }
143
144 for (const pid_t child : children) {
145 kill(child, SIGINT);
146 }
147}
148
149// Test thgat if we request arbitrarily many processes that we only get back as
150// many processes as actually exist and that nothing breaks.
151TEST_F(TopTest, AllTopProcesses) {
152 constexpr int kNProcesses = 1000000;
153
154 Top top(&event_loop_);
155 top.set_track_top_processes(true);
156 event_loop_.AddTimer([this]() { event_loop_.Exit(); })
157 ->Setup(event_loop_.monotonic_now() + std::chrono::seconds(2));
158 event_loop_.Run();
159 flatbuffers::FlatBufferBuilder fbb;
160 fbb.ForceDefaults(true);
161 // There should only be at most 2-3 processes visible inside the bazel
162 // sandbox.
163 fbb.Finish(top.TopProcesses(&fbb, kNProcesses));
164 aos::FlatbufferDetachedBuffer<TopProcessesFbs> info = fbb.Release();
165 ASSERT_GT(kNProcesses, info.message().processes()->size());
166 double last_cpu = std::numeric_limits<double>::infinity();
167 std::set<pid_t> observed_pids;
168 for (const ProcessInfo *info : *info.message().processes()) {
169 SCOPED_TRACE(aos::FlatbufferToJson(info));
170 LOG(INFO) << aos::FlatbufferToJson(info);
171 ASSERT_EQ(0, observed_pids.count(info->pid()));
172 observed_pids.insert(info->pid());
173 ASSERT_TRUE(info->has_name());
174 ASSERT_LE(0.0, info->cpu_usage());
175 ASSERT_GE(last_cpu, info->cpu_usage());
176 last_cpu = info->cpu_usage();
177 ASSERT_LE(0, info->physical_memory());
178 }
179}
180
181} // namespace aos::util::testing