mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 21:37:35 +00:00
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.
This commit is contained in:
parent
86a9a52355
commit
712ae73581
9 changed files with 243 additions and 123 deletions
|
@ -47,6 +47,8 @@ String ProcessModel::column_name(int column) const
|
||||||
return "";
|
return "";
|
||||||
case Column::PID:
|
case Column::PID:
|
||||||
return "PID";
|
return "PID";
|
||||||
|
case Column::TID:
|
||||||
|
return "TID";
|
||||||
case Column::State:
|
case Column::State:
|
||||||
return "State";
|
return "State";
|
||||||
case Column::User:
|
case Column::User:
|
||||||
|
@ -81,6 +83,8 @@ GModel::ColumnMetadata ProcessModel::column_metadata(int column) const
|
||||||
return { 16, TextAlignment::CenterLeft };
|
return { 16, TextAlignment::CenterLeft };
|
||||||
case Column::PID:
|
case Column::PID:
|
||||||
return { 32, TextAlignment::CenterRight };
|
return { 32, TextAlignment::CenterRight };
|
||||||
|
case Column::TID:
|
||||||
|
return { 32, TextAlignment::CenterRight };
|
||||||
case Column::State:
|
case Column::State:
|
||||||
return { 75, TextAlignment::CenterLeft };
|
return { 75, TextAlignment::CenterLeft };
|
||||||
case Column::Priority:
|
case Column::Priority:
|
||||||
|
@ -117,47 +121,49 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const
|
||||||
{
|
{
|
||||||
ASSERT(is_valid(index));
|
ASSERT(is_valid(index));
|
||||||
|
|
||||||
auto it = m_processes.find(m_pids[index.row()]);
|
auto it = m_threads.find(m_pids[index.row()]);
|
||||||
auto& process = *(*it).value;
|
auto& thread = *(*it).value;
|
||||||
|
|
||||||
if (role == Role::Sort) {
|
if (role == Role::Sort) {
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case Column::Icon:
|
case Column::Icon:
|
||||||
return 0;
|
return 0;
|
||||||
case Column::PID:
|
case Column::PID:
|
||||||
return process.current_state.pid;
|
return thread.current_state.pid;
|
||||||
|
case Column::TID:
|
||||||
|
return thread.current_state.tid;
|
||||||
case Column::State:
|
case Column::State:
|
||||||
return process.current_state.state;
|
return thread.current_state.state;
|
||||||
case Column::User:
|
case Column::User:
|
||||||
return process.current_state.user;
|
return thread.current_state.user;
|
||||||
case Column::Priority:
|
case Column::Priority:
|
||||||
if (process.current_state.priority == "Idle")
|
if (thread.current_state.priority == "Idle")
|
||||||
return 0;
|
return 0;
|
||||||
if (process.current_state.priority == "Low")
|
if (thread.current_state.priority == "Low")
|
||||||
return 1;
|
return 1;
|
||||||
if (process.current_state.priority == "Normal")
|
if (thread.current_state.priority == "Normal")
|
||||||
return 2;
|
return 2;
|
||||||
if (process.current_state.priority == "High")
|
if (thread.current_state.priority == "High")
|
||||||
return 3;
|
return 3;
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
return 3;
|
return 3;
|
||||||
case Column::Virtual:
|
case Column::Virtual:
|
||||||
return (int)process.current_state.amount_virtual;
|
return (int)thread.current_state.amount_virtual;
|
||||||
case Column::Physical:
|
case Column::Physical:
|
||||||
return (int)process.current_state.amount_resident;
|
return (int)thread.current_state.amount_resident;
|
||||||
case Column::CPU:
|
case Column::CPU:
|
||||||
return process.current_state.cpu_percent;
|
return thread.current_state.cpu_percent;
|
||||||
case Column::Name:
|
case Column::Name:
|
||||||
return process.current_state.name;
|
return thread.current_state.name;
|
||||||
// FIXME: GVariant with unsigned?
|
// FIXME: GVariant with unsigned?
|
||||||
case Column::Syscalls:
|
case Column::Syscalls:
|
||||||
return (int)process.current_state.syscall_count;
|
return (int)thread.current_state.syscall_count;
|
||||||
case Column::InodeFaults:
|
case Column::InodeFaults:
|
||||||
return (int)process.current_state.inode_faults;
|
return (int)thread.current_state.inode_faults;
|
||||||
case Column::ZeroFaults:
|
case Column::ZeroFaults:
|
||||||
return (int)process.current_state.zero_faults;
|
return (int)thread.current_state.zero_faults;
|
||||||
case Column::CowFaults:
|
case Column::CowFaults:
|
||||||
return (int)process.current_state.cow_faults;
|
return (int)thread.current_state.cow_faults;
|
||||||
}
|
}
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
return {};
|
return {};
|
||||||
|
@ -166,8 +172,8 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const
|
||||||
if (role == Role::Display) {
|
if (role == Role::Display) {
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case Column::Icon:
|
case Column::Icon:
|
||||||
if (process.current_state.icon_id != -1) {
|
if (thread.current_state.icon_id != -1) {
|
||||||
auto icon_buffer = SharedBuffer::create_from_shared_buffer_id(process.current_state.icon_id);
|
auto icon_buffer = SharedBuffer::create_from_shared_buffer_id(thread.current_state.icon_id);
|
||||||
if (icon_buffer) {
|
if (icon_buffer) {
|
||||||
auto icon_bitmap = GraphicsBitmap::create_with_shared_buffer(GraphicsBitmap::Format::RGBA32, *icon_buffer, { 16, 16 });
|
auto icon_bitmap = GraphicsBitmap::create_with_shared_buffer(GraphicsBitmap::Format::RGBA32, *icon_buffer, { 16, 16 });
|
||||||
if (icon_bitmap)
|
if (icon_bitmap)
|
||||||
|
@ -176,38 +182,40 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const
|
||||||
}
|
}
|
||||||
return *m_generic_process_icon;
|
return *m_generic_process_icon;
|
||||||
case Column::PID:
|
case Column::PID:
|
||||||
return process.current_state.pid;
|
return thread.current_state.pid;
|
||||||
|
case Column::TID:
|
||||||
|
return thread.current_state.tid;
|
||||||
case Column::State:
|
case Column::State:
|
||||||
return process.current_state.state;
|
return thread.current_state.state;
|
||||||
case Column::User:
|
case Column::User:
|
||||||
return process.current_state.user;
|
return thread.current_state.user;
|
||||||
case Column::Priority:
|
case Column::Priority:
|
||||||
if (process.current_state.priority == "Idle")
|
if (thread.current_state.priority == "Idle")
|
||||||
return String::empty();
|
return String::empty();
|
||||||
if (process.current_state.priority == "High")
|
if (thread.current_state.priority == "High")
|
||||||
return *m_high_priority_icon;
|
return *m_high_priority_icon;
|
||||||
if (process.current_state.priority == "Low")
|
if (thread.current_state.priority == "Low")
|
||||||
return *m_low_priority_icon;
|
return *m_low_priority_icon;
|
||||||
if (process.current_state.priority == "Normal")
|
if (thread.current_state.priority == "Normal")
|
||||||
return *m_normal_priority_icon;
|
return *m_normal_priority_icon;
|
||||||
return process.current_state.priority;
|
return thread.current_state.priority;
|
||||||
case Column::Virtual:
|
case Column::Virtual:
|
||||||
return pretty_byte_size(process.current_state.amount_virtual);
|
return pretty_byte_size(thread.current_state.amount_virtual);
|
||||||
case Column::Physical:
|
case Column::Physical:
|
||||||
return pretty_byte_size(process.current_state.amount_resident);
|
return pretty_byte_size(thread.current_state.amount_resident);
|
||||||
case Column::CPU:
|
case Column::CPU:
|
||||||
return process.current_state.cpu_percent;
|
return thread.current_state.cpu_percent;
|
||||||
case Column::Name:
|
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?
|
// FIXME: It's weird that GVariant doesn't support unsigned ints. Should it?
|
||||||
case Column::Syscalls:
|
case Column::Syscalls:
|
||||||
return (int)process.current_state.syscall_count;
|
return (int)thread.current_state.syscall_count;
|
||||||
case Column::InodeFaults:
|
case Column::InodeFaults:
|
||||||
return (int)process.current_state.inode_faults;
|
return (int)thread.current_state.inode_faults;
|
||||||
case Column::ZeroFaults:
|
case Column::ZeroFaults:
|
||||||
return (int)process.current_state.zero_faults;
|
return (int)thread.current_state.zero_faults;
|
||||||
case Column::CowFaults:
|
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();
|
auto all_processes = CProcessStatisticsReader::get_all();
|
||||||
|
|
||||||
unsigned last_sum_times_scheduled = 0;
|
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;
|
last_sum_times_scheduled += it.value->current_state.times_scheduled;
|
||||||
|
|
||||||
HashTable<pid_t> live_pids;
|
HashTable<PidAndTid> live_pids;
|
||||||
unsigned sum_times_scheduled = 0;
|
unsigned sum_times_scheduled = 0;
|
||||||
for (auto& it : all_processes) {
|
for (auto& it : all_processes) {
|
||||||
ProcessState state;
|
for (auto& thread : it.value.threads) {
|
||||||
state.pid = it.value.pid;
|
ThreadState state;
|
||||||
state.times_scheduled = it.value.times_scheduled;
|
state.pid = it.value.pid;
|
||||||
state.user = it.value.username;
|
state.user = it.value.username;
|
||||||
state.priority = it.value.priority;
|
state.syscall_count = it.value.syscall_count;
|
||||||
state.syscall_count = it.value.syscall_count;
|
state.inode_faults = it.value.inode_faults;
|
||||||
state.inode_faults = it.value.inode_faults;
|
state.zero_faults = it.value.zero_faults;
|
||||||
state.zero_faults = it.value.zero_faults;
|
state.cow_faults = it.value.cow_faults;
|
||||||
state.cow_faults = it.value.cow_faults;
|
state.name = it.value.name;
|
||||||
state.state = it.value.state;
|
state.amount_virtual = it.value.amount_virtual;
|
||||||
state.name = it.value.name;
|
state.amount_resident = it.value.amount_resident;
|
||||||
state.amount_virtual = it.value.amount_virtual;
|
state.icon_id = it.value.icon_id;
|
||||||
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<Process>());
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
|
|
||||||
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<Thread>());
|
||||||
|
}
|
||||||
|
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();
|
m_pids.clear();
|
||||||
float total_cpu_percent = 0;
|
float total_cpu_percent = 0;
|
||||||
Vector<pid_t, 16> pids_to_remove;
|
Vector<PidAndTid, 16> pids_to_remove;
|
||||||
for (auto& it : m_processes) {
|
for (auto& it : m_threads) {
|
||||||
if (!live_pids.contains(it.key)) {
|
if (!live_pids.contains(it.key)) {
|
||||||
pids_to_remove.append(it.key);
|
pids_to_remove.append(it.key);
|
||||||
continue;
|
continue;
|
||||||
|
@ -264,13 +276,13 @@ void ProcessModel::update()
|
||||||
auto& process = *it.value;
|
auto& process = *it.value;
|
||||||
u32 times_scheduled_diff = process.current_state.times_scheduled - process.previous_state.times_scheduled;
|
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);
|
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;
|
total_cpu_percent += process.current_state.cpu_percent;
|
||||||
m_pids.append(it.key);
|
m_pids.append(it.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto pid : pids_to_remove)
|
for (auto pid : pids_to_remove)
|
||||||
m_processes.remove(pid);
|
m_threads.remove(pid);
|
||||||
|
|
||||||
if (on_new_cpu_data_point)
|
if (on_new_cpu_data_point)
|
||||||
on_new_cpu_data_point(total_cpu_percent);
|
on_new_cpu_data_point(total_cpu_percent);
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/String.h>
|
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/String.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibGUI/GModel.h>
|
#include <LibGUI/GModel.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
class GraphWidget;
|
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 {
|
class ProcessModel final : public GModel {
|
||||||
public:
|
public:
|
||||||
enum Column {
|
enum Column {
|
||||||
|
@ -18,6 +27,7 @@ public:
|
||||||
Priority,
|
Priority,
|
||||||
User,
|
User,
|
||||||
PID,
|
PID,
|
||||||
|
TID,
|
||||||
Virtual,
|
Virtual,
|
||||||
Physical,
|
Physical,
|
||||||
Syscalls,
|
Syscalls,
|
||||||
|
@ -44,7 +54,8 @@ public:
|
||||||
private:
|
private:
|
||||||
ProcessModel();
|
ProcessModel();
|
||||||
|
|
||||||
struct ProcessState {
|
struct ThreadState {
|
||||||
|
int tid;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
unsigned times_scheduled;
|
unsigned times_scheduled;
|
||||||
String name;
|
String name;
|
||||||
|
@ -61,16 +72,23 @@ private:
|
||||||
int icon_id;
|
int icon_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Process {
|
struct Thread {
|
||||||
ProcessState current_state;
|
ThreadState current_state;
|
||||||
ProcessState previous_state;
|
ThreadState previous_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
HashMap<uid_t, String> m_usernames;
|
HashMap<uid_t, String> m_usernames;
|
||||||
HashMap<pid_t, NonnullOwnPtr<Process>> m_processes;
|
HashMap<PidAndTid, NonnullOwnPtr<Thread>> m_threads;
|
||||||
Vector<pid_t> m_pids;
|
Vector<PidAndTid> m_pids;
|
||||||
RefPtr<GraphicsBitmap> m_generic_process_icon;
|
RefPtr<GraphicsBitmap> m_generic_process_icon;
|
||||||
RefPtr<GraphicsBitmap> m_high_priority_icon;
|
RefPtr<GraphicsBitmap> m_high_priority_icon;
|
||||||
RefPtr<GraphicsBitmap> m_low_priority_icon;
|
RefPtr<GraphicsBitmap> m_low_priority_icon;
|
||||||
RefPtr<GraphicsBitmap> m_normal_priority_icon;
|
RefPtr<GraphicsBitmap> m_normal_priority_icon;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
template<>
|
||||||
|
struct Traits<PidAndTid> : public GenericTraits<PidAndTid> {
|
||||||
|
static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -55,8 +55,8 @@ void ProcessStateWidget::refresh()
|
||||||
auto& data = active_process_data.value();
|
auto& data = active_process_data.value();
|
||||||
|
|
||||||
m_pid_label->set_text(String::format("%s(%d)", data.name.characters(), pid));
|
m_pid_label->set_text(String::format("%s(%d)", data.name.characters(), pid));
|
||||||
m_state_label->set_text(data.state);
|
m_state_label->set_text(data.threads.first().state);
|
||||||
m_cpu_label->set_text(String::format("%d", data.times_scheduled));
|
m_cpu_label->set_text(String::format("%d", data.threads.first().times_scheduled));
|
||||||
m_memory_label->set_text(String::format("%d", data.amount_resident));
|
m_memory_label->set_text(String::format("%d", data.amount_resident));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -677,13 +677,11 @@ Optional<KBuffer> procfs$all(InodeIdentifier)
|
||||||
auto build_process = [&](const Process& process) {
|
auto build_process = [&](const Process& process) {
|
||||||
auto process_object = array.add_object();
|
auto process_object = array.add_object();
|
||||||
process_object.add("pid", process.pid());
|
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("pgid", process.tty() ? process.tty()->pgid() : 0);
|
||||||
process_object.add("pgp", process.pgid());
|
process_object.add("pgp", process.pgid());
|
||||||
process_object.add("sid", process.sid());
|
process_object.add("sid", process.sid());
|
||||||
process_object.add("uid", process.uid());
|
process_object.add("uid", process.uid());
|
||||||
process_object.add("gid", process.gid());
|
process_object.add("gid", process.gid());
|
||||||
process_object.add("state", process.main_thread().state_string());
|
|
||||||
process_object.add("ppid", process.ppid());
|
process_object.add("ppid", process.ppid());
|
||||||
process_object.add("nfds", process.number_of_open_file_descriptors());
|
process_object.add("nfds", process.number_of_open_file_descriptors());
|
||||||
process_object.add("name", process.name());
|
process_object.add("name", process.name());
|
||||||
|
@ -691,13 +689,21 @@ Optional<KBuffer> procfs$all(InodeIdentifier)
|
||||||
process_object.add("amount_virtual", (u32)process.amount_virtual());
|
process_object.add("amount_virtual", (u32)process.amount_virtual());
|
||||||
process_object.add("amount_resident", (u32)process.amount_resident());
|
process_object.add("amount_resident", (u32)process.amount_resident());
|
||||||
process_object.add("amount_shared", (u32)process.amount_shared());
|
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("syscall_count", process.syscall_count());
|
||||||
process_object.add("inode_faults", process.inode_faults());
|
process_object.add("inode_faults", process.inode_faults());
|
||||||
process_object.add("zero_faults", process.zero_faults());
|
process_object.add("zero_faults", process.zero_faults());
|
||||||
process_object.add("cow_faults", process.cow_faults());
|
process_object.add("cow_faults", process.cow_faults());
|
||||||
process_object.add("icon_id", process.icon_id());
|
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());
|
build_process(*Scheduler::colonel());
|
||||||
for (auto* process : processes)
|
for (auto* process : processes)
|
||||||
|
|
|
@ -26,13 +26,11 @@ HashMap<pid_t, CProcessStatistics> CProcessStatisticsReader::get_all()
|
||||||
|
|
||||||
// kernel data first
|
// kernel data first
|
||||||
process.pid = process_object.get("pid").to_u32();
|
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.pgid = process_object.get("pgid").to_u32();
|
||||||
process.pgp = process_object.get("pgp").to_u32();
|
process.pgp = process_object.get("pgp").to_u32();
|
||||||
process.sid = process_object.get("sid").to_u32();
|
process.sid = process_object.get("sid").to_u32();
|
||||||
process.uid = process_object.get("uid").to_u32();
|
process.uid = process_object.get("uid").to_u32();
|
||||||
process.gid = process_object.get("gid").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.ppid = process_object.get("ppid").to_u32();
|
||||||
process.nfds = process_object.get("nfds").to_u32();
|
process.nfds = process_object.get("nfds").to_u32();
|
||||||
process.name = process_object.get("name").to_string();
|
process.name = process_object.get("name").to_string();
|
||||||
|
@ -40,14 +38,24 @@ HashMap<pid_t, CProcessStatistics> CProcessStatisticsReader::get_all()
|
||||||
process.amount_virtual = process_object.get("amount_virtual").to_u32();
|
process.amount_virtual = process_object.get("amount_virtual").to_u32();
|
||||||
process.amount_resident = process_object.get("amount_resident").to_u32();
|
process.amount_resident = process_object.get("amount_resident").to_u32();
|
||||||
process.amount_shared = process_object.get("amount_shared").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.syscall_count = process_object.get("syscall_count").to_u32();
|
||||||
process.inode_faults = process_object.get("inode_faults").to_u32();
|
process.inode_faults = process_object.get("inode_faults").to_u32();
|
||||||
process.zero_faults = process_object.get("zero_faults").to_u32();
|
process.zero_faults = process_object.get("zero_faults").to_u32();
|
||||||
process.cow_faults = process_object.get("cow_faults").to_u32();
|
process.cow_faults = process_object.get("cow_faults").to_u32();
|
||||||
process.icon_id = process_object.get("icon_id").to_int();
|
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
|
// and synthetic data last
|
||||||
process.username = username_from_uid(process.uid);
|
process.username = username_from_uid(process.uid);
|
||||||
map.set(process.pid, process);
|
map.set(process.pid, process);
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/String.h>
|
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
struct CThreadStatistics {
|
||||||
|
int tid;
|
||||||
|
unsigned times_scheduled;
|
||||||
|
unsigned ticks;
|
||||||
|
String state;
|
||||||
|
String priority;
|
||||||
|
};
|
||||||
|
|
||||||
struct CProcessStatistics {
|
struct CProcessStatistics {
|
||||||
// Keep this in sync with /proc/all.
|
// Keep this in sync with /proc/all.
|
||||||
// From the kernel side:
|
// From the kernel side:
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
unsigned times_scheduled;
|
|
||||||
unsigned pgid;
|
unsigned pgid;
|
||||||
unsigned pgp;
|
unsigned pgp;
|
||||||
unsigned sid;
|
unsigned sid;
|
||||||
uid_t uid;
|
uid_t uid;
|
||||||
gid_t gid;
|
gid_t gid;
|
||||||
String state;
|
|
||||||
pid_t ppid;
|
pid_t ppid;
|
||||||
unsigned nfds;
|
unsigned nfds;
|
||||||
String name;
|
String name;
|
||||||
|
@ -21,14 +28,14 @@ struct CProcessStatistics {
|
||||||
size_t amount_virtual;
|
size_t amount_virtual;
|
||||||
size_t amount_resident;
|
size_t amount_resident;
|
||||||
size_t amount_shared;
|
size_t amount_shared;
|
||||||
unsigned ticks;
|
|
||||||
String priority;
|
|
||||||
unsigned syscall_count;
|
unsigned syscall_count;
|
||||||
unsigned inode_faults;
|
unsigned inode_faults;
|
||||||
unsigned zero_faults;
|
unsigned zero_faults;
|
||||||
unsigned cow_faults;
|
unsigned cow_faults;
|
||||||
int icon_id;
|
int icon_id;
|
||||||
|
|
||||||
|
Vector<CThreadStatistics> threads;
|
||||||
|
|
||||||
// synthetic
|
// synthetic
|
||||||
String username;
|
String username;
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,10 +44,12 @@ void WSCPUMonitor::get_cpu_usage(unsigned& busy, unsigned& idle)
|
||||||
auto all_processes = CProcessStatisticsReader::get_all();
|
auto all_processes = CProcessStatisticsReader::get_all();
|
||||||
|
|
||||||
for (auto& it : all_processes) {
|
for (auto& it : all_processes) {
|
||||||
if (it.value.pid == 0)
|
for (auto& jt : it.value.threads) {
|
||||||
idle += it.value.times_scheduled;
|
if (it.value.pid == 0)
|
||||||
else
|
idle += jt.times_scheduled;
|
||||||
busy += it.value.times_scheduled;
|
else
|
||||||
|
busy += jt.times_scheduled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,9 @@ int main(int argc, char** argv)
|
||||||
proc.pgp,
|
proc.pgp,
|
||||||
proc.sid,
|
proc.sid,
|
||||||
proc.uid,
|
proc.uid,
|
||||||
proc.state.characters(),
|
proc.threads.first().state.characters(),
|
||||||
proc.ppid,
|
proc.ppid,
|
||||||
proc.times_scheduled,
|
proc.threads.first().times_scheduled,
|
||||||
proc.nfds,
|
proc.nfds,
|
||||||
tty.characters(),
|
tty.characters(),
|
||||||
proc.name.characters());
|
proc.name.characters());
|
||||||
|
|
125
Userland/top.cpp
125
Userland/top.cpp
|
@ -1,9 +1,9 @@
|
||||||
#include <AK/String.h>
|
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <AK/JsonArray.h>
|
#include <AK/JsonArray.h>
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
#include <AK/JsonValue.h>
|
#include <AK/JsonValue.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
|
#include <AK/String.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibCore/CProcessStatisticsReader.h>
|
#include <LibCore/CProcessStatisticsReader.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
@ -11,15 +11,55 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
struct ProcessData {
|
struct ThreadData {
|
||||||
CProcessStatistics stats;
|
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 times_scheduled_since_prev { 0 };
|
||||||
unsigned cpu_percent { 0 };
|
unsigned cpu_percent { 0 };
|
||||||
unsigned cpu_percent_decimal { 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<PidAndTid> : public GenericTraits<PidAndTid> {
|
||||||
|
static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
struct Snapshot {
|
struct Snapshot {
|
||||||
HashMap<unsigned, ProcessData> map;
|
HashMap<PidAndTid, ThreadData> map;
|
||||||
u32 sum_times_scheduled { 0 };
|
u32 sum_times_scheduled { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,10 +71,35 @@ static Snapshot get_snapshot()
|
||||||
|
|
||||||
for (auto& it : all_processes) {
|
for (auto& it : all_processes) {
|
||||||
auto& stats = it.value;
|
auto& stats = it.value;
|
||||||
snapshot.sum_times_scheduled += stats.times_scheduled;
|
for (auto& thread : stats.threads) {
|
||||||
ProcessData process_data;
|
snapshot.sum_times_scheduled += thread.times_scheduled;
|
||||||
process_data.stats = stats;
|
ThreadData thread_data;
|
||||||
snapshot.map.set(stats.pid, move(process_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;
|
return snapshot;
|
||||||
|
@ -42,7 +107,7 @@ static Snapshot get_snapshot()
|
||||||
|
|
||||||
int main(int, char**)
|
int main(int, char**)
|
||||||
{
|
{
|
||||||
Vector<ProcessData*> processes;
|
Vector<ThreadData*> threads;
|
||||||
auto prev = get_snapshot();
|
auto prev = get_snapshot();
|
||||||
usleep(10000);
|
usleep(10000);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -50,8 +115,9 @@ int main(int, char**)
|
||||||
auto sum_diff = current.sum_times_scheduled - prev.sum_times_scheduled;
|
auto sum_diff = current.sum_times_scheduled - prev.sum_times_scheduled;
|
||||||
|
|
||||||
printf("\033[3J\033[H\033[2J");
|
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",
|
"PID",
|
||||||
|
"TID",
|
||||||
"PRI",
|
"PRI",
|
||||||
"USER",
|
"USER",
|
||||||
"STATE",
|
"STATE",
|
||||||
|
@ -60,38 +126,39 @@ int main(int, char**)
|
||||||
"%CPU",
|
"%CPU",
|
||||||
"NAME");
|
"NAME");
|
||||||
for (auto& it : current.map) {
|
for (auto& it : current.map) {
|
||||||
pid_t pid = it.key;
|
auto pid_and_tid = it.key;
|
||||||
if (pid == 0)
|
if (pid_and_tid.pid == 0)
|
||||||
continue;
|
continue;
|
||||||
u32 times_scheduled_now = it.value.stats.times_scheduled;
|
u32 times_scheduled_now = it.value.times_scheduled;
|
||||||
auto jt = prev.map.find(pid);
|
auto jt = prev.map.find(pid_and_tid);
|
||||||
if (jt == prev.map.end())
|
if (jt == prev.map.end())
|
||||||
continue;
|
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;
|
u32 times_scheduled_diff = times_scheduled_now - times_scheduled_before;
|
||||||
it.value.times_scheduled_since_prev = times_scheduled_diff;
|
it.value.times_scheduled_since_prev = times_scheduled_diff;
|
||||||
it.value.cpu_percent = ((times_scheduled_diff * 100) / sum_diff);
|
it.value.cpu_percent = ((times_scheduled_diff * 100) / sum_diff);
|
||||||
it.value.cpu_percent_decimal = (((times_scheduled_diff * 1000) / sum_diff) % 10);
|
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;
|
return p2->times_scheduled_since_prev < p1->times_scheduled_since_prev;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (auto* process : processes) {
|
for (auto* thread : threads) {
|
||||||
printf("%6d %c %-8s %-10s %6zu %6zu %2u.%1u %s\n",
|
printf("%6d %3d %c %-8s %-10s %6zu %6zu %2u.%1u %s\n",
|
||||||
process->stats.pid,
|
thread->pid,
|
||||||
process->stats.priority[0],
|
thread->tid,
|
||||||
process->stats.username.characters(),
|
thread->priority[0],
|
||||||
process->stats.state.characters(),
|
thread->username.characters(),
|
||||||
process->stats.amount_virtual / 1024,
|
thread->state.characters(),
|
||||||
process->stats.amount_resident / 1024,
|
thread->amount_virtual / 1024,
|
||||||
process->cpu_percent,
|
thread->amount_resident / 1024,
|
||||||
process->cpu_percent_decimal,
|
thread->cpu_percent,
|
||||||
process->stats.name.characters());
|
thread->cpu_percent_decimal,
|
||||||
|
thread->name.characters());
|
||||||
}
|
}
|
||||||
processes.clear_with_capacity();
|
threads.clear_with_capacity();
|
||||||
prev = move(current);
|
prev = move(current);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue