Definir eventos personalizados para o Systrace

O Systrace mostra informações sobre processos somente no nível do sistema. Por esse motivo, às vezes é difícil saber quais métodos do app ou do jogo estavam sendo executados em determinado momento em relação aos eventos do sistema.

A plataforma Android oferece uma API de rastreamento que pode ser usada para etiquetar uma seção específica do código. Se você capturar um novo rastreamento do sistema da versão "debug" (depuração) do seu app e incluir a opção -a, conforme mostrado no snippet a seguir, esses eventos personalizados aparecerão no relatório do Systrace:

    python systrace.py -a com.example.myapp -b 16384 \
      -o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal \
      dalvik camera input res
    

Este guia descreve como definir eventos personalizados tanto no código gerenciado quanto no código nativo.

Código gerenciado

No Android 4.3 (API de nível 18) e versões posteriores, é possível usar a classe Trace no código para definir eventos personalizados que aparecem nos relatórios do Systrace, conforme mostrado no snippet de código a seguir.

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

Além disso, não é possível chamar beginSection() em uma linha de execução e depois encerrá-lo em outra. É preciso chamar os dois métodos na mesma linha de execução.

Kotlin

    class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup,
                viewType: Int): MyViewHolder {
            return try {
                Trace.beginSection("MyAdapter.onCreateViewHolder")
                MyViewHolder.newInstance(parent)
            } finally {
                // In try and catch statements, always call "endSection()" in a
                // "finally" block. That way, the method is invoked even when an
                // exception occurs.
                Trace.endSection()
            }
        }

        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            Trace.beginSection("MyAdapter.onBindViewHolder")
            try {
                try {
                    Trace.beginSection("MyAdapter.queryDatabase")
                    val rowItem = queryDatabase(position)
                    dataset.add(rowItem)
                } finally {
                    Trace.endSection()
                }
                holder.bind(dataset[position])
            } finally {
                Trace.endSection()
            }
        }
    }
    

Java

    public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            Trace.beginSection("MyAdapter.onCreateViewHolder");
            MyViewHolder myViewHolder;
            try {
                myViewHolder = MyViewHolder.newInstance(parent);
            } finally {
                // In try and catch statements, always call "endSection()" in a
                // "finally" block. That way, the method is invoked even when an
                // exception occurs.
                Trace.endSection();
            }
            return myViewHolder;
        }

       @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            Trace.beginSection("MyAdapter.onBindViewHolder");
            try {
                try {
                    Trace.beginSection("MyAdapter.queryDatabase");
                    RowItem rowItem = queryDatabase(position);
                    dataset.add(rowItem);
                } finally {
                    Trace.endSection();
                }
                holder.bind(dataset.get(position));
            } finally {
                Trace.endSection();
            }
        }
    }
    

Código nativo

O Android 6.0 (API de nível 23) e versões posteriores é compatível com uma API de rastreamento nativa, trace.h, para gravar eventos de rastreamento no buffer do sistema que você pode analisar usando 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.

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 jogo, conforme apresentado no snippet de código a seguir:

        &num;include <android/trace.h>
        &num;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), você pode 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:

        &num;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.

    Além disso, não é possível chamar ATrace_beginSection() em uma linha de execução e encerrá-lo 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. Você pode usar a macro ATRACE_CALL() para facilitar a configuração desse 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:

        &num;define ATRACE_NAME(name) ScopedTrace ___tracer(name)
    
        // ATRACE_CALL is an ATRACE_NAME that uses the current function name.
        &num;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.

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