Serenity Operating System
at master 626 lines 29 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, Undefine <cqundefine@gmail.com> 4 * Copyright (c) 2022, the SerenityOS developers. 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include "GraphWidget.h" 10#include "MemoryStatsWidget.h" 11#include "NetworkStatisticsWidget.h" 12#include "ProcessFileDescriptorMapWidget.h" 13#include "ProcessMemoryMapWidget.h" 14#include "ProcessModel.h" 15#include "ProcessStateWidget.h" 16#include "ProcessUnveiledPathsWidget.h" 17#include "ThreadStackWidget.h" 18#include <AK/NumberFormat.h> 19#include <Applications/SystemMonitor/ProcessWindowGML.h> 20#include <Applications/SystemMonitor/SystemMonitorGML.h> 21#include <LibConfig/Client.h> 22#include <LibCore/ArgsParser.h> 23#include <LibCore/EventLoop.h> 24#include <LibCore/Object.h> 25#include <LibCore/System.h> 26#include <LibCore/Timer.h> 27#include <LibGUI/Action.h> 28#include <LibGUI/ActionGroup.h> 29#include <LibGUI/Application.h> 30#include <LibGUI/BoxLayout.h> 31#include <LibGUI/FileIconProvider.h> 32#include <LibGUI/GroupBox.h> 33#include <LibGUI/Icon.h> 34#include <LibGUI/JsonArrayModel.h> 35#include <LibGUI/Label.h> 36#include <LibGUI/LazyWidget.h> 37#include <LibGUI/Menu.h> 38#include <LibGUI/Menubar.h> 39#include <LibGUI/MessageBox.h> 40#include <LibGUI/Painter.h> 41#include <LibGUI/Process.h> 42#include <LibGUI/SeparatorWidget.h> 43#include <LibGUI/SortingProxyModel.h> 44#include <LibGUI/StackWidget.h> 45#include <LibGUI/Statusbar.h> 46#include <LibGUI/TabWidget.h> 47#include <LibGUI/TreeView.h> 48#include <LibGUI/Widget.h> 49#include <LibGUI/Window.h> 50#include <LibGfx/Font/FontDatabase.h> 51#include <LibGfx/Palette.h> 52#include <LibMain/Main.h> 53#include <LibThreading/BackgroundAction.h> 54#include <serenity.h> 55#include <signal.h> 56#include <spawn.h> 57#include <stdio.h> 58#include <unistd.h> 59 60static ErrorOr<NonnullRefPtr<GUI::Window>> build_process_window(pid_t); 61static void build_performance_tab(GUI::Widget&); 62 63static RefPtr<GUI::Statusbar> statusbar; 64 65namespace SystemMonitor { 66 67class ProgressbarPaintingDelegate final : public GUI::TableCellPaintingDelegate { 68public: 69 virtual ~ProgressbarPaintingDelegate() override = default; 70 71 virtual void paint(GUI::Painter& painter, Gfx::IntRect const& a_rect, Palette const& palette, GUI::ModelIndex const& index) override 72 { 73 auto rect = a_rect.shrunken(2, 2); 74 auto percentage = index.data(GUI::ModelRole::Custom).to_i32(); 75 76 auto data = index.data(); 77 DeprecatedString text; 78 if (data.is_string()) 79 text = data.as_string(); 80 Gfx::StylePainter::paint_progressbar(painter, rect, palette, 0, 100, percentage, text); 81 painter.draw_rect(rect, Color::Black); 82 } 83}; 84 85class UnavailableProcessWidget final : public GUI::Frame { 86 C_OBJECT(UnavailableProcessWidget) 87public: 88 virtual ~UnavailableProcessWidget() override = default; 89 90 DeprecatedString const& text() const { return m_text; } 91 void set_text(DeprecatedString text) 92 { 93 m_text = move(text); 94 update(); 95 } 96 97private: 98 UnavailableProcessWidget() 99 { 100 REGISTER_STRING_PROPERTY("text", text, set_text); 101 } 102 103 virtual void paint_event(GUI::PaintEvent& event) override 104 { 105 Frame::paint_event(event); 106 if (text().is_empty()) 107 return; 108 GUI::Painter painter(*this); 109 painter.add_clip_rect(event.rect()); 110 painter.draw_text(frame_inner_rect(), text(), Gfx::TextAlignment::Center, palette().window_text(), Gfx::TextElision::Right); 111 } 112 113 DeprecatedString m_text; 114}; 115 116class StorageTabWidget final : public GUI::LazyWidget { 117 C_OBJECT(StorageTabWidget) 118public: 119 StorageTabWidget() 120 { 121 this->on_first_show = [](GUI::LazyWidget& self) { 122 auto& fs_table_view = *self.find_child_of_type_named<GUI::TableView>("storage_table"); 123 124 Vector<GUI::JsonArrayModel::FieldSpec> df_fields; 125 df_fields.empend("mount_point", "Mount point", Gfx::TextAlignment::CenterLeft); 126 df_fields.empend("class_name", "Class", Gfx::TextAlignment::CenterLeft); 127 df_fields.empend("source", "Source", Gfx::TextAlignment::CenterLeft); 128 df_fields.empend( 129 "Size", Gfx::TextAlignment::CenterRight, 130 [](JsonObject const& object) { 131 StringBuilder size_builder; 132 size_builder.append(' '); 133 size_builder.append(human_readable_size(object.get_u64("total_block_count"sv).value_or(0) * object.get_u64("block_size"sv).value_or(0))); 134 size_builder.append(' '); 135 return size_builder.to_deprecated_string(); 136 }, 137 [](JsonObject const& object) { 138 return object.get_u64("total_block_count"sv).value_or(0) * object.get_u64("block_size"sv).value_or(0); 139 }, 140 [](JsonObject const& object) { 141 auto total_blocks = object.get_u64("total_block_count"sv).value_or(0); 142 if (total_blocks == 0) 143 return 0; 144 auto free_blocks = object.get_u64("free_block_count"sv).value_or(0); 145 auto used_blocks = total_blocks - free_blocks; 146 int percentage = (static_cast<double>(used_blocks) / static_cast<double>(total_blocks) * 100.0); 147 return percentage; 148 }); 149 df_fields.empend( 150 "Used", Gfx::TextAlignment::CenterRight, 151 [](JsonObject const& object) { 152 auto total_blocks = object.get_u64("total_block_count"sv).value_or(0); 153 auto free_blocks = object.get_u64("free_block_count"sv).value_or(0); 154 auto used_blocks = total_blocks - free_blocks; 155 return human_readable_size(used_blocks * object.get_u64("block_size"sv).value_or(0)); }, 156 [](JsonObject const& object) { 157 auto total_blocks = object.get_u64("total_block_count"sv).value_or(0); 158 auto free_blocks = object.get_u64("free_block_count"sv).value_or(0); 159 auto used_blocks = total_blocks - free_blocks; 160 return used_blocks * object.get_u64("block_size"sv).value_or(0); 161 }); 162 df_fields.empend( 163 "Available", Gfx::TextAlignment::CenterRight, 164 [](JsonObject const& object) { 165 return human_readable_size(object.get_u64("free_block_count"sv).value_or(0) * object.get_u64("block_size"sv).value_or(0)); 166 }, 167 [](JsonObject const& object) { 168 return object.get_u64("free_block_count"sv).value_or(0) * object.get_u64("block_size"sv).value_or(0); 169 }); 170 df_fields.empend("Access", Gfx::TextAlignment::CenterLeft, [](JsonObject const& object) { 171 bool readonly = object.get_bool("readonly"sv).value_or(false); 172 int mount_flags = object.get_i32("mount_flags"sv).value_or(0); 173 return readonly || (mount_flags & MS_RDONLY) ? "Read-only" : "Read/Write"; 174 }); 175 df_fields.empend("Mount flags", Gfx::TextAlignment::CenterLeft, [](JsonObject const& object) { 176 int mount_flags = object.get_i32("mount_flags"sv).value_or(0); 177 StringBuilder builder; 178 bool first = true; 179 auto check = [&](int flag, StringView name) { 180 if (!(mount_flags & flag)) 181 return; 182 if (!first) 183 builder.append(','); 184 builder.append(name); 185 first = false; 186 }; 187 check(MS_NODEV, "nodev"sv); 188 check(MS_NOEXEC, "noexec"sv); 189 check(MS_NOSUID, "nosuid"sv); 190 check(MS_BIND, "bind"sv); 191 check(MS_RDONLY, "ro"sv); 192 check(MS_WXALLOWED, "wxallowed"sv); 193 check(MS_AXALLOWED, "axallowed"sv); 194 check(MS_NOREGULAR, "noregular"sv); 195 if (builder.string_view().is_empty()) 196 return DeprecatedString("defaults"); 197 return builder.to_deprecated_string(); 198 }); 199 df_fields.empend("free_block_count", "Free blocks", Gfx::TextAlignment::CenterRight); 200 df_fields.empend("total_block_count", "Total blocks", Gfx::TextAlignment::CenterRight); 201 df_fields.empend("free_inode_count", "Free inodes", Gfx::TextAlignment::CenterRight); 202 df_fields.empend("total_inode_count", "Total inodes", Gfx::TextAlignment::CenterRight); 203 df_fields.empend("block_size", "Block size", Gfx::TextAlignment::CenterRight); 204 205 fs_table_view.set_model(MUST(GUI::SortingProxyModel::create(GUI::JsonArrayModel::create("/sys/kernel/df", move(df_fields))))); 206 207 fs_table_view.set_column_painting_delegate(3, make<ProgressbarPaintingDelegate>()); 208 209 fs_table_view.model()->invalidate(); 210 }; 211 } 212}; 213 214} 215 216REGISTER_WIDGET(SystemMonitor, StorageTabWidget) 217REGISTER_WIDGET(SystemMonitor, UnavailableProcessWidget) 218 219static bool can_access_pid(pid_t pid) 220{ 221 int rc = kill(pid, 0); 222 return rc == 0; 223} 224 225ErrorOr<int> serenity_main(Main::Arguments arguments) 226{ 227 { 228 // Before we do anything else, boost our process priority to the maximum allowed. 229 // It's very frustrating when the system is bogged down under load and you just want 230 // System Monitor to work. 231 sched_param param { 232 .sched_priority = THREAD_PRIORITY_MAX, 233 }; 234 sched_setparam(0, &param); 235 } 236 237 TRY(Core::System::pledge("stdio thread proc recvfd sendfd rpath exec unix")); 238 239 auto app = TRY(GUI::Application::try_create(arguments)); 240 241 Config::pledge_domain("SystemMonitor"); 242 243 TRY(Core::System::unveil("/etc/passwd", "r")); 244 TRY(Core::System::unveil("/res", "r")); 245 TRY(Core::System::unveil("/proc", "r")); 246 TRY(Core::System::unveil("/sys/kernel", "r")); 247 TRY(Core::System::unveil("/dev", "r")); 248 TRY(Core::System::unveil("/bin", "r")); 249 TRY(Core::System::unveil("/bin/Escalator", "x")); 250 TRY(Core::System::unveil("/usr/lib", "r")); 251 252 // This directory only exists if ports are installed 253 if (auto result = Core::System::unveil("/usr/local/bin", "r"); result.is_error() && result.error().code() != ENOENT) 254 return result.release_error(); 255 256 if (auto result = Core::System::unveil("/usr/local/lib", "r"); result.is_error() && result.error().code() != ENOENT) 257 return result.release_error(); 258 259 // This file is only accessible when running as root if it is available on the disk image. 260 // It might be possible to not have this file on the disk image, if the user decided to not 261 // include kernel symbols for debug purposes so don't fail if the error is ENOENT. 262 if (auto result = Core::System::unveil("/boot/Kernel.debug", "r"); result.is_error() && (result.error().code() != EACCES && result.error().code() != ENOENT)) 263 return result.release_error(); 264 265 TRY(Core::System::unveil("/bin/Profiler", "rx")); 266 TRY(Core::System::unveil("/bin/Inspector", "rx")); 267 TRY(Core::System::unveil("/bin/HackStudio", "rx")); 268 TRY(Core::System::unveil(nullptr, nullptr)); 269 270 StringView args_tab = "processes"sv; 271 Core::ArgsParser parser; 272 parser.add_option(args_tab, "Tab, one of 'processes', 'graphs', 'fs', 'hardware', or 'network'", "open-tab", 't', "tab"); 273 parser.parse(arguments); 274 StringView args_tab_view = args_tab; 275 276 auto app_icon = GUI::Icon::default_icon("app-system-monitor"sv); 277 278 auto window = GUI::Window::construct(); 279 window->set_title("System Monitor"); 280 window->resize(560, 430); 281 282 auto main_widget = TRY(window->set_main_widget<GUI::Widget>()); 283 TRY(main_widget->load_from_gml(system_monitor_gml)); 284 auto& tabwidget = *main_widget->find_descendant_of_type_named<GUI::TabWidget>("main_tabs"); 285 statusbar = main_widget->find_descendant_of_type_named<GUI::Statusbar>("statusbar"); 286 287 auto& process_table_container = *tabwidget.find_descendant_of_type_named<GUI::Widget>("processes"); 288 289 auto process_model = ProcessModel::create(); 290 process_model->on_state_update = [&](int process_count, int thread_count) { 291 statusbar->set_text(0, DeprecatedString::formatted("Processes: {}", process_count)); 292 statusbar->set_text(1, DeprecatedString::formatted("Threads: {}", thread_count)); 293 }; 294 295 auto& performance_widget = *tabwidget.find_descendant_of_type_named<GUI::Widget>("performance"); 296 build_performance_tab(performance_widget); 297 298 auto& process_table_view = *process_table_container.find_child_of_type_named<GUI::TreeView>("process_table"); 299 process_table_view.set_model(TRY(GUI::SortingProxyModel::create(process_model))); 300 301 for (auto column = 0; column < ProcessModel::Column::__Count; ++column) { 302 process_table_view.set_column_visible(column, 303 Config::read_bool("SystemMonitor"sv, "ProcessTableColumns"sv, process_model->column_name(column), 304 process_model->is_default_column(column))); 305 } 306 307 process_table_view.set_key_column_and_sort_order(ProcessModel::Column::CPU, GUI::SortOrder::Descending); 308 process_model->update(); 309 310 i32 frequency = Config::read_i32("SystemMonitor"sv, "Monitor"sv, "Frequency"sv, 3); 311 if (frequency != 1 && frequency != 3 && frequency != 5) { 312 frequency = 3; 313 Config::write_i32("SystemMonitor"sv, "Monitor"sv, "Frequency"sv, frequency); 314 } 315 316 auto update_stats = [&] { 317 // FIXME: remove the primitive re-toggling code once persistent model indices work. 318 auto toggled_indices = process_table_view.selection().indices(); 319 toggled_indices.remove_all_matching([&](auto const& index) { return !process_table_view.is_toggled(index); }); 320 process_model->update(); 321 if (!process_table_view.selection().is_empty()) 322 process_table_view.selection().for_each_index([&](auto& selection) { 323 if (toggled_indices.contains_slow(selection)) 324 process_table_view.expand_all_parents_of(selection); 325 }); 326 327 if (auto* memory_stats_widget = SystemMonitor::MemoryStatsWidget::the()) 328 memory_stats_widget->refresh(); 329 }; 330 update_stats(); 331 auto& refresh_timer = window->add<Core::Timer>(frequency * 1000, move(update_stats)); 332 refresh_timer.start(); 333 334 auto selected_id = [&](ProcessModel::Column column) -> pid_t { 335 if (process_table_view.selection().is_empty()) 336 return -1; 337 auto pid_index = process_table_view.model()->index(process_table_view.selection().first().row(), column, process_table_view.selection().first().parent()); 338 return pid_index.data().to_i32(); 339 }; 340 341 auto selected_name = [&](ProcessModel::Column column) -> DeprecatedString { 342 if (process_table_view.selection().is_empty()) 343 return {}; 344 auto pid_index = process_table_view.model()->index(process_table_view.selection().first().row(), column, process_table_view.selection().first().parent()); 345 return pid_index.data().to_deprecated_string(); 346 }; 347 348 auto kill_action = GUI::Action::create( 349 "&Kill Process", { Mod_Ctrl, Key_K }, { Key_Delete }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/kill.png"sv)), [&](const GUI::Action&) { 350 pid_t pid = selected_id(ProcessModel::Column::PID); 351 if (pid == -1) 352 return; 353 auto rc = GUI::MessageBox::show(window, DeprecatedString::formatted("Do you really want to kill \"{}\" (PID {})?", selected_name(ProcessModel::Column::Name), pid), "System Monitor"sv, GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); 354 if (rc == GUI::Dialog::ExecResult::Yes) 355 kill(pid, SIGKILL); 356 }, 357 &process_table_view); 358 359 auto stop_action = GUI::Action::create( 360 "&Stop Process", { Mod_Ctrl, Key_S }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/stop-hand.png"sv)), [&](const GUI::Action&) { 361 pid_t pid = selected_id(ProcessModel::Column::PID); 362 if (pid == -1) 363 return; 364 auto rc = GUI::MessageBox::show(window, DeprecatedString::formatted("Do you really want to stop \"{}\" (PID {})?", selected_name(ProcessModel::Column::Name), pid), "System Monitor"sv, GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); 365 if (rc == GUI::Dialog::ExecResult::Yes) 366 kill(pid, SIGSTOP); 367 }, 368 &process_table_view); 369 370 auto continue_action = GUI::Action::create( 371 "&Continue Process", { Mod_Ctrl, Key_C }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/continue.png"sv)), [&](const GUI::Action&) { 372 pid_t pid = selected_id(ProcessModel::Column::PID); 373 if (pid != -1) 374 kill(pid, SIGCONT); 375 }, 376 &process_table_view); 377 378 auto profile_action = GUI::Action::create( 379 "&Profile Process", { Mod_Ctrl, Key_P }, 380 TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-profiler.png"sv)), [&](auto&) { 381 pid_t pid = selected_id(ProcessModel::Column::PID); 382 if (pid == -1) 383 return; 384 auto pid_string = DeprecatedString::number(pid); 385 GUI::Process::spawn_or_show_error(window, "/bin/Profiler"sv, Array { "--pid", pid_string.characters() }); 386 }, 387 &process_table_view); 388 389 auto debug_action = GUI::Action::create( 390 "Debug in HackStudio", { Mod_Ctrl, Key_D }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hack-studio.png"sv)), [&](const GUI::Action&) { 391 pid_t pid = selected_id(ProcessModel::Column::PID); 392 if (pid == -1) 393 return; 394 auto pid_string = DeprecatedString::number(pid); 395 GUI::Process::spawn_or_show_error(window, "/bin/HackStudio"sv, Array { "--pid", pid_string.characters() }); 396 }, 397 &process_table_view); 398 399 HashMap<pid_t, NonnullRefPtr<GUI::Window>> process_windows; 400 401 auto process_properties_action = GUI::CommonActions::make_properties_action( 402 [&](auto&) { 403 auto pid = selected_id(ProcessModel::Column::PID); 404 if (pid == -1) 405 return; 406 407 RefPtr<GUI::Window> process_window; 408 auto it = process_windows.find(pid); 409 if (it == process_windows.end()) { 410 auto process_window_or_error = build_process_window(pid); 411 if (process_window_or_error.is_error()) 412 return; 413 process_window = process_window_or_error.release_value(); 414 process_window->on_close_request = [pid, &process_windows] { 415 process_windows.remove(pid); 416 return GUI::Window::CloseRequestDecision::Close; 417 }; 418 process_windows.set(pid, *process_window); 419 } else { 420 process_window = it->value; 421 } 422 process_window->show(); 423 process_window->move_to_front(); 424 }, 425 &process_table_view); 426 427 auto& file_menu = window->add_menu("&File"); 428 file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) { 429 GUI::Application::the()->quit(); 430 })); 431 432 auto process_context_menu = GUI::Menu::construct(); 433 process_context_menu->add_action(kill_action); 434 process_context_menu->add_action(stop_action); 435 process_context_menu->add_action(continue_action); 436 process_context_menu->add_separator(); 437 process_context_menu->add_action(profile_action); 438 process_context_menu->add_action(debug_action); 439 process_context_menu->add_separator(); 440 process_context_menu->add_action(process_properties_action); 441 process_table_view.on_context_menu_request = [&]([[maybe_unused]] const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) { 442 if (index.is_valid()) 443 process_context_menu->popup(event.screen_position(), process_properties_action); 444 }; 445 446 auto& frequency_menu = window->add_menu("F&requency"); 447 GUI::ActionGroup frequency_action_group; 448 frequency_action_group.set_exclusive(true); 449 450 auto make_frequency_action = [&](int seconds) { 451 auto action = GUI::Action::create_checkable(DeprecatedString::formatted("&{} Sec", seconds), [&refresh_timer, seconds](auto&) { 452 Config::write_i32("SystemMonitor"sv, "Monitor"sv, "Frequency"sv, seconds); 453 refresh_timer.restart(seconds * 1000); 454 }); 455 action->set_status_tip(DeprecatedString::formatted("Refresh every {} seconds", seconds)); 456 action->set_checked(frequency == seconds); 457 frequency_action_group.add_action(*action); 458 frequency_menu.add_action(*action); 459 }; 460 461 make_frequency_action(1); 462 make_frequency_action(3); 463 make_frequency_action(5); 464 465 auto& help_menu = window->add_menu("&Help"); 466 help_menu.add_action(GUI::CommonActions::make_command_palette_action(window)); 467 help_menu.add_action(GUI::CommonActions::make_about_action("System Monitor", app_icon, window)); 468 469 process_table_view.on_activation = [&](auto&) { 470 if (process_properties_action->is_enabled()) 471 process_properties_action->activate(); 472 }; 473 474 static pid_t last_selected_pid; 475 476 process_table_view.on_selection_change = [&] { 477 pid_t pid = selected_id(ProcessModel::Column::PID); 478 if (pid == last_selected_pid || pid < 1) 479 return; 480 last_selected_pid = pid; 481 bool has_access = can_access_pid(pid); 482 kill_action->set_enabled(has_access); 483 stop_action->set_enabled(has_access); 484 continue_action->set_enabled(has_access); 485 profile_action->set_enabled(has_access); 486 debug_action->set_enabled(has_access); 487 process_properties_action->set_enabled(has_access); 488 }; 489 490 app->on_action_enter = [](GUI::Action const& action) { 491 statusbar->set_override_text(action.status_tip()); 492 }; 493 app->on_action_leave = [](GUI::Action const&) { 494 statusbar->set_override_text({}); 495 }; 496 497 window->show(); 498 window->set_icon(app_icon.bitmap_for_size(16)); 499 500 if (args_tab_view == "processes") 501 tabwidget.set_active_widget(&process_table_container); 502 else if (args_tab_view == "graphs") 503 tabwidget.set_active_widget(&performance_widget); 504 else if (args_tab_view == "fs") 505 tabwidget.set_active_widget(tabwidget.find_descendant_of_type_named<SystemMonitor::StorageTabWidget>("storage")); 506 else if (args_tab_view == "network") 507 tabwidget.set_active_widget(tabwidget.find_descendant_of_type_named<GUI::Widget>("network")); 508 509 int exec = app->exec(); 510 511 // When exiting the application, save the configuration of the columns 512 // to be loaded the next time the application is opened. 513 auto& process_table_header = process_table_view.column_header(); 514 for (auto column = 0; column < ProcessModel::Column::__Count; ++column) 515 Config::write_bool("SystemMonitor"sv, "ProcessTableColumns"sv, process_model->column_name(column), process_table_header.is_section_visible(column)); 516 517 return exec; 518} 519 520ErrorOr<NonnullRefPtr<GUI::Window>> build_process_window(pid_t pid) 521{ 522 auto window = GUI::Window::construct(); 523 window->resize(480, 360); 524 window->set_title(DeprecatedString::formatted("PID {} - System Monitor", pid)); 525 526 auto app_icon = GUI::Icon::default_icon("app-system-monitor"sv); 527 window->set_icon(app_icon.bitmap_for_size(16)); 528 529 auto main_widget = TRY(window->set_main_widget<GUI::Widget>()); 530 TRY(main_widget->load_from_gml(process_window_gml)); 531 532 GUI::ModelIndex process_index; 533 for (int row = 0; row < ProcessModel::the().row_count({}); ++row) { 534 auto index = ProcessModel::the().index(row, ProcessModel::Column::PID); 535 if (index.data().to_i32() == pid) { 536 process_index = index; 537 break; 538 } 539 } 540 541 VERIFY(process_index.is_valid()); 542 if (auto icon_data = process_index.sibling_at_column(ProcessModel::Column::Icon).data(); icon_data.is_icon()) { 543 main_widget->find_descendant_of_type_named<GUI::Label>("icon_label")->set_icon(icon_data.as_icon().bitmap_for_size(32)); 544 } 545 546 main_widget->find_descendant_of_type_named<GUI::Label>("process_name")->set_text(DeprecatedString::formatted("{} (PID {})", process_index.sibling_at_column(ProcessModel::Column::Name).data().to_deprecated_string(), pid)); 547 548 main_widget->find_descendant_of_type_named<SystemMonitor::ProcessStateWidget>("process_state")->set_pid(pid); 549 main_widget->find_descendant_of_type_named<SystemMonitor::ProcessFileDescriptorMapWidget>("open_files")->set_pid(pid); 550 main_widget->find_descendant_of_type_named<SystemMonitor::ThreadStackWidget>("thread_stack")->set_ids(pid, pid); 551 main_widget->find_descendant_of_type_named<SystemMonitor::ProcessMemoryMapWidget>("memory_map")->set_pid(pid); 552 main_widget->find_descendant_of_type_named<SystemMonitor::ProcessUnveiledPathsWidget>("unveiled_paths")->set_pid(pid); 553 554 auto& widget_stack = *main_widget->find_descendant_of_type_named<GUI::StackWidget>("widget_stack"); 555 auto& unavailable_process_widget = *widget_stack.find_descendant_of_type_named<SystemMonitor::UnavailableProcessWidget>("unavailable_process"); 556 unavailable_process_widget.set_text(DeprecatedString::formatted("Unable to access PID {}", pid)); 557 558 if (can_access_pid(pid)) 559 widget_stack.set_active_widget(widget_stack.find_descendant_of_type_named<GUI::TabWidget>("available_process")); 560 else 561 widget_stack.set_active_widget(&unavailable_process_widget); 562 563 return window; 564} 565 566void build_performance_tab(GUI::Widget& graphs_container) 567{ 568 auto& cpu_graph_group_box = *graphs_container.find_descendant_of_type_named<GUI::GroupBox>("cpu_graph"); 569 570 size_t cpu_graphs_per_row = min(4, ProcessModel::the().cpus().size()); 571 auto cpu_graph_rows = ceil_div(ProcessModel::the().cpus().size(), cpu_graphs_per_row); 572 cpu_graph_group_box.set_fixed_height(120u * cpu_graph_rows); 573 574 Vector<SystemMonitor::GraphWidget&> cpu_graphs; 575 for (auto row = 0u; row < cpu_graph_rows; ++row) { 576 auto& cpu_graph_row = cpu_graph_group_box.add<GUI::Widget>(); 577 cpu_graph_row.set_layout<GUI::HorizontalBoxLayout>(6); 578 cpu_graph_row.set_fixed_height(108); 579 for (auto i = 0u; i < cpu_graphs_per_row; ++i) { 580 auto& cpu_graph = cpu_graph_row.add<SystemMonitor::GraphWidget>(); 581 cpu_graph.set_max(100); 582 cpu_graph.set_value_format(0, { 583 .graph_color_role = ColorRole::SyntaxPreprocessorStatement, 584 .text_formatter = [](u64 value) { 585 return DeprecatedString::formatted("Total: {}%", value); 586 }, 587 }); 588 cpu_graph.set_value_format(1, { 589 .graph_color_role = ColorRole::SyntaxPreprocessorValue, 590 .text_formatter = [](u64 value) { 591 return DeprecatedString::formatted("Kernel: {}%", value); 592 }, 593 }); 594 cpu_graphs.append(cpu_graph); 595 } 596 } 597 ProcessModel::the().on_cpu_info_change = [cpu_graphs](Vector<NonnullOwnPtr<ProcessModel::CpuInfo>> const& cpus) mutable { 598 float sum_cpu = 0; 599 for (size_t i = 0; i < cpus.size(); ++i) { 600 cpu_graphs[i].add_value({ static_cast<size_t>(cpus[i]->total_cpu_percent), static_cast<size_t>(cpus[i]->total_cpu_percent_kernel) }); 601 sum_cpu += cpus[i]->total_cpu_percent; 602 } 603 float cpu_usage = sum_cpu / (float)cpus.size(); 604 statusbar->set_text(2, DeprecatedString::formatted("CPU usage: {}%", (int)roundf(cpu_usage))); 605 }; 606 607 auto& memory_graph = *graphs_container.find_descendant_of_type_named<SystemMonitor::GraphWidget>("memory_graph"); 608 memory_graph.set_value_format(0, { 609 .graph_color_role = ColorRole::SyntaxComment, 610 .text_formatter = [](u64 bytes) { 611 return DeprecatedString::formatted("Committed: {}", human_readable_size(bytes)); 612 }, 613 }); 614 memory_graph.set_value_format(1, { 615 .graph_color_role = ColorRole::SyntaxPreprocessorStatement, 616 .text_formatter = [](u64 bytes) { 617 return DeprecatedString::formatted("Allocated: {}", human_readable_size(bytes)); 618 }, 619 }); 620 memory_graph.set_value_format(2, { 621 .graph_color_role = ColorRole::SyntaxPreprocessorValue, 622 .text_formatter = [](u64 bytes) { 623 return DeprecatedString::formatted("Kernel heap: {}", human_readable_size(bytes)); 624 }, 625 }); 626}