Laboratorio 2.1 Solución de problemas de rendimiento mediante createdump en Linux

Se aplica a:   .NET Core 2.1, .NET Core 3.1, .NET 5

Este artículo le ayuda a analizar problemas de rendimiento y describe cómo usar createdump y ProcDump para capturar archivos de volcado de memoria de .NET Core manualmente en Linux.

Requisitos previos

Los requisitos mínimos para seguir estos laboratorios de solución de problemas son los siguientes:

  • Una ASP.NET Core para demostrar problemas de rendimiento de CPU baja y cpu alta y problemas de bloqueo.
  • El depurador lldb, instalado y configurado para cargar la extensión SOS cuando se abre un volcado de núcleo.

Si ha seguido las partes anteriores de esta serie, debe tener lista la siguiente configuración:

  • Nginx está configurado para hospedar dos sitios web:
    • El primero escucha las solicitudes mediante el encabezado de host myfirstwebsite ( ) y las solicitudes de enrutamiento a la aplicación de demostración ASP.NET Core que escucha en el puerto http://myfirstwebsite 5000.
    • El segundo escucha las solicitudes mediante el encabezado de host de buggyamb ( ) y las solicitudes de enrutamiento ASP.NET Core la segunda aplicación de ejemplo de errores que escucha en el puerto http://buggyamb 5001.
  • Ambas ASP.NET Core deben ejecutarse como servicios que se reinician automáticamente cuando se reinicia el servidor o la aplicación deja de responder.
  • El firewall local de Linux está habilitado y configurado para permitir el tráfico SSH y HTTP.

Nota

Si la configuración no está lista, vaya a "Part 2 Create and run ASP.NET Core apps."

Objetivo de este laboratorio

Hasta ahora, en esta serie de solución de problemas, ha analizado un problema de bloqueo. En este laboratorio, se le dará la oportunidad de analizar un problema de rendimiento y aprender a usar createdump y ProcDump para capturar archivos de volcado de memoria manualmente.

Reproducir el problema

En una parte anterior, ha probado el primer escenario "lento" seleccionando el vínculo Lento. Al hacerlo, la página se carga correctamente, pero mucho más lenta de lo esperado. En esta parte, usarás la característica Generador de carga de BuggyAmb para solucionar este problema de rendimiento. Se trata de una característica "experimental" que envía hasta seis solicitudes simultáneas a cualquier recurso problemático. Está limitado a seis porque usa llamadas de jQuery y Ajax para emitir las solicitudes. Los exploradores web establecen un límite en la mayoría de las solicitudes Ajax en seis solicitudes simultáneas en una dirección URL determinada. Si desea aprender a usar el generador de carga para reproducir diferentes escenarios, consulte Experimental "Load Generator".

Para reproducir el problema, abra Páginas de problemas, seleccione Generador de carga y, a continuación, envíe seis solicitudes en el escenario Lento.

Problema de BuggyAmb.

En la siguiente lista se muestra lo que, finalmente, debe ver en el explorador. Los tiempos de respuesta mostrados son altos. El tiempo de respuesta esperado es inferior a un segundo. Eso es lo que puede esperar ver al seleccionar el vínculo Resultados esperados de la página de aterrizaje de la aplicación.

Carga de BuggyAmb.

Este es el problema que solucionará.

Supervisar los síntomas

Recuerde que una buena sesión de solución de problemas comienza definiendo el problema y comprendiendo los síntomas. Se usará para supervisar la memoria del proceso y el uso de CPU para el proceso que hospeda la aplicación ASP.NET Core cuando intente reproducir el problema htop generando carga. Si no recuerda lo que htop es, compruebe las partes de la serie anterior.

Antes de intentar reproducir el problema, empiece estableciendo una línea base para el rendimiento de la aplicación. Seleccione Resultados esperados o envíe varias solicitudes al escenario de resultados esperados mediante la característica Generador de carga. A continuación, compruebe cómo son la CPU y el uso de memoria cuando el problema no se está manifestando. Usarás para htop comprobar el uso de la CPU y la memoria.

