ネイティブ コード内のカスタム トレース イベント

Android 6.0(API レベル 23)以降では、ネイティブ トレース API trace.h がサポートされており、システム バッファにトレース イベントを書き込んで、Perfetto または Systrace を使用して分析できます。この API の一般的なユースケースとしては、特定のコードブロックが実行に要した時間をモニタリングする場合や、コードブロックと望ましくないシステム動作とを関連付ける場合などが該当します。

注: 使用できるメモリが不足しているか、メモリが過度に断片化している場合は、API レベル 27 以下のデバイスとエミュレータでは、「Atrace could not allocate enough memory to record a trace」というメッセージが表示されます。この問題が発生し、キャプチャに完全なデータセットがない場合は、バックグラウンド プロセスを閉じるか、デバイスまたはエミュレータを再起動します。

アプリやゲーム内のネイティブ コード内で発生するカスタム イベントを定義する手順は次のとおりです。

  1. アプリやゲームの中でカスタム イベントをキャプチャするために使用する ATrace 関数の関数ポインタを定義します。次のコード スニペットをご覧ください。

    #include <android/trace.h>
    #include <dlfcn.h>
    
    void *(*ATrace_beginSection) (const char* sectionName);
    void *(*ATrace_endSection) (void);
    
    typedef void *(*fp_ATrace_beginSection) (const char* sectionName);
    typedef void *(*fp_ATrace_endSection) (void);
  2. 下記のコード スニペットに示すように、実行時に ATrace シンボルをロードします。このプロセスは通常、オブジェクト コンストラクタ内で実行します。

    // Retrieve a handle to libandroid.
    void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
    
    // Access the native tracing functions.
    if (lib != NULL) {
        // Use dlsym() to prevent crashes on devices running Android 5.1
        // (API level 22) or lower.
        ATrace_beginSection = reinterpret_cast<fp_ATrace_beginSection>(
            dlsym(lib, "ATrace_beginSection"));
        ATrace_endSection = reinterpret_cast<fp_ATrace_endSection>(
            dlsym(lib, "ATrace_endSection"));
    }

    注: セキュリティ上の理由から、アプリやゲームのデバッグ バージョンに限り、dlopen() 呼び出しを組み込みます。

    注: Android 4.3(API レベル 18)にトレース サポートを戻すには、JNI を使用して、上記のスニペットに示したコードを中心とするマネージコード内のメソッドを呼び出します。

  3. カスタム イベントの最初と最後で、それぞれ ATrace_beginSection()ATrace_endSection() を呼び出します。

    #include <android/trace.h>
    
    char *customEventName = new char[32];
    sprintf(customEventName, "User tapped %s button", buttonName);
    
    ATrace_beginSection(customEventName);
    // Your app or game's response to the button being pressed.
    ATrace_endSection();

    注: ATrace_beginSection() を複数回呼び出した後で ATrace_endSection() を呼び出すと、最後に呼び出された ATrace_beginSection() メソッドだけが終了します。そのため、ネスト呼び出しの場合、各 ATrace_beginSection() 呼び出しと各 ATrace_endSection() 呼び出しが適切にマッチングしているか確認します。

    また、あるスレッドで ATrace_beginSection() を呼び出して、別のスレッドから終了することはできません。1 つのスレッド内で両方の関数を呼び出す必要があります。

ヒント

以下のヒントは必須ではありませんが、ネイティブ コードの分析が容易になる場合があります。

関数全体をトレースする

コールスタックや関数のタイミングをインストルメント化する場合は、関数全体をトレースすると便利です。ATRACE_CALL() マクロを使用すると、この種のトレースを簡単にセットアップできます。また、このようなマクロを使用すると、トレース対象関数が例外をスローしたり、早期に return を呼び出したりする可能性のあるケースにおいて、try ブロックや catch ブロックの作成をスキップできます。

関数全体をトレースするマクロを作成する手順は次のとおりです。

  1. マクロを定義します。

    #define ATRACE_NAME(name) ScopedTrace ___tracer(name)
    
    // ATRACE_CALL is an ATRACE_NAME that uses the current function name.
    #define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)
    
    class ScopedTrace {
      public:
        inline ScopedTrace(const char *name) {
          ATrace_beginSection(name);
        }
    
        inline ~ScopedTrace() {
          ATrace_endSection();
        }
    };
  2. トレースする関数内でマクロを呼び出します。

    void myExpensiveFunction() {
      ATRACE_CALL();
      // Code that you want to trace.
    }

スレッドに名前を付ける

下記のコード スニペットに示すように、イベントが発生する各スレッドに対して名前を付けることができます。この手順を行っておくと、ゲーム内の個々のアクションに属するスレッドを簡単に識別できるようになります。

#include <pthread.h>

static void *render_scene(void *parm) {
    // Code for preparing your app or game's visual components.
}

static void *load_main_menu(void *parm) {
    // Code that executes your app or game's main logic.
}

void init_threads() {
    pthread_t render_thread, main_thread;

    pthread_create(&render_thread, NULL, render_scene, NULL);
    pthread_create(&main_thread, NULL, load_main_menu, NULL);

    pthread_setname_np(render_thread, "MyRenderer");
    pthread_setname_np(main_thread, "MyMainMenu");
}