diff options
| author | Dario Nieuwenhuis <[email protected]> | 2025-04-02 00:34:05 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-04-02 00:34:05 +0000 |
| commit | a137a160671d3ede48519e5faf316f31f6f6cbd3 (patch) | |
| tree | a2405acf82fd0710e7b83c220e570c57a3e14c80 | |
| parent | 3bc809eb3ece600164a37a80cd64154efa6d15a5 (diff) | |
| parent | ef3c1b87d16d17e312e869546fae00f895f68989 (diff) | |
Merge pull request #4033 from jamesmunns/james/upstream-trace
[embassy-executor] Add two new `trace` hooks, improve docs
| -rw-r--r-- | embassy-executor/src/raw/mod.rs | 15 | ||||
| -rw-r--r-- | embassy-executor/src/raw/trace.rs | 149 |
2 files changed, 164 insertions, 0 deletions
diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index e38a2af66..5b1f33a0e 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs | |||
| @@ -138,6 +138,12 @@ impl TaskRef { | |||
| 138 | pub(crate) fn as_ptr(self) -> *const TaskHeader { | 138 | pub(crate) fn as_ptr(self) -> *const TaskHeader { |
| 139 | self.ptr.as_ptr() | 139 | self.ptr.as_ptr() |
| 140 | } | 140 | } |
| 141 | |||
| 142 | /// Get the ID for a task | ||
| 143 | #[cfg(feature = "trace")] | ||
| 144 | pub fn as_id(self) -> u32 { | ||
| 145 | self.ptr.as_ptr() as u32 | ||
| 146 | } | ||
| 141 | } | 147 | } |
| 142 | 148 | ||
| 143 | /// Raw storage in which a task can be spawned. | 149 | /// Raw storage in which a task can be spawned. |
| @@ -213,6 +219,9 @@ impl<F: Future + 'static> TaskStorage<F> { | |||
| 213 | let mut cx = Context::from_waker(&waker); | 219 | let mut cx = Context::from_waker(&waker); |
| 214 | match future.poll(&mut cx) { | 220 | match future.poll(&mut cx) { |
| 215 | Poll::Ready(_) => { | 221 | Poll::Ready(_) => { |
| 222 | #[cfg(feature = "trace")] | ||
| 223 | let exec_ptr: *const SyncExecutor = this.raw.executor.load(Ordering::Relaxed); | ||
| 224 | |||
| 216 | // As the future has finished and this function will not be called | 225 | // As the future has finished and this function will not be called |
| 217 | // again, we can safely drop the future here. | 226 | // again, we can safely drop the future here. |
| 218 | this.future.drop_in_place(); | 227 | this.future.drop_in_place(); |
| @@ -224,6 +233,9 @@ impl<F: Future + 'static> TaskStorage<F> { | |||
| 224 | // Make sure we despawn last, so that other threads can only spawn the task | 233 | // Make sure we despawn last, so that other threads can only spawn the task |
| 225 | // after we're done with it. | 234 | // after we're done with it. |
| 226 | this.raw.state.despawn(); | 235 | this.raw.state.despawn(); |
| 236 | |||
| 237 | #[cfg(feature = "trace")] | ||
| 238 | trace::task_end(exec_ptr, &p); | ||
| 227 | } | 239 | } |
| 228 | Poll::Pending => {} | 240 | Poll::Pending => {} |
| 229 | } | 241 | } |
| @@ -420,6 +432,9 @@ impl SyncExecutor { | |||
| 420 | /// | 432 | /// |
| 421 | /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. | 433 | /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. |
| 422 | pub(crate) unsafe fn poll(&'static self) { | 434 | pub(crate) unsafe fn poll(&'static self) { |
| 435 | #[cfg(feature = "trace")] | ||
| 436 | trace::poll_start(self); | ||
| 437 | |||
| 423 | self.run_queue.dequeue_all(|p| { | 438 | self.run_queue.dequeue_all(|p| { |
| 424 | let task = p.header(); | 439 | let task = p.header(); |
| 425 | 440 | ||
diff --git a/embassy-executor/src/raw/trace.rs b/embassy-executor/src/raw/trace.rs index b34387b58..aba519c8f 100644 --- a/embassy-executor/src/raw/trace.rs +++ b/embassy-executor/src/raw/trace.rs | |||
| @@ -1,16 +1,157 @@ | |||
| 1 | //! # Tracing | ||
| 2 | //! | ||
| 3 | //! The `trace` feature enables a number of callbacks that can be used to track the | ||
| 4 | //! lifecycle of tasks and/or executors. | ||
| 5 | //! | ||
| 6 | //! Callbacks will have one or both of the following IDs passed to them: | ||
| 7 | //! | ||
| 8 | //! 1. A `task_id`, a `u32` value unique to a task for the duration of the time it is valid | ||
| 9 | //! 2. An `executor_id`, a `u32` value unique to an executor for the duration of the time it is | ||
| 10 | //! valid | ||
| 11 | //! | ||
| 12 | //! Today, both `task_id` and `executor_id` are u32s containing the least significant 32 bits of | ||
| 13 | //! the address of the task or executor, however this is NOT a stable guarantee, and MAY change | ||
| 14 | //! at any time. | ||
| 15 | //! | ||
| 16 | //! IDs are only guaranteed to be unique for the duration of time the item is valid. If a task | ||
| 17 | //! ends, and is re-spawned, it MAY or MAY NOT have the same ID. For tasks, this valid time is defined | ||
| 18 | //! as the time between `_embassy_trace_task_new` and `_embassy_trace_task_end` for a given task. | ||
| 19 | //! For executors, this time is not defined, but is often "forever" for practical embedded | ||
| 20 | //! programs. | ||
| 21 | //! | ||
| 22 | //! Callbacks can be used by enabling the `trace` feature, and providing implementations of the | ||
| 23 | //! `extern "Rust"` functions below. All callbacks must be implemented. | ||
| 24 | //! | ||
| 25 | //! ## Task Tracing lifecycle | ||
| 26 | //! | ||
| 27 | //! ```text | ||
| 28 | //! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 29 | //! │(1) │ | ||
| 30 | //! │ │ | ||
| 31 | //! ╔════▼════╗ (2) ┌─────────┐ (3) ┌─────────┐ │ | ||
| 32 | //! │ ║ SPAWNED ║────▶│ WAITING │────▶│ RUNNING │ | ||
| 33 | //! ╚═════════╝ └─────────┘ └─────────┘ │ | ||
| 34 | //! │ ▲ ▲ │ │ │ | ||
| 35 | //! │ (4) │ │(6) │ | ||
| 36 | //! │ │(7) └ ─ ─ ┘ │ │ | ||
| 37 | //! │ │ │ │ | ||
| 38 | //! │ ┌──────┐ (5) │ │ ┌─────┐ | ||
| 39 | //! │ IDLE │◀────────────────┘ └─▶│ END │ │ | ||
| 40 | //! │ └──────┘ └─────┘ | ||
| 41 | //! ┌──────────────────────┐ │ | ||
| 42 | //! └ ┤ Task Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 43 | //! └──────────────────────┘ | ||
| 44 | //! ``` | ||
| 45 | //! | ||
| 46 | //! 1. A task is spawned, `_embassy_trace_task_new` is called | ||
| 47 | //! 2. A task is enqueued for the first time, `_embassy_trace_task_ready_begin` is called | ||
| 48 | //! 3. A task is polled, `_embassy_trace_task_exec_begin` is called | ||
| 49 | //! 4. WHILE a task is polled, the task is re-awoken, and `_embassy_trace_task_ready_begin` is | ||
| 50 | //! called. The task does not IMMEDIATELY move state, until polling is complete and the | ||
| 51 | //! RUNNING state is existed. `_embassy_trace_task_exec_end` is called when polling is | ||
| 52 | //! complete, marking the transition to WAITING | ||
| 53 | //! 5. Polling is complete, `_embassy_trace_task_exec_end` is called | ||
| 54 | //! 6. The task has completed, and `_embassy_trace_task_end` is called | ||
| 55 | //! 7. A task is awoken, `_embassy_trace_task_ready_begin` is called | ||
| 56 | //! | ||
| 57 | //! ## Executor Tracing lifecycle | ||
| 58 | //! | ||
| 59 | //! ```text | ||
| 60 | //! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 61 | //! │(1) │ | ||
| 62 | //! │ │ | ||
| 63 | //! ╔═══▼══╗ (2) ┌────────────┐ (3) ┌─────────┐ │ | ||
| 64 | //! │ ║ IDLE ║──────────▶│ SCHEDULING │──────▶│ POLLING │ | ||
| 65 | //! ╚══════╝ └────────────┘ └─────────┘ │ | ||
| 66 | //! │ ▲ │ ▲ │ | ||
| 67 | //! │ (5) │ │ (4) │ │ | ||
| 68 | //! │ └──────────────┘ └────────────┘ | ||
| 69 | //! ┌──────────────────────────┐ │ | ||
| 70 | //! └ ┤ Executor Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 71 | //! └──────────────────────────┘ | ||
| 72 | //! ``` | ||
| 73 | //! | ||
| 74 | //! 1. The executor is started (no associated trace) | ||
| 75 | //! 2. A task on this executor is awoken. `_embassy_trace_task_ready_begin` is called | ||
| 76 | //! when this occurs, and `_embassy_trace_poll_start` is called when the executor | ||
| 77 | //! actually begins running | ||
| 78 | //! 3. The executor has decided a task to poll. `_embassy_trace_task_exec_begin` is called | ||
| 79 | //! 4. The executor finishes polling the task. `_embassy_trace_task_exec_end` is called | ||
| 80 | //! 5. The executor has finished polling tasks. `_embassy_trace_executor_idle` is called | ||
| 81 | |||
| 1 | #![allow(unused)] | 82 | #![allow(unused)] |
| 83 | |||
| 2 | use crate::raw::{SyncExecutor, TaskRef}; | 84 | use crate::raw::{SyncExecutor, TaskRef}; |
| 3 | 85 | ||
| 4 | #[cfg(not(feature = "rtos-trace"))] | 86 | #[cfg(not(feature = "rtos-trace"))] |
| 5 | extern "Rust" { | 87 | extern "Rust" { |
| 88 | /// This callback is called when the executor begins polling. This will always | ||
| 89 | /// be paired with a later call to `_embassy_trace_executor_idle`. | ||
| 90 | /// | ||
| 91 | /// This marks the EXECUTOR state transition from IDLE -> SCHEDULING. | ||
| 92 | fn _embassy_trace_poll_start(executor_id: u32); | ||
| 93 | |||
| 94 | /// This callback is called AFTER a task is initialized/allocated, and BEFORE | ||
| 95 | /// it is enqueued to run for the first time. If the task ends (and does not | ||
| 96 | /// loop "forever"), there will be a matching call to `_embassy_trace_task_end`. | ||
| 97 | /// | ||
| 98 | /// Tasks start life in the SPAWNED state. | ||
| 6 | fn _embassy_trace_task_new(executor_id: u32, task_id: u32); | 99 | fn _embassy_trace_task_new(executor_id: u32, task_id: u32); |
| 100 | |||
| 101 | /// This callback is called AFTER a task is destructed/freed. This will always | ||
| 102 | /// have a prior matching call to `_embassy_trace_task_new`. | ||
| 103 | fn _embassy_trace_task_end(executor_id: u32, task_id: u32); | ||
| 104 | |||
| 105 | /// This callback is called AFTER a task has been dequeued from the runqueue, | ||
| 106 | /// and BEFORE the task is polled. There will always be a matching call to | ||
| 107 | /// `_embassy_trace_task_exec_end`. | ||
| 108 | /// | ||
| 109 | /// This marks the TASK state transition from WAITING -> RUNNING | ||
| 110 | /// This marks the EXECUTOR state transition from SCHEDULING -> POLLING | ||
| 7 | fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32); | 111 | fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32); |
| 112 | |||
| 113 | /// This callback is called AFTER a task has completed polling. There will | ||
| 114 | /// always be a matching call to `_embassy_trace_task_exec_begin`. | ||
| 115 | /// | ||
| 116 | /// This marks the TASK state transition from either: | ||
| 117 | /// * RUNNING -> IDLE - if there were no `_embassy_trace_task_ready_begin` events | ||
| 118 | /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task | ||
| 119 | /// * RUNNING -> WAITING - if there WAS a `_embassy_trace_task_ready_begin` event | ||
| 120 | /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task | ||
| 121 | /// | ||
| 122 | /// This marks the EXECUTOR state transition from POLLING -> SCHEDULING | ||
| 8 | fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32); | 123 | fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32); |
| 124 | |||
| 125 | /// This callback is called AFTER the waker for a task is awoken, and BEFORE it | ||
| 126 | /// is added to the run queue. | ||
| 127 | /// | ||
| 128 | /// If the given task is currently RUNNING, this marks no state change, BUT the | ||
| 129 | /// RUNNING task will then move to the WAITING stage when polling is complete. | ||
| 130 | /// | ||
| 131 | /// If the given task is currently IDLE, this marks the TASK state transition | ||
| 132 | /// from IDLE -> WAITING. | ||
| 133 | /// | ||
| 134 | /// NOTE: This may be called from an interrupt, outside the context of the current | ||
| 135 | /// task or executor. | ||
| 9 | fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32); | 136 | fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32); |
| 137 | |||
| 138 | /// This callback is called AFTER all dequeued tasks in a single call to poll | ||
| 139 | /// have been processed. This will always be paired with a call to | ||
| 140 | /// `_embassy_trace_executor_idle`. | ||
| 141 | /// | ||
| 142 | /// This marks the EXECUTOR state transition from SCHEDULING -> IDLE | ||
| 10 | fn _embassy_trace_executor_idle(executor_id: u32); | 143 | fn _embassy_trace_executor_idle(executor_id: u32); |
| 11 | } | 144 | } |
| 12 | 145 | ||
| 13 | #[inline] | 146 | #[inline] |
| 147 | pub(crate) fn poll_start(executor: &SyncExecutor) { | ||
| 148 | #[cfg(not(feature = "rtos-trace"))] | ||
| 149 | unsafe { | ||
| 150 | _embassy_trace_poll_start(executor as *const _ as u32) | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | #[inline] | ||
| 14 | pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { | 155 | pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { |
| 15 | #[cfg(not(feature = "rtos-trace"))] | 156 | #[cfg(not(feature = "rtos-trace"))] |
| 16 | unsafe { | 157 | unsafe { |
| @@ -22,6 +163,14 @@ pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { | |||
| 22 | } | 163 | } |
| 23 | 164 | ||
| 24 | #[inline] | 165 | #[inline] |
| 166 | pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) { | ||
| 167 | #[cfg(not(feature = "rtos-trace"))] | ||
| 168 | unsafe { | ||
| 169 | _embassy_trace_task_end(executor as u32, task.as_ptr() as u32) | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | #[inline] | ||
| 25 | pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { | 174 | pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { |
| 26 | #[cfg(not(feature = "rtos-trace"))] | 175 | #[cfg(not(feature = "rtos-trace"))] |
| 27 | unsafe { | 176 | unsafe { |
