debugging language-agnostic logging

debugging - ¿Cómo se reproducen los errores que ocurren esporádicamente?



language-agnostic logging (28)

Tenemos un error en nuestra aplicación que no ocurre siempre y, por lo tanto, no conocemos su "lógica". Ni siquiera lo reproduzco en 100 ocasiones hoy.

Descargo de responsabilidad: este error existe y lo he visto. No es un pebkac o algo similar.

¿Cuáles son los consejos comunes para reproducir este tipo de error?


" Heisenbugs " requiere grandes habilidades para diagnosticar, y si desea ayuda de personas aquí debe describir esto con mucho más detalle, y escuchar pacientemente varias pruebas y controles, informar el resultado aquí e iterar hasta resolverlo (o decidir es demasiado caro en términos de recursos).

Probablemente tendrá que decirnos su situación real, el idioma, la base de datos, el sistema operativo, la estimación de la carga de trabajo, la hora del día en el pasado y una miríada de otras cosas, haga una lista de las pruebas que ya realizó, cómo fueron y qué listo para hacer más y compartir los resultados.

Y esto no garantiza que colectivamente podamos encontrarlo, tampoco ...


¡contrata a algunos probadores!


¿Qué entorno de desarrollo? Para C ++, su mejor apuesta puede ser la grabación / repetición de la estación de trabajo VMWare, consulte: http://stackframe.blogspot.com/2007/04/workstation-60-and-death-of.html

Otras sugerencias incluyen inspeccionar el seguimiento de la pila y una descripción detallada del código ... realmente no hay una bala de plata :)


@ p.marino - no hay suficientes representantes para comentar = /

tl; dr - fallas de compilación debido a la hora del día

Mencionaste la hora del día y eso me llamó la atención. Una vez, un error fue que alguien se quedó más tarde en el trabajo por la noche, trató de construir y comprometerse antes de irse y siguió teniendo una falla. Finalmente se dieron por vencidos y se fueron a casa. Cuando atraparon a la mañana siguiente construyeron bien, se comprometieron (probablemente debería haber sido más sospechoso =]) y la construcción funcionó para todos. Una semana o dos más tarde, alguien se quedó hasta tarde y tuvo una falla de compilación inesperada. Resulta que hubo un error en el código que hizo una compilación después de las 7 p. M.>>

También encontramos un error en un rincón poco utilizado del proyecto este enero que provocó problemas para coordinar diferentes esquemas porque no teníamos en cuenta que los diferentes calendarios estaban basados ​​en 0 Y 1 mes. Entonces, si nadie se había equivocado con esa parte del proyecto, posiblemente no hubiéramos encontrado el error hasta enero. 2011

Estos fueron más fáciles de solucionar que los problemas de enhebrado, pero aún así creo que interesantes.


A menudo, este tipo de errores están relacionados con la memoria corrupta y, por esa razón, es posible que no aparezcan con mucha frecuencia. Debería intentar ejecutar su software con algún tipo de perfil de memoria, por ejemplo, valgrind, para ver si algo sale mal.


Agregar registro detallado. Se necesitarán varias iteraciones (a veces, una docena) para agregar suficiente registro para comprender el escenario. Ahora el problema es que si el problema es una condición de carrera, que es probable si no se reproduce de manera confiable, el registro puede cambiar el tiempo y el problema dejará de suceder. En este caso, no inicie sesión en un archivo, pero mantenga un búfer rotatorio del registro en la memoria y solo bótelo en el disco cuando detecte que se ha producido el problema.

Editar: un poco más de ideas: si se trata de una aplicación gui, ejecuta las pruebas con una herramienta de automatización qa que te permite reproducir macros. Si se trata de una aplicación de tipo servicio, intenta adivinar al menos qué está pasando y luego crea de manera programática patrones de uso "anormales" que podrían ejercer el código que sospechas. Crea cargas más altas de lo normal, etc.


Agregue algún tipo de registro o seguimiento. Por ejemplo, registre las últimas acciones X que el usuario haya cometido antes de causar el error (solo si puede establecer una condición para que coincida con el error).


Agregue controles de condición previa y posterior a los métodos relacionados con este error.

Puede echarle un vistazo al diseño por contrato


Analiza el problema en un par y lee el código por parejas. Tome nota de los problemas que usted SABE que son ciertos y trate de afirmar qué condiciones previas lógicas deben ser ciertas para que esto suceda. Sigue la evidencia como un CSI.

