Hola, soy Melanie Drake, ingeniera de sistemas en Operaciones de Desarrollo de JMP. Nuestro grupo proporciona procesos automatizados que generan ejecutables e instaladores de JMP, ejecutan pruebas automatizadas y muchas otras tareas para apoyar la I+D de JMP.
Hoy voy a hablar sobre cómo recopilamos información de los registros de procesos y usamos JMP para detectar problemas y supervisar el estado general de uno de estos procesos automatizados. Los procesos generan gran cantidad de datos, ya sean lecturas de instrumentos, mediciones de una línea de producción o registros de estado. Estos datos se pueden guardar en texto plano o, en ocasiones, en formato estructurado, como XML, JSON o CSV. Toda esta información ofrece numerosas oportunidades para encontrar y solucionar problemas.
Sin embargo, estos datos suelen escribirse en numerosos archivos separados que se crean y actualizan constantemente. Puede resultar muy difícil detectar y rastrear errores. Hablaré del flujo de pruebas unitarias JSL nocturnas. Este proceso genera muchos archivos de registro a diario. Descubrimos que era muy difícil rastrear y solucionar errores específicos. Teníamos una cantidad ingente de datos, pero utilizarlos era muy complicado.
¿Qué es el flujo de pruebas unitarias de JSL? Es un conjunto de pruebas de regresión de JSL que ejecutamos con la compilación diaria de JMP. Se ejecuta cada noche en todas las versiones de JMP que están en desarrollo y en diversas versiones de sistemas operativos Windows y Macintosh. Los evaluadores utilizan esta ejecución nocturna para detectar errores en JMP y que los desarrolladores puedan corregirlos rápidamente. Si algún flujo de pruebas no se ejecuta correctamente, no se obtienen los resultados de esas pruebas.
Durante algunas semanas del verano, tuvimos cuatro versiones de JMP en desarrollo simultáneamente. Esto significaba que ejecutábamos 18 pruebas diarias con distintas versiones de JMP y sistemas operativos. Cada prueba podía tardar entre 8 y 12 horas. Dividimos cada prueba de Windows en tres que se ejecutaban simultáneamente para que los resultados de todas las pruebas de Windows se completaran mucho más rápido. Esto permitió a nuestro equipo de pruebas en Pekín utilizar esos resultados el mismo día.
En resumen, revisábamos 36 registros de consola distintos cada día. Podíamos dedicar, y de hecho dedicamos, mucho tiempo a intentar averiguar dónde y cuándo se producía cada error. La solución para gestionar conjuntos de datos dispares es consolidarlos todos en un solo lugar: una tabla de datos de JMP, por supuesto.
Una vez en JMP, puedes visualizar y analizar tus datos, y compartir tus hallazgos con otros mediante JMP Live. En nuestro caso, recopilamos nuestros registros en tablas de datos de JMP. Usamos gráficos para rastrear y solucionar todos nuestros problemas, y podemos supervisar continuamente el estado del sistema publicando en JMP Live. Terminamos con dos tablas de datos diferentes, ya que la salida de nuestros registros era distinta entre Macintosh y Windows. Si bien algunos errores eran comunes, muchos otros eran específicos de un sistema operativo.
Primero, describiré el flujo de trabajo general y luego explicaré el script y haré una demostración de nuestro proceso. El primer paso es determinar dónde se encuentran los datos y cómo se importarán a JMP. Los archivos de registro se pueden guardar en disco o en un servidor, y se puede acceder a ellos desde la web. También se pueden recuperar mediante una API o una consulta a la base de datos. En nuestro caso, accedemos a un archivo de texto a través de la interfaz web desde un servidor Jenkins, donde ejecutamos todos nuestros procesos automatizados. Utilizamos una solicitud HTTP para obtener la información y, posteriormente, usamos Parse JSON para convertirla a un formato utilizable.
El segundo paso consiste en importar los datos a JMP. Esto puede comenzar con los archivos de registro, a los que quizás se les añadan otros metadatos. Se puede colocar todo el registro en una sola celda, o distribuirlo en varias celdas, columnas o incluso filas. Todo depende del tipo de datos y de cómo se necesite visualizarlos. En nuestro caso, tenemos un registro por cada ejecución de flujo. Colocamos una ejecución de flujo por fila y un registro completo en una sola celda. Además, recopilamos muchos otros metadatos.
El tercer paso consiste en analizar el texto. Puede usar Parse XML o Parse JSON para obtener la información sin procesar. Una vez que la tenga, puede usar expresiones regulares, coincidencia de patrones JSL o funciones de caracteres JSL para extraer la información. Puede usar columnas de fórmulas, columnas de indicadores o columnas de respuestas múltiples para recopilar esa información en formatos que pueda usar en sus gráficos.
En nuestro caso, utilizamos casi todos los métodos mencionados. Empleamos columnas de fórmulas para recopilar información específica para las columnas de metadatos y, posteriormente, funciones de caracteres JSL y expresiones regulares para analizar los registros. También utilizamos columnas de indicadores y de respuestas múltiples para recopilar información y generar gráficos visualmente atractivos.
El cuarto paso consiste en explorar y graficar los datos. JMP ofrece Graph Builder y muchas otras plataformas de graficación. Estas permiten encontrar patrones en los errores, determinar dónde se correlacionan entre sí y realizar un seguimiento a lo largo del tiempo. En nuestro caso, utilizamos Graph Builder de forma intensiva. Creamos mapas de calor para visualizar las correlaciones. Empleamos gráficos de barras y diagramas de dispersión para rastrear los errores a lo largo del tiempo, las versiones de JMP y las versiones del sistema operativo. También utilizamos gráficos de barras apiladas y mapas de calor para visualizar el estado de los flujos y las ejecuciones faltantes a lo largo del tiempo. Mostraré algunos ejemplos más adelante.
Finalmente, en el paso cinco, querrás compartir tus gráficos con tus compañeros. Puedes presentar hallazgos específicos a audiencias específicas. Puedes compartirlos para fomentar la visibilidad, la transparencia y el trabajo en equipo. También puedes ocultar datos privados, personales o confidenciales. En nuestro caso, publicamos un subconjunto de los datos en un servidor interno de JMP Live. Publicamos solo lo necesario para los gráficos. Una vez publicados, estos gráficos son muy fáciles de analizar en las reuniones y permiten supervisar fácilmente el buen funcionamiento general de todo nuestro proceso.
JMP 19 incluye una nueva función llamada Etiquetas de Columna. Nos han resultado extremadamente útiles para este proyecto. Usamos tres etiquetas de columna: una para etiquetar las marcas de tiempo de ciertos hitos, otra para etiquetar los errores que encontramos y una tercera para etiquetar las columnas que usamos en los gráficos. Les mostraré cómo funciona todo esto más adelante.
¿Por qué hicimos esto? Para empezar, simplifica enormemente nuestro código JSL. Podemos referirnos a las columnas con una etiqueta específica en lugar de codificar directamente el nombre de cada columna. Esto también significa que no necesitamos actualizar los nombres de las columnas cuando corregimos errores, por ejemplo. Simplemente etiquetamos la nueva columna y se incorpora automáticamente. De esta forma, resulta muy sencillo seleccionar y publicar solo los datos necesarios.
Ahora veremos una demostración y les mostraré nuestro script JSL. Antes de empezar, ejecutamos unas pocas líneas para configurarlo todo. Incluimos otros scripts con funciones y definiciones de variables. Configuramos la ruta de la carpeta donde se guardan nuestras tablas principales. Estas contienen todos los datos que hemos recopilado hasta ahora. Finalmente, abrimos un archivo CSV con la información de configuración.
Nuestro archivo CSV real tiene más columnas, pero contienen información interna que eliminé para esta presentación. La información básica que necesitamos para cada proyecto es el nombre de cada flujo de prueba y la tabla principal específica del sistema operativo que contiene todos los datos recopilados hasta el momento de todos los flujos de prueba para dicho sistema operativo.
Ahora necesitamos importar nuestros datos. Cada combinación de versión de JMP y versión de sistema operativo constituye un proyecto de Jenkins independiente con su propia página. Cada ejecución de flujo de prueba es una subpágina de la página principal. Analizaremos este primer proyecto: JMP 19 en Sonoma. Estableceré la iteración en 1. A continuación, importaré el nombre de cada trabajo y la tabla de destino donde se almacenarán los datos.
Comenzamos usando una solicitud HTTP para obtener el JSON de la página principal, que contiene una lista de las URL de cada ejecución de transmisión. Enviamos esa solicitud a Jenkins, obtenemos el JSON y lo analizamos para nuestra lista de compilaciones. Como no puedo ejecutar esto en vivo aquí, simplemente crearé una lista. Como pueden ver, es muy larga. Esta lista contiene el ID de compilación de cada ejecución de transmisión de prueba realizada hasta ahora para JMP 19 en Macintosh Sonoma.
Pero ya disponemos de gran parte de estos datos en la tabla principal, y no queremos perder tiempo leyendo y procesando información que ya tenemos. Para evitar esto, abrimos nuestra tabla principal. Esta contiene todos los resultados de Macintosh, no solo los de este proyecto. A continuación, obtenemos una lista de los ID de compilación que ya tenemos para este proyecto. En otras palabras, los ID de las filas de la tabla principal que coinciden con JMP 19 en Sonoma. Convertimos ambas listas de ID de compilación en matrices asociativas para poder restar los ID actuales de nuestra nueva lista. La magia matemática de las matrices asociativas nos proporciona una lista reducida con solo las ejecuciones que aún no hemos procesado. Será mucho más fácil y rápido procesar 3 filas que 395 y luego descartar la mayoría.
El segundo paso consiste en importar esos datos a JMP. Si la lista no está vacía, utilizamos una función que hemos desarrollado para obtener los datos necesarios de cada ejecución de flujo de datos en nuestra lista y los importa a una tabla de datos. Esta función vuelve a utilizar Parse JSON para extraer los datos de seguimiento que necesitamos y añadirlos a la nueva tabla de datos, creando una columna y una fila para cada flujo de prueba.
Voy a abrir una tabla de ejemplo para que veas cómo es. Como puedes ver, ya tenemos algunos metadatos. Obtenemos la duración de cada flujo, su nombre, su número de identificación, el estado de la compilación (si se realizó correctamente o no) y la fecha y hora de inicio. Lo más importante es que tenemos la URL que lleva al archivo de registro de texto, del cual obtendremos toda la información. A continuación, añadiremos una columna a esta tabla para almacenar el contenido del archivo de registro y utilizaremos la función "Cargar archivo de texto" para leerlo desde esa URL e insertarlo en la tabla.
El tercer paso consiste en analizar los metadatos y los registros de la consola de estas filas. Añadimos algunas columnas derivadas nuevas. Por ejemplo, tomamos la duración y la convertimos en un tiempo de ejecución medido en minutos, lo cual facilita su comprensión. También añadimos una columna de fecha que solo contiene la fecha, sin la marca de tiempo. Esto simplifica considerablemente algunos de nuestros gráficos. Finalmente, añadimos otra columna que indica el día de la semana. También la utilizamos en algunos de nuestros gráficos.
El siguiente paso es preparar el terreno para analizar el registro. Añadimos columnas para recopilar las horas de inicio de ciertos hitos. Definimos columnas de inicio diferentes para Windows y Macintosh, y al añadirlas, las etiquetamos como inicios. Aquí abajo pueden ver estas etiquetas verdes que indican que todas las columnas están etiquetadas con la misma etiqueta.
La siguiente columna que añadimos es la de último elemento iniciado. Si la prueba se detiene debido a un error, podemos obtener el último hito que se completó correctamente. Será la última columna con la etiqueta "starts" para esta fila que tenga una marca de fecha y hora. Si el proceso pierde la conexión con la máquina virtual o con el servidor, obtendremos errores de JavaScript en el registro. Sin duda, recopilamos estos errores.
Finalmente, añadimos una columna de respuesta múltiple para recopilar información de errores. Volveré a esto en breve. El siguiente paso es comenzar a analizar el registro. Usamos un bucle para analizar toda la consola de cada fila, extraer la información que necesitamos y agregarla a todas las nuevas columnas de esa fila. El registro de la consola se encuentra en una sola celda por fila. Lo convertimos en una lista de cadenas, donde cada línea del registro es una cadena.
Luego, recorremos la lista una vez y comparamos cada línea con los elementos específicos que buscamos: horas de inicio, errores, etcétera. Esto es mucho más eficiente que analizar el registro una vez por cada elemento que queremos extraer. Lo primero que buscamos es un error de JavaScript. Si lo encontramos, lo obtenemos y lo guardamos en la tabla de datos, y luego pasamos al registro de la consola de la siguiente fila. Una vez que el trabajo se ha detenido de esta manera, no habrá más información que obtener.
En nuestros proyectos de Jenkins, buscamos errores específicos, y esas búsquedas se imprimen al final del registro y se denominan buscadores de texto. Cuando encontramos la primera instancia de un buscador de texto, recopilamos los datos hasta el final del registro. Detenemos el bucle en ese punto, ya que este es el final del registro. Pasamos esos datos a una función, que les mostraré en breve.
Además de esos dos aspectos, buscamos los hitos e insertamos sus marcas de fecha y hora en la tabla para cada uno. Finalmente, al final del bucle, recopilamos los identificadores de texto. Cada identificador de texto encontrado se añade a la tabla como una columna indicadora con un cero si no se encuentra el error o un uno si se encuentra.
Esto deja casos sin registrar donde no buscamos ese error. Por ejemplo, si es nuevo, no lo habíamos buscado antes. Cada columna de búsqueda de texto está etiquetada como tal. Una vez completadas estas columnas indicadoras, obtenemos los nombres de cada columna etiquetada como "buscador de texto" que contiene un "1" en esa fila y la añadimos a la columna de respuestas múltiples, que se utiliza posteriormente para la creación de gráficos.
Finalmente, agregamos el último elemento a la tabla de esta fila. Los bucles para las filas siguientes compararán los textos encontrados con las columnas existentes y solo agregarán los que aún no tenemos. A continuación, se muestra un ejemplo de cómo se ve esta tabla después de ejecutar todo el proceso. Conservamos todos los metadatos recopilados inicialmente, el texto del registro y todos los datos derivados. Ahora, todas las columnas de inicio incluyen marcas de fecha y hora, por lo que sabemos con exactitud cuándo comenzó cada una.
Nuestro último proceso iniciado recopiló el último elemento que se procesó correctamente. No se produjeron errores de desconexión de Java, pero de haberlos habido, estarían registrados.
Finalmente, hemos añadido todas las columnas de nuestro buscador de texto. Verás que están marcadas en rojo. Todas estas ejecuciones fueron exitosas, pero incluí algunos errores detectados para mostrarte cómo se ven en nuestra columna de respuestas múltiples. Como puedes ver, esta tuvo dos fallos y está separada por punto y coma. Esta otra solo tuvo uno.
El último paso consiste en añadir toda esta nueva información a nuestra tabla de datos principal. Es simplemente una concatenación. Íbamos a añadir nuestras tres nuevas filas a la tabla. El último paso es guardar esta tabla. La comprimimos al guardarla, ya que estos registros a veces tienen cientos o incluso miles de líneas. Con 1333 filas, eso representa una gran cantidad de datos, y estas tablas pueden llegar a ser muy grandes. Esa es la parte central del proceso y lo que más tiempo nos llevó desarrollar. Decidir exactamente qué información necesitábamos fue un proceso iterativo.
El último paso consiste en explorar y graficar tus datos. Hemos guardado muchos gráficos que diseñamos para ayudarnos a diagnosticar problemas. Te mostraré algunos. Este muestra la duración de la transmisión según la fecha y el sistema operativo.
Las transmisiones de corta duración suelen ser aquellas que se emiten rápidamente y simplemente se detienen. Rara vez obtenemos resultados de ellas. Una transmisión que duró mucho tiempo probablemente se atascó en algún punto y probablemente terminará agotándose. Todos estos son problemas que debemos investigar más a fondo. Esta transmisión es muy constante. Siempre es genial ver eso.
Otro gráfico útil es este que muestra las correlaciones entre los errores de JavaScript y la última ejecución exitosa. Esto puede ser muy útil para diagnosticar problemas. Este último es probablemente mi favorito. Es un mapa de calor que muestra el estado de compilación en las ejecuciones faltantes.
Como puede verse, ejecutamos estas pruebas cinco días a la semana. No las ejecutamos los fines de semana. En esta tabla faltan muchas pruebas, ya que simplemente no se realizó ninguna. Estas pruebas se ejecutaron, pero con errores. Todas las pruebas exitosas se realizaron correctamente. Aquí están las tres filas que acabamos de agregar a esta tabla.
El quinto paso consiste en compartir los gráficos. Dado que ya están publicados en JMP Live, solo necesitamos actualizar la tabla y los gráficos en JMP Live. Creamos una conexión a nuestra instancia de JMP Live. Esto se define en Archivo > Publicar > Administrar conexiones. Abrimos la tabla de datos y, a continuación, ejecutamos el script del subconjunto.
Quizás se pregunten por qué nos molestamos en hacerlo. Verán que nuestra tabla principal original tiene 39 columnas. Nuestra tabla de subconjuntos solo tiene nueve, y esas nueve columnas no incluyen los extensos registros de la consola. Esto hace que estas tablas de subconjuntos sean mucho más pequeñas, lo que mejora el rendimiento de JMP Live. Además, es una ventaja poder ocultar fácilmente los datos privados o confidenciales. Solo publicamos los datos estrictamente necesarios para los gráficos que utilizamos.
Enviamos esa tabla de subconjuntos y otra información, como la conexión a JMP Live, a una función que hemos desarrollado que recorre cada gráfico de la tabla. A continuación, actualiza cada tabla. Después, actualiza la tabla de subconjuntos en cada gráfico de JMP Live y, finalmente, pasa a la siguiente tabla, en este caso, Windows. Con esto concluye el proceso. Cada día que se ejecuta, obtenemos resultados completamente nuevos.
Aquí tenéis una muestra de algunos de los gráficos que hemos publicado en JMP Live. Desde nuestro panel de control, estos y otros gráficos nos ofrecen una visión general muy rápida, lo que nos permite identificar fácilmente tendencias y puntos problemáticos. Al tratarse de JMP Live, podemos examinar cada gráfico individualmente y obtener información adicional. Todos nuestros gráficos se publican con filtros de datos locales. Esto nos permite analizar únicamente ciertas ejecuciones de flujo de prueba durante un periodo de tiempo determinado. Por ejemplo, solo las ejecuciones de flujo de prueba de JMP 20 durante las últimas semanas.
En definitiva, ¿qué logramos? Automatizamos nuestro análisis. Basta con consultar las gráficas para ver qué sucede. Se actualizan automáticamente. No tenemos que gestionar minuciosamente errores específicos. Se publican automáticamente. Con todo predefinido, el proceso funciona sin problemas a diario. A medida que analizábamos nuestros datos, decidimos qué debíamos monitorizar. Modificamos la información que imprimíamos en los registros de la consola, como añadir las horas de inicio de los hitos. Decidimos qué gráficas eran las más adecuadas para mostrar los problemas que debíamos resolver.
Para cuando terminamos de ejecutar este script, habíamos logrado un gran éxito. Habíamos resuelto todos nuestros problemas hasta el momento, y nuestras gráficas ya no eran tan llamativas, puesto que las pruebas de flujo solían ejecutarse correctamente. Gracias.