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