La mayoría de las personas dice instintivamente "agregar más registros", y esta puede ser una solución. Pero para muchos problemas, esto empeora las cosas, ya que el registro puede cambiar las dependencias de tiempo lo suficiente como para que el problema sea más o menos frecuente. Cambiar la frecuencia de 1 en 1000 a 1 en 1,000,000 no lo acercará a la verdadera fuente del problema.

Entonces, si su razonamiento lógico no resuelve el problema, probablemente le proporcione algunos detalles que podría investigar con el registro o las afirmaciones en su código.


Dado que esto es independiente del lenguaje, mencionaré algunos axiomas de la depuración.

Nada que una computadora haga nunca es al azar. Una ''ocurrencia aleatoria'' indica un patrón aún no descubierto. La depuración comienza con el aislamiento del patrón. Varíe los elementos individuales y evalúe qué hace un cambio en el comportamiento del error.

Diferente usuario, misma computadora? Mismo usuario, diferente computadora? ¿Es la ocurrencia fuertemente periódica? ¿El reinicio cambia la periodicidad?

FYI- Una vez vi un error que experimentó una sola persona. Literalmente me refiero a una persona, no a una cuenta de usuario. El usuario A nunca vería el problema en su sistema, el usuario B se sentaría en esa estación de trabajo, iniciaría sesión como usuario A y podría reproducir el error de inmediato. No debe haber forma concebible para que la aplicación conozca la diferencia entre el cuerpo físico en la silla. Sin embargo-

Los usuarios usaron la aplicación de diferentes maneras. El usuario A solía usar una tecla rápida para invocar una acción y el usuario B usaba un control en pantalla. La diferencia en el comportamiento del usuario se convertiría en un error visible unas pocas acciones más adelante.

CUALQUIER diferencia que afecte el comportamiento del error debe ser investigada, incluso si no tiene sentido.


Digamos que estoy comenzando con una aplicación de producción.

  1. Normalmente agrego el registro de depuración alrededor de las áreas donde creo que está ocurriendo el error. Configuro las declaraciones de registro para darme una idea del estado de la aplicación. Luego, enciendo el nivel de registro de depuración y solicito al usuario / operador que me notifique la hora de la siguiente aparición de error. Luego analizo el registro para ver qué sugerencias da sobre el estado de la aplicación y si eso conduce a una mejor comprensión de lo que podría estar yendo mal.

  2. Repito el paso 1 hasta que tengo una buena idea de dónde puedo comenzar a depurar el código en el depurador

  3. A veces, el número de iteraciones del código que se ejecuta es clave, pero otras veces puede ser la interacción de un componente con un sistema externo (base de datos, máquina de usuario específica, sistema operativo, etc.). Tómese su tiempo para configurar un entorno de depuración que coincida con el entorno de producción lo más cerca posible. La tecnología de VM es una buena herramienta para resolver este problema.

  4. Luego procedo a través del depurador. Esto podría incluir la creación de un arnés de prueba de algún tipo que coloque el código / componentes en el estado que he observado en los registros. Saber cómo configurar puntos de interrupción condicionales puede ahorrarle mucho tiempo, así que familiarícese con esa y otras características dentro de su depurador.

  5. Depurar, depurar, depurar. Si no vas a ninguna parte después de unas horas, toma un descanso y trabaja en algo que no está relacionado por un tiempo. Vuelve con una mente fresca y perspectiva.

  6. Si ya no has llegado a ninguna parte, vuelve al paso 1 y haz otra iteración.

  7. Para problemas realmente difíciles, puede que tenga que recurrir a la instalación de un depurador en el sistema donde se está produciendo el error. Eso combinado con su arnés de prueba del paso 4 generalmente puede descifrar los problemas realmente desconcertantes.


El equipo con el que trabajo ha reclutado a los usuarios para registrar el tiempo que pasan en nuestra aplicación con CamStudio cuando tenemos un error molesto que rastrear. Es fácil de instalar y usar, y hace que la reproducción de esos molestos errores sea mucho más fácil, ya que puedes ver lo que los usuarios están haciendo. Tampoco tiene relación con el idioma en el que está trabajando, ya que solo está grabando el escritorio de Windows.

Sin embargo, esta ruta parece ser viable solo si está desarrollando aplicaciones corporativas y tiene buenas relaciones con sus usuarios.


Es bastante común que los programadores no puedan reiterar una falla experimentada por el usuario simplemente porque han desarrollado ciertos flujos de trabajo y hábitos en el uso de la aplicación que obviamente rodea el error.

