feat: graphing in MTK toolkit, kernel exposes CPU time, Processes app Performance tab
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -230,6 +230,7 @@ typedef struct {
|
||||
uint8_t _pad[3];
|
||||
char name[64];
|
||||
uint64_t heap_used;
|
||||
uint64_t cpu_time_ms;
|
||||
} mtk_procinfo;
|
||||
|
||||
/* ====================================================================
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
/* ====================================================================
|
||||
|
||||
Reference in New Issue
Block a user