From 712ae73581a1b44b36654824229f9cc475381c34 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 26 Nov 2019 21:25:45 +0100 Subject: [PATCH] Kernel: Expose per-thread information in /proc/all Previously it was not possible to see what each thread in a process was up to, or how much CPU it was consuming. This patch fixes that. SystemMonitor and "top" now show threads instead of just processes. "ps" is gonna need some more fixing, but it at least builds for now. Fixes #66. --- Applications/SystemMonitor/ProcessModel.cpp | 144 ++++++++++-------- Applications/SystemMonitor/ProcessModel.h | 32 +++- DevTools/HackStudio/ProcessStateWidget.cpp | 4 +- Kernel/FileSystem/ProcFS.cpp | 14 +- .../LibCore/CProcessStatisticsReader.cpp | 16 +- Libraries/LibCore/CProcessStatisticsReader.h | 17 ++- Servers/WindowServer/WSCPUMonitor.cpp | 10 +- Userland/ps.cpp | 4 +- Userland/top.cpp | 125 +++++++++++---- 9 files changed, 243 insertions(+), 123 deletions(-) diff --git a/Applications/SystemMonitor/ProcessModel.cpp b/Applications/SystemMonitor/ProcessModel.cpp index ec79fa0c99..2445976ade 100644 --- a/Applications/SystemMonitor/ProcessModel.cpp +++ b/Applications/SystemMonitor/ProcessModel.cpp @@ -47,6 +47,8 @@ String ProcessModel::column_name(int column) const return ""; case Column::PID: return "PID"; + case Column::TID: + return "TID"; case Column::State: return "State"; case Column::User: @@ -81,6 +83,8 @@ GModel::ColumnMetadata ProcessModel::column_metadata(int column) const return { 16, TextAlignment::CenterLeft }; case Column::PID: return { 32, TextAlignment::CenterRight }; + case Column::TID: + return { 32, TextAlignment::CenterRight }; case Column::State: return { 75, TextAlignment::CenterLeft }; case Column::Priority: @@ -117,47 +121,49 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const { ASSERT(is_valid(index)); - auto it = m_processes.find(m_pids[index.row()]); - auto& process = *(*it).value; + auto it = m_threads.find(m_pids[index.row()]); + auto& thread = *(*it).value; if (role == Role::Sort) { switch (index.column()) { case Column::Icon: return 0; case Column::PID: - return process.current_state.pid; + return thread.current_state.pid; + case Column::TID: + return thread.current_state.tid; case Column::State: - return process.current_state.state; + return thread.current_state.state; case Column::User: - return process.current_state.user; + return thread.current_state.user; case Column::Priority: - if (process.current_state.priority == "Idle") + if (thread.current_state.priority == "Idle") return 0; - if (process.current_state.priority == "Low") + if (thread.current_state.priority == "Low") return 1; - if (process.current_state.priority == "Normal") + if (thread.current_state.priority == "Normal") return 2; - if (process.current_state.priority == "High") + if (thread.current_state.priority == "High") return 3; ASSERT_NOT_REACHED(); return 3; case Column::Virtual: - return (int)process.current_state.amount_virtual; + return (int)thread.current_state.amount_virtual; case Column::Physical: - return (int)process.current_state.amount_resident; + return (int)thread.current_state.amount_resident; case Column::CPU: - return process.current_state.cpu_percent; + return thread.current_state.cpu_percent; case Column::Name: - return process.current_state.name; + return thread.current_state.name; // FIXME: GVariant with unsigned? case Column::Syscalls: - return (int)process.current_state.syscall_count; + return (int)thread.current_state.syscall_count; case Column::InodeFaults: - return (int)process.current_state.inode_faults; + return (int)thread.current_state.inode_faults; case Column::ZeroFaults: - return (int)process.current_state.zero_faults; + return (int)thread.current_state.zero_faults; case Column::CowFaults: - return (int)process.current_state.cow_faults; + return (int)thread.current_state.cow_faults; } ASSERT_NOT_REACHED(); return {}; @@ -166,8 +172,8 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const if (role == Role::Display) { switch (index.column()) { case Column::Icon: - if (process.current_state.icon_id != -1) { - auto icon_buffer = SharedBuffer::create_from_shared_buffer_id(process.current_state.icon_id); + if (thread.current_state.icon_id != -1) { + auto icon_buffer = SharedBuffer::create_from_shared_buffer_id(thread.current_state.icon_id); if (icon_buffer) { auto icon_bitmap = GraphicsBitmap::create_with_shared_buffer(GraphicsBitmap::Format::RGBA32, *icon_buffer, { 16, 16 }); if (icon_bitmap) @@ -176,38 +182,40 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const } return *m_generic_process_icon; case Column::PID: - return process.current_state.pid; + return thread.current_state.pid; + case Column::TID: + return thread.current_state.tid; case Column::State: - return process.current_state.state; + return thread.current_state.state; case Column::User: - return process.current_state.user; + return thread.current_state.user; case Column::Priority: - if (process.current_state.priority == "Idle") + if (thread.current_state.priority == "Idle") return String::empty(); - if (process.current_state.priority == "High") + if (thread.current_state.priority == "High") return *m_high_priority_icon; - if (process.current_state.priority == "Low") + if (thread.current_state.priority == "Low") return *m_low_priority_icon; - if (process.current_state.priority == "Normal") + if (thread.current_state.priority == "Normal") return *m_normal_priority_icon; - return process.current_state.priority; + return thread.current_state.priority; case Column::Virtual: - return pretty_byte_size(process.current_state.amount_virtual); + return pretty_byte_size(thread.current_state.amount_virtual); case Column::Physical: - return pretty_byte_size(process.current_state.amount_resident); + return pretty_byte_size(thread.current_state.amount_resident); case Column::CPU: - return process.current_state.cpu_percent; + return thread.current_state.cpu_percent; case Column::Name: - return process.current_state.name; + return thread.current_state.name; // FIXME: It's weird that GVariant doesn't support unsigned ints. Should it? case Column::Syscalls: - return (int)process.current_state.syscall_count; + return (int)thread.current_state.syscall_count; case Column::InodeFaults: - return (int)process.current_state.inode_faults; + return (int)thread.current_state.inode_faults; case Column::ZeroFaults: - return (int)process.current_state.zero_faults; + return (int)thread.current_state.zero_faults; case Column::CowFaults: - return (int)process.current_state.cow_faults; + return (int)thread.current_state.cow_faults; } } @@ -219,44 +227,48 @@ void ProcessModel::update() auto all_processes = CProcessStatisticsReader::get_all(); unsigned last_sum_times_scheduled = 0; - for (auto& it : m_processes) + for (auto& it : m_threads) last_sum_times_scheduled += it.value->current_state.times_scheduled; - HashTable live_pids; + HashTable live_pids; unsigned sum_times_scheduled = 0; for (auto& it : all_processes) { - ProcessState state; - state.pid = it.value.pid; - state.times_scheduled = it.value.times_scheduled; - state.user = it.value.username; - state.priority = it.value.priority; - state.syscall_count = it.value.syscall_count; - state.inode_faults = it.value.inode_faults; - state.zero_faults = it.value.zero_faults; - state.cow_faults = it.value.cow_faults; - state.state = it.value.state; - state.name = it.value.name; - state.amount_virtual = it.value.amount_virtual; - state.amount_resident = it.value.amount_resident; - state.icon_id = it.value.icon_id; - sum_times_scheduled += it.value.times_scheduled; - { - auto pit = m_processes.find(it.value.pid); - if (pit == m_processes.end()) - m_processes.set(it.value.pid, make()); - } - auto pit = m_processes.find(it.value.pid); - ASSERT(pit != m_processes.end()); - (*pit).value->previous_state = (*pit).value->current_state; - (*pit).value->current_state = state; + for (auto& thread : it.value.threads) { + ThreadState state; + state.pid = it.value.pid; + state.user = it.value.username; + state.syscall_count = it.value.syscall_count; + state.inode_faults = it.value.inode_faults; + state.zero_faults = it.value.zero_faults; + state.cow_faults = it.value.cow_faults; + state.name = it.value.name; + state.amount_virtual = it.value.amount_virtual; + state.amount_resident = it.value.amount_resident; + state.icon_id = it.value.icon_id; - live_pids.set(it.value.pid); + state.tid = thread.tid; + state.times_scheduled = thread.times_scheduled; + state.priority = thread.priority; + state.state = thread.state; + sum_times_scheduled += thread.times_scheduled; + { + auto pit = m_threads.find({ it.value.pid, thread.tid }); + if (pit == m_threads.end()) + m_threads.set({ it.value.pid, thread.tid }, make()); + } + auto pit = m_threads.find({ it.value.pid, thread.tid }); + ASSERT(pit != m_threads.end()); + (*pit).value->previous_state = (*pit).value->current_state; + (*pit).value->current_state = state; + + live_pids.set({ it.value.pid, thread.tid }); + } } m_pids.clear(); float total_cpu_percent = 0; - Vector pids_to_remove; - for (auto& it : m_processes) { + Vector pids_to_remove; + for (auto& it : m_threads) { if (!live_pids.contains(it.key)) { pids_to_remove.append(it.key); continue; @@ -264,13 +276,13 @@ void ProcessModel::update() auto& process = *it.value; u32 times_scheduled_diff = process.current_state.times_scheduled - process.previous_state.times_scheduled; process.current_state.cpu_percent = ((float)times_scheduled_diff * 100) / (float)(sum_times_scheduled - last_sum_times_scheduled); - if (it.key != 0) { + if (it.key.pid != 0) { total_cpu_percent += process.current_state.cpu_percent; m_pids.append(it.key); } } for (auto pid : pids_to_remove) - m_processes.remove(pid); + m_threads.remove(pid); if (on_new_cpu_data_point) on_new_cpu_data_point(total_cpu_percent); diff --git a/Applications/SystemMonitor/ProcessModel.h b/Applications/SystemMonitor/ProcessModel.h index fc54488583..c6365d109f 100644 --- a/Applications/SystemMonitor/ProcessModel.h +++ b/Applications/SystemMonitor/ProcessModel.h @@ -1,13 +1,22 @@ #pragma once -#include #include +#include #include #include #include class GraphWidget; +struct PidAndTid { + bool operator==(const PidAndTid& other) const + { + return pid == other.pid && tid == other.tid; + } + pid_t pid; + int tid; +}; + class ProcessModel final : public GModel { public: enum Column { @@ -18,6 +27,7 @@ public: Priority, User, PID, + TID, Virtual, Physical, Syscalls, @@ -44,7 +54,8 @@ public: private: ProcessModel(); - struct ProcessState { + struct ThreadState { + int tid; pid_t pid; unsigned times_scheduled; String name; @@ -61,16 +72,23 @@ private: int icon_id; }; - struct Process { - ProcessState current_state; - ProcessState previous_state; + struct Thread { + ThreadState current_state; + ThreadState previous_state; }; HashMap m_usernames; - HashMap> m_processes; - Vector m_pids; + HashMap> m_threads; + Vector m_pids; RefPtr m_generic_process_icon; RefPtr m_high_priority_icon; RefPtr m_low_priority_icon; RefPtr m_normal_priority_icon; }; + +namespace AK { +template<> +struct Traits : public GenericTraits { + static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); } +}; +} diff --git a/DevTools/HackStudio/ProcessStateWidget.cpp b/DevTools/HackStudio/ProcessStateWidget.cpp index 2509f0944e..f195c91cd8 100644 --- a/DevTools/HackStudio/ProcessStateWidget.cpp +++ b/DevTools/HackStudio/ProcessStateWidget.cpp @@ -55,8 +55,8 @@ void ProcessStateWidget::refresh() auto& data = active_process_data.value(); m_pid_label->set_text(String::format("%s(%d)", data.name.characters(), pid)); - m_state_label->set_text(data.state); - m_cpu_label->set_text(String::format("%d", data.times_scheduled)); + m_state_label->set_text(data.threads.first().state); + m_cpu_label->set_text(String::format("%d", data.threads.first().times_scheduled)); m_memory_label->set_text(String::format("%d", data.amount_resident)); } diff --git a/Kernel/FileSystem/ProcFS.cpp b/Kernel/FileSystem/ProcFS.cpp index bb7a86d7bc..928719b4fe 100644 --- a/Kernel/FileSystem/ProcFS.cpp +++ b/Kernel/FileSystem/ProcFS.cpp @@ -677,13 +677,11 @@ Optional procfs$all(InodeIdentifier) auto build_process = [&](const Process& process) { auto process_object = array.add_object(); process_object.add("pid", process.pid()); - process_object.add("times_scheduled", process.main_thread().times_scheduled()); process_object.add("pgid", process.tty() ? process.tty()->pgid() : 0); process_object.add("pgp", process.pgid()); process_object.add("sid", process.sid()); process_object.add("uid", process.uid()); process_object.add("gid", process.gid()); - process_object.add("state", process.main_thread().state_string()); process_object.add("ppid", process.ppid()); process_object.add("nfds", process.number_of_open_file_descriptors()); process_object.add("name", process.name()); @@ -691,13 +689,21 @@ Optional procfs$all(InodeIdentifier) process_object.add("amount_virtual", (u32)process.amount_virtual()); process_object.add("amount_resident", (u32)process.amount_resident()); process_object.add("amount_shared", (u32)process.amount_shared()); - process_object.add("ticks", process.main_thread().ticks()); - process_object.add("priority", to_string(process.main_thread().priority())); process_object.add("syscall_count", process.syscall_count()); process_object.add("inode_faults", process.inode_faults()); process_object.add("zero_faults", process.zero_faults()); process_object.add("cow_faults", process.cow_faults()); process_object.add("icon_id", process.icon_id()); + auto thread_array = process_object.add_array("threads"); + process.for_each_thread([&](const Thread& thread) { + auto thread_object = thread_array.add_object(); + thread_object.add("tid", thread.tid()); + thread_object.add("times_scheduled", thread.times_scheduled()); + thread_object.add("ticks", thread.ticks()); + thread_object.add("state", thread.state_string()); + thread_object.add("priority", to_string(thread.priority())); + return IterationDecision::Continue; + }); }; build_process(*Scheduler::colonel()); for (auto* process : processes) diff --git a/Libraries/LibCore/CProcessStatisticsReader.cpp b/Libraries/LibCore/CProcessStatisticsReader.cpp index 1ccb72a9e3..c9e2becbe2 100644 --- a/Libraries/LibCore/CProcessStatisticsReader.cpp +++ b/Libraries/LibCore/CProcessStatisticsReader.cpp @@ -26,13 +26,11 @@ HashMap CProcessStatisticsReader::get_all() // kernel data first process.pid = process_object.get("pid").to_u32(); - process.times_scheduled = process_object.get("times_scheduled").to_u32(); process.pgid = process_object.get("pgid").to_u32(); process.pgp = process_object.get("pgp").to_u32(); process.sid = process_object.get("sid").to_u32(); process.uid = process_object.get("uid").to_u32(); process.gid = process_object.get("gid").to_u32(); - process.state = process_object.get("state").to_string(); process.ppid = process_object.get("ppid").to_u32(); process.nfds = process_object.get("nfds").to_u32(); process.name = process_object.get("name").to_string(); @@ -40,14 +38,24 @@ HashMap CProcessStatisticsReader::get_all() process.amount_virtual = process_object.get("amount_virtual").to_u32(); process.amount_resident = process_object.get("amount_resident").to_u32(); process.amount_shared = process_object.get("amount_shared").to_u32(); - process.ticks = process_object.get("ticks").to_u32(); - process.priority = process_object.get("priority").to_string(); process.syscall_count = process_object.get("syscall_count").to_u32(); process.inode_faults = process_object.get("inode_faults").to_u32(); process.zero_faults = process_object.get("zero_faults").to_u32(); process.cow_faults = process_object.get("cow_faults").to_u32(); process.icon_id = process_object.get("icon_id").to_int(); + auto thread_array = process_object.get("threads").as_array(); + thread_array.for_each([&](auto& value) { + auto& thread_object = value.as_object(); + CThreadStatistics thread; + thread.tid = thread_object.get("tid").to_u32(); + thread.times_scheduled = thread_object.get("times_scheduled").to_u32(); + thread.state = thread_object.get("state").to_string(); + thread.ticks = thread_object.get("ticks").to_u32(); + thread.priority = thread_object.get("priority").to_string(); + process.threads.append(move(thread)); + }); + // and synthetic data last process.username = username_from_uid(process.uid); map.set(process.pid, process); diff --git a/Libraries/LibCore/CProcessStatisticsReader.h b/Libraries/LibCore/CProcessStatisticsReader.h index c793d1f8f4..848821cb26 100644 --- a/Libraries/LibCore/CProcessStatisticsReader.h +++ b/Libraries/LibCore/CProcessStatisticsReader.h @@ -1,19 +1,26 @@ #pragma once -#include #include +#include +#include + +struct CThreadStatistics { + int tid; + unsigned times_scheduled; + unsigned ticks; + String state; + String priority; +}; struct CProcessStatistics { // Keep this in sync with /proc/all. // From the kernel side: pid_t pid; - unsigned times_scheduled; unsigned pgid; unsigned pgp; unsigned sid; uid_t uid; gid_t gid; - String state; pid_t ppid; unsigned nfds; String name; @@ -21,14 +28,14 @@ struct CProcessStatistics { size_t amount_virtual; size_t amount_resident; size_t amount_shared; - unsigned ticks; - String priority; unsigned syscall_count; unsigned inode_faults; unsigned zero_faults; unsigned cow_faults; int icon_id; + Vector threads; + // synthetic String username; }; diff --git a/Servers/WindowServer/WSCPUMonitor.cpp b/Servers/WindowServer/WSCPUMonitor.cpp index d1fb2d8653..175c5d2672 100644 --- a/Servers/WindowServer/WSCPUMonitor.cpp +++ b/Servers/WindowServer/WSCPUMonitor.cpp @@ -44,10 +44,12 @@ void WSCPUMonitor::get_cpu_usage(unsigned& busy, unsigned& idle) auto all_processes = CProcessStatisticsReader::get_all(); for (auto& it : all_processes) { - if (it.value.pid == 0) - idle += it.value.times_scheduled; - else - busy += it.value.times_scheduled; + for (auto& jt : it.value.threads) { + if (it.value.pid == 0) + idle += jt.times_scheduled; + else + busy += jt.times_scheduled; + } } } diff --git a/Userland/ps.cpp b/Userland/ps.cpp index a56da0c12d..1cadff536b 100644 --- a/Userland/ps.cpp +++ b/Userland/ps.cpp @@ -28,9 +28,9 @@ int main(int argc, char** argv) proc.pgp, proc.sid, proc.uid, - proc.state.characters(), + proc.threads.first().state.characters(), proc.ppid, - proc.times_scheduled, + proc.threads.first().times_scheduled, proc.nfds, tty.characters(), proc.name.characters()); diff --git a/Userland/top.cpp b/Userland/top.cpp index 9654493a2b..de987d7055 100644 --- a/Userland/top.cpp +++ b/Userland/top.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include #include #include @@ -11,15 +11,55 @@ #include #include -struct ProcessData { - CProcessStatistics stats; +struct ThreadData { + int tid; + pid_t pid; + unsigned pgid; + unsigned pgp; + unsigned sid; + uid_t uid; + gid_t gid; + pid_t ppid; + unsigned nfds; + String name; + String tty; + size_t amount_virtual; + size_t amount_resident; + size_t amount_shared; + unsigned syscall_count; + unsigned inode_faults; + unsigned zero_faults; + unsigned cow_faults; + int icon_id; + unsigned times_scheduled; + unsigned times_scheduled_since_prev { 0 }; unsigned cpu_percent { 0 }; unsigned cpu_percent_decimal { 0 }; + + String priority; + String username; + String state; }; +struct PidAndTid { + bool operator==(const PidAndTid& other) const + { + return pid == other.pid && tid == other.tid; + } + pid_t pid; + int tid; +}; + +namespace AK { +template<> +struct Traits : public GenericTraits { + static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); } +}; +} + struct Snapshot { - HashMap map; + HashMap map; u32 sum_times_scheduled { 0 }; }; @@ -31,10 +71,35 @@ static Snapshot get_snapshot() for (auto& it : all_processes) { auto& stats = it.value; - snapshot.sum_times_scheduled += stats.times_scheduled; - ProcessData process_data; - process_data.stats = stats; - snapshot.map.set(stats.pid, move(process_data)); + for (auto& thread : stats.threads) { + snapshot.sum_times_scheduled += thread.times_scheduled; + ThreadData thread_data; + thread_data.tid = thread.tid; + thread_data.pid = stats.pid; + thread_data.pgid = stats.pgid; + thread_data.pgp = stats.pgp; + thread_data.sid = stats.sid; + thread_data.uid = stats.uid; + thread_data.gid = stats.gid; + thread_data.ppid = stats.ppid; + thread_data.nfds = stats.nfds; + thread_data.name = stats.name; + thread_data.tty = stats.tty; + thread_data.amount_virtual = stats.amount_virtual; + thread_data.amount_resident = stats.amount_resident; + thread_data.amount_shared = stats.amount_shared; + thread_data.syscall_count = stats.syscall_count; + thread_data.inode_faults = stats.inode_faults; + thread_data.zero_faults = stats.zero_faults; + thread_data.cow_faults = stats.cow_faults; + thread_data.icon_id = stats.icon_id; + thread_data.times_scheduled = thread.times_scheduled; + thread_data.priority = thread.priority; + thread_data.state = thread.state; + thread_data.username = stats.username; + + snapshot.map.set({ stats.pid, thread.tid }, move(thread_data)); + } } return snapshot; @@ -42,7 +107,7 @@ static Snapshot get_snapshot() int main(int, char**) { - Vector processes; + Vector threads; auto prev = get_snapshot(); usleep(10000); for (;;) { @@ -50,8 +115,9 @@ int main(int, char**) auto sum_diff = current.sum_times_scheduled - prev.sum_times_scheduled; printf("\033[3J\033[H\033[2J"); - printf("\033[47;30m%6s %3s %-8s %-8s %6s %6s %4s %s\033[K\033[0m\n", + printf("\033[47;30m%6s %3s %3s %-8s %-10s %6s %6s %4s %s\033[K\033[0m\n", "PID", + "TID", "PRI", "USER", "STATE", @@ -60,38 +126,39 @@ int main(int, char**) "%CPU", "NAME"); for (auto& it : current.map) { - pid_t pid = it.key; - if (pid == 0) + auto pid_and_tid = it.key; + if (pid_and_tid.pid == 0) continue; - u32 times_scheduled_now = it.value.stats.times_scheduled; - auto jt = prev.map.find(pid); + u32 times_scheduled_now = it.value.times_scheduled; + auto jt = prev.map.find(pid_and_tid); if (jt == prev.map.end()) continue; - u32 times_scheduled_before = (*jt).value.stats.times_scheduled; + u32 times_scheduled_before = (*jt).value.times_scheduled; u32 times_scheduled_diff = times_scheduled_now - times_scheduled_before; it.value.times_scheduled_since_prev = times_scheduled_diff; it.value.cpu_percent = ((times_scheduled_diff * 100) / sum_diff); it.value.cpu_percent_decimal = (((times_scheduled_diff * 1000) / sum_diff) % 10); - processes.append(&it.value); + threads.append(&it.value); } - quick_sort(processes.begin(), processes.end(), [](auto* p1, auto* p2) { + quick_sort(threads.begin(), threads.end(), [](auto* p1, auto* p2) { return p2->times_scheduled_since_prev < p1->times_scheduled_since_prev; }); - for (auto* process : processes) { - printf("%6d %c %-8s %-10s %6zu %6zu %2u.%1u %s\n", - process->stats.pid, - process->stats.priority[0], - process->stats.username.characters(), - process->stats.state.characters(), - process->stats.amount_virtual / 1024, - process->stats.amount_resident / 1024, - process->cpu_percent, - process->cpu_percent_decimal, - process->stats.name.characters()); + for (auto* thread : threads) { + printf("%6d %3d %c %-8s %-10s %6zu %6zu %2u.%1u %s\n", + thread->pid, + thread->tid, + thread->priority[0], + thread->username.characters(), + thread->state.characters(), + thread->amount_virtual / 1024, + thread->amount_resident / 1024, + thread->cpu_percent, + thread->cpu_percent_decimal, + thread->name.characters()); } - processes.clear_with_capacity(); + threads.clear_with_capacity(); prev = move(current); sleep(1); }