Cómo medir tiempos en C# y .Net con precisión

Cómo medir tiempos en .Net (con precisión)

Hace unos días mientras comíamos los compañeros de trabajo, salió el tema sobre lo preciso o impreciso que puede ser medir tiempos desde el sistema operativo. Mis compañeros (programadores C/C++ en su mayoría), tenían dudas sobre lo preciso que puede ser medir tiempos desde un entorno administrado. Nunca he necesitado tales precisiones, así que era totalmente novato en ese tema, pero hoy que he tenido un momento, he investigado un poco sobre cómo medir tiempos en C# y .Net con precisión:

La clase Stopwatch

En C#, el ecosistema .Net nos provee de una herramienta ya lista para utilizar con una precisión de microsegundo (si el hardware lo permite) dentro del namespace «System.Diagnostics«. Está herramienta es la clase Stopwatch . Esta disponible en todos los entornos a partir de .Net Framework 2.0 (incluyendo .Net Core y .Net Standard), así que si no la conocías, es una buena idea añadirla al cinturón.

Para usarla, simplemente es necesario instanciarla y llamar a sus métodos Start y Stop, después nos devuelve un TimeSpan con el tiempo transcurrido:

Stopwatch timeMeasure = new Stopwatch();
timeMeasure.Start();
int operacion = 10 / 4;
timeMeasure.Stop();
Console.WriteLine($"Tiempo: {timeMeasure.Elapsed.TotalMilliseconds} ms");

Pero no se queda ahí, la clase también tiene dos propiedades estáticas (ReadOnly) que nos pueden dar mucha información sobre la medida:

  • IsHighResolution (bool): Nos va a indicar si el hardware permite mediciones de alta precisión.
  • Frequency (long): Nos va a dar el valor de ticks (mediciones) por segundo que soporta nuestro hardware.

Con estos dos datos extra, podemos saber si nuestra medida es de alta precisión, e incluso la resolución de la medida:

using System.Diagnostics;
//------
Stopwatch timeMeasure = new Stopwatch();
timeMeasure.Start();
int operacion = 10 / 4;
timeMeasure.Stop();
Console.WriteLine($"Tiempo: {timeMeasure.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Precision: {(1.0 / Stopwatch.Frequency).ToString("E")} segundos");
if (Stopwatch.IsHighResolution)
    Console.WriteLine("Alta precisión");
else
    Console.WriteLine("Baja precisión");

Después de probarlo, ya puedo decirles a mis compañeros que se pueden medir tiempos en C# y .Net con precisión y sin nada que envidiar a otros lenguajes de más bajo nivel como C/C++.

Herramientas de desarrollo: AppVeyor

La imagen muestra el logo de Nuget y AppVeyor

Hoy vengo con otra entrada de esas que me hacen ilusión, y es que volvemos a colaborar con José de VariableNotFound, esta vez para hablar de cómo crear paquetes Nuget (con SourceLink, por supuesto), sobre el repositorio Nuget.org, y sobre como publicar nuestros paquetes automáticamente con un sistema de CI/CD como AppVeyor.

En la primera parte de la entrada, que se ha publicado en VariableNotFound hablábamos de cómo crear un paquete Nuget con SourceLink y publicarlo en Nuget.org (si no sabes lo que es, deberías echarle un vistazo a la entrada hablando del tema). Como comentábamos al final, hacerlo a mano se hace tedioso, por eso es una buena idea utilizar una herramienta de Despliegue Continuo (CD por sus siglas en ingles).

En su momento publique en el blog de compañero de trabajo unas entradas sobre AppVeyor en las que explicaba como añadir AppVeyor a nuestro repositorio. Hoy, vamos a continuar aquella entrada añadiendo el CD hacia Nuget.org (aunque repasemos la parte que es común).

En primer lugar, nos tendremos que crear una cuenta en AppVeyor, algo que es muy fácil si tenemos cuenta de GitHub. Simplemente vamos a «Sign in» y seleccionamos iniciar con GitHub (u otros servicios).

Añadiendo AppVeyor a nuestro repositorio

Login AppVeyor

Con nuestra cuenta creada, añadimos el proyecto con el botón «NEW PROJECT»

Señala el botón de crear proyecto en AppVeyor

Y después, seleccionamos el proyecto que queremos, y pulsamos sobre el botón «Add»

