Serenity Operating System
at master 586 lines 22 kB view raw
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}