diff options
author | kleines Filmröllchen <filmroellchen@serenityos.org> | 2022-04-05 00:32:37 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-04-06 12:47:50 +0200 |
commit | 0a61b45b646ad60de110db25f61f25ba7715aa71 (patch) | |
tree | d43b8826d49d6a05fe8f4fea48dfe0e79c8cb6a1 /Userland/Applications | |
parent | cd5ed44f64297f10cdfa253354da7da508d8be06 (diff) | |
download | serenity-0a61b45b646ad60de110db25f61f25ba7715aa71.zip |
SystemMonitor: Display processes and their threads in a tree :^)
This shows all non-main threads as children of the process they belong
to. We also show the TID as that is important to distinguish the
different threads in one process.
Fixes #65
:skeleyak:
Diffstat (limited to 'Userland/Applications')
-rw-r--r-- | Userland/Applications/SystemMonitor/ProcessModel.cpp | 186 | ||||
-rw-r--r-- | Userland/Applications/SystemMonitor/ProcessModel.h | 119 | ||||
-rw-r--r-- | Userland/Applications/SystemMonitor/SystemMonitor.gml | 4 | ||||
-rw-r--r-- | Userland/Applications/SystemMonitor/main.cpp | 10 |
4 files changed, 265 insertions, 54 deletions
diff --git a/Userland/Applications/SystemMonitor/ProcessModel.cpp b/Userland/Applications/SystemMonitor/ProcessModel.cpp index bd66c376f4..98f31612de 100644 --- a/Userland/Applications/SystemMonitor/ProcessModel.cpp +++ b/Userland/Applications/SystemMonitor/ProcessModel.cpp @@ -8,10 +8,15 @@ #include "ProcessModel.h" #include <AK/JsonObject.h> #include <AK/JsonValue.h> +#include <AK/NonnullRefPtr.h> #include <AK/NumberFormat.h> #include <LibCore/File.h> #include <LibCore/ProcessStatisticsReader.h> #include <LibGUI/FileIconProvider.h> +#include <LibGUI/Icon.h> +#include <LibGUI/ModelIndex.h> +#include <LibGUI/ModelRole.h> +#include <unistd.h> static ProcessModel* s_the; @@ -44,9 +49,20 @@ ProcessModel::ProcessModel() m_kernel_process_icon = GUI::Icon::default_icon("gear"); } -int ProcessModel::row_count(GUI::ModelIndex const&) const +int ProcessModel::row_count(GUI::ModelIndex const& index) const { - return m_tids.size(); + if (!index.is_valid()) + return m_processes.size(); + // Anything in the second level (threads of processes) doesn't have children. + // This way, we don't get infinitely recursing main threads without having to handle that special case elsewhere. + if (index.parent().is_valid()) + return 0; + auto const& thread = *static_cast<Thread const*>(index.internal_data()); + // Only the main thread has the other threads as its children. + // Also, if there's not more than one thread, we won't draw that. + if (thread.is_main_thread() && thread.current_state.process.threads.size() > 1) + return thread.current_state.process.threads.size() - 1; + return 0; } int ProcessModel::column_count(GUI::ModelIndex const&) const @@ -165,8 +181,7 @@ GUI::Variant ProcessModel::data(GUI::ModelIndex const& index, GUI::ModelRole rol } } - auto it = m_threads.find(m_tids[index.row()]); - auto& thread = *(*it).value; + auto const& thread = *static_cast<Thread const*>(index.internal_data()); if (role == GUI::ModelRole::Sort) { switch (index.column()) { @@ -236,11 +251,8 @@ GUI::Variant ProcessModel::data(GUI::ModelIndex const& index, GUI::ModelRole rol if (role == GUI::ModelRole::Display) { switch (index.column()) { - case Column::Icon: { - if (thread.current_state.kernel) - return m_kernel_process_icon; - return GUI::FileIconProvider::icon_for_executable(thread.current_state.executable); - } + case Column::Icon: + return icon_for(thread); case Column::PID: return thread.current_state.pid; case Column::TID: @@ -304,19 +316,78 @@ GUI::Variant ProcessModel::data(GUI::ModelIndex const& index, GUI::ModelRole rol } } + if (role == GUI::ModelRole::Icon) + return icon_for(thread); + + if (role == GUI::ModelRole::IconOpacity) { + if (thread.current_state.uid != getuid()) + return 0.5f; + return {}; + } + return {}; } +GUI::Icon ProcessModel::icon_for(Thread const& thread) const +{ + if (thread.current_state.kernel) + return m_kernel_process_icon; + return GUI::FileIconProvider::icon_for_executable(thread.current_state.executable); +} + +GUI::ModelIndex ProcessModel::index(int row, int column, GUI::ModelIndex const& parent) const +{ + if (row < 0 || column < 0) + return {}; + // Process index; we display the main thread here. + if (!parent.is_valid()) { + if (row >= static_cast<int>(m_processes.size())) + return {}; + auto corresponding_thread = m_processes[row].main_thread(); + return create_index(row, column, corresponding_thread.ptr()); + } + // Thread under process. + auto const& parent_thread = *static_cast<Thread const*>(parent.internal_data()); + auto const& process = parent_thread.current_state.process; + // dbgln("Getting thread model index in process {} for col {} row {}", process.pid, column, row); + if (row >= static_cast<int>(process.threads.size())) + return {}; + return create_index(row, column, &process.non_main_thread(row)); +} + +int ProcessModel::thread_model_row(Thread const& thread) const +{ + auto const& process = thread.current_state.process; + // A process's main thread uses the global process index. + if (process.pid == thread.current_state.pid) + return m_processes.find_first_index(process).value_or(0); + + return process.threads.find_first_index(thread).value_or(0); +} + +GUI::ModelIndex ProcessModel::parent_index(GUI::ModelIndex const& index) const +{ + if (!index.is_valid()) + return {}; + auto const& thread = *static_cast<Thread*>(index.internal_data()); + // There's no parent for the main thread. + if (thread.current_state.pid == thread.current_state.tid) + return {}; + // FIXME: We can't use first_matching here (not even a const version) because Optional cannot contain references. + auto const& parent = thread.current_state.process; + + return create_index(m_processes.find_first_index(parent).release_value(), index.column(), parent.main_thread().ptr()); +} + Vector<GUI::ModelIndex> ProcessModel::matches(StringView searching, unsigned flags, GUI::ModelIndex const&) { Vector<GUI::ModelIndex> found_indices; - for (auto& thread : m_threads) { + for (auto const& thread : m_threads) { if (string_matches(thread.value->current_state.name, searching, flags)) { - auto maybe_tid_index = m_tids.find_first_index(thread.key); - if (!maybe_tid_index.has_value()) - continue; - found_indices.append(create_index(maybe_tid_index.value(), Column::Name)); + auto tid_row = thread_model_row(thread.value); + + found_indices.append(create_index(tid_row, Column::Name, reinterpret_cast<void const*>(thread.value.ptr()))); if (flags & FirstMatchOnly) break; } @@ -327,7 +398,7 @@ Vector<GUI::ModelIndex> ProcessModel::matches(StringView searching, unsigned fla void ProcessModel::update() { - auto previous_tid_count = m_tids.size(); + auto previous_tid_count = m_threads.size(); auto all_processes = Core::ProcessStatisticsReader::get_all(m_proc_all); HashTable<int> live_tids; @@ -340,14 +411,46 @@ void ProcessModel::update() m_total_time_scheduled_kernel = all_processes->total_time_scheduled_kernel; m_has_total_scheduled_time = true; - for (auto& process : all_processes.value().processes) { + for (size_t i = 0; i < all_processes->processes.size(); ++i) { + auto const& process = all_processes->processes[i]; + NonnullOwnPtr<Process>* process_state = nullptr; + for (size_t i = 0; i < m_processes.size(); ++i) { + auto* other_process = &m_processes.ptr_at(i); + if ((*other_process)->pid == process.pid) { + process_state = other_process; + break; + } + } + if (!process_state) { + m_processes.append(make<Process>()); + process_state = &m_processes.ptr_at(m_processes.size() - 1); + } + (*process_state)->pid = process.pid; for (auto& thread : process.threads) { - ThreadState state; - state.kernel = process.kernel; + ThreadState state(**process_state); + state.tid = thread.tid; state.pid = process.pid; + state.ppid = process.ppid; + state.pgid = process.pgid; + state.sid = process.sid; + state.time_user = thread.time_user; + state.time_kernel = thread.time_kernel; + state.kernel = process.kernel; + state.executable = process.executable; + state.name = thread.name; + state.uid = process.uid; + state.state = thread.state; state.user = process.username; state.pledge = process.pledge; state.veil = process.veil; + state.cpu = thread.cpu; + state.priority = thread.priority; + state.amount_virtual = process.amount_virtual; + state.amount_resident = process.amount_resident; + state.amount_dirty_private = process.amount_dirty_private; + state.amount_clean_inode = process.amount_clean_inode; + state.amount_purgeable_volatile = process.amount_purgeable_volatile; + state.amount_purgeable_nonvolatile = process.amount_purgeable_nonvolatile; state.syscall_count = thread.syscall_count; state.inode_faults = thread.inode_faults; state.zero_faults = thread.zero_faults; @@ -358,36 +461,22 @@ void ProcessModel::update() state.ipv4_socket_write_bytes = thread.ipv4_socket_write_bytes; state.file_read_bytes = thread.file_read_bytes; state.file_write_bytes = thread.file_write_bytes; - state.amount_virtual = process.amount_virtual; - state.amount_resident = process.amount_resident; - state.amount_dirty_private = process.amount_dirty_private; - state.amount_clean_inode = process.amount_clean_inode; - state.amount_purgeable_volatile = process.amount_purgeable_volatile; - state.amount_purgeable_nonvolatile = process.amount_purgeable_nonvolatile; - - state.name = thread.name; - state.executable = process.executable; - - state.ppid = process.ppid; - state.tid = thread.tid; - state.pgid = process.pgid; - state.sid = process.sid; - state.time_user = thread.time_user; - state.time_kernel = thread.time_kernel; - state.cpu = thread.cpu; state.cpu_percent = 0; - state.priority = thread.priority; - state.state = thread.state; - auto& thread_data = *m_threads.ensure(thread.tid, [] { return make<Thread>(); }); - thread_data.previous_state = move(thread_data.current_state); - thread_data.current_state = move(state); + + auto thread_data = m_threads.ensure(thread.tid, [&] { return make_ref_counted<Thread>(**process_state); }); + thread_data->previous_state = move(thread_data->current_state); + thread_data->current_state = move(state); + if (auto maybe_thread_index = (*process_state)->threads.find_first_index(thread_data); maybe_thread_index.has_value()) { + (*process_state)->threads.ptr_at(maybe_thread_index.value()) = thread_data; + } else { + (*process_state)->threads.append(thread_data); + } live_tids.set(thread.tid); } } } - m_tids.clear(); for (auto& c : m_cpus) { c.total_cpu_percent = 0.0; c.total_cpu_percent_kernel = 0.0; @@ -409,12 +498,21 @@ void ProcessModel::update() auto& cpu_info = m_cpus[thread.current_state.cpu]; cpu_info.total_cpu_percent += thread.current_state.cpu_percent; cpu_info.total_cpu_percent_kernel += thread.current_state.cpu_percent_kernel; - m_tids.append(it.key); } } - for (auto tid : tids_to_remove) + // FIXME: Also remove dead threads from processes + for (auto tid : tids_to_remove) { m_threads.remove(tid); + for (size_t i = 0; i < m_processes.size(); ++i) { + auto& process = m_processes[i]; + process.threads.remove_all_matching([&](auto const& thread) { return thread->current_state.tid == tid; }); + if (process.threads.size() == 0) { + m_processes.remove(i); + --i; + } + } + } if (on_cpu_info_change) on_cpu_info_change(m_cpus); @@ -424,5 +522,5 @@ void ProcessModel::update() // FIXME: This is a rather hackish way of invalidating indices. // It would be good if GUI::Model had a way to orchestrate removal/insertion while preserving indices. - did_update(previous_tid_count == m_tids.size() ? GUI::Model::UpdateFlag::DontInvalidateIndices : GUI::Model::UpdateFlag::InvalidateAllIndices); + did_update(previous_tid_count == m_threads.size() ? GUI::Model::UpdateFlag::DontInvalidateIndices : GUI::Model::UpdateFlag::InvalidateAllIndices); } diff --git a/Userland/Applications/SystemMonitor/ProcessModel.h b/Userland/Applications/SystemMonitor/ProcessModel.h index e076ccaa32..93f7618f46 100644 --- a/Userland/Applications/SystemMonitor/ProcessModel.h +++ b/Userland/Applications/SystemMonitor/ProcessModel.h @@ -11,7 +11,10 @@ #include <AK/NonnullOwnPtrVector.h> #include <AK/String.h> #include <AK/Vector.h> +#include <LibGUI/Icon.h> #include <LibGUI/Model.h> +#include <LibGUI/ModelIndex.h> +#include <sys/types.h> #include <unistd.h> class GraphWidget; @@ -20,9 +23,9 @@ class ProcessModel final : public GUI::Model { public: enum Column { Icon = 0, + Name, PID, TID, - Name, CPU, State, User, @@ -57,10 +60,13 @@ public: static NonnullRefPtr<ProcessModel> create() { return adopt_ref(*new ProcessModel); } virtual ~ProcessModel() override = default; + virtual int tree_column() const override { return Column::Name; } virtual int row_count(GUI::ModelIndex const&) const override; virtual int column_count(GUI::ModelIndex const&) const override; virtual String column_name(int column) const override; virtual GUI::Variant data(GUI::ModelIndex const&, GUI::ModelRole) const override; + virtual GUI::ModelIndex index(int row, int column, GUI::ModelIndex const& parent = {}) const override; + virtual GUI::ModelIndex parent_index(GUI::ModelIndex const&) const override; virtual bool is_searchable() const override { return true; } virtual Vector<GUI::ModelIndex> matches(StringView, unsigned = MatchesFlag::AllMatching, GUI::ModelIndex const& = GUI::ModelIndex()) override; virtual bool is_column_sortable(int column_index) const override { return column_index != Column::Icon; } @@ -85,6 +91,8 @@ public: private: ProcessModel(); + struct Process; + struct ThreadState { pid_t tid; pid_t pid; @@ -96,6 +104,7 @@ private: bool kernel; String executable; String name; + uid_t uid; String state; String user; String pledge; @@ -120,16 +129,118 @@ private: unsigned file_write_bytes; float cpu_percent; float cpu_percent_kernel; + Process& process; + + ThreadState(Process& argument_process) + : process(argument_process) + { + } + ThreadState(ThreadState&& other) = default; + ThreadState& operator=(ThreadState&& other) + { + this->tid = other.tid; + this->pid = other.pid; + this->ppid = other.ppid; + this->pgid = other.pgid; + this->sid = other.sid; + this->time_user = other.time_user; + this->time_kernel = other.time_kernel; + this->kernel = other.kernel; + this->executable = other.executable; + this->name = other.name; + this->uid = other.uid; + this->state = other.state; + this->user = other.user; + this->pledge = other.pledge; + this->veil = other.veil; + this->cpu = other.cpu; + this->priority = other.priority; + this->amount_virtual = other.amount_virtual; + this->amount_resident = other.amount_resident; + this->amount_dirty_private = other.amount_dirty_private; + this->amount_clean_inode = other.amount_clean_inode; + this->amount_purgeable_volatile = other.amount_purgeable_volatile; + this->amount_purgeable_nonvolatile = other.amount_purgeable_nonvolatile; + this->syscall_count = other.syscall_count; + this->inode_faults = other.inode_faults; + this->zero_faults = other.zero_faults; + this->cow_faults = other.cow_faults; + this->unix_socket_read_bytes = other.unix_socket_read_bytes; + this->unix_socket_write_bytes = other.unix_socket_write_bytes; + this->ipv4_socket_read_bytes = other.ipv4_socket_read_bytes; + this->ipv4_socket_write_bytes = other.ipv4_socket_write_bytes; + this->file_read_bytes = other.file_read_bytes; + this->file_write_bytes = other.file_write_bytes; + this->cpu_percent = other.cpu_percent; + this->cpu_percent_kernel = other.cpu_percent_kernel; + this->process = other.process; + + return *this; + } + ~ThreadState() = default; }; - struct Thread { + struct Thread : public RefCounted<Thread> { ThreadState current_state; ThreadState previous_state; + + Thread(Process& process) + : current_state(process) + , previous_state(process) + { + } + + bool operator==(Thread const& other) const + { + return current_state.tid == other.current_state.tid; + } + + bool is_main_thread() const + { + return current_state.tid == current_state.process.pid; + } + }; + + struct Process { + pid_t pid; + NonnullRefPtrVector<Thread> threads; + + bool operator==(Process const& other) const + { + return this->pid == other.pid; + } + + NonnullRefPtr<Thread> main_thread() const + { + return *threads.first_matching([this](auto const thread) { return thread->current_state.tid == pid; }).value(); + } + + // Return anything but the main thread; therefore, valid indices are anything up to threads.size()-1 exclusive. + Thread const& non_main_thread(size_t index) const + { + auto main_thread_index = -1; + for (size_t i = 0; i < threads.size(); ++i) { + if (threads[i].is_main_thread()) { + main_thread_index = static_cast<int>(i); + break; + } + } + VERIFY(main_thread_index >= 0); + // Shift all indices starting from the main thread's index upwards, so that the user doesn't have to worry about index discontinuities. + if (index >= static_cast<size_t>(main_thread_index)) + return threads[index + 1]; + return threads[index]; + } }; - HashMap<int, NonnullOwnPtr<Thread>> m_threads; + GUI::Icon icon_for(Thread const& thread) const; + + int thread_model_row(Thread const& thread) const; + + // The thread list contains the same threads as the Process structs. + HashMap<int, NonnullRefPtr<Thread>> m_threads; + NonnullOwnPtrVector<Process> m_processes; NonnullOwnPtrVector<CpuInfo> m_cpus; - Vector<int> m_tids; RefPtr<Core::File> m_proc_all; GUI::Icon m_kernel_process_icon; u64 m_total_time_scheduled { 0 }; diff --git a/Userland/Applications/SystemMonitor/SystemMonitor.gml b/Userland/Applications/SystemMonitor/SystemMonitor.gml index 530a564d03..99ed6dabe8 100644 --- a/Userland/Applications/SystemMonitor/SystemMonitor.gml +++ b/Userland/Applications/SystemMonitor/SystemMonitor.gml @@ -23,9 +23,11 @@ spacing: 0 } - @GUI::TableView { + @GUI::TreeView { name: "process_table" column_headers_visible: true + should_fill_selected_rows: true + selection_behavior: "SelectRows" } } diff --git a/Userland/Applications/SystemMonitor/main.cpp b/Userland/Applications/SystemMonitor/main.cpp index 30f6beaade..eb2272ebad 100644 --- a/Userland/Applications/SystemMonitor/main.cpp +++ b/Userland/Applications/SystemMonitor/main.cpp @@ -7,8 +7,6 @@ */ #include "GraphWidget.h" -#include "LibCore/EventLoop.h" -#include "LibCore/Object.h" #include "MemoryStatsWidget.h" #include "NetworkStatisticsWidget.h" #include "ProcessFileDescriptorMapWidget.h" @@ -22,6 +20,8 @@ #include <Applications/SystemMonitor/SystemMonitorGML.h> #include <LibConfig/Client.h> #include <LibCore/ArgsParser.h> +#include <LibCore/EventLoop.h> +#include <LibCore/Object.h> #include <LibCore/System.h> #include <LibCore/Timer.h> #include <LibGUI/Action.h> @@ -43,7 +43,7 @@ #include <LibGUI/StackWidget.h> #include <LibGUI/Statusbar.h> #include <LibGUI/TabWidget.h> -#include <LibGUI/TableView.h> +#include <LibGUI/TreeView.h> #include <LibGUI/Widget.h> #include <LibGUI/Window.h> #include <LibGfx/FontDatabase.h> @@ -385,12 +385,12 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) auto& performance_widget = *tabwidget.find_descendant_of_type_named<GUI::Widget>("performance"); build_performance_tab(performance_widget); - auto& process_table_view = *process_table_container.find_child_of_type_named<GUI::TableView>("process_table"); + auto& process_table_view = *process_table_container.find_child_of_type_named<GUI::TreeView>("process_table"); process_table_view.set_model(TRY(GUI::SortingProxyModel::create(process_model))); for (auto column = 0; column < ProcessModel::Column::__Count; ++column) process_table_view.set_column_visible(column, false); - process_table_view.set_column_visible(ProcessModel::Column::Icon, true); process_table_view.set_column_visible(ProcessModel::Column::PID, true); + process_table_view.set_column_visible(ProcessModel::Column::TID, true); process_table_view.set_column_visible(ProcessModel::Column::Name, true); process_table_view.set_column_visible(ProcessModel::Column::CPU, true); process_table_view.set_column_visible(ProcessModel::Column::User, true); |