SystemMonitor: Beef up CPU and memory graphs

We now show the total CPU usage as well as the kernel portion.
For the memory graphs we show the amount of committed memory,
actually allocated memory, and the portion of the kernel heap.
This commit is contained in:
Tom 2021-01-04 20:14:52 -07:00 committed by Andreas Kling
parent b4a783d923
commit de6a4d49b8
Notes: sideshowbarker 2024-07-19 00:06:11 +09:00
7 changed files with 207 additions and 54 deletions

View File

@ -27,6 +27,7 @@
#include "GraphWidget.h" #include "GraphWidget.h"
#include <LibGUI/Painter.h> #include <LibGUI/Painter.h>
#include <LibGfx/Font.h> #include <LibGfx/Font.h>
#include <LibGfx/Path.h>
GraphWidget::GraphWidget() GraphWidget::GraphWidget()
{ {
@ -36,9 +37,9 @@ GraphWidget::~GraphWidget()
{ {
} }
void GraphWidget::add_value(int value) void GraphWidget::add_value(Vector<int, 1>&& value)
{ {
m_values.enqueue(value); m_values.enqueue(move(value));
update(); update();
} }
@ -48,28 +49,115 @@ void GraphWidget::paint_event(GUI::PaintEvent& event)
GUI::Painter painter(*this); GUI::Painter painter(*this);
painter.add_clip_rect(event.rect()); painter.add_clip_rect(event.rect());
painter.add_clip_rect(frame_inner_rect()); painter.add_clip_rect(frame_inner_rect());
painter.fill_rect(event.rect(), Color::Black); painter.fill_rect(event.rect(), m_background_color);
auto inner_rect = frame_inner_rect(); auto inner_rect = frame_inner_rect();
float scale = (float)inner_rect.height() / (float)m_max; float scale = (float)inner_rect.height() / (float)m_max;
Gfx::IntPoint prev_point; if (!m_values.is_empty()) {
for (size_t i = 0; i < m_values.size(); ++i) { // Draw one set of values at a time
int x = inner_rect.right() - (i * 2) + 1; for (size_t k = 0; k < m_value_format.size(); k++) {
if (x < 0) const auto& format = m_value_format[k];
break; if (format.line_color == Color::Transparent && format.background_color == Color::Transparent)
float scaled_value = (float)m_values.at(m_values.size() - i - 1) * scale; continue;
Gfx::IntPoint point = { x, inner_rect.bottom() - (int)scaled_value }; m_calculated_points.clear_with_capacity();
if (i != 0) for (size_t i = 0; i < m_values.size(); i++) {
painter.draw_line(prev_point, point, m_graph_color); int x = inner_rect.right() - (i * 2) + 1;
prev_point = point; if (x < 0)
break;
const auto& current_values = m_values.at(m_values.size() - i - 1);
if (current_values.size() <= k) {
// Don't have a data point
m_calculated_points.append({ -1, -1 });
continue;
}
float value = current_values[k];
if (m_stack_values) {
for (size_t l = k + 1; l < current_values.size(); l++)
value += current_values[l];
}
float scaled_value = value * scale;
Gfx::IntPoint current_point { x, inner_rect.bottom() - (int)scaled_value };
m_calculated_points.append(current_point);
}
ASSERT(m_calculated_points.size() <= m_values.size());
if (format.background_color != Color::Transparent) {
// Fill the background for the area we have values for
Gfx::Path path;
size_t points_in_path = 0;
bool started_path = false;
const Gfx::IntPoint* current_point = nullptr;
const Gfx::IntPoint* first_point = nullptr;
auto check_fill_area = [&]() {
if (!started_path)
return;
if (points_in_path > 1) {
ASSERT(current_point);
ASSERT(first_point);
path.line_to({ current_point->x() - 1, inner_rect.bottom() + 1 });
path.line_to({ first_point->x() + 1, inner_rect.bottom() + 1 });
path.close();
painter.fill_path(path, format.background_color, Gfx::Painter::WindingRule::EvenOdd);
} else if (points_in_path == 1 && current_point) {
// Can't fill any area, we only have one data point.
// Just draw a vertical line as a "fill"...
painter.draw_line(*current_point, { current_point->x(), inner_rect.bottom() }, format.background_color);
}
path = {};
points_in_path = 0;
first_point = nullptr;
started_path = false;
};
for (size_t i = 0; i < m_calculated_points.size(); i++) {
current_point = &m_calculated_points[i];
if (current_point->x() < 0) {
check_fill_area();
continue;
}
if (!started_path) {
path.move_to({ current_point->x() + 1, current_point->y() });
points_in_path = 1;
first_point = current_point;
started_path = true;
} else {
path.line_to({ current_point->x(), current_point->y() });
points_in_path++;
}
}
check_fill_area();
}
if (format.line_color != Color::Transparent) {
// Draw the line for the data points we have
const Gfx::IntPoint* previous_point = nullptr;
for (size_t i = 0; i < m_calculated_points.size(); i++) {
const auto& current_point = m_calculated_points[i];
if (current_point.x() < 0) {
previous_point = nullptr;
continue;
}
if (previous_point)
painter.draw_line(*previous_point, current_point, format.line_color);
previous_point = &current_point;
}
}
}
} }
if (!m_values.is_empty() && text_formatter) { if (!m_values.is_empty() && !m_value_format.is_empty()) {
Gfx::IntRect text_rect = inner_rect.shrunken(8, 8); const auto& current_values = m_values.last();
text_rect.set_height(font().glyph_height()); int y = 0;
auto text = text_formatter(m_values.last(), m_max); for (size_t i = 0; i < min(m_value_format.size(), current_values.size()); i++) {
painter.draw_text(text_rect.translated(1, 1), text.characters(), Gfx::TextAlignment::CenterRight, Color::Black); const auto& format = m_value_format[i];
painter.draw_text(text_rect, text.characters(), Gfx::TextAlignment::CenterRight, m_text_color); if (!format.text_formatter)
continue;
auto constrain_rect = inner_rect.shrunken(8, 8);
auto text_rect = constrain_rect.translated(0, y).intersected(constrain_rect);
text_rect.set_height(font().glyph_height());
auto text = format.text_formatter(current_values[i]);
if (format.text_shadow_color != Color::Transparent)
painter.draw_text(text_rect.translated(1, 1), text.characters(), Gfx::TextAlignment::CenterRight, format.text_shadow_color);
painter.draw_text(text_rect, text.characters(), Gfx::TextAlignment::CenterRight, format.line_color);
y += text_rect.height() + 4;
}
} }
} }

View File

@ -35,12 +35,25 @@ public:
virtual ~GraphWidget() override; virtual ~GraphWidget() override;
void set_max(int max) { m_max = max; } void set_max(int max) { m_max = max; }
void add_value(int); int max() const { return m_max; }
void set_graph_color(Color color) { m_graph_color = color; } void add_value(Vector<int, 1>&&);
void set_text_color(Color color) { m_text_color = color; }
Function<String(int value, int max)> text_formatter; void set_background_color(Color color) { m_background_color = color; }
struct ValueFormat {
Color line_color { Color::Transparent };
Color background_color { Color::Transparent };
Color text_shadow_color { Color::Transparent };
Function<String(int)> text_formatter;
};
void set_value_format(size_t index, ValueFormat&& format)
{
if (m_value_format.size() <= index)
m_value_format.resize(index + 1);
m_value_format[index] = move(format);
}
void set_stack_values(bool stack_values) { m_stack_values = stack_values; }
private: private:
explicit GraphWidget(); explicit GraphWidget();
@ -48,7 +61,10 @@ private:
virtual void paint_event(GUI::PaintEvent&) override; virtual void paint_event(GUI::PaintEvent&) override;
int m_max { 100 }; int m_max { 100 };
CircularQueue<int, 4000> m_values; Vector<ValueFormat, 1> m_value_format;
Color m_graph_color; CircularQueue<Vector<int, 1>, 4000> m_values;
Color m_text_color; Color m_background_color { Color::Black };
bool m_stack_values { false };
Vector<Gfx::IntPoint, 1> m_calculated_points;
}; };

View File

@ -69,7 +69,8 @@ MemoryStatsWidget::MemoryStatsWidget(GraphWidget& graph)
return label; return label;
}; };
m_user_physical_pages_label = build_widgets_for_label("Userspace physical:"); m_user_physical_pages_label = build_widgets_for_label("Physical memory:");
m_user_physical_pages_committed_label = build_widgets_for_label("Committed memory:");
m_supervisor_physical_pages_label = build_widgets_for_label("Supervisor physical:"); m_supervisor_physical_pages_label = build_widgets_for_label("Supervisor physical:");
m_kmalloc_space_label = build_widgets_for_label("Kernel heap:"); m_kmalloc_space_label = build_widgets_for_label("Kernel heap:");
m_kmalloc_count_label = build_widgets_for_label("Calls kmalloc:"); m_kmalloc_count_label = build_widgets_for_label("Calls kmalloc:");
@ -109,22 +110,29 @@ void MemoryStatsWidget::refresh()
unsigned kmalloc_available = json.get("kmalloc_available").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_allocated = json.get("user_physical_allocated").to_u32();
unsigned user_physical_available = json.get("user_physical_available").to_u32(); unsigned user_physical_available = json.get("user_physical_available").to_u32();
unsigned user_physical_committed = json.get("user_physical_committed").to_u32();
unsigned user_physical_uncommitted = json.get("user_physical_uncommitted").to_u32();
unsigned super_physical_alloc = json.get("super_physical_allocated").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 super_physical_free = json.get("super_physical_available").to_u32();
unsigned kmalloc_call_count = json.get("kmalloc_call_count").to_u32(); unsigned kmalloc_call_count = json.get("kmalloc_call_count").to_u32();
unsigned kfree_call_count = json.get("kfree_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 kmalloc_bytes_total = kmalloc_allocated + kmalloc_available;
size_t user_pages_available = user_physical_allocated + user_physical_available; size_t user_physical_pages_total = user_physical_allocated + user_physical_available;
size_t supervisor_pages_available = super_physical_alloc + super_physical_free; size_t supervisor_pages_total = super_physical_alloc + super_physical_free;
m_kmalloc_space_label->set_text(String::formatted("{}K/{}K", bytes_to_kb(kmalloc_allocated), bytes_to_kb(kmalloc_sum_available))); size_t physical_pages_total = user_physical_pages_total + supervisor_pages_total;
m_user_physical_pages_label->set_text(String::formatted("{}K/{}K", page_count_to_kb(user_physical_allocated), page_count_to_kb(user_pages_available))); size_t physical_pages_in_use = user_physical_allocated + super_physical_alloc;
m_supervisor_physical_pages_label->set_text(String::formatted("{}K/{}K", page_count_to_kb(super_physical_alloc), page_count_to_kb(supervisor_pages_available))); size_t total_userphysical_and_swappable_pages = user_physical_allocated + user_physical_committed + user_physical_uncommitted;
m_kmalloc_space_label->set_text(String::formatted("{}K/{}K", bytes_to_kb(kmalloc_allocated), bytes_to_kb(kmalloc_bytes_total)));
m_user_physical_pages_label->set_text(String::formatted("{}K/{}K", page_count_to_kb(physical_pages_in_use), page_count_to_kb(physical_pages_total)));
m_user_physical_pages_committed_label->set_text(String::formatted("{}K", page_count_to_kb(user_physical_committed)));
m_supervisor_physical_pages_label->set_text(String::formatted("{}K/{}K", page_count_to_kb(super_physical_alloc), page_count_to_kb(supervisor_pages_total)));
m_kmalloc_count_label->set_text(String::formatted("{}", kmalloc_call_count)); m_kmalloc_count_label->set_text(String::formatted("{}", kmalloc_call_count));
m_kfree_count_label->set_text(String::formatted("{}", kfree_call_count)); m_kfree_count_label->set_text(String::formatted("{}", kfree_call_count));
m_kmalloc_difference_label->set_text(String::formatted("{:+}", kmalloc_call_count - kfree_call_count)); m_kmalloc_difference_label->set_text(String::formatted("{:+}", kmalloc_call_count - kfree_call_count));
m_graph.set_max(page_count_to_kb(user_pages_available)); m_graph.set_max(page_count_to_kb(total_userphysical_and_swappable_pages) + bytes_to_kb(kmalloc_bytes_total));
m_graph.add_value(page_count_to_kb(user_physical_allocated)); m_graph.add_value({ (int)page_count_to_kb(user_physical_committed), (int)page_count_to_kb(user_physical_allocated), (int)bytes_to_kb(kmalloc_bytes_total) });
} }

View File

@ -44,6 +44,7 @@ private:
GraphWidget& m_graph; GraphWidget& m_graph;
RefPtr<GUI::Label> m_user_physical_pages_label; RefPtr<GUI::Label> m_user_physical_pages_label;
RefPtr<GUI::Label> m_user_physical_pages_committed_label;
RefPtr<GUI::Label> m_supervisor_physical_pages_label; RefPtr<GUI::Label> m_supervisor_physical_pages_label;
RefPtr<GUI::Label> m_kmalloc_space_label; RefPtr<GUI::Label> m_kmalloc_space_label;
RefPtr<GUI::Label> m_kmalloc_count_label; RefPtr<GUI::Label> m_kmalloc_count_label;

