Poder reponder esta simple pregunta cuando nuestra aplicación está en producción nos puede dar un motón de información sobre la salud de la misma. Todo modelo de salud de una aplicación debería darnos la respuesta a esta simple pregunta para las operaciones más significativas que realiza la aplicación.
Evidentemente, en situaciones donde hay problemas de rendimiento, siempre podríamos responder esta cuestión utilizando un ‘profiler’. Pero esto es reactivo, ya hay problemas de rendimiento, y entonces actuamos. Sin duda no es lo más eficiente, lo más eficiente es actuar de manera proactiva, ser capaz de detectar que nuestra aplicación se está comportando de manera anómala en lo relativo a rendimiento y poder tener una pista clara de que partes de la misma están fallando. Con un modelo de salud, parte esencial de toda arquitectura de aplicaciones moderna, podemos detectar estas situaciones.
Una manera, a menudo muy adecuada, de plantear un modelo de salud, es seleccionar una serie de operaciones clave que nuestra aplicación realiza y publicar contadores de rendimiento que nos permitan monitorizar cuanto tiempo están tardando en llevarse a cabo esas operaciones y sobre todo detectar desviaciones de los parámetros normales de funcionamiento.
Supongamos, por ejemplo, que en una aplicación de banca pudiesemos monitorizar el tiempo que tardan en realizarse las operaciones sobre cuentas. Tras poner la aplicación en producción y hacer los ajustes necesarios, sabemos que la aplicación esta funcionando correctamente. Entonces establecemos una línea base para el modelo de salud de nuestra aplicación: las transferencias duran X, los cargos Y, los abonos Z, etc… A partir de este momento podemos detectar de manera temprana variaciones significativas en estos valores y por lo tanto detectar problemas de rendimiento en cuanto se producen, lo que nos da una oportunidad de actuar de manera temprana y que la calidad de servicio de nuestra aplicación no se vea resentida.
Si queréis saber más sobre modelos de salud, no os perdáis la charla que mis compañeros Iván González y Unai Zorrilla dieron en el lanzamiento de Visual Studio 2008 y que está publicada en Channel 9.
Para poder actuar como he descrito, tenemos que saber como publicar mediante contadores de rendimiento lo que dura una operación de nuestra aplicación. Para ello basta con utilizar el API de contadores de rendimento de .Net. En concreto el tipo de contador que utilizaremos en esta ocasión es PerformanceCounterType.AverageTimer32 que siempre debe apoyarse en otro contador de tipo PerformanceCounterType.AverageBase.
El procedimiento es simple. Creamos una categoría de contadores y añadimos a la misma los contadores en cuestión. Cuando termina la operación que queremos medir, llamamos al método Increment del contador de tipo PerformanceCounterType.AverageBase y, aquí es donde viene el truco del asunto, al método IncrementBy del contador de tipo AverageTimer32, pasándole el tiempo empleado por la operación en ‘ticks’. La manera más lógica de obtener este tiempo es utilizar un objeto Stopwatch en concreto la propiedad ElapsedTicks. Destacar que aunque el contador espera ‘ticks’ cuando lo recogarmos desde el monitor de rendimiento, nos informa del valor en milisegundos.
A continuación podéis ver un ejemplo:
using System;
using System.Diagnostics;
using System.Threading;
namespace OperationTimePerformanceCounterSample
{
using System.Diagnostics;
namespace OperationTimePerformanceCounterSample
{
class OperationTimePerformaceCounter
{
Stopwatch _sw = new Stopwatch();
PerformanceCounter _pc;
PerformanceCounter _pcBase;
public OperationTimePerformaceCounter(string categoryName, string counterName, string counterBaseName)
{
_pc = new PerformanceCounter(categoryName, counterName, false);
_pcBase = new PerformanceCounter(categoryName, counterBaseName, false);
}
public void Start()
{
_sw.Start();
}
public void Finish()
{
_sw.Stop();
_pc.IncrementBy(_sw.ElapsedTicks);
_pcBase.Increment();
}
}
}
class Program
{
//Constantes con los nombre de los contadores y categorias
const string categoryName = «Operation Time Performace Counter Category»;
const string categoryHelp = «Demonstrates usage of the AverageTimer32 performance counter type.»;
const string counterTimerName = «Test Average Time Counter»;
const string counterTimerHelp = «Test Average Time Counter»;
const string counterBaseName = «Test Base Counter»;
const string counterBaseHelp = «Test Base Counter»;
static void Main(string[] args)
{
//Controlamos el Control + C para para la aplicación
Console.WriteLine(«Pulse Control + C para terminar…»);
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
//Eliminamos la catergia de contadores si ya existe
if (PerformanceCounterCategory.Exists(categoryName))
PerformanceCounterCategory.Delete(categoryName);
//Creamos los contadores necesarios
//Necesitamos dos contadores, aunque solo se publican como uno solo
//Uno actua de base y el otro recoge las medidas
CounterCreationDataCollection counters = new CounterCreationDataCollection();
CounterCreationData counterTimer = new CounterCreationData(counterTimerName, counterTimerHelp, PerformanceCounterType.AverageTimer32);
counters.Add(counterTimer);
CounterCreationData counterBase = new CounterCreationData(counterBaseName, counterBaseHelp, PerformanceCounterType.AverageBase);
counters.Add(counterBase);
//Creamos la categoria y añadimos los contadores
PerformanceCounterCategory.Create(
categoryName,
categoryHelp,
PerformanceCounterCategoryType.SingleInstance, counters);
//Simulamos una operación
while (true)
{
//La clase OperationTimePerformaceCounter encapsula la medición
//de tiempo
OperationTimePerformaceCounter pc = new OperationTimePerformaceCounter(categoryName, counterTimerName, counterBaseName);
pc.Start();
SimulateWork(); //Simulamos trabajo
pc.Finish();
}
}
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
if (PerformanceCounterCategory.Exists(categoryName))
PerformanceCounterCategory.Delete(categoryName);
}
private static void SimulateWork()
{
Random rnd = new Random();
Thread.Sleep(rnd.Next(0, 5000));
}
}
}
El código de la clase OperationTimePerformaceCounter que encapsula la medición del tiempo y la actualización de los contadores es el siguiente:
using System.Diagnostics;
namespace OperationTimePerformanceCounterSample
{
class OperationTimePerformaceCounter
{
//Objeto Stopwatch para medir la duración de la aplicación
Stopwatch _sw = new Stopwatch();
//Contadores de rendimiento
PerformanceCounter _pc;
PerformanceCounter _pcBase;
public OperationTimePerformaceCounter(string categoryName, string counterName, string counterBaseName)
{
//Abrimos los contadores
_pc = new PerformanceCounter(categoryName, counterName, false);
_pcBase = new PerformanceCounter(categoryName, counterBaseName, false);
}
/// <summary>
/// Método que llamamos cuando comienza la operación
/// </summary>
public void Start()
{
_sw.Start(); //Inciamos el cronómetro
}
/// <summary>
/// Método que llamamos cuando termina la operación
/// </summary>
public void Finish()
{
_sw.Stop(); //Paramos el cronómetro
//Actualizamos los contadores
_pc.IncrementBy(_sw.ElapsedTicks);
_pcBase.Increment();
}
}
}
Si ejecutamos el código (que puedes encontrar como adjunto a esto post) el resultado será el siguiente, visto con el monitor de rendimiento de Windows:
Espero que os sea útil e incorporéis este tipo de contadores a los modelos de salud de vuestras arquitecturas.