diff --git a/kernel/src/Api/Process.hpp b/kernel/src/Api/Process.hpp index 39dc95f..aa1d6b6 100644 --- a/kernel/src/Api/Process.hpp +++ b/kernel/src/Api/Process.hpp @@ -107,6 +107,7 @@ namespace Montauk { buf[count].name[j] = '\0'; } buf[count].heapUsed = Sched::g_allocatedPages[i] * 0x1000; + buf[count].cpuTimeMs = proc->cpuTimeMs; count++; } return count; diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index 3a8e4b9..0e35615 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -407,6 +407,7 @@ namespace Montauk { uint8_t _pad[3]; char name[64]; uint64_t heapUsed; // heapNext - UserHeapBase (bytes) + uint64_t cpuTimeMs; // accumulated scheduler runtime }; // Bluetooth scan result (returned by SYS_BTSCAN) diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 622c17b..8069011 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -163,6 +163,7 @@ namespace Sched { processTable[i].stackBase = 0; processTable[i].entryPoint = 0; processTable[i].sliceRemaining = 0; + processTable[i].cpuTimeMs = 0; processTable[i].pml4Phys = 0; processTable[i].kernelStackTop = 0; processTable[i].userStackTop = 0; @@ -353,6 +354,7 @@ namespace Sched { proc.stackBase = (uint64_t)kernelStackBase; proc.entryPoint = entry; proc.sliceRemaining = TimeSliceMs; + proc.cpuTimeMs = 0; proc.pml4Phys = pml4Phys; proc.kernelStackTop = kernelStackTop; proc.userStackTop = UserStackTop - 8; @@ -638,6 +640,8 @@ namespace Sched { return; } + processTable[slot].cpuTimeMs += elapsedMs; + // Check if another CPU requested this process be killed. // We are on the CPU running it, so ExitProcess is safe here. if (processTable[slot].killPending) { diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index 3b6030b..58ee9ff 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -42,6 +42,7 @@ namespace Sched { uint64_t stackBase; // Bottom of allocated kernel stack (lowest address) uint64_t entryPoint; uint64_t sliceRemaining; // Milliseconds left in current time slice + uint64_t cpuTimeMs; // Accumulated scheduler runtime in milliseconds uint64_t pml4Phys; // Physical address of per-process PML4 uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL) uint64_t userStackTop; // User-space stack top diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 06a0cb4..c5d7237 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -377,6 +377,7 @@ namespace Montauk { uint8_t _pad[3]; char name[64]; uint64_t heapUsed; // heapNext - UserHeapBase (bytes) + uint64_t cpuTimeMs; // accumulated scheduler runtime }; struct MemStats { diff --git a/programs/include/gui/mtk.hpp b/programs/include/gui/mtk.hpp index fa8d743..dabfbfd 100644 --- a/programs/include/gui/mtk.hpp +++ b/programs/include/gui/mtk.hpp @@ -8,5 +8,6 @@ #include "gui/mtk/theme.hpp" #include "gui/mtk/widgets.hpp" +#include "gui/mtk/graph.hpp" #include "gui/mtk/table.hpp" #include "gui/mtk/hosts.hpp" diff --git a/programs/include/gui/mtk/graph.hpp b/programs/include/gui/mtk/graph.hpp new file mode 100644 index 0000000..d503820 --- /dev/null +++ b/programs/include/gui/mtk/graph.hpp @@ -0,0 +1,290 @@ +/* + * graph.hpp + * Montauk Toolkit graph helpers + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once + +#include "gui/mtk/widgets.hpp" + +namespace gui::mtk { + +struct GraphHistory { + uint64_t* samples; + int capacity; + int count; + int head; +}; + +struct GraphStyle { + Color background; + Color border; + Color plot_bg; + Color grid; + Color label; + Color value; + Color line; + Color fill; + int radius; + int padding; + int grid_x; + int grid_y; + int line_thickness; + bool fill_area; +}; + +inline GraphStyle make_graph_style(const Theme& theme, Color line) { + return { + theme.window_bg, + theme.border, + theme.surface_alt, + mix(theme.surface_alt, line, 18), + theme.text_subtle, + theme.text, + line, + lighten(line, 218), + theme.radius_md, + 8, + 4, + 4, + 2, + true, + }; +} + +inline void graph_history_init(GraphHistory* history, uint64_t* samples, int capacity) { + if (!history) return; + history->samples = samples; + history->capacity = capacity; + history->count = 0; + history->head = 0; +} + +inline void graph_history_clear(GraphHistory* history) { + if (!history) return; + history->count = 0; + history->head = 0; +} + +inline void graph_history_push(GraphHistory* history, uint64_t value) { + if (!history || !history->samples || history->capacity <= 0) return; + history->samples[history->head] = value; + history->head = (history->head + 1) % history->capacity; + if (history->count < history->capacity) + history->count++; +} + +inline uint64_t graph_history_at(const GraphHistory& history, int index) { + if (!history.samples || history.capacity <= 0 || + index < 0 || index >= history.count) { + return 0; + } + + int start = history.head - history.count; + while (start < 0) start += history.capacity; + return history.samples[(start + index) % history.capacity]; +} + +inline uint64_t graph_history_max(const GraphHistory& history) { + uint64_t max_value = 0; + for (int i = 0; i < history.count; i++) { + uint64_t value = graph_history_at(history, i); + if (value > max_value) max_value = value; + } + return max_value; +} + +inline int graph_sample_x(const Rect& plot, int index, int count) { + if (count <= 1) return plot.x + plot.w - 1; + return plot.x + (int)(((int64_t)index * (plot.w - 1)) / (count - 1)); +} + +inline int graph_sample_y(const Rect& plot, uint64_t sample, uint64_t max_value) { + if (plot.h <= 1) return plot.y; + if (max_value == 0) max_value = 1; + if (sample > max_value) sample = max_value; + uint64_t scaled = (sample * (uint64_t)(plot.h - 1)) / max_value; + return plot.y + plot.h - 1 - (int)scaled; +} + +inline void graph_draw_point(Canvas& c, + const Rect& clip, + int x, + int y, + Color color, + int thickness) { + thickness = gui_max(thickness, 1); + int radius = thickness / 2; + for (int py = y - radius; py <= y - radius + thickness - 1; py++) { + for (int px = x - radius; px <= x - radius + thickness - 1; px++) { + if (clip.contains(px, py)) + c.put_pixel(px, py, color); + } + } +} + +inline void graph_draw_line(Canvas& c, + const Rect& clip, + int x0, + int y0, + int x1, + int y1, + Color color, + int thickness) { + int dx = gui_abs(x1 - x0); + int sx = x0 < x1 ? 1 : -1; + int dy = -gui_abs(y1 - y0); + int sy = y0 < y1 ? 1 : -1; + int err = dx + dy; + + for (;;) { + graph_draw_point(c, clip, x0, y0, color, thickness); + if (x0 == x1 && y0 == y1) break; + int e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x0 += sx; + } + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } +} + +inline void graph_fill_segment(Canvas& c, + const Rect& clip, + int x0, + int y0, + int x1, + int y1, + Color color) { + int bottom = clip.y + clip.h - 1; + if (x0 == x1) { + int y = gui_min(y0, y1); + if (clip.contains(x0, y)) + c.vline(x0, y, bottom - y + 1, color); + return; + } + + if (x1 < x0) { + int tx = x0; x0 = x1; x1 = tx; + int ty = y0; y0 = y1; y1 = ty; + } + + for (int x = x0; x <= x1; x++) { + if (x < clip.x || x >= clip.x + clip.w) continue; + int y = y0 + (int)(((int64_t)(y1 - y0) * (x - x0)) / (x1 - x0)); + if (y < clip.y) y = clip.y; + if (y > bottom) y = bottom; + c.vline(x, y, bottom - y + 1, color); + } +} + +inline void draw_line_graph(Canvas& c, + const Rect& bounds, + const GraphHistory& history, + uint64_t max_value, + const char* title, + const char* value_label, + const Theme& theme, + const GraphStyle& style) { + if (bounds.empty()) return; + + draw_rounded_frame(c, bounds, style.radius, style.background, style.border); + + int fh = system_font_height(); + int pad = gui_max(style.padding, 4); + int title_y = bounds.y + pad; + int title_x = bounds.x + pad; + + if (title && title[0]) + c.text(title_x, title_y, title, style.label); + + if (value_label && value_label[0]) { + int value_w = text_width(value_label); + int value_x = bounds.x + bounds.w - pad - value_w; + if (value_x > title_x) + c.text(value_x, title_y, value_label, style.value); + } + + Rect plot = { + bounds.x + pad, + bounds.y + pad + fh + 7, + bounds.w - pad * 2, + bounds.h - pad * 2 - fh - 7 + }; + if (plot.empty()) return; + + c.fill_rect(plot.x, plot.y, plot.w, plot.h, style.plot_bg); + + int sample_count = history.count; + if (sample_count > 0) { + if (max_value == 0) + max_value = graph_history_max(history); + if (max_value == 0) + max_value = 1; + + if (style.fill_area && sample_count > 1) { + int prev_x = graph_sample_x(plot, 0, sample_count); + int prev_y = graph_sample_y(plot, graph_history_at(history, 0), max_value); + for (int i = 1; i < sample_count; i++) { + int x = graph_sample_x(plot, i, sample_count); + int y = graph_sample_y(plot, graph_history_at(history, i), max_value); + graph_fill_segment(c, plot, prev_x, prev_y, x, y, style.fill); + prev_x = x; + prev_y = y; + } + } + } + + if (style.grid_y > 0) { + for (int i = 1; i < style.grid_y; i++) { + int y = plot.y + (plot.h * i) / style.grid_y; + c.hline(plot.x, y, plot.w, style.grid); + } + } + + if (style.grid_x > 0) { + for (int i = 1; i < style.grid_x; i++) { + int x = plot.x + (plot.w * i) / style.grid_x; + c.vline(x, plot.y, plot.h, style.grid); + } + } + + c.rect(plot.x, plot.y, plot.w, plot.h, style.border); + + if (sample_count == 1) { + int x = graph_sample_x(plot, 0, sample_count); + int y = graph_sample_y(plot, graph_history_at(history, 0), max_value); + graph_draw_point(c, plot, x, y, style.line, style.line_thickness + 1); + } else if (sample_count > 1) { + int prev_x = graph_sample_x(plot, 0, sample_count); + int prev_y = graph_sample_y(plot, graph_history_at(history, 0), max_value); + for (int i = 1; i < sample_count; i++) { + int x = graph_sample_x(plot, i, sample_count); + int y = graph_sample_y(plot, graph_history_at(history, i), max_value); + graph_draw_line(c, plot, prev_x, prev_y, x, y, + style.line, style.line_thickness); + prev_x = x; + prev_y = y; + } + } + + (void)theme; +} + +inline void draw_line_graph(Canvas& c, + const Rect& bounds, + const GraphHistory& history, + uint64_t max_value, + const char* title, + const char* value_label, + const Theme& theme, + Color line) { + GraphStyle style = make_graph_style(theme, line); + draw_line_graph(c, bounds, history, max_value, title, value_label, theme, style); +} + +} // namespace gui::mtk diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index 9afd213..3ba78d5 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -230,6 +230,7 @@ typedef struct { uint8_t _pad[3]; char name[64]; uint64_t heap_used; + uint64_t cpu_time_ms; } mtk_procinfo; /* ==================================================================== diff --git a/programs/src/procmgr/main.cpp b/programs/src/procmgr/main.cpp index e1527cc..b35162c 100644 --- a/programs/src/procmgr/main.cpp +++ b/programs/src/procmgr/main.cpp @@ -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; diff --git a/template/docs/syscalls.md b/template/docs/syscalls.md index a8e91fa..7b688d2 100644 --- a/template/docs/syscalls.md +++ b/template/docs/syscalls.md @@ -41,9 +41,12 @@ int proclist(ProcInfo* buf, int max); // List all processes (re **`ProcInfo` struct:** ```cpp struct ProcInfo { - int pid; - char name[32]; - // ... additional fields + int32_t pid; + int32_t parentPid; + uint8_t state; + char name[64]; + uint64_t heapUsed; + uint64_t cpuTimeMs; }; ``` diff --git a/template/sysroot/include/Api/Syscall.hpp b/template/sysroot/include/Api/Syscall.hpp index 8d41e79..a941755 100644 --- a/template/sysroot/include/Api/Syscall.hpp +++ b/template/sysroot/include/Api/Syscall.hpp @@ -310,10 +310,11 @@ namespace Montauk { struct ProcInfo { int32_t pid; int32_t parentPid; - uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated + uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Blocked, 4=Terminated uint8_t _pad[3]; char name[64]; uint64_t heapUsed; // heapNext - UserHeapBase (bytes) + uint64_t cpuTimeMs; // accumulated scheduler runtime }; struct MemStats { diff --git a/template/sysroot/include/libc/montauk.h b/template/sysroot/include/libc/montauk.h index 0e3b087..177d4dd 100644 --- a/template/sysroot/include/libc/montauk.h +++ b/template/sysroot/include/libc/montauk.h @@ -199,10 +199,11 @@ typedef struct { typedef struct { int32_t pid; int32_t parent_pid; - uint8_t state; /* 0=Free, 1=Ready, 2=Running, 3=Terminated */ + uint8_t state; /* 0=Free, 1=Ready, 2=Running, 3=Blocked, 4=Terminated */ uint8_t _pad[3]; char name[64]; uint64_t heap_used; + uint64_t cpu_time_ms; } mtk_procinfo; /* ====================================================================