Con esto, nuestro proyecto se compilará automáticamente cada vez que hagamos un «push» al repositorio y ejecutará las pruebas unitarias, aunque obviamente fallará porque aún no hemos configurado que tiene que hacer.

Para configurarlo, vamos a la pestaña «settings»

En primer lugar, en el menú «General» vamos a tener que bajar hasta casi el final, y seleccionar «On» en la opción «.NET Core .csproj patching «, con esto, vamos a conseguir cambiar la versión del paquete a la que queramos. Recordemos, qué si intentamos resubir un paquete con la misma versión que uno que ya existe, fallará. Como lo que nos interesa es que la versión se actualice y no se repita, vamos a utilizar la variable de AppVeyor «version«, la cual depende de los campos «Next build number» y «Build version format«, siendo el primero el número de compilación (es autoincremental) y el segundo la versión «Major» que queremos que tenga nuestro paquete, una vez hecho, pulsamos sobre el botón «Save» para guardar los cambios.

La imagen muestra como activar el parcheo de versión del paquete Nuget en AppVeyor

Pasamos al menú «Environment», en este solo vamos a necesitar seleccionar la imagen de Visual Studio 2017 y pulsar en el botón «Save».

La imagen muestra como seleccionar Visual Studio 2017 en AppVeyor

Ahora vamos con el menú «Build», aquí le vamos a indicar como tiene que hacer la compilación, además de ejecutar scripts para preparar los prerrequisitos que tengamos, por ejemplo, hacer un «nuget restore». Esta es quizás la parte más compleja del trabajo.

Lo primero, seleccionamos la configuración «Release» escribiéndolo en el apartado «Configuraton» al inicio, y después hay que bajar casi hasta el final para seleccionar que queremos hacer un paquete («Package NuGet projects»), que queremos incluir los símbolos para SourceLink («Include NuGet symbol packages»), y por último y muy importante (si no, no compilará), añadir un comando previo a la compilación que ejecutará un «nuget restore».

La imagen muestra la configuración del menú "Build" de AppVeyor

Con esto, tras pulsar en «Save», si hacemos cambios sobre el repositorio, podemos ver que ejecuta toda la integración continua.

La imagen muestra la salida de la consola de AppVeyor

Configurando el despliegue en Nuget.org desde AppVeyor

Por último, vamos al menú «Deployment», y añadimos un nuevo despliegue pulsando sobre «Add deployment» y seleccionando Nuget en la lista desplegable. Ahora, simplemente introducimos nuestra API Key, le decimos que no queremos publicar un paquete de símbolos, y por ultimo y MUY IMPORTANTE, como no queremos que se haga deploy de cada push, añadimos la condición de que para hacer un deploy, tiene que ser un push con un «tag», esto lo conseguimos poniendo como condición que la variable de AppVeyor «APPVEYOR_REPO_TAG» sea true.

La imagen muestra la configuración de el despliegue en AppVeyor

Si con esto pulsamos sobre salvar, y mandamos un nuevo tag al repositorio, veremos cómo al igual que si lo enviábamos manualmente, aparece disponible en Nuget.org. Como siempre, dejo el código fuente en GitHub.

En próximas entradas, hablaremos sobre como utilizar Azure Pipelines como CD además de CI y las ventajas que tiene cerrar el ciclo CI/CD.

Herramientas de desarrollo: Azure Pipelines (CI)

azure pipelines

Hace un par de semanas, empezamos esta serie sobre Integración Continua y Despliegue Continuo (CI/CD) con Travis CI y una pequeña explicación sobre que es la integración continua. Como comentábamos, a lo largo de estas entradas, vamos a hablar de los principales servicios de CI/CD, y hoy es el turno de Azure Pipelines, herramienta que forma parte de Azure DevOps. Para quien no lo conozca, es un conjunto de herramientas para trabajo colaborativo de Microsoft, que además es gratuito para fines Open-Source.

Creando el proyecto en Azure DevOps

Para poder utilizar esta herramienta, vamos a la web de Azure DevOps (si no nos hemos registrado, tendremos que hacerlo) y vamos a crear un proyecto:

Create

Esto nos lanza una segunda ventana, donde tendremos que elegir el nombre del proyecto y si queremos que sea público o privado, en este caso, lo elegimos público y pulsamos en «Create»:

Nombre Azure DevOps

