Eventos de seguimiento personalizados en código nativo

Android 6.0 (nivel de API 23) y versiones posteriores admiten una API de seguimiento nativa, trace.h, que permite escribir eventos de seguimiento en el búfer del sistema que luego puedes analizar con Perfetto o Systrace. Algunos casos de uso comunes para esta API consisten en observar el tiempo que tarda un bloque de código específico en ejecutarse y asociar un bloque de código con comportamientos del sistema no deseados.

Nota: En los dispositivos y emuladores que ejecutan el nivel de API 27 o una versión inferior, si la memoria no es suficiente o está demasiado fragmentada, recibirás el siguiente mensaje: Atrace could not allocate enough memory to record a trace. En ese caso, tu captura no tendrá un conjunto completo de datos, y deberás cerrar procesos en segundo plano, o reiniciar el dispositivo o el emulador.

Si quieres definir los eventos personalizados que se producen en el código nativo dentro de tu app o juego, completa los siguientes pasos:

  1. Define punteros de función para las funciones de ATrace que uses a fin de capturar eventos personalizados en tu app o juego, como se muestra en el siguiente fragmento de código:

    #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. Carga los símbolos de ATrace en el tiempo de ejecución, como se muestra en el siguiente fragmento de código. Por lo general, este proceso se lleva a cabo en un constructor de objetos.

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

    Precaución: Por motivos de seguridad, incluye las llamadas a dlopen() solo en la versión de depuración de tu app o juego.

    Nota: Si quieres que las versiones anteriores a Android 4.3 (nivel de API 18) admitan el seguimiento, puedes usar JNI para llamar a los métodos en el código administrado alrededor del código que se muestra en el fragmento anterior.

  3. Llame a ATrace_beginSection() y a ATrace_endSection() al principio y al final, respectivamente, de tu evento personalizado:

    #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();

    Nota: Cuando llamas a ATrace_beginSection() varias veces, la llamada a ATrace_endSection() solo finaliza el método ATrace_beginSection() al que se llamó más recientemente. Por lo tanto, para las llamadas anidadas, asegúrate de que cada llamada a ATrace_beginSection() coincida con una llamada a ATrace_endSection().

    Además, no puedes llamar a ATrace_beginSection() en un subproceso y terminar la llamada desde un subproceso distinto. Debes llamar a ambas funciones desde el mismo subproceso.

Sugerencias

Las siguientes sugerencias son opcionales, pero podrían facilitar el análisis de tu código nativo.

Cómo hacer el seguimiento de una función completa

Cuando instrumentas tu pila de llamadas o sincronización de funciones, quizás te convenga hacer el seguimiento de funciones completas. Puedes usar la macro ATRACE_CALL() para facilitar la configuración de este tipo de seguimiento. Además, una macro de este tipo te permite omitir la creación de bloques try y catch para casos en los que la función registrada podría generar una excepción o llamar a return con anticipación.

Si deseas crear una macro para hacer el seguimiento de una función completa, sigue estos pasos:

  1. Define la macro:

    #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. Llama a la macro dentro de la función de la que quieres hacer el seguimiento:

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

Cómo asignarles un nombre a los subprocesos

Puedes asignarle un nombre a cada subproceso en el que ocurren tus eventos, como se indica en el siguiente fragmento de código. Este paso facilita la identificación de los subprocesos que pertenecen a acciones específicas dentro de tu juego.

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