mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 12:38:12 +00:00
SystemMonitor: Add a utilization graph for each processor
This commit is contained in:
parent
d99901660d
commit
cdc78515b6
3 changed files with 87 additions and 16 deletions
|
@ -30,6 +30,7 @@
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
#include <AK/JsonValue.h>
|
#include <AK/JsonValue.h>
|
||||||
#include <AK/SharedBuffer.h>
|
#include <AK/SharedBuffer.h>
|
||||||
|
#include <LibCore/File.h>
|
||||||
#include <LibCore/ProcessStatisticsReader.h>
|
#include <LibCore/ProcessStatisticsReader.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -50,6 +51,45 @@ ProcessModel::ProcessModel()
|
||||||
m_high_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/highpriority16.png");
|
m_high_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/highpriority16.png");
|
||||||
m_low_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/lowpriority16.png");
|
m_low_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/lowpriority16.png");
|
||||||
m_normal_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/normalpriority16.png");
|
m_normal_priority_icon = Gfx::Bitmap::load_from_file("/res/icons/normalpriority16.png");
|
||||||
|
|
||||||
|
auto file = Core::File::construct("/proc/cpuinfo");
|
||||||
|
if (file->open(Core::IODevice::ReadOnly)) {
|
||||||
|
OwnPtr<CpuInfo> cpu;
|
||||||
|
u32 cpu_id = 0;
|
||||||
|
while (file->can_read_line()) {
|
||||||
|
auto line = file->read_line(1024);
|
||||||
|
if (line.is_null())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto str = String::copy(line, Chomp);
|
||||||
|
if (str.is_empty() && cpu)
|
||||||
|
m_cpus.append(cpu.release_nonnull());
|
||||||
|
|
||||||
|
size_t i;
|
||||||
|
bool have_val = false;
|
||||||
|
for (i = 0; i < str.length(); i++) {
|
||||||
|
if (str[i] == ':') {
|
||||||
|
have_val = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!have_val)
|
||||||
|
continue;
|
||||||
|
auto key = str.substring(0, i);
|
||||||
|
auto val = str.substring(i + 1, str.length() - i - 1).trim_whitespace();
|
||||||
|
|
||||||
|
if (!cpu)
|
||||||
|
cpu = make<CpuInfo>(cpu_id++);
|
||||||
|
|
||||||
|
cpu->values.set(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpu)
|
||||||
|
m_cpus.append(cpu.release_nonnull());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_cpus.is_empty())
|
||||||
|
m_cpus.append(make<CpuInfo>(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessModel::~ProcessModel()
|
ProcessModel::~ProcessModel()
|
||||||
|
@ -97,6 +137,8 @@ String ProcessModel::column_name(int column) const
|
||||||
return "Purg:N";
|
return "Purg:N";
|
||||||
case Column::CPU:
|
case Column::CPU:
|
||||||
return "CPU";
|
return "CPU";
|
||||||
|
case Column::Processor:
|
||||||
|
return "Processor";
|
||||||
case Column::Name:
|
case Column::Name:
|
||||||
return "Name";
|
return "Name";
|
||||||
case Column::Syscalls:
|
case Column::Syscalls:
|
||||||
|
@ -157,6 +199,7 @@ GUI::Variant ProcessModel::data(const GUI::ModelIndex& index, Role role) const
|
||||||
case Column::PurgeableVolatile:
|
case Column::PurgeableVolatile:
|
||||||
case Column::PurgeableNonvolatile:
|
case Column::PurgeableNonvolatile:
|
||||||
case Column::CPU:
|
case Column::CPU:
|
||||||
|
case Column::Processor:
|
||||||
case Column::Syscalls:
|
case Column::Syscalls:
|
||||||
case Column::InodeFaults:
|
case Column::InodeFaults:
|
||||||
case Column::ZeroFaults:
|
case Column::ZeroFaults:
|
||||||
|
@ -206,6 +249,8 @@ GUI::Variant ProcessModel::data(const GUI::ModelIndex& index, Role role) const
|
||||||
return (int)thread.current_state.amount_purgeable_nonvolatile;
|
return (int)thread.current_state.amount_purgeable_nonvolatile;
|
||||||
case Column::CPU:
|
case Column::CPU:
|
||||||
return thread.current_state.cpu_percent;
|
return thread.current_state.cpu_percent;
|
||||||
|
case Column::Processor:
|
||||||
|
return thread.current_state.cpu;
|
||||||
case Column::Name:
|
case Column::Name:
|
||||||
return thread.current_state.name;
|
return thread.current_state.name;
|
||||||
case Column::Syscalls:
|
case Column::Syscalls:
|
||||||
|
@ -275,6 +320,8 @@ GUI::Variant ProcessModel::data(const GUI::ModelIndex& index, Role role) const
|
||||||
return pretty_byte_size(thread.current_state.amount_purgeable_nonvolatile);
|
return pretty_byte_size(thread.current_state.amount_purgeable_nonvolatile);
|
||||||
case Column::CPU:
|
case Column::CPU:
|
||||||
return thread.current_state.cpu_percent;
|
return thread.current_state.cpu_percent;
|
||||||
|
case Column::Processor:
|
||||||
|
return thread.current_state.cpu;
|
||||||
case Column::Name:
|
case Column::Name:
|
||||||
return thread.current_state.name;
|
return thread.current_state.name;
|
||||||
case Column::Syscalls:
|
case Column::Syscalls:
|
||||||
|
@ -346,6 +393,7 @@ void ProcessModel::update()
|
||||||
|
|
||||||
state.tid = thread.tid;
|
state.tid = thread.tid;
|
||||||
state.times_scheduled = thread.times_scheduled;
|
state.times_scheduled = thread.times_scheduled;
|
||||||
|
state.cpu = thread.cpu;
|
||||||
state.priority = thread.priority;
|
state.priority = thread.priority;
|
||||||
state.effective_priority = thread.effective_priority;
|
state.effective_priority = thread.effective_priority;
|
||||||
state.state = thread.state;
|
state.state = thread.state;
|
||||||
|
@ -365,7 +413,8 @@ void ProcessModel::update()
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pids.clear();
|
m_pids.clear();
|
||||||
float total_cpu_percent = 0;
|
for (auto& c : m_cpus)
|
||||||
|
c.total_cpu_percent = 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)) {
|
||||||
|
@ -376,15 +425,15 @@ void ProcessModel::update()
|
||||||
u32 times_scheduled_diff = process.current_state.times_scheduled - process.previous_state.times_scheduled;
|
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);
|
process.current_state.cpu_percent = ((float)times_scheduled_diff * 100) / (float)(sum_times_scheduled - last_sum_times_scheduled);
|
||||||
if (it.key.pid != 0) {
|
if (it.key.pid != 0) {
|
||||||
total_cpu_percent += process.current_state.cpu_percent;
|
m_cpus[process.current_state.cpu].total_cpu_percent += process.current_state.cpu_percent;
|
||||||
m_pids.append(it.key);
|
m_pids.append(it.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto pid : pids_to_remove)
|
for (auto pid : pids_to_remove)
|
||||||
m_threads.remove(pid);
|
m_threads.remove(pid);
|
||||||
|
|
||||||
if (on_new_cpu_data_point)
|
if (on_cpu_info_change)
|
||||||
on_new_cpu_data_point(total_cpu_percent);
|
on_cpu_info_change(m_cpus);
|
||||||
|
|
||||||
did_update(GUI::Model::UpdateFlag::DontInvalidateIndexes);
|
did_update(GUI::Model::UpdateFlag::DontInvalidateIndexes);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/NonnullOwnPtrVector.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibGUI/Model.h>
|
#include <LibGUI/Model.h>
|
||||||
|
@ -49,6 +50,7 @@ public:
|
||||||
Icon = 0,
|
Icon = 0,
|
||||||
Name,
|
Name,
|
||||||
CPU,
|
CPU,
|
||||||
|
Processor,
|
||||||
State,
|
State,
|
||||||
Priority,
|
Priority,
|
||||||
EffectivePriority,
|
EffectivePriority,
|
||||||
|
@ -87,7 +89,21 @@ public:
|
||||||
virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override;
|
virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override;
|
||||||
virtual void update() override;
|
virtual void update() override;
|
||||||
|
|
||||||
Function<void(float)> on_new_cpu_data_point;
|
struct CpuInfo
|
||||||
|
{
|
||||||
|
u32 id;
|
||||||
|
float total_cpu_percent{0.0};
|
||||||
|
HashMap<String, String> values;
|
||||||
|
|
||||||
|
CpuInfo(u32 id):
|
||||||
|
id(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Function<void(const NonnullOwnPtrVector<CpuInfo>&)> on_cpu_info_change;
|
||||||
|
|
||||||
|
const NonnullOwnPtrVector<CpuInfo>& cpus() const { return m_cpus; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ProcessModel();
|
ProcessModel();
|
||||||
|
@ -101,6 +117,7 @@ private:
|
||||||
String user;
|
String user;
|
||||||
String pledge;
|
String pledge;
|
||||||
String veil;
|
String veil;
|
||||||
|
u32 cpu;
|
||||||
u32 priority;
|
u32 priority;
|
||||||
u32 effective_priority;
|
u32 effective_priority;
|
||||||
size_t amount_virtual;
|
size_t amount_virtual;
|
||||||
|
@ -130,6 +147,7 @@ private:
|
||||||
|
|
||||||
HashMap<uid_t, String> m_usernames;
|
HashMap<uid_t, String> m_usernames;
|
||||||
HashMap<PidAndTid, NonnullOwnPtr<Thread>> m_threads;
|
HashMap<PidAndTid, NonnullOwnPtr<Thread>> m_threads;
|
||||||
|
NonnullOwnPtrVector<CpuInfo> m_cpus;
|
||||||
Vector<PidAndTid> m_pids;
|
Vector<PidAndTid> m_pids;
|
||||||
RefPtr<Gfx::Bitmap> m_generic_process_icon;
|
RefPtr<Gfx::Bitmap> m_generic_process_icon;
|
||||||
RefPtr<Gfx::Bitmap> m_high_priority_icon;
|
RefPtr<Gfx::Bitmap> m_high_priority_icon;
|
||||||
|
|
|
@ -480,20 +480,24 @@ NonnullRefPtr<GUI::Widget> build_graphs_tab()
|
||||||
self.layout()->set_margins({ 4, 4, 4, 4 });
|
self.layout()->set_margins({ 4, 4, 4, 4 });
|
||||||
|
|
||||||
auto& cpu_graph_group_box = self.add<GUI::GroupBox>("CPU usage");
|
auto& cpu_graph_group_box = self.add<GUI::GroupBox>("CPU usage");
|
||||||
cpu_graph_group_box.set_layout<GUI::VerticalBoxLayout>();
|
cpu_graph_group_box.set_layout<GUI::HorizontalBoxLayout>();
|
||||||
cpu_graph_group_box.layout()->set_margins({ 6, 16, 6, 6 });
|
cpu_graph_group_box.layout()->set_margins({ 6, 16, 6, 6 });
|
||||||
cpu_graph_group_box.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
|
cpu_graph_group_box.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
|
||||||
cpu_graph_group_box.set_preferred_size(0, 120);
|
cpu_graph_group_box.set_preferred_size(0, 120);
|
||||||
auto& cpu_graph = cpu_graph_group_box.add<GraphWidget>();
|
Vector<GraphWidget*> cpu_graphs;
|
||||||
cpu_graph.set_max(100);
|
for (size_t i = 0; i < ProcessModel::the().cpus().size(); i++) {
|
||||||
cpu_graph.set_text_color(Color::Green);
|
auto& cpu_graph = cpu_graph_group_box.add<GraphWidget>();
|
||||||
cpu_graph.set_graph_color(Color::from_rgb(0x00bb00));
|
cpu_graph.set_max(100);
|
||||||
cpu_graph.text_formatter = [](int value, int) {
|
cpu_graph.set_text_color(Color::Green);
|
||||||
return String::format("%d%%", value);
|
cpu_graph.set_graph_color(Color::from_rgb(0x00bb00));
|
||||||
};
|
cpu_graph.text_formatter = [](int value, int) {
|
||||||
|
return String::format("%d%%", value);
|
||||||
ProcessModel::the().on_new_cpu_data_point = [graph = &cpu_graph](float cpu_percent) {
|
};
|
||||||
graph->add_value(cpu_percent);
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto& memory_graph_group_box = self.add<GUI::GroupBox>("Memory usage");
|
auto& memory_graph_group_box = self.add<GUI::GroupBox>("Memory usage");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue