Casi todos habremos tenido alguna vez la sensación de no entender lo que ocurre en nuestro código ante una incidencia en el entorno de producción de uno de nuestros desarrollos. Habíamos comprobado el correcto funcionamiento del código utilizando pruebas unitarias, pruebas de integración, ya habíamos pasado los filtros de QA en entornos de test y desarrollo, los procesos de despliegue automático no habían dado problemas… y sin embargo, acabamos viendo una condición que parecía imposible cuando estábamos tecleando las líneas encargadas de esa funcionalidad.

El contexto en el que se tienen que desenvolver las aplicaciones en producción acaba generando estados en ocasiones contradictorios al que podíamos suponer durante el desarrollo de nuestro código o sus dependencias. Bien por las diferencias con el hardware entre entornos, el volumen de tráfico, el tiempo continuado en ejecución o las diferentes entradas de datos que se deben soportar, poder reproducir siempre las mismas condiciones resulta prácticamente imposible.

Cuando se presenta esta situación la solución se hace más complicada aún, ya que en estos entornos no disponemos de ninguna de nuestras herramientas con las que trabajamos en el día a día. Tenemos que averiguar en qué condiciones se están ejecutando nuestras líneas de código sin poder contar con el código fuente, ni nuestro IDE y sus herramientas de depuración, y seguramente con una conexión bastante limitada, sin privilegios de acceso para conocer datos del entorno e incluso con un ancho de banda muy ajustado.

Y es precisamente en este contexto cuando tenemos que dar una respuesta más rápida y precisa para resolver una incidencia. Una anomalía en producción supone siempre un problema para el negocio y no se pueden dar soluciones parciales o de “prueba y error”.

A la hora de conocer qué está sucediendo en nuestro código tenemos que intentar recopilar la mayor cantidad de información sobre lo que afecta a la ejecución de la forma menos intrusiva posible. Nuestros primeros aliados son los ficheros de log y métricas como contadores de rendimiento, pero muchas veces su falta o exceso de detalle no nos permiten intuir nada sobre las causas de lo que está sucediendo. También podemos capturar datos de otras fuentes de información y llevarlos a nuestros entornos, pero puede que no tengamos autorización para ello. Sin embargo, hay una herramienta más que puede representar el estado de nuestra aplicación y que muchos desarrolladores solemos pasar por alto en estas situaciones: los volcados de memoria.

Un volcado de memoria es una fotografía del estado interno en memoria de un proceso en el momento en que se tomó. Dentro de él se almacenan datos muy valiosos para averiguar qué está sucediendo en el proceso como el estado de la pila de llamadas para todos los hilos, el valor actual de las variables o el contenido del heap. Podemos saber si ha pasado el Garbage Collector y los bloqueos que pueden estar ocurriendo entre hilos.

El principal problema para trabajar con volcados de memoria es que para sacarle todo el jugo a la información que contiene hay que conocer muy bien tanto las estructuras a bajo nivel del proceso y del sistema operativo, como el código de la aplicación en ejecución. Las herramientas generalistas de alto nivel no pueden tener ese conocimiento de nuestra aplicación, por lo que se limitan a orientarnos para el análisis posterior que deberemos realizar por nuestra parte.

Según el sistema operativo sobre el que ejecutemos nuestra aplicación y el lenguaje o plataforma sobre la que la hayamos desarrollado, las herramientas a emplear serán distintas. En este artículo vamos a conocer una forma muy sencilla y muy poco intrusiva para nuestros entornos de producción, de capturar y analizar volcados de memoria de procesos de aplicaciones desarrolladas en la plataforma Microsoft .NET y ejecutadas en sistemas operativos Windows.

La manera más sencilla de obtener un volcado de memoria con el estado actual de un proceso de Windows está disponible en el administrador de tareas. Desde la pestaña Detalles en la que nos aparecen listados todos los procesos actualmente en ejecución tan solo tendremos que hacer click derecho sobre el nombre del proceso del que queremos conocer su estado y seleccionar la opción Crear archivo de volcado. En el ejemplo que vemos a continuación, al tratarse de una aplicación ejecutándose sobre .NET Core, elegiremos un proceso de nombre dotnet.

Nos aparecerá un pequeño cuadro de diálogo que nos informará de la ruta en la que se ha almacenado el fichero que contiene el volcado de memoria. Generalmente esta ruta será la carpeta temporal del usuario con el que hemos iniciado sesión en Windows.

Una vez que tenemos localizado el fichero ya podemos analizarlo en la misma máquina o copiarlo a otro entorno donde podamos examinarlo con mayor detenimiento sin sobrecargar innecesariamente los equipos de producción. Para esta tarea de análisis nos debemos apoyar en alguna herramienta que facilite la inspección de todo el contenido del volcado. Existen varias alternativas en el mercado, algunas gratuitas y otras de pago, y últimamente también de ejecución en la nube en vez de en local, pero todavía la más potente sigue siendo WinDbg.

Esta herramienta gratuita de ejecución local de Microsoft permite investigar los ficheros de volcados de memoria mediante un entorno gráfico y una consola con innumerables comandos con los que obtener cualquier tipo de detalle sobre nuestro proceso. Sin embargo, precisamente la potencia y versatilidad de esta herramienta exige un conocimiento importante del funcionamiento a bajo nivel de los procesos de Windows y .NET, por lo que resulta bastante intimidatoria para quienes estemos comenzando a andar en este terreno. Por este motivo prefiero centrarme en otra herramienta que nos proporciona Microsoft para este mismo propósito, que aunque no nos ofrezca todas las posibilidades de Windbg nos permite aproximarnos a lo que ocurre en un proceso de Windows sin grandes conocimientos a bajo nivel.

Se trata de DebugDiag, que puede descargarse desde este enlace. Para no alterar más de lo necesario el entorno de producción y tener una mejor experiencia de uso de esta herramienta, recomiendo instalarla en nuestro equipo de desarrollo y cargar aquí los volcados de memoria que obtengamos de producción.

DebugDiag se compone de dos herramientas: DebugDiag Collection, para hacer capturas de volcados de memoria, y DebugDiag Analysis, para realizar la inspección posterior. Nos centraremos en la segunda porque la captura del volcado la hemos realizado previamente con el Administrador de Tareas y así evitamos instalar nuevas herramientas en máquinas de producción. DebugDiag Analysis realiza una serie de análisis sobre los volcados que finaliza mostrando un informe con conclusiones orientativas sobre lo que está pasando. Cuando arrancamos DebugDiag Analysis observaremos esta interfaz:

Para comenzar a trabajar con DebugDiag Analysis nos bastará con añadir desde el botón Add Data Files el fichero de volcado de memoria importado desde el entorno de producción y seleccionar las reglas de análisis que queremos aplicar. Para la versión 2.1 tendremos disponibles las siguientes:

  • CrashHangAnalysis
  • DotNetMemoryAnalysis
  • MemoryAnalysis
  • PerfAnalysis
  • SharePointAnalysis

Salvo que el proceso a analizar se trate de una aplicación sobre Sharepoint, seleccionaremos todas las reglas salvo la última, y comenzaremos el análisis pulsando en Start Analysis. Tras esperar un tiempo en el que se aplican las distintas reglas, que variará en función del tamaño del volcado y el número de reglas seleccionadas, se nos mostrará una ventana del navegador Internet Explorer con el informe resultado del análisis:

La estructura de este informe variará en función de las reglas elegidas para la generación del informe, pero en todos los casos sigue el siguiente esquema:

  • Analysis Summary. Contiene un resumen con las conclusiones más relevantes a tenor del resultado de cada una de las reglas. La herramienta nos puede adelantar así si el problema de rendimiento se debe a un interbloqueo de hilos o si hay un exceso de consumo de memoria, etc. También aparecerán aquí las incidencias encontradas a la hora de generar el informe.
  • Analysis Details. Dentro de esta sección tendremos un apartado con el resultado de cada una de las reglas elegidas al lanzar la generación del informe. Para cada regla veremos la siguiente información:
    • MemoryAnalysis. El resultado de ésta nos muestra estadísticas de uso de memoria dentro del proceso como los módulos cargados, uso de memoria por cada thread y lo más interesante, la utilización del heap
    • PerfAnalysis. Aquí tendremos la información de la pila de llamadas en la que se encontraba cada thread en el momento de la generación del volcado. También podremos observar estadísticas gráficas sobre las funciones más comunes en todos los hilos
    • CrashHangAnalysis. Aquí de nuevo obtendremos información relativa al estado de los threads en ejecución, pero con un enfoque de búsqueda de causas que determinen la razón por la que la aplicación se ha podido quedar sin respuesta. Concretamente podremos consultar estadísticas de tiempo de uso de CPU por cada thread o threads agrupados por pila de llamadas común.
    • DotNetMemoryAnalysis. Nos aporta información sobre el estado del CLR en tiempo de ejecución
  • Analysis Rule Summary. Aquí tenemos datos sobre las reglas ejecutadas y sus versiones.

Conviene recordar de todas formas que debemos considerar DebugDiag como una herramienta introductoria para tener una idea de lo que ocurre dentro de la ejecución de nuestros procesos en producción. No debemos tomar sus recomendaciones como dogmas de fe sino más bien como una hipótesis a considerar con más prioridad frente a otras. Si queremos explorar posteriormente el volcado con WinDbg, estas conclusiones de DebugDiag nos pueden dar una pista de por dónde empezar.

Espero que este artículo pueda servir a todos aquellos desarrolladores que desconocían esta posibilidad de depuración para arrojar un poco de luz en los problemas que se encuentren con su software en producción y con suerte, ¡os ayude a resolverlos!