1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-16 23:05:08 +00:00
serenity/Applications/SystemMonitor/ProcessModel.cpp
Andreas Kling 712ae73581 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.
2019-11-26 21:37:30 +01:00

291 lines
9.5 KiB
C++

#include "ProcessModel.h"
#include "GraphWidget.h"
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <LibC/SharedBuffer.h>
#include <LibCore/CProcessStatisticsReader.h>
#include <fcntl.h>
#include <stdio.h>
static ProcessModel* s_the;
ProcessModel& ProcessModel::the()
{
ASSERT(s_the);
return *s_the;
}
ProcessModel::ProcessModel()
{
ASSERT(!s_the);
s_the = this;
m_generic_process_icon = GraphicsBitmap::load_from_file("/res/icons/gear16.png");
m_high_priority_icon = GraphicsBitmap::load_from_file("/res/icons/highpriority16.png");
m_low_priority_icon = GraphicsBitmap::load_from_file("/res/icons/lowpriority16.png");
m_normal_priority_icon = GraphicsBitmap::load_from_file("/res/icons/normalpriority16.png");
}
ProcessModel::~ProcessModel()
{
}
int ProcessModel::row_count(const GModelIndex&) const
{
return m_pids.size();
}
int ProcessModel::column_count(const GModelIndex&) const
{
return Column::__Count;
}
String ProcessModel::column_name(int column) const
{
switch (column) {
case Column::Icon:
return "";
case Column::PID:
return "PID";
case Column::TID:
return "TID";
case Column::State:
return "State";
case Column::User:
return "User";
case Column::Priority:
return "Pr";
case Column::Virtual:
return "Virtual";
case Column::Physical:
return "Physical";
case Column::CPU:
return "CPU";
case Column::Name:
return "Name";
case Column::Syscalls:
return "Syscalls";
case Column::InodeFaults:
return "F:Inode";
case Column::ZeroFaults:
return "F:Zero";
case Column::CowFaults:
return "F:CoW";
default:
ASSERT_NOT_REACHED();
}
}
GModel::ColumnMetadata ProcessModel::column_metadata(int column) const
{
switch (column) {
case Column::Icon:
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:
return { 16, TextAlignment::CenterLeft };
case Column::User:
return { 50, TextAlignment::CenterLeft };
case Column::Virtual:
return { 65, TextAlignment::CenterRight };
case Column::Physical:
return { 65, TextAlignment::CenterRight };
case Column::CPU:
return { 32, TextAlignment::CenterRight };
case Column::Name:
return { 140, TextAlignment::CenterLeft };
case Column::Syscalls:
return { 60, TextAlignment::CenterRight };
case Column::InodeFaults:
return { 60, TextAlignment::CenterRight };
case Column::ZeroFaults:
return { 60, TextAlignment::CenterRight };
case Column::CowFaults:
return { 60, TextAlignment::CenterRight };
default:
ASSERT_NOT_REACHED();
}
}
static String pretty_byte_size(size_t size)
{
return String::format("%uK", size / 1024);
}
GVariant ProcessModel::data(const GModelIndex& index, Role role) const
{
ASSERT(is_valid(index));
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 thread.current_state.pid;
case Column::TID:
return thread.current_state.tid;
case Column::State:
return thread.current_state.state;
case Column::User:
return thread.current_state.user;
case Column::Priority:
if (thread.current_state.priority == "Idle")
return 0;
if (thread.current_state.priority == "Low")
return 1;
if (thread.current_state.priority == "Normal")
return 2;
if (thread.current_state.priority == "High")
return 3;
ASSERT_NOT_REACHED();
return 3;
case Column::Virtual:
return (int)thread.current_state.amount_virtual;
case Column::Physical:
return (int)thread.current_state.amount_resident;
case Column::CPU:
return thread.current_state.cpu_percent;
case Column::Name:
return thread.current_state.name;
// FIXME: GVariant with unsigned?
case Column::Syscalls:
return (int)thread.current_state.syscall_count;
case Column::InodeFaults:
return (int)thread.current_state.inode_faults;
case Column::ZeroFaults:
return (int)thread.current_state.zero_faults;
case Column::CowFaults:
return (int)thread.current_state.cow_faults;
}
ASSERT_NOT_REACHED();
return {};
}
if (role == Role::Display) {
switch (index.column()) {
case Column::Icon:
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)
return *icon_bitmap;
}
}
return *m_generic_process_icon;
case Column::PID:
return thread.current_state.pid;
case Column::TID:
return thread.current_state.tid;
case Column::State:
return thread.current_state.state;
case Column::User:
return thread.current_state.user;
case Column::Priority:
if (thread.current_state.priority == "Idle")
return String::empty();
if (thread.current_state.priority == "High")
return *m_high_priority_icon;
if (thread.current_state.priority == "Low")
return *m_low_priority_icon;
if (thread.current_state.priority == "Normal")
return *m_normal_priority_icon;
return thread.current_state.priority;
case Column::Virtual:
return pretty_byte_size(thread.current_state.amount_virtual);
case Column::Physical:
return pretty_byte_size(thread.current_state.amount_resident);
case Column::CPU:
return thread.current_state.cpu_percent;
case Column::Name:
return thread.current_state.name;
// FIXME: It's weird that GVariant doesn't support unsigned ints. Should it?
case Column::Syscalls:
return (int)thread.current_state.syscall_count;
case Column::InodeFaults:
return (int)thread.current_state.inode_faults;
case Column::ZeroFaults:
return (int)thread.current_state.zero_faults;
case Column::CowFaults:
return (int)thread.current_state.cow_faults;
}
}
return {};
}
void ProcessModel::update()
{
auto all_processes = CProcessStatisticsReader::get_all();
unsigned last_sum_times_scheduled = 0;
for (auto& it : m_threads)
last_sum_times_scheduled += it.value->current_state.times_scheduled;
HashTable<PidAndTid> live_pids;
unsigned sum_times_scheduled = 0;
for (auto& it : all_processes) {
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;
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<PidAndTid, 16> pids_to_remove;
for (auto& it : m_threads) {
if (!live_pids.contains(it.key)) {
pids_to_remove.append(it.key);
continue;
}
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.pid != 0) {
total_cpu_percent += process.current_state.cpu_percent;
m_pids.append(it.key);
}
}
for (auto pid : pids_to_remove)
m_threads.remove(pid);
if (on_new_cpu_data_point)
on_new_cpu_data_point(total_cpu_percent);
did_update();
}