feat: graphing in MTK toolkit, kernel exposes CPU time, Processes app Performance tab

This commit is contained in:
2026-05-02 15:26:59 +02:00
parent e98071e97e
commit 3919d166f7
12 changed files with 553 additions and 11 deletions
+243 -6
View File
@@ -15,8 +15,8 @@ extern "C" {
using namespace gui;
static constexpr int INIT_W = 620;
static constexpr int INIT_H = 420;
static constexpr int INIT_W = 760;
static constexpr int INIT_H = 520;
static constexpr int PM_TOOLBAR_H = 36;
static constexpr int PM_TAB_H = 32;
static constexpr int PM_HEADER_H = 24;
@@ -24,6 +24,7 @@ static constexpr int PM_ITEM_H = 28;
static constexpr int PM_POLL_MS = 1000;
static constexpr int PM_MAX_PROCS = 256;
static constexpr int PM_MAX_WINDOWS = 64;
static constexpr int PM_GRAPH_CAP = 120;
static constexpr Color PM_TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
static constexpr Color PM_HEADER_BG = Color::from_rgb(0xF0, 0xF0, 0xF0);
@@ -35,19 +36,28 @@ static constexpr Color PM_DANGER = Color::from_rgb(0xCC, 0x33, 0x33);
static constexpr Color PM_INFO = Color::from_rgb(0x33, 0x66, 0xCC);
enum ProcMgrTab {
PM_TAB_PROCESSES = 0,
PM_TAB_WINDOWS = 1,
PM_TAB_COUNT = 2,
PM_TAB_PROCESSES = 0,
PM_TAB_PERFORMANCE = 1,
PM_TAB_WINDOWS = 2,
PM_TAB_COUNT = 3,
};
static const char* g_tab_labels[PM_TAB_COUNT] = {
"Processes",
"Performance",
"Windows",
};
struct ProcCpuSnapshot {
int pid;
uint64_t cpu_time_ms;
};
struct ProcMgrState {
Montauk::ProcInfo procs[PM_MAX_PROCS];
Montauk::WinInfo windows[PM_MAX_WINDOWS];
Montauk::MemStats mem;
Montauk::SysInfo sys_info;
int proc_count;
int win_count;
int selected;
@@ -57,7 +67,27 @@ struct ProcMgrState {
int active_tab;
int mouse_x;
int mouse_y;
int cpu_count;
int cpu_percent;
int mem_percent;
uint64_t total_process_heap;
uint64_t last_poll_ms;
uint64_t last_cpu_wall_ms;
bool have_cpu_sample;
ProcCpuSnapshot prev_cpu[PM_MAX_PROCS];
int prev_cpu_count;
uint64_t cpu_samples[PM_GRAPH_CAP];
uint64_t mem_samples[PM_GRAPH_CAP];
uint64_t proc_samples[PM_GRAPH_CAP];
uint64_t win_samples[PM_GRAPH_CAP];
uint64_t heap_samples[PM_GRAPH_CAP];
mtk::GraphHistory cpu_history;
mtk::GraphHistory mem_history;
mtk::GraphHistory proc_history;
mtk::GraphHistory win_history;
mtk::GraphHistory heap_history;
};
static WsWindow g_win;
@@ -76,6 +106,50 @@ static mtk::Theme pm_theme() {
return theme;
}
static void init_graph_histories() {
mtk::graph_history_init(&g_pm.cpu_history, g_pm.cpu_samples, PM_GRAPH_CAP);
mtk::graph_history_init(&g_pm.mem_history, g_pm.mem_samples, PM_GRAPH_CAP);
mtk::graph_history_init(&g_pm.proc_history, g_pm.proc_samples, PM_GRAPH_CAP);
mtk::graph_history_init(&g_pm.win_history, g_pm.win_samples, PM_GRAPH_CAP);
mtk::graph_history_init(&g_pm.heap_history, g_pm.heap_samples, PM_GRAPH_CAP);
}
static int parse_positive_int_after_comma(const char* text) {
if (!text) return 0;
const char* p = text;
while (*p && *p != ',') p++;
if (*p == ',') p++;
while (*p && (*p < '0' || *p > '9')) p++;
int value = 0;
while (*p >= '0' && *p <= '9') {
value = value * 10 + (*p - '0');
p++;
}
return value;
}
static int detect_cpu_count() {
Montauk::DevInfo devs[16];
int count = montauk::devlist(devs, 16);
if (count < 0) count = 0;
for (int i = 0; i < count; i++) {
if (devs[i].category != 0) continue;
int cores = parse_positive_int_after_comma(devs[i].detail);
if (cores > 0) return cores;
}
return 1;
}
static void refresh_static_info() {
montauk::get_info(&g_pm.sys_info);
if (g_pm.sys_info.maxProcesses == 0)
g_pm.sys_info.maxProcesses = PM_MAX_PROCS;
g_pm.cpu_count = detect_cpu_count();
if (g_pm.cpu_count <= 0) g_pm.cpu_count = 1;
}
static void format_size(char* buf, uint64_t size) {
if (size < 1024ULL) {
snprintf(buf, 24, "%llu B", (unsigned long long)size);
@@ -169,6 +243,78 @@ static int live_process_count() {
return count;
}
static uint64_t total_process_heap() {
uint64_t total = 0;
for (int i = 0; i < g_pm.proc_count; i++) {
uint8_t state = g_pm.procs[i].state;
if (state == 1 || state == 2 || state == 3)
total += g_pm.procs[i].heapUsed;
}
return total;
}
static int find_prev_cpu_snapshot(int pid) {
for (int i = 0; i < g_pm.prev_cpu_count; i++) {
if (g_pm.prev_cpu[i].pid == pid)
return i;
}
return -1;
}
static uint64_t process_cpu_delta_ms() {
uint64_t delta = 0;
for (int i = 0; i < g_pm.proc_count; i++) {
int prev = find_prev_cpu_snapshot(g_pm.procs[i].pid);
if (prev < 0) continue;
uint64_t old_time = g_pm.prev_cpu[prev].cpu_time_ms;
uint64_t new_time = g_pm.procs[i].cpuTimeMs;
if (new_time >= old_time)
delta += new_time - old_time;
}
return delta;
}
static void store_cpu_snapshot() {
g_pm.prev_cpu_count = gui_min(g_pm.proc_count, PM_MAX_PROCS);
for (int i = 0; i < g_pm.prev_cpu_count; i++) {
g_pm.prev_cpu[i].pid = g_pm.procs[i].pid;
g_pm.prev_cpu[i].cpu_time_ms = g_pm.procs[i].cpuTimeMs;
}
}
static void sample_performance(uint64_t now) {
uint64_t elapsed = g_pm.have_cpu_sample ? now - g_pm.last_cpu_wall_ms : 0;
uint64_t cpu_delta = process_cpu_delta_ms();
if (g_pm.have_cpu_sample && elapsed > 0) {
uint64_t capacity = elapsed * (uint64_t)gui_max(g_pm.cpu_count, 1);
uint64_t percent = capacity > 0 ? (cpu_delta * 100ULL) / capacity : 0;
if (percent > 100ULL) percent = 100ULL;
g_pm.cpu_percent = (int)percent;
} else {
g_pm.cpu_percent = 0;
}
store_cpu_snapshot();
g_pm.have_cpu_sample = true;
g_pm.last_cpu_wall_ms = now;
if (g_pm.mem.totalBytes > 0) {
g_pm.mem_percent = (int)((g_pm.mem.usedBytes * 100ULL) / g_pm.mem.totalBytes);
if (g_pm.mem_percent > 100) g_pm.mem_percent = 100;
} else {
g_pm.mem_percent = 0;
}
g_pm.total_process_heap = total_process_heap();
mtk::graph_history_push(&g_pm.cpu_history, (uint64_t)g_pm.cpu_percent);
mtk::graph_history_push(&g_pm.mem_history, g_pm.mem.usedBytes);
mtk::graph_history_push(&g_pm.proc_history, (uint64_t)live_process_count());
mtk::graph_history_push(&g_pm.win_history, (uint64_t)g_pm.win_count);
mtk::graph_history_push(&g_pm.heap_history, g_pm.total_process_heap);
}
static int find_process_index_by_pid(int pid) {
for (int i = 0; i < g_pm.proc_count; i++) {
if (g_pm.procs[i].pid == pid)
@@ -223,6 +369,9 @@ static bool refresh_state(bool force) {
if (g_pm.win_count < 0) g_pm.win_count = 0;
g_pm.selected_window = find_window_index_by_id(prev_win_id);
montauk::memstats(&g_pm.mem);
sample_performance(now);
clamp_scrolls();
ensure_process_selection_visible();
ensure_window_selection_visible();
@@ -358,7 +507,10 @@ static void render_toolbar(Canvas& c, const mtk::Theme& theme, int fh) {
mtk::widget_state(false, mouse_in_rect(kill_rect), can_kill_selected()), theme);
char status[96];
if (g_pm.active_tab == PM_TAB_PROCESSES) {
if (g_pm.active_tab == PM_TAB_PERFORMANCE) {
snprintf(status, sizeof(status), "CPU %d%%, Memory %d%%",
g_pm.cpu_percent, g_pm.mem_percent);
} else if (g_pm.active_tab == PM_TAB_PROCESSES) {
snprintf(status, sizeof(status), "%d shown, %d live",
g_pm.proc_count, live_process_count());
} else {
@@ -467,6 +619,86 @@ static void render_windows(Canvas& c, const mtk::Theme& theme, int fh) {
theme, style, draw_window_row, nullptr);
}
static uint64_t scale_with_headroom(uint64_t observed, uint64_t floor) {
uint64_t scale = observed > floor ? observed : floor;
if (scale == 0) return 1;
return scale + scale / 4;
}
static void format_memory_value(char* buf, int max) {
char used[24];
char total[24];
format_size(used, g_pm.mem.usedBytes);
format_size(total, g_pm.mem.totalBytes);
snprintf(buf, max, "%s / %s", used, total);
}
static void render_graph(Canvas& c,
const Rect& rect,
const mtk::GraphHistory& history,
uint64_t max_value,
const char* title,
const char* value,
Color line,
const mtk::Theme& theme) {
mtk::GraphStyle style = mtk::make_graph_style(theme, line);
style.background = theme.window_bg;
style.plot_bg = Color::from_rgb(0xFA, 0xFA, 0xFA);
style.grid = mtk::mix(style.plot_bg, line, 16);
style.fill = mtk::lighten(line, 225);
mtk::draw_line_graph(c, rect, history, max_value, title, value, theme, style);
}
static void render_performance(Canvas& c, const mtk::Theme& theme, int fh) {
(void)fh;
Rect body = {0, content_top(), g_win.width, gui_max(g_win.height - content_top(), 0)};
int margin = 12;
int gap = 10;
int col_w = gui_max((body.w - margin * 2 - gap) / 2, 80);
int row_h = gui_max((body.h - margin * 2 - gap * 2) / 3, 72);
int x0 = body.x + margin;
int x1 = x0 + col_w + gap;
int y0 = body.y + margin;
int y1 = y0 + row_h + gap;
int y2 = y1 + row_h + gap;
Rect cpu_rect = {x0, y0, col_w, row_h};
Rect mem_rect = {x1, y0, col_w, row_h};
Rect proc_rect = {x0, y1, col_w, row_h};
Rect heap_rect = {x1, y1, col_w, row_h};
Rect win_rect = {x0, y2, body.w - margin * 2, row_h};
char cpu_value[32];
snprintf(cpu_value, sizeof(cpu_value), "%d%%", g_pm.cpu_percent);
render_graph(c, cpu_rect, g_pm.cpu_history, 100, "CPU", cpu_value,
PM_INFO, theme);
char mem_value[64];
format_memory_value(mem_value, sizeof(mem_value));
render_graph(c, mem_rect, g_pm.mem_history,
g_pm.mem.totalBytes > 0 ? g_pm.mem.totalBytes : 1,
"Memory", mem_value, PM_SUCCESS, theme);
char proc_value[48];
snprintf(proc_value, sizeof(proc_value), "%d live", live_process_count());
render_graph(c, proc_rect, g_pm.proc_history,
g_pm.sys_info.maxProcesses > 0 ? g_pm.sys_info.maxProcesses : PM_MAX_PROCS,
"Processes", proc_value, Color::from_rgb(0x7B, 0x3E, 0xB8), theme);
char heap_value[32];
format_size(heap_value, g_pm.total_process_heap);
uint64_t heap_scale = scale_with_headroom(mtk::graph_history_max(g_pm.heap_history),
1024ULL * 1024ULL);
render_graph(c, heap_rect, g_pm.heap_history, heap_scale,
"Process Heap", heap_value, PM_WARNING, theme);
char win_value[40];
snprintf(win_value, sizeof(win_value), "%d windows", g_pm.win_count);
render_graph(c, win_rect, g_pm.win_history, PM_MAX_WINDOWS,
"Windows", win_value, Color::from_rgb(0x00, 0x88, 0x88), theme);
}
static void render() {
mtk::StandaloneHost host(&g_win);
Canvas c = host.canvas();
@@ -478,6 +710,9 @@ static void render() {
render_tabs(c, theme);
switch (g_pm.active_tab) {
case PM_TAB_PERFORMANCE:
render_performance(c, theme, fh);
break;
case PM_TAB_PROCESSES:
render_processes(c, theme, fh);
break;
@@ -634,6 +869,8 @@ extern "C" void _start() {
montauk::exit(1);
montauk::memset(&g_pm, 0, sizeof(g_pm));
init_graph_histories();
refresh_static_info();
g_pm.selected = -1;
g_pm.selected_window = -1;
g_pm.active_tab = PM_TAB_PROCESSES;