driver_tools/subcommands/dump/
mod.rs1pub mod args;
6
7use anyhow::{Result, format_err};
8use args::DumpCommand;
9use flex_fuchsia_driver_development as fdd;
10use fuchsia_driver_dev::Device;
11#[cfg(feature = "fdomain")]
12use fuchsia_driver_dev_fdomain as fuchsia_driver_dev;
13use std::collections::{BTreeMap, VecDeque};
14use std::io::Write;
15
16const INDENT_SIZE: usize = 2;
17
18trait NodeInfoPrinter {
19 fn print(&self, writer: &mut dyn Write, indent_level: usize) -> Result<()>;
20
21 fn print_graph_node(&self, writer: &mut dyn Write) -> Result<()>;
22 fn print_graph_edge(&self, writer: &mut dyn Write, child: &fdd::NodeInfo) -> Result<()>;
23}
24
25impl NodeInfoPrinter for Device {
26 fn print(&self, writer: &mut dyn Write, indent_level: usize) -> Result<()> {
27 let koid_str = match &self.0.driver_host_koid {
28 Some(koid) => format!("{}", koid),
29 None => format!("None"),
30 };
31
32 writeln!(
33 writer,
34 "{:indent$}[{}] pid={} {}",
35 "",
36 self.extract_name()?,
37 koid_str,
38 self.0.bound_driver_url.as_deref().unwrap_or(""),
39 indent = indent_level * INDENT_SIZE,
40 )?;
41 Ok(())
42 }
43
44 fn print_graph_node(&self, writer: &mut dyn Write) -> Result<()> {
45 writeln!(
46 writer,
47 " \"{}\" [label=\"{}\"]",
48 self.0.id.as_ref().ok_or_else(|| format_err!("Node missing id"))?,
49 self.extract_name()?,
50 )?;
51 Ok(())
52 }
53
54 fn print_graph_edge(&self, writer: &mut dyn Write, child: &fdd::NodeInfo) -> Result<()> {
55 writeln!(
56 writer,
57 " \"{}\" -> \"{}\"",
58 self.0.id.as_ref().ok_or_else(|| format_err!("Node missing id"))?,
59 child.id.as_ref().ok_or_else(|| format_err!("Child node missing id"))?
60 )?;
61 Ok(())
62 }
63}
64
65fn print_tree(
66 writer: &mut dyn Write,
67 root: &Device,
68 device_map: &BTreeMap<u64, &Device>,
69) -> Result<()> {
70 let mut stack = VecDeque::new();
71 stack.push_front((root, 0));
72 while let Some((device, indent_level)) = stack.pop_front() {
73 device.print(writer, indent_level)?;
74 if let Some(child_ids) = &device.0.child_ids {
75 for id in child_ids.iter().rev() {
76 if let Some(child) = device_map.get(id) {
77 stack.push_front((child, indent_level + 1));
78 }
79 }
80 }
81 }
82 Ok(())
83}
84
85pub async fn dump(
86 cmd: DumpCommand,
87 writer: &mut dyn Write,
88 driver_development_proxy: fdd::ManagerProxy,
89) -> Result<()> {
90 let devices: Vec<Device> = fuchsia_driver_dev::get_device_info(
91 &driver_development_proxy,
92 &[],
93 false,
94 )
95 .await?
96 .into_iter()
97 .map(|device| device.into())
98 .collect();
99
100 let device_map = devices
101 .iter()
102 .map(|device| {
103 if let Some(id) = device.0.id {
104 Ok((id, device))
105 } else {
106 Err(format_err!("Missing device id"))
107 }
108 })
109 .collect::<Result<BTreeMap<_, _>>>()?;
110
111 if cmd.graph {
112 let digraph_prefix = r#"digraph {
113 forcelabels = true; splines="ortho"; ranksep = 1.2; nodesep = 0.5;
114 node [ shape = "box" color = " #2a5b4f" penwidth = 2.25 fontname = "prompt medium" fontsize = 10 margin = 0.22 ];
115 edge [ color = " #37474f" penwidth = 1 style = dashed fontname = "roboto mono" fontsize = 10 ];"#;
116 writeln!(writer, "{}", digraph_prefix)?;
117 for device in devices.iter() {
118 device.print_graph_node(writer)?;
119 }
120
121 for device in devices.iter() {
122 if let Some(child_ids) = &device.0.child_ids {
123 for id in child_ids.iter().rev() {
124 let child = &device_map[&id];
125 device.print_graph_edge(writer, &child.0)?;
126 }
127 }
128 }
129
130 writeln!(writer, "}}")?;
131 } else {
132 let roots = devices.iter().filter(|device| {
133 if let Some(node_filter) = &cmd.device {
134 let name = device.extract_name().unwrap_or("");
135 name == node_filter
136 } else {
137 if let Some(parent_ids) = device.0.parent_ids.as_ref() {
138 for parent_id in parent_ids.iter() {
139 if device_map.contains_key(parent_id) {
140 return false;
141 }
142 }
143 true
144 } else {
145 true
146 }
147 }
148 });
149
150 for root in roots {
151 print_tree(writer, root, &device_map)?;
152 }
153 }
154 Ok(())
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use anyhow::Context;
161 use argh::FromArgs;
162 use flex_client::fidl::ServerEnd;
163 use fuchsia_async as fasync;
164 use futures::future::{Future, FutureExt};
165 use futures::stream::StreamExt;
166 #[cfg(feature = "fdomain")]
167 use std::sync::Arc;
168
169 async fn test_dump<F, Fut>(
170 #[cfg(feature = "fdomain")] client: Arc<flex_client::Client>,
171 cmd: DumpCommand,
172 on_driver_development_request: F,
173 ) -> Result<String>
174 where
175 F: Fn(fdd::ManagerRequest) -> Fut + Send + Sync + 'static,
176 Fut: Future<Output = Result<()>> + Send + Sync,
177 {
178 #[cfg(not(feature = "fdomain"))]
179 let client = flex_client::fidl::ZirconClient;
180 let (driver_development_proxy, mut driver_development_requests) =
181 client.create_proxy_and_stream::<fdd::ManagerMarker>();
182
183 let mut writer = Vec::new();
185 let request_handler_task = fasync::Task::spawn(async move {
186 while let Some(res) = driver_development_requests.next().await {
187 let request = res.context("Failed to get next request")?;
188 on_driver_development_request(request).await.context("Failed to handle request")?;
189 }
190 anyhow::bail!("Driver development request stream unexpectedly closed");
191 });
192 futures::select! {
193 res = request_handler_task.fuse() => {
194 res?;
195 anyhow::bail!("Request handler task unexpectedly finished");
196 }
197 res = dump(cmd, &mut writer, driver_development_proxy).fuse() => res.context("Dump command failed")?,
198 }
199
200 String::from_utf8(writer).context("Failed to convert dump output to a string")
201 }
202
203 async fn run_device_info_iterator_server(
204 mut device_infos: Vec<fdd::NodeInfo>,
205 iterator: ServerEnd<fdd::NodeInfoIteratorMarker>,
206 ) -> Result<()> {
207 let mut iterator = iterator.into_stream();
208 while let Some(res) = iterator.next().await {
209 let request = res.context("Failed to get request")?;
210 match request {
211 fdd::NodeInfoIteratorRequest::GetNext { responder } => {
212 responder
213 .send(&device_infos)
214 .context("Failed to send device infos to responder")?;
215 device_infos.clear();
216 }
217 }
218 }
219 Ok(())
220 }
221
222 #[fasync::run_singlethreaded(test)]
223 async fn test_simple() {
224 #[cfg(feature = "fdomain")]
225 let client = fdomain_local::local_client_empty();
226 let cmd = DumpCommand::from_args(&["dump"], &[]).unwrap();
227
228 let output = test_dump(
229 #[cfg(feature = "fdomain")]
230 Arc::clone(&client),
231 cmd,
232 |request: fdd::ManagerRequest| async move {
233 match request {
234 fdd::ManagerRequest::GetNodeInfo {
235 node_filter: _,
236 iterator,
237 control_handle: _,
238 exact_match: _,
239 } => {
240 let parent_id = 0;
241 let child_id = 1;
242 run_device_info_iterator_server(
243 vec![
244 fdd::NodeInfo {
245 id: Some(parent_id),
246 parent_ids: Some(Vec::new()),
247 child_ids: Some(vec![child_id]),
248 driver_host_koid: Some(0),
249 bound_driver_url: Some(String::from(
250 "fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm",
251 )),
252 moniker: Some(String::from("foo")),
253 ..Default::default()
254 },
255 fdd::NodeInfo {
256 id: Some(child_id),
257 parent_ids: Some(vec![parent_id]),
258 child_ids: Some(Vec::new()),
259 driver_host_koid: Some(0),
260 bound_driver_url: Some(String::from(
261 "fuchsia-pkg://fuchsia.com/bar-package#meta/bar.cm",
262 )),
263 moniker: Some(String::from("foo.bar")),
264 ..Default::default()
265 },
266 ],
267 iterator,
268 )
269 .await
270 .context("Failed to run device info iterator server")?;
271 }
272 _ => {}
273 }
274 Ok(())
275 },
276 )
277 .await
278 .unwrap();
279
280 assert_eq!(
281 output,
282 r#"[foo] pid=0 fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm
283 [bar] pid=0 fuchsia-pkg://fuchsia.com/bar-package#meta/bar.cm
284"#
285 );
286 }
287
288 #[fasync::run_singlethreaded(test)]
289 async fn test_duplicates_are_filtered() {
290 #[cfg(feature = "fdomain")]
291 let client = fdomain_local::local_client_empty();
292 let cmd = DumpCommand::from_args(&["dump"], &[]).unwrap();
293
294 let output = test_dump(
295 #[cfg(feature = "fdomain")]
296 Arc::clone(&client),
297 cmd,
298 |request: fdd::ManagerRequest| async move {
299 match request {
300 fdd::ManagerRequest::GetNodeInfo {
301 node_filter: _,
302 iterator,
303 control_handle: _,
304 exact_match: _,
305 } => {
306 run_device_info_iterator_server(make_test_devices(), iterator)
307 .await
308 .context("Failed to run device info iterator server")?;
309 }
310 _ => {}
311 }
312 Ok(())
313 },
314 )
315 .await
316 .unwrap();
317
318 assert_eq!(
319 output,
320 r#"[platform] pid=0 fuchsia-pkg://fuchsia.com/root-package#meta/root.cm
321 [parent] pid=0 fuchsia-pkg://fuchsia.com/parent-package#meta/parent.cm
322 [child] pid=0 fuchsia-pkg://fuchsia.com/child-package#meta/child.cm
323[parent] pid=0 fuchsia-pkg://fuchsia.com/parent-package#meta/parent.cm
324 [child] pid=0 fuchsia-pkg://fuchsia.com/child-package#meta/child.cm
325"#
326 );
327 }
328
329 #[fasync::run_singlethreaded(test)]
330 async fn test_with_node_filter() {
331 #[cfg(feature = "fdomain")]
332 let client = fdomain_local::local_client_empty();
333 let cmd = DumpCommand::from_args(&["dump"], &["parent"]).unwrap();
334
335 let output = test_dump(
336 #[cfg(feature = "fdomain")]
337 Arc::clone(&client),
338 cmd,
339 |request: fdd::ManagerRequest| async move {
340 match request {
341 fdd::ManagerRequest::GetNodeInfo {
342 node_filter: _,
343 iterator,
344 control_handle: _,
345 exact_match: _,
346 } => {
347 run_device_info_iterator_server(make_test_devices(), iterator)
348 .await
349 .context("Failed to run device info iterator server")?;
350 }
351 _ => {}
352 }
353 Ok(())
354 },
355 )
356 .await
357 .unwrap();
358
359 assert_eq!(
360 output,
361 r#"[parent] pid=0 fuchsia-pkg://fuchsia.com/parent-package#meta/parent.cm
362 [child] pid=0 fuchsia-pkg://fuchsia.com/child-package#meta/child.cm
363"#
364 );
365 }
366
367 fn make_test_devices() -> Vec<fdd::NodeInfo> {
368 let null_id = 0;
369 let root_id = 1;
370 let composite_parent_id = 2;
371 let composite_child_id = 3;
372 vec![
373 fdd::NodeInfo {
375 id: Some(root_id),
376 parent_ids: Some(vec![null_id]),
377 child_ids: Some(vec![composite_parent_id]),
378 driver_host_koid: Some(0),
379 bound_driver_url: Some(String::from(
380 "fuchsia-pkg://fuchsia.com/root-package#meta/root.cm",
381 )),
382 moniker: Some(String::from("sys.platform")),
383 ..Default::default()
384 },
385 fdd::NodeInfo {
387 id: Some(composite_parent_id),
388 parent_ids: None,
389 child_ids: Some(vec![composite_child_id]),
390 driver_host_koid: Some(0),
391 bound_driver_url: Some(String::from(
392 "fuchsia-pkg://fuchsia.com/parent-package#meta/parent.cm",
393 )),
394 moniker: Some(String::from("parent")),
395 ..Default::default()
396 },
397 fdd::NodeInfo {
399 id: Some(composite_child_id),
400 parent_ids: Some(vec![composite_parent_id]),
401 child_ids: Some(Vec::new()),
402 driver_host_koid: Some(0),
403 bound_driver_url: Some(String::from(
404 "fuchsia-pkg://fuchsia.com/child-package#meta/child.cm",
405 )),
406 moniker: Some(String::from("parent.child")),
407 ..Default::default()
408 },
409 ]
410 }
411}