Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure

Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
rajames
2026-10-01 15:40:09 -04:00
co-authored by Junie
parent 3c709c115b
commit a8b70e88d3
630 changed files with 2646 additions and 2036 deletions
+720
View File
@@ -0,0 +1,720 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
physics_runtime.c - Phase 1 physics runtime scaffolding
Implements the host snapshot shim (POSIX) and provides a
fixed-size analytics heap compatible with the HOLA shared-memory layout.
The implementation favours predictable costs and clear extension points for
future governance-approved features.
*/
#include "physics_runtime.h"
#include "platform_time.h"
/* STARFORTH_MINIMAL: kernel build — skip Linux /proc runtime monitoring.
* Provide stub API so vmcore link succeeds; none of these are called. */
#if defined(STARFORTH_MINIMAL)
int physics_runtime_init(size_t analytics_heap_bytes)
{ (void)analytics_heap_bytes; return 0; }
void physics_runtime_shutdown(void) {}
int physics_analytics_publish_event(uint32_t channel, const void *payload, uint16_t payload_bytes)
{ (void)channel; (void)payload; (void)payload_bytes; return 0; }
int physics_host_snapshot(physics_host_snapshot_t *out)
{ (void)out; return -1; }
#else /* !STARFORTH_MINIMAL — full Linux/POSIX implementation below */
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
/* HISTORICAL: L4Re support has been removed as an active target.
* #ifdef __l4__
* #include <l4/sys/kip.h>
* #include <l4/re/env.h>
* #endif
*/
#include <sched.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
/* ------------------------------------------------------------------------- */
/* Analytics heap internals */
/* ------------------------------------------------------------------------- */
typedef struct physics_runtime_state {
physics_analytics_heap_t heap;
int heap_ready;
uint64_t backend_seq;
} physics_runtime_state_t;
static physics_runtime_state_t g_runtime = {0};
/**
* @brief Round @c value up to the next multiple of @c alignment.
*
* @c alignment must be a power of two. Used internally to satisfy
* cache-line and struct alignment requirements in the analytics heap.
*
* @param value Value to align
* @param alignment Alignment boundary (must be power of two)
* @return Smallest multiple of @c alignment that is >= @c value
*/
static size_t align_up(size_t value, size_t alignment) {
return (value + alignment - 1u) & ~(alignment - 1u);
}
/* HISTORICAL: this whole section (through the matching "!__l4__" comment
* below) was wrapped in `#if !defined(__l4__)` -- these PSI/cgroup helpers
* are Linux-specific and were excluded from L4Re builds. L4Re support has
* been removed as an active target, so the guard (now always-true) has
* been removed too. */
/**
* @brief Parse a floating-point average field from a Linux PSI file line.
*
* Searches @c line for @c tag (e.g. @c "avg10="), reads the following
* floating-point value, and returns it scaled to millipercentage (×1000).
* Returns 0 on parse failure or negative value.
*
* @param line Raw text line from a @c /proc/pressure/ PSI file (cpu, memory, or io)
* @param tag Field prefix to locate (e.g. @c "avg10=")
* @return Scaled value in milli-units, or 0 on failure
*/
static uint32_t parse_avg_milli(const char *line, const char *tag) {
const char *p = strstr(line, tag);
if (!p) return 0;
p += strlen(tag);
errno = 0;
char *end = NULL;
double value = strtod(p, &end);
if (errno != 0 || end == p || value < 0.0) {
return 0;
}
double scaled = value * 1000.0;
if (scaled > (double) UINT32_MAX) scaled = (double) UINT32_MAX;
return (uint32_t)(scaled + 0.5);
}
/**
* @brief Read a Linux Pressure Stall Information (PSI) file.
*
* Parses @c /proc/pressure/{cpu,io,memory} and populates the six output
* pointers with avg10/avg60/avg300 milli-values for both the @c some and
* @c full stall categories. Any output pointer may be NULL to skip that field.
* Returns a bitmask: bit 0 set if @c some line was found, bit 1 if @c full.
*
* @param path Path to PSI file (e.g. @c "/proc/pressure/cpu")
* @param some10 avg10 for the @c some stall category (milli-units), or NULL
* @param some60 avg60 for the @c some stall category, or NULL
* @param some300 avg300 for the @c some stall category, or NULL
* @param full10 avg10 for the @c full stall category, or NULL
* @param full60 avg60 for the @c full stall category, or NULL
* @param full300 avg300 for the @c full stall category, or NULL
* @return Bitmask of parsed sections (0 = none, 1 = some, 2 = full, 3 = both)
*/
static int read_psi_file(const char *path,
uint32_t *some10, uint32_t *some60, uint32_t *some300,
uint32_t *full10, uint32_t *full60, uint32_t *full300) {
FILE *f = fopen(path, "r");
if (!f) {
return 0;
}
char line[256];
int mask = 0;
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "some", 4) == 0) {
if (some10) *some10 = parse_avg_milli(line, "avg10=");
if (some60) *some60 = parse_avg_milli(line, "avg60=");
if (some300) *some300 = parse_avg_milli(line, "avg300=");
mask |= 1;
} else if (strncmp(line, "full", 4) == 0) {
if (full10) *full10 = parse_avg_milli(line, "avg10=");
if (full60) *full60 = parse_avg_milli(line, "avg60=");
if (full300) *full300 = parse_avg_milli(line, "avg300=");
mask |= 2;
}
}
fclose(f);
return mask;
}
/**
* @brief Read aggregate CPU jiffy totals from @c /proc/stat.
*
* Parses the first @c cpu line from @c /proc/stat and sums user, nice, system,
* idle, iowait, irq, softirq, and steal columns into @c *total. The idle
* estimate is @c idle_j + @c iowait. Either output pointer may be NULL.
*
* @param total Output: total jiffy count across all categories
* @param idle Output: idle + iowait jiffies (effective idle)
* @return 0 on success, -1 on file open or parse failure
*/
static int read_proc_stat_totals(uint64_t *total, uint64_t *idle) {
FILE *f = fopen("/proc/stat", "r");
if (!f) {
return -1;
}
char line[256];
if (!fgets(line, sizeof(line), f)) {
fclose(f);
return -1;
}
fclose(f);
unsigned long long user = 0, nice = 0, system = 0, idle_j = 0;
unsigned long long iowait = 0, irq = 0, softirq = 0, steal = 0;
int scanned = sscanf(line, "cpu %llu %llu %llu %llu %llu %llu %llu %llu",
&user, &nice, &system, &idle_j, &iowait, &irq, &softirq, &steal);
if (scanned < 4) {
return -1;
}
unsigned long long total_j = user + nice + system + idle_j + iowait + irq + softirq + steal;
if (total) *total = total_j;
if (idle) *idle = idle_j + iowait;
return 0;
}
/**
* @brief Resolve the cgroup v2 filesystem path for the current process.
*
* Reads @c /proc/self/cgroup, finds the cgroup2 hierarchy entry (the line
* with @c "::" separator), and constructs the full sysfs path under
* @c /sys/fs/cgroup. The root cgroup ("/") maps to @c /sys/fs/cgroup itself.
*
* @param out Output buffer for the resolved path
* @param out_sz Size of @c out in bytes
* @return 0 on success, -1 if the path could not be resolved
*/
static int resolve_cgroup_path(char *out, size_t out_sz) {
FILE *f = fopen("/proc/self/cgroup", "r");
if (!f) {
return -1;
}
char line[512];
int rc = -1;
while (fgets(line, sizeof(line), f)) {
char *sep = strstr(line, "::");
if (!sep) continue;
char *path = sep + 2;
char *newline = strchr(path, '\n');
if (newline) *newline = '\0';
if (*path == '\0' || strcmp(path, "/") == 0) {
if (snprintf(out, out_sz, "%s", "/sys/fs/cgroup") < (int) out_sz) {
rc = 0;
break;
}
} else {
if (snprintf(out, out_sz, "/sys/fs/cgroup%s", path) < (int) out_sz) {
rc = 0;
break;
}
}
}
fclose(f);
return rc;
}
/**
* @brief Read the cumulative CPU usage in microseconds from a cgroup's @c cpu.stat.
*
* Opens @c <cgpath>/cpu.stat and extracts the @c usage_usec field.
* Returns 0 if the file cannot be opened or the field is absent.
*
* @param cgpath Absolute path to the cgroup directory
* @return Cumulative CPU usage in microseconds, or 0 on failure
*/
static uint64_t read_cgroup_cpu_usage_us(const char *cgpath) {
char path[PATH_MAX];
if (snprintf(path, sizeof(path), "%s/cpu.stat", cgpath) >= (int) sizeof(path)) {
return 0;
}
FILE *f = fopen(path, "r");
if (!f) {
return 0;
}
char line[256];
uint64_t value = 0;
while (fgets(line, sizeof(line), f)) {
if (sscanf(line, "usage_usec %" SCNu64, &value) == 1) {
break;
}
}
fclose(f);
return value;
}
/**
* @brief Read current memory usage in bytes from a cgroup's @c memory.current.
*
* Opens @c <cgpath>/memory.current and reads the single integer value.
* Returns 0 if the file cannot be opened or parsed.
*
* @param cgpath Absolute path to the cgroup directory
* @return Current memory usage in bytes, or 0 on failure
*/
static uint64_t read_cgroup_memory_current(const char *cgpath) {
char path[PATH_MAX];
if (snprintf(path, sizeof(path), "%s/memory.current", cgpath) >= (int) sizeof(path)) {
return 0;
}
FILE *f = fopen(path, "r");
if (!f) {
return 0;
}
unsigned long long value = 0;
if (fscanf(f, "%llu", &value) != 1) {
value = 0;
}
fclose(f);
return value;
}
/* end of the section historically wrapped in `#if !defined(__l4__)` */
/**
* @brief Allocate and initialise the HOLA analytics heap.
*
* Allocates a contiguous block of at least @c requested_bytes (or
* @c PHYSICS_ANALYTICS_DEFAULT_HEAP_BYTES if 0), enforcing a minimum that
* accommodates the header plus 512 KB ring buffer. Partitions the block into
* header (cache-line aligned), ring (60%), summary (30%), and scratch regions,
* then populates the @c physics_analytics_header_t with magic, version, and
* offsets. Safe to call repeatedly — returns 0 immediately if already ready.
*
* @param requested_bytes Desired heap size in bytes (0 = use default)
* @return 0 on success, -1 if allocation failed
*/
static int analytics_heap_allocate(size_t requested_bytes) {
if (g_runtime.heap_ready) {
return 0;
}
size_t bytes = requested_bytes;
if (bytes == 0) {
bytes = PHYSICS_ANALYTICS_DEFAULT_HEAP_BYTES;
}
/* Enforce minimum viable size: header + ring + summary */
const size_t min_bytes = align_up(sizeof(physics_analytics_header_t), 64) + (512u * 1024u);
if (bytes < min_bytes) {
bytes = min_bytes;
}
uint8_t *base = NULL;
/* HISTORICAL: this used to be an #if !defined(__l4__)/#else branch --
* both did an identical malloc(), with the L4Re side noting "simple
* malloc is fine for now; future work can switch to dataspace". L4Re
* support has been removed as an active target. */
base = (uint8_t *) malloc(bytes);
if (!base) {
return -1;
}
memset(base, 0, bytes);
physics_analytics_heap_t heap = {0};
heap.base = base;
heap.bytes = bytes;
size_t header_bytes = align_up(sizeof(physics_analytics_header_t), 64);
heap.header = (physics_analytics_header_t *) base;
/* Split remaining space into ring (60%), summary (30%), scratch (remainder) */
size_t remaining = bytes - header_bytes;
size_t ring_bytes = align_up((remaining * 6u) / 10u, 64);
if (ring_bytes > remaining) ring_bytes = remaining;
size_t summary_bytes = remaining > ring_bytes ? align_up((remaining - ring_bytes) * 3u / 4u, 64) : 0;
if (summary_bytes > remaining - ring_bytes) summary_bytes = remaining - ring_bytes;
size_t scratch_bytes = remaining - ring_bytes - summary_bytes;
heap.ring = base + header_bytes;
heap.ring_bytes = ring_bytes;
heap.summary = heap.ring + ring_bytes;
heap.summary_bytes = summary_bytes;
heap.scratch = heap.summary + summary_bytes;
heap.scratch_bytes = scratch_bytes;
/* Populate header */
physics_analytics_header_t *hdr = heap.header;
hdr->magic = HOLA_SHARED_MAGIC;
hdr->version_major = HOLA_PROTOCOL_MAJOR;
hdr->version_minor = HOLA_PROTOCOL_MINOR;
hdr->heap_bytes = (uint32_t) bytes;
hdr->ring_offset = (uint32_t) header_bytes;
hdr->ring_bytes = (uint32_t) ring_bytes;
hdr->summary_offset = (uint32_t)(heap.summary - heap.base);
hdr->summary_bytes = (uint32_t) summary_bytes;
hdr->scratch_offset = (uint32_t)(heap.scratch - heap.base);
hdr->scratch_bytes = (uint32_t) scratch_bytes;
hdr->produce_seq = 0;
hdr->consume_seq = 0;
hdr->write_offset = 0;
hdr->read_offset = 0;
hdr->dropped_events = 0;
hdr->flags = 0;
g_runtime.heap = heap;
g_runtime.heap_ready = 1;
g_runtime.backend_seq = 0;
return 0;
}
/**
* @brief Free the analytics heap and reset all global heap state.
*
* Safe to call when the heap is not ready. After this call,
* @c g_runtime.heap_ready is 0 and the heap pointer is NULL.
*/
static void analytics_heap_release(void) {
if (!g_runtime.heap_ready) {
return;
}
free(g_runtime.heap.base);
g_runtime.heap = (physics_analytics_heap_t) {
0
};
g_runtime.heap_ready = 0;
g_runtime.backend_seq = 0;
}
/**
* @brief Compute free bytes available in the analytics ring buffer.
*
* Assumes single-producer, single-consumer access with no concurrent writers.
* Clamps out-of-range @c write_offset and @c read_offset before computing.
*
* @param hdr Pointer to the analytics heap header
* @return Number of free bytes available for a new event write
*/
static size_t analytics_ring_free(const physics_analytics_header_t *hdr) {
uint32_t write = hdr->write_offset;
uint32_t read = hdr->read_offset;
uint32_t size = hdr->ring_bytes;
if (write >= size) write = 0;
if (read >= size) read = 0;
if (write >= read) {
return (size_t) (size - write + read - 1u);
}
return (size_t) (read - write - 1u);
}
/* ------------------------------------------------------------------------- */
/* Public API */
/* ------------------------------------------------------------------------- */
/**
* @brief Initialise the physics runtime and analytics heap.
*
* Must be called before any other physics_runtime_* function.
* Idempotent — safe to call multiple times; subsequent calls return 0
* immediately without re-allocating.
*
* @param analytics_heap_bytes Desired analytics heap size (0 = default)
* @return 0 on success, -1 on allocation failure
*/
int physics_runtime_init(size_t analytics_heap_bytes) {
return analytics_heap_allocate(analytics_heap_bytes);
}
/**
* @brief Shut down the physics runtime and release the analytics heap.
*
* Safe to call when the runtime was never initialised. After this call
* the analytics heap is gone and no events can be published until
* @c physics_runtime_init() is called again.
*/
void physics_runtime_shutdown(void) {
analytics_heap_release();
}
/**
* @brief Return a pointer to the live analytics heap descriptor.
*
* @return Pointer to the heap descriptor, or NULL if not yet initialised
*/
const physics_analytics_heap_t *physics_analytics_heap_info(void) {
return g_runtime.heap_ready ? &g_runtime.heap : NULL;
}
/**
* @brief Publish an analytics event into the ring buffer.
*
* Writes a @c physics_analytics_event_header_t followed by the payload
* (8-byte padded) into the ring at the current write offset. If insufficient
* space is available, increments @c dropped_events and returns -1. The event
* is timestamped with @c sf_monotonic_ns() at write time.
*
* @param channel Event channel identifier (e.g. @c PHYSICS_ANALYTICS_CHANNEL_HOST_SNAPSHOT)
* @param payload Event payload bytes (must not be NULL)
* @param payload_bytes Length of payload in bytes
* @return 0 on success, -1 if the heap is not ready, payload is NULL, or ring is full
*/
int physics_analytics_publish_event(uint32_t channel, const void *payload, uint16_t payload_bytes) {
if (!g_runtime.heap_ready || !payload) {
return -1;
}
physics_analytics_header_t *hdr = g_runtime.heap.header;
uint8_t *ring = g_runtime.heap.ring;
uint32_t ring_bytes = hdr->ring_bytes;
if (ring_bytes == 0) {
return -1;
}
size_t padded_payload = align_up(payload_bytes, 8);
size_t total_bytes = sizeof(physics_analytics_event_header_t) + padded_payload;
if (total_bytes >= ring_bytes) {
/* Payload cannot fit even into an empty ring */
hdr->dropped_events++;
return -1;
}
size_t free_bytes = analytics_ring_free(hdr);
if (total_bytes > free_bytes) {
hdr->dropped_events++;
return -1;
}
uint32_t write = hdr->write_offset;
if (write >= ring_bytes) {
write = 0;
}
if (write + total_bytes > ring_bytes) {
size_t tail = ring_bytes - write;
memset(ring + write, 0, tail);
write = 0;
}
physics_analytics_event_header_t *evt = (physics_analytics_event_header_t *) (ring + write);
evt->channel = channel;
evt->payload_bytes = payload_bytes;
evt->reserved = 0;
evt->timestamp_ns = sf_monotonic_ns();
memcpy((uint8_t *) (evt + 1), payload, payload_bytes);
if (padded_payload > payload_bytes) {
memset(((uint8_t *) (evt + 1)) + payload_bytes, 0, padded_payload - payload_bytes);
}
write += (uint32_t) total_bytes;
if (write >= ring_bytes) {
write = 0;
}
hdr->write_offset = write;
hdr->produce_seq++;
return 0;
}
/* ------------------------------------------------------------------------- */
/* Host snapshot shim */
/* ------------------------------------------------------------------------- */
/**
* @brief Collect a host system snapshot using POSIX / Linux interfaces.
*
* Populates @c out with monotonic and realtime timestamps, CPU count,
* scheduler policy and priority, load average (scaled × 1000), runnable
* thread estimate, PSI pressure readings, @c /proc/stat CPU jiffy totals,
* and cgroup v2 CPU usage and memory current values. Sets flags in
* @c out->flags to indicate which fields were successfully populated.
*
* @param out Output snapshot struct; zeroed and populated by this function
* @return 0 on success
*/
static int snapshot_posix(physics_host_snapshot_t *out) {
memset(out, 0, sizeof(*out));
out->backend = PHYSICS_HOST_BACKEND_POSIX;
out->monotonic_time_ns = sf_monotonic_ns();
out->realtime_ns = sf_realtime_ns();
long cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
out->cpu_count = (cpu_count > 0) ? (uint32_t) cpu_count : 1u;
/* Scheduler information */
int policy = sched_getscheduler(0);
if (policy >= 0) {
out->scheduler_policy = (uint32_t) policy;
struct sched_param param = {0};
if (sched_getparam(0, &param) == 0) {
out->scheduler_priority = (uint32_t) param.sched_priority;
}
#if defined(_POSIX_PRIORITY_SCHEDULING)
if (policy == SCHED_RR) {
struct timespec ts = {0};
if (sched_rr_get_interval(0, &ts) == 0) {
out->scheduler_quantum_ns = (uint32_t)((ts.tv_sec * 1000000000LL) + ts.tv_nsec);
}
}
#endif
}
/* Load estimate (scaled by 1000) */
#if defined(_GNU_SOURCE)
double loads[3] = {0.0, 0.0, 0.0};
if (getloadavg(loads, 3) > 0) {
out->load_avg_milli = (uint32_t)(loads[0] * 1000.0);
}
#endif
/* Runnable thread estimate: best effort via load average */
if (out->load_avg_milli > 0) {
out->runnable_threads = (out->load_avg_milli + 999u) / 1000u;
}
int psi_mask = 0;
psi_mask |= read_psi_file("/proc/pressure/cpu",
&out->psi_cpu_some_avg10_milli,
&out->psi_cpu_some_avg60_milli,
&out->psi_cpu_some_avg300_milli,
&out->psi_cpu_full_avg10_milli,
&out->psi_cpu_full_avg60_milli,
&out->psi_cpu_full_avg300_milli);
psi_mask |= read_psi_file("/proc/pressure/io",
&out->psi_io_some_avg10_milli,
&out->psi_io_some_avg60_milli,
&out->psi_io_some_avg300_milli,
&out->psi_io_full_avg10_milli,
&out->psi_io_full_avg60_milli,
&out->psi_io_full_avg300_milli);
psi_mask |= read_psi_file("/proc/pressure/memory",
&out->psi_mem_some_avg10_milli,
&out->psi_mem_some_avg60_milli,
&out->psi_mem_some_avg300_milli,
NULL, NULL, NULL);
if (psi_mask) {
out->flags |= PHYSICS_HOST_FLAG_PSI;
}
uint64_t total_jiffies = 0;
uint64_t idle_jiffies = 0;
if (read_proc_stat_totals(&total_jiffies, &idle_jiffies) == 0) {
out->cpu_total_jiffies = total_jiffies;
out->cpu_idle_jiffies = idle_jiffies;
out->flags |= PHYSICS_HOST_FLAG_CPU_STAT;
}
char cgpath[PATH_MAX];
if (resolve_cgroup_path(cgpath, sizeof(cgpath)) == 0) {
uint64_t usage = read_cgroup_cpu_usage_us(cgpath);
uint64_t mem_cur = read_cgroup_memory_current(cgpath);
out->cgroup_cpu_usage_us = usage;
out->cgroup_memory_current_bytes = mem_cur;
if (usage != 0 || mem_cur != 0) {
out->flags |= PHYSICS_HOST_FLAG_CGROUP;
}
}
out->backend_seq = ++g_runtime.backend_seq;
return 0;
}
/* HISTORICAL: an L4Re backend used to sit here, selected ahead of
* snapshot_posix() by physics_host_snapshot() below whenever __l4__ was
* defined. L4Re support has been removed as an active target.
*
* static int snapshot_l4re(physics_host_snapshot_t *out) {
* memset(out, 0, sizeof(*out));
* out->backend = PHYSICS_HOST_BACKEND_L4RE;
* out->monotonic_time_ns = sf_monotonic_ns();
* out->realtime_ns = sf_realtime_ns();
*
* l4_kernel_info_t *kip = l4re_kip();
* (void) kip;
* out->cpu_count = 1u; // TODO: query processor count from KIP once available
*
* out->backend_seq = ++g_runtime.backend_seq;
* return 0;
* }
*/
/**
* @brief Capture a host environment snapshot and publish it to the analytics ring.
*
* Uses the POSIX backend. On success also publishes the snapshot as a
* @c PHYSICS_ANALYTICS_CHANNEL_HOST_SNAPSHOT event to the analytics ring
* buffer (if the heap is initialised).
*
* @param out Output snapshot struct to populate (must not be NULL)
* @return 0 on success, -1 if @c out is NULL or the backend fails
*/
int physics_host_snapshot(physics_host_snapshot_t *out) {
if (!out) {
return -1;
}
int rc;
/* HISTORICAL: an #ifdef __l4__ block here tried snapshot_l4re() first
* and `goto publish` past snapshot_posix() on success. L4Re support
* has been removed as an active target. */
rc = snapshot_posix(out);
if (rc != 0) {
return rc;
}
if (g_runtime.heap_ready) {
physics_analytics_publish_event(PHYSICS_ANALYTICS_CHANNEL_HOST_SNAPSHOT,
out,
(uint16_t) sizeof(*out));
}
return 0;
}
#endif /* STARFORTH_MINIMAL */
/* ------------------------------------------------------------------------- */
/* End */
/* ------------------------------------------------------------------------- */