driver_tools/subcommands/dump/
mod.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub 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        /* exact_match= */ 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        // Run the command and mock driver development server.
174        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            // Root device
343            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            // Composite parent
355            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            // Composite child
367            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}