Con esta frecuencia de 1/100, diría que lo primero que debe hacer es manejar excepciones y registrar cualquier cosa en cualquier lugar, o podría pasar otra semana buscando este error. También haga una lista de prioridades de articulaciones y características potencialmente delicadas en su proyecto. Por ejemplo: 1 - Subprocesamiento múltiple 2 - Punteros salvajes / arreglos sueltos 3 - Dependencia de los dispositivos de entrada, etc. Esto lo ayudará a segmentar las áreas que puede brutar-forzar-hasta-romper-nuevamente como lo sugieren otros carteles.


Esto ha funcionado para heisenbugs realmente extraños. (También recomendaría obtener una copia de "Debugging" de Dave Argans, ¡estas ideas se derivan en parte del uso de sus ideas!)

(0) Compruebe el ram del sistema usando algo como Memtest86.

Todo el sistema muestra el problema, por lo tanto, haga una plantilla de prueba que lo ejercite todo. Diga que es una cosa del lado del servidor con una GUI, usted ejecuta todo con un marco de prueba GUI haciendo los aportes necesarios para provocar el problema.

No falla el 100% del tiempo, por lo que debe fallar con más frecuencia.

Comience por cortar el sistema a la mitad (chuleta binaria), peor caso, debe eliminar los subsistemas de a uno por vez. apagarlos si no pueden ser comentados.

Vea si todavía falla. ¿Falla más a menudo?

Mantenga registros de prueba adecuados, ¡y solo cambie una variable a la vez!

En el peor de los casos, usa la plantilla y prueba durante semanas para obtener estadísticas significativas. Esto es duro; pero recuerda, la plantilla está haciendo el trabajo.

No tengo hilos y solo un proceso, y no hablo con el hardware

Si el sistema no tiene hilos, no hay procesos de comunicación y contactos sin hardware; Es complicado; Los heisenbugs generalmente son sincronización, pero en el caso sin procesos sin hilos, es más probable que sean datos no inicializados, o datos usados ​​después de ser liberados, ya sea en el montón o en la pila. Intenta usar un verificador como valgrind.

Para problemas con hilos / procesos múltiples:

Intente ejecutarlo en una cantidad diferente de CPU. Si se está ejecutando en 1, prueba con 4! Intenta forzar un sistema de 4 computadoras en 1. En su mayoría, garantizará que las cosas sucedan de a una por vez.

Si hay hilos o procesos de comunicación, esto puede eliminar errores.

Si esto no ayuda, pero sospecha que se trata de sincronización o subprocesamiento, intente cambiar el tamaño de la división de tiempo del sistema operativo. ¡Hágalo tan fino como lo permita su proveedor de sistema operativo! ¡A veces esto ha hecho que las condiciones de carrera ocurran casi todo el tiempo!

Por el contrario, intente ir más lento en las timeslices.

A continuación, establezca la plantilla de prueba que se ejecuta con los depuradores conectados por todos lados y espere a que la plantilla de prueba se detenga por una falla.

Si todo lo demás falla, coloque el hardware en el congelador y ejecútelo allí. El tiempo de todo se cambiará.


Esto varía (como dices), pero algunas de las cosas que son útiles con esto pueden ser

  • Accediendo inmediatamente al depurador cuando ocurre el problema y volcando todos los hilos (o el equivalente, como volcar el núcleo inmediatamente o lo que sea).
  • corriendo con el inicio de sesión activado pero por lo demás completamente en modo de lanzamiento / producción. (Esto es posible en algunos entornos aleatorios como c y rieles, pero no muchos otros).
  • hacer cosas para empeorar las condiciones de borde en la máquina ... forzar baja memoria / alta carga / más hilos / servir más solicitudes
  • Asegurarte de que en realidad estás escuchando lo que dicen los usuarios que están enfrentando el problema. Asegurándose de que en realidad están explicando los detalles relevantes. Este parece ser el que rompe mucho a las personas en el campo. Tratar de reproducir el problema equivocado es aburrido.
  • Acostúmbrate a leer el ensamblaje que se produjo optimizando los compiladores. Esto parece detener a la gente a veces, y no es aplicable a todos los idiomas / plataformas, pero puede ayudar
  • Esté preparado para aceptar que es su culpa (del desarrollador). No entres en la trampa de insistir que el código es perfecto.
  • a veces es necesario rastrear el problema en la máquina en la que está sucediendo.

