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:
Andreas Kling 2019-11-26 21:25:45 +01:00
parent 86a9a52355
commit 712ae73581
9 changed files with 243 additions and 123 deletions

View file

@ -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<pid_t> live_pids;
HashTable<PidAndTid> 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<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;
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<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();
float total_cpu_percent = 0;
Vector<pid_t, 16> pids_to_remove;
for (auto& it : m_processes) {
Vector<PidAndTid, 16> 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);

View file

@ -1,13 +1,22 @@
#pragma once
#include <AK/String.h>
#include <AK/HashMap.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibGUI/GModel.h>
#include <unistd.h>
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<uid_t, String> m_usernames;
HashMap<pid_t, NonnullOwnPtr<Process>> m_processes;
Vector<pid_t> m_pids;
HashMap<PidAndTid, NonnullOwnPtr<Thread>> m_threads;
Vector<PidAndTid> m_pids;
RefPtr<GraphicsBitmap> m_generic_process_icon;
RefPtr<GraphicsBitmap> m_high_priority_icon;
RefPtr<GraphicsBitmap> m_low_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); }
};
}

View file

@ -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));
}

View file

@ -677,13 +677,11 @@ Optional<KBuffer> 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<KBuffer> 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)

View file

@ -26,13 +26,11 @@ HashMap<pid_t, CProcessStatistics> 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<pid_t, CProcessStatistics> 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);

View file

@ -1,19 +1,26 @@
#pragma once
#include <AK/String.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 {
// 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<CThreadStatistics> threads;
// synthetic
String username;
};

View file

@ -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;
}
}
}

View file

@ -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());

View file

@ -1,9 +1,9 @@
#include <AK/String.h>
#include <AK/HashMap.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/QuickSort.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibCore/CProcessStatisticsReader.h>
#include <fcntl.h>
@ -11,15 +11,55 @@
#include <stdlib.h>
#include <unistd.h>
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<PidAndTid> : public GenericTraits<PidAndTid> {
static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); }
};
}
struct Snapshot {
HashMap<unsigned, ProcessData> map;
HashMap<PidAndTid, ThreadData> 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<ProcessData*> processes;
Vector<ThreadData*> 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);
}