Pruebas de rendimiento de Firefox OS con Raptor

25 agosto, 2015 3:47 por

Esta es una traducción del artículo original publicado en el blog de Mozilla Hacks. Traducción por José Suárez.

Cuando hablamos de rendimiento en la web, nos llega a la mente un gran número de preguntas muy familiares:

  • ¿Por qué ésta página tarda tanto en cargar?
  • ¿Como puedo optimizar el JavaScript para que sea más rápido?
  • Si hago algunos cambios al código, ¿se hará más lenta la aplicación?

He estado trabajando en hacer este tipo de preguntas mas fáciles para Gaia, la UI (interfaz gráfica) de Firefox OS, el sistema operativo para sistemas móviles completamente centrado en la web. Escribir paginas web para el escritorio con buen rendimiento tiene su propia idiosincrasia, y escribir aplicaciones nativas usando tecnologías web es un reto de mayor magnitud. Quiero describir los retos que he enfrentado en hacer el rendimiento un tema fácil de enfrentar en Firefox OS, así como documentar mis soluciones y exponer los agujeros en las API Web que necesitan ser llenados.

De ahora en adelante voy a referirme a las páginas web, documentos, y similares como aplicaciones. Aunque los “documentos web” típicamente no necesitan la atención al rendimiento que les voy a dar aquí, las mismas técnicas se les podrían aplicar.

Arreglando el ciclo de vida de las aplicaciones

Una pregunta que comúnmente me preguntan con respecto a las aplicaciones de Firefox OS:

Cuanto tardó la aplicación en cargar?

Una pregunta difícil, dado que no podemos estar seguros que hablamos el mismo lenguaje. Basado en la UX (experiencia de usuario) y mi propia investigación en Mozilla, he tratado de adoptar esta definición para determinar el tiempo que tarda una aplicación en cargar:

El tiempo que tarda en cargar la aplicación es medida desde el momento que el usuario inicia la petición de la aplicación hasta el momento en que la aplicación aparece lista para interactuar con el usuario.

En los dispositivos móviles, esto es generalmente desde el momento en que el usuario hace clic (pulsa) en el icono de la aplicación, hasta que aparece visualmente completamente cargada; cuando parece que el usuario puede comenzar a interactuar con la aplicación. Algo de este tiempo es delegado al sistema operativo para abrir la aplicacián, lo cual se sale de control de la aplicación en cuestión, pero la mayoría del tiempo de carga debería ser dentro de la aplicación.

¿window load, entonces?

Con SPAs (aplicaciones de una sola página), Ajax, cargadores de scripts, ejecuciones diferidas, y sus amigos, window load ya no tiene mucho sentido. Si pudiésemos medir el tiempo que tarda en llegar al load, nuestro trabajo sería mucho mas fácil. Desafortunadamente, no hay manera de inferir el momento en que la aplicación está visualmente cargada en una manera predecible para todos. En vez de eso confiamos en que las aplicaciones nos muestren ese momento para nosotros.

Para Firefox OS, he ayudado a desarrollar una serie de momentos convencionales que son relevantes para cada casi toda aplicación para mostrar su ciclo de vida de carga (también documentado como pautas de rendimiento en MDN):

navegación cargada (navigationLoaded)

La aplicación designa que su interfaz de navegación existe en el DOM, y esta marcada como lista para ser mostrada, por ejemplo cuando un elemento no esta display: none u otra funcionalidad que afectara la visibilidad del elemento de interfaz.

navegación interactiva (navigationInteractive)

La aplicación designa que su interfaz de navegación tiene sus eventos ligados y esta lista para la interacción con el usuario.

visualmente cargada (visuallyLoaded)

La aplicación designa que esta visualmente cargada. Es decir, el contenido de la aplicación existe en el DOM, y ha sido marcado como listo para ser mostrado de nuevo, sin display: none u otra funcionalidad escondida.

contenido interactivo (contentInteractive)

La aplicación designa que ha ligado los eventos para el set mínimo de funcionalidad, para permitir al usuario interactuar con el contenido que se dispone en visuallyLoaded.

totalmente cargada (fullyLoaded)

La aplicación ha sido cargada completamente. O sea, cualquier contenido adicional y sus funcionalidades han sido inyectadas en el DOM, y han sido marcadas como visibles. La aplicación esta lista para la interacción con los usuarios. Cualquier procesamiento de fondo de inicialización ha sido completado y debería haber llegado a un estado estable, en espera a la interacción del usuario.

El momento importante es visuallyLoaded. Esto se correlaciona directamente con lo que el usuario percibe como “estar listo”. Adicionalmente, visuallyLoaded se empareja muy buen con las verificaciones de rendimiento basadas en cámaras.

Denotando momentos

Con una clara definición del ciclo de vida del inicio de las aplicaciones, podemos denotar esos momentos usando la API de tiempo de usuario disponible en Firefox OS a partir de la versión 2.2:

window.performance.mark(string markName)

Específicamente durante un inicio:

performance.mark('navigationLoaded');
performance.mark('navigationInteractive');
...
performance.mark('visuallyLoaded');
...
performance.mark('contentInteractive');
performance.mark('fullyLoaded');

Puedes usar el metodo measure() para crear medidas entre el inicio y otra marca, o incluso entre otras 2 marcas:

// Denotamos el punto de interacción
performance.mark('tapOnButton');

loadUI();

// Capturamos el tiempo desde ahora (sectionLoaded) hasta tapOnButton
performance.measure('sectionLoaded', 'tapOnButton');

Tomar esas medidas de rendimiento es bastante sencillo, con getEntries, getEntriesByName, o getEntriesByType los cuales obtienen una colección de entradas. El punto de este articulo no es el uso de User Timing, así que sigamos.

Armados con el momento en que la aplicación está visualmente cargada, sabemos cuanto tiempo le tomó a la aplicación cargar, porque podemos compararlo con – oh, espera, no. No sabemos aún el momento en que el usuario intenta abrir la aplicación.

Mientras que con los sitios web en el escritorio puede ser fácil determinar el momento exacto en que una petición fue iniciada, hacerlo en Firefox OS no es tan simple. Para abrir una aplicación, el usuario normalmente pulsa en el ícono en la pantalla de inicio. La pantalla de inicio vive en un proceso separado de la aplicación siendo iniciada, y no podemos comunicar los datos de rendimiento entre ellos.

Resolviendo los problemas con Raptor

Sin las APIs o mecanismos de interacción disponibles en la plataforma, para superar ésta y otras dificultades, hemos construido herramientas para ayudar. Así fue cómo la herramienta de pruebas de rendimiento Raptor se originó. Con Raptor podemos recolectar métricas desde aplicaciones Gaia y responder las preguntas de rendimiento que tenemos.

Raptor fue creado con las siguientes metas en mente:

  • Realizar pruebas de rendimiento en Firefox OS sin afectar el rendimiento. No deberíamos necesitar polyfills, código de prueba, o hacks para obtener medidas realistas de rendimiento.
  • Utilizar las APIs web tanto como sea posible. Llenando los vacíos como sea necesario.
  • Mantenerse lo suficientemente flexible para funcionar con los diferentes estilos de arquitectura de las aplicaciones
  • Ser extensible para realizar pruebas de rendimiento fuera de las normas.

Problema: Determinar el momento en que el usuario intenta abrir la aplicación

Dadas dos aplicaciones independientes – la pantalla de inicio, y otra aplicación instalada – ¿cómo podemos crear una marca de rendimiento en una y compararla con otra? Incluso si pudiésemos enviar nuestra marca de rendimiento a otra aplicación, son incomparables, De acuerdo al tiempo de alta resolución el valor producido incrementaría monótonamente desde el momento que la pagina se origina, lo cual es diferente en el contexto de cada página. Esos valores representan la cantidad de tiempo que paso desde un momento a otro, y no el momento absoluto.

La primer ruptura que existe en las APIs de rendimiento es que no hay manera de asociar una marca de rendimiento en una aplicación con otra aplicación. Raptor toma un método simplista: procesamiento de registros (logs).

Si, lo leíste bien, cada vez que Gecko recibe una marca de rendimiento, se registra el mensaje (en adb logcat) y Raptor toma y analiza el registro, buscando las marcas. Un registro típico es algo así (lo vamos a descifrar más adelante):

I/PerformanceTiming( 6118): Performance Entry: clock.gaiamobile.org|mark|visuallyLoaded|1074.739956|0.000000|1434771805380

Lo importante que ver aquí es su origen: clock.gaiamobile.org, o la aplicación Clock (Reloj); aquí la aplicación de reloj ha creado su propia marca visuallyLoaded. En el caso de la pantalla de inicio, queremos crear un marcador que se destina para un contexto totalmente diferente. Esto va a necesitar metadatos adicionales, para que vaya junto a la marca, pero desafortunadamente la API User Timing no tiene esa habilidad aún. En Gaia hemos adoptado por la convencion @ para sobreescribir el contexto de la marca. Vamos a usarlo para marcar el momento en que la aplicación quiere abrirse, al momento en que el usuario pulse sobre el ícono:

performance.mark('appLaunch@' + appOrigin)

Abriendo el reloj, desde la pantalla de inicio y ejecutando este marcador, obtenemos el siguiente registro:

I/PerformanceTiming( 5582): Performance Entry: verticalhome.gaiamobile.org|mark|appLaunch@clock.gaiamobile.org|80081.169720|0.000000|1434771804212

Con Raptor, hemos cambiado el contexto del marcador, si vemos la convencion @.

Problema: números incomparables

La segunda ruptura que existe en las APIs de rendimiento tiene que ver con la incomparabiliadd de marcas de rendimiento a través de los procesos. Usando performance.mark() en dos aplicaciones separadas no produce números significantes que podamos comparar para determinar el tiempo de carga, porque sus valores no comparten una punto de referencia de tiempo. Afortunadamente hay una referencia de tiempo absoluta que todos los JS pueden acceder: el epoch Unix.

Mirando la salida de Date.now() en cualquier momento, nos regresará el numero de milisegundos desde enero 1, 1970. Raptor tuvo que hacer un importante compromiso: abandonar la precisión del tiempo de alta resolución por la comparabilidad del epoch Unix. Mirando al registro anterior, vamos a analizarlo. Vean la correlación de algunas partes de su contraparte con User Timing:

  • Nivel de registro y etiqueta: I/PerformanceTiming
  • ID de proceso: 5582
  • Contexto base: verticalhome.gaiamobile.org
  • Tipo de registro: mark, pero puede ser measure
  • Nombre de registro: appLaunch@clock.gaiamobile.org, la convención @ sobreescribiendo el contexto de la marca
  • Tiempo de inicio: 80081.169720
  • Duración: 0.000000, esto es una marca, no una medida.
  • Epoch: 1434771804212

Para cada marca y medida de rendimiento, Gecko también captura el epoch de la marca, y podemos usar esto para comaprar los tiempos entre los procesos.

Pros y contras

Todo es un juego de balance, y realizar pruebas con Raptor no es la excepción:

  • Cambiamos tiempos de alta resolución por resoluciones de milisegundos, para comparar números entre procesos.
  • Cambiamos las APIs de JavaScript por análisis de registros, para poder acceder a los datos sin inyectar lógica propia en cada aplicación, lo que afectaría su rendimiento.
  • Cambiamos una API de interacción de alto nivel, Marionette, por interacciones de bajo nivel utilizando Orangutan detrás de camaras. Mientras que esto nos provee eventos transparentes para la plataforma, también hace difícil que escribir pruebas complejas. Hay planes para mejorar esto en el futuro agregando integración con Marionette.

