1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// Copyright 2024 the Android Trace Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

#![doc = concat!(
// TODO: Is this a new pattern?
"[AndroidTrace]: crate::AndroidTrace
[dlsym]: libc::dlsym

<style>
.rustdoc-hidden { display: none; }
</style>

<!-- Hide the header section of the README when using rustdoc -->
<div style=\"display:none\">
",
    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);
}