mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 07:37:35 +00:00
ProcessManager: Rename it to SystemMonitor
This is a more appropriate name now that it does a lot more than just manage processes ^)
This commit is contained in:
parent
c7040cee62
commit
cbdda91065
23 changed files with 9 additions and 9 deletions
52
Applications/SystemMonitor/GraphWidget.cpp
Normal file
52
Applications/SystemMonitor/GraphWidget.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include "GraphWidget.h"
|
||||
#include <LibGUI/GPainter.h>
|
||||
|
||||
GraphWidget::GraphWidget(GWidget* parent)
|
||||
: GFrame(parent)
|
||||
{
|
||||
set_frame_thickness(2);
|
||||
set_frame_shape(FrameShape::Container);
|
||||
set_frame_shadow(FrameShadow::Sunken);
|
||||
}
|
||||
|
||||
GraphWidget::~GraphWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void GraphWidget::add_value(int value)
|
||||
{
|
||||
m_values.enqueue(value);
|
||||
update();
|
||||
}
|
||||
|
||||
void GraphWidget::paint_event(GPaintEvent& event)
|
||||
{
|
||||
GFrame::paint_event(event);
|
||||
GPainter painter(*this);
|
||||
painter.add_clip_rect(event.rect());
|
||||
painter.add_clip_rect(frame_inner_rect());
|
||||
painter.fill_rect(event.rect(), Color::Black);
|
||||
|
||||
auto inner_rect = frame_inner_rect();
|
||||
float scale = (float)inner_rect.height() / (float)m_max;
|
||||
|
||||
Point prev_point;
|
||||
for (int i = 0; i < m_values.size(); ++i) {
|
||||
int x = inner_rect.right() - (i * 2) + 1;
|
||||
if (x < 0)
|
||||
break;
|
||||
float scaled_value = (float)m_values.at(m_values.size() - i - 1) * scale;
|
||||
Point point = { x, inner_rect.bottom() - (int)scaled_value };
|
||||
if (i != 0)
|
||||
painter.draw_line(prev_point, point, m_graph_color);
|
||||
prev_point = point;
|
||||
}
|
||||
|
||||
if (!m_values.is_empty() && text_formatter) {
|
||||
Rect text_rect = inner_rect.shrunken(8, 8);
|
||||
text_rect.set_height(font().glyph_height());
|
||||
auto text = text_formatter(m_values.last(), m_max);
|
||||
painter.draw_text(text_rect.translated(1, 1), text.characters(), TextAlignment::CenterRight, Color::Black);
|
||||
painter.draw_text(text_rect, text.characters(), TextAlignment::CenterRight, m_text_color);
|
||||
}
|
||||
}
|
24
Applications/SystemMonitor/GraphWidget.h
Normal file
24
Applications/SystemMonitor/GraphWidget.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include <AK/CircularQueue.h>
|
||||
#include <LibGUI/GFrame.h>
|
||||
|
||||
class GraphWidget final : public GFrame {
|
||||
public:
|
||||
explicit GraphWidget(GWidget* parent);
|
||||
virtual ~GraphWidget() override;
|
||||
|
||||
void set_max(int max) { m_max = max; }
|
||||
void add_value(int);
|
||||
|
||||
void set_graph_color(Color color) { m_graph_color = color; }
|
||||
void set_text_color(Color color) { m_text_color = color; }
|
||||
|
||||
Function<String(int value, int max)> text_formatter;
|
||||
|
||||
private:
|
||||
virtual void paint_event(GPaintEvent&) override;
|
||||
|
||||
int m_max { 100 };
|
||||
CircularQueue<int, 4000> m_values;
|
||||
Color m_graph_color;
|
||||
Color m_text_color;
|
||||
};
|
16
Applications/SystemMonitor/Makefile
Normal file
16
Applications/SystemMonitor/Makefile
Normal file
|
@ -0,0 +1,16 @@
|
|||
include ../../Makefile.common
|
||||
|
||||
OBJS = \
|
||||
ProcessModel.o \
|
||||
ProcessTableView.o \
|
||||
MemoryStatsWidget.o \
|
||||
GraphWidget.o \
|
||||
ProcessStacksWidget.o \
|
||||
ProcessMemoryMapWidget.o \
|
||||
ProcessFileDescriptorMapWidget.o \
|
||||
NetworkStatisticsWidget.o \
|
||||
main.o
|
||||
|
||||
APP = SystemMonitor
|
||||
|
||||
include ../Makefile.common
|
94
Applications/SystemMonitor/MemoryStatsWidget.cpp
Normal file
94
Applications/SystemMonitor/MemoryStatsWidget.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#include "MemoryStatsWidget.h"
|
||||
#include "GraphWidget.h"
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibGUI/GBoxLayout.h>
|
||||
#include <LibGUI/GLabel.h>
|
||||
#include <LibGUI/GPainter.h>
|
||||
#include <LibDraw/StylePainter.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
MemoryStatsWidget::MemoryStatsWidget(GraphWidget& graph, GWidget* parent)
|
||||
: GWidget(parent)
|
||||
, m_graph(graph)
|
||||
, m_proc_memstat("/proc/memstat")
|
||||
{
|
||||
if (!m_proc_memstat.open(CIODevice::OpenMode::ReadOnly))
|
||||
ASSERT_NOT_REACHED();
|
||||
set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
|
||||
set_preferred_size(0, 72);
|
||||
|
||||
set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
layout()->set_margins({ 0, 8, 0, 0 });
|
||||
layout()->set_spacing(3);
|
||||
|
||||
auto build_widgets_for_label = [this](const String& description) -> GLabel* {
|
||||
auto* container = new GWidget(this);
|
||||
container->set_layout(make<GBoxLayout>(Orientation::Horizontal));
|
||||
container->set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed);
|
||||
container->set_preferred_size(275, 12);
|
||||
auto* description_label = new GLabel(description, container);
|
||||
description_label->set_font(Font::default_bold_font());
|
||||
description_label->set_text_alignment(TextAlignment::CenterLeft);
|
||||
auto* label = new GLabel(container);
|
||||
label->set_text_alignment(TextAlignment::CenterRight);
|
||||
return label;
|
||||
};
|
||||
|
||||
m_user_physical_pages_label = build_widgets_for_label("Userspace physical:");
|
||||
m_supervisor_physical_pages_label = build_widgets_for_label("Supervisor physical:");
|
||||
m_kmalloc_label = build_widgets_for_label("Kernel heap:");
|
||||
m_kmalloc_count_label = build_widgets_for_label("Calls kmalloc/kfree:");
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
MemoryStatsWidget::~MemoryStatsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
static inline size_t page_count_to_kb(size_t kb)
|
||||
{
|
||||
return (kb * 4096) / 1024;
|
||||
}
|
||||
|
||||
static inline size_t bytes_to_kb(size_t bytes)
|
||||
{
|
||||
return bytes / 1024;
|
||||
}
|
||||
|
||||
void MemoryStatsWidget::refresh()
|
||||
{
|
||||
m_proc_memstat.seek(0);
|
||||
|
||||
auto file_contents = m_proc_memstat.read_all();
|
||||
auto json = JsonValue::from_string(file_contents).as_object();
|
||||
|
||||
unsigned kmalloc_eternal_allocated = json.get("kmalloc_eternal_allocated").to_u32();
|
||||
(void)kmalloc_eternal_allocated;
|
||||
unsigned kmalloc_allocated = json.get("kmalloc_allocated").to_u32();
|
||||
unsigned kmalloc_available = json.get("kmalloc_available").to_u32();
|
||||
unsigned user_physical_allocated = json.get("user_physical_allocated").to_u32();
|
||||
unsigned user_physical_available = json.get("user_physical_available").to_u32();
|
||||
unsigned super_physical_alloc = json.get("super_physical_allocated").to_u32();
|
||||
unsigned super_physical_free = json.get("super_physical_available").to_u32();
|
||||
unsigned kmalloc_call_count = json.get("kmalloc_call_count").to_u32();
|
||||
unsigned kfree_call_count = json.get("kfree_call_count").to_u32();
|
||||
|
||||
size_t kmalloc_sum_available = kmalloc_allocated + kmalloc_available;
|
||||
size_t user_pages_available = user_physical_allocated + user_physical_available;
|
||||
size_t supervisor_pages_available = super_physical_alloc + super_physical_free;
|
||||
|
||||
m_kmalloc_label->set_text(String::format("%uK/%uK", bytes_to_kb(kmalloc_allocated), bytes_to_kb(kmalloc_sum_available)));
|
||||
m_user_physical_pages_label->set_text(String::format("%uK/%uK", page_count_to_kb(user_physical_allocated), page_count_to_kb(user_pages_available)));
|
||||
m_supervisor_physical_pages_label->set_text(String::format("%uK/%uK", page_count_to_kb(super_physical_alloc), page_count_to_kb(supervisor_pages_available)));
|
||||
m_kmalloc_count_label->set_text(String::format("%u/%u (+%u)", kmalloc_call_count, kfree_call_count, kmalloc_call_count - kfree_call_count));
|
||||
|
||||
m_graph.set_max(page_count_to_kb(user_pages_available));
|
||||
m_graph.add_value(page_count_to_kb(user_physical_allocated));
|
||||
}
|
||||
|
||||
void MemoryStatsWidget::timer_event(CTimerEvent&)
|
||||
{
|
||||
refresh();
|
||||
}
|
25
Applications/SystemMonitor/MemoryStatsWidget.h
Normal file
25
Applications/SystemMonitor/MemoryStatsWidget.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibCore/CFile.h>
|
||||
#include <LibGUI/GWidget.h>
|
||||
|
||||
class GLabel;
|
||||
class GraphWidget;
|
||||
|
||||
class MemoryStatsWidget final : public GWidget {
|
||||
public:
|
||||
MemoryStatsWidget(GraphWidget& graph, GWidget* parent);
|
||||
virtual ~MemoryStatsWidget() override;
|
||||
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
virtual void timer_event(CTimerEvent&) override;
|
||||
|
||||
GraphWidget& m_graph;
|
||||
GLabel* m_user_physical_pages_label { nullptr };
|
||||
GLabel* m_supervisor_physical_pages_label { nullptr };
|
||||
GLabel* m_kmalloc_label { nullptr };
|
||||
GLabel* m_kmalloc_count_label { nullptr };
|
||||
CFile m_proc_memstat;
|
||||
};
|
75
Applications/SystemMonitor/NetworkStatisticsWidget.cpp
Normal file
75
Applications/SystemMonitor/NetworkStatisticsWidget.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
#include "NetworkStatisticsWidget.h"
|
||||
#include <LibGUI/GBoxLayout.h>
|
||||
#include <LibGUI/GGroupBox.h>
|
||||
#include <LibGUI/GJsonArrayModel.h>
|
||||
#include <LibGUI/GTableView.h>
|
||||
|
||||
NetworkStatisticsWidget::NetworkStatisticsWidget(GWidget* parent)
|
||||
: GWidget(parent)
|
||||
{
|
||||
set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
set_fill_with_background_color(true);
|
||||
set_background_color(Color::WarmGray);
|
||||
|
||||
auto* adapters_group_box = new GGroupBox("Adapters", this);
|
||||
adapters_group_box->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
adapters_group_box->layout()->set_margins({ 6, 16, 6, 6 });
|
||||
adapters_group_box->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
|
||||
adapters_group_box->set_preferred_size(0, 120);
|
||||
|
||||
m_adapter_table_view = new GTableView(adapters_group_box);
|
||||
m_adapter_table_view->set_size_columns_to_fit_content(true);
|
||||
|
||||
Vector<GJsonArrayModel::FieldSpec> net_adapters_fields;
|
||||
net_adapters_fields.empend("name", "Name", TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("class_name", "Class", TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("mac_address", "MAC", TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("ipv4_address", "IPv4", TextAlignment::CenterLeft);
|
||||
net_adapters_fields.empend("packets_in", "Pkt In", TextAlignment::CenterRight);
|
||||
net_adapters_fields.empend("packets_out", "Pkt Out", TextAlignment::CenterRight);
|
||||
net_adapters_fields.empend("bytes_in", "Bytes In", TextAlignment::CenterRight);
|
||||
net_adapters_fields.empend("bytes_out", "Bytes Out", TextAlignment::CenterRight);
|
||||
m_adapter_table_view->set_model(GJsonArrayModel::create("/proc/net/adapters", move(net_adapters_fields)));
|
||||
|
||||
auto* sockets_group_box = new GGroupBox("Sockets", this);
|
||||
sockets_group_box->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
sockets_group_box->layout()->set_margins({ 6, 16, 6, 6 });
|
||||
sockets_group_box->set_size_policy(SizePolicy::Fill, SizePolicy::Fill);
|
||||
sockets_group_box->set_preferred_size(0, 0);
|
||||
|
||||
m_socket_table_view = new GTableView(sockets_group_box);
|
||||
m_socket_table_view->set_size_columns_to_fit_content(true);
|
||||
|
||||
Vector<GJsonArrayModel::FieldSpec> net_tcp_fields;
|
||||
net_tcp_fields.empend("peer_address", "Peer", TextAlignment::CenterLeft);
|
||||
net_tcp_fields.empend("peer_port", "Port", TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("local_address", "Local", TextAlignment::CenterLeft);
|
||||
net_tcp_fields.empend("local_port", "Port", TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("state", "State", TextAlignment::CenterLeft);
|
||||
net_tcp_fields.empend("ack_number", "Ack#", TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("sequence_number", "Seq#", TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("packets_in", "Pkt In", TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("packets_out", "Pkt Out", TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("bytes_in", "Bytes In", TextAlignment::CenterRight);
|
||||
net_tcp_fields.empend("bytes_out", "Bytes Out", TextAlignment::CenterRight);
|
||||
m_socket_table_view->set_model(GJsonArrayModel::create("/proc/net/tcp", move(net_tcp_fields)));
|
||||
|
||||
m_update_timer = new CTimer(
|
||||
1000, [this] {
|
||||
update_models();
|
||||
},
|
||||
this);
|
||||
|
||||
update_models();
|
||||
}
|
||||
|
||||
NetworkStatisticsWidget::~NetworkStatisticsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkStatisticsWidget::update_models()
|
||||
{
|
||||
m_adapter_table_view->model()->update();
|
||||
m_socket_table_view->model()->update();
|
||||
}
|
20
Applications/SystemMonitor/NetworkStatisticsWidget.h
Normal file
20
Applications/SystemMonitor/NetworkStatisticsWidget.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibCore/CTimer.h>
|
||||
#include <LibGUI/GWidget.h>
|
||||
|
||||
class GTableView;
|
||||
|
||||
class NetworkStatisticsWidget final : public GWidget {
|
||||
C_OBJECT(NetworkStatisticsWidget)
|
||||
public:
|
||||
explicit NetworkStatisticsWidget(GWidget* parent = nullptr);
|
||||
virtual ~NetworkStatisticsWidget() override;
|
||||
|
||||
private:
|
||||
void update_models();
|
||||
|
||||
GTableView* m_adapter_table_view { nullptr };
|
||||
GTableView* m_socket_table_view { nullptr };
|
||||
CTimer* m_update_timer { nullptr };
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
#include "ProcessFileDescriptorMapWidget.h"
|
||||
#include <LibGUI/GBoxLayout.h>
|
||||
#include <LibGUI/GJsonArrayModel.h>
|
||||
#include <LibGUI/GTableView.h>
|
||||
|
||||
ProcessFileDescriptorMapWidget::ProcessFileDescriptorMapWidget(GWidget* parent)
|
||||
: GWidget(parent)
|
||||
{
|
||||
set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
m_table_view = new GTableView(this);
|
||||
m_table_view->set_size_columns_to_fit_content(true);
|
||||
|
||||
Vector<GJsonArrayModel::FieldSpec> pid_fds_fields;
|
||||
pid_fds_fields.empend("fd", "FD", TextAlignment::CenterRight);
|
||||
pid_fds_fields.empend("class", "Class", TextAlignment::CenterLeft);
|
||||
pid_fds_fields.empend("offset", "Offset", TextAlignment::CenterRight);
|
||||
pid_fds_fields.empend("absolute_path", "Path", TextAlignment::CenterLeft);
|
||||
pid_fds_fields.empend("Access", TextAlignment::CenterLeft, [](auto& object) {
|
||||
return object.get("seekable").to_bool() ? "Seekable" : "Sequential";
|
||||
});
|
||||
|
||||
m_table_view->set_model(GJsonArrayModel::create({}, move(pid_fds_fields)));
|
||||
}
|
||||
|
||||
ProcessFileDescriptorMapWidget::~ProcessFileDescriptorMapWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessFileDescriptorMapWidget::set_pid(pid_t pid)
|
||||
{
|
||||
if (m_pid == pid)
|
||||
return;
|
||||
m_pid = pid;
|
||||
static_cast<GJsonArrayModel*>(m_table_view->model())->set_json_path(String::format("/proc/%d/fds", m_pid));
|
||||
}
|
18
Applications/SystemMonitor/ProcessFileDescriptorMapWidget.h
Normal file
18
Applications/SystemMonitor/ProcessFileDescriptorMapWidget.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibGUI/GWidget.h>
|
||||
|
||||
class GTableView;
|
||||
|
||||
class ProcessFileDescriptorMapWidget final : public GWidget {
|
||||
C_OBJECT(ProcessFileDescriptorMapWidget);
|
||||
public:
|
||||
explicit ProcessFileDescriptorMapWidget(GWidget* parent);
|
||||
virtual ~ProcessFileDescriptorMapWidget() override;
|
||||
|
||||
void set_pid(pid_t);
|
||||
|
||||
private:
|
||||
GTableView* m_table_view { nullptr };
|
||||
pid_t m_pid { -1 };
|
||||
};
|
41
Applications/SystemMonitor/ProcessMemoryMapWidget.cpp
Normal file
41
Applications/SystemMonitor/ProcessMemoryMapWidget.cpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#include "ProcessMemoryMapWidget.h"
|
||||
#include <LibGUI/GBoxLayout.h>
|
||||
#include <LibGUI/GJsonArrayModel.h>
|
||||
#include <LibGUI/GTableView.h>
|
||||
|
||||
ProcessMemoryMapWidget::ProcessMemoryMapWidget(GWidget* parent)
|
||||
: GWidget(parent)
|
||||
{
|
||||
set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
m_table_view = new GTableView(this);
|
||||
m_table_view->set_size_columns_to_fit_content(true);
|
||||
Vector<GJsonArrayModel::FieldSpec> pid_vm_fields;
|
||||
pid_vm_fields.empend("Address", TextAlignment::CenterLeft, [](auto& object) {
|
||||
return String::format("%#x", object.get("address").to_u32());
|
||||
});
|
||||
pid_vm_fields.empend("size", "Size", TextAlignment::CenterRight);
|
||||
pid_vm_fields.empend("amount_resident", "Resident", TextAlignment::CenterRight);
|
||||
pid_vm_fields.empend("Access", TextAlignment::CenterLeft, [](auto& object) {
|
||||
StringBuilder builder;
|
||||
if (object.get("readable").to_bool())
|
||||
builder.append('R');
|
||||
if (object.get("writable").to_bool())
|
||||
builder.append('W');
|
||||
return builder.to_string();
|
||||
});
|
||||
pid_vm_fields.empend("name", "Name", TextAlignment::CenterLeft);
|
||||
m_table_view->set_model(GJsonArrayModel::create({}, move(pid_vm_fields)));
|
||||
}
|
||||
|
||||
ProcessMemoryMapWidget::~ProcessMemoryMapWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessMemoryMapWidget::set_pid(pid_t pid)
|
||||
{
|
||||
if (m_pid == pid)
|
||||
return;
|
||||
m_pid = pid;
|
||||
static_cast<GJsonArrayModel*>(m_table_view->model())->set_json_path(String::format("/proc/%d/vm", pid));
|
||||
}
|
18
Applications/SystemMonitor/ProcessMemoryMapWidget.h
Normal file
18
Applications/SystemMonitor/ProcessMemoryMapWidget.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibGUI/GWidget.h>
|
||||
|
||||
class GTableView;
|
||||
|
||||
class ProcessMemoryMapWidget final : public GWidget {
|
||||
C_OBJECT(ProcessMemoryMapWidget);
|
||||
public:
|
||||
explicit ProcessMemoryMapWidget(GWidget* parent);
|
||||
virtual ~ProcessMemoryMapWidget() override;
|
||||
|
||||
void set_pid(pid_t);
|
||||
|
||||
private:
|
||||
GTableView* m_table_view { nullptr };
|
||||
pid_t m_pid { -1 };
|
||||
};
|
242
Applications/SystemMonitor/ProcessModel.cpp
Normal file
242
Applications/SystemMonitor/ProcessModel.cpp
Normal file
|
@ -0,0 +1,242 @@
|
|||
#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>
|
||||
|
||||
ProcessModel::ProcessModel(GraphWidget& graph)
|
||||
: m_graph(graph)
|
||||
{
|
||||
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::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";
|
||||
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::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 };
|
||||
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_processes.find(m_pids[index.row()]);
|
||||
auto& process = *(*it).value;
|
||||
|
||||
if (role == Role::Sort) {
|
||||
switch (index.column()) {
|
||||
case Column::Icon:
|
||||
return 0;
|
||||
case Column::PID:
|
||||
return process.current_state.pid;
|
||||
case Column::State:
|
||||
return process.current_state.state;
|
||||
case Column::User:
|
||||
return process.current_state.user;
|
||||
case Column::Priority:
|
||||
if (process.current_state.priority == "Idle")
|
||||
return 0;
|
||||
if (process.current_state.priority == "Low")
|
||||
return 1;
|
||||
if (process.current_state.priority == "Normal")
|
||||
return 2;
|
||||
if (process.current_state.priority == "High")
|
||||
return 3;
|
||||
ASSERT_NOT_REACHED();
|
||||
return 3;
|
||||
case Column::Virtual:
|
||||
return (int)process.current_state.amount_virtual;
|
||||
case Column::Physical:
|
||||
return (int)process.current_state.amount_resident;
|
||||
case Column::CPU:
|
||||
return process.current_state.cpu_percent;
|
||||
case Column::Name:
|
||||
return process.current_state.name;
|
||||
// FIXME: GVariant with unsigned?
|
||||
case Column::Syscalls:
|
||||
return (int)process.current_state.syscall_count;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
return {};
|
||||
}
|
||||
|
||||
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 (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 process.current_state.pid;
|
||||
case Column::State:
|
||||
return process.current_state.state;
|
||||
case Column::User:
|
||||
return process.current_state.user;
|
||||
case Column::Priority:
|
||||
if (process.current_state.priority == "Idle")
|
||||
return String::empty();
|
||||
if (process.current_state.priority == "High")
|
||||
return *m_high_priority_icon;
|
||||
if (process.current_state.priority == "Low")
|
||||
return *m_low_priority_icon;
|
||||
if (process.current_state.priority == "Normal")
|
||||
return *m_normal_priority_icon;
|
||||
return process.current_state.priority;
|
||||
case Column::Virtual:
|
||||
return pretty_byte_size(process.current_state.amount_virtual);
|
||||
case Column::Physical:
|
||||
return pretty_byte_size(process.current_state.amount_resident);
|
||||
case Column::CPU:
|
||||
return process.current_state.cpu_percent;
|
||||
case Column::Name:
|
||||
return process.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 {};
|
||||
}
|
||||
|
||||
void ProcessModel::update()
|
||||
{
|
||||
auto all_processes = CProcessStatisticsReader::get_all();
|
||||
|
||||
unsigned last_sum_times_scheduled = 0;
|
||||
for (auto& it : m_processes)
|
||||
last_sum_times_scheduled += it.value->current_state.times_scheduled;
|
||||
|
||||
HashTable<pid_t> 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.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;
|
||||
|
||||
live_pids.set(it.value.pid);
|
||||
}
|
||||
|
||||
m_pids.clear();
|
||||
float total_cpu_percent = 0;
|
||||
Vector<pid_t, 16> pids_to_remove;
|
||||
for (auto& it : m_processes) {
|
||||
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 != 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_graph.add_value(total_cpu_percent);
|
||||
|
||||
did_update();
|
||||
}
|
68
Applications/SystemMonitor/ProcessModel.h
Normal file
68
Applications/SystemMonitor/ProcessModel.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AKString.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/GModel.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class GraphWidget;
|
||||
|
||||
class ProcessModel final : public GModel {
|
||||
public:
|
||||
enum Column {
|
||||
Icon = 0,
|
||||
Name,
|
||||
CPU,
|
||||
State,
|
||||
Priority,
|
||||
User,
|
||||
PID,
|
||||
Virtual,
|
||||
Physical,
|
||||
Syscalls,
|
||||
__Count
|
||||
};
|
||||
|
||||
static NonnullRefPtr<ProcessModel> create(GraphWidget& graph) { return adopt(*new ProcessModel(graph)); }
|
||||
virtual ~ProcessModel() override;
|
||||
|
||||
virtual int row_count(const GModelIndex&) const override;
|
||||
virtual int column_count(const GModelIndex&) const override;
|
||||
virtual String column_name(int column) const override;
|
||||
virtual ColumnMetadata column_metadata(int column) const override;
|
||||
virtual GVariant data(const GModelIndex&, Role = Role::Display) const override;
|
||||
virtual void update() override;
|
||||
|
||||
private:
|
||||
explicit ProcessModel(GraphWidget&);
|
||||
|
||||
GraphWidget& m_graph;
|
||||
|
||||
struct ProcessState {
|
||||
pid_t pid;
|
||||
unsigned times_scheduled;
|
||||
String name;
|
||||
String state;
|
||||
String user;
|
||||
String priority;
|
||||
size_t amount_virtual;
|
||||
size_t amount_resident;
|
||||
unsigned syscall_count;
|
||||
float cpu_percent;
|
||||
int icon_id;
|
||||
};
|
||||
|
||||
struct Process {
|
||||
ProcessState current_state;
|
||||
ProcessState previous_state;
|
||||
};
|
||||
|
||||
HashMap<uid_t, String> m_usernames;
|
||||
HashMap<pid_t, NonnullOwnPtr<Process>> m_processes;
|
||||
Vector<pid_t> 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;
|
||||
};
|
38
Applications/SystemMonitor/ProcessStacksWidget.cpp
Normal file
38
Applications/SystemMonitor/ProcessStacksWidget.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include "ProcessStacksWidget.h"
|
||||
#include <LibCore/CFile.h>
|
||||
#include <LibCore/CTimer.h>
|
||||
#include <LibGUI/GBoxLayout.h>
|
||||
|
||||
ProcessStacksWidget::ProcessStacksWidget(GWidget* parent)
|
||||
: GWidget(parent)
|
||||
{
|
||||
set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
layout()->set_margins({ 4, 4, 4, 4 });
|
||||
m_stacks_editor = new GTextEditor(GTextEditor::Type::MultiLine, this);
|
||||
m_stacks_editor->set_readonly(true);
|
||||
|
||||
m_timer = new CTimer(1000, [this] { refresh(); }, this);
|
||||
}
|
||||
|
||||
ProcessStacksWidget::~ProcessStacksWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessStacksWidget::set_pid(pid_t pid)
|
||||
{
|
||||
if (m_pid == pid)
|
||||
return;
|
||||
m_pid = pid;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void ProcessStacksWidget::refresh()
|
||||
{
|
||||
CFile file(String::format("/proc/%d/stack", m_pid));
|
||||
if (!file.open(CIODevice::ReadOnly)) {
|
||||
m_stacks_editor->set_text(String::format("Unable to open %s", file.filename().characters()));
|
||||
return;
|
||||
}
|
||||
|
||||
m_stacks_editor->set_text(file.read_all());
|
||||
}
|
21
Applications/SystemMonitor/ProcessStacksWidget.h
Normal file
21
Applications/SystemMonitor/ProcessStacksWidget.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibGUI/GTextEditor.h>
|
||||
#include <LibGUI/GWidget.h>
|
||||
|
||||
class CTimer;
|
||||
|
||||
class ProcessStacksWidget final : public GWidget {
|
||||
C_OBJECT(ProcessStacksWidget)
|
||||
public:
|
||||
explicit ProcessStacksWidget(GWidget* parent);
|
||||
virtual ~ProcessStacksWidget() override;
|
||||
|
||||
void set_pid(pid_t);
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
pid_t m_pid { -1 };
|
||||
GTextEditor* m_stacks_editor { nullptr };
|
||||
CTimer* m_timer { nullptr };
|
||||
};
|
42
Applications/SystemMonitor/ProcessTableView.cpp
Normal file
42
Applications/SystemMonitor/ProcessTableView.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "ProcessTableView.h"
|
||||
#include "ProcessModel.h"
|
||||
#include <LibGUI/GSortingProxyModel.h>
|
||||
#include <stdio.h>
|
||||
|
||||
ProcessTableView::ProcessTableView(GraphWidget& graph, GWidget* parent)
|
||||
: GTableView(parent)
|
||||
{
|
||||
set_size_columns_to_fit_content(true);
|
||||
set_model(GSortingProxyModel::create(ProcessModel::create(graph)));
|
||||
model()->set_key_column_and_sort_order(ProcessModel::Column::CPU, GSortOrder::Descending);
|
||||
refresh();
|
||||
|
||||
on_selection = [this](auto&) {
|
||||
if (on_process_selected)
|
||||
on_process_selected(selected_pid());
|
||||
};
|
||||
}
|
||||
|
||||
ProcessTableView::~ProcessTableView()
|
||||
{
|
||||
}
|
||||
|
||||
void ProcessTableView::refresh()
|
||||
{
|
||||
model()->update();
|
||||
}
|
||||
|
||||
void ProcessTableView::model_notification(const GModelNotification& notification)
|
||||
{
|
||||
if (notification.type() == GModelNotification::ModelUpdated) {
|
||||
// Do something?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pid_t ProcessTableView::selected_pid() const
|
||||
{
|
||||
if (!model()->selected_index().is_valid())
|
||||
return -1;
|
||||
return model()->data(model()->index(model()->selected_index().row(), ProcessModel::Column::PID), GModel::Role::Sort).as_int();
|
||||
}
|
23
Applications/SystemMonitor/ProcessTableView.h
Normal file
23
Applications/SystemMonitor/ProcessTableView.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <LibGUI/GTableView.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class GraphWidget;
|
||||
class ProcessModel;
|
||||
|
||||
class ProcessTableView final : public GTableView {
|
||||
public:
|
||||
ProcessTableView(GraphWidget&, GWidget* parent);
|
||||
virtual ~ProcessTableView() override;
|
||||
|
||||
pid_t selected_pid() const;
|
||||
|
||||
void refresh();
|
||||
|
||||
Function<void(pid_t)> on_process_selected;
|
||||
|
||||
private:
|
||||
virtual void model_notification(const GModelNotification&) override;
|
||||
};
|
260
Applications/SystemMonitor/main.cpp
Normal file
260
Applications/SystemMonitor/main.cpp
Normal file
|
@ -0,0 +1,260 @@
|
|||
#include "GraphWidget.h"
|
||||
#include "MemoryStatsWidget.h"
|
||||
#include "NetworkStatisticsWidget.h"
|
||||
#include "ProcessFileDescriptorMapWidget.h"
|
||||
#include "ProcessMemoryMapWidget.h"
|
||||
#include "ProcessStacksWidget.h"
|
||||
#include "ProcessTableView.h"
|
||||
#include <LibCore/CTimer.h>
|
||||
#include <LibDraw/PNGLoader.h>
|
||||
#include <LibGUI/GAction.h>
|
||||
#include <LibGUI/GApplication.h>
|
||||
#include <LibGUI/GBoxLayout.h>
|
||||
#include <LibGUI/GGroupBox.h>
|
||||
#include <LibGUI/GJsonArrayModel.h>
|
||||
#include <LibGUI/GLabel.h>
|
||||
#include <LibGUI/GMenuBar.h>
|
||||
#include <LibGUI/GSortingProxyModel.h>
|
||||
#include <LibGUI/GSplitter.h>
|
||||
#include <LibGUI/GTabWidget.h>
|
||||
#include <LibGUI/GToolBar.h>
|
||||
#include <LibGUI/GWidget.h>
|
||||
#include <LibGUI/GWindow.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static String human_readable_size(u32 size)
|
||||
{
|
||||
if (size < (64 * KB))
|
||||
return String::format("%u", size);
|
||||
if (size < MB)
|
||||
return String::format("%u KB", size / KB);
|
||||
if (size < GB)
|
||||
return String::format("%u MB", size / MB);
|
||||
return String::format("%u GB", size / GB);
|
||||
}
|
||||
|
||||
static GWidget* build_file_systems_tab();
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
GApplication app(argc, argv);
|
||||
|
||||
auto* keeper = new GWidget;
|
||||
keeper->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
keeper->set_fill_with_background_color(true);
|
||||
keeper->set_background_color(Color::WarmGray);
|
||||
keeper->layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
auto* tabwidget = new GTabWidget(keeper);
|
||||
|
||||
auto* process_container_splitter = new GSplitter(Orientation::Vertical, nullptr);
|
||||
tabwidget->add_widget("Processes", process_container_splitter);
|
||||
|
||||
auto* process_table_container = new GWidget(process_container_splitter);
|
||||
|
||||
auto* graphs_container = new GWidget;
|
||||
graphs_container->set_fill_with_background_color(true);
|
||||
graphs_container->set_background_color(Color::WarmGray);
|
||||
graphs_container->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
graphs_container->layout()->set_margins({ 4, 4, 4, 4 });
|
||||
|
||||
auto* cpu_graph_group_box = new GGroupBox("CPU usage", graphs_container);
|
||||
cpu_graph_group_box->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
cpu_graph_group_box->layout()->set_margins({ 6, 16, 6, 6 });
|
||||
cpu_graph_group_box->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
|
||||
cpu_graph_group_box->set_preferred_size(0, 120);
|
||||
auto* cpu_graph = new GraphWidget(cpu_graph_group_box);
|
||||
cpu_graph->set_max(100);
|
||||
cpu_graph->set_text_color(Color::Green);
|
||||
cpu_graph->set_graph_color(Color::from_rgb(0x00bb00));
|
||||
cpu_graph->text_formatter = [](int value, int) {
|
||||
return String::format("%d%%", value);
|
||||
};
|
||||
|
||||
auto* memory_graph_group_box = new GGroupBox("Memory usage", graphs_container);
|
||||
memory_graph_group_box->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
memory_graph_group_box->layout()->set_margins({ 6, 16, 6, 6 });
|
||||
memory_graph_group_box->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
|
||||
memory_graph_group_box->set_preferred_size(0, 120);
|
||||
auto* memory_graph = new GraphWidget(memory_graph_group_box);
|
||||
memory_graph->set_text_color(Color::Cyan);
|
||||
memory_graph->set_graph_color(Color::from_rgb(0x00bbbb));
|
||||
memory_graph->text_formatter = [](int value, int max) {
|
||||
return String::format("%d / %d KB", value, max);
|
||||
};
|
||||
|
||||
tabwidget->add_widget("Graphs", graphs_container);
|
||||
|
||||
tabwidget->add_widget("File systems", build_file_systems_tab());
|
||||
|
||||
auto* network_stats_widget = new NetworkStatisticsWidget(nullptr);
|
||||
tabwidget->add_widget("Network", network_stats_widget);
|
||||
|
||||
process_table_container->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
process_table_container->layout()->set_margins({ 4, 0, 4, 4 });
|
||||
process_table_container->layout()->set_spacing(0);
|
||||
|
||||
auto* toolbar = new GToolBar(process_table_container);
|
||||
toolbar->set_has_frame(false);
|
||||
auto* process_table_view = new ProcessTableView(*cpu_graph, process_table_container);
|
||||
auto* memory_stats_widget = new MemoryStatsWidget(*memory_graph, graphs_container);
|
||||
|
||||
auto* refresh_timer = new CTimer(1000, [&] {
|
||||
process_table_view->refresh();
|
||||
memory_stats_widget->refresh();
|
||||
});
|
||||
|
||||
auto kill_action = GAction::create("Kill process", GraphicsBitmap::load_from_file("/res/icons/kill16.png"), [process_table_view](const GAction&) {
|
||||
pid_t pid = process_table_view->selected_pid();
|
||||
if (pid != -1)
|
||||
kill(pid, SIGKILL);
|
||||
});
|
||||
|
||||
auto stop_action = GAction::create("Stop process", GraphicsBitmap::load_from_file("/res/icons/stop16.png"), [process_table_view](const GAction&) {
|
||||
pid_t pid = process_table_view->selected_pid();
|
||||
if (pid != -1)
|
||||
kill(pid, SIGSTOP);
|
||||
});
|
||||
|
||||
auto continue_action = GAction::create("Continue process", GraphicsBitmap::load_from_file("/res/icons/continue16.png"), [process_table_view](const GAction&) {
|
||||
pid_t pid = process_table_view->selected_pid();
|
||||
if (pid != -1)
|
||||
kill(pid, SIGCONT);
|
||||
});
|
||||
|
||||
toolbar->add_action(kill_action);
|
||||
toolbar->add_action(stop_action);
|
||||
toolbar->add_action(continue_action);
|
||||
|
||||
auto menubar = make<GMenuBar>();
|
||||
auto app_menu = make<GMenu>("System Monitor");
|
||||
app_menu->add_action(GAction::create("Quit", { Mod_Alt, Key_F4 }, [](const GAction&) {
|
||||
GApplication::the().quit(0);
|
||||
return;
|
||||
}));
|
||||
menubar->add_menu(move(app_menu));
|
||||
|
||||
auto process_menu = make<GMenu>("Process");
|
||||
process_menu->add_action(kill_action);
|
||||
process_menu->add_action(stop_action);
|
||||
process_menu->add_action(continue_action);
|
||||
menubar->add_menu(move(process_menu));
|
||||
|
||||
auto process_context_menu = make<GMenu>("Process context menu");
|
||||
process_context_menu->add_action(kill_action);
|
||||
process_context_menu->add_action(stop_action);
|
||||
process_context_menu->add_action(continue_action);
|
||||
process_table_view->on_context_menu_request = [&](const GModelIndex& index, const GContextMenuEvent& event) {
|
||||
(void)index;
|
||||
process_context_menu->popup(event.screen_position());
|
||||
};
|
||||
|
||||
auto frequency_menu = make<GMenu>("Frequency");
|
||||
frequency_menu->add_action(GAction::create("0.25 sec", [refresh_timer](auto&) {
|
||||
refresh_timer->restart(250);
|
||||
}));
|
||||
frequency_menu->add_action(GAction::create("0.5 sec", [refresh_timer](auto&) {
|
||||
refresh_timer->restart(500);
|
||||
}));
|
||||
frequency_menu->add_action(GAction::create("1 sec", [refresh_timer](auto&) {
|
||||
refresh_timer->restart(1000);
|
||||
}));
|
||||
frequency_menu->add_action(GAction::create("3 sec", [refresh_timer](auto&) {
|
||||
refresh_timer->restart(3000);
|
||||
}));
|
||||
frequency_menu->add_action(GAction::create("5 sec", [refresh_timer](auto&) {
|
||||
refresh_timer->restart(5000);
|
||||
}));
|
||||
menubar->add_menu(move(frequency_menu));
|
||||
|
||||
auto help_menu = make<GMenu>("Help");
|
||||
help_menu->add_action(GAction::create("About", [](const GAction&) {
|
||||
dbgprintf("FIXME: Implement Help/About\n");
|
||||
}));
|
||||
menubar->add_menu(move(help_menu));
|
||||
|
||||
app.set_menubar(move(menubar));
|
||||
|
||||
auto* process_tab_widget = new GTabWidget(process_container_splitter);
|
||||
|
||||
auto* open_files_widget = new ProcessFileDescriptorMapWidget(nullptr);
|
||||
process_tab_widget->add_widget("Open files", open_files_widget);
|
||||
|
||||
auto* memory_map_widget = new ProcessMemoryMapWidget(nullptr);
|
||||
process_tab_widget->add_widget("Memory map", memory_map_widget);
|
||||
|
||||
auto* stacks_widget = new ProcessStacksWidget(nullptr);
|
||||
process_tab_widget->add_widget("Stacks", stacks_widget);
|
||||
|
||||
process_table_view->on_process_selected = [&](pid_t pid) {
|
||||
open_files_widget->set_pid(pid);
|
||||
stacks_widget->set_pid(pid);
|
||||
memory_map_widget->set_pid(pid);
|
||||
};
|
||||
|
||||
auto* window = new GWindow;
|
||||
window->set_title("System Monitor");
|
||||
window->set_rect(20, 200, 680, 400);
|
||||
window->set_main_widget(keeper);
|
||||
|
||||
window->show();
|
||||
|
||||
window->set_icon(load_png("/res/icons/16x16/app-system-monitor.png"));
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
GWidget* build_file_systems_tab()
|
||||
{
|
||||
auto* fs_widget = new GWidget(nullptr);
|
||||
fs_widget->set_layout(make<GBoxLayout>(Orientation::Vertical));
|
||||
fs_widget->layout()->set_margins({ 4, 4, 4, 4 });
|
||||
auto* fs_table_view = new GTableView(fs_widget);
|
||||
fs_table_view->set_size_columns_to_fit_content(true);
|
||||
|
||||
Vector<GJsonArrayModel::FieldSpec> df_fields;
|
||||
df_fields.empend("mount_point", "Mount point", TextAlignment::CenterLeft);
|
||||
df_fields.empend("class_name", "Class", TextAlignment::CenterLeft);
|
||||
df_fields.empend(
|
||||
"Size", TextAlignment::CenterRight,
|
||||
[](const JsonObject& object) {
|
||||
return human_readable_size(object.get("total_block_count").to_u32() * object.get("block_size").to_u32());
|
||||
},
|
||||
[](const JsonObject& object) {
|
||||
return object.get("total_block_count").to_u32() * object.get("block_size").to_u32();
|
||||
});
|
||||
df_fields.empend(
|
||||
"Used", TextAlignment::CenterRight,
|
||||
[](const JsonObject& object) {
|
||||
auto total_blocks = object.get("total_block_count").to_u32();
|
||||
auto free_blocks = object.get("free_block_count").to_u32();
|
||||
auto used_blocks = total_blocks - free_blocks;
|
||||
return human_readable_size(used_blocks * object.get("block_size").to_u32()); },
|
||||
[](const JsonObject& object) {
|
||||
auto total_blocks = object.get("total_block_count").to_u32();
|
||||
auto free_blocks = object.get("free_block_count").to_u32();
|
||||
auto used_blocks = total_blocks - free_blocks;
|
||||
return used_blocks * object.get("block_size").to_u32();
|
||||
});
|
||||
df_fields.empend(
|
||||
"Available", TextAlignment::CenterRight,
|
||||
[](const JsonObject& object) {
|
||||
return human_readable_size(object.get("free_block_count").to_u32() * object.get("block_size").to_u32());
|
||||
},
|
||||
[](const JsonObject& object) {
|
||||
return object.get("free_block_count").to_u32() * object.get("block_size").to_u32();
|
||||
});
|
||||
df_fields.empend("Access", TextAlignment::CenterLeft, [](const JsonObject& object) {
|
||||
return object.get("readonly").to_bool() ? "Read-only" : "Read/Write";
|
||||
});
|
||||
df_fields.empend("free_block_count", "Free blocks", TextAlignment::CenterRight);
|
||||
df_fields.empend("total_block_count", "Total blocks", TextAlignment::CenterRight);
|
||||
df_fields.empend("free_inode_count", "Free inodes", TextAlignment::CenterRight);
|
||||
df_fields.empend("total_inode_count", "Total inodes", TextAlignment::CenterRight);
|
||||
df_fields.empend("block_size", "Block size", TextAlignment::CenterRight);
|
||||
fs_table_view->set_model(GSortingProxyModel::create(GJsonArrayModel::create("/proc/df", move(df_fields))));
|
||||
fs_table_view->model()->update();
|
||||
return fs_widget;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue