Define eventos personalizados para Systrace

Systrace te muestra información sobre los procesos solo a nivel del sistema; por lo tanto, algunas veces resulta difícil saber cuáles de los métodos de tu app o juego estaban en ejecución en un momento determinado respecto de los eventos del sistema.

La plataforma Android proporciona una API de seguimiento que puedes usar para etiquetar una sección de código específica. Si capturas un seguimiento del sistema nuevo de la versión de "depuración" de tu app e incluyes la opción -a, como se muestra en el siguiente fragmento, estos eventos personalizados aparecen en un informe de 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
    

En esta guía se describe cómo definir eventos personalizados tanto en el código administrado como en el código nativo.

Código administrado

En Android 4.3 (nivel de API 18) y versiones posteriores, puedes usar la clase Trace en tu código a fin de definir eventos personalizados que, luego, aparezcan en los informes de Systrace, como se muestra en el siguiente fragmento de código.

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

Además, no puedes llamar a beginSection() en un subproceso y terminar la llamada desde un subproceso distinto. Debes llamar a ambos métodos en el mismo subproceso.

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

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

Si quieres definir los eventos personalizados que ocurren 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 juego, como se muestra en el siguiente fragmento de código:

        &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. 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. Llama a ATrace_beginSection() y ATrace_endSection() al principio y al final de tu 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();
        

    Nota: Cuando llamas a ATrace_beginSection() varias veces, la llamada a ATrace_endSection() solo finaliza el método ATrace_beginSection() 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() correctamente.

    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 que la configuración de este tipo de seguimiento sea más sencilla. Además, esta macro te permite omitir la creación de bloques try y catch para casos en los que la función de la que estás haciendo el seguimiento podría mostrar una excepción o llamar a return de manera anticipada.

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

  1. Define la 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. 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.

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