Modelos de salud: ¿Cúanto tarda una operación determinada de mi aplicación?

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:

Tiempo de operación en el monitor de rendimiento

Espero que os sea útil e incorporéis este tipo de contadores a los modelos de salud de vuestras arquitecturas.

10 comentarios sobre “Modelos de salud: ¿Cúanto tarda una operación determinada de mi aplicación?”

  1. Una pregunta, la implementación entiendo que se hace sobre la misma aplicación, con lo que la propia estimación puede afectar al rendimiento de la misma, ¿ no seria mas adecuado realizar el analisis del rendimiento sobre las pruebas unitarias o con un programa aparte ?

    Excelente tu artículo. Saludos.

  2. ¡Hola Juan!

    La verdad es que no entiendo muy bien tu pregunta. Cuando hablamos de líneas base de salud, la idea es establecer esa línea base en el entorno real de producción.

    Para planeamiento de la capacidad si que valdría el enfoque que comentas.

    Si tu preocupación es el impacto de los contadores en el rendimiento, he de decirte que es mínimo. Están sumamente optimizados.

    ¡Un saludo!

  3. Rodrigo, por si interesa decir que dentro de System.Diagnostics hay un nuevo namespace PerformanceData que incluye lo necesario para trabajar con las nuevas API’s de contadores de rendimiento introducidas con Windows Vista / Server 2008

    Unai

  4. Aúpa Unai:

    La verdad es que desconocía esas novedades. Tienen muy buena pinta… pero que pereza aprenderse otro API.

    De todos modos, lo comentado en el artículo, sigue funcionando perfectamente en Windows 7 🙂

    ¡Un saludo y gracias maestro!

  5. Mirando un poco el nuevo namespace y el nuevo API la verdad es que es una maravilla.

    En esencia, declaras los contadores en un manifiesto, lo ‘compilas’, le insertas como recurso en tu ejectuble y así te ahorras el código repetitivo de crear los contadores y las colecciones de contadores… y solo tienes que preocuparte de alimentarlos… ¡mola! nos ahorramos la parte más tediosa de crear los contadores y quitamos un motón de código que aporta cero valor.

    Eso sí, si nuestra aplicación tiene que funcionar en 2003 o XP nos olvidamos…

    ¡Un saludo!

  6. Hola!

    Antes que nada felicitar al Sr. Rodrigo por su post, me parece muy útil.

    Ya sería el colmo de la utilidad que conocierais algún namespace que haga algo similar en JAVA, que me toca lidiar con él de vez en cuando.

    Salu2 y, de nuevo, felicidades y gracias por tus posts que son bárbaros.

  7. Francisco, la casualidad de que hace un tiempo miré el tema para Java. Aunque no es mi especialidad los caminos del señor son tortuosos…:)

    Lo único que encontré sobre el tema fué artículos que proponen alternativas para este asunto y un ejemplos de una implementación. Pero nada tan simple y elegante como lo que tenemos en .Net. La ‘multiplataforma’ no es gratis…

    Básicamente se trata de hacer llamadas nativas desde dentro de Java:
    http://www.developer.com/java/data/article.php/3087741

    No es dificil si has usado el API de Performace Counters desde C y por supuesto no es multiplataforma.

    Hay otras alternativas, pero requieren usar software de terceros:
    http://www.javaworld.com/javaworld/jw-11-2004/jw-1108-windowspm.html

    También recuerdo que había algún producto de pago que facilitaba la labor… pero no recuerdo su nombre. 🙁

    ¡Un saludo!

  8. Buen aporte, aunque tambien me gustaria verlo utilizando la API de PerformanceData

    Por alguna estraña razon al momento de descargar el codigo, este se encuentra vacio.

  9. Hola Andres:

    Quizás me aníme a hacer un ejemplillo con el nuevo API…

    Ya solucioné lo del código vacio. Entre CS e IE se arman un taco con los zip… en rar va perfectamente.

    ¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *