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 <LibGUI/Painter.h>
#include <LibGfx/Font.h>
#include <LibGfx/Path.h>
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();
}
@ -48,28 +49,115 @@ void GraphWidget::paint_event(GUI::PaintEvent& event)
GUI::Painter painter(*this);
painter.add_clip_rect(event.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();
float scale = (float)inner_rect.height() / (float)m_max;
Gfx::IntPoint prev_point;
for (size_t 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;
Gfx::IntPoint 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()) {
// Draw one set of values at a time
for (size_t k = 0; k < m_value_format.size(); k++) {
const auto& format = m_value_format[k];
if (format.line_color == Color::Transparent && format.background_color == Color::Transparent)
continue;
m_calculated_points.clear_with_capacity();
for (size_t i = 0; i < m_values.size(); i++) {
int x = inner_rect.right() - (i * 2) + 1;
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) {
Gfx::IntRect 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(), Gfx::TextAlignment::CenterRight, Color::Black);
painter.draw_text(text_rect, text.characters(), Gfx::TextAlignment::CenterRight, m_text_color);
if (!m_values.is_empty() && !m_value_format.is_empty()) {
const auto& current_values = m_values.last();
int y = 0;
for (size_t i = 0; i < min(m_value_format.size(), current_values.size()); i++) {
const auto& format = m_value_format[i];
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;
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 set_text_color(Color color) { m_text_color = color; }
void add_value(Vector<int, 1>&&);
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:
explicit GraphWidget();
@ -48,7 +61,10 @@ private:
virtual void paint_event(GUI::PaintEvent&) override;
int m_max { 100 };
CircularQueue<int, 4000> m_values;
Color m_graph_color;
Color m_text_color;
Vector<ValueFormat, 1> m_value_format;
CircularQueue<Vector<int, 1>, 4000> m_values;
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;
};
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_kmalloc_space_label = build_widgets_for_label("Kernel heap:");
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 user_physical_allocated = json.get("user_physical_allocated").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_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;
size_t kmalloc_bytes_total = kmalloc_allocated + kmalloc_available;
size_t user_physical_pages_total = user_physical_allocated + user_physical_available;
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)));
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)));
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 physical_pages_total = user_physical_pages_total + supervisor_pages_total;
size_t physical_pages_in_use = user_physical_allocated + super_physical_alloc;
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_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_graph.set_max(page_count_to_kb(user_pages_available));
m_graph.add_value(page_count_to_kb(user_physical_allocated));
m_graph.set_max(page_count_to_kb(total_userphysical_and_swappable_pages) + bytes_to_kb(kmalloc_bytes_total));
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;
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_kmalloc_space_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 all_processes = Core::ProcessStatisticsReader::get_all(m_proc_all);
u64 last_sum_ticks_scheduled = 0;
for (auto& it : m_threads)
last_sum_ticks_scheduled += it.value->current_state.ticks_user + it.value->current_state.ticks_kernel;
u64 last_sum_ticks_scheduled = 0, last_sum_ticks_scheduled_kernel = 0;
for (auto& it : m_threads) {
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;
u64 sum_ticks_scheduled = 0;
u64 sum_ticks_scheduled = 0, sum_ticks_scheduled_kernel = 0;
if (all_processes.has_value()) {
for (auto& it : all_processes.value()) {
for (auto& thread : it.value.threads) {
@ -399,6 +402,7 @@ void ProcessModel::update()
state.effective_priority = thread.effective_priority;
state.state = thread.state;
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 });
if (pit == m_threads.end())
@ -415,8 +419,10 @@ void ProcessModel::update()
}
m_pids.clear();
for (auto& c : m_cpus)
for (auto& c : m_cpus) {
c.total_cpu_percent = 0.0;
c.total_cpu_percent_kernel = 0.0;
}
Vector<PidAndTid, 16> pids_to_remove;
for (auto& it : m_threads) {
if (!live_pids.contains(it.key)) {
@ -424,11 +430,15 @@ void ProcessModel::update()
continue;
}
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.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) {
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);
}
}

View File

@ -95,6 +95,7 @@ public:
struct CpuInfo {
u32 id;
float total_cpu_percent { 0.0 };
float total_cpu_percent_kernel { 0.0 };
CpuInfo(u32 id)
: id(id)
@ -144,6 +145,7 @@ private:
unsigned file_read_bytes;
unsigned file_write_bytes;
float cpu_percent;
float cpu_percent_kernel;
};
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++) {
auto& cpu_graph = cpu_graph_group_box.add<GraphWidget>();
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::formatted("{}%", value);
};
cpu_graph.set_background_color(Color::White);
cpu_graph.set_value_format(0, {
.line_color = Color::Blue,
.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);
}
ProcessModel::the().on_cpu_info_change = [cpu_graphs](const NonnullOwnPtrVector<ProcessModel::CpuInfo>& cpus) {
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");
@ -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.set_fixed_height(120);
auto& memory_graph = memory_graph_group_box.add<GraphWidget>();
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::formatted("{} / {} KiB", value, max);
};
memory_graph.set_background_color(Color::White);
memory_graph.set_stack_values(true);
memory_graph.set_value_format(0, {
.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);
};