Skip to main content

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