Специальные события трассировки в нативном коде

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() в одном потоке и завершить его в другом. Обе функции должны вызываться из одного потока.

Советы по удобству

Следующие советы необязательны, но могут упростить анализ нативного кода.

Отслеживание всей функции

При инструментировании стека вызовов или времени выполнения функций может быть полезно отслеживать функции целиком. Чтобы упростить настройку такого отслеживания, можно использовать макрос ATRACE_CALL(). Кроме того, такой макрос позволяет не создавать блоки try и catch в случаях, когда отслеживаемая функция может вызвать исключение или преждевременно вызвать return.

Чтобы создать макрос для отслеживания всей функции, выполните следующие действия:

  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");
}