component_debug/cli/
list.rs

1// Copyright 2023 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
5use crate::realm::{get_all_instances, Instance};
6use ansi_term::Colour;
7use anyhow::Result;
8use fidl_fuchsia_sys2 as fsys;
9use prettytable::format::consts::FORMAT_CLEAN;
10use prettytable::{cell, row, Table};
11use std::collections::HashSet;
12use std::str::FromStr;
13
14/// Filters that can be applied when listing components
15#[derive(Debug, PartialEq)]
16pub enum ListFilter {
17    Running,
18    Stopped,
19    /// Filters components that are an ancestor of the component with the given name.
20    /// Includes the named component.
21    Ancestor(String),
22    /// Filters components that are a descendant of the component with the given name.
23    /// Includes the named component.
24    Descendant(String),
25    /// Filters components that are a relative (either an ancestor or a descendant) of the
26    /// component with the given name. Includes the named component.
27    Relative(String),
28}
29
30impl FromStr for ListFilter {
31    type Err = &'static str;
32
33    fn from_str(s: &str) -> Result<Self, Self::Err> {
34        match s {
35            "running" => Ok(ListFilter::Running),
36            "stopped" => Ok(ListFilter::Stopped),
37            filter => match filter.split_once(":") {
38                Some((function, arg)) => match function {
39                    "ancestor" | "ancestors" => Ok(ListFilter::Ancestor(arg.to_string())),
40                    "descendant" | "descendants" => Ok(ListFilter::Descendant(arg.to_string())),
41                    "relative" | "relatives" => Ok(ListFilter::Relative(arg.to_string())),
42                    _ => Err("unknown function for list filter."),
43                },
44                None => Err("list filter should be 'running', 'stopped', 'ancestors:<component_name>', 'descendants:<component_name>', or 'relatives:<component_name>'."),
45            },
46        }
47    }
48}
49
50pub async fn list_cmd_print<W: std::io::Write>(
51    filter: Option<ListFilter>,
52    verbose: bool,
53    realm_query: fsys::RealmQueryProxy,
54    mut writer: W,
55) -> Result<()> {
56    let instances = get_instances_matching_filter(filter, &realm_query).await?;
57
58    if verbose {
59        let table = create_table(instances);
60        table.print(&mut writer)?;
61    } else {
62        for instance in instances {
63            writeln!(writer, "{}", instance.moniker)?;
64        }
65    }
66
67    Ok(())
68}
69
70pub async fn list_cmd_serialized(
71    filter: Option<ListFilter>,
72    realm_query: fsys::RealmQueryProxy,
73) -> Result<Vec<Instance>> {
74    let basic_infos = get_instances_matching_filter(filter, &realm_query).await?;
75    Ok(basic_infos)
76}
77
78/// Creates a verbose table containing information about all instances.
79fn create_table(instances: Vec<Instance>) -> Table {
80    let mut table = Table::new();
81    table.set_format(*FORMAT_CLEAN);
82    table.set_titles(row!("State", "Moniker", "URL"));
83
84    for instance in instances {
85        let state = instance.resolved_info.map_or(Colour::Red.paint("Stopped"), |r| {
86            r.execution_info
87                .map_or(Colour::Yellow.paint("Resolved"), |_| Colour::Green.paint("Running"))
88        });
89
90        table.add_row(row!(state, instance.moniker.to_string(), instance.url));
91    }
92    table
93}
94
95pub async fn get_instances_matching_filter(
96    filter: Option<ListFilter>,
97    realm_query: &fsys::RealmQueryProxy,
98) -> Result<Vec<Instance>> {
99    let instances = get_all_instances(realm_query).await?;
100
101    let mut instances = match filter {
102        Some(ListFilter::Running) => instances
103            .into_iter()
104            .filter(|i| i.resolved_info.as_ref().map_or(false, |r| r.execution_info.is_some()))
105            .collect(),
106        Some(ListFilter::Stopped) => instances
107            .into_iter()
108            .filter(|i| i.resolved_info.as_ref().map_or(true, |r| r.execution_info.is_none()))
109            .collect(),
110        Some(ListFilter::Ancestor(m)) => filter_ancestors(instances, m),
111        Some(ListFilter::Descendant(m)) => filter_descendants(instances, m),
112        Some(ListFilter::Relative(m)) => filter_relatives(instances, m),
113        _ => instances,
114    };
115
116    instances.sort_by_key(|c| c.moniker.to_string());
117
118    Ok(instances)
119}
120
121fn filter_ancestors(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
122    let mut ancestors = HashSet::new();
123
124    // Find monikers with this child as the leaf.
125    for instance in &instances {
126        if let Some(child) = instance.moniker.leaf() {
127            if child.to_string() == child_str {
128                // Add this moniker to ancestor list.
129                let mut cur_moniker = instance.moniker.clone();
130                ancestors.insert(cur_moniker.clone());
131
132                // Loop over parents of this moniker and add them to ancestor list.
133                while let Some(parent) = cur_moniker.parent() {
134                    ancestors.insert(parent.clone());
135                    cur_moniker = parent;
136                }
137            }
138        }
139    }
140
141    instances.into_iter().filter(|i| ancestors.contains(&i.moniker)).collect()
142}
143
144fn filter_descendants(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
145    let mut descendants = HashSet::new();
146
147    // Find monikers with this child as the leaf.
148    for instance in &instances {
149        if let Some(child) = instance.moniker.leaf() {
150            if child.to_string() == child_str {
151                // Get all descendants of this moniker.
152                for possible_child_instance in &instances {
153                    if possible_child_instance.moniker.has_prefix(&instance.moniker) {
154                        descendants.insert(possible_child_instance.moniker.clone());
155                    }
156                }
157            }
158        }
159    }
160
161    instances.into_iter().filter(|i| descendants.contains(&i.moniker)).collect()
162}
163
164fn filter_relatives(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
165    let mut relatives = HashSet::new();
166
167    // Find monikers with this child as the leaf.
168    for instance in &instances {
169        if let Some(child) = instance.moniker.leaf() {
170            if child.to_string() == child_str {
171                // Loop over parents of this moniker and add them to relatives list.
172                let mut cur_moniker = instance.moniker.clone();
173                while let Some(parent) = cur_moniker.parent() {
174                    relatives.insert(parent.clone());
175                    cur_moniker = parent;
176                }
177
178                // Get all descendants of this moniker and add them to relatives list.
179                for possible_child_instance in &instances {
180                    if possible_child_instance.moniker.has_prefix(&instance.moniker) {
181                        relatives.insert(possible_child_instance.moniker.clone());
182                    }
183                }
184            }
185        }
186    }
187
188    instances.into_iter().filter(|i| relatives.contains(&i.moniker)).collect()
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use crate::test_utils::*;
195    use moniker::Moniker;
196    use std::collections::HashMap;
197
198    fn create_query() -> fsys::RealmQueryProxy {
199        // Serve RealmQuery for CML components.
200        let query = serve_realm_query(
201            vec![
202                fsys::Instance {
203                    moniker: Some("./".to_string()),
204                    url: Some("fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string()),
205                    instance_id: None,
206                    resolved_info: Some(fsys::ResolvedInfo {
207                        resolved_url: Some(
208                            "fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string(),
209                        ),
210                        execution_info: None,
211                        ..Default::default()
212                    }),
213                    ..Default::default()
214                },
215                fsys::Instance {
216                    moniker: Some("./core".to_string()),
217                    url: Some("fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string()),
218                    instance_id: None,
219                    resolved_info: Some(fsys::ResolvedInfo {
220                        resolved_url: Some(
221                            "fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string(),
222                        ),
223                        execution_info: Some(fsys::ExecutionInfo {
224                            start_reason: Some("Debugging Workflow".to_string()),
225                            ..Default::default()
226                        }),
227                        ..Default::default()
228                    }),
229                    ..Default::default()
230                },
231                fsys::Instance {
232                    moniker: Some("./core/appmgr".to_string()),
233                    url: Some("fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string()),
234                    instance_id: None,
235                    resolved_info: Some(fsys::ResolvedInfo {
236                        resolved_url: Some(
237                            "fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string(),
238                        ),
239                        execution_info: Some(fsys::ExecutionInfo {
240                            start_reason: Some("Debugging Workflow".to_string()),
241                            ..Default::default()
242                        }),
243                        ..Default::default()
244                    }),
245                    ..Default::default()
246                },
247            ],
248            HashMap::new(),
249            HashMap::new(),
250            HashMap::new(),
251        );
252        query
253    }
254
255    #[fuchsia::test]
256    async fn no_filter() {
257        let query = create_query();
258
259        let instances = get_instances_matching_filter(None, &query).await.unwrap();
260        assert_eq!(
261            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
262            vec![
263                Moniker::root(),
264                Moniker::parse_str("/core").unwrap(),
265                Moniker::parse_str("/core/appmgr").unwrap(),
266            ]
267        );
268    }
269
270    #[fuchsia::test]
271    async fn running_only() {
272        let query = create_query();
273
274        let instances =
275            get_instances_matching_filter(Some(ListFilter::Running), &query).await.unwrap();
276        assert_eq!(
277            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
278            vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
279        );
280    }
281
282    #[fuchsia::test]
283    async fn stopped_only() {
284        let query = create_query();
285
286        let instances =
287            get_instances_matching_filter(Some(ListFilter::Stopped), &query).await.unwrap();
288        assert_eq!(
289            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
290            [Moniker::root()]
291        );
292    }
293
294    #[fuchsia::test]
295    async fn descendants_only() {
296        let query = create_query();
297
298        let instances =
299            get_instances_matching_filter(Some(ListFilter::Descendant("core".to_string())), &query)
300                .await
301                .unwrap();
302        assert_eq!(
303            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
304            vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
305        );
306    }
307
308    #[fuchsia::test]
309    async fn ancestors_only() {
310        let query = create_query();
311
312        let instances =
313            get_instances_matching_filter(Some(ListFilter::Ancestor("core".to_string())), &query)
314                .await
315                .unwrap();
316        assert_eq!(
317            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
318            vec![Moniker::root(), Moniker::parse_str("/core").unwrap()]
319        );
320    }
321
322    #[fuchsia::test]
323    async fn relative_only() {
324        let query = create_query();
325
326        let instances =
327            get_instances_matching_filter(Some(ListFilter::Relative("core".to_string())), &query)
328                .await
329                .unwrap();
330        assert_eq!(
331            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
332            vec![
333                Moniker::root(),
334                Moniker::parse_str("/core").unwrap(),
335                Moniker::parse_str("/core/appmgr").unwrap(),
336            ]
337        );
338    }
339}