¿Porque analizar los registros?

Quizás seas de las personas que piensan que analizar registros sea odioso, y coincido contigo hasta cierto punto. A pesar que deseo que cada solución sea mediante APIs de rendimiento, desafortunadamente aún no existen. Esta es otra razón por la cual proyectos como Firefox OS son importantes para llevar adelante la web: encontramos casos en los que aún no están implementados para la web, ver los agujeros para encontrar que falta, y al final mejorar las APIs para todos intentando llenar esos agujeros con estándares. El análisis de registros es una solución temporal de Raptor hasta que la web nos alcance.

Flujo de trabajo de Raptor

Raptor es un módulo de Node.js construido como parte del proyecto Gaia, que permite al proyecto hacer pruebas de rendimiento contra el dispositivo o el emulador. Una vez que tengas las dependencias del proyecto instaladas, correr pruebas de rendimiento es muy fácil:

  1. Instala el perfil de Raptor en el dispositivo; esto establece varias configuraciones para asistirte con las pruebas de rendimiento. Nota: este es un perfil diferente que reseteará Gaia, asé que ten en mente esto si tienes algunas configuraciones guardadas. make raptor
  2. Elige una prueba para ejecutar. Las pruebas están guardadas en tests/raptor en el directorio Gaia, así que deberás buscarla manualmente. Hay planes para mejorar la API de la línea de comando pronto.
  3. Corre la prueba. Por ejemplo, puedes correr una prueba de rendimiento en la corrida en frío de la aplicación de reloj, usando el siguiente comando, especificando el npumero de veces que corridas para iniciarlo.
    APP=clock RUNS=5 node tests/raptor/launch_test
  4. Observa la salida de la consola. Al final de la prueba, tendrás una tabla de resultados con algunas estadísticas acerca de que las corridas de rendimiento completados. Ejemplo:
[Cold Launch: Clock Results] Results for clock.gaiamobile.org

Metric                            Mean     Median   Min      Max      StdDev  p95
--------------------------------  -------  -------  -------  -------  ------  -------
coldlaunch.navigationLoaded       214.100  212.000  176.000  269.000  19.693  247.000
coldlaunch.navigationInteractive  245.433  242.000  216.000  310.000  19.944  274.000
coldlaunch.visuallyLoaded         798.433  810.500  674.000  967.000  71.869  922.000
coldlaunch.contentInteractive     798.733  810.500  675.000  967.000  71.730  922.000
coldlaunch.fullyLoaded            802.133  813.500  682.000  969.000  72.036  928.000
coldlaunch.rss                    10.850   10.800   10.600   11.300   0.180   11.200
coldlaunch.uss                    0.000    0.000    0.000    0.000    0.000   n/a
coldlaunch.pss                    6.190    6.200    5.900    6.400    0.114   6.300

Visualizando el rendimiento

Acceder al registro sin modificar ayuda a ver cuánto tarda algo, o para determinar si un cambio que hiciste causó que algún numero incrementara, pero no ayuda mucho para monitorear cambios con el tiempo. Raptor tiene dos métodos para visualizar los datos de rendimiento con el tiempo, para poder mejorar el rendimiento.

Medidas oficiales

En raptor.mozilla.org tenemos un panel para valores de rendimiento persistentes con el tiempo. En nuestra infraestructura de automatizacián, ejecutamos pruebas contra dispositivos para cada build que genera mozilla-central o b2g-inbound (Nota: la fuente de los builds puede cambiar en el futuro.) Ahora mismo está limitado para los dispositivos Flame que corren con una memoria de 319MB, pero hay planes de expandirse a diferentes configuraciones de memoria, en dispositivos adicionales, en el futuro cercano. Cuando la automatización recibe un nuevo build, corremos nuestro conjunto de pruebas de rendimiento en el dispositivo, capturando números como el tiempo en que la aplicación carga, y la memoria en fullyLoaded, la duración de los reinicios, y la corriente eléctrica actual. Esos números son guardados y visualizados muchas veces por día, variando según los cambios de código del día.