Existe una buena posibilidad de que su aplicación sea MTWIDNTBMT (con múltiples subprocesos cuando no es necesario que tenga múltiples subprocesos) o tal vez solo con subprocesos múltiples (para ser cortés). Una buena forma de reproducir errores esporádicos en aplicaciones de subprocesos múltiples es rociar un código como este alrededor (C #):

Random rnd = new Random(); System.Threading.Thread.Sleep(rnd.Next(2000));

y / o esto:

for (int i = 0; i < 4000000000; i++) { // tight loop }

para simular subprocesos que completan sus tareas en momentos diferentes de lo habitual o para inmovilizar el procesador durante largos tramos.

A lo largo de los años he heredado muchas aplicaciones defectuosas y de subprocesos múltiples, y el código como los ejemplos anteriores generalmente hace que los errores esporádicos se produzcan con mucha más frecuencia.


Intenta agregar código en tu aplicación para rastrear el error automáticamente una vez que sucede (o incluso avisarte por correo / SMS)

Registre lo que pueda para que cuando ocurra pueda capturar el estado correcto del sistema.

Otra cosa: intente aplicar pruebas automáticas que puedan abarcar más territorio que las pruebas basadas en humanos de una manera formada. Es una posibilidad remota, pero una buena práctica en general.


Junto con mucha paciencia, una oración tranquila y maldiciendo necesitaría:

  • un buen mecanismo para registrar las acciones del usuario
  • un buen mecanismo para recopilar el estado de los datos cuando el usuario realiza algunas acciones (estado en la aplicación, base de datos, etc.)
  • Compruebe el entorno del servidor (por ejemplo, un software antivirus que se ejecuta en un momento determinado, etc.) y registre los tiempos del error y vea si puede encontrar alguna tendencia
  • algunas más oraciones y maldiciones ...

HTH.


La depuración es difícil y consume mucho tiempo, especialmente si no puede reproducir el problema de manera determinista. Mi consejo para usted es averiguar los pasos para reproducirlo de manera determinista (no solo algunas veces).

Ha habido mucha investigación en el campo de la reproducción de fallas en los últimos años y sigue siendo muy activa. Las técnicas de grabación y reproducción han sido (hasta ahora) la dirección de investigación de la mayoría de los investigadores. Esto es lo que necesitas hacer:

1) Analice el código fuente y determine cuáles son las fuentes de no determinismo en la aplicación, es decir, cuáles son los aspectos que pueden llevar su aplicación a través de diferentes rutas de ejecución (por ejemplo, entrada de usuario, señales del sistema operativo)

2) Regístrese la próxima vez que ejecute la aplicación

3) Cuando su aplicación falla nuevamente, tiene los pasos para reproducir el error en su registro.

Si su registro todavía no reproduce el error, entonces está tratando con un error de concurrencia. En ese caso, debe ver cómo su aplicación accede a las variables compartidas. No intente registrar los accesos a las variables compartidas, ya que estaría registrando demasiados datos, lo que provocaría ralentizaciones graves y registros de gran tamaño. Desafortunadamente, no hay mucho que pueda decir que lo ayude a reproducir errores de simultaneidad, porque la investigación todavía tiene un largo camino por recorrer en este tema. Lo mejor que puedo hacer es proporcionar una referencia al avance más reciente (hasta ahora) en el tema de la reproducción determinística de errores de concurrencia:

http://www.gsd.inesc-id.pt/~nmachado/software/Symbiosis_Tutorial.html

Atentamente


Lea cuidadosamente el trazado de la pila e intente adivinar qué podría pasar; luego intente rastrear / log cada línea de código que potencialmente pueda causar problemas.

Mantenga su enfoque en la eliminación de recursos; muchos errores furtivos esporádicos que encontré estaban relacionados con cerrar / tirar cosas :).


No hay una buena respuesta general a la pregunta, pero esto es lo que he encontrado:

  1. Se necesita talento para este tipo de cosas. No todos los desarrolladores son los más adecuados, incluso si son superestrellas en otras áreas. Así que conozca a su equipo, que tiene talento para ello, y espero que pueda darles suficientes caramelos para entusiasmarlos por ayudarlo, incluso si no es su área.

  2. Trabaja hacia atrás y trátalo como una investigación científica. Comienza con el error, lo que ves está mal. Desarrolle hipótesis sobre qué podría causarlo (esta es la parte creativa / imaginativa, el arte para el que no todos tienen talento) y ayuda mucho saber cómo funciona el código. Para cada una de esas hipótesis (preferiblemente ordenadas por lo que usted cree que es más probable, una vez más pura sensación intestinal), desarrolle una prueba que intente eliminarla como la causa y pruebe la hipótesis. Cualquier falla dada para cumplir con una predicción no significa que la hipótesis es incorrecta. Pruebe la hipótesis hasta que se confirme que está equivocada (aunque, como es menos probable, es posible que desee pasar a otra hipótesis primero, simplemente no descarte esta hasta que tenga una falla definitiva).

  3. Reúna tantos datos como puedas durante este proceso. Extenso registro y cualquier otra cosa aplicable. No descarte una hipótesis porque le faltan datos, en lugar de remediar la falta de datos. Muy a menudo, la inspiración para la hipótesis correcta proviene del examen de los datos. Notar algo apagado en un seguimiento de pila, un problema extraño en un registro, algo que falta que debería estar allí en una base de datos, etc.

  4. Revisa cada suposición. Muchas veces he visto que un problema no se corrige rápidamente porque no se siguió investigando una llamada a un método general, por lo que se asumió que el problema no era aplicable. "Oh, eso debería ser simple". (Ver el punto 1).

