blob: ecdd5c2abd2573e5409cce906744b9c5ea169458 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{
commands::{types::*, utils},
text_formatter,
types::Error,
},
argh::FromArgs,
async_trait::async_trait,
derivative::Derivative,
diagnostics_data::{Inspect, InspectData, InspectHandleName},
glob,
itertools::Itertools,
serde::Serialize,
std::{cmp::Ordering, fmt, ops::Deref},
};
#[derive(Derivative, Serialize, PartialEq)]
#[derivative(Eq)]
pub struct ShowResultItem(InspectData);
impl Deref for ShowResultItem {
type Target = InspectData;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialOrd for ShowResultItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ShowResultItem {
fn cmp(&self, other: &Self) -> Ordering {
self.moniker.cmp(&other.moniker).then_with(|| {
let this_name = self.metadata.name.as_ref().map(InspectHandleName::as_ref);
let other_name = other.metadata.name.as_ref().map(InspectHandleName::as_ref);
this_name.cmp(&other_name)
})
}
}
#[derive(Serialize)]
pub struct ShowResult(Vec<ShowResultItem>);
impl fmt::Display for ShowResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for item in self.0.iter() {
text_formatter::output_schema(f, &item.0)?;
}
Ok(())
}
}
/// Prints the inspect hierarchies that match the given selectors.
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "show")]
pub struct ShowCommand {
#[argh(option)]
/// the name of the manifest file that we are interested in. If this is provided, the output
/// will only contain monikers for components whose url contains the provided name.
pub manifest: Option<String>,
#[argh(positional)]
/// selectors for which the selectors should be queried. If no selectors are provided, inspect
/// data for the whole system will be returned. If `--manifest` is provided then the selectors
/// should be tree selectors, otherwise component selectors or full selectors.
pub selectors: Vec<String>,
#[argh(option)]
/// the filenames we are interested in. If any are provided, the output will only
/// contain data from components which expose Inspect under the given file under
/// their out/diagnostics directory.
/// Supports shell globs expansions.
pub file: Vec<String>,
#[argh(option)]
/// A selector specifying what `fuchsia.diagnostics.ArchiveAccessor` to connect to.
/// The selector will be in the form of:
/// <moniker>:<directory>:fuchsia.diagnostics.ArchiveAccessorName
///
/// Typically this is the output of `iquery list-accessors`.
///
/// For example: `bootstrap/archivist:expose:fuchsia.diagnostics.FeedbackArchiveAccessor`
/// means that the command will connect to the `FeedbackArchiveAccecssor`
/// exposed by `bootstrap/archivist`.
///
/// NOTE: if `<directory>` starts with `out`, then it means `out/svc` implicitly.
pub accessor: Option<String>,
}
#[async_trait]
impl Command for ShowCommand {
type Result = ShowResult;
async fn execute<P: DiagnosticsProvider>(&self, provider: &P) -> Result<Self::Result, Error> {
let selectors = utils::get_selectors_for_manifest(
&self.manifest,
&self.selectors,
&self.accessor,
provider,
)
.await?;
let selectors = utils::expand_selectors(selectors)?;
let accessor = match &self.accessor {
Some(a) => {
let elements: Vec<&str> = a.split(":").collect();
if elements.len() != 3 {
return Err(Error::InvalidAccessor(a.to_owned()));
}
// Append "svc" to "out" if needed.
let dir = elements[1];
let dir = if dir == "out" || dir.starts_with("out/") {
let dir_elements = &dir.split("/").collect::<Vec<_>>();
["out/svc"].iter().chain(dir_elements[1..].into_iter()).join("/")
} else {
dir.to_owned()
};
Some([elements[0], &dir, elements[2]].join(":"))
}
_ => None,
};
let inspect_data_iter =
provider.snapshot::<Inspect>(&accessor, &selectors).await?.into_iter();
// Filter out by filename on the Inspect metadata.
let filter_fn: Box<dyn Fn(&InspectData) -> bool> = if !&self.file.is_empty() {
let mut glob_patterns = vec![];
for file in self.file.iter() {
glob_patterns.push(
glob::Pattern::new(&file)
.map_err(|_e| Error::InvalidFilePattern(file.to_owned()))?,
)
}
Box::new(move |d: &InspectData| {
glob_patterns.iter().any(|glob| {
glob.matches(&d.metadata.name.as_ref().map(|name| name.as_ref()).unwrap_or(""))
})
})
} else {
Box::new(|_d: &InspectData| true)
};
let mut results = inspect_data_iter
.filter(filter_fn)
.map(|mut d: InspectData| {
if let Some(hierarchy) = &mut d.payload {
hierarchy.sort();
}
ShowResultItem(d)
})
.collect::<Vec<_>>();
results.sort();
Ok(ShowResult(results))
}
}