blob: 2663a17cb9de47ff2723f65dfbe89884dbc4b164 [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 {
anyhow::{anyhow, Context, Result},
async_std::fs::File,
ffx_core::ffx_plugin,
ffx_trace_args::{Record, TraceCommand, TraceSubCommand},
fidl_fuchsia_tracing_controller::{
ControllerProxy, StartOptions, StopOptions, TerminateOptions, TraceConfig,
},
std::io::{stdout, Write},
std::time::Duration,
};
#[ffx_plugin("tracing", ControllerProxy = "core/appmgr:out:fuchsia.tracing.controller.Controller")]
pub async fn trace(controller_proxy: ControllerProxy, cmd: TraceCommand) -> Result<()> {
match cmd.sub_cmd {
TraceSubCommand::ListProviders(_sub_cmd) => {
list_providers_cmd_impl(controller_proxy, Box::new(stdout())).await?
}
TraceSubCommand::Record(sub_cmd) => {
record_cmd_impl(sub_cmd, controller_proxy, Box::new(stdout())).await?
}
}
Ok(())
}
async fn list_providers_cmd_impl<W: Write>(
controller_proxy: ControllerProxy,
mut writer: W,
) -> Result<()> {
let providers = controller_proxy.get_providers().await?;
writeln!(&mut writer, "Trace providers:")?;
for provider in &providers {
writeln!(&mut writer, "- {}", provider.name.as_ref().unwrap_or(&"<unknown>".to_string()))?;
}
Ok(())
}
async fn record_trace<W: Write>(
opts: Record,
controller_proxy: ControllerProxy,
server_socket: fidl::Socket,
mut writer: W,
) -> Result<()> {
let trace_config = TraceConfig {
buffer_size_megabytes_hint: Some(opts.buffer_size),
categories: Some(opts.categories),
..TraceConfig::EMPTY
};
write!(writer, "initialize tracing\n")?;
controller_proxy.initialize_tracing(trace_config, server_socket)?;
write!(writer, "starting trace\n")?;
controller_proxy
.start_tracing(StartOptions::EMPTY)
.await?
.map_err(|e| anyhow!("Failed to start trace: {:?}", e))?;
write!(writer, "waiting for {} seconds\n", &opts.duration)?;
fuchsia_async::Timer::new(Duration::from_secs_f64(opts.duration)).await;
write!(writer, "stopping trace\n")?;
controller_proxy
.stop_tracing(StopOptions { write_results: Some(true), ..StopOptions::EMPTY })
.await?;
write!(writer, "terminating tracing\n")?;
controller_proxy
.terminate_tracing(TerminateOptions {
write_results: Some(true),
..TerminateOptions::EMPTY
})
.await?;
Ok(())
}
async fn record_cmd_impl<W: Write>(
opts: Record,
controller_proxy: ControllerProxy,
writer: W,
) -> Result<()> {
let (client_socket, server_socket) =
fidl::Socket::create(fidl::SocketOpts::STREAM).context("failed ot create socket")?;
let client_socket = fidl::AsyncSocket::from_socket(client_socket)?;
let f = File::create(&opts.output).await?;
let t = fuchsia_async::Task::spawn(async move {
let mut f = f;
futures::io::copy(client_socket, &mut f).await
});
record_trace(opts, controller_proxy, server_socket, writer).await?;
t.cancel().await;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_tracing_controller::{ControllerRequest, ProviderInfo, TerminateResult};
use std::sync::{Arc, Mutex};
const TEST_TRACE_OUTPUT: &'static [u8] = &[0x00, 0x55, 0xaa, 0xff];
#[derive(Debug)]
struct FakeControllerState {
buffer_size: u32,
categories: Vec<String>,
output: Option<fidl::Socket>,
}
fn setup_fake_controller_server() -> (Arc<Mutex<FakeControllerState>>, ControllerProxy) {
let state = Arc::new(Mutex::new(FakeControllerState {
buffer_size: 0,
categories: vec![],
output: None,
}));
(
state.clone(),
setup_fake_controller_proxy(move |req| match req {
ControllerRequest::InitializeTracing { config, output, control_handle: _ } => {
let mut state = state.lock().unwrap();
if let Some(buffer_size) = config.buffer_size_megabytes_hint {
state.buffer_size = buffer_size;
}
if let Some(categories) = config.categories {
state.categories = categories;
}
state.output = Some(output);
}
ControllerRequest::TerminateTracing { options: _, responder } => {
responder.send(TerminateResult::EMPTY).unwrap();
}
ControllerRequest::StartTracing { options: _, responder } => {
responder.send(&mut Ok(())).unwrap();
}
ControllerRequest::StopTracing { options: _, responder } => {
let state = state.lock().unwrap();
state.output.as_ref().unwrap().write(TEST_TRACE_OUTPUT).unwrap();
responder.send().unwrap();
}
ControllerRequest::GetProviders { responder } => {
let a = ProviderInfo {
id: Some(1),
pid: Some(10),
name: Some("provider_a".to_string()),
..ProviderInfo::EMPTY
};
let b = ProviderInfo {
id: Some(2),
pid: Some(20),
name: Some("provider_b".to_string()),
..ProviderInfo::EMPTY
};
responder.send(&mut vec![a, b].into_iter()).unwrap();
}
ControllerRequest::GetKnownCategories { responder: _ } => (),
ControllerRequest::WatchAlert { responder: _ } => (),
}),
)
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_list_providers() {
const TEST_OUTPUT: &'static [u8] = b"\
Trace providers:\
\n- provider_a\
\n- provider_b\
\n";
let mut output = Vec::new();
let (_state, proxy) = setup_fake_controller_server();
list_providers_cmd_impl(proxy, &mut output).await.expect("list_providers_cmd_impl");
assert_eq!(output, TEST_OUTPUT);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_record() {
let trace_file = tempfile::NamedTempFile::new().unwrap();
let trace_path = trace_file.into_temp_path();
let mut output = Vec::new();
let (state, proxy) = setup_fake_controller_server();
record_cmd_impl(
Record {
buffer_size: 1,
categories: vec!["a".to_string(), "b".to_string(), "c".to_string()],
duration: 0.5,
output: trace_path.to_str().unwrap().to_string(),
},
proxy,
&mut output,
)
.await
.expect("record_cmd_impl");
let out_string = String::from_utf8(output).unwrap();
let state = state.lock().unwrap();
// Verify that record handles its arguments correctly.
assert_eq!(state.categories, vec!["a".to_string(), "b".to_string(), "c".to_string(),]);
assert_eq!(state.buffer_size, 1);
assert!(out_string.contains("waiting for 0.5 seconds"));
// Verify that the trace was written correctly.
let trace_output = std::fs::read(&trace_path).unwrap();
assert_eq!(trace_output, TEST_TRACE_OUTPUT);
// Verify that we've cleaned up the temp file.
trace_path.close().unwrap();
}
}