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