diff --git a/awkernel_async_lib/Cargo.toml b/awkernel_async_lib/Cargo.toml index 1abcf8e9c..931a6fa14 100644 --- a/awkernel_async_lib/Cargo.toml +++ b/awkernel_async_lib/Cargo.toml @@ -45,3 +45,4 @@ no_preempt = [] spinlock = ["awkernel_lib/spinlock"] clippy = [] perf = [] +period-index-propagation = ["perf"] diff --git a/awkernel_async_lib/src/dag.rs b/awkernel_async_lib/src/dag.rs index 498df9728..4763cb1ae 100644 --- a/awkernel_async_lib/src/dag.rs +++ b/awkernel_async_lib/src/dag.rs @@ -67,6 +67,13 @@ use crate::{ time_interval::interval, Attribute, MultipleReceiver, MultipleSender, VectorToPublishers, VectorToSubscribers, }; + +#[cfg(feature = "period-index-propagation")] +use crate::task::perf::{ + get_period_index, increment_period_index, record_sink_exec_end_timestamp, + record_subscribe_timestamp, update_cycle_end_timestamp, update_cycle_start_timestamp, +}; + use alloc::{ borrow::Cow, boxed::Box, @@ -926,10 +933,43 @@ where Args::create_subscribers(subscribe_topic_names, Attribute::default()); loop { - let args: <::Subscribers as MultipleReceiver>::Item = - subscribers.recv_all().await; - let results = f(args); - publishers.send_all(results).await; + #[cfg(feature = "period-index-propagation")] + { + let (args, period_index): ( + <::Subscribers as MultipleReceiver>::Item, + u32, + ) = subscribers.recv_all_with_period_index().await; + + // [end] pubsub communication latency + let end = awkernel_lib::time::Time::now().uptime().as_nanos() as u64; + record_subscribe_timestamp( + period_index as usize, + end, + 1, + dag_info.node_id, + dag_info.dag_id, + ); + + let results = f(args); + publishers + .send_all_with_period_index( + results, + 1, + period_index as usize, + dag_info.node_id, + dag_info.dag_id, + ) + .await; + } + + #[cfg(not(feature = "period-index-propagation"))] + { + let args: <::Subscribers as MultipleReceiver>::Item = + subscribers.recv_all().await; + + let results = f(args); + publishers.send_all(results).await; + } } }; @@ -966,13 +1006,37 @@ where Attribute::default(), ); - let mut interval = interval(period); + let mut interval = interval(period, dag_info.dag_id); // Consume the first tick here to start the loop's main body without an initial delay. interval.tick().await; loop { - let results = f(); - publishers.send_all(results).await; + #[cfg(feature = "period-index-propagation")] + { + let index = get_period_index(dag_info.dag_id) as usize; + if index != 0 { + // [start] cycle deviation index >= 1 + let release_time = awkernel_lib::time::Time::now().uptime().as_nanos() as u64; + update_cycle_start_timestamp(index, release_time, dag_info.dag_id); + } + let results = f(); + publishers + .send_all_with_period_index( + results, + 0, + index, + dag_info.node_id, + dag_info.dag_id, + ) + .await; + increment_period_index(dag_info.dag_id); + } + + #[cfg(not(feature = "period-index-propagation"))] + { + let results = f(); + publishers.send_all(results).await; + } #[cfg(feature = "perf")] periodic_measure(); @@ -1006,8 +1070,42 @@ where Args::create_subscribers(subscribe_topic_names, Attribute::default()); loop { - let args: ::Item = subscribers.recv_all().await; - f(args); + #[cfg(feature = "period-index-propagation")] + { + let (args, period_index): (::Item, u32) = + subscribers.recv_all_with_period_index().await; + + // [end] pubsub communication latency + let end = awkernel_lib::time::Time::now().uptime().as_nanos() as u64; + record_subscribe_timestamp( + period_index as usize, + end, + 2, + dag_info.node_id, + dag_info.dag_id, + ); + + let timenow = awkernel_lib::time::Time::now().uptime().as_nanos() as u64; + if period_index != 0 { + update_cycle_end_timestamp(period_index as usize, timenow, dag_info.dag_id); + } + + f(args); + let sink_exec_end = awkernel_lib::time::Time::now().uptime().as_nanos() as u64; + record_sink_exec_end_timestamp( + period_index as usize, + sink_exec_end, + dag_info.node_id, + dag_info.dag_id, + ); + } + + #[cfg(not(feature = "period-index-propagation"))] + { + let args: ::Item = + subscribers.recv_all().await; + f(args); + } } }; diff --git a/awkernel_async_lib/src/pubsub.rs b/awkernel_async_lib/src/pubsub.rs index c216bae0b..f6bbddda3 100644 --- a/awkernel_async_lib/src/pubsub.rs +++ b/awkernel_async_lib/src/pubsub.rs @@ -52,11 +52,16 @@ use core::{ use futures::Future; use pin_project::pin_project; +#[cfg(feature = "period-index-propagation")] +use crate::task::perf::record_publish_timestamp; + /// Data and timestamp. #[derive(Clone)] pub struct Data { pub timestamp: awkernel_lib::time::Time, pub data: T, + #[cfg(feature = "period-index-propagation")] + pub period_index: u32, } /// Publisher. @@ -260,6 +265,8 @@ struct Sender<'a, T: 'static + Send> { subscribers: VecDeque>, state: SenderState, timestamp: awkernel_lib::time::Time, + #[cfg(feature = "period-index-propagation")] + period_index: u32, } enum SenderState { @@ -276,8 +283,16 @@ impl<'a, T: Send> Sender<'a, T> { subscribers: Default::default(), state: SenderState::Start, timestamp: awkernel_lib::time::Time::now(), + #[cfg(feature = "period-index-propagation")] + period_index: 0, } } + + #[cfg(feature = "period-index-propagation")] + pub(super) fn with_period_index(mut self, index: u32) -> Self { + self.period_index = index; + self + } } impl Future for Sender<'_, T> @@ -309,6 +324,8 @@ where if let Err(data) = guard.push(Data { timestamp: awkernel_lib::time::Time::now(), data: data.clone(), + #[cfg(feature = "period-index-propagation")] + period_index: *this.period_index, }) { // If the send buffer is full, then remove the oldest one and store again. guard.pop(); @@ -342,6 +359,8 @@ where match inner.queue.push(Data { timestamp: *this.timestamp, data: data.clone(), + #[cfg(feature = "period-index-propagation")] + period_index: *this.period_index, }) { Ok(_) => { // Wake the subscriber up. @@ -386,8 +405,105 @@ where sender.await; r#yield().await; } + + #[cfg(feature = "period-index-propagation")] + pub async fn send_with_period_index( + &self, + data: T, + pub_id: u32, + index: usize, + node_id: u32, + dag_id: u32, + ) { + // [start] pubsub communication latency + let start = awkernel_lib::time::Time::now().uptime().as_nanos() as u64; + record_publish_timestamp(index, start, pub_id, node_id, dag_id); + let period_index = match u32::try_from(index) { + Ok(period_index) => period_index, + Err(_) => { + log::warn!( + "Period index {} exceeds u32::MAX; saturating period index", + index + ); + u32::MAX + } + }; + let sender = Sender::new(self, data).with_period_index(period_index); + sender.await; + r#yield().await; + } } +#[cfg(all(test, feature = "period-index-propagation"))] +mod period_index_propagation_tests { + use super::*; + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + fn block_on(future: F) -> F::Output { + fn raw_waker() -> RawWaker { + fn clone(_: *const ()) -> RawWaker { + raw_waker() + } + fn wake(_: *const ()) {} + fn wake_by_ref(_: *const ()) {} + fn drop(_: *const ()) {} + + RawWaker::new( + core::ptr::null(), + &RawWakerVTable::new(clone, wake, wake_by_ref, drop), + ) + } + + let waker = unsafe { Waker::from_raw(raw_waker()) }; + let mut future = Box::pin(future); + + // Since `wake` is an operation that does nothing, returning `Poll::Pending` would normally cause a deadlock. + // However, this test runs in a single thread and follows the “send → receive” order (where `send` always completes before `recv`). + // In practice, it should not hang. + loop { + let mut context = Context::from_waker(&waker); + match Pin::as_mut(&mut future).poll(&mut context) { + Poll::Ready(output) => return output, + Poll::Pending => {} + } + } + } + + #[test] + fn send_with_period_index_propagates_period_index_to_receiver() { + block_on(async { + let (publisher, subscriber) = create_pubsub::(Attribute::default()); + publisher.send_with_period_index(42, 1, 7, 99, 0).await; + + let received = subscriber.recv().await; + assert_eq!(received.data, 42); + assert_eq!(received.period_index, 7); + }); + } + + #[test] + fn tuple_recv_all_with_period_index_returns_shared_period_index() { + block_on(async { + let (publisher1, subscriber1) = create_pubsub::(Attribute::default()); + let (publisher2, subscriber2) = create_pubsub::(Attribute::default()); + + publisher1.send_with_period_index(10, 11, 3, 21, 0).await; + publisher2.send_with_period_index(20, 12, 3, 22, 0).await; + + let ((value1, value2), period_index) = (subscriber1, subscriber2) + .recv_all_with_period_index() + .await; + + assert_eq!(value1, 10); + assert_eq!(value2, 20); + assert_eq!(period_index, 3); + }); + } +} /// Create an anonymous publisher and an anonymous subscriber. /// This channel works as a channel of multiple producers and multiple consumers. /// @@ -756,12 +872,27 @@ pub trait MultipleReceiver { type Item; fn recv_all(&self) -> Pin + Send + '_>>; + + #[cfg(feature = "period-index-propagation")] + fn recv_all_with_period_index( + &self, + ) -> Pin + Send + '_>>; } pub trait MultipleSender { type Item; fn send_all(&self, item: Self::Item) -> Pin + Send + '_>>; + + #[cfg(feature = "period-index-propagation")] + fn send_all_with_period_index( + &self, + item: Self::Item, + pub_id: u32, + index: usize, + node_id: u32, + dag_id: u32, + ) -> Pin + Send + '_>>; } pub trait VectorToPublishers { type Publishers: MultipleSender; @@ -834,6 +965,13 @@ macro_rules! impl_async_receiver_for_tuple { fn recv_all(&self) -> Pin + Send + '_>> { Box::pin(async move{}) } + + #[cfg(feature = "period-index-propagation")] + fn recv_all_with_period_index( + &self, + ) -> Pin + Send + '_>> { + Box::pin(async move { ((), 0) }) + } } impl MultipleSender for () { @@ -842,6 +980,18 @@ macro_rules! impl_async_receiver_for_tuple { fn send_all(&self, _item: Self::Item) -> Pin + Send + '_>> { Box::pin(async move{}) } + + #[cfg(feature = "period-index-propagation")] + fn send_all_with_period_index( + &self, + _item: Self::Item, + _pub_id: u32, + _index: usize, + _node_id: u32, + _dag_id: u32, + ) -> Pin + Send + '_>> { + Box::pin(async move{}) + } } }; ($(($T:ident, $idx:tt, $idx2:tt)),+) => { @@ -854,6 +1004,40 @@ macro_rules! impl_async_receiver_for_tuple { ($($idx.recv().await.data,)+) }) } + + #[cfg(feature = "period-index-propagation")] + fn recv_all_with_period_index( + &self, + ) -> Pin + Send + '_>> { + let ($($idx,)+) = self; + Box::pin(async move { + let mut period_index: Option = None; + $( + let item = $idx.recv().await; + match period_index { + // Multiple upstream nodes may not produce the same period_index + // at the same time because of startup skew, delayed or dropped + // messages, or DAG branches with different path lengths. + // Treating this as a panic would be too disruptive here, so we + // keep the first period_index and only warn on mismatches. + Some(expected) => { + if expected != item.period_index { + log::warn!( + "recv_all_with_period_index received mismatched periods: expected {}, got {}", + expected, + item.period_index + ); + } + } + None => { + period_index = Some(item.period_index); + } + } + let $idx2 = item.data; + )+ + (($($idx2,)+), period_index.expect("recv_all_with_period_index requires at least one subscriber")) + }) + } } impl<$($T: Clone + Sync + Send + 'static),+> MultipleSender for ($(Publisher<$T>,)+) { @@ -868,6 +1052,24 @@ macro_rules! impl_async_receiver_for_tuple { )+ }) } + + #[cfg(feature = "period-index-propagation")] + fn send_all_with_period_index( + &self, + item: Self::Item, + pub_id: u32, + index: usize, + node_id: u32, + dag_id: u32, + ) -> Pin + Send + '_>> { + let ($($idx,)+) = self; + let ($($idx2,)+) = item; + Box::pin(async move { + $( + $idx.send_with_period_index($idx2, pub_id, index, node_id, dag_id).await; + )+ + }) + } } }; } diff --git a/awkernel_async_lib/src/task.rs b/awkernel_async_lib/src/task.rs index 8f0235260..f4a61e8a5 100644 --- a/awkernel_async_lib/src/task.rs +++ b/awkernel_async_lib/src/task.rs @@ -484,8 +484,11 @@ fn get_next_task(execution_ensured: bool) -> Option> { #[cfg(feature = "perf")] pub mod perf { + use alloc::boxed::Box; + use alloc::string::{String, ToString}; use awkernel_lib::cpu::NUM_MAX_CPU; use core::ptr::{read_volatile, write_volatile}; + use core::sync::atomic::AtomicU32; #[derive(Debug, Clone, PartialEq, Eq)] #[repr(u8)] @@ -494,6 +497,7 @@ pub mod perf { Kernel, Task, ContextSwitch, + ContextSwitchMain, Interrupt, Idle, } @@ -505,8 +509,9 @@ pub mod perf { 1 => Self::Kernel, 2 => Self::Task, 3 => Self::ContextSwitch, - 4 => Self::Interrupt, - 5 => Self::Idle, + 4 => Self::ContextSwitchMain, + 5 => Self::Interrupt, + 6 => Self::Idle, _ => panic!("From for PerfState::from: invalid value"), } } @@ -520,6 +525,7 @@ pub mod perf { static mut TASK_TIME: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut INTERRUPT_TIME: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut CONTEXT_SWITCH_TIME: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; + static mut CONTEXT_SWITCH_MAIN_TIME: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut IDLE_TIME: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut PERF_TIME: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; @@ -527,6 +533,7 @@ pub mod perf { static mut TASK_WCET: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut INTERRUPT_WCET: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut CONTEXT_SWITCH_WCET: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; + static mut CONTEXT_SWITCH_MAIN_WCET: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut IDLE_WCET: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut PERF_WCET: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; @@ -534,9 +541,646 @@ pub mod perf { static mut TASK_COUNT: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut INTERRUPT_COUNT: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut CONTEXT_SWITCH_COUNT: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; + static mut CONTEXT_SWITCH_MAIN_COUNT: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut IDLE_COUNT: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; static mut PERF_COUNT: [u64; NUM_MAX_CPU] = [0; NUM_MAX_CPU]; + use alloc::{collections::BTreeMap, vec::Vec}; + use awkernel_lib::sync::{mcs::MCSNode, mutex::Mutex}; + // NOTE: When logging evaluation results, this value can be adjusted based on awkernel's execution time. + // This value matches the one actually used in the awkernel evaluation branch, and it is a ring buffer. + const MAX_LOGS: usize = 8192; + + type DagTimestampMap = BTreeMap; + type DagTimestampTable = [DagTimestampMap; MAX_LOGS]; + + static SEND_OUTER_TIMESTAMP: Mutex>> = Mutex::new(None); + static RECV_OUTER_TIMESTAMP: Mutex>> = Mutex::new(None); + static ABSOLUTE_DEADLINE: Mutex>> = Mutex::new(None); + static RELATIVE_DEADLINE: Mutex>> = Mutex::new(None); + + pub static PERIOD_INDEX: Mutex> = Mutex::new(BTreeMap::new()); + + pub fn get_period_index(dag_id: u32) -> u32 { + let mut node = MCSNode::new(); + let period_count = PERIOD_INDEX.lock(&mut node); + period_count + .get(&dag_id) + .map(|count| count.load(core::sync::atomic::Ordering::Relaxed)) + .unwrap_or(0) + } + + pub fn increment_period_index(dag_id: u32) -> u32 { + let mut node = MCSNode::new(); + let mut period_count = PERIOD_INDEX.lock(&mut node); + let count = period_count + .entry(dag_id) + .or_insert_with(|| AtomicU32::new(0)); + count.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1 + } + // This value represents the type of pubsub (publish only, subscribe only, or both publish and subscribe) + // and is a fixed value. + const MAX_PUBSUB: usize = 3; + // This value indicates the maximum number of nodes in the DAG + // and can be adjusted based on the structure of the DAG used for evaluation. + const MAX_NODES: usize = 5; + + type DagTimestamps = [[[u64; MAX_NODES]; MAX_PUBSUB]; MAX_LOGS]; + + #[derive(Clone)] + struct PubSubTable { + by_dag: BTreeMap>, + } + + impl PubSubTable { + fn new() -> Self { + Self { + by_dag: BTreeMap::new(), + } + } + + fn get_or_insert_dag(&mut self, dag_id: u32) -> &mut DagTimestamps { + self.by_dag + .entry(dag_id) + .or_insert_with(|| Box::new([[[0u64; MAX_NODES]; MAX_PUBSUB]; MAX_LOGS])) + } + } + + static PUBLISH: Mutex>> = Mutex::new(None); + static SUBSCRIBE: Mutex>> = Mutex::new(None); + static SINK_EXEC_END: Mutex>> = Mutex::new(None); + + #[inline(always)] + fn to_ring_buffer_index(period_index: usize) -> usize { + period_index % MAX_LOGS + } + + pub fn update_cycle_start_timestamp(period_index: usize, new_timestamp: u64, dag_id: u32) { + let log_index = to_ring_buffer_index(period_index); + + let mut node = MCSNode::new(); + let mut recorder_opt = SEND_OUTER_TIMESTAMP.lock(&mut node); + + let recorder = + recorder_opt.get_or_insert_with(|| Box::new(core::array::from_fn(|_| BTreeMap::new()))); + + recorder[log_index].entry(dag_id).or_insert(new_timestamp); + } + + pub fn update_cycle_end_timestamp(period_index: usize, new_timestamp: u64, dag_id: u32) { + let log_index = to_ring_buffer_index(period_index); + + let mut node = MCSNode::new(); + let mut recorder_opt = RECV_OUTER_TIMESTAMP.lock(&mut node); + + let recorder = + recorder_opt.get_or_insert_with(|| Box::new(core::array::from_fn(|_| BTreeMap::new()))); + + recorder[log_index].insert(dag_id, new_timestamp); + } + + pub fn update_absolute_deadline_timestamp(period_index: usize, deadline: u64, dag_id: u32) { + let log_index = to_ring_buffer_index(period_index); + + let mut node = MCSNode::new(); + let mut recorder_opt = ABSOLUTE_DEADLINE.lock(&mut node); + + let recorder = + recorder_opt.get_or_insert_with(|| Box::new(core::array::from_fn(|_| BTreeMap::new()))); + + recorder[log_index].entry(dag_id).or_insert(deadline); + } + + pub fn update_relative_deadline_timestamp(period_index: usize, deadline: u64, dag_id: u32) { + let log_index = to_ring_buffer_index(period_index); + + let mut node = MCSNode::new(); + let mut recorder_opt = RELATIVE_DEADLINE.lock(&mut node); + + let recorder = + recorder_opt.get_or_insert_with(|| Box::new(core::array::from_fn(|_| BTreeMap::new()))); + + recorder[log_index].entry(dag_id).or_insert(deadline); + } + + pub fn record_publish_timestamp( + period_index: usize, + new_timestamp: u64, + pub_id: u32, + node_id: u32, + dag_id: u32, + ) { + let log_index = to_ring_buffer_index(period_index); + if (pub_id as usize) >= MAX_PUBSUB { + log::warn!( + "Publish ID out of bounds: {} (max {})", + pub_id as usize, + MAX_PUBSUB + ); + return; + } + + let node_id_usize = node_id as usize; + if node_id_usize >= MAX_NODES { + log::warn!( + "Publish node ID out of bounds: {} (max {})", + node_id_usize, + MAX_NODES + ); + return; + } + + let mut node = MCSNode::new(); + let mut recorder_opt = PUBLISH.lock(&mut node); + + let recorder = recorder_opt.get_or_insert_with(|| Box::new(PubSubTable::new())); + let pub_id = pub_id as usize; + let table = recorder.get_or_insert_dag(dag_id); + + if table[log_index][pub_id][node_id_usize] == 0 { + table[log_index][pub_id][node_id_usize] = new_timestamp; + } + } + + pub fn record_subscribe_timestamp( + period_index: usize, + new_timestamp: u64, + sub_id: u32, + node_id: u32, + dag_id: u32, + ) { + let log_index = to_ring_buffer_index(period_index); + if (sub_id as usize) >= MAX_PUBSUB { + log::warn!( + "Subscribe ID out of bounds: {} (max {})", + sub_id as usize, + MAX_PUBSUB + ); + return; + } + + let node_id_usize = node_id as usize; + if node_id_usize >= MAX_NODES { + log::warn!( + "Subscribe node ID out of bounds: {} (max {})", + node_id_usize, + MAX_NODES + ); + return; + } + + let mut node = MCSNode::new(); + let mut recorder_opt = SUBSCRIBE.lock(&mut node); + + let recorder = recorder_opt.get_or_insert_with(|| Box::new(PubSubTable::new())); + let sub_id = sub_id as usize; + let table = recorder.get_or_insert_dag(dag_id); + + if table[log_index][sub_id][node_id_usize] == 0 { + table[log_index][sub_id][node_id_usize] = new_timestamp; + } + } + + pub fn record_sink_exec_end_timestamp( + period_index: usize, + new_timestamp: u64, + node_id: u32, + dag_id: u32, + ) { + let log_index = to_ring_buffer_index(period_index); + + let node_id_usize = node_id as usize; + if node_id_usize >= MAX_NODES { + log::warn!( + "Sink exec-end node ID out of bounds: {} (max {})", + node_id_usize, + MAX_NODES + ); + return; + } + + let mut node = MCSNode::new(); + let mut recorder_opt = SINK_EXEC_END.lock(&mut node); + + let recorder = recorder_opt.get_or_insert_with(|| Box::new(PubSubTable::new())); + let table = recorder.get_or_insert_dag(dag_id); + + if table[log_index][0][node_id_usize] == 0 { + table[log_index][0][node_id_usize] = new_timestamp; + } + } + + #[derive(Clone, Copy, PartialEq, Eq)] + pub enum DagNodeRole { + Source, + Middle, + Sink, + } + + pub struct DagNodeTimingSample { + pub node_id: u32, + pub role: DagNodeRole, + /// exec time samples in nanoseconds per period + pub exec_samples: Vec, + /// comm time samples (from previous node) in nanoseconds per period + pub comm_samples: Vec, + } + + pub struct DagTimingStats { + pub nodes: Vec, + /// RECV_OUTER - SEND_OUTER per period (excludes sink exec) + pub e2e_partial_samples: Vec, + } + + fn get_or_insert_timing_node( + nodes: &mut Vec, + node_id: u32, + role: DagNodeRole, + ) -> &mut DagNodeTimingSample { + if let Some(pos) = nodes.iter().position(|n| n.node_id == node_id) { + &mut nodes[pos] + } else { + nodes.push(DagNodeTimingSample { + node_id, + role, + exec_samples: Vec::new(), + comm_samples: Vec::new(), + }); + nodes.last_mut().unwrap() + } + } + + /// Scan all pubsub tables and compute per-DAG timing stats. + /// + /// Assumes a linear pipeline topology: source → (middle)* → sink. + /// pub_id=0: source publish; sub_id=1: middle subscribe; pub_id=1: middle publish; + /// sub_id=2: sink subscribe; SINK_EXEC_END: sink exec end. + pub fn collect_all_dag_timing_stats() -> BTreeMap { + let mut n1 = MCSNode::new(); + let mut n2 = MCSNode::new(); + let mut n3 = MCSNode::new(); + let mut n4 = MCSNode::new(); + let mut n5 = MCSNode::new(); + + let send_outer_opt = SEND_OUTER_TIMESTAMP.lock(&mut n1); + let recv_outer_opt = RECV_OUTER_TIMESTAMP.lock(&mut n2); + let publish_opt = PUBLISH.lock(&mut n3); + let subscribe_opt = SUBSCRIBE.lock(&mut n4); + let sink_exec_end_opt = SINK_EXEC_END.lock(&mut n5); + + let mut dag_ids: Vec = Vec::new(); + if let Some(t) = publish_opt.as_ref() { + dag_ids.extend(t.by_dag.keys().copied()); + } + if let Some(t) = subscribe_opt.as_ref() { + for k in t.by_dag.keys() { + if !dag_ids.contains(k) { + dag_ids.push(*k); + } + } + } + dag_ids.sort_unstable(); + + let mut result: BTreeMap = BTreeMap::new(); + + for dag_id in dag_ids { + let mut stats = DagTimingStats { + nodes: Vec::new(), + e2e_partial_samples: Vec::new(), + }; + + // Extract dag-specific table slices once before the period loop. + // Using `match &*opt` (same pattern as print_timestamp_table) to avoid + // lifetime issues with closures that return references via and_then. + let pub_dag: Option<&DagTimestamps> = match &*publish_opt { + Some(pt) => pt.by_dag.get(&dag_id).map(|b| b.as_ref()), + None => None, + }; + let sub_dag: Option<&DagTimestamps> = match &*subscribe_opt { + Some(st) => st.by_dag.get(&dag_id).map(|b| b.as_ref()), + None => None, + }; + let sink_dag: Option<&DagTimestamps> = match &*sink_exec_end_opt { + Some(se) => se.by_dag.get(&dag_id).map(|b| b.as_ref()), + None => None, + }; + + for p in 0..MAX_LOGS { + let send_outer = match &*send_outer_opt { + Some(arr) => arr[p].get(&dag_id).copied().unwrap_or(0), + None => 0, + }; + let recv_outer = match &*recv_outer_opt { + Some(arr) => arr[p].get(&dag_id).copied().unwrap_or(0), + None => 0, + }; + + if send_outer != 0 && recv_outer != 0 && recv_outer > send_outer { + stats.e2e_partial_samples.push(recv_outer - send_outer); + } + + // source pub (pub_id=0) + let mut src_pub_ts = 0u64; + let mut src_node_id = 0u32; + if let Some(t) = pub_dag { + for k in 0..MAX_NODES { + if t[p][0][k] != 0 { + src_pub_ts = t[p][0][k]; + src_node_id = k as u32; + break; + } + } + } + if src_pub_ts != 0 && send_outer != 0 && src_pub_ts > send_outer { + get_or_insert_timing_node(&mut stats.nodes, src_node_id, DagNodeRole::Source) + .exec_samples + .push(src_pub_ts - send_outer); + } + + // Collect ALL middle nodes (slot [1]). + // All spawn_reactor nodes use sub_id=1 / pub_id=1 regardless of chain position. + // Sort by subscribe time to reconstruct linear chain order, then compute + // comm (from predecessor pub) and exec (pub - sub) for each node. + let mut mid_nodes: Vec<(u64, u64, u32)> = Vec::new(); // (sub_ts, pub_ts, node_id) + for k in 0..MAX_NODES { + let sub_ts = sub_dag.map(|t| t[p][1][k]).unwrap_or(0); + let pub_ts = pub_dag.map(|t| t[p][1][k]).unwrap_or(0); + if sub_ts != 0 || pub_ts != 0 { + mid_nodes.push((sub_ts, pub_ts, k as u32)); + } + } + mid_nodes.sort_by_key(|&(sub_ts, _, _)| sub_ts); + + let mut prev_pub_ts = src_pub_ts; + for &(mid_sub_ts, mid_pub_ts, mid_node_id) in &mid_nodes { + if prev_pub_ts != 0 && mid_sub_ts != 0 && mid_sub_ts > prev_pub_ts { + get_or_insert_timing_node( + &mut stats.nodes, + mid_node_id, + DagNodeRole::Middle, + ) + .comm_samples + .push(mid_sub_ts - prev_pub_ts); + } + if mid_sub_ts != 0 && mid_pub_ts != 0 && mid_pub_ts > mid_sub_ts { + get_or_insert_timing_node( + &mut stats.nodes, + mid_node_id, + DagNodeRole::Middle, + ) + .exec_samples + .push(mid_pub_ts - mid_sub_ts); + } + if mid_pub_ts != 0 { + prev_pub_ts = mid_pub_ts; + } + } + + // sink sub (sub_id=2) + let last_mid_pub_ts = mid_nodes + .last() + .map(|&(_, pub_ts, _)| pub_ts) + .unwrap_or(src_pub_ts); + let mut sink_sub_ts = 0u64; + let mut sink_node_id = 0u32; + if let Some(t) = sub_dag { + for k in 0..MAX_NODES { + if t[p][2][k] != 0 { + sink_sub_ts = t[p][2][k]; + sink_node_id = k as u32; + break; + } + } + } + if last_mid_pub_ts != 0 && sink_sub_ts != 0 && sink_sub_ts > last_mid_pub_ts { + get_or_insert_timing_node(&mut stats.nodes, sink_node_id, DagNodeRole::Sink) + .comm_samples + .push(sink_sub_ts - last_mid_pub_ts); + } + + // sink exec end (index 0 in SINK_EXEC_END) + let mut sink_exec_end_ts = 0u64; + if let Some(t) = sink_dag { + for k in 0..MAX_NODES { + if t[p][0][k] != 0 { + sink_exec_end_ts = t[p][0][k]; + break; + } + } + } + if sink_sub_ts != 0 && sink_exec_end_ts != 0 && sink_exec_end_ts > sink_sub_ts { + get_or_insert_timing_node(&mut stats.nodes, sink_node_id, DagNodeRole::Sink) + .exec_samples + .push(sink_exec_end_ts - sink_sub_ts); + } + } + + stats.nodes.sort_by_key(|n| n.node_id); + result.insert(dag_id, stats); + } + + result + } + + // For precision of the cycle + pub fn print_timestamp_table() { + let mut node1 = MCSNode::new(); + let mut node2 = MCSNode::new(); + let mut node3 = MCSNode::new(); + let mut node4 = MCSNode::new(); + const MAX_ROWS_TO_PRINT: usize = 256; + + let send_outer_opt = SEND_OUTER_TIMESTAMP.lock(&mut node1); + let recv_outer_opt = RECV_OUTER_TIMESTAMP.lock(&mut node2); + let absolute_deadline_opt = ABSOLUTE_DEADLINE.lock(&mut node3); + let relative_deadline_opt = RELATIVE_DEADLINE.lock(&mut node4); + + let mut rows = Vec::new(); + let mut truncated = false; + 'collect_rows: for i in 0..MAX_LOGS { + let mut dag_ids = Vec::new(); + if let Some(arr) = &*send_outer_opt { + dag_ids.extend(arr[i].keys().copied()); + } + if let Some(arr) = &*recv_outer_opt { + dag_ids.extend(arr[i].keys().copied()); + } + if let Some(arr) = &*absolute_deadline_opt { + dag_ids.extend(arr[i].keys().copied()); + } + if let Some(arr) = &*relative_deadline_opt { + dag_ids.extend(arr[i].keys().copied()); + } + + dag_ids.sort_unstable(); + dag_ids.dedup(); + + for dag_id in dag_ids { + let pre_send_outer = match &*send_outer_opt { + Some(arr) => arr[i].get(&dag_id).copied().unwrap_or(0), + None => 0, + }; + let fin_recv_outer = match &*recv_outer_opt { + Some(arr) => arr[i].get(&dag_id).copied().unwrap_or(0), + None => 0, + }; + let absolute_deadline = match &*absolute_deadline_opt { + Some(arr) => arr[i].get(&dag_id).copied().unwrap_or(0), + None => 0, + }; + let relative_deadline = match &*relative_deadline_opt { + Some(arr) => arr[i].get(&dag_id).copied().unwrap_or(0), + None => 0, + }; + + if pre_send_outer != 0 + || fin_recv_outer != 0 + || absolute_deadline != 0 + || relative_deadline != 0 + { + if rows.len() >= MAX_ROWS_TO_PRINT { + truncated = true; + break 'collect_rows; + } + rows.push(( + i, + dag_id, + pre_send_outer, + fin_recv_outer, + absolute_deadline, + relative_deadline, + )); + } + } + } + drop(relative_deadline_opt); + drop(absolute_deadline_opt); + drop(recv_outer_opt); + drop(send_outer_opt); + + log::info!("--- Timestamp Summary (in nanoseconds) ---"); + log::info!( + "{: ^5} | {: ^5} | {: ^14} | {: ^14} | {: ^14} | {: ^14} | {: ^14}", + "Index", + "DAG-ID", + "Send-Outer", + "Recv-Outer", + "Latency", + "Absolute Deadline", + "Relative Deadline" + ); + + log::info!("-----|--------|----------------|----------------|----------------|--------------------|--------------------"); + + for (i, dag_id, pre_send_outer, fin_recv_outer, absolute_deadline, relative_deadline) in + rows + { + let format_ts = |ts: u64| -> String { + if ts == 0 { + "-".to_string() + } else { + ts.to_string() + } + }; + + let latency_str = if pre_send_outer != 0 && fin_recv_outer != 0 { + fin_recv_outer.saturating_sub(pre_send_outer).to_string() + } else { + "-".to_string() + }; + + log::info!( + "{: >5} | {: >5} | {: >14} | {: >14} | {: >14} | {: >20} | {: >20}", + i, + format_ts(dag_id as u64), + format_ts(pre_send_outer), + format_ts(fin_recv_outer), + latency_str, + format_ts(absolute_deadline), + format_ts(relative_deadline), + ); + } + if truncated { + log::warn!( + "Timestamp Summary truncated to {} rows; call print_timestamp_table() again to continue inspection", + MAX_ROWS_TO_PRINT + ); + } + log::info!("----------------------------------------------------------"); + } + + // For pubsub communication latency + pub fn print_pubsub_table() { + let mut node1 = MCSNode::new(); + let mut node2 = MCSNode::new(); + + let publish_opt = PUBLISH.lock(&mut node1); + let subscribe_opt = SUBSCRIBE.lock(&mut node2); + + let mut dag_ids: Vec = Vec::new(); + if let Some(t) = publish_opt.as_ref() { + dag_ids.extend(t.by_dag.keys().copied()); + } + if let Some(t) = subscribe_opt.as_ref() { + for k in t.by_dag.keys() { + if !dag_ids.contains(k) { + dag_ids.push(*k); + } + } + } + dag_ids.sort_unstable(); + + log::info!("--- Pub/Sub Timestamp Summary (in nanoseconds) ---"); + log::info!( + "{: ^6} | {: ^5} | {: ^10} | {: ^7} | {: ^14} | {: ^14}", + "DAG-ID", + "Index", + "Pub/Sub ID", + "Node ID", + "Publish Time", + "Subscribe Time" + ); + log::info!("--------|-------|------------|---------|----------------|----------------"); + + let format_ts = |ts: u64| -> String { + if ts == 0 { + "-".to_string() + } else { + ts.to_string() + } + }; + + for dag_id in &dag_ids { + for i in 0..MAX_LOGS { + for j in 0..MAX_PUBSUB { + for k in 0..MAX_NODES { + let pub_time = match &*publish_opt { + Some(pt) => pt.by_dag.get(dag_id).map(|ts| ts[i][j][k]).unwrap_or(0), + None => 0, + }; + let sub_time = match &*subscribe_opt { + Some(st) => st.by_dag.get(dag_id).map(|ts| ts[i][j][k]).unwrap_or(0), + None => 0, + }; + + if pub_time != 0 || sub_time != 0 { + log::info!( + "{: >6} | {: >5} | {: >10} | {: >7} | {: >14} | {: >14}", + dag_id, + i, + j, + k, + format_ts(pub_time), + format_ts(sub_time), + ); + } + } + } + } + } + log::info!("--------------------------------------------------------------"); + } + fn update_time_and_state(next_state: PerfState) { let end = awkernel_lib::delay::cpu_counter(); let cpu_id = awkernel_lib::cpu::cpu_id(); @@ -584,6 +1228,14 @@ pub mod perf { let wcet = read_volatile(&CONTEXT_SWITCH_WCET[cpu_id]); write_volatile(&mut CONTEXT_SWITCH_WCET[cpu_id], wcet.max(diff)); }, + PerfState::ContextSwitchMain => unsafe { + let t = read_volatile(&CONTEXT_SWITCH_MAIN_TIME[cpu_id]); + write_volatile(&mut CONTEXT_SWITCH_MAIN_TIME[cpu_id], t + diff); + let c = read_volatile(&CONTEXT_SWITCH_MAIN_COUNT[cpu_id]); + write_volatile(&mut CONTEXT_SWITCH_MAIN_COUNT[cpu_id], c + 1); + let wcet = read_volatile(&CONTEXT_SWITCH_MAIN_WCET[cpu_id]); + write_volatile(&mut CONTEXT_SWITCH_MAIN_WCET[cpu_id], wcet.max(diff)); + }, PerfState::Idle => unsafe { let t = read_volatile(&IDLE_TIME[cpu_id]); write_volatile(&mut IDLE_TIME[cpu_id], t + diff); @@ -639,6 +1291,7 @@ pub mod perf { PerfState::Kernel => start_kernel(), PerfState::Task => start_task(), PerfState::ContextSwitch => start_context_switch(), + PerfState::ContextSwitchMain => start_context_switch_main(), PerfState::Interrupt => { start_interrupt(); } @@ -651,6 +1304,11 @@ pub mod perf { update_time_and_state(PerfState::ContextSwitch); } + #[inline(always)] + pub(crate) fn start_context_switch_main() { + update_time_and_state(PerfState::ContextSwitchMain); + } + #[inline(always)] pub fn start_idle() { update_time_and_state(PerfState::Idle); @@ -752,6 +1410,210 @@ pub mod perf { pub fn get_perf_wcet(cpu_id: usize) -> u64 { unsafe { read_volatile(&PERF_WCET[cpu_id]) } } + + // ─── DAG aggregate statistics ──────────────────────────────────────────── + + #[cfg(feature = "period-index-propagation")] + fn samples_avg(s: &[u64]) -> Option { + if s.is_empty() { + None + } else { + Some(s.iter().sum::() / s.len() as u64) + } + } + + #[cfg(feature = "period-index-propagation")] + fn samples_wcet(s: &[u64]) -> Option { + s.iter().copied().reduce(u64::max) + } + + #[cfg(feature = "period-index-propagation")] + pub struct DagNodeStats { + pub node_id: u32, + pub role: DagNodeRole, + pub exec_avg: Option, + pub exec_wcet: Option, + pub comm_avg: Option, + pub comm_wcet: Option, + } + + #[cfg(feature = "period-index-propagation")] + pub struct DagStats { + pub dag_id: u32, + pub cores: Vec, + pub total_preempts: u64, + pub exec_avg: u64, + pub exec_wcet: u64, + pub comm_avg: u64, + pub comm_wcet: u64, + pub e2e_partial_avg: Option, + pub e2e_partial_wcet: Option, + pub period_count: u32, + pub nodes: Vec, + } + + /// Aggregate pubsub timestamps and task metadata into per-DAG statistics. + #[cfg(feature = "period-index-propagation")] + pub fn get_all_dag_stats() -> Vec { + let timing_map = collect_all_dag_timing_stats(); + let all_tasks = super::get_tasks(); + + let mut result = Vec::new(); + + for (dag_id, timing) in &timing_map { + let mut cores: Vec = Vec::new(); + let mut total_preempts: u64 = 0; + + for t in &all_tasks { + let mut node = MCSNode::new(); + let info = t.info.lock(&mut node); + if let Some(di) = info.get_dag_info() { + if di.dag_id == *dag_id { + total_preempts += info.get_num_preemption(); + if let Some(core) = t.partitioned_core { + if !cores.contains(&core) { + cores.push(core); + } + } + } + } + } + cores.sort_unstable(); + + let mut nodes = Vec::new(); + let mut exec_avg_sum: u64 = 0; + let mut exec_wcet_max: u64 = 0; + let mut comm_avg_sum: u64 = 0; + let mut comm_wcet_max: u64 = 0; + + for nt in &timing.nodes { + let ea = samples_avg(&nt.exec_samples); + let ew = samples_wcet(&nt.exec_samples); + let ca = samples_avg(&nt.comm_samples); + let cw = samples_wcet(&nt.comm_samples); + exec_avg_sum += ea.unwrap_or(0); + exec_wcet_max = exec_wcet_max.max(ew.unwrap_or(0)); + comm_avg_sum += ca.unwrap_or(0); + comm_wcet_max = comm_wcet_max.max(cw.unwrap_or(0)); + nodes.push(DagNodeStats { + node_id: nt.node_id, + role: nt.role, + exec_avg: ea, + exec_wcet: ew, + comm_avg: ca, + comm_wcet: cw, + }); + } + + result.push(DagStats { + dag_id: *dag_id, + cores, + total_preempts, + exec_avg: exec_avg_sum, + exec_wcet: exec_wcet_max, + comm_avg: comm_avg_sum, + comm_wcet: comm_wcet_max, + e2e_partial_avg: samples_avg(&timing.e2e_partial_samples), + e2e_partial_wcet: samples_wcet(&timing.e2e_partial_samples), + period_count: timing.e2e_partial_samples.len() as u32, + nodes, + }); + } + + result + } + + #[cfg(feature = "period-index-propagation")] + pub fn print_dag_table() { + use alloc::format; + let stats = get_all_dag_stats(); + if stats.is_empty() { + return; + } + + log::info!("--- DAG Statistics (ns) ---"); + log::info!( + "{:>4} | {:>9} | {:>7} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9}", + "DAG", + "#preempt", + "periods", + "exec_avg", + "exec_wc", + "comm_avg", + "comm_wc", + "e2e_avg", + "e2e_wc" + ); + log::info!("-----|-----------|---------|-----------|-----------|-----------|-----------|-----------|----------"); + + for dag in &stats { + let e2e_avg = dag + .e2e_partial_avg + .map(|v| format!("{}", v)) + .unwrap_or_else(|| "-".into()); + let e2e_wcet = dag + .e2e_partial_wcet + .map(|v| format!("{}", v)) + .unwrap_or_else(|| "-".into()); + log::info!( + "{:>4} | {:>9} | {:>7} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9}", + dag.dag_id, + dag.total_preempts, + dag.period_count, + dag.exec_avg, + dag.exec_wcet, + dag.comm_avg, + dag.comm_wcet, + e2e_avg, + e2e_wcet, + ); + } + + for dag in &stats { + log::info!("DAG {} node detail:", dag.dag_id); + log::info!( + "{:>5} | {:6} | {:>9} | {:>9} | {:>9} | {:>9}", + "node", + "role", + "exec_avg", + "exec_wc", + "comm_avg", + "comm_wc" + ); + for n in &dag.nodes { + let role_str = match n.role { + DagNodeRole::Source => "src ", + DagNodeRole::Middle => "middle", + DagNodeRole::Sink => "sink ", + }; + let ea = n + .exec_avg + .map(|v| format!("{}", v)) + .unwrap_or_else(|| "-".into()); + let ew = n + .exec_wcet + .map(|v| format!("{}", v)) + .unwrap_or_else(|| "-".into()); + let ca = n + .comm_avg + .map(|v| format!("{}", v)) + .unwrap_or_else(|| "-".into()); + let cw = n + .comm_wcet + .map(|v| format!("{}", v)) + .unwrap_or_else(|| "-".into()); + log::info!( + "{:>5} | {} | {:>9} | {:>9} | {:>9} | {:>9}", + n.node_id, + role_str, + ea, + ew, + ca, + cw + ); + } + } + } } pub fn run_main() { diff --git a/awkernel_async_lib/src/time_interval.rs b/awkernel_async_lib/src/time_interval.rs index 96ce16219..3fe640f0c 100644 --- a/awkernel_async_lib/src/time_interval.rs +++ b/awkernel_async_lib/src/time_interval.rs @@ -29,6 +29,8 @@ //! SOFTWARE. use crate::sleep_task::Sleep; +#[cfg(feature = "period-index-propagation")] +use crate::task::perf::{get_period_index, update_cycle_start_timestamp}; use alloc::boxed::Box; use awkernel_lib::time::Time; use core::{ @@ -86,7 +88,7 @@ pub enum MissedTickBehavior { /// use crate::time_interval::interval; /// use core::time::Duration; /// -/// let mut interval = interval(Duration::from_secs(1)); +/// let mut interval = interval(Duration::from_secs(1), 0); /// let mut ticks = 0; /// while ticks < 5 { /// let tick_time = interval.tick().await; @@ -95,13 +97,19 @@ pub enum MissedTickBehavior { /// } /// ``` /// -pub fn interval(period: Duration) -> Interval { +pub(crate) fn interval(period: Duration, _dag_id: u32) -> Interval { assert!(!period.is_zero(), "`period` must be non-zero."); - interval_at(Time::now(), period) + interval_at(Time::now(), period, _dag_id) } -pub fn interval_at(start: Time, period: Duration) -> Interval { +pub(crate) fn interval_at(start: Time, period: Duration, _dag_id: u32) -> Interval { assert!(!period.is_zero(), "`period` must be non-zero."); + #[cfg(feature = "period-index-propagation")] + { + let index = get_period_index(_dag_id) as usize; + // [start] cycle deviation index == 0 (basis of cycle deviation) + update_cycle_start_timestamp(index, start.uptime().as_nanos() as u64, _dag_id); + } Interval { delay: None, next_tick_target: start, diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 847e6ec39..2faed7a66 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -80,10 +80,12 @@ default = ["x86"] heap-tlsf = ["awkernel_lib/heap-tlsf"] heap-wf-alloc = ["awkernel_lib/heap-wf-alloc"] perf = ["awkernel_async_lib/perf", "userland/perf"] +period-index-propagation = ["perf", "awkernel_async_lib/period-index-propagation"] no_std_unwinding = ["dep:unwinding"] debug = ["dep:gimli", "dep:addr2line"] x86 = [ "perf", + "period-index-propagation", "no_std_unwinding", "awkernel_lib/x86", "dep:x86_64", @@ -95,6 +97,7 @@ x86 = [ "heap-wf-alloc", ] rv32 = [ + "perf", "no_std_unwinding", "awkernel_lib/rv32", "dep:ns16550a", @@ -102,6 +105,7 @@ rv32 = [ "heap-tlsf", ] rv64 = [ + "perf", "no_std_unwinding", "awkernel_lib/rv64", "dep:ns16550a", diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 5473dd170..7c183064f 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -30,6 +30,8 @@ mod nostd; static PRIMARY_READY: AtomicBool = AtomicBool::new(false); static NUM_READY_WORKER: AtomicU16 = AtomicU16::new(0); +#[cfg(feature = "period-index-propagation")] +const PERF_PRINT_INTERVAL_SECS: u64 = 30; /// `main` function is called from each CPU. /// `kernel_info.cpu_id` represents the CPU identifier. @@ -75,7 +77,17 @@ fn main(kernel_info: KernelInfo) { // Enable awkernel_lib::cpu::sleep_cpu() and awkernel_lib::cpu::wakeup_cpu(). unsafe { awkernel_lib::cpu::init_sleep() }; + #[cfg(feature = "period-index-propagation")] + let mut last_print = awkernel_lib::time::Time::now(); + loop { + #[cfg(feature = "period-index-propagation")] + if last_print.elapsed().as_secs() >= PERF_PRINT_INTERVAL_SECS { + // awkernel_async_lib::task::perf::print_timestamp_table(); + awkernel_async_lib::task::perf::print_dag_table(); + + last_print = awkernel_lib::time::Time::now(); + } // handle IRQs { let _irq_enable = awkernel_lib::interrupt::InterruptEnable::new();