Ejecute y filtre para mostrar solo los procesos que pertenecen al usuario bajo el que se ejecuta htop la aplicación de errores. El usuario de la aplicación ASP.NET Core de destino en este caso es www-data. Presione la tecla U para seleccionar ese usuario de www-data de la lista. Presione también Mayús+H para ocultar los subprocesos. Como puede ver, hay cuatro procesos en ejecución en el contexto de www-data y dos de ellos son los procesos Nginx. Los otros son para la aplicación de errores y la aplicación de demostración que creó al configurar el entorno.

Dado que aún no ha reproducido el problema de rendimiento, observe que todas las estadísticas de uso de cpu y memoria son actualmente bajas.

BuggyAmb bajo.

Ahora, vuelva al explorador de cliente y envíe seis solicitudes al escenario lento mediante el generador de carga. Después de eso, vuelve rápidamente a tu dispositivo Linux y observa el consumo de recursos de proceso en htop . Debería ver que el uso de CPU de la aplicación de errores aumentó considerablemente y el uso de memoria fluctúa hacia arriba y hacia abajo.

Memoria de BuggyAmb.

Nota

Dado que esta salida se toma de una máquina virtual que está equipada con dos CPU lógicas, muestra más del 100 por ciento de htop uso de CPU.

Después de procesar todas las solicitudes, disminuye el uso de la CPU y la memoria. Tanto las tendencias de uso de cpu como de memoria deben hacer sospechar que puede haber un uso intensivo de GC (recolector de elementos no utilizados) en la aplicación durante el procesamiento de las solicitudes.

Recopilar archivos de volcado de memoria

Al solucionar un problema de rendimiento, se capturan y analizan los archivos de volcado de memoria consecutivos. La idea de la captura de varios archivos de volcado es sencilla: un volcado de proceso es una instantánea de la memoria del proceso. No contiene información anterior. Para solucionar problemas de rendimiento, debe capturar varios archivos de volcado de memoria manuales o archivos de volcado de memoria principales para poder comparar los subprocesos y los montón, y así sucesivamente.

Use las siguientes opciones recomendadas para capturar archivos de volcado de memoria manuales a petición:

  • Createdump
  • Procdump
  • Dotnet-dump

Createdump

Createdump se incluye junto con el tiempo de ejecución de .NET Core. Se encuentra en el directorio en tiempo de ejecución. Puede encontrar las rutas de acceso de directorio en tiempo de ejecución mediante el dotnet --list-runtimes comando.

Tiempos de ejecución de BuggyAmb.

Dado que la aplicación de errores es una aplicación de .NET Core 3.1, la ruta de acceso completa de createdump es /usr/share/dotnet/shared/Microsoft.NETCore.App/3.1.10/createdump .

La forma más sencilla de este comando es createdump <PID> . Esto escribirá un volcado de memoria para el proceso de destino. Puede indicar la herramienta donde crear los archivos de volcado agregando el -f modificador: createdump <PID> -f <filepath> . Para este ejercicio, cree los archivos de volcado en el ~/dumps/ directorio.

Capturarás dos archivos de volcado de memoria consecutivos del proceso de BuggyAmb con 10 segundos de diferencia. Debe capturar los archivos de volcado mientras reproduce el problema de "solicitudes de respuesta lenta". Para empezar, primero debe buscar el PID del proceso. Use el htop comando systemctl status buggyamb.service or. En las listas siguientes, el PID de proceso es 11724.

Para crear los archivos de volcado, siga estos pasos:

  1. Cree el primer archivo: sudo /usr/share/dotnet/shared/Microsoft.NETCore.App/3.1.10/createdump 11724 -f ~/dumps/coredump.manual.1.%d .
  2. Espere 10 segundos después de escribir el primer archivo de volcado.
  3. Cree el segundo archivo: sudo /usr/share/dotnet/shared/Microsoft.NETCore.App/3.1.10/createdump 11724 -f ~/dumps/coredump.manual.2.%d

Al final, debe tener dos archivos de volcado de memoria. Tenga en cuenta el tamaño de cada archivo de volcado.

BuggyAmb ll.

Analizar archivos de volcado en lldb

Ya debería saber cómo abrir archivos de volcado en lldb. Abra ambos archivos en lldb en dos sesiones SSH diferentes.

El objetivo es desarrollar una teoría sobre lo que podría estar causando el problema de rendimiento. Ya sabe que el uso de cpu y memoria es alto cuando se produce el problema. Para comprobar la memoria administrada, puede usar el dumpheap -stat comando. Antes de empezar, echa un vistazo rápido al primer archivo de volcado.

Ejecute el clrthreads comando para obtener una lista de subprocesos administrados.

BuggyAmb clrthreads.

Nota

Uno de los subprocesos tiene el modo GC establecido en Cooperativa y los otros están establecidos en Preventivo.

Si el modo GC de un subproceso está establecido en Preventivo, esto significa que el GC puede suspender este subproceso en cualquier momento. En cambio, el modo Cooperativo significa que el GC debe esperar a que el subproceso cambie al modo preventivo antes de suspenderlo. Cuando el subproceso ejecuta código administrado, está en modo Cooperativo.

Comience examinando el subproceso en modo cooperativo. El identificador de subproceso del depurador para el subproceso cooperativo es 19 en la lista de ejemplo. El identificador será diferente al repetir el ejercicio. Cambie al subproceso ejecutando thread select 19 y, a continuación, ejecute clrstack para enumerar la pila de llamadas administradas. La página "lenta" de la aplicación de errores está realizando una operación de concat de cadena.

BuggyAmb clrstack.

Esto debería hacer que desconfiese porque debería saber que las operaciones de concat de cadena son costosas. Esto se debe a que los objetos de cadena en .NET son inmutables, lo que significa que sus valores no se pueden cambiar después de asignarse. Tenga en cuenta este fragmento de código pseudo:

string myText = "Debugging";
myText = myText + " .NET Core";
myText = myText + " is awesome";

Este código crea varias cadenas en la memoria: Debugging Debugging .NET Core , y Debugging .NET Core is awesome . Se deben crear tres objetos de cadena diferentes para generar (concatenar) una cadena final. Si esto ocurre con la suficiente frecuencia, puede crear presión de memoria para que se pueda desencadenar la GC.

Esta teoría suena prometedora. Sin embargo, debe intentar comprobar que es correcto. Antes de ver el montón administrado y, aunque ya está posicionado en el contexto del subproceso, examine los objetos a los que se hace referencia desde este subproceso para intentar determinar cuáles son los valores de cadena y string[] objetos. Ejecute dso y céntrate en matrices de cadenas y cadenas.

BuggyAmb dso.

Intente inspeccionar la matriz de cadenas. Ejecute dumpobj con la dirección del objeto. Sin embargo, tenga en cuenta que esto solo muestra que el objeto en cuestión es una matriz. SOS proporciona un dumparray comando para investigar las matrices. Ejecute dumparray 00007faf309528c8 para obtener la lista de los elementos de la matriz. (Recuerde que la dirección del objeto de matriz será diferente en el archivo de volcado que está examinando).

BuggyAmb dumpobj.

Vuelva a dumpobj ejecutar el comando con las direcciones de cadena resultantes que se encuentran dentro de la matriz. Elija algunas de las direcciones e investigue.

BuggyAmb dumpobj2.

Estas cadenas son similares a las cadenas que se encuentran en la tabla de productos que muestra la página.

Tenga en cuenta que lldb (o SOS) podría no mostrar el valor de cadena si las cadenas son grandes. En estos casos, una de las opciones es usar los comandos nativos de lldb para examinar la dirección de memoria nativa. Esto es similar al uso de d* los comandos (por ejemplo, dc ) en WinDbg.

El siguiente comando lee la memoria nativa en la ubicación de memoria especificada y muestra los primeros 384 bytes. La lista usa una de las direcciones de cadena para demostrarlo. El comando que se ejecuta es memory read -c 384 00007fb14d5da040 .

Lectura de BuggyAmb.

El número de cadenas a las que hace referencia la pila del subproceso parece confirmar la teoría de que el problema de concat de cadena está causando el problema de rendimiento.

Sin embargo, la investigación aún no ha finalizado. Tiene dos archivos de volcado de memoria. Por lo tanto, comparará el montón de memoria administrada y comprobará cómo cambió el montón en el tiempo.

Ejecute el dumpheap -stat comando en cada archivo de volcado. El siguiente es del primer archivo. En la lista siguiente, hay 105.401 objetos de cadena y el tamaño total de los objetos de cadena es de unos 480 MB. Observe también que la memoria posiblemente está fragmentada y que el motivo de la fragmentación parece estar relacionado con los objetos y objetos de la matriz de System.Data.DataRow cadenas.