Mirando las gráficas, puedes ver en algunas aplicaciones específicas, enfocándote o expandiendo la búsqueda de tiempo y hacer manipulaciones de búsquedas avanzadas, para visualizar el rendimiento. Viendo las tendencias con el tiempo, puedes incluso encontrar regresiones que ha tenido Firefox OS.

Visualización local

La misma herramienta de visualización en raptor.mozilla.org también está disponible como una imagen de Docker. Luego de correr las pruebas locales de Raptor, los datos serán reportados en tu propio panel, basado en las medidas locales. Hay algunos prerequisitos para ver localmente los datos, así que asegúrate de leer los documentos de ayuda de Raptor en MDN.

Regresiones de rendimiento

Crear lindas gráficas que muestren las medidas está todo bien, pero encontrar tendencias en los datos y el ruido puede ser muy difícil. Las gráficas nos ayudan a entender los datos y hacerlos accesibles para otros para comunicarse fácilmente acerca del tema, pero usar gráficas para encontrar regresiones en el rendimiento es reactivo; debemos ser proactivos para mantener todo rápido.

Encontrando regresiones en CI

Rob Wood ha hecho un trabajo increíble en nuestra integración continua pre-commit para la detección de regresiones de rendimiento en posibles commits. Con cada pull request enviado al repositorio Gaia, nuestro sistema automático corre pruebas de rendimiento con Raptor contra el branch objetivo, con y sin el parche aplicado. Luego de un numero de iteraciones para estar seguros, tenemos la habilidad de rechazar parches para Gaia si una regresión es muy severa. Por razones de escalabilidad usamos emuladores para correr esas pruebas, así que hay inconvenientes como una gran variabilidad en las medidas reportadas. Esta variabilidad limita la precisión con la cual podemos detectar regresiones.

Encontrando regresiones en automatización

Por suerte tenemos automatización post-commit para correr pruebas de rendimiento contra dispositivos reales, y es desde donde el panel recibe los datos. Basados en una herramienta excelente hecha en Python de Will Lanchance, obtenemos datos históricos diarios, intentando descubrir cualquier regresión que pudo llegar a Firefox OS en los anteriores 7 días. Cualquier anomalía de rendimiento encontrada es rápidamente reportada a Bugzilla, y los componentes relevantes son notificados.

Conclusión y siguientes pasos

Raptor, combinado con el User Timing, nos ha dado el conocimiento para hacer preguntas sobre el funcionamiento de Gaia y recibir las respuestas correctas. En el futuro, planeamos mejorar las APIs de la herramientas y agregarle interacciones de más alto nivel. Raptor debería ser capaz de trabajar con herramientas de terceros, algo que no es fácil de hacer ahora mismo.

Raptor ha sido una herramienta genial de crear, al mismo tiempo ayudándonos a llevar la web mas allá en el espacio del rendimiento. Planeamos usarlo para mantener Firefox OS rápido, y para seguir proactivos protegiendo el rendimiento de Gaia.

The following two tabs change content below.

jorgev

AMO Product Manager at Mozilla
Jorge trabaja para el equipo de complementos de Mozilla, y se dedica a Mozilla Hispano y Mozilla Costa Rica en su tiempo libre. Actualmente está encargado del blog de Mozilla Hispano Labs.

Compartir artículo:

  • ¡Participa!

    Colabora con la comunidad »
    En Mozilla lo importante son las personas. Descubre cómo puedes colaborar.

    Boletín Firefox

    Suscríbete al boletín de novedades de Firefox.

  • Descargas

    Descarga los programas de Mozilla.

    Lo más visto

    cc-by-sa