Serenity Operating System
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, ¶m);
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}