BuggyAmb dumpheap.

Continúe ejecutando el mismo dumpheap -stat comando en el segundo archivo de volcado. Debería ver un cambio en las estadísticas de fragmentación, pero eso no es importante en el contexto de esta investigación. La parte importante es el número de objetos de cadena y el aumento significativo en el tamaño de estos objetos.

Estadísticas de BuggyAmb.

Al mismo tiempo, el número de System.Data.DataRow objetos también aumenta.

Es posible que sospeche que hay un problema que implica un montón de objetos grandes (LOH). Por lo tanto, es posible que desee examinar los objetos LOH. En este caso, debe ejecutar los dumpheap -stat -min 85000 comandos. La siguiente lista contiene las estadísticas de LOH para el primer volcado de memoria.

BuggyAmb min.

Y estas son las estadísticas de LOH para el segundo volcado de memoria.

BuggyAmb min2.

Esto también muestra claramente el aumento del montón. Todo esto parece estar relacionado con los string objetos.

Por último, ¿qué sucede si eliges un objeto "live" de LOH para encontrar su raíz? "Live", en este caso, significa que el objeto está enraizado en algún lugar y, por lo tanto, la aplicación está utilizando activamente para que el proceso de GC no lo elimine.

Controlar esta situación es fácil. Ejecute dumpheap -stat -min 85000 -live. Este comando muestra solo los objetos que están enraizados en algún lugar. En este ejemplo, solo hay instancias correctas de string objetos que están en el LOH.

BuggyAmb en directo.

Use la dirección MT del string objeto para obtener la lista de las direcciones de esos objetos en directo. Ejecute dumpheap -mt 00007fb1602c0f90 -min 85000 -live.

BuggyAmb live2.

Ahora, elija una dirección aleatoriamente de la lista resultante. En la siguiente captura de pantalla, se muestra la tercera dirección de la lista. Puede intentar examinar la dirección elegida ejecutando dumpobj . Sin embargo, como se trata de un objeto grande, el depurador no mostrará el valor. Por lo tanto, en su lugar, inspeccione la dirección de memoria nativa una vez más y verá que se trata de un objeto que se asemeja a lo que puede encontrar en la lista de la tabla de productos de la página que responde string lentamente.

Página BuggyAmb.

Examine la raíz del objeto que ha enumerado. Para ello, use el comando gcroot SOS. Este comando simplemente toma la dirección del objeto como un parámetro en su forma más sencilla. Como puede ver, esto se basa en el subproceso donde se ejecuta la página string "lenta". Incluso debería ver el nombre del archivo de origen y la información del número de línea.

BuggyAmb gcroot.

Nota

Ver el nombre del archivo de origen y la información del número de línea depende de dónde se esté solucionando la solución de problemas y de si los símbolos se establecen correctamente. En el peor de los casos, puede recuperar al menos el identificador de subproceso. En la siguiente lista, b6c es un identificador de subproceso administrado. Si ejecuta , clrthreads puede encontrar el identificador de subproceso correspondiente. BuggyAmb b6c.

Como se muestra en la captura de pantalla anterior, el identificador de subproceso del depurador para el id. de subproceso administrado b6c es 23. Cambie al subproceso 23 y compruebe la pila de llamadas administradas. Como ha visto anteriormente, este subproceso también debe realizar operaciones de concat de cadena.

Hilo BuggyAmb.

Y si examina en la pila de llamadas nativas mediante el comando, es posible que observe que el GC asigna memoria bt para este subproceso.

BuggyAmb b6c.

Esta evidencia confirma la teoría de que el problema está relacionado con un gran número de operaciones de concatenación de cadenas que crean cadenas cada vez más grandes que se desencadenan durante el procesamiento de una página "lenta".

La solución para este problema no está dentro del ámbito de esta serie. Sin embargo, tenga en cuenta que la solución es fácil de implementar mediante una instancia StringBuilder de clase en lugar de operaciones de concat de cadena.

Siguientes pasos

Laboratorio 2.2 Capturar archivos de volcado de datos mediante ProcDump