Con esto, hemos creado un repositorio en Azure DevOps, el cual nos permite gestionar nuestro código como lo podrían hacer unos servicios como GitHub o GitLab, pero la parte que nos interesa es la de crear un pipeline, así que seleccionamos sobre el:

Vamos a crear nuestro pipeline

Crear Azure Pipelines

Simplemente pulsamos en «New pipeline»

Boton New

Esto nos va a mostrar un sencillo asistente, en el cual, primero seleccionamos desde donde va a coger el código, en nuestro caso, GitHub.

Azure Pipelines GitHub

Esto nos mostrará los repositorios disponibles si ya nos hemos conectado, y si no, nos mostrará un botón para autorizar.

Autorizar

Una vez autorizado, simplemente elegimos nuestro proyecto, y esto nos muestra un control donde tenemos que escribir nuestro .yml (igual que hacíamos en los demás CI), en nuestro caso, hemos utilizado el mismo proyecto en el que hablamos sobre mocking, por lo tanto, al ser NetCore, podemos compilarlo y ejecutar sus pruebas en Windows, Linux y MacOS (si fuese .Net Framework, solo podríamos con Windows y los comandos serían ligeramente diferentes), así que nuestro .yml podría ser algo como esto:

# Matriz de máquinas que vamos a utilizar
strategy:
  matrix:
    linux:
      imageName: 'ubuntu-16.04'
    mac:
      imageName: 'macos-10.13'
    windows:
      imageName: 'vs2017-win2016'

# Configuramos el pool con la imagen
pool:
  vmImage: $(imageName)

# Variables que vamos a necesitar (podemos definir tantas como queramos)
variables:
    solution: '**/*.sln'
    testProject: '**/PruebasUnitarias.csproj'
    buildConfiguration: 'Release'
    netCoreVersion: '2.2.103'

# Pasos a ejecutar
steps:

# Instalar Nuget
- task: [email protected]

# Instalar la versión de NetCore que hemos definido en las variables
- task: [email protected]
  inputs:
    version: '$(netCoreVersion)'

# Ejecutar un 'nuget restore'
- task: [email protected]
  inputs:
    restoreSolution: '$(solution)'

# Lanzamos el comando de compilación
- task: [email protected]
  inputs:
    command: build
    projects: '$(solution)'
    arguments: '--configuration $(buildConfiguration)' 

# Lanzamos el comando de test
- task: [email protected]
  inputs:
    command: test
    projects: '$(testProject)'
    arguments: '--configuration $(buildConfiguration)' 

En caso de que esto no sea suficiente, dejo también un enlace hacia la documentación oficial.

Con esto, si todo ha ido bien, deberíamos poder ir a la pestaña «Builds» y en ella ver nuestra integración en ejecución.

Azure Pipelines Build

Si pulsamos sobre la compilación, nos mostrará el resultado en el que podemos ver la ejecución de nuestros 3 jobs (uno en cada sistema operativo), con los resultados de la ejecución de cada una de las instrucciones que le pusimos en el «.yml».

Resultados de Azure Pipelines

Por último, solo nos queda ya poder añadir esa badge tan chula a nuestro proyecto, para que todo el mundo sepa que todo está bien, para eso (lo han puesto un poco escondido…), vamos a los 3 puntos que tenemos arriba a la derecha.

Boton para Badge

Pinchamos sobre Status Badge, y nos muestra una nueva ventana con el código listo para copiarlo y pegarlo en nuestro Markdown.

Código Badge

Con esto, hemos conseguido que nuestro repositorio ejecute la integración continua, de modo que, cada vez que hagamos un «push», se lance automáticamente la integración en Azure Pipelines.

Igual que con Travis CI, cuando añadimos esto, se añade a nuestro repositorio de GitHub los checks en cada «push» que indican si la integración ha ido bien:

Historial Commits

Además de lanzarse (al igual que con Travis CI) en cada Pull Request.

Prueba PR

Como último dato, se puede utilizar más de un servicio de integración continua sin ningún tipo de problema al mismo repositorio (como se puede ver en la imagen de arriba en la que están Travis CI, AppVeyor y Azure Pipelines).

En las siguientes entradas de esta serie, veremos cómo podemos utilizar estos mismos sistemas de integración continua, para hacer el despliegue de nuestro proyecto, por ejemplo a Nuget.org o Azure WebApp, consiguiendo así automatizar todo el proceso de manera cómoda.