Eventos de rastreamento personalizado em código nativo

O Android 6.0 (API de nível 23) ou mais recentes oferece suporte para uma API de rastreamento nativa, trace.h, para registrar eventos de rastreamento no buffer do sistema que podem ser analisados usando o Perfetto ou o Systrace. Casos de uso comuns para essa API incluem observar o tempo que um determinado bloco de código leva para executar e associar um bloco com comportamento de sistema indesejado.

Observação: em dispositivos e emuladores com API de nível 27 e versões anteriores, se não houver memória suficiente disponível ou se a memória estiver muito fragmentada, você receberá a seguinte mensagem: Atrace could not allocate enough memory to record a trace. Se isso acontecer e a captura não tiver um conjunto completo de dados, feche os processos em segundo plano ou reinicie o dispositivo ou emulador.

Para definir eventos personalizados que ocorrem no código nativo do seu app ou jogo, siga as etapas a seguir:

  1. Defina os ponteiros de função para as funções ATrace usadas para capturar eventos personalizados no app ou jogo, conforme apresentado no snippet de código a seguir:

    #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. Carregue os símbolos ATrace no tempo de execução, conforme mostrado no snippet de código a seguir. Normalmente, esse processo é executado em um construtor de objeto.

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

    Cuidado: por motivos de segurança, só inclua chamadas dlopen() na versão de depuração do seu app ou jogo.

    Observação: para oferecer compatibilidade de rastreamento até o Android 4.3 (API de nível 18), é possível usar o JNI para chamar os métodos no código gerenciado ao redor do código mostrado no snippet anterior.

  3. Chame ATrace_beginSection() e ATrace_endSection() no início e no fim do seu evento personalizado, respectivamente:

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

    Observação: quando ATrace_beginSection() é chamado várias vezes, chamar ATrace_endSection() encerra apenas o método ATrace_beginSection() chamado mais recentemente. Por esse motivo, combine corretamente cada chamada de ATrace_beginSection() com uma chamada de ATrace_endSection() para chamadas aninhadas, como as do snippet a seguir.

    Além disso, não é possível chamar ATrace_beginSection() em uma linha de execução e depois encerrá-la em outra. É preciso chamar as duas funções na mesma linha de execução.

Dicas para sua conveniência

As dicas a seguir são opcionais, mas podem facilitar a análise do seu código nativo.

Rastrear uma função inteira

Ao instrumentar a pilha de chamadas ou o tempo da função, pode ser útil rastrear funções inteiras. É possível usar a macro ATRACE_CALL() para facilitar esse tipo de rastreamento. Além disso, essa macro permite pular a criação de blocos try e catch para casos em que a função rastreada pode gerar uma exceção ou chamar return antecipadamente.

Para criar uma macro para rastrear uma função inteira, siga estas etapas:

  1. Defina a 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. Chame a macro na função que você quer rastrear:

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

Nomeie suas linhas de execução

Você pode dar um nome para cada linha de execução em que os eventos ocorrem, conforme demonstrado no snippet de código a seguir. Esta etapa facilita a identificação das linhas de execução que pertencem a ações específicas no seu jogo.

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