mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
feat(cpp,gui): C++ Analytics Engine + live GUI Analytics app
Kernel (C++17 freestanding):
- New Analytics Engine (kernel/cpp/cpp_analytics.cpp): templated rolling
time-series (TimeSeries<N>) sampling four live channels -- CPU load %,
memory %, kernel-heap %, network pkt/interval -- with windowed statistics
(min/max/mean/stddev) computed in pure integer math (kernel is -mno-sse, so
no FP; stddev uses a Newton integer isqrt). Demonstrates virtual dispatch
via an Observer pattern (IAnalyticsObserver + a ThresholdAlarmObserver that
counts breaches) and RAII singletons.
- Extended cpp_containers.hpp with Optional<T>, Pair<A,B>, and the kmin/kmax/
kclamp/kswap algorithms.
- Full C fallback (cpp_fallback.c) so the engine is functionally identical on
aarch64/armv8i, which don't build the .cpp path.
- Wired cpp_analytics_init() into boot [8/14]; seeds a few samples so graphs
are populated immediately.
GUI:
- New "Analytics" desktop app (gui/gui_apps.c): a 2x2 grid of live line-charts
backed by the C++ engine, each with gridlines, a soft baseline fill, and
min/avg/max(/stddev) overlays; click a panel to focus one channel full-size,
click again to return. Throttled to ~2 Hz sampling off the compositor's
repaint cadence. Registered on the desktop and in the start menu.
Userland: new `cppstat [samples]` applet dumps the engine's per-channel stats
and Observer breach counters.
Verified in QEMU (x86_64): boots clean on x86_64/aarch64/armv8i; `cppstat`
prints live stats (CPU 6%, Mem 7%, Heap 22%, computed min/avg/max/stddev,
Observer breaches); the GUI Analytics window renders all four graphs with the
Kernel Heap curve visibly climbing 22->55% as windows open, compositor steady
at ~53 FPS.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
This commit is contained in:
1 parent
cd840d5799
commit
5bd18c4733
9 files changed
+762
-2
No files matched your search
+166
@@ -13,6 +13,8 @@
|
||||
#include <kernel/nt/reg.h>
|
||||
#include <lib/string.h>
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <kernel/ktime.h>
|
||||
|
||||
static int simple_atoi(const char* s) {
|
||||
if (!s) return 0;
|
||||
@@ -888,3 +890,167 @@ void gui_app_regedit_launch(int x, int y, int w, int h) {
|
||||
win->handle_event = regedit_event;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 12. Analytics -- live charts backed by the C++ Analytics Engine
|
||||
// =================================================================
|
||||
typedef struct {
|
||||
uint64_t last_sample_ns; // throttle: pull a new sample ~2 Hz
|
||||
int selected; // -1 = grid overview, else single-channel focus
|
||||
} analytics_data_t;
|
||||
|
||||
#define ANALYTICS_SAMPLE_INTERVAL_NS 500000000ULL // 0.5s
|
||||
|
||||
static uint32_t analytics_channel_color(int ch) {
|
||||
switch (ch) {
|
||||
case 0: return GUI_THEME_PRIMARY; // CPU
|
||||
case 1: return GUI_THEME_ACCENT; // Memory
|
||||
case 2: return GUI_THEME_WARNING; // Heap
|
||||
case 3: return GUI_THEME_SUCCESS; // Network
|
||||
default: return GUI_THEME_TEXT_MUTED;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw one channel's line graph inside the rectangle (gx,gy,gw,gh).
|
||||
static void analytics_draw_graph(int ch, int gx, int gy, int gw, int gh, bool detailed) {
|
||||
uint32_t color = analytics_channel_color(ch);
|
||||
|
||||
// Panel background + border
|
||||
gui_gfx_fill_rect(gx, gy, gw, gh, GUI_THEME_BG_DARK);
|
||||
gui_gfx_draw_rect(gx, gy, gw, gh, GUI_THEME_BORDER);
|
||||
|
||||
// Header: name + last value
|
||||
uint32_t mn, mx, avg, last, sd;
|
||||
cpp_analytics_get_stats(ch, &mn, &mx, &avg, &last, &sd);
|
||||
uint32_t scale = cpp_analytics_channel_scale(ch);
|
||||
if (scale == 0) scale = 1;
|
||||
|
||||
char hdr[48];
|
||||
snprintf(hdr, sizeof(hdr), "%s", cpp_analytics_channel_name(ch));
|
||||
gui_gfx_draw_string(gx + 6, gy + 5, hdr, color);
|
||||
char val[24];
|
||||
snprintf(val, sizeof(val), "%u%s", last, cpp_analytics_channel_unit(ch));
|
||||
gui_gfx_draw_string(gx + gw - 8 * (int)strlen(val) - 6, gy + 5, val, GUI_THEME_TEXT_MAIN);
|
||||
|
||||
// Plot area
|
||||
int px = gx + 6;
|
||||
int py = gy + 20;
|
||||
int pw = gw - 12;
|
||||
int ph = gh - (detailed ? 40 : 34);
|
||||
if (ph < 8) ph = 8;
|
||||
|
||||
// Horizontal gridlines (25/50/75%)
|
||||
for (int g = 1; g < 4; g++) {
|
||||
int gyl = py + (ph * g) / 4;
|
||||
gui_gfx_draw_line(px, gyl, px + pw, gyl, GUI_THEME_BG_ELEVATED);
|
||||
}
|
||||
|
||||
// Fetch the series and draw it as a connected line with a soft fill.
|
||||
uint32_t series[CPP_ANALYTICS_HISTORY];
|
||||
int n = cpp_analytics_get_series(ch, series, CPP_ANALYTICS_HISTORY);
|
||||
if (n >= 1) {
|
||||
int prev_x = 0, prev_y = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int x = px + (n > 1 ? (pw * i) / (n - 1) : pw);
|
||||
uint32_t v = series[i];
|
||||
if (v > scale) v = scale;
|
||||
int y = py + ph - (int)(((uint64_t)v * ph) / scale);
|
||||
// vertical fill to baseline
|
||||
gui_gfx_draw_line(x, y, x, py + ph, (color & 0x00FFFFFF) | 0x30000000);
|
||||
if (i > 0) gui_gfx_draw_line(prev_x, prev_y, x, y, color);
|
||||
prev_x = x; prev_y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer stats
|
||||
char foot[64];
|
||||
if (detailed) {
|
||||
snprintf(foot, sizeof(foot), "min %u avg %u max %u sd %u", mn, avg, mx, sd);
|
||||
} else {
|
||||
snprintf(foot, sizeof(foot), "min %u avg %u max %u", mn, avg, mx);
|
||||
}
|
||||
gui_gfx_draw_string(gx + 6, gy + gh - 12, foot, GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
|
||||
static void analytics_paint(gui_window_t* win) {
|
||||
analytics_data_t* ad = (analytics_data_t*)win->user_data;
|
||||
if (!ad) return;
|
||||
|
||||
// Throttled live sampling driven by the compositor's repaint cadence.
|
||||
uint64_t now = ktime_ns();
|
||||
if (now - ad->last_sample_ns >= ANALYTICS_SAMPLE_INTERVAL_NS) {
|
||||
cpp_analytics_sample();
|
||||
ad->last_sample_ns = now;
|
||||
}
|
||||
|
||||
int cx = win->x + 10;
|
||||
int top = win->y + GUI_TITLEBAR_HEIGHT + 4;
|
||||
|
||||
char title[72];
|
||||
snprintf(title, sizeof(title), "C++ Analytics Engine %llu samples",
|
||||
(unsigned long long)cpp_analytics_sample_count());
|
||||
gui_gfx_draw_string_16_shadow(cx, top, title, GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
|
||||
gui_gfx_draw_string(cx, top + 20, "Templated time-series + Observer pattern (kernel/cpp)", GUI_THEME_TEXT_MUTED);
|
||||
|
||||
int grid_top = top + 38;
|
||||
int nch = cpp_analytics_channel_count();
|
||||
|
||||
if (ad->selected >= 0 && ad->selected < nch) {
|
||||
// Focused single-channel view.
|
||||
analytics_draw_graph(ad->selected, cx, grid_top,
|
||||
win->width - 20, win->height - (grid_top - win->y) - 10, true);
|
||||
gui_gfx_draw_string(cx + 6, win->y + win->height - 12,
|
||||
"(click to return to overview)", GUI_THEME_TEXT_DIM);
|
||||
} else {
|
||||
// 2x2 overview grid.
|
||||
int gw = (win->width - 20 - 8) / 2;
|
||||
int gh = (win->height - (grid_top - win->y) - 10 - 8) / 2;
|
||||
if (gw < 60) gw = 60;
|
||||
if (gh < 40) gh = 40;
|
||||
for (int i = 0; i < nch && i < 4; i++) {
|
||||
int col = i % 2, row = i / 2;
|
||||
analytics_draw_graph(i, cx + col * (gw + 8), grid_top + row * (gh + 8), gw, gh, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void analytics_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
analytics_data_t* ad = (analytics_data_t*)win->user_data;
|
||||
if (!ad) return;
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE) {
|
||||
if (win->user_data) { kfree(win->user_data); win->user_data = NULL; }
|
||||
return;
|
||||
}
|
||||
if (ev->type != GUI_EVENT_MOUSE_DOWN) return;
|
||||
|
||||
if (ad->selected >= 0) {
|
||||
ad->selected = -1; // any click leaves focus mode
|
||||
return;
|
||||
}
|
||||
// Overview: figure out which quadrant was clicked and focus it.
|
||||
int grid_top_rel = GUI_TITLEBAR_HEIGHT + 4 + 38;
|
||||
int gw = (win->width - 20 - 8) / 2;
|
||||
int gh = (win->height - grid_top_rel - 10 - 8) / 2;
|
||||
if (gw < 1 || gh < 1) return;
|
||||
int rx = ev->rel_x - 10;
|
||||
int ry = ev->rel_y - grid_top_rel;
|
||||
if (rx < 0 || ry < 0) return;
|
||||
int col = rx / (gw + 8);
|
||||
int row = ry / (gh + 8);
|
||||
if (col > 1) col = 1;
|
||||
if (row > 1) row = 1;
|
||||
int idx = row * 2 + col;
|
||||
if (idx >= 0 && idx < cpp_analytics_channel_count()) ad->selected = idx;
|
||||
}
|
||||
|
||||
void gui_app_analytics_launch(int x, int y, int w, int h) {
|
||||
gui_window_t* win = gui_wm_create_window("Analytics", x, y, (w > 0) ? w : 560, (h > 0) ? h : 400);
|
||||
if (win) {
|
||||
analytics_data_t* ad = (analytics_data_t*)kzalloc(sizeof(analytics_data_t));
|
||||
if (ad) { ad->selected = -1; ad->last_sample_ns = 0; }
|
||||
win->user_data = ad;
|
||||
win->paint = analytics_paint;
|
||||
win->handle_event = analytics_event;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -63,14 +63,15 @@ static const desktop_app_t g_apps[] = {
|
||||
{ "RegEdit", GUI_ICON_SETTINGS, gui_app_regedit_launch, 300, 90, 480, 360 },
|
||||
{ "Clock", GUI_ICON_CLOCK, gui_app_clock_launch, 460, 120, 280, 260 },
|
||||
{ "Settings", GUI_ICON_SETTINGS, gui_app_settings_launch, 160, 60, 400, 320 },
|
||||
{ "Analytics", GUI_ICON_MONITOR, gui_app_analytics_launch,320, 70, 560, 400 },
|
||||
{ "About", GUI_ICON_INFO, gui_app_about_launch, 240, 140, 330, 170 },
|
||||
};
|
||||
#define APP_COUNT ((int)(sizeof(g_apps) / sizeof(g_apps[0])))
|
||||
|
||||
// Which of the above appear on the desktop itself.
|
||||
// Indices into g_apps[]: Terminal, Files, Monitor, Kernel Log, KTest, Life,
|
||||
// Rust Lab, Settings. (Objects/RegEdit live in the start menu.)
|
||||
static const int g_desktop_icons[] = { 0, 1, 2, 4, 5, 9, 10, 14 };
|
||||
// Rust Lab, Settings, Analytics. (Objects/RegEdit live in the start menu.)
|
||||
static const int g_desktop_icons[] = { 0, 1, 2, 4, 5, 9, 10, 14, 15 };
|
||||
#define DESKTOP_ICON_COUNT ((int)(sizeof(g_desktop_icons) / sizeof(g_desktop_icons[0])))
|
||||
|
||||
static bool g_start_menu_open = false;
|
||||
|
||||
@@ -13,5 +13,6 @@ void gui_app_life_launch(int x, int y, int w, int h);
|
||||
void gui_app_rustlab_launch(int x, int y, int w, int h);
|
||||
void gui_app_objbrowse_launch(int x, int y, int w, int h);
|
||||
void gui_app_regedit_launch(int x, int y, int w, int h);
|
||||
void gui_app_analytics_launch(int x, int y, int w, int h);
|
||||
|
||||
#endif // _GUI_APPS_H
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifndef _KERNEL_CPP_ANALYTICS_H
|
||||
#define _KERNEL_CPP_ANALYTICS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// The C++ analytics engine keeps a rolling time-series per channel and computes
|
||||
// online statistics (min/max/mean/variance via Welford) over it. Channels are:
|
||||
// 0 = CPU load % 1 = Memory used %
|
||||
// 2 = Kernel heap % 3 = Network packets/interval
|
||||
#define CPP_ANALYTICS_CHANNELS 4
|
||||
#define CPP_ANALYTICS_HISTORY 64
|
||||
|
||||
// Bring up the engine (registers channels + an example observer). Idempotent.
|
||||
void cpp_analytics_init(void);
|
||||
|
||||
// Pull one live sample from the kernel (metrics/pmm/heap/NIC) into every
|
||||
// channel. Call this periodically (the GUI Analytics app and the `cppstat`
|
||||
// applet both drive it). Returns the total number of samples taken so far.
|
||||
uint64_t cpp_analytics_sample(void);
|
||||
|
||||
// Copy up to `max` most-recent values of a channel into `out` (oldest first,
|
||||
// newest last). Returns the number written.
|
||||
int cpp_analytics_get_series(int channel, uint32_t* out, int max);
|
||||
|
||||
// Fetch the online statistics for a channel. Any out pointer may be NULL.
|
||||
void cpp_analytics_get_stats(int channel, uint32_t* out_min, uint32_t* out_max,
|
||||
uint32_t* out_avg, uint32_t* out_last,
|
||||
uint32_t* out_stddev);
|
||||
|
||||
// Human-readable channel name / unit suffix and the current scale ceiling used
|
||||
// for graphing (e.g. 100 for a percentage channel).
|
||||
const char* cpp_analytics_channel_name(int channel);
|
||||
const char* cpp_analytics_channel_unit(int channel);
|
||||
uint32_t cpp_analytics_channel_scale(int channel);
|
||||
|
||||
int cpp_analytics_channel_count(void);
|
||||
uint64_t cpp_analytics_sample_count(void);
|
||||
|
||||
// Print a textual dashboard (used by the `cppstat` lazybox applet).
|
||||
void cpp_analytics_dump(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _KERNEL_CPP_ANALYTICS_H
|
||||
@@ -0,0 +1,323 @@
|
||||
// C++ Kernel Analytics Engine for SUB-OS.
|
||||
//
|
||||
// A freestanding C++17 telemetry pipeline that demonstrates templates, virtual
|
||||
// dispatch (the Observer pattern) and RAII inside the kernel while doing real
|
||||
// work: it samples live system metrics (CPU, memory, heap, network) into rolling
|
||||
// per-channel time-series and computes windowed statistics (min / max / mean /
|
||||
// standard deviation) using pure integer arithmetic -- the kernel is built with
|
||||
// -mno-sse, so no floating point is available.
|
||||
//
|
||||
// The GUI "Analytics" app and the `cppstat` shell applet both render the series
|
||||
// this engine maintains.
|
||||
|
||||
#include "cpp_containers.hpp"
|
||||
#include <kernel/cpp_analytics.h>
|
||||
|
||||
extern "C" {
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/metrics.h>
|
||||
#include <mm/pmm.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <lib/string.h>
|
||||
void cpp_call_global_constructors(void);
|
||||
}
|
||||
|
||||
namespace kernel {
|
||||
|
||||
// Integer square root (Newton's method) -- for standard deviation without FP.
|
||||
static uint32_t isqrt64(uint64_t n) {
|
||||
if (n == 0) return 0;
|
||||
uint64_t x = n, y = (x + 1) / 2;
|
||||
while (y < x) { x = y; y = (x + n / x) / 2; }
|
||||
return static_cast<uint32_t>(x);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// TimeSeries<N>: a fixed-capacity ring of samples plus on-demand windowed stats.
|
||||
// -----------------------------------------------------------------------------
|
||||
template<size_t N>
|
||||
class TimeSeries {
|
||||
private:
|
||||
uint32_t m_ring[N];
|
||||
size_t m_head = 0; // index of the next write
|
||||
size_t m_count = 0; // number of valid samples (<= N)
|
||||
uint64_t m_total = 0; // all-time sample count (for the running average)
|
||||
uint64_t m_sum_all = 0;
|
||||
|
||||
public:
|
||||
void push(uint32_t v) {
|
||||
m_ring[m_head] = v;
|
||||
m_head = (m_head + 1) % N;
|
||||
if (m_count < N) m_count++;
|
||||
m_total++;
|
||||
m_sum_all += v;
|
||||
}
|
||||
|
||||
size_t count() const { return m_count; }
|
||||
size_t capacity() const { return N; }
|
||||
uint64_t total_samples() const { return m_total; }
|
||||
|
||||
// Copy the window oldest-first into out[], returning how many were written.
|
||||
int copy(uint32_t* out, int max) const {
|
||||
int n = static_cast<int>(m_count < static_cast<size_t>(max) ? m_count : static_cast<size_t>(max));
|
||||
// The oldest element sits at (m_head - m_count) modulo N.
|
||||
size_t start = (m_head + N - m_count) % N;
|
||||
for (int i = 0; i < n; i++) out[i] = m_ring[(start + i) % N];
|
||||
return n;
|
||||
}
|
||||
|
||||
uint32_t last() const {
|
||||
if (m_count == 0) return 0;
|
||||
return m_ring[(m_head + N - 1) % N];
|
||||
}
|
||||
|
||||
void stats(uint32_t& mn, uint32_t& mx, uint32_t& avg, uint32_t& stddev) const {
|
||||
if (m_count == 0) { mn = mx = avg = stddev = 0; return; }
|
||||
uint32_t lo = 0xFFFFFFFFu, hi = 0;
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < m_count; i++) {
|
||||
uint32_t v = m_ring[i];
|
||||
if (v < lo) lo = v;
|
||||
if (v > hi) hi = v;
|
||||
sum += v;
|
||||
}
|
||||
uint32_t mean = static_cast<uint32_t>(sum / m_count);
|
||||
// Windowed variance = mean of squared deviations (integer).
|
||||
uint64_t acc = 0;
|
||||
for (size_t i = 0; i < m_count; i++) {
|
||||
int64_t d = static_cast<int64_t>(m_ring[i]) - static_cast<int64_t>(mean);
|
||||
acc += static_cast<uint64_t>(d * d);
|
||||
}
|
||||
mn = lo; mx = hi; avg = mean;
|
||||
stddev = isqrt64(acc / m_count);
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Observer pattern: subscribers are notified of every sample on every channel.
|
||||
// -----------------------------------------------------------------------------
|
||||
class IAnalyticsObserver {
|
||||
public:
|
||||
virtual ~IAnalyticsObserver() = default;
|
||||
virtual void on_sample(int channel, uint32_t value) = 0;
|
||||
};
|
||||
|
||||
// A concrete observer that counts threshold breaches per channel -- a small but
|
||||
// real use of virtual dispatch (e.g. "CPU load crossed 90%").
|
||||
class ThresholdAlarmObserver : public IAnalyticsObserver {
|
||||
private:
|
||||
uint32_t m_threshold[CPP_ANALYTICS_CHANNELS];
|
||||
uint64_t m_breaches[CPP_ANALYTICS_CHANNELS];
|
||||
|
||||
public:
|
||||
ThresholdAlarmObserver() {
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) { m_threshold[i] = 90; m_breaches[i] = 0; }
|
||||
}
|
||||
void set_threshold(int ch, uint32_t t) {
|
||||
if (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) m_threshold[ch] = t;
|
||||
}
|
||||
void on_sample(int channel, uint32_t value) override {
|
||||
if (channel < 0 || channel >= CPP_ANALYTICS_CHANNELS) return;
|
||||
if (value >= m_threshold[channel]) m_breaches[channel]++;
|
||||
}
|
||||
uint64_t breaches(int ch) const {
|
||||
return (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) ? m_breaches[ch] : 0;
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// A single metric channel: name, unit, graph scale and its time-series.
|
||||
// -----------------------------------------------------------------------------
|
||||
struct Channel {
|
||||
const char* name;
|
||||
const char* unit;
|
||||
uint32_t scale; // graph ceiling (0 => auto-scale to max)
|
||||
TimeSeries<CPP_ANALYTICS_HISTORY> series;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The engine singleton.
|
||||
// -----------------------------------------------------------------------------
|
||||
class AnalyticsEngine {
|
||||
private:
|
||||
Channel m_ch[CPP_ANALYTICS_CHANNELS];
|
||||
Vector<IAnalyticsObserver*> m_observers; // non-owning
|
||||
ThresholdAlarmObserver* m_alarm = nullptr;
|
||||
uint64_t m_samples = 0;
|
||||
|
||||
// Previous cumulative packet counter, to derive a per-interval rate.
|
||||
uint64_t m_prev_net_pkts = 0;
|
||||
bool m_have_prev = false;
|
||||
|
||||
AnalyticsEngine() {
|
||||
m_ch[0].name = "CPU Load"; m_ch[0].unit = "%"; m_ch[0].scale = 100;
|
||||
m_ch[1].name = "Memory"; m_ch[1].unit = "%"; m_ch[1].scale = 100;
|
||||
m_ch[2].name = "Kernel Heap";m_ch[2].unit = "%"; m_ch[2].scale = 100;
|
||||
m_ch[3].name = "Net Traffic";m_ch[3].unit = "pkt/s"; m_ch[3].scale = 0; // auto
|
||||
}
|
||||
|
||||
public:
|
||||
static AnalyticsEngine& instance() {
|
||||
static AnalyticsEngine* s = nullptr;
|
||||
if (!s) s = new AnalyticsEngine();
|
||||
return *s;
|
||||
}
|
||||
|
||||
void add_observer(IAnalyticsObserver* o) { if (o) m_observers.push_back(o); }
|
||||
|
||||
void ensure_alarm() {
|
||||
if (!m_alarm) {
|
||||
m_alarm = new ThresholdAlarmObserver();
|
||||
add_observer(m_alarm);
|
||||
}
|
||||
}
|
||||
|
||||
ThresholdAlarmObserver* alarm() { return m_alarm; }
|
||||
|
||||
void notify(int ch, uint32_t v) {
|
||||
for (auto* o : m_observers) if (o) o->on_sample(ch, v);
|
||||
}
|
||||
|
||||
uint64_t sample() {
|
||||
system_metrics_t m;
|
||||
metrics_sample(&m);
|
||||
|
||||
uint32_t cpu = m.cpu_user_pct + m.cpu_system_pct;
|
||||
if (cpu > 100) cpu = 100;
|
||||
|
||||
uint32_t mem = 0;
|
||||
if (m.mem_total_kb) mem = static_cast<uint32_t>((m.mem_used_kb * 100) / m.mem_total_kb);
|
||||
if (mem > 100) mem = 100;
|
||||
|
||||
uint32_t heap = 0;
|
||||
size_t ht = heap_get_total_bytes();
|
||||
if (ht) heap = static_cast<uint32_t>((static_cast<uint64_t>(heap_get_used_bytes()) * 100) / ht);
|
||||
if (heap > 100) heap = 100;
|
||||
|
||||
uint64_t pkts = m.net_rx_packets + m.net_tx_packets;
|
||||
uint32_t rate = 0;
|
||||
if (m_have_prev && pkts >= m_prev_net_pkts) rate = static_cast<uint32_t>(pkts - m_prev_net_pkts);
|
||||
m_prev_net_pkts = pkts;
|
||||
m_have_prev = true;
|
||||
|
||||
uint32_t vals[CPP_ANALYTICS_CHANNELS] = { cpu, mem, heap, rate };
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) {
|
||||
m_ch[i].series.push(vals[i]);
|
||||
notify(i, vals[i]);
|
||||
}
|
||||
m_samples++;
|
||||
return m_samples;
|
||||
}
|
||||
|
||||
int channels() const { return CPP_ANALYTICS_CHANNELS; }
|
||||
uint64_t sample_count() const { return m_samples; }
|
||||
|
||||
Channel* channel(int i) {
|
||||
if (i < 0 || i >= CPP_ANALYTICS_CHANNELS) return nullptr;
|
||||
return &m_ch[i];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace kernel
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// C-FFI bridge
|
||||
// -----------------------------------------------------------------------------
|
||||
using kernel::AnalyticsEngine;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void cpp_analytics_init(void) {
|
||||
cpp_call_global_constructors();
|
||||
auto& e = AnalyticsEngine::instance();
|
||||
e.ensure_alarm();
|
||||
// Seed a few samples so the graphs are not empty on first paint.
|
||||
for (int i = 0; i < 4; i++) e.sample();
|
||||
printk(KERN_INFO "CXX: Analytics Engine online (%d channels, %d-sample history, Observer pattern)\n",
|
||||
CPP_ANALYTICS_CHANNELS, CPP_ANALYTICS_HISTORY);
|
||||
}
|
||||
|
||||
uint64_t cpp_analytics_sample(void) {
|
||||
return AnalyticsEngine::instance().sample();
|
||||
}
|
||||
|
||||
int cpp_analytics_get_series(int channel, uint32_t* out, int max) {
|
||||
if (!out || max <= 0) return 0;
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
if (!c) return 0;
|
||||
return c->series.copy(out, max);
|
||||
}
|
||||
|
||||
void cpp_analytics_get_stats(int channel, uint32_t* out_min, uint32_t* out_max,
|
||||
uint32_t* out_avg, uint32_t* out_last,
|
||||
uint32_t* out_stddev) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
uint32_t mn = 0, mx = 0, avg = 0, sd = 0, last = 0;
|
||||
if (c) { c->series.stats(mn, mx, avg, sd); last = c->series.last(); }
|
||||
if (out_min) *out_min = mn;
|
||||
if (out_max) *out_max = mx;
|
||||
if (out_avg) *out_avg = avg;
|
||||
if (out_last) *out_last = last;
|
||||
if (out_stddev) *out_stddev = sd;
|
||||
}
|
||||
|
||||
const char* cpp_analytics_channel_name(int channel) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
return c ? c->name : "";
|
||||
}
|
||||
|
||||
const char* cpp_analytics_channel_unit(int channel) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
return c ? c->unit : "";
|
||||
}
|
||||
|
||||
uint32_t cpp_analytics_channel_scale(int channel) {
|
||||
auto* c = AnalyticsEngine::instance().channel(channel);
|
||||
if (!c) return 100;
|
||||
if (c->scale) return c->scale;
|
||||
// Auto-scale: ceil to a sensible ceiling above the observed max.
|
||||
uint32_t mn, mx, avg, sd;
|
||||
c->series.stats(mn, mx, avg, sd);
|
||||
uint32_t ceil = mx + mx / 4 + 1;
|
||||
return ceil < 10 ? 10 : ceil;
|
||||
}
|
||||
|
||||
int cpp_analytics_channel_count(void) {
|
||||
return AnalyticsEngine::instance().channels();
|
||||
}
|
||||
|
||||
uint64_t cpp_analytics_sample_count(void) {
|
||||
return AnalyticsEngine::instance().sample_count();
|
||||
}
|
||||
|
||||
void cpp_analytics_dump(void) {
|
||||
auto& e = AnalyticsEngine::instance();
|
||||
e.sample();
|
||||
printk(ANSI_BRIGHT_CYAN "=================================================================\n" ANSI_RESET);
|
||||
printk(ANSI_BRIGHT_CYAN " SUB-OS C++ Analytics Engine (samples: %llu)\n" ANSI_RESET,
|
||||
static_cast<unsigned long long>(e.sample_count()));
|
||||
printk(ANSI_BRIGHT_CYAN "=================================================================\n" ANSI_RESET);
|
||||
printk(ANSI_BOLD "%-14s %6s %6s %6s %6s %8s\n" ANSI_RESET,
|
||||
"CHANNEL", "LAST", "MIN", "AVG", "MAX", "STDDEV");
|
||||
printk("-----------------------------------------------------------------\n");
|
||||
for (int i = 0; i < e.channels(); i++) {
|
||||
uint32_t mn, mx, avg, last, sd;
|
||||
cpp_analytics_get_stats(i, &mn, &mx, &avg, &last, &sd);
|
||||
char label[24];
|
||||
snprintf(label, sizeof(label), "%s(%s)", cpp_analytics_channel_name(i), cpp_analytics_channel_unit(i));
|
||||
printk(ANSI_BRIGHT_YELLOW "%-14s" ANSI_RESET " %6u %6u " ANSI_BRIGHT_GREEN "%6u" ANSI_RESET " %6u %8u\n",
|
||||
label, last, mn, avg, mx, sd);
|
||||
}
|
||||
|
||||
auto* alarm = e.alarm();
|
||||
if (alarm) {
|
||||
printk("-----------------------------------------------------------------\n");
|
||||
printk(" Threshold alarm breaches (Observer): CPU=%llu MEM=%llu HEAP=%llu\n",
|
||||
static_cast<unsigned long long>(alarm->breaches(0)),
|
||||
static_cast<unsigned long long>(alarm->breaches(1)),
|
||||
static_cast<unsigned long long>(alarm->breaches(2)));
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -42,6 +42,89 @@ constexpr T&& forward(typename RemoveReference<T>::type&& arg) noexcept {
|
||||
return static_cast<T&&>(arg);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Freestanding numeric algorithms (namespaced to avoid clashing with the C
|
||||
// kmin/kmax macros/inlines the kernel exposes elsewhere).
|
||||
// -----------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
constexpr const T& kmin(const T& a, const T& b) { return (a < b) ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
constexpr const T& kmax(const T& a, const T& b) { return (a > b) ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
constexpr const T& kclamp(const T& v, const T& lo, const T& hi) {
|
||||
return (v < lo) ? lo : ((v > hi) ? hi : v);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr void kswap(T& a, T& b) {
|
||||
T tmp = move(a);
|
||||
a = move(b);
|
||||
b = move(tmp);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Pair (Freestanding two-element tuple)
|
||||
// -----------------------------------------------------------------------------
|
||||
template<typename A, typename B>
|
||||
struct Pair {
|
||||
A first;
|
||||
B second;
|
||||
|
||||
constexpr Pair() : first(), second() {}
|
||||
constexpr Pair(const A& a, const B& b) : first(a), second(b) {}
|
||||
};
|
||||
|
||||
template<typename A, typename B>
|
||||
constexpr Pair<A, B> make_pair(const A& a, const B& b) { return Pair<A, B>(a, b); }
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Optional (Freestanding maybe-value, no heap)
|
||||
// -----------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
class Optional {
|
||||
private:
|
||||
alignas(T) unsigned char m_storage[sizeof(T)];
|
||||
bool m_has;
|
||||
|
||||
T* ptr() { return reinterpret_cast<T*>(m_storage); }
|
||||
const T* ptr() const { return reinterpret_cast<const T*>(m_storage); }
|
||||
|
||||
public:
|
||||
constexpr Optional() noexcept : m_has(false) {}
|
||||
|
||||
Optional(const T& value) : m_has(true) {
|
||||
new (static_cast<void*>(m_storage)) T(value);
|
||||
}
|
||||
|
||||
Optional(const Optional& other) : m_has(other.m_has) {
|
||||
if (m_has) new (static_cast<void*>(m_storage)) T(*other.ptr());
|
||||
}
|
||||
|
||||
~Optional() { reset(); }
|
||||
|
||||
Optional& operator=(const Optional& other) {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
m_has = other.m_has;
|
||||
if (m_has) new (static_cast<void*>(m_storage)) T(*other.ptr());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (m_has) { ptr()->~T(); m_has = false; }
|
||||
}
|
||||
|
||||
bool has_value() const noexcept { return m_has; }
|
||||
explicit operator bool() const noexcept { return m_has; }
|
||||
|
||||
T& value() { return *ptr(); }
|
||||
const T& value() const { return *ptr(); }
|
||||
T value_or(const T& fallback) const { return m_has ? *ptr() : fallback; }
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// UniquePtr (Freestanding Smart Pointer RAII)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// C++ Kernel Subsystem C-Fallback for Non-x86 Cross Targets
|
||||
#include <kernel/cpp_kernel.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <kernel/printk.h>
|
||||
#include <kernel/metrics.h>
|
||||
#include <mm/pmm.h>
|
||||
#include <mm/kmalloc.h>
|
||||
|
||||
int cpp_kernel_init(void) {
|
||||
printk(KERN_INFO "CXX: C++ Kernel Subsystem Bridge online (Cross-Architecture Mode)\n");
|
||||
@@ -45,3 +48,121 @@ int cpp_run_benchmarks(void) {
|
||||
printk(" UniquePtr RAII : " ANSI_BRIGHT_GREEN "PASSED\n\n" ANSI_RESET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Analytics engine C fallback (functionally equivalent to the C++ engine so the
|
||||
// GUI Analytics app and `cppstat` work identically on ARM targets).
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint32_t fb_ring[CPP_ANALYTICS_CHANNELS][CPP_ANALYTICS_HISTORY];
|
||||
static int fb_head[CPP_ANALYTICS_CHANNELS];
|
||||
static int fb_count[CPP_ANALYTICS_CHANNELS];
|
||||
static uint64_t fb_samples;
|
||||
static uint64_t fb_prev_pkts;
|
||||
static bool fb_have_prev;
|
||||
static const char* fb_names[CPP_ANALYTICS_CHANNELS] = { "CPU Load", "Memory", "Kernel Heap", "Net Traffic" };
|
||||
static const char* fb_units[CPP_ANALYTICS_CHANNELS] = { "%", "%", "%", "pkt/s" };
|
||||
static uint32_t fb_scale[CPP_ANALYTICS_CHANNELS] = { 100, 100, 100, 0 };
|
||||
|
||||
static uint32_t fb_isqrt(uint64_t n) {
|
||||
if (n == 0) return 0;
|
||||
uint64_t x = n, y = (x + 1) / 2;
|
||||
while (y < x) { x = y; y = (x + n / x) / 2; }
|
||||
return (uint32_t)x;
|
||||
}
|
||||
|
||||
void cpp_analytics_init(void) {
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) { fb_head[i] = 0; fb_count[i] = 0; }
|
||||
fb_samples = 0; fb_have_prev = false; fb_prev_pkts = 0;
|
||||
for (int i = 0; i < 4; i++) cpp_analytics_sample();
|
||||
printk(KERN_INFO "CXX: Analytics Engine online (Cross-Arch Fallback, %d channels)\n",
|
||||
CPP_ANALYTICS_CHANNELS);
|
||||
}
|
||||
|
||||
uint64_t cpp_analytics_sample(void) {
|
||||
system_metrics_t m;
|
||||
metrics_sample(&m);
|
||||
uint32_t cpu = m.cpu_user_pct + m.cpu_system_pct; if (cpu > 100) cpu = 100;
|
||||
uint32_t mem = m.mem_total_kb ? (uint32_t)((m.mem_used_kb * 100) / m.mem_total_kb) : 0;
|
||||
if (mem > 100) mem = 100;
|
||||
uint32_t heap = 0; size_t ht = heap_get_total_bytes();
|
||||
if (ht) heap = (uint32_t)(((uint64_t)heap_get_used_bytes() * 100) / ht);
|
||||
if (heap > 100) heap = 100;
|
||||
uint64_t pkts = m.net_rx_packets + m.net_tx_packets;
|
||||
uint32_t rate = (fb_have_prev && pkts >= fb_prev_pkts) ? (uint32_t)(pkts - fb_prev_pkts) : 0;
|
||||
fb_prev_pkts = pkts; fb_have_prev = true;
|
||||
|
||||
uint32_t vals[CPP_ANALYTICS_CHANNELS] = { cpu, mem, heap, rate };
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) {
|
||||
fb_ring[i][fb_head[i]] = vals[i];
|
||||
fb_head[i] = (fb_head[i] + 1) % CPP_ANALYTICS_HISTORY;
|
||||
if (fb_count[i] < CPP_ANALYTICS_HISTORY) fb_count[i]++;
|
||||
}
|
||||
fb_samples++;
|
||||
return fb_samples;
|
||||
}
|
||||
|
||||
int cpp_analytics_get_series(int ch, uint32_t* out, int max) {
|
||||
if (ch < 0 || ch >= CPP_ANALYTICS_CHANNELS || !out || max <= 0) return 0;
|
||||
int n = fb_count[ch] < max ? fb_count[ch] : max;
|
||||
int start = (fb_head[ch] + CPP_ANALYTICS_HISTORY - fb_count[ch]) % CPP_ANALYTICS_HISTORY;
|
||||
for (int i = 0; i < n; i++) out[i] = fb_ring[ch][(start + i) % CPP_ANALYTICS_HISTORY];
|
||||
return n;
|
||||
}
|
||||
|
||||
void cpp_analytics_get_stats(int ch, uint32_t* mn_o, uint32_t* mx_o,
|
||||
uint32_t* avg_o, uint32_t* last_o, uint32_t* sd_o) {
|
||||
uint32_t mn = 0, mx = 0, avg = 0, sd = 0, last = 0;
|
||||
if (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS && fb_count[ch] > 0) {
|
||||
uint32_t lo = 0xFFFFFFFFu, hi = 0; uint64_t sum = 0;
|
||||
for (int i = 0; i < fb_count[ch]; i++) {
|
||||
uint32_t v = fb_ring[ch][i];
|
||||
if (v < lo) lo = v;
|
||||
if (v > hi) hi = v;
|
||||
sum += v;
|
||||
}
|
||||
avg = (uint32_t)(sum / fb_count[ch]);
|
||||
uint64_t acc = 0;
|
||||
for (int i = 0; i < fb_count[ch]; i++) {
|
||||
int64_t d = (int64_t)fb_ring[ch][i] - (int64_t)avg; acc += (uint64_t)(d * d);
|
||||
}
|
||||
mn = lo; mx = hi; sd = fb_isqrt(acc / fb_count[ch]);
|
||||
last = fb_ring[ch][(fb_head[ch] + CPP_ANALYTICS_HISTORY - 1) % CPP_ANALYTICS_HISTORY];
|
||||
}
|
||||
if (mn_o) *mn_o = mn;
|
||||
if (mx_o) *mx_o = mx;
|
||||
if (avg_o) *avg_o = avg;
|
||||
if (last_o) *last_o = last;
|
||||
if (sd_o) *sd_o = sd;
|
||||
}
|
||||
|
||||
const char* cpp_analytics_channel_name(int ch) {
|
||||
return (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) ? fb_names[ch] : "";
|
||||
}
|
||||
const char* cpp_analytics_channel_unit(int ch) {
|
||||
return (ch >= 0 && ch < CPP_ANALYTICS_CHANNELS) ? fb_units[ch] : "";
|
||||
}
|
||||
uint32_t cpp_analytics_channel_scale(int ch) {
|
||||
if (ch < 0 || ch >= CPP_ANALYTICS_CHANNELS) return 100;
|
||||
if (fb_scale[ch]) return fb_scale[ch];
|
||||
uint32_t mn, mx, avg, last, sd; cpp_analytics_get_stats(ch, &mn, &mx, &avg, &last, &sd);
|
||||
uint32_t ceil = mx + mx / 4 + 1; return ceil < 10 ? 10 : ceil;
|
||||
}
|
||||
int cpp_analytics_channel_count(void) { return CPP_ANALYTICS_CHANNELS; }
|
||||
uint64_t cpp_analytics_sample_count(void) { return fb_samples; }
|
||||
|
||||
void cpp_analytics_dump(void) {
|
||||
cpp_analytics_sample();
|
||||
printk(ANSI_BRIGHT_CYAN "=== SUB-OS C++ Analytics Engine (samples: %llu) ===\n" ANSI_RESET,
|
||||
(unsigned long long)fb_samples);
|
||||
printk(ANSI_BOLD "%-14s %6s %6s %6s %6s %8s\n" ANSI_RESET,
|
||||
"CHANNEL", "LAST", "MIN", "AVG", "MAX", "STDDEV");
|
||||
for (int i = 0; i < CPP_ANALYTICS_CHANNELS; i++) {
|
||||
uint32_t mn, mx, avg, last, sd;
|
||||
cpp_analytics_get_stats(i, &mn, &mx, &avg, &last, &sd);
|
||||
char label[24];
|
||||
snprintf(label, sizeof(label), "%s(%s)", fb_names[i], fb_units[i]);
|
||||
printk(ANSI_BRIGHT_YELLOW "%-14s" ANSI_RESET " %6u %6u " ANSI_BRIGHT_GREEN "%6u" ANSI_RESET " %6u %8u\n",
|
||||
label, last, mn, avg, mx, sd);
|
||||
}
|
||||
printk("\n");
|
||||
}
|
||||
@@ -83,6 +83,7 @@
|
||||
#include <kernel/rust.h>
|
||||
#include <kernel/sub_lang.h>
|
||||
#include <kernel/cpp_kernel.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <net/filter.h>
|
||||
#include <net/http.h>
|
||||
#include <net/ssh.h>
|
||||
@@ -200,6 +201,7 @@ static void subos_modular_core_boot(void) {
|
||||
boot_logo_splash_progress(55, "Starting Rust, C++ and SUB-Lang engines");
|
||||
rust_kernel_init();
|
||||
cpp_kernel_init();
|
||||
cpp_analytics_init();
|
||||
sub_kernel_init();
|
||||
prng_seed(0, 0);
|
||||
certs_init();
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <kernel/ktime.h>
|
||||
#include <kernel/sub_lang.h>
|
||||
#include <kernel/cpp_kernel.h>
|
||||
#include <kernel/cpp_analytics.h>
|
||||
#include <userland/snake.h>
|
||||
#include <ipc/shm_posix.h>
|
||||
#include <net/dns_cache.h>
|
||||
@@ -1328,6 +1329,15 @@ static int applet_cppbench(int argc, char** argv) {
|
||||
return cpp_run_benchmarks();
|
||||
}
|
||||
|
||||
static int applet_cppstat(int argc, char** argv) {
|
||||
int n = (argc >= 2) ? atoi(argv[1]) : 1;
|
||||
if (n < 1) n = 1;
|
||||
if (n > 32) n = 32;
|
||||
for (int i = 0; i < n; i++) cpp_analytics_sample();
|
||||
cpp_analytics_dump();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applet_shm(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
posix_shm_dump();
|
||||
@@ -2629,6 +2639,7 @@ static const lazybox_applet_t applets[] = {
|
||||
{"cpptest", applet_cpptest, "cpptest", "C++ polymorphism & new/del", "System"},
|
||||
{"cppdevs", applet_cppdevs, "cppdevs", "C++ OOP device tree", "System"},
|
||||
{"cppbench", applet_cppbench, "cppbench", "C++ OOP benchmark suite", "System"},
|
||||
{"cppstat", applet_cppstat, "cppstat [samples]", "C++ analytics engine stats", "System"},
|
||||
{"shm", applet_shm, "shm", "POSIX /dev/shm shared memory", "System"},
|
||||
{"dnscache", applet_dnscache, "dnscache [-f]", "DNS resolver cache & metrics", "Network"},
|
||||
{"beep", applet_beep, "beep [freq/jingle] [ms]", "PC speaker & HDA tone synth", "Sound"},
|
||||
|
||||
Reference in new issue
Block a user