Si te quedas sin hipótesis, esto generalmente se debe a un conocimiento insuficiente del sistema (esto es cierto incluso si escribiste cada línea de código por ti mismo), y necesitas revisar y revisar el código y obtener información adicional sobre el sistema que vendrá. arriba con una nueva idea.

Por supuesto, nada de lo anterior garantiza nada, pero ese es el enfoque que he encontrado que arroja resultados consistentes.


Para proyectos .NET Puede usar Elmah (Módulos y controladores de registro de errores) para monitorear su aplicación en busca de excepciones no detectadas, es muy simple de instalar y proporciona una interfaz muy agradable para buscar errores desconocidos

http://code.google.com/p/elmah/

Esto me salvó hoy al detectar un error muy aleatorio que estaba ocurriendo durante un proceso de registro

Aparte de eso, solo puedo recomendar tratar de obtener la mayor cantidad posible de información de los usuarios y tener un conocimiento profundo del flujo de trabajo del proyecto.

En su mayoría salen de noche ... principalmente


Pruebas unitarias Probar un error en la aplicación a menudo es horrendo porque hay tanto ruido, tantos factores variables. En general, cuanto mayor es la pila (heno), más difícil es identificar el problema. Extender creativamente el marco de prueba de la unidad para abarcar los casos extremos puede ahorrar horas o incluso días de tamizado

Habiendo dicho eso, no hay una bala de plata. Siento tu dolor.


Sugeriría escribir todo lo que el usuario ha estado haciendo. Si tiene permite decir 10 informes de errores de este tipo, puede intentar encontrar algo que los conecte.


Suponiendo que está en Windows, y su "error" es un bloqueo o algún tipo de corrupción en el código no administrado (C / C ++), entonces eche un vistazo a Application Verifier de Microsoft. La herramienta tiene varias paradas que se pueden habilitar para verificar cosas durante el tiempo de ejecución. Si tiene una idea del escenario donde ocurre su error, intente ejecutar el escenario (o una versión de estrés del escenario) con AppVerifer ejecutándose. Asegúrese de activar pageheap en AppVerifier o considere compilar su código con el modificador / RTCcsu (consulte http://msdn.microsoft.com/en-us/library/8wtf2dfz.aspx para obtener más información).


Toneladas de registro y revisión cuidadosa del código son sus únicas opciones.

Esto puede ser especialmente doloroso si la aplicación se implementa y no puede ajustar el registro. En ese momento, tu única opción es pasar por el código con un peine de dientes finos e intentar razonar sobre cómo el programa podría entrar en mal estado (¡método científico para rescatar!)


Use un reportero de bloqueo mejorado. En el entorno Delphi, tenemos EurekaLog y MadExcept. Existen otras herramientas en otros entornos. O puede diagnosticar el volcado del núcleo. Está buscando el seguimiento de la pila, que le mostrará dónde está volando, cómo llegó allí, qué hay en la memoria, etc. También es útil tener una captura de pantalla de la aplicación, si se trata de una interacción del usuario. E información sobre la máquina en la que se colgó (versión del sistema operativo y parche, qué más se está ejecutando en ese momento, etc.). Ambas herramientas que mencioné pueden hacer esto.

Si es algo que sucede con algunos usuarios pero no puede reproducirlo, y ellos pueden, siéntese con ellos y mire. Si no es aparente, cambie de asiento: "conduce" y ellos le dirán qué hacer. Descubrirá los problemas de usabilidad sutiles de esa manera. hacer doble clic en un botón de un solo clic, por ejemplo, iniciar la reentrada en el evento OnClick. Esa clase de cosas. Si los usuarios son remotos, use WebEx, Wink, etc., para grabarlos y colóquelos, para que pueda analizar la reproducción.


todo lo anterior, además de arrojar algún robot blando de fuerza bruta sobre él que es semi aleatorio, y escalar una gran cantidad de afirmar / verificar (c / c ++, probablemente similar en otras lenguas) a través del código