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