tree: c693a8bfa4d9e1db4827907a95228324e7e62917 [path history] [tgz]
  1. benchmark/
  2. bindgen/
  3. example/
  4. src/
  5. Android.bp
  6. OWNERS
  7. README.md
libatrace_rust/README.md

libatrace_rust - ATrace bindings for Rust

Wrapper library for ATrace methods from libcutils.

Quick start

Using ATrace bindings directly

Add the library to your rustlibs in Android.bp:

rustlibs: [
    ...
    "libatrace_rust",
    ...
],

Call tracing methods:

fn important_function() {
    // Use this macro to trace a function.
    atrace::trace_method!(AtraceTag::App);

    if condition {
        // Use a scoped event to trace inside a scope.
        let _event = atrace::begin_scoped_event(AtraceTag::App, "Inside a scope");
        ...
    }

    // Or just use the wrapped API directly.
    atrace::atrace_begin(AtraceTag::App, "My event");
    ...
    atrace::atrace_end(AtraceTag::App)
}

See more in the example.

You're all set! Now you can collect a trace with your favorite tracing tool like Perfetto.

Using the tracing crate

You can use the ATrace layer for the tracing crate. Compared to using the bindings directly, it has better instrumentation points and customizability. The main drawback is lower performance. See the Performance section below for more information.

Add the tracing libraries to your rustlibs in Android.bp:

    rustlibs: [
        ...
        "libatrace_tracing_subscriber",
        "libtracing_subscriber",
        "libtracing",
        ...
    ],

Initialize the subscriber before calling the tracing methods, usually somewhere in the beginning of main().

// Initialize the subscriber, panic if it fails.
tracing_subscriber::registry()
        .with(AtraceSubscriber::default().with_filter())
        .init();

The subscriber defaults to AtraceTag::App. Use other tags by creating the subscriber with AtraceSubscriber::new(tag: AtraceTag).

You can combine the subscriber with other layers. In that case, omit with_filter() from the AtraceSubscriber initialization - it is an optimization that disables instrumentation points when ATrace is disabled and it would affect other layers as well.

Now you can record spans and events:

// This macro would automatically create and enter a span with function name and arguments.
#[tracing::instrument]
fn important_function() {
    if condition {
        // Use span! to trace inside a scope.
        let _entered = tracing::span!(tracing::Level::TRACE, "Inside a scope").entered();
        ...
    }

    // Use event! to record an instant event.
    // You can annotate spans and events with fields. They will be appended to the end of
    // the Atrace event.
    tracing::info!(field="value", "My event");
}

See more in the example and check out the docs for the tracing and tracing-subscriber crates.

Performance

This section is an overview, you can find specific numbers in benchmark/README.md.

ATrace bindings

When tracing is enabled, you can expect 1-10 us per event - this is a significant cost that may affect the performance of hot high-frequency methods. When the events are disabled, calling them is cheap - on the order of 5-10 ns. There is a 10-20% overhead from the wrapper, mostly caused by string conversion when tracing is enabled.

Tracing subscriber

The subscriber uses the bindings and adds its own overhead that depends on usage:

  • With tracing disabled and subscriber created with_filter, events cost around 30 ns. Not using the filter brings the cost up to 100-400 ns per event.
  • Instant events (event!) add roughly 200 ns to the bindings - 1.5 vs 1.3 us.
  • Spans (span!) are roughly 400 ns slower - 2.8 vs 2.4 us.
  • Using fields adds time that depends on the amount of the fields and the cost of converting them to strings. Typically it is around an extra 500 ns per event and an extra 1 us for a span.