Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ no-atomic-cas = []
multi-core = []
error-msg = []
defmt = ["dep:defmt", "dep:defmt-rtt"]
metrics = ["hal_api/metrics"]

[build-dependencies]
cbindgen = "0.28.0"
Expand All @@ -53,7 +54,7 @@ rand = "0.8.5"
cfg_aliases = "0.2.1"

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)', 'cfg(metrics)'] }

[profile.dev]
panic = "abort"
Expand Down
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ extern crate cbindgen;
fn main() {
println!("cargo::rerun-if-changed=src");
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-env-changed=METRICS");
if std::env::var("METRICS").map_or(false, |v| v == "true" || v == "1") {
println!("cargo::rustc-cfg=metrics");
}
let out_dir = std::env::var("OUT_DIR").unwrap();

if gen_syscall_match(Path::new("src/syscalls"), Path::new(&out_dir)).is_err() {
Expand Down
6 changes: 6 additions & 0 deletions machine/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@ name = "hal-api"
version = "0.1.0"
edition = "2024"

[features]
metrics = []

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(metrics)'] }

[dependencies]
seq-macro = "0.3.6"
6 changes: 6 additions & 0 deletions machine/api/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
println!("cargo::rerun-if-env-changed=METRICS");
if std::env::var("METRICS").map_or(false, |v| v == "true" || v == "1") {
println!("cargo::rustc-cfg=metrics");
}
}
27 changes: 27 additions & 0 deletions machine/api/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ pub struct Descriptor {
pub fin: Option<FinFn>,
}

/// Per-stack resource snapshot. Available when the `metrics` feature is enabled.
/// Backends that do not override `Stacklike::metrics` return all-zero values.
#[cfg(any(feature = "metrics", metrics))]
#[derive(Debug, Clone, Copy)]
pub struct StackMetrics {
/// Total bytes allocated for this stack.
pub total_bytes: usize,
/// Bytes currently consumed (from stack top down to current SP).
pub used_bytes: usize,
/// Bytes still available for use.
pub free_bytes: usize,
/// Peak bytes ever used since the stack was created (high-water mark).
pub peak_used_bytes: usize,
}

pub trait Stacklike {
type ElemSize: Copy;
type StackPtr;
Expand All @@ -25,6 +40,18 @@ pub trait Stacklike {

fn sp(&self) -> *mut c_void;

/// Returns a metrics snapshot for this stack.
/// Backends that do not implement full metrics tracking return all-zero values.
#[cfg(any(feature = "metrics", metrics))]
fn metrics(&self) -> StackMetrics {
StackMetrics {
total_bytes: 0,
used_bytes: 0,
free_bytes: 0,
peak_used_bytes: 0,
}
}

//fn push_tinit<F, const N: usize>(&mut self, init: &ThreadInitializer<F, N, Self::ElemSize>) -> Result<CtxPtr>;

// Pushes a function context onto the stack, which will be executed when the IRQ returns.
Expand Down
3 changes: 2 additions & 1 deletion machine/cortex-m/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ syn = { version = "2.0.36", features = ["full"] }
[features]
panic-exit = []
panic-uart = []
metrics = ["hal-api/metrics"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)', 'cfg(cortex_m)', 'cfg(disabled)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)', 'cfg(cortex_m)', 'cfg(disabled)', 'cfg(metrics)'] }
5 changes: 5 additions & 0 deletions machine/cortex-m/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ mod vector_table {
///
/// Exits with error code 1 if any critical build step fails
fn main() {
println!("cargo::rerun-if-env-changed=METRICS");
if env::var("METRICS").map_or(false, |v| v == "true" || v == "1") {
println!("cargo::rustc-cfg=metrics");
}

if !hal_builder::check_enabled("cortex-m") || !check_cortex_m() {
return;
}
Expand Down
103 changes: 103 additions & 0 deletions machine/cortex-m/src/native/sched.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ pub struct ArmStack {
sp: StackPtr,
/// The size of the stack
size: NonZero<usize>,
/// High-water mark: largest sp offset ever recorded via set_sp.
#[cfg(any(feature = "metrics", metrics))]
peak_offset: usize,
}

impl ArmStack {
Expand Down Expand Up @@ -178,6 +181,87 @@ impl ArmStack {
}
}

#[cfg(all(test, any(feature = "metrics", metrics)))]
mod metrics_tests {
use super::*;
use core::num::NonZero;
use hal_api::stack::{Descriptor, Stacklike};
use hal_api::mem::PhysAddr;

const STACK_WORDS: usize = 256;

// Each test gets its own static buffer to avoid aliasing between parallel tests.
static mut BUF_A: [u32; STACK_WORDS] = [0u32; STACK_WORDS];
static mut BUF_B: [u32; STACK_WORDS] = [0u32; STACK_WORDS];

fn make_stack(buf: &mut [u32; STACK_WORDS]) -> ArmStack {
let top = unsafe { buf.as_mut_ptr().add(STACK_WORDS) };
extern "C" fn entry() {}
unsafe {
ArmStack::new(Descriptor {
top: PhysAddr::new(top as usize),
size: NonZero::new(STACK_WORDS).unwrap(),
entry,
fin: None,
})
.unwrap()
}
}

#[test]
fn metrics_total_bytes_matches_size() {
let stack = make_stack(unsafe { &mut BUF_A });
let m = stack.metrics();
let expected_total = STACK_WORDS * core::mem::size_of::<u32>();
assert_eq!(m.total_bytes, expected_total);
assert_eq!(m.total_bytes, m.used_bytes + m.free_bytes);
}

#[test]
fn metrics_used_bytes_after_init() {
// After new(), push_irq_ret_fn has consumed FRAME_WORDS (18) words.
let stack = make_stack(unsafe { &mut BUF_A });
let m = stack.metrics();
let word = core::mem::size_of::<u32>();
// Frame is 18 words; we allow for an optional alignment word.
assert!(m.used_bytes >= 18 * word);
assert!(m.used_bytes <= 20 * word);
assert!(m.free_bytes < m.total_bytes);
}

#[test]
fn metrics_peak_starts_at_zero() {
// peak_offset is only updated through set_sp; new() increments sp directly.
let stack = make_stack(unsafe { &mut BUF_A });
assert_eq!(stack.metrics().peak_used_bytes, 0);
}

#[test]
fn metrics_peak_tracks_high_water_mark() {
let mut stack = make_stack(unsafe { &mut BUF_A });
let word = core::mem::size_of::<u32>();

// Simulate two context saves at increasing depths.
let sp_deep = StackPtr { offset: 50 };
stack.set_sp(sp_deep);
assert_eq!(stack.metrics().peak_used_bytes, 50 * word);

let sp_shallow = StackPtr { offset: 20 };
stack.set_sp(sp_shallow);
// Peak must not decrease.
assert_eq!(stack.metrics().peak_used_bytes, 50 * word);
assert_eq!(stack.metrics().used_bytes, 20 * word);
}

#[test]
fn metrics_free_plus_used_equals_total() {
let mut stack = make_stack(unsafe { &mut BUF_B });
stack.set_sp(StackPtr { offset: 100 });
let m = stack.metrics();
assert_eq!(m.used_bytes + m.free_bytes, m.total_bytes);
}
}

impl hal_api::stack::Stacklike for ArmStack {
type ElemSize = u32;
type StackPtr = StackPtr;
Expand All @@ -202,6 +286,8 @@ impl hal_api::stack::Stacklike for ArmStack {
top,
sp: StackPtr { offset: 0 },
size,
#[cfg(any(feature = "metrics", metrics))]
peak_offset: 0,
};

stack.push_irq_ret_fn(entry, ctx, fin)?;
Expand All @@ -217,9 +303,26 @@ impl hal_api::stack::Stacklike for ArmStack {
}

fn set_sp(&mut self, sp: StackPtr) {
#[cfg(any(feature = "metrics", metrics))]
if sp.offset > self.peak_offset {
self.peak_offset = sp.offset;
}
self.sp = sp;
}

#[cfg(any(feature = "metrics", metrics))]
fn metrics(&self) -> hal_api::stack::StackMetrics {
let word = core::mem::size_of::<u32>();
let total_bytes = self.size.get() * word;
let used_bytes = self.sp.offset * word;
hal_api::stack::StackMetrics {
total_bytes,
used_bytes,
free_bytes: total_bytes.saturating_sub(used_bytes),
peak_used_bytes: self.peak_offset * word,
}
Comment on lines +319 to +323
}

fn sp(&self) -> *mut c_void {
self.sp.as_ptr(self.top).as_ptr() as *mut c_void
}
Expand Down
6 changes: 6 additions & 0 deletions options.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ name = "Runtime Symbols"
description = "Enables runtime symbols for debugging. This will increase the binary size by potentially a lot. When enabled e.g. backtraces can display function names."
type = "Boolean"

[debug.metrics]
name = "Metrics"
description = "Enables runtime metrics collection for heap allocator and stack usage. Increases binary size and adds small per-alloc/free overhead."
type = "Boolean"
default = false

[debug.uart]
name = "Debug UART"
description = "Select the UART peripheral to use for debug output."
Expand Down
1 change: 1 addition & 0 deletions presets/stm32l4r5zi_def.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ OSIRIS_MACHINE = "cortex-m"
# Debugging configuration
OSIRIS_DEBUG_UART = "LPUART1"
OSIRIS_DEBUG_RUNTIMESYMBOLS = "false"
METRICS = "false"

# Tuning parameters
OSIRIS_TUNING_ENABLEFPU = "false"
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ mod sync;
mod syscalls;
mod time;

#[cfg(any(feature = "metrics", metrics))]
pub mod metrics;

// Public, for now.
pub mod drivers;
pub mod uapi;
Expand Down
6 changes: 6 additions & 0 deletions src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ pub unsafe fn free(ptr: NonNull<u8>, size: usize) {
unsafe { allocator.free(ptr, size) };
}

/// Returns a metrics snapshot of the global kernel heap.
#[cfg(any(feature = "metrics", metrics))]
pub(crate) fn global_metrics() -> alloc::Metrics {
GLOBAL_ALLOCATOR.lock().metrics()
}

/// Aligns a size to be a multiple of the u128 alignment.
///
/// `size` - The size to align.
Expand Down
46 changes: 46 additions & 0 deletions src/mem/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,52 @@ use crate::error::Result;

pub mod bestfit;

/// Snapshot of allocator resource usage. Available when the `metrics` feature is enabled.
#[cfg(any(feature = "metrics", metrics))]
#[derive(Debug, Clone, Copy, Default)]
pub struct Metrics {
pub total_bytes: usize,
pub free_bytes: usize,
pub free_blocks: usize,
pub alloc_count: u64,
pub free_count: u64,
}

#[cfg(any(feature = "metrics", metrics))]
impl Metrics {
pub const fn new() -> Self {
Self {
total_bytes: 0,
free_bytes: 0,
free_blocks: 0,
alloc_count: 0,
free_count: 0,
}
}

pub fn allocated_bytes(&self) -> usize {
self.total_bytes.saturating_sub(self.free_bytes)
}

pub(crate) fn record_add_range(&mut self, total: usize, free: usize) {
self.total_bytes = self.total_bytes.saturating_add(total);
self.free_bytes = self.free_bytes.saturating_add(free);
self.free_blocks += 1;
}

pub(crate) fn record_alloc(&mut self, consumed_bytes: usize, blocks_removed: usize) {
self.free_bytes = self.free_bytes.saturating_sub(consumed_bytes);
self.free_blocks = self.free_blocks.saturating_sub(blocks_removed);
self.alloc_count += 1;
}

pub(crate) fn record_free(&mut self, added_bytes: usize) {
self.free_bytes = self.free_bytes.saturating_add(added_bytes);
self.free_blocks += 1;
self.free_count += 1;
}
}

#[cfg(target_pointer_width = "64")]
pub const MAX_ADDR: usize = 2_usize.pow(48);

Expand Down
Loading
Loading