Skip to main content

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::{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        /* exact_match= */ 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        // Run the command and mock driver development server.
184        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            // Root device
374            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            // Composite parent
386            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            // Composite child
398            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}