1use fuchsia_async::ScopeHandle;
6use fuchsia_async::instrument::{AtomicFutureHandle, Hooks, TaskInstrument};
7use fuchsia_inspect::{self as inspect, Node, NumericProperty, Property};
8use fuchsia_sync::Mutex;
9use std::any::Any;
10use std::collections::HashSet;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicUsize, Ordering};
13
14fn get_unique_name(names: &mut HashSet<String>, base_name: String) -> String {
15 if names.insert(base_name.clone()) {
16 return base_name;
17 }
18 let mut i = 1;
19 loop {
20 let new_name = format!("{}_{}", base_name, i);
21 if names.insert(new_name.clone()) {
22 return new_name;
23 }
24 i += 1;
25 }
26}
27
28pub struct InspectTaskInstrument {
30 root: Node,
31 next_task_id: AtomicUsize,
32 child_names: Arc<Mutex<HashSet<String>>>,
33}
34
35struct TaskNode {
36 _node: Node,
37 polls: inspect::UintProperty,
38 completed: inspect::BoolProperty,
39 max_poll_duration_micros: inspect::UintProperty,
40}
41
42struct InspectHooks {
43 task_node: TaskNode,
44 poll_start_time: zx::BootInstant,
45 max_poll_duration_micros: u64,
46 name: String,
47 parent_names: Arc<Mutex<HashSet<String>>>,
48}
49
50impl Drop for InspectHooks {
51 fn drop(&mut self) {
52 self.parent_names.lock().remove(&self.name);
53 }
54}
55
56impl Hooks for InspectHooks {
57 fn task_completed(&mut self) {
58 self.task_node.completed.set(true);
59 }
60
61 fn task_poll_end(&mut self) {
62 let duration = fuchsia_async::BootInstant::now().into_zx() - self.poll_start_time;
63 let duration_micros = duration.into_micros() as u64;
64 if duration_micros > self.max_poll_duration_micros {
65 self.max_poll_duration_micros = duration_micros;
66 self.task_node.max_poll_duration_micros.set(duration_micros);
67 }
68 }
69
70 fn task_poll_start(&mut self) {
71 self.poll_start_time = fuchsia_async::BootInstant::now().into_zx();
72 self.task_node.polls.add(1);
73 }
74}
75
76struct ScopeInspect {
77 node: Node,
78 child_names: Arc<Mutex<HashSet<String>>>,
79 name: String,
80 parent_names: Arc<Mutex<HashSet<String>>>,
81}
82
83impl Drop for ScopeInspect {
84 fn drop(&mut self) {
85 self.parent_names.lock().remove(&self.name);
86 }
87}
88
89pub struct InspectTaskConfiguration {
94 pub inspect_root: Node,
96}
97
98impl InspectTaskConfiguration {
99 pub fn new(inspect_root: Node) -> Self {
100 Self { inspect_root }
101 }
102}
103
104impl InspectTaskInstrument {
105 pub fn new(config: InspectTaskConfiguration) -> Arc<Self> {
107 Arc::new(Self {
108 root: config.inspect_root,
109 next_task_id: AtomicUsize::new(0),
110 child_names: Arc::new(Mutex::new(HashSet::new())),
111 })
112 }
113}
114
115impl TaskInstrument for InspectTaskInstrument {
116 fn task_created<'a>(&self, parent_scope: &ScopeHandle, task: &mut AtomicFutureHandle<'a>) {
117 let id = self.next_task_id.fetch_add(1, Ordering::Relaxed);
118 let base_name = format!("task_{}", id);
119 let (parent_node, parent_names) = parent_scope
120 .instrument_data()
121 .and_then(|scope| scope.downcast_ref::<ScopeInspect>())
122 .map(|scope| (&scope.node, Arc::clone(&scope.child_names)))
123 .unwrap_or_else(|| (&self.root, Arc::clone(&self.child_names)));
124 let name = {
125 let mut names = parent_names.lock();
126 get_unique_name(&mut names, base_name)
127 };
128 let node = parent_node.create_child(&name);
129 let polls = node.create_uint("polls", 0);
130 let completed = node.create_bool("completed", false);
131 let max_poll_duration_micros = node.create_uint("max_poll_duration_micros", 0);
132 let task_node = TaskNode { _node: node, polls, completed, max_poll_duration_micros };
133 task.add_hooks(InspectHooks {
134 task_node,
135 poll_start_time: zx::BootInstant::get(),
136 max_poll_duration_micros: 0,
137 name,
138 parent_names,
139 })
140 }
141
142 fn scope_created(
143 &self,
144 scope_name: &str,
145 parent_scope: Option<&ScopeHandle>,
146 ) -> Box<dyn Any + Send + Sync> {
147 let (parent_node, parent_names) = parent_scope
148 .map(|scope| scope.instrument_data())
149 .flatten()
150 .and_then(|scope| scope.downcast_ref::<ScopeInspect>())
151 .map(|scope| (&scope.node, Arc::clone(&scope.child_names)))
152 .unwrap_or_else(|| (&self.root, Arc::clone(&self.child_names)));
153 let name = {
154 let mut names = parent_names.lock();
155 get_unique_name(&mut names, scope_name.to_string())
156 };
157 let node = parent_node.create_child(&name);
158 Box::new(ScopeInspect {
159 node,
160 child_names: Arc::new(Mutex::new(HashSet::new())),
161 name,
162 parent_names,
163 })
164 }
165}
166
167pub fn default_root() -> Node {
168 fuchsia_inspect::component::inspector()
169 .root()
170 .create_child("fuchsia.inspect.AsyncInstrumentation")
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use diagnostics_assertions::assert_data_tree;
177 use fuchsia_async::{self as fasync, EHandle, MonotonicInstant};
178 use std::pin::pin;
179
180 #[fuchsia::test]
181 fn test_max_poll_duration() {
182 let inspector = inspect::Inspector::default();
183 let instrument = InspectTaskInstrument::new(InspectTaskConfiguration::new(
184 inspector.root().clone_weak(),
185 ));
186 let mut exec =
187 fasync::TestExecutorBuilder::new().fake_time(true).instrument(instrument).build();
188
189 exec.set_fake_time(MonotonicInstant::from_nanos(0));
190 let mut fut = pin!(async {
191 let child_scope = fuchsia_async::Scope::new_with_name("task_1");
192 let child_task = fuchsia_async::Task::local(async move {
193 futures::future::pending::<()>().await;
194 });
195 assert_data_tree!(inspector, root: {
196 root: {
197 task_0:{
199 polls: 1u64,
200 completed: false,
201 max_poll_duration_micros: 0u64,
202 },
203 task_1:{},
205 task_1_1:{
206 polls: 0u64,
207 completed: false,
208 max_poll_duration_micros: 0u64,
209 },
210 }
211 });
212 drop(child_task);
214 drop(child_scope);
215 fuchsia_async::yield_now().await;
219 assert_data_tree!(inspector, root: {
220 root: {
221 task_0:{
223 polls: 2u64,
224 completed: false,
225 max_poll_duration_micros: 0u64,
226 },
227 }
228 });
229 EHandle::local().set_fake_time(MonotonicInstant::from_nanos(1000 * 200));
231 fuchsia_async::yield_now().await;
232 assert_data_tree!(inspector, root: {
233 root: {
234 task_0:{
236 polls: 3u64,
237 completed: false,
238 max_poll_duration_micros: 200u64,
239 },
240 }
241 });
242 let scope = fuchsia_async::Scope::new_with_name("test scope");
243 let _scope_2 = fuchsia_async::Scope::new_with_name("test scope");
244 let _scope_3 = fuchsia_async::Scope::new_with_name("test scope");
245
246 let child_scope = scope.new_child_with_name("test child scope");
247 let _child_task = child_scope.spawn(async move {});
248 assert_data_tree!(inspector, root: {
250 root: {
251 task_0:{
253 polls: 3u64,
254 completed: false,
255 max_poll_duration_micros: 200u64,
256 },
257 "test scope":{
258 "test child scope":{
259 task_2:{
260 polls: 0u64,
261 completed: false,
262 max_poll_duration_micros: 0u64,
263 }
264 }
265 },
266 "test scope_1":{},
267 "test scope_2":{},
268 }
269 });
270 });
271 let _ = exec.run_until_stalled(&mut fut);
272 }
273}