blob: 8767cd4bcb14afc5e102fee4b891696c3018baa8 [file] [log] [blame]
James Kuszmaul418fd062022-03-22 15:22:27 -07001#include "aos/util/top.h"
2
3#include <dirent.h>
4#include <unistd.h>
5
6#include <queue>
7#include <string>
8
9#include "absl/strings/numbers.h"
10#include "absl/strings/str_format.h"
11#include "absl/strings/str_split.h"
12
Austin Schuh979d4772022-12-30 14:50:41 -080013#define PF_KTHREAD 0x00200000
14
James Kuszmaul418fd062022-03-22 15:22:27 -070015namespace aos::util {
16namespace {
17std::optional<std::string> ReadShortFile(std::string_view file_name) {
18 // Open as input and seek to end immediately.
19 std::ifstream file(std::string(file_name), std::ios_base::in);
20 if (!file.good()) {
21 VLOG(1) << "Can't read " << file_name;
22 return std::nullopt;
23 }
24 const size_t kMaxLineLength = 4096;
25 char buffer[kMaxLineLength];
26 file.read(buffer, kMaxLineLength);
27 if (!file.eof()) {
28 return std::nullopt;
29 }
30 return std::string(buffer, file.gcount());
31}
32} // namespace
33
34std::optional<ProcStat> ReadProcStat(pid_t pid) {
35 std::optional<std::string> contents =
36 ReadShortFile(absl::StrFormat("/proc/%d/stat", pid));
37 if (!contents.has_value()) {
38 return std::nullopt;
39 }
40 const size_t start_name = contents->find_first_of('(');
41 const size_t end_name = contents->find_last_of(')');
42 if (start_name == std::string::npos || end_name == std::string::npos ||
43 end_name < start_name) {
44 VLOG(1) << "No name found in stat line " << contents.value();
45 return std::nullopt;
46 }
47 std::string_view name(contents->c_str() + start_name + 1,
48 end_name - start_name - 1);
49
50 std::vector<std::string_view> fields =
51 absl::StrSplit(std::string_view(contents->c_str() + end_name + 1,
52 contents->size() - end_name - 1),
53 ' ', absl::SkipWhitespace());
54 constexpr int kNumFieldsAfterName = 50;
55 if (fields.size() != kNumFieldsAfterName) {
56 VLOG(1) << "Incorrect number of fields " << fields.size();
57 return std::nullopt;
58 }
59 // The first field is a character for the current process state; every single
60 // field after that should be an integer.
61 if (fields[0].size() != 1) {
62 VLOG(1) << "State field is too long: " << fields[0];
63 return std::nullopt;
64 }
65 std::array<absl::int128, kNumFieldsAfterName - 1> numbers;
66 for (int ii = 1; ii < kNumFieldsAfterName; ++ii) {
67 if (!absl::SimpleAtoi(fields[ii], &numbers[ii - 1])) {
68 VLOG(1) << "Failed to parse field " << ii << " as number: " << fields[ii];
69 return std::nullopt;
70 }
71 }
72 return ProcStat{
73 .pid = pid,
74 .name = std::string(name),
75 .state = fields.at(0).at(0),
76 .parent_pid = static_cast<int64_t>(numbers.at(0)),
77 .group_id = static_cast<int64_t>(numbers.at(1)),
78 .session_id = static_cast<int64_t>(numbers.at(2)),
79 .tty = static_cast<int64_t>(numbers.at(3)),
80 .tpgid = static_cast<int64_t>(numbers.at(4)),
81 .kernel_flags = static_cast<uint64_t>(numbers.at(5)),
82 .minor_faults = static_cast<uint64_t>(numbers.at(6)),
83 .children_minor_faults = static_cast<uint64_t>(numbers.at(7)),
84 .major_faults = static_cast<uint64_t>(numbers.at(8)),
85 .children_major_faults = static_cast<uint64_t>(numbers.at(9)),
86 .user_mode_ticks = static_cast<uint64_t>(numbers.at(10)),
87 .kernel_mode_ticks = static_cast<uint64_t>(numbers.at(11)),
88 .children_user_mode_ticks = static_cast<int64_t>(numbers.at(12)),
89 .children_kernel_mode_ticks = static_cast<int64_t>(numbers.at(13)),
90 .priority = static_cast<int64_t>(numbers.at(14)),
91 .nice = static_cast<int64_t>(numbers.at(15)),
92 .num_threads = static_cast<int64_t>(numbers.at(16)),
93 .itrealvalue = static_cast<int64_t>(numbers.at(17)),
94 .start_time_ticks = static_cast<uint64_t>(numbers.at(18)),
95 .virtual_memory_size = static_cast<uint64_t>(numbers.at(19)),
96 .resident_set_size = static_cast<int64_t>(numbers.at(20)),
97 .rss_soft_limit = static_cast<uint64_t>(numbers.at(21)),
98 .start_code_address = static_cast<uint64_t>(numbers.at(22)),
99 .end_code_address = static_cast<uint64_t>(numbers.at(23)),
100 .start_stack_address = static_cast<uint64_t>(numbers.at(24)),
101 .stack_pointer = static_cast<uint64_t>(numbers.at(25)),
102 .instruction_pointer = static_cast<uint64_t>(numbers.at(26)),
103 .signal_bitmask = static_cast<uint64_t>(numbers.at(27)),
104 .blocked_signals = static_cast<uint64_t>(numbers.at(28)),
105 .ignored_signals = static_cast<uint64_t>(numbers.at(29)),
106 .caught_signals = static_cast<uint64_t>(numbers.at(30)),
107 .wchan = static_cast<uint64_t>(numbers.at(31)),
108 .swap_pages = static_cast<uint64_t>(numbers.at(32)),
109 .children_swap_pages = static_cast<uint64_t>(numbers.at(33)),
110 .exit_signal = static_cast<int64_t>(numbers.at(34)),
111 .processor = static_cast<int64_t>(numbers.at(35)),
112 .rt_priority = static_cast<uint64_t>(numbers.at(36)),
113 .scheduling_policy = static_cast<uint64_t>(numbers.at(37)),
114 .block_io_delay_ticks = static_cast<uint64_t>(numbers.at(38)),
115 .guest_ticks = static_cast<uint64_t>(numbers.at(39)),
116 .children_guest_ticks = static_cast<uint64_t>(numbers.at(40)),
117 .start_data_address = static_cast<uint64_t>(numbers.at(41)),
118 .end_data_address = static_cast<uint64_t>(numbers.at(42)),
119 .start_brk_address = static_cast<uint64_t>(numbers.at(43)),
120 .start_arg_address = static_cast<uint64_t>(numbers.at(44)),
121 .end_arg_address = static_cast<uint64_t>(numbers.at(45)),
122 .start_env_address = static_cast<uint64_t>(numbers.at(46)),
123 .end_env_address = static_cast<uint64_t>(numbers.at(47)),
124 .exit_code = static_cast<int64_t>(numbers.at(48))};
125}
126
127Top::Top(aos::EventLoop *event_loop)
128 : event_loop_(event_loop),
129 clock_tick_(std::chrono::nanoseconds(1000000000 / sysconf(_SC_CLK_TCK))),
130 page_size_(sysconf(_SC_PAGESIZE)) {
131 TimerHandler *timer = event_loop_->AddTimer([this]() { UpdateReadings(); });
132 event_loop_->OnRun([timer, this]() {
133 timer->Setup(event_loop_->monotonic_now(), kSamplePeriod);
134 });
135}
136
137std::chrono::nanoseconds Top::TotalProcessTime(const ProcStat &proc_stat) {
138 return (proc_stat.user_mode_ticks + proc_stat.kernel_mode_ticks) *
139 clock_tick_;
140}
141
142aos::monotonic_clock::time_point Top::ProcessStartTime(
143 const ProcStat &proc_stat) {
144 return aos::monotonic_clock::time_point(proc_stat.start_time_ticks *
145 clock_tick_);
146}
147
148uint64_t Top::RealMemoryUsage(const ProcStat &proc_stat) {
149 return proc_stat.resident_set_size * page_size_;
150}
151
152void Top::UpdateReadings() {
153 aos::monotonic_clock::time_point now = event_loop_->monotonic_now();
154 // Get all the processes that we *might* care about.
155 std::set<pid_t> pids = pids_to_track_;
James Kuszmaul63a45482022-04-19 16:12:01 -0700156 // Ensure that we check on the status of every process that we are already
157 // tracking.
Austin Schuh60e77942022-05-16 17:48:24 -0700158 for (const auto &reading : readings_) {
James Kuszmaul63a45482022-04-19 16:12:01 -0700159 pids.insert(reading.first);
160 }
James Kuszmaul418fd062022-03-22 15:22:27 -0700161 if (track_all_) {
162 DIR *const dir = opendir("/proc");
163 if (dir == nullptr) {
164 PLOG(FATAL) << "Failed to open /proc";
165 }
166 while (true) {
167 struct dirent *const dir_entry = readdir(dir);
168 if (dir_entry == nullptr) {
169 break;
170 }
171 pid_t pid;
172 if (dir_entry->d_type == DT_DIR &&
173 absl::SimpleAtoi(dir_entry->d_name, &pid)) {
174 pids.insert(pid);
175 }
176 }
James Kuszmaul28c9e392022-11-04 13:24:12 -0700177 closedir(dir);
James Kuszmaul418fd062022-03-22 15:22:27 -0700178 }
179
180 for (const pid_t pid : pids) {
181 std::optional<ProcStat> proc_stat = ReadProcStat(pid);
182 // Stop tracking processes that have died.
183 if (!proc_stat.has_value()) {
184 readings_.erase(pid);
185 continue;
186 }
187 const aos::monotonic_clock::time_point start_time =
188 ProcessStartTime(*proc_stat);
189 auto reading_iter = readings_.find(pid);
190 if (reading_iter == readings_.end()) {
Austin Schuh979d4772022-12-30 14:50:41 -0800191 reading_iter =
192 readings_
193 .insert(std::make_pair(
194 pid, ProcessReadings{.name = proc_stat->name,
195 .start_time = start_time,
196 .cpu_percent = 0.0,
197 .kthread = !!(proc_stat->kernel_flags &
198 PF_KTHREAD),
199 .readings = {}}))
200 .first;
James Kuszmaul418fd062022-03-22 15:22:27 -0700201 }
202 ProcessReadings &process = reading_iter->second;
203 // The process associated with the PID has changed; reset the state.
204 if (process.start_time != start_time) {
205 process.name = proc_stat->name;
206 process.start_time = start_time;
207 process.readings.Reset();
208 }
James Kuszmaul6b35e3a2022-04-06 15:00:39 -0700209 // If the process name has changed (e.g., if our first reading for a process
210 // name occurred before execvp was called), then update it.
211 if (process.name != proc_stat->name) {
212 process.name = proc_stat->name;
213 }
James Kuszmaul418fd062022-03-22 15:22:27 -0700214
215 process.readings.Push(Reading{now, TotalProcessTime(*proc_stat),
216 RealMemoryUsage(*proc_stat)});
217 if (process.readings.size() == 2) {
218 process.cpu_percent =
219 aos::time::DurationInSeconds(process.readings[1].total_run_time -
220 process.readings[0].total_run_time) /
221 aos::time::DurationInSeconds(process.readings[1].reading_time -
222 process.readings[0].reading_time);
223 } else {
224 process.cpu_percent = 0.0;
225 }
226 }
Austin Schuh608514f2022-12-30 15:51:30 -0800227
228 if (on_reading_update_) {
229 on_reading_update_();
230 }
James Kuszmaul418fd062022-03-22 15:22:27 -0700231}
232
233flatbuffers::Offset<ProcessInfo> Top::InfoForProcess(
234 flatbuffers::FlatBufferBuilder *fbb, pid_t pid) {
235 auto reading_iter = readings_.find(pid);
236 if (reading_iter == readings_.end()) {
237 return {};
238 }
239 const ProcessReadings &reading = reading_iter->second;
240 const flatbuffers::Offset<flatbuffers::String> name =
241 fbb->CreateString(reading.name);
242 ProcessInfo::Builder builder(*fbb);
243 builder.add_pid(pid);
244 builder.add_name(name);
245 builder.add_cpu_usage(reading.cpu_percent);
246 builder.add_physical_memory(
247 reading.readings[reading.readings.size() - 1].memory_usage);
248 return builder.Finish();
249}
250
251flatbuffers::Offset<TopProcessesFbs> Top::TopProcesses(
252 flatbuffers::FlatBufferBuilder *fbb, int n) {
253 // Pair is {cpu_usage, pid}.
254 std::priority_queue<std::pair<double, pid_t>> cpu_usages;
255 for (const auto &pair : readings_) {
256 // Deliberately include 0.0 percent CPU things in the usage list so that if
257 // the user asks for an arbitrarily large number of processes they'll get
258 // everything.
259 cpu_usages.push(std::make_pair(pair.second.cpu_percent, pair.first));
260 }
261 std::vector<flatbuffers::Offset<ProcessInfo>> offsets;
262 for (int ii = 0; ii < n && !cpu_usages.empty(); ++ii) {
263 offsets.push_back(InfoForProcess(fbb, cpu_usages.top().second));
264 cpu_usages.pop();
265 }
266 const flatbuffers::Offset<
267 flatbuffers::Vector<flatbuffers::Offset<ProcessInfo>>>
268 vector_offset = fbb->CreateVector(offsets);
269 TopProcessesFbs::Builder builder(*fbb);
270 builder.add_processes(vector_offset);
271 return builder.Finish();
272}
273
274} // namespace aos::util