android_trace 0.1.1

Support for Android NDK Tracing
Documentation
// Copyright 2024 the Android Trace Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

// https://linebender.org/blog/doc-include
//! [AndroidTrace]: crate::AndroidTrace
//! [dlsym]: libc::dlsym
// File links are not supported by rustdoc
//! [LICENSE-APACHE]: https://github.com/linebender/android_trace/blob/main/LICENSE-APACHE
//! [LICENSE-MIT]: https://github.com/linebender/android_trace/blob/main/LICENSE-MIT
//!
//! <style>
//! .rustdoc-hidden { display: none; }
//! </style>
#![doc =  include_str!("../README.md")]

#[cfg(not(feature = "api_level_23"))]
use ffi::ATraceAPILevel23Methods;
#[cfg(not(feature = "api_level_29"))]
use ffi::ATraceAPILevel29Methods;

use core::ffi::CStr;
use std::fmt::Debug;

#[cfg(not(target_os = "android"))]
compile_error!(
    r#"android_trace only supports Android. If you are depending on it, ensure that it is within
    [target.'cfg(target_os = "android")'.dependencies]
    in your Cargo.toml"#
);

mod ffi;

/// A handle to the available NDK tracing functions
///
/// All access is thread safe
#[derive(Clone)]
pub struct AndroidTrace {
    #[cfg(not(feature = "api_level_23"))]
    api_level_23: Option<&'static ATraceAPILevel23Methods>,
    #[cfg(not(feature = "api_level_29"))]
    api_level_29: Option<&'static ATraceAPILevel29Methods>,
}

impl AndroidTrace {
    /// Get a handle to all of the NDK tracing functions available on this device.
    ///
    /// This should be expected to have a low runtime cost.
    ///
    /// Can subsequently be used across multiple threads
    pub fn new() -> Self {
        Self {
            #[cfg(not(feature = "api_level_23"))]
            api_level_23: ATraceAPILevel23Methods::get(),
            #[cfg(not(feature = "api_level_29"))]
            api_level_29: ATraceAPILevel29Methods::get(),
        }
    }

    /// Get a handle to the first level of the NDK tracing functions, where available
    /// on this device.
    ///
    /// This should be expected to have a low runtime cost.
    pub fn new_downlevel() -> Self {
        Self {
            #[cfg(not(feature = "api_level_23"))]
            api_level_23: ATraceAPILevel23Methods::get(),
            #[cfg(not(feature = "api_level_29"))]
            api_level_29: None,
        }
    }

    /// Returns Some(true) if tracing through Android Trace is enabled (and Some(false) if it is disabled).
    /// This value is *not* guaranteed to have the same value over time.
    /// Tracing may begin and end during execution.
    /// Note that the Android platform does not provide any non-polling method for determining whether
    /// this tracing is enabled.
    ///
    /// If `ATrace_isEnabled` is not available, returns None.
    /// This means that none of the tracing methods will have any effect during this program execution,
    /// and so can be skipped.
    ///
    /// Calls [`ATrace_isEnabled`](https://developer.android.com/ndk/reference/group/tracing#atrace_isenabled)
    /// if available. This is only available since Android API level 23. If the `api_level_23` feature is not
    /// enabled, this will attempt to access a dynamically linked version of the underlying function.
    /// Please note that `api_level_23` is a default feature.
    #[doc(alias = "ATrace_isEnabled")]
    #[must_use = "Detecting if tracing is enabled has no side effects"]
    pub fn is_enabled(&self) -> Option<bool> {
        // SAFETY: No preconditions
        #[cfg(feature = "api_level_23")]
        unsafe {
            Some(ffi::atrace_is_enabled_raw())
        }
        #[cfg(not(feature = "api_level_23"))]
        if let Some(methods) = self.api_level_23 {
            // Safety: No preconditions
            let result = unsafe { (methods.is_enabled)() };
            Some(result)
        } else {
            None
        }
    }

    /// Writes a tracing message to indicate that the given section of code has begun.
    ///
    /// This should be followed by a call to [`Self::end_section`] on the same thread, to close the
    /// opened section.
    ///
    /// Calls [`ATrace_beginSection`](https://developer.android.com/ndk/reference/group/tracing#atrace_beginsection)
    /// if available. This is only available since Android API level 23. If the `api_level_23` feature is not
    /// enabled, this will attempt to access a dynamically linked version of the underlying function.
    /// Please note that `api_level_23` is a default feature.
    ///
    /// If `ATrace_beginSection` is not available, this has no effect.
    #[doc(alias = "ATrace_beginSection")]
    pub fn begin_section(&self, section_name: &CStr) {
        #[cfg(feature = "api_level_23")]
        unsafe {
            // SAFETY: section_name is a valid C string
            ffi::atrace_begin_section_raw(section_name.as_ptr());
        }
        #[cfg(not(feature = "api_level_23"))]
        if let Some(methods) = self.api_level_23 {
            // SAFETY: section_name is a valid C string
            unsafe { (methods.begin_section)(section_name.as_ptr()) }
        }
    }

    /// Writes a tracing message to indicate that a given section of code has ended.
    ///
    /// This should follow a call to [`Self::begin_section`] on the same thread, as
    /// this will be closing an opened section.
    ///
    /// Calls [`ATrace_endSection`](https://developer.android.com/ndk/reference/group/tracing#atrace_endsection)
    /// if available. This is only available since Android API level 23. If the `api_level_23` feature is not
    /// enabled, this will attempt to access a dynamically linked version of the underlying function.
    /// Please note that `api_level_23` is a default feature
    ///
    /// If `ATrace_endSection` is not available, this has no effect.
    #[doc(alias = "ATrace_endSection")]
    pub fn end_section(&self) {
        // SAFETY: No preconditions.
        #[cfg(feature = "api_level_23")]
        unsafe {
            ffi::atrace_end_section_raw();
        }
        #[cfg(not(feature = "api_level_23"))]
        if let Some(methods) = self.api_level_23 {
            // Safety: No preconditions
            unsafe { (methods.end_section)() }
        }
    }

