22// Copyright (C) Microsoft Corporation. All rights reserved.
33//
44
5+ //
6+ // Android trace_region implementation using the NDK ATrace API.
7+ //
8+ // This uses ATrace_beginAsyncSection/ATrace_endAsyncSection (API 29+) which pair
9+ // begin/end events by name + cookie, allowing trace regions to be moved across
10+ // threads (e.g. when an RAII trace_region is captured into an async continuation).
11+ // The sync API (ATrace_beginSection/ATrace_endSection) uses a per-thread stack,
12+ // which breaks when begin and end occur on different threads.
13+ //
14+ // When targeting minSdkVersion < 29, the async functions are resolved at runtime
15+ // via dlsym. If unavailable (device below API 29), ATrace is silently skipped
16+ // (logcat output at trace_level::log still works).
17+ //
18+ // NOTE: Async trace sections may not be visible in Android Studio's Profiler UI.
19+ // To view them, capture a trace with Perfetto:
20+ //
21+ // adb shell atrace --async_start -a <your.package.name> -c
22+ // # ... interact with the app ...
23+ // adb shell atrace --async_stop -o /data/local/tmp/trace.txt
24+ // adb pull /data/local/tmp/trace.txt
25+ //
26+ // Then open the trace file at https://ui.perfetto.dev
27+ //
28+
529#pragma once
630
31+ #include < atomic>
32+ #include < string>
33+ #include < android/trace.h>
34+ #include < android/log.h>
35+ #if __ANDROID_MIN_SDK_VERSION__ < 29
36+ #include < dlfcn.h>
37+ #endif
38+
739namespace arcana
840{
941 enum class trace_level
@@ -12,33 +44,134 @@ namespace arcana
1244 log,
1345 };
1446
15- // TODO: https://developer.android.com/topic/performance/tracing/custom-events-native
16- // https://developer.android.com/ndk/reference/group/tracing
1747 class trace_region final
1848 {
1949 public:
2050 trace_region () = delete ;
2151 trace_region (const trace_region&) = delete ;
2252 trace_region& operator =(const trace_region&) = delete ;
2353
24- trace_region (const char *)
54+ trace_region (const char * name) :
55+ m_cookie{s_enabled ? s_nextCookie.fetch_add (1 , std::memory_order_relaxed) : 0 },
56+ m_name{m_cookie != 0 ? name : " " }
2557 {
58+ if (m_cookie != 0 )
59+ {
60+ if (s_logEnabled)
61+ {
62+ __android_log_print (ANDROID_LOG_DEBUG, " trace_region" , " [trace_region] BEGIN %s (cookie=%d, this=%p)" , m_name.c_str (), m_cookie, static_cast <const void *>(this ));
63+ }
64+ traceBegin (m_name.c_str (), m_cookie);
65+ }
2666 }
2767
28- trace_region (trace_region&&) = default ;
68+ // Move constructor transfers ownership; the moved-from region becomes inactive
69+ // (cookie set to 0) so its destructor won't emit a spurious end event.
70+ trace_region (trace_region&& other) :
71+ m_cookie{other.m_cookie },
72+ m_name{std::move (other.m_name )}
73+ {
74+ other.m_cookie = 0 ;
75+ }
2976
3077 ~trace_region ()
3178 {
79+ if (m_cookie != 0 )
80+ {
81+ if (s_logEnabled)
82+ {
83+ __android_log_print (ANDROID_LOG_DEBUG, " trace_region" , " [trace_region] END (cookie=%d, this=%p)" , m_cookie, static_cast <const void *>(this ));
84+ }
85+ traceEnd (m_name.c_str (), m_cookie);
86+ }
3287 }
3388
34- trace_region& operator =(trace_region&&) = default ;
89+ trace_region& operator =(trace_region&& other)
90+ {
91+ if (this == &other)
92+ {
93+ return *this ;
94+ }
95+
96+ if (m_cookie != 0 )
97+ {
98+ if (s_logEnabled)
99+ {
100+ __android_log_print (ANDROID_LOG_DEBUG, " trace_region" , " [trace_region] END (move) (cookie=%d, this=%p)" , m_cookie, static_cast <const void *>(this ));
101+ }
102+ traceEnd (m_name.c_str (), m_cookie);
103+ }
104+
105+ m_cookie = other.m_cookie ;
106+ m_name = std::move (other.m_name );
107+ other.m_cookie = 0 ;
35108
36- static void enable (trace_level = trace_level::mark)
109+ return *this ;
110+ }
111+
112+ static void enable (trace_level level = trace_level::mark)
37113 {
114+ if (s_enabled)
115+ {
116+ return ;
117+ }
118+
119+ s_enabled = true ;
120+ s_logEnabled = level == trace_level::log;
38121 }
39122
40123 static void disable ()
41124 {
125+ if (!s_enabled)
126+ {
127+ return ;
128+ }
129+
130+ s_enabled = false ;
131+ s_logEnabled = false ;
132+ }
133+
134+ private:
135+ static void traceBegin (const char * name, int32_t cookie)
136+ {
137+ #if __ANDROID_MIN_SDK_VERSION__ >= 29
138+ ATrace_beginAsyncSection (name, cookie);
139+ #else
140+ if (s_beginAsync)
141+ s_beginAsync (name, cookie);
142+ #endif
42143 }
144+
145+ static void traceEnd (const char * name, int32_t cookie)
146+ {
147+ #if __ANDROID_MIN_SDK_VERSION__ >= 29
148+ ATrace_endAsyncSection (name, cookie);
149+ #else
150+ if (s_endAsync)
151+ s_endAsync (name, cookie);
152+ #endif
153+ }
154+
155+ static inline std::atomic<bool > s_enabled{false };
156+ static inline std::atomic<bool > s_logEnabled{false };
157+ static inline std::atomic<int32_t > s_nextCookie{1 };
158+
159+ #if __ANDROID_MIN_SDK_VERSION__ < 29
160+ // Resolve async trace functions at load time. These are available on
161+ // devices running API 29+, even when the app targets a lower minSdkVersion.
162+ using AsyncTraceFunc = void (*)(const char *, int32_t );
163+ static inline const AsyncTraceFunc s_beginAsync{
164+ reinterpret_cast <AsyncTraceFunc>(dlsym (RTLD_DEFAULT, " ATrace_beginAsyncSection" ))};
165+ static inline const AsyncTraceFunc s_endAsync{
166+ reinterpret_cast <AsyncTraceFunc>(dlsym (RTLD_DEFAULT, " ATrace_endAsyncSection" ))};
167+ #endif
168+
169+ // Cookie uniquely identifies this trace interval for the async API, analogous
170+ // to os_signpost_id_t on Apple. A cookie of 0 means the region is inactive.
171+ int32_t m_cookie;
172+ // Name is stored as std::string (not const char*) because callers may pass
173+ // c_str() from a temporary std::string, and ATrace_endAsyncSection needs the
174+ // name to match the corresponding begin call.
175+ std::string m_name;
43176 };
44177}
0 commit comments