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 0d5e0e4cad Kernel+SystemMonitor: Expose amount of per-process dirty private memory
Dirty private memory is all memory in non-inode-backed mappings that's
process-private, meaning it's not shared with any other process.

This patch exposes that number via SystemMonitor, giving us an idea of
how much memory each process is responsible for all on its own.
2019-12-29 12:28:32 +01:00

371 lines
13 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::DirtyPrivate:
return "DirtyP";
case Column::PurgeableVolatile:
return "Purg:V";
case Column::PurgeableNonvolatile:
return "Purg:N";
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";
case Column::IPv4SocketReadBytes:
return "IPv4 In";
case Column::IPv4SocketWriteBytes:
return "IPv4 Out";
case Column::UnixSocketReadBytes:
return "Unix In";
case Column::UnixSocketWriteBytes:
return "Unix Out";
case Column::FileReadBytes:
return "File In";
case Column::FileWriteBytes:
return "File Out";
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::DirtyPrivate:
return { 65, TextAlignment::CenterRight };
case Column::PurgeableVolatile:
return { 65, TextAlignment::CenterRight };
case Column::PurgeableNonvolatile:
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 };
case Column::FileReadBytes:
return { 60, TextAlignment::CenterRight };
case Column::FileWriteBytes:
return { 60, TextAlignment::CenterRight };
case Column::UnixSocketReadBytes:
return { 60, TextAlignment::CenterRight };
case Column::UnixSocketWriteBytes:
return { 60, TextAlignment::CenterRight };
case Column::IPv4SocketReadBytes:
return { 60, TextAlignment::CenterRight };
case Column::IPv4SocketWriteBytes:
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::DirtyPrivate:
return (int)thread.current_state.amount_dirty_private;
case Column::PurgeableVolatile:
return (int)thread.current_state.amount_purgeable_volatile;
case Column::PurgeableNonvolatile:
return (int)thread.current_state.amount_purgeable_nonvolatile;
case Column::CPU:
return thread.current_state.cpu_percent;
case Column::Name:
return thread.current_state.name;
case Column::Syscalls:
return thread.current_state.syscall_count;
case Column::InodeFaults:
return thread.current_state.inode_faults;
case Column::ZeroFaults:
return thread.current_state.zero_faults;
case Column::CowFaults:
return thread.current_state.cow_faults;
case Column::IPv4SocketReadBytes:
return thread.current_state.ipv4_socket_read_bytes;
case Column::IPv4SocketWriteBytes:
return thread.current_state.ipv4_socket_write_bytes;
case Column::UnixSocketReadBytes:
return thread.current_state.unix_socket_read_bytes;
case Column::UnixSocketWriteBytes:
return thread.current_state.unix_socket_write_bytes;
case Column::FileReadBytes:
return thread.current_state.file_read_bytes;
case Column::FileWriteBytes:
return thread.current_state.file_write_bytes;
}
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::DirtyPrivate:
return pretty_byte_size(thread.current_state.amount_dirty_private);
case Column::PurgeableVolatile:
return pretty_byte_size(thread.current_state.amount_purgeable_volatile);
case Column::PurgeableNonvolatile:
return pretty_byte_size(thread.current_state.amount_purgeable_nonvolatile);
case Column::CPU:
return thread.current_state.cpu_percent;
case Column::Name:
return thread.current_state.name;
case Column::Syscalls:
return thread.current_state.syscall_count;
case Column::InodeFaults:
return thread.current_state.inode_faults;
case Column::ZeroFaults:
return thread.current_state.zero_faults;
case Column::CowFaults:
return thread.current_state.cow_faults;
case Column::IPv4SocketReadBytes:
return thread.current_state.ipv4_socket_read_bytes;
case Column::IPv4SocketWriteBytes:
return thread.current_state.ipv4_socket_write_bytes;
case Column::UnixSocketReadBytes:
return thread.current_state.unix_socket_read_bytes;
case Column::UnixSocketWriteBytes:
return thread.current_state.unix_socket_write_bytes;
case Column::FileReadBytes:
return thread.current_state.file_read_bytes;
case Column::FileWriteBytes:
return thread.current_state.file_write_bytes;
}
}
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 = thread.syscall_count;
state.inode_faults = thread.inode_faults;
state.zero_faults = thread.zero_faults;
state.cow_faults = thread.cow_faults;
state.unix_socket_read_bytes = thread.unix_socket_read_bytes;
state.unix_socket_write_bytes = thread.unix_socket_write_bytes;
state.ipv4_socket_read_bytes = thread.ipv4_socket_read_bytes;
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 = it.value.amount_virtual;
state.amount_resident = it.value.amount_resident;
state.amount_dirty_private = it.value.amount_dirty_private;
state.amount_purgeable_volatile = it.value.amount_purgeable_volatile;
state.amount_purgeable_nonvolatile = it.value.amount_purgeable_nonvolatile;
state.icon_id = it.value.icon_id;
state.name = thread.name;
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();
}