8/12/2011 3:17 Lucas Ontivero

Como crear un Profiler para .Net

Quiero mostrar cómo es posible modificar nuestros assemblies ya compilador (no no no, antes que los procese el JIT) para agregarles o quitarles código IL. En este caso, voy a ilustrar esto mediante la creación de un sencillísimo Profiler.

Trabajos previos

Este está inspirado en un artículo muy interesante, aunque algo viejo, de Gabriel Schenker sobre como crear un profiler con Mono.Cecil para aplicaciones Silverlight (parte 1 y parte 2 – nos debe la tercera parte). Me gustó bastante pero por desgracia no puso el código (y odio cuando hacen eso!), usa una versión vieja de Mono.Cecil y me pareció buena idea cambiar algunas partes del enfoque original.

Paso a Paso

Objetivo

Lo primero es tomar un ensamblado .net y modificarlo inyectándole algún código. Lo que queremos lograr es, dado un método cualquiera como el que se ve abajo en la Fig.1 insertarle dos llamadas a dos métodos estáticos como se ve en la Fig.2:

image
Fig.1 – Método original

image
Fig.2 – Método instrumentado

Como puede verse, las dos llamadas extras, una al inicio y otra al final del método original, son métodos estáticos que recolectan información del tiempo de ejecución mediante un Stopwatch. Aquí hay una diferencia con la versión de Gabriel Schenker en cuanto a que yo le paso la firma del método mientras que el toma esa información del StackFrame dentro del método WhenEnteringMethod. Esto es más rápido y reduce mucho el código (aunque mi objetivo no fue “mejorar” el código sino simplemente jugar un rato). Otra diferencia es en la forma de medir el tiempo.

Instrumentando

Lo primero que hacemos es tomar la ruta de un ensamblado, leerlo con AssemblyDefinition.ReadAssembly, y luego por cada método encontrado llamamos a InstrumentMethod. Por último guardamos el ensamblado en la misma ruta (lo sobreescribimos).

image

Si el método tiene código, esto es: no es abstracto, no es un método de una interface, no es un método parcial, etc. Le inyectamos nuestras llamadas:

image

Aquí es donde insertamos la primera llamada y le pasamos por parámetro el nombre completo de método.

image

Recolectando información

Entonces ahora todos nuestros métodos, lo primero que hacen es invocar al método WhenEnteringMethod que muestro abajo:

image

Esto básicamente determina cual es el método que se estaba ejecutando y desde el cual se realiza la llamada al método que se está ejecutando en este momento. El método Enter es el que incrementa el número de llamados e inicia el stopwatch. Por último, cuando se sale del método se ejecuta WhenLeavingMethod el cual detiene el stopwatch.

image

Esto genera un árbol de llamadas que podemos ver con nuestro depurador. Por ejemplo, el método EntryPoint (método ficticio que representa al llamador de mas alto nivel) realiza dos llamadas (Calls): la primera al constructor de la clase AClass (AClass.ctor())  una solo vez la cual no tarda nada (0.0 milisegundos) y luego una llamada al método PublicVoidFunction la cual tarda 400 milisegundos. Esta última a su vez invoca 50 veces al método MethodWithAFunctionParameter.

Verlo en acción

image

Para visualizarlo con mayor facilidad podemos bajar esto a un xml y verlo así:

image

Conclusión

Hacer un buen profiler no es algo sencillo en lo absoluto y existen miles de consideraciones a tener en cuenta, no obstante este ejemplo es útil para demostrar lo sencillo que es manipular los ensamblados mediante Mono.Cecil lo que nos abre un universo inmenso de posibilidades para realizar herramientas como profilers, herramientas de coverage, herramientas para implementar AOP, ect.

El código puede descargarse desde aquí

Archivado en: ,,,,
Comparte este post:

# re: Como crear un Profiler para .Net

Friday, December 9, 2011 12:57 PM by Juanma

Muy buen artículo, Lucas. Ahora sólo te falta hacer un UI y empezar a competir con dotTrace :-)

Ya que te veo puesto en el tema y sólo por curiosidad, ¿se te ocurre como hacer un profile de la memoria?

Juanma.