Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "ProcessModel.h"
9#include <AK/JsonObject.h>
10#include <AK/JsonValue.h>
11#include <AK/NonnullRefPtr.h>
12#include <AK/NumberFormat.h>
13#include <LibCore/DeprecatedFile.h>
14#include <LibCore/ProcessStatisticsReader.h>
15#include <LibGUI/FileIconProvider.h>
16#include <LibGUI/Icon.h>
17#include <LibGUI/ModelIndex.h>
18#include <LibGUI/ModelRole.h>
19#include <unistd.h>
20
21static ProcessModel* s_the;
22
23ProcessModel& ProcessModel::the()
24{
25 VERIFY(s_the);
26 return *s_the;
27}
28
29ProcessModel::ProcessModel()
30{
31 VERIFY(!s_the);
32 s_the = this;
33
34 auto file = Core::DeprecatedFile::construct("/sys/kernel/cpuinfo");
35 if (file->open(Core::OpenMode::ReadOnly)) {
36 auto buffer = file->read_all();
37 auto json = JsonValue::from_string({ buffer });
38 auto cpuinfo_array = json.value().as_array();
39 cpuinfo_array.for_each([&](auto& value) {
40 auto& cpu_object = value.as_object();
41 auto cpu_id = cpu_object.get_u32("processor"sv).value();
42 m_cpus.append(make<CpuInfo>(cpu_id));
43 });
44 }
45
46 if (m_cpus.is_empty())
47 m_cpus.append(make<CpuInfo>(0));
48
49 m_kernel_process_icon = GUI::Icon::default_icon("gear"sv);
50}
51
52int ProcessModel::row_count(GUI::ModelIndex const& index) const
53{
54 if (!index.is_valid())
55 return m_processes.size();
56 // Anything in the second level (threads of processes) doesn't have children.
57 // This way, we don't get infinitely recursing main threads without having to handle that special case elsewhere.
58 if (index.parent().is_valid())
59 return 0;
60 auto const& thread = *static_cast<Thread const*>(index.internal_data());
61 // Only the main thread has the other threads as its children.
62 // Also, if there's not more than one thread, we won't draw that.
63 if (thread.is_main_thread() && thread.current_state.process.threads.size() > 1)
64 return thread.current_state.process.threads.size() - 1;
65 return 0;
66}
67
68int ProcessModel::column_count(GUI::ModelIndex const&) const
69{
70 return Column::__Count;
71}
72
73DeprecatedString ProcessModel::column_name(int column) const
74{
75 switch (column) {
76 case Column::Icon:
77 return "";
78 case Column::PID:
79 return "PID";
80 case Column::TID:
81 return "TID";
82 case Column::PPID:
83 return "PPID";
84 case Column::PGID:
85 return "PGID";
86 case Column::SID:
87 return "SID";
88 case Column::State:
89 return "State";
90 case Column::User:
91 return "User";
92 case Column::Priority:
93 return "Pr";
94 case Column::Virtual:
95 return "Virtual";
96 case Column::Physical:
97 return "Physical";
98 case Column::DirtyPrivate:
99 return "Private";
100 case Column::CleanInode:
101 return "CleanI";
102 case Column::PurgeableVolatile:
103 return "Purg:V";
104 case Column::PurgeableNonvolatile:
105 return "Purg:N";
106 case Column::CPU:
107 return "CPU";
108 case Column::Processor:
109 return "Processor";
110 case Column::Name:
111 return "Name";
112 case Column::Syscalls:
113 return "Syscalls";
114 case Column::InodeFaults:
115 return "F:Inode";
116 case Column::ZeroFaults:
117 return "F:Zero";
118 case Column::CowFaults:
119 return "F:CoW";
120 case Column::IPv4SocketReadBytes:
121 return "IPv4 In";
122 case Column::IPv4SocketWriteBytes:
123 return "IPv4 Out";
124 case Column::UnixSocketReadBytes:
125 return "Unix In";
126 case Column::UnixSocketWriteBytes:
127 return "Unix Out";
128 case Column::FileReadBytes:
129 return "File In";
130 case Column::FileWriteBytes:
131 return "File Out";
132 case Column::Pledge:
133 return "Pledge";
134 case Column::Veil:
135 return "Veil";
136 case Column::Command:
137 return "Command";
138 default:
139 VERIFY_NOT_REACHED();
140 }
141}
142
143GUI::Variant ProcessModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const
144{
145 VERIFY(is_within_range(index));
146
147 if (role == GUI::ModelRole::TextAlignment) {
148 switch (index.column()) {
149 case Column::Icon:
150 case Column::Name:
151 case Column::State:
152 case Column::User:
153 case Column::Pledge:
154 case Column::Veil:
155 case Column::Command:
156 return Gfx::TextAlignment::CenterLeft;
157 case Column::PID:
158 case Column::TID:
159 case Column::PPID:
160 case Column::PGID:
161 case Column::SID:
162 case Column::Priority:
163 case Column::Virtual:
164 case Column::Physical:
165 case Column::DirtyPrivate:
166 case Column::CleanInode:
167 case Column::PurgeableVolatile:
168 case Column::PurgeableNonvolatile:
169 case Column::CPU:
170 case Column::Processor:
171 case Column::Syscalls:
172 case Column::InodeFaults:
173 case Column::ZeroFaults:
174 case Column::CowFaults:
175 case Column::FileReadBytes:
176 case Column::FileWriteBytes:
177 case Column::UnixSocketReadBytes:
178 case Column::UnixSocketWriteBytes:
179 case Column::IPv4SocketReadBytes:
180 case Column::IPv4SocketWriteBytes:
181 return Gfx::TextAlignment::CenterRight;
182 default:
183 VERIFY_NOT_REACHED();
184 }
185 }
186
187 auto const& thread = *static_cast<Thread const*>(index.internal_data());
188
189 if (role == GUI::ModelRole::Sort) {
190 switch (index.column()) {
191 case Column::Icon:
192 return 0;
193 case Column::PID:
194 return thread.current_state.pid;
195 case Column::TID:
196 return thread.current_state.tid;
197 case Column::PPID:
198 return thread.current_state.ppid;
199 case Column::PGID:
200 return thread.current_state.pgid;
201 case Column::SID:
202 return thread.current_state.sid;
203 case Column::State:
204 return thread.current_state.state;
205 case Column::User:
206 return thread.current_state.user;
207 case Column::Priority:
208 return thread.current_state.priority;
209 case Column::Virtual:
210 return (int)thread.current_state.amount_virtual;
211 case Column::Physical:
212 return (int)thread.current_state.amount_resident;
213 case Column::DirtyPrivate:
214 return (int)thread.current_state.amount_dirty_private;
215 case Column::CleanInode:
216 return (int)thread.current_state.amount_clean_inode;
217 case Column::PurgeableVolatile:
218 return (int)thread.current_state.amount_purgeable_volatile;
219 case Column::PurgeableNonvolatile:
220 return (int)thread.current_state.amount_purgeable_nonvolatile;
221 case Column::CPU:
222 return thread.current_state.cpu_percent;
223 case Column::Processor:
224 return thread.current_state.cpu;
225 case Column::Name:
226 return thread.current_state.name;
227 case Column::Command:
228 return thread.current_state.command;
229 case Column::Syscalls:
230 return thread.current_state.syscall_count;
231 case Column::InodeFaults:
232 return thread.current_state.inode_faults;
233 case Column::ZeroFaults:
234 return thread.current_state.zero_faults;
235 case Column::CowFaults:
236 return thread.current_state.cow_faults;
237 case Column::IPv4SocketReadBytes:
238 return thread.current_state.ipv4_socket_read_bytes;
239 case Column::IPv4SocketWriteBytes:
240 return thread.current_state.ipv4_socket_write_bytes;
241 case Column::UnixSocketReadBytes:
242 return thread.current_state.unix_socket_read_bytes;
243 case Column::UnixSocketWriteBytes:
244 return thread.current_state.unix_socket_write_bytes;
245 case Column::FileReadBytes:
246 return thread.current_state.file_read_bytes;
247 case Column::FileWriteBytes:
248 return thread.current_state.file_write_bytes;
249 case Column::Pledge:
250 return thread.current_state.pledge;
251 case Column::Veil:
252 return thread.current_state.veil;
253 }
254 VERIFY_NOT_REACHED();
255 }
256
257 if (role == GUI::ModelRole::Display) {
258 switch (index.column()) {
259 case Column::Icon:
260 return icon_for(thread);
261 case Column::PID:
262 return thread.current_state.pid;
263 case Column::TID:
264 return thread.current_state.tid;
265 case Column::PPID:
266 return thread.current_state.ppid;
267 case Column::PGID:
268 return thread.current_state.pgid;
269 case Column::SID:
270 return thread.current_state.sid;
271 case Column::State:
272 return thread.current_state.state;
273 case Column::User:
274 return thread.current_state.user;
275 case Column::Priority:
276 return thread.current_state.priority;
277 case Column::Virtual:
278 return human_readable_size(thread.current_state.amount_virtual);
279 case Column::Physical:
280 return human_readable_size(thread.current_state.amount_resident);
281 case Column::DirtyPrivate:
282 return human_readable_size(thread.current_state.amount_dirty_private);
283 case Column::CleanInode:
284 return human_readable_size(thread.current_state.amount_clean_inode);
285 case Column::PurgeableVolatile:
286 return human_readable_size(thread.current_state.amount_purgeable_volatile);
287 case Column::PurgeableNonvolatile:
288 return human_readable_size(thread.current_state.amount_purgeable_nonvolatile);
289 case Column::CPU:
290 return DeprecatedString::formatted("{:.2}", thread.current_state.cpu_percent);
291 case Column::Processor:
292 return thread.current_state.cpu;
293 case Column::Name:
294 if (thread.current_state.kernel)
295 return DeprecatedString::formatted("{} (*)", thread.current_state.name);
296 return thread.current_state.name;
297 case Column::Command:
298 return thread.current_state.command;
299 case Column::Syscalls:
300 return thread.current_state.syscall_count;
301 case Column::InodeFaults:
302 return thread.current_state.inode_faults;
303 case Column::ZeroFaults:
304 return thread.current_state.zero_faults;
305 case Column::CowFaults:
306 return thread.current_state.cow_faults;
307 case Column::IPv4SocketReadBytes:
308 return thread.current_state.ipv4_socket_read_bytes;
309 case Column::IPv4SocketWriteBytes:
310 return thread.current_state.ipv4_socket_write_bytes;
311 case Column::UnixSocketReadBytes:
312 return thread.current_state.unix_socket_read_bytes;
313 case Column::UnixSocketWriteBytes:
314 return thread.current_state.unix_socket_write_bytes;
315 case Column::FileReadBytes:
316 return thread.current_state.file_read_bytes;
317 case Column::FileWriteBytes:
318 return thread.current_state.file_write_bytes;
319 case Column::Pledge:
320 return thread.current_state.pledge;
321 case Column::Veil:
322 return thread.current_state.veil;
323 }
324 }
325
326 if (role == GUI::ModelRole::Icon)
327 return icon_for(thread);
328
329 if (role == GUI::ModelRole::IconOpacity) {
330 if (thread.current_state.uid != getuid())
331 return 0.5f;
332 return {};
333 }
334
335 return {};
336}
337
338GUI::Icon ProcessModel::icon_for(Thread const& thread) const
339{
340 if (thread.current_state.kernel)
341 return m_kernel_process_icon;
342 return GUI::FileIconProvider::icon_for_executable(thread.current_state.executable);
343}
344
345GUI::ModelIndex ProcessModel::index(int row, int column, GUI::ModelIndex const& parent) const
346{
347 if (row < 0 || column < 0)
348 return {};
349 // Process index; we display the main thread here.
350 if (!parent.is_valid()) {
351 if (row >= static_cast<int>(m_processes.size()))
352 return {};
353 auto corresponding_thread = m_processes[row]->main_thread();
354 if (!corresponding_thread.has_value())
355 return {};
356 return create_index(row, column, corresponding_thread.release_value().ptr());
357 }
358 // Thread under process.
359 auto const& parent_thread = *static_cast<Thread const*>(parent.internal_data());
360 auto const& process = parent_thread.current_state.process;
361 // dbgln("Getting thread model index in process {} for col {} row {}", process.pid, column, row);
362 if (row >= static_cast<int>(process.threads.size()))
363 return {};
364 return create_index(row, column, &process.non_main_thread(row));
365}
366
367int ProcessModel::thread_model_row(Thread const& thread) const
368{
369 auto const& process = thread.current_state.process;
370 // A process's main thread uses the global process index.
371 if (process.pid == thread.current_state.pid) {
372 auto it = m_processes.find_if([&](auto& entry) {
373 return entry.ptr() == &process;
374 });
375 if (it == m_processes.end())
376 return 0;
377 return it.index();
378 }
379
380 return process.threads.find_first_index(thread).value_or(0);
381}
382
383GUI::ModelIndex ProcessModel::parent_index(GUI::ModelIndex const& index) const
384{
385 if (!index.is_valid())
386 return {};
387 auto const& thread = *static_cast<Thread*>(index.internal_data());
388 // There's no parent for the main thread.
389 if (thread.current_state.pid == thread.current_state.tid)
390 return {};
391 // FIXME: We can't use first_matching here (not even a const version) because Optional cannot contain references.
392 auto const& parent = thread.current_state.process;
393 if (!parent.main_thread().has_value())
394 return {};
395
396 auto process_index = [&]() -> size_t {
397 auto it = m_processes.find_if([&](auto& entry) {
398 return entry.ptr() == &parent;
399 });
400 if (it == m_processes.end())
401 return 0;
402 return it.index();
403 }();
404 return create_index(process_index, index.column(), parent.main_thread().value().ptr());
405}
406
407Vector<GUI::ModelIndex> ProcessModel::matches(StringView searching, unsigned flags, GUI::ModelIndex const&)
408{
409 Vector<GUI::ModelIndex> found_indices;
410
411 for (auto const& thread : m_threads) {
412 if (string_matches(thread.value->current_state.name, searching, flags)) {
413 auto tid_row = thread_model_row(thread.value);
414
415 found_indices.append(create_index(tid_row, Column::Name, reinterpret_cast<void const*>(thread.value.ptr())));
416 if (flags & FirstMatchOnly)
417 break;
418 }
419 }
420
421 return found_indices;
422}
423
424static ErrorOr<DeprecatedString> try_read_command_line(pid_t pid)
425{
426 auto file = TRY(Core::File::open(DeprecatedString::formatted("/proc/{}/cmdline", pid), Core::File::OpenMode::Read));
427 auto data = TRY(file->read_until_eof());
428 auto json = TRY(JsonValue::from_string(StringView { data.bytes() }));
429 auto array = json.as_array().values();
430 return DeprecatedString::join(" "sv, array);
431}
432
433static DeprecatedString read_command_line(pid_t pid)
434{
435 auto string_or_error = try_read_command_line(pid);
436 if (string_or_error.is_error()) {
437 return "";
438 }
439 return string_or_error.release_value();
440}
441
442void ProcessModel::update()
443{
444 auto previous_tid_count = m_threads.size();
445 auto all_processes = Core::ProcessStatisticsReader::get_all(m_proc_all);
446
447 HashTable<int> live_tids;
448 u64 total_time_scheduled_diff = 0;
449 if (!all_processes.is_error()) {
450 if (m_has_total_scheduled_time)
451 total_time_scheduled_diff = all_processes.value().total_time_scheduled - m_total_time_scheduled;
452
453 m_total_time_scheduled = all_processes.value().total_time_scheduled;
454 m_total_time_scheduled_kernel = all_processes.value().total_time_scheduled_kernel;
455 m_has_total_scheduled_time = true;
456
457 for (size_t i = 0; i < all_processes.value().processes.size(); ++i) {
458 auto const& process = all_processes.value().processes[i];
459 NonnullOwnPtr<Process>* process_state = nullptr;
460 for (size_t i = 0; i < m_processes.size(); ++i) {
461 auto* other_process = &m_processes[i];
462 if ((*other_process)->pid == process.pid) {
463 process_state = other_process;
464 break;
465 }
466 }
467 if (!process_state) {
468 m_processes.append(make<Process>());
469 process_state = &m_processes.last();
470 }
471 (*process_state)->pid = process.pid;
472 for (auto& thread : process.threads) {
473 ThreadState state(**process_state);
474 state.tid = thread.tid;
475 state.pid = process.pid;
476 state.ppid = process.ppid;
477 state.pgid = process.pgid;
478 state.sid = process.sid;
479 state.time_user = thread.time_user;
480 state.time_kernel = thread.time_kernel;
481 state.kernel = process.kernel;
482 state.executable = process.executable;
483 state.name = thread.name;
484 state.command = read_command_line(process.pid);
485 state.uid = process.uid;
486 state.state = thread.state;
487 state.user = process.username;
488 state.pledge = process.pledge;
489 state.veil = process.veil;
490 state.cpu = thread.cpu;
491 state.priority = thread.priority;
492 state.amount_virtual = process.amount_virtual;
493 state.amount_resident = process.amount_resident;
494 state.amount_dirty_private = process.amount_dirty_private;
495 state.amount_clean_inode = process.amount_clean_inode;
496 state.amount_purgeable_volatile = process.amount_purgeable_volatile;
497 state.amount_purgeable_nonvolatile = process.amount_purgeable_nonvolatile;
498 state.syscall_count = thread.syscall_count;
499 state.inode_faults = thread.inode_faults;
500 state.zero_faults = thread.zero_faults;
501 state.cow_faults = thread.cow_faults;
502 state.unix_socket_read_bytes = thread.unix_socket_read_bytes;
503 state.unix_socket_write_bytes = thread.unix_socket_write_bytes;
504 state.ipv4_socket_read_bytes = thread.ipv4_socket_read_bytes;
505 state.ipv4_socket_write_bytes = thread.ipv4_socket_write_bytes;
506 state.file_read_bytes = thread.file_read_bytes;
507 state.file_write_bytes = thread.file_write_bytes;
508 state.cpu_percent = 0;
509
510 auto thread_data = m_threads.ensure(thread.tid, [&] { return make_ref_counted<Thread>(**process_state); });
511 thread_data->previous_state = move(thread_data->current_state);
512 thread_data->current_state = move(state);
513 if (auto maybe_thread_index = (*process_state)->threads.find_first_index(thread_data); maybe_thread_index.has_value()) {
514 (*process_state)->threads[maybe_thread_index.value()] = thread_data;
515 } else {
516 (*process_state)->threads.append(thread_data);
517 }
518
519 live_tids.set(thread.tid);
520 }
521 }
522 }
523
524 for (auto& c : m_cpus) {
525 c->total_cpu_percent = 0.0;
526 c->total_cpu_percent_kernel = 0.0;
527 }
528
529 Vector<int, 16> tids_to_remove;
530 for (auto& it : m_threads) {
531 if (!live_tids.contains(it.key)) {
532 tids_to_remove.append(it.key);
533 continue;
534 }
535 auto& thread = *it.value;
536 u64 time_scheduled_diff = (thread.current_state.time_user + thread.current_state.time_kernel)
537 - (thread.previous_state.time_user + thread.previous_state.time_kernel);
538 u64 time_scheduled_diff_kernel = thread.current_state.time_kernel - thread.previous_state.time_kernel;
539 thread.current_state.cpu_percent = total_time_scheduled_diff > 0 ? (float)((time_scheduled_diff * 1000) / total_time_scheduled_diff) / 10.0f : 0;
540 thread.current_state.cpu_percent_kernel = total_time_scheduled_diff > 0 ? (float)((time_scheduled_diff_kernel * 1000) / total_time_scheduled_diff) / 10.0f : 0;
541 if (it.value->current_state.pid != 0) {
542 auto& cpu_info = m_cpus[thread.current_state.cpu];
543 cpu_info->total_cpu_percent += thread.current_state.cpu_percent;
544 cpu_info->total_cpu_percent_kernel += thread.current_state.cpu_percent_kernel;
545 }
546 }
547
548 // FIXME: Also remove dead threads from processes
549 for (auto tid : tids_to_remove) {
550 m_threads.remove(tid);
551 for (size_t i = 0; i < m_processes.size(); ++i) {
552 auto& process = m_processes[i];
553 process->threads.remove_all_matching([&](auto const& thread) { return thread->current_state.tid == tid; });
554 if (process->threads.size() == 0) {
555 m_processes.remove(i);
556 --i;
557 }
558 }
559 }
560
561 if (on_cpu_info_change)
562 on_cpu_info_change(m_cpus);
563
564 if (on_state_update)
565 on_state_update(!all_processes.is_error() ? all_processes.value().processes.size() : 0, m_threads.size());
566
567 // FIXME: This is a rather hackish way of invalidating indices.
568 // It would be good if GUI::Model had a way to orchestrate removal/insertion while preserving indices.
569 did_update(previous_tid_count == m_threads.size() ? GUI::Model::UpdateFlag::DontInvalidateIndices : GUI::Model::UpdateFlag::InvalidateAllIndices);
570}
571
572bool ProcessModel::is_default_column(int index) const
573{
574 switch (index) {
575 case Column::PID:
576 case Column::TID:
577 case Column::Name:
578 case Column::CPU:
579 case Column::User:
580 case Column::Virtual:
581 case Column::DirtyPrivate:
582 return true;
583 default:
584 return false;
585 }
586}