    /// Writes a tracing message to indicate that a given section of code has begun.
    ///
    /// This should be followed by a call to [`Self::end_async_section`] with the same `section_name` and `cookie`,
    /// although this subsequent call can occur on any thread.
    ///
    /// Calls [`ATrace_beginAsyncSection`](https://developer.android.com/ndk/reference/group/tracing#atrace_beginasyncsection)
    /// if available. This is only available since Android API level 29. If the `api_level_29` feature is not
    /// enabled, this will attempt to access a dynamically linked version of the underlying function.
    ///
    /// If `ATrace_beginAsyncSection` is not available, this has no effect.
    #[doc(alias = "ATrace_beginAsyncSection")]
    pub fn begin_async_section(&self, section_name: &CStr, cookie: i32) -> Option<()> {
        // SAFETY: No preconditions.
        #[cfg(feature = "api_level_29")]
        unsafe {
            ffi::atrace_begin_async_section_raw(section_name.as_ptr(), cookie);
            Some(())
        }
        #[cfg(not(feature = "api_level_29"))]
        if let Some(methods) = self.api_level_29 {
            // Safety: No preconditions
            unsafe { (methods.begin_async_section)(section_name.as_ptr(), cookie) }
            Some(())
        } else {
            None
        }
    }

    /// Writes a tracing message to indicate that a given section of code has ended
    ///
    /// This should follow a call to [`Self::begin_async_section`] with the same `section_name` and `cookie`,
    /// although this call can occur on any thread
    ///
    /// Calls [`ATrace_endAsyncSection`](https://developer.android.com/ndk/reference/group/tracing#atrace_endasyncsection)
    /// if available. This is only available since Android API level 29. If the `api_level_29` feature is not
    /// enabled, this will attempt to access a dynamically linked version of the underlying function.
    ///
    /// If `ATrace_endAsyncSection` is not available, this has no effect
    #[doc(alias = "ATrace_endAsyncSection")]
    pub fn end_async_section(&self, section_name: &CStr, cookie: i32) -> Option<()> {
        // SAFETY: No preconditions.
        #[cfg(feature = "api_level_29")]
        unsafe {
            ffi::atrace_end_async_section_raw(section_name.as_ptr(), cookie);
            Some(())
        }
        #[cfg(not(feature = "api_level_29"))]
        if let Some(methods) = self.api_level_29 {
            // Safety: No preconditions
            unsafe { (methods.end_async_section)(section_name.as_ptr(), cookie) }
            Some(())
        } else {
            None
        }
    }

    /// Whether the [`Self::set_counter`], [`Self::begin_async_section`] and [`Self::end_async_section`]
    /// might do anything.
    ///
    /// This value *will* be the same across multiple calls (on this [`AndroidTrace`] instance -
    /// see [`Self::new_downlevel`]), and so can be used as an early-fastpath feature.
    ///
    /// Note that you should also call [`Self::is_enabled`] if calculating
    /// an individual value to pass to the corresponding functions will be expensive
    pub fn could_use_api_level_29(&self) -> bool {
        #[cfg(not(feature = "api_level_29"))]
        return self.api_level_29.is_some();
        #[cfg(feature = "api_level_29")]
        true
    }

    /// Writes a trace message to indicate that the counter with the given name has the given value.
    ///
    /// Calls [`ATrace_setCounter`](https://developer.android.com/ndk/reference/group/tracing#atrace_setcounter)
    /// if available. This is only available since Android API level 29. If the `api_level_29` feature is not
    /// enabled, this will attempt to access a dynamically linked version of the underlying function.
    ///
    /// If `ATrace_endAsyncSection` is not available, this has no effect
    #[doc(alias = "ATrace_setCounter")]
    pub fn set_counter(&self, counter_name: &CStr, value: i64) -> Option<()> {
        // SAFETY: No preconditions.
        #[cfg(feature = "api_level_29")]
        unsafe {
            ffi::atrace_set_counter_raw(counter_name.as_ptr(), value);
            Some(())
        }
        #[cfg(not(feature = "api_level_29"))]
        if let Some(methods) = self.api_level_29 {
            // Safety: No preconditions
            unsafe { (methods.set_counter)(counter_name.as_ptr(), value) }
            Some(())
        } else {
            None
        }
    }
}

impl Default for AndroidTrace {
    fn default() -> Self {
        Self::new()
    }
}

impl Debug for AndroidTrace {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        #[cfg(not(feature = "api_level_23"))]
        let has_level_23 = self.api_level_23.is_some();
        #[cfg(feature = "api_level_23")]
        let has_level_23 = true;
        #[cfg(not(feature = "api_level_29"))]
        let has_level_29 = self.api_level_29.is_some();
        #[cfg(feature = "api_level_29")]
        let has_level_29 = true;
        let api_level = match (has_level_29, has_level_23) {
            (true, true) => "29",
            (false, true) => "23",
            (false, false) => "None",
            (true, false) => "Unexpected: 29 but not 23",
        };
        f.debug_struct("AndroidTrace")
            .field("api_level", &api_level)
            .finish()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use static_assertions as sa;
    sa::assert_impl_all!(AndroidTrace: Send, Sync);
}