blob: 150b3d62e7e2674b6624de7af48bade1aae691a3 [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::tracing::types::{
InitializeRequest, ResultsDestination, TerminateRequest, TerminateResponse,
};
use anyhow::Error;
use base64;
use fidl_fuchsia_tracing_controller::{
ControllerMarker, ControllerProxy, StartErrorCode, StartOptions, StopOptions, TerminateOptions,
TraceConfig,
};
use fuchsia_async;
use fuchsia_component::{self as app};
use fuchsia_zircon as zx;
use futures::{future, io::AsyncReadExt, TryFutureExt};
use parking_lot::RwLock;
use serde_json::{self, from_value, to_value, Value};
// This list should be kept in sync with defaultCategories in //garnet/bin/traceutil/actions.go
const DEFAULT_CATEGORIES: &[&'static str] = &[
"app",
"audio",
"benchmark",
"blobfs",
"gfx",
"input",
"kernel:meta",
"kernel:sched",
"ledger",
"magma",
"minfs",
"modular",
"view",
"flutter",
"dart",
"dart:compiler",
"dart:dart",
"dart:debugger",
"dart:embedder",
"dart:gc",
"dart:isolate",
"dart:profiler",
"dart:vm",
];
/// Perform tracing operations.
///
/// Note this object is shared among all threads created by server.
///
/// This facade does not hold onto a Tracing proxy as the server may be
/// long-running while individual tests set up and tear down Tracing.
#[derive(Debug)]
pub struct TracingFacade {
status: RwLock<Status>,
}
#[derive(Debug)]
pub struct Status {
controller: Option<ControllerProxy>,
data_socket: Option<zx::Socket>,
}
impl TracingFacade {
pub fn new() -> TracingFacade {
TracingFacade { status: RwLock::new(Status::new()) }
}
/// 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, args: Value) -> Result<Value, Error> {
let request: InitializeRequest = parse_args(args)?;
let trace_controller = app::client::connect_to_service::<ControllerMarker>()?;
let (write_socket, read_socket) = zx::Socket::create(zx::SocketOpts::STREAM)?;
let mut config = TraceConfig::EMPTY;
match request.categories {
Some(cats) => {
config.categories = Some(cats);
}
None => {
config.categories =
Some(DEFAULT_CATEGORIES.iter().map(|&s| s.to_owned()).collect());
}
}
config.buffer_size_megabytes_hint = request.buffer_size;
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(to_value(())?)
}
/// Start tracing.
///
/// There must be a trace session initialized through this facade, otherwise an error is
/// returned. Within a trace session, tracing may be started and stopped multiple times.
pub async fn start(&self) -> Result<Value, Error> {
let status = self.status.read();
let trace_controller = status
.controller
.as_ref()
.ok_or_else(|| format_err!("No trace session has been initialized"))?;
let options = StartOptions::EMPTY;
let response = trace_controller.start_tracing(options).await?;
match response {
Ok(_) => Ok(to_value(())?),
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 through this facade, otherwise an error is
/// returned. Within a trace session, tracing may be started and stopped multiple times.
pub async fn stop(&self) -> Result<Value, Error> {
let status = self.status.read();
let trace_controller = status
.controller
.as_ref()
.ok_or_else(|| format_err!("No trace session has been initialized"))?;
let options = StopOptions::EMPTY;
trace_controller.stop_tracing(options).await?;
Ok(to_value(())?)
}
/// Terminate tracing and optionally download the trace data.
///
/// Any trace session on the system will be terminated whether initialized through this facade
/// or through other means. Downloading trace data will only work for sessions initialized
/// through this facade. Attempting to download trace data from another trace session will
/// likely result in the request hanging as trace_manager will attempt to write the data to a
/// socket with no readers.
///
/// For documentation on the args parameter, see
/// [TerminateRequest](crate::tracing::types::TerminateRequest).
pub async fn terminate(&self, args: Value) -> Result<Value, Error> {
let request: TerminateRequest = parse_args(args)?;
let controller = match self.status.write().controller.take() {
Some(controller) => controller,
None => app::client::connect_to_service::<ControllerMarker>()?,
};
let result = match request.results_destination {
ResultsDestination::Ignore => {
let options =
TerminateOptions { write_results: Some(false), ..TerminateOptions::EMPTY };
controller.terminate_tracing(options).await?;
TerminateResponse { data: None }
}
ResultsDestination::WriteAndReturn => {
let options =
TerminateOptions { write_results: Some(true), ..TerminateOptions::EMPTY };
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?;
TerminateResponse { data: Some(base64::encode(&drain_result)) }
}
};
Ok(to_value(result)?)
}
}
// Helper function that wraps from_value, attempting to deserialize null as if it were an empty
// object.
fn parse_args<T: serde::de::DeserializeOwned>(args: Value) -> Result<T, serde_json::error::Error> {
if args.is_null() {
from_value(serde_json::value::Value::Object(serde_json::map::Map::new()))
} else {
from_value(args)
}
}
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)
}
impl Status {
fn new() -> Status {
Status { controller: None, data_socket: None }
}
}