diff --git a/gui/gui_apps.c b/gui/gui_apps.c index 3480a2a..3b7526c 100644 --- a/gui/gui_apps.c +++ b/gui/gui_apps.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include 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; + } +} diff --git a/gui/gui_desktop.c b/gui/gui_desktop.c index 6e7e1c7..2d7a27d 100644 --- a/gui/gui_desktop.c +++ b/gui/gui_desktop.c @@ -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; diff --git a/include/gui/gui_apps.h b/include/gui/gui_apps.h index 12f8d98..fe378c3 100644 --- a/include/gui/gui_apps.h +++ b/include/gui/gui_apps.h @@ -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 diff --git a/include/kernel/cpp_analytics.h b/include/kernel/cpp_analytics.h new file mode 100644 index 0000000..125fd03 --- /dev/null +++ b/include/kernel/cpp_analytics.h @@ -0,0 +1,52 @@ +#ifndef _KERNEL_CPP_ANALYTICS_H +#define _KERNEL_CPP_ANALYTICS_H + +#include +#include +#include + +#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 diff --git a/kernel/cpp/cpp_analytics.cpp b/kernel/cpp/cpp_analytics.cpp new file mode 100644 index 0000000..61cb195 --- /dev/null +++ b/kernel/cpp/cpp_analytics.cpp @@ -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 + +extern "C" { + #include + #include + #include + #include + #include + 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(x); +} + +// ----------------------------------------------------------------------------- +// TimeSeries: a fixed-capacity ring of samples plus on-demand windowed stats. +// ----------------------------------------------------------------------------- +template +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(m_count < static_cast(max) ? m_count : static_cast(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(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(m_ring[i]) - static_cast(mean); + acc += static_cast(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 series; +}; + +// ----------------------------------------------------------------------------- +// The engine singleton. +// ----------------------------------------------------------------------------- +class AnalyticsEngine { +private: + Channel m_ch[CPP_ANALYTICS_CHANNELS]; + Vector 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((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((static_cast(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(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(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(alarm->breaches(0)), + static_cast(alarm->breaches(1)), + static_cast(alarm->breaches(2))); + } + printk("\n"); +} + +} // extern "C" diff --git a/kernel/cpp/cpp_containers.hpp b/kernel/cpp/cpp_containers.hpp index ad1b854..fa86972 100644 --- a/kernel/cpp/cpp_containers.hpp +++ b/kernel/cpp/cpp_containers.hpp @@ -42,6 +42,89 @@ constexpr T&& forward(typename RemoveReference::type&& arg) noexcept { return static_cast(arg); } +// ----------------------------------------------------------------------------- +// Freestanding numeric algorithms (namespaced to avoid clashing with the C +// kmin/kmax macros/inlines the kernel exposes elsewhere). +// ----------------------------------------------------------------------------- +template +constexpr const T& kmin(const T& a, const T& b) { return (a < b) ? a : b; } + +template +constexpr const T& kmax(const T& a, const T& b) { return (a > b) ? a : b; } + +template +constexpr const T& kclamp(const T& v, const T& lo, const T& hi) { + return (v < lo) ? lo : ((v > hi) ? hi : v); +} + +template +constexpr void kswap(T& a, T& b) { + T tmp = move(a); + a = move(b); + b = move(tmp); +} + +// ----------------------------------------------------------------------------- +// Pair (Freestanding two-element tuple) +// ----------------------------------------------------------------------------- +template +struct Pair { + A first; + B second; + + constexpr Pair() : first(), second() {} + constexpr Pair(const A& a, const B& b) : first(a), second(b) {} +}; + +template +constexpr Pair make_pair(const A& a, const B& b) { return Pair(a, b); } + +// ----------------------------------------------------------------------------- +// Optional (Freestanding maybe-value, no heap) +// ----------------------------------------------------------------------------- +template +class Optional { +private: + alignas(T) unsigned char m_storage[sizeof(T)]; + bool m_has; + + T* ptr() { return reinterpret_cast(m_storage); } + const T* ptr() const { return reinterpret_cast(m_storage); } + +public: + constexpr Optional() noexcept : m_has(false) {} + + Optional(const T& value) : m_has(true) { + new (static_cast(m_storage)) T(value); + } + + Optional(const Optional& other) : m_has(other.m_has) { + if (m_has) new (static_cast(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(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) // ----------------------------------------------------------------------------- diff --git a/kernel/cpp/cpp_fallback.c b/kernel/cpp/cpp_fallback.c index 913184f..1746a54 100644 --- a/kernel/cpp/cpp_fallback.c +++ b/kernel/cpp/cpp_fallback.c @@ -1,7 +1,10 @@ // C++ Kernel Subsystem C-Fallback for Non-x86 Cross Targets #include +#include #include +#include #include +#include 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"); +} diff --git a/kernel/main.c b/kernel/main.c index 948c2b6..56acb96 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -83,6 +83,7 @@ #include #include #include +#include #include #include #include @@ -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(); diff --git a/userland/lazybox/lazybox.c b/userland/lazybox/lazybox.c index 5733dac..3f9bae8 100644 --- a/userland/lazybox/lazybox.c +++ b/userland/lazybox/lazybox.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -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"},