summaryrefslogtreecommitdiff
path: root/Applications/SystemMonitor
diff options
context:
space:
mode:
Diffstat (limited to 'Applications/SystemMonitor')
-rw-r--r--Applications/SystemMonitor/GraphWidget.cpp52
-rw-r--r--Applications/SystemMonitor/GraphWidget.h24
-rw-r--r--Applications/SystemMonitor/Makefile16
-rw-r--r--Applications/SystemMonitor/MemoryStatsWidget.cpp94
-rw-r--r--Applications/SystemMonitor/MemoryStatsWidget.h25
-rw-r--r--Applications/SystemMonitor/NetworkStatisticsWidget.cpp75
-rw-r--r--Applications/SystemMonitor/NetworkStatisticsWidget.h20
-rw-r--r--Applications/SystemMonitor/ProcessFileDescriptorMapWidget.cpp36
-rw-r--r--Applications/SystemMonitor/ProcessFileDescriptorMapWidget.h18
-rw-r--r--Applications/SystemMonitor/ProcessMemoryMapWidget.cpp41
-rw-r--r--Applications/SystemMonitor/ProcessMemoryMapWidget.h18
-rw-r--r--Applications/SystemMonitor/ProcessModel.cpp242
-rw-r--r--Applications/SystemMonitor/ProcessModel.h68
-rw-r--r--Applications/SystemMonitor/ProcessStacksWidget.cpp38
-rw-r--r--Applications/SystemMonitor/ProcessStacksWidget.h21
-rw-r--r--Applications/SystemMonitor/ProcessTableView.cpp42
-rw-r--r--Applications/SystemMonitor/ProcessTableView.h23
-rw-r--r--Applications/SystemMonitor/main.cpp260
18 files changed, 1113 insertions, 0 deletions
diff --git a/Applications/SystemMonitor/GraphWidget.cpp b/Applications/SystemMonitor/GraphWidget.cpp
new file mode 100644
index 0000000000..3266e5526d
--- /dev/null
+++ b/Applications/SystemMonitor/GraphWidget.cpp
@@ -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);
+ }
+}
diff --git a/Applications/SystemMonitor/GraphWidget.h b/Applications/SystemMonitor/GraphWidget.h
new file mode 100644
index 0000000000..1d39beb1f1
--- /dev/null
+++ b/Applications/SystemMonitor/GraphWidget.h
@@ -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;
+};
diff --git a/Applications/SystemMonitor/Makefile b/Applications/SystemMonitor/Makefile
new file mode 100644
index 0000000000..9c760c378b
--- /dev/null
+++ b/Applications/SystemMonitor/Makefile
@@ -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
diff --git a/Applications/SystemMonitor/MemoryStatsWidget.cpp b/Applications/SystemMonitor/MemoryStatsWidget.cpp
new file mode 100644
index 0000000000..6412dba161
--- /dev/null
+++ b/Applications/SystemMonitor/MemoryStatsWidget.cpp
@@ -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();
+}
diff --git a/Applications/SystemMonitor/MemoryStatsWidget.h b/Applications/SystemMonitor/MemoryStatsWidget.h
new file mode 100644
index 0000000000..e3912b9b94
--- /dev/null
+++ b/Applications/SystemMonitor/MemoryStatsWidget.h
@@ -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;
+};
diff --git a/Applications/SystemMonitor/NetworkStatisticsWidget.cpp b/Applications/SystemMonitor/NetworkStatisticsWidget.cpp
new file mode 100644
index 0000000000..4820755d5e
--- /dev/null
+++ b/Applications/SystemMonitor/NetworkStatisticsWidget.cpp
@@ -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();
+}
diff --git a/Applications/SystemMonitor/NetworkStatisticsWidget.h b/Applications/SystemMonitor/NetworkStatisticsWidget.h
new file mode 100644
index 0000000000..502474bee1
--- /dev/null
+++ b/Applications/SystemMonitor/NetworkStatisticsWidget.h
@@ -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 };
+};
diff --git a/Applications/SystemMonitor/ProcessFileDescriptorMapWidget.cpp b/Applications/SystemMonitor/ProcessFileDescriptorMapWidget.cpp
new file mode 100644
index 0000000000..bcc461ede8
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessFileDescriptorMapWidget.cpp
@@ -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));
+}
diff --git a/Applications/SystemMonitor/ProcessFileDescriptorMapWidget.h b/Applications/SystemMonitor/ProcessFileDescriptorMapWidget.h
new file mode 100644
index 0000000000..a858281b6d
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessFileDescriptorMapWidget.h
@@ -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 };
+};
diff --git a/Applications/SystemMonitor/ProcessMemoryMapWidget.cpp b/Applications/SystemMonitor/ProcessMemoryMapWidget.cpp
new file mode 100644
index 0000000000..938f8f5237
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessMemoryMapWidget.cpp
@@ -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));
+}
diff --git a/Applications/SystemMonitor/ProcessMemoryMapWidget.h b/Applications/SystemMonitor/ProcessMemoryMapWidget.h
new file mode 100644
index 0000000000..fa1091286e
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessMemoryMapWidget.h
@@ -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 };
+};
diff --git a/Applications/SystemMonitor/ProcessModel.cpp b/Applications/SystemMonitor/ProcessModel.cpp
new file mode 100644
index 0000000000..b2f78245c8
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessModel.cpp
@@ -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();
+}
diff --git a/Applications/SystemMonitor/ProcessModel.h b/Applications/SystemMonitor/ProcessModel.h
new file mode 100644
index 0000000000..883da5d5cb
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessModel.h
@@ -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;
+};
diff --git a/Applications/SystemMonitor/ProcessStacksWidget.cpp b/Applications/SystemMonitor/ProcessStacksWidget.cpp
new file mode 100644
index 0000000000..d7e4a822da
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessStacksWidget.cpp
@@ -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());
+}
diff --git a/Applications/SystemMonitor/ProcessStacksWidget.h b/Applications/SystemMonitor/ProcessStacksWidget.h
new file mode 100644
index 0000000000..9b0c6ac8f0
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessStacksWidget.h
@@ -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 };
+};
diff --git a/Applications/SystemMonitor/ProcessTableView.cpp b/Applications/SystemMonitor/ProcessTableView.cpp
new file mode 100644
index 0000000000..2871018182
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessTableView.cpp
@@ -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();
+}
diff --git a/Applications/SystemMonitor/ProcessTableView.h b/Applications/SystemMonitor/ProcessTableView.h
new file mode 100644
index 0000000000..98d3f3edb1
--- /dev/null
+++ b/Applications/SystemMonitor/ProcessTableView.h
@@ -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;
+};
diff --git a/Applications/SystemMonitor/main.cpp b/Applications/SystemMonitor/main.cpp
new file mode 100644
index 0000000000..de3de2f8db
--- /dev/null
+++ b/Applications/SystemMonitor/main.cpp
@@ -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;
+}