aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2025-04-02 00:34:05 +0000
committerGitHub <[email protected]>2025-04-02 00:34:05 +0000
commita137a160671d3ede48519e5faf316f31f6f6cbd3 (patch)
treea2405acf82fd0710e7b83c220e570c57a3e14c80
parent3bc809eb3ece600164a37a80cd64154efa6d15a5 (diff)
parentef3c1b87d16d17e312e869546fae00f895f68989 (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.rs15
-rw-r--r--embassy-executor/src/raw/trace.rs149
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
2use crate::raw::{SyncExecutor, TaskRef}; 84use crate::raw::{SyncExecutor, TaskRef};
3 85
4#[cfg(not(feature = "rtos-trace"))] 86#[cfg(not(feature = "rtos-trace"))]
5extern "Rust" { 87extern "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]
147pub(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]
14pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { 155pub(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]
166pub(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]
25pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { 174pub(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 {