blob: 962c0c0581563de75364723e2caba9dcc225a83e [file] [log] [blame]
// Copyright 2022 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::{format_err, Error};
use fidl_fuchsia_tracing_controller::{
ControllerMarker, ControllerProxy, StartErrorCode, StartOptions, StopOptions, TerminateOptions,
TraceConfig,
};
use fuchsia_async;
use fuchsia_component::{self as app};
use fuchsia_sync::RwLock;
use fuchsia_zircon as zx;
use futures::{future, io::AsyncReadExt, TryFutureExt};
use std::fs;
use std::io::Write;
const TRACE_FILE: &'static str = "/custom_artifacts/trace.fxt";
const BUFFER_SIZE_MB: u32 = 36;
struct Status {
controller: Option<ControllerProxy>,
data_socket: Option<zx::Socket>,
}
pub struct SingleSessionTrace {
status: RwLock<Status>,
}
/// Provides controls to capture a single trace session
///
/// To use:
/// ```
/// let trace = SingleSessionTrace::new();
/// trace.initialize(args.trace_config).await?;
/// trace.start().await?;
/// ...
/// trace.stop().await?;
/// trace.terminate().await?
/// ```
impl SingleSessionTrace {
pub fn new() -> SingleSessionTrace {
SingleSessionTrace { status: RwLock::new(Status { controller: None, data_socket: None }) }
}
/// Initialize a trace session.
///
/// A trace session allows for starting and stopping the collection of trace data. Trace data
/// is then returned all at once when [terminate] is called.
///
/// For documentation on the args parameter, see
/// [InitializeRequest](crate::tracing::types::InitializeRequest).
///
/// There can only be one trace session active on the system at a time. If there is a trace
/// session from another controller active on the system, initialize may still return
/// success, as trace_manager accepts the initialize_tracing call as a no-op. If needed,
/// [terminate] may be used to ensure that no trace session is active on the system.
pub async fn initialize(&self, categories: Vec<String>) -> Result<(), Error> {
let trace_controller = app::client::connect_to_protocol::<ControllerMarker>()?;
let (write_socket, read_socket) = zx::Socket::create_stream();
let config = TraceConfig {
buffer_size_megabytes_hint: Some(BUFFER_SIZE_MB),
categories: Some(categories),
..Default::default()
};
trace_controller.initialize_tracing(config, write_socket)?;
{
let mut status = self.status.write();
status.data_socket = Some(read_socket);
status.controller = Some(trace_controller);
}
Ok(())
}
/// Start tracing.
///
/// There must be a trace session initialized first, otherwise an error is returned.
/// Within a trace session, tracing may be started and stopped multiple times.
pub async fn start(&self) -> Result<(), Error> {
let status = self.status.read();
let trace_controller = status
.controller
.as_ref()
.ok_or_else(|| format_err!("No trace session has been initialized"))?;
match trace_controller.start_tracing(StartOptions::default()).await? {
Ok(_) => Ok(()),
Err(e) => match e {
StartErrorCode::NotInitialized => {
Err(format_err!("trace_manager reports trace not initialized"))
}
StartErrorCode::AlreadyStarted => Err(format_err!("Trace already started")),
StartErrorCode::Stopping => Err(format_err!("Trace is stopping")),
StartErrorCode::Terminating => Err(format_err!("Trace is terminating")),
},
}
}
/// Stop tracing.
///
/// There must be a trace session initialized first, otherwise an error is returned.
/// Within a trace session, tracing may be started and stopped multiple times.
pub async fn stop(&self) -> Result<(), Error> {
let status = self.status.read();
let trace_controller = status
.controller
.as_ref()
.ok_or_else(|| format_err!("No trace session has been initialized"))?;
trace_controller.stop_tracing(StopOptions::default()).await?;
Ok(())
}
/// Terminate tracing and convert data to json using `trace2json`.
///
/// Both raw trace file and converted trace json file will be stored in test's custom artifact.
pub async fn terminate(&self) -> Result<(), Error> {
let controller = match self.status.write().controller.take() {
Some(controller) => controller,
None => app::client::connect_to_protocol::<ControllerMarker>()?,
};
let options = TerminateOptions { write_results: Some(true), ..Default::default() };
let terminate_fut = controller.terminate_tracing(options).map_err(Error::from);
let data_socket = self.status.write().data_socket.take();
let drain_fut = drain_socket(data_socket);
// Note: It is important that these two futures are handled concurrently, as trace_manager
// writes the trace data before completing the terminate FIDL call.
let (_terminate_result, drain_result) = future::try_join(terminate_fut, drain_fut).await?;
// write this to file
let mut trace_file = fs::File::create(TRACE_FILE)?;
trace_file.write_all(&drain_result)?;
Ok(())
}
}
async fn drain_socket(socket: Option<zx::Socket>) -> Result<Vec<u8>, Error> {
let mut ret = Vec::new();
if let Some(socket) = socket {
let mut socket = fuchsia_async::Socket::from_socket(socket);
socket.read_to_end(&mut ret).await?;
}
Ok(ret)
}