diff --git a/crates/layer/src/error.rs b/crates/layer/src/error.rs index 54ace55..a966adf 100644 --- a/crates/layer/src/error.rs +++ b/crates/layer/src/error.rs @@ -8,14 +8,25 @@ pub enum Error { /// A generic C++ exception occurred in the C++ code. #[error("C++ code threw an exception: {0}")] Cxx(#[from] cxx::Exception), + /// A generic OS-level IO error occurred. #[error("IO error: {0}")] IO(#[from] io::Error), + /// An internal `Mutex` is poisoned due to some other thread panicking while + /// holding the mutex. #[error("Encountered a poisoned mutex")] PoisonedMutex, + /// An operation can't complete because someone already called + /// `layer.stop()` on the layer. #[error("The layer has been stopped")] LayerStopped, + /// An operation timed out according to one of the timeouts set in the layer + /// builder. #[error("Failed to complete operation before timeout was reached")] TimedOut, + /// A flush failed due to some unknown reason. + /// + /// This is not necessarily separate from the `TimedOut` case; we simply + /// don't know the reason why the flush failed. #[error("Failed to flush data due to some unknown reason (might also be due to timeout)")] FlushFailed, } diff --git a/crates/layer/src/lib.rs b/crates/layer/src/lib.rs index f66fcad..962b6f9 100644 --- a/crates/layer/src/lib.rs +++ b/crates/layer/src/lib.rs @@ -1,4 +1,5 @@ #![deny(clippy::all)] +#![deny(missing_docs)] //! # `tracing-perfetto-sdk-layer` //! A suite of tracing layers that reports traces via the C++ Perfetto SDK //! @@ -35,6 +36,35 @@ //! }); //! ``` //! +//! ## Trace config +//! +//! The layer constructor expects a `TraceConfig` protobuf message to configure +//! tracing settings. This can either be supplied as a `prost`-generated struct +//! or as raw (protobuf-encoded) bytes. The raw bytes option might be useful if +//! you want to embed a config with your binary that doesn't have to change, and +//! you want to save on space/overhead/... In the end the config will +//! always be encoded to bytes anyway to be sent to the C++ SDK. +//! +//! Since the protobuf schema also implements `serde` traits, you can load/store +//! the config file in any format you like, as long as there is a `serde` codec +//! for it. To activate the `tracing` data source, you need to add at least the +//! `rust_tracing` data source in the relevant config section (here, the config +//! is in YAML format): +//! +//! ```yaml +//! buffers: +//! - size_kb: 1024 +//! data_sources: +//! - config: +//! name: "rust_tracing" +//! ``` +//! +//! This is a good starting point for a minimal config file. If you want to +//! activate additional data sources, you can add them here; just remember that +//! most of them rely on the `traced daemon running on the host, and requires +//! you to use system mode (Explained further below in the section "Controlling +//! output destinations"). +//! //! ## Using counters //! //! When logging trace events, such as when calling `tracing::info!()` and @@ -55,6 +85,9 @@ //! tracing::info!(counter.mem_usage.bytes=42, counter.cpu_usage.percent=23.2, "hi!"); //! ``` //! +//! The unit will be reported properly in the Perfetto UI at the track-level +//! next to the sidebar of each line chart. +//! //! **NOTE**: at the time of writing, counters are only implemented by the //! [`NativeLayer`]. //! @@ -99,6 +132,32 @@ //! system's `traced` daemon, or (in the case of [`NativeLayer`]) poll for //! system-wide traces from the `traced` daemon and include them in the trace //! file. +//! +//! ## Sharing +//! +//! Layers are cloneable and will internally keep a reference count to keep the +//! layer internals alive. This means you can make a clone of the layer to +//! control it from many places at once. For example, it can be useful to have a +//! clone of the layer to be able to call `.flush()` or `.stop()` from somewhere +//! else. +//! +//! ## Timeouts +//! +//! The layers allow you to configure various timeouts that will be used +//! throughout the lifecycle of a layer. These are: +//! +//! * `drop_flush_timeout`: When the layer is dropped: how long to wait for a +//! final flush to complete before destroying the layer. If you want to +//! avoid this cost during `drop()`, call `.stop()` manually before the +//! layer is dropped. +//! * `background_flush_timeout`: In the case of [`NativeLayer`], a background +//! thread is used to fetch data from the SDK into the Rust world. This +//! controls how long we wait for each flush to complete; however, if it +//! doesn't complete in time, another attempt will soon be made. +//! * `background_poll_timeout`: How long to wait for new data to arrive +//! during each attempt of the background thread. +//! * `background_poll_interval`: How long to wait between attempts to poll +//! for new data in the background thread. // Internal modules: mod debug_annotations; diff --git a/crates/layer/src/native_layer.rs b/crates/layer/src/native_layer.rs index efa8f82..f2747aa 100644 --- a/crates/layer/src/native_layer.rs +++ b/crates/layer/src/native_layer.rs @@ -24,6 +24,8 @@ pub struct NativeLayer { inner: sync::Arc>, } +/// A builder for [`NativeLayer`]; use [`NativeLayer::from_config`] or +/// [`NativeLayer::from_config_bytes`] to create a new instance. pub struct Builder<'c, W> { config_bytes: borrow::Cow<'c, [u8]>, writer: W, @@ -65,11 +67,26 @@ impl NativeLayer where W: for<'w> fmt::MakeWriter<'w> + Send + Sync + 'static, { + /// Create a new layer builder from the supplied proto config. + /// + /// The proto config is usually read from an external source using the + /// prototext syntax, or else using one of the `serde` codecs. The config + /// will internally be encoded to bytes straight away, so prefer + /// [`NativeLayer::from_config_bytes`] if you already have the byte + /// representation. + /// + /// The built layer will write traces to the supplied writer. pub fn from_config(config: schema::TraceConfig, writer: W) -> Builder<'static, W> { use prost::Message as _; Builder::new(config.encode_to_vec().into(), writer) } + /// Create a new layer builder from the supplied proto config bytes. + /// + /// The proto config bytes needs to be an already encoded message of type + /// [`schema::TraceConfig`]. + /// + /// The built layer will write traces to the supplied writer. pub fn from_config_bytes(config_bytes: &[u8], writer: W) -> Builder { Builder::new(config_bytes.into(), writer) } @@ -181,10 +198,13 @@ where } } + /// Flush internal buffers, making the best effort for all pending writes to + /// be visible on this layer's `writer`. pub fn flush(&self, timeout: time::Duration) -> error::Result<()> { self.inner.flush(timeout) } + /// Stop the layer and stop collecting traces. pub fn stop(&self) -> error::Result<()> { self.inner.stop() } @@ -633,26 +653,36 @@ where } } + /// If `Some`, force the specified trace flavor. If `None`, use heuristics + /// for every created span to determine the flavor. pub fn with_force_flavor(mut self, force_flavor: Option) -> Self { self.force_flavor = force_flavor; self } + /// Enable in-process collection, where traces will be collected by buffers + /// in the Perfetto SDK and spilled to file in-process. pub fn with_enable_in_process(mut self, enable_in_process: bool) -> Self { self.enable_in_process = enable_in_process; self } + /// Enable system collection, where traces will be sent/collected from the + /// `traced` daemon, and additional system-wide data sources (such as + /// `ftrace`, `procfs`, `sysfs`, etc.) can be collected too. pub fn with_enable_system(mut self, enable_system: bool) -> Self { self.enable_system = enable_system; self } + /// The timeout of the final flush that will happen when dropping this + /// layer. pub fn with_drop_flush_timeout(mut self, drop_flush_timeout: time::Duration) -> Self { self.drop_flush_timeout = drop_flush_timeout; self } + /// The timeout of each flush in the background trace polling thread. pub fn with_background_flush_timeout( mut self, background_flush_timeout: time::Duration, @@ -661,11 +691,13 @@ where self } + /// The timeout of each poll in the background trace polling thread. pub fn with_background_poll_timeout(mut self, background_poll_timeout: time::Duration) -> Self { self.background_poll_timeout = background_poll_timeout; self } + /// The delay between each poll in the background trace polling thread. pub fn with_background_poll_interval( mut self, background_poll_interval: time::Duration, @@ -674,6 +706,7 @@ where self } + /// Turn this builder into a built layer. pub fn build(self) -> error::Result> { NativeLayer::build(self) } diff --git a/crates/layer/src/sdk_layer.rs b/crates/layer/src/sdk_layer.rs index e0d3e9d..8d8fe0d 100644 --- a/crates/layer/src/sdk_layer.rs +++ b/crates/layer/src/sdk_layer.rs @@ -19,6 +19,8 @@ pub struct SdkLayer { inner: sync::Arc, } +/// A builder for [`SdkLayer`]; use [`SdkLayer::from_config`] or +/// [`SdkLayer::from_config_bytes`] to create a new instance. pub struct Builder<'c> { config_bytes: borrow::Cow<'c, [u8]>, output_file: Option, @@ -46,6 +48,18 @@ struct Inner { } impl SdkLayer { + /// Create a new layer builder from the supplied proto config. + /// + /// The proto config is usually read from an external source using the + /// prototext syntax, or else using one of the `serde` codecs. The config + /// will internally be encoded to bytes straight away, so prefer + /// [`NativeLayer::from_config_bytes`] if you already have the byte + /// representation. + /// + /// The built layer will write traces to the supplied output file. If no + /// file is specified, it is probably only useful to run in system mode, so + /// that the traces are sent to the `traced` daemon instead (else they would + /// just get lost). pub fn from_config( config: schema::TraceConfig, output_file: Option, @@ -54,6 +68,15 @@ impl SdkLayer { Builder::new(config.encode_to_vec().into(), output_file) } + /// Create a new layer builder from the supplied proto config bytes. + /// + /// The proto config bytes needs to be an already encoded message of type + /// [`schema::TraceConfig`]. + /// + /// The built layer will write traces to the supplied output file. If no + /// file is specified, it is probably only useful to run in system mode, so + /// that the traces are sent to the `traced` daemon instead (else they would + /// just get lost). pub fn from_config_bytes(config_bytes: &[u8], output_file: Option) -> Builder { Builder::new(config_bytes.into(), output_file) } @@ -183,10 +206,13 @@ impl SdkLayer { ) } + /// Flush internal buffers, making the best effort for all pending writes to + /// be visible on this layer's `output_file`. pub fn flush(&self, timeout: time::Duration) -> error::Result<()> { self.inner.flush(timeout) } + /// Stop the layer and stop collecting traces. pub fn stop(&self) -> error::Result<()> { self.inner.stop() } @@ -354,21 +380,29 @@ impl<'c> Builder<'c> { } } - pub fn with_drop_flush_timeout(mut self, drop_flush_timeout: time::Duration) -> Self { - self.drop_flush_timeout = drop_flush_timeout; - self - } - + /// Enable in-process collection, where traces will be collected by buffers + /// in the Perfetto SDK and spilled to file in-process. pub fn with_enable_in_process(mut self, enable_in_process: bool) -> Self { self.enable_in_process = enable_in_process; self } + /// Enable system collection, where traces will be sent/collected from the + /// `traced` daemon, and additional system-wide data sources (such as + /// `ftrace`, `procfs`, `sysfs`, etc.) can be collected too. pub fn with_enable_system(mut self, enable_system: bool) -> Self { self.enable_system = enable_system; self } + /// The timeout of the final flush that will happen when dropping this + /// layer. + pub fn with_drop_flush_timeout(mut self, drop_flush_timeout: time::Duration) -> Self { + self.drop_flush_timeout = drop_flush_timeout; + self + } + + /// Turn this builder into a built layer. pub fn build(self) -> error::Result { SdkLayer::build(self) }