View File

@ -352,12 +352,15 @@ void ProcessModel::update()
auto previous_pid_count = m_pids.size(); auto previous_pid_count = m_pids.size();
auto all_processes = Core::ProcessStatisticsReader::get_all(m_proc_all); auto all_processes = Core::ProcessStatisticsReader::get_all(m_proc_all);
u64 last_sum_ticks_scheduled = 0; u64 last_sum_ticks_scheduled = 0, last_sum_ticks_scheduled_kernel = 0;
for (auto& it : m_threads) for (auto& it : m_threads) {
last_sum_ticks_scheduled += it.value->current_state.ticks_user + it.value->current_state.ticks_kernel; auto& current_state = it.value->current_state;
last_sum_ticks_scheduled += current_state.ticks_user + current_state.ticks_kernel;
last_sum_ticks_scheduled_kernel += current_state.ticks_kernel;
}
HashTable<PidAndTid> live_pids; HashTable<PidAndTid> live_pids;
u64 sum_ticks_scheduled = 0; u64 sum_ticks_scheduled = 0, sum_ticks_scheduled_kernel = 0;
if (all_processes.has_value()) { if (all_processes.has_value()) {
for (auto& it : all_processes.value()) { for (auto& it : all_processes.value()) {
for (auto& thread : it.value.threads) { for (auto& thread : it.value.threads) {
@ -399,6 +402,7 @@ void ProcessModel::update()
state.effective_priority = thread.effective_priority; state.effective_priority = thread.effective_priority;
state.state = thread.state; state.state = thread.state;
sum_ticks_scheduled += thread.ticks_user + thread.ticks_kernel; sum_ticks_scheduled += thread.ticks_user + thread.ticks_kernel;
sum_ticks_scheduled_kernel += thread.ticks_kernel;
{ {
auto pit = m_threads.find({ it.value.pid, thread.tid }); auto pit = m_threads.find({ it.value.pid, thread.tid });
if (pit == m_threads.end()) if (pit == m_threads.end())
@ -415,8 +419,10 @@ void ProcessModel::update()
} }
m_pids.clear(); m_pids.clear();
for (auto& c : m_cpus) for (auto& c : m_cpus) {
c.total_cpu_percent = 0.0; c.total_cpu_percent = 0.0;
c.total_cpu_percent_kernel = 0.0;
}
Vector<PidAndTid, 16> pids_to_remove; Vector<PidAndTid, 16> pids_to_remove;
for (auto& it : m_threads) { for (auto& it : m_threads) {
if (!live_pids.contains(it.key)) { if (!live_pids.contains(it.key)) {
@ -424,11 +430,15 @@ void ProcessModel::update()
continue; continue;
} }
auto& process = *it.value; auto& process = *it.value;
u32 times_scheduled_diff = (process.current_state.ticks_user + process.current_state.ticks_kernel) u32 ticks_scheduled_diff = (process.current_state.ticks_user + process.current_state.ticks_kernel)
- (process.previous_state.ticks_user + process.previous_state.ticks_kernel); - (process.previous_state.ticks_user + process.previous_state.ticks_kernel);
process.current_state.cpu_percent = ((float)times_scheduled_diff * 100) / (float)(sum_ticks_scheduled - last_sum_ticks_scheduled); u32 ticks_scheduled_diff_kernel = process.current_state.ticks_kernel - process.previous_state.ticks_kernel;
process.current_state.cpu_percent = ((float)ticks_scheduled_diff * 100) / (float)(sum_ticks_scheduled - last_sum_ticks_scheduled);
process.current_state.cpu_percent_kernel = ((float)ticks_scheduled_diff_kernel * 100) / (float)(sum_ticks_scheduled - last_sum_ticks_scheduled);
if (it.key.pid != 0) { if (it.key.pid != 0) {
m_cpus[process.current_state.cpu].total_cpu_percent += process.current_state.cpu_percent; auto& cpu_info = m_cpus[process.current_state.cpu];
cpu_info.total_cpu_percent += process.current_state.cpu_percent;
cpu_info.total_cpu_percent_kernel += process.current_state.cpu_percent_kernel;
m_pids.append(it.key); m_pids.append(it.key);
} }
} }

View File

@ -95,6 +95,7 @@ public:
struct CpuInfo { struct CpuInfo {
u32 id; u32 id;
float total_cpu_percent { 0.0 }; float total_cpu_percent { 0.0 };
float total_cpu_percent_kernel { 0.0 };
CpuInfo(u32 id) CpuInfo(u32 id)
: id(id) : id(id)
@ -144,6 +145,7 @@ private:
unsigned file_read_bytes; unsigned file_read_bytes;
unsigned file_write_bytes; unsigned file_write_bytes;
float cpu_percent; float cpu_percent;
float cpu_percent_kernel;
}; };
struct Thread { struct Thread {

View File

@ -549,16 +549,26 @@ NonnullRefPtr<GUI::Widget> build_graphs_tab()
for (size_t i = 0; i < ProcessModel::the().cpus().size(); i++) { for (size_t i = 0; i < ProcessModel::the().cpus().size(); i++) {
auto& cpu_graph = cpu_graph_group_box.add<GraphWidget>(); auto& cpu_graph = cpu_graph_group_box.add<GraphWidget>();
cpu_graph.set_max(100); cpu_graph.set_max(100);
cpu_graph.set_text_color(Color::Green); cpu_graph.set_background_color(Color::White);
cpu_graph.set_graph_color(Color::from_rgb(0x00bb00)); cpu_graph.set_value_format(0, {
cpu_graph.text_formatter = [](int value, int) { .line_color = Color::Blue,
return String::formatted("{}%", value); .background_color = Color::from_rgb(0xaaaaff),
}; .text_formatter = [](int value) {
return String::formatted("Total: {}%", value);
},
});
cpu_graph.set_value_format(1, {
.line_color = Color::Red,
.background_color = Color::from_rgb(0xffaaaa),
.text_formatter = [](int value) {
return String::formatted("Kernel: {}%", value);
},
});
cpu_graphs.append(&cpu_graph); cpu_graphs.append(&cpu_graph);
} }
ProcessModel::the().on_cpu_info_change = [cpu_graphs](const NonnullOwnPtrVector<ProcessModel::CpuInfo>& cpus) { ProcessModel::the().on_cpu_info_change = [cpu_graphs](const NonnullOwnPtrVector<ProcessModel::CpuInfo>& cpus) {
for (size_t i = 0; i < cpus.size(); i++) for (size_t i = 0; i < cpus.size(); i++)
cpu_graphs[i]->add_value(cpus[i].total_cpu_percent); cpu_graphs[i]->add_value({ (int)cpus[i].total_cpu_percent, (int)cpus[i].total_cpu_percent_kernel });
}; };
auto& memory_graph_group_box = self.add<GUI::GroupBox>("Memory usage"); auto& memory_graph_group_box = self.add<GUI::GroupBox>("Memory usage");
@ -566,11 +576,29 @@ NonnullRefPtr<GUI::Widget> build_graphs_tab()
memory_graph_group_box.layout()->set_margins({ 6, 16, 6, 6 }); memory_graph_group_box.layout()->set_margins({ 6, 16, 6, 6 });
memory_graph_group_box.set_fixed_height(120); memory_graph_group_box.set_fixed_height(120);
auto& memory_graph = memory_graph_group_box.add<GraphWidget>(); auto& memory_graph = memory_graph_group_box.add<GraphWidget>();
memory_graph.set_text_color(Color::Cyan); memory_graph.set_background_color(Color::White);
memory_graph.set_graph_color(Color::from_rgb(0x00bbbb)); memory_graph.set_stack_values(true);
memory_graph.text_formatter = [](int value, int max) { memory_graph.set_value_format(0, {
return String::formatted("{} / {} KiB", value, max); .line_color = Color::from_rgb(0x619910),
}; .background_color = Color::from_rgb(0xbbffbb),
.text_formatter = [&memory_graph](int value) {
return String::formatted("Committed: {} KiB", value);
},
});
memory_graph.set_value_format(1, {
.line_color = Color::Blue,
.background_color = Color::from_rgb(0xaaaaff),
.text_formatter = [&memory_graph](int value) {
return String::formatted("Allocated: {} KiB", value);
},
});
memory_graph.set_value_format(2, {
.line_color = Color::Red,
.background_color = Color::from_rgb(0xffaaaa),
.text_formatter = [&memory_graph](int value) {
return String::formatted("Kernel heap: {} KiB", value);
},
});
self.add<MemoryStatsWidget>(memory_graph); self.add<MemoryStatsWidget>(memory_graph);
}; };