PerformanceCounter en 5 min

Les compteurs de performance Windows permettent de mesurer l’évolution de certaines données de performance liées à une machine (charge CPU, mémoire utilisée, les entrées/sorties réseau) ou à un ou plusieurs processus (taille des piles, quantité de mémoire managée utilisée etc…).

Cet outil peut être utile pour monitorer des données de performance d’un point de vue général dans des données liées à une machine mais aussi plus spécifiquement à un processus si on souhaite, par exemple, révéler une fuite mémoire ou évaluer les performances de l’exécution d’un processus.

Il existe un certain nombre de compteurs de performance livrés avec Windows mais il est possible de créer ses propres compteurs et de les alimenter avec un processus .NET classique. Sur MSDN, on peut avoir la liste complête des compteurs de performance: Performance Counters in the .NET Framework.

Si on dispose des droits administrateur, on peut créer des catégories de compteurs de performance. Il n’est pas nécessaire d’avoir ces droits pour alimenter les compteurs.

1. Types de compteurs de performance

Les principaux types de compteurs sont:

  • NumberOfItems32: permet d’indiquer une grandeur totale comme le nombre d’éléments ou d’opérations. Ce type permet d’indiquer un nombre entier 32 bits, toutefois l’équivalent NumberOfItems64 existe pour des entiers 64 bits. Une grandeur de ce type est utilisée sans transformation.
  • RateOfCountsPerSecond32: permet d’indiquer une grandeur par seconde. Par exemple le nombre d’opérations par seconde. De même, ce type permet d’indiquer un nombre entier 32 bits, l’équivalent pour un entier 64 bits est RateOfCountsPerSecond64. Une grandeur de ce type est utilisée en prenant en compte le temps en ticks de l’horloge système.
  • AverageTimer32: permet d’indiquer le temps moyen en seconde pour effectuer un processus. Ce type permet d’indiquer un entier 32 bits, l’équivalent pour un entier 64 bits est AverageTimer64. Une grandeur de ce type est utilisée en prenant en compte le temps en ticks de l’horloge système.

D’autres types sont disponibles, des exemples sont précisés sur MSDN.

Précisions sur le type RateOfCountsPerSecond32

Les grandeurs de type RateOfCountsPerSecond32 indiquent un nombre par seconde.
Donc si on considère une valeur V0 lue au temps T0 et une autre valeur V1 lue au temps T1.
La formule: (V1 - V0) / (T1 - T0) permet de calculer le nombre d’éléments traités V1 – V0 pour la durée T1 – T0 en ticks.
Pour ramener ce nombre d’éléments à la durée d’une seconde, il faut utiliser F c’est-à-dire le fréquence de ticks par seconde (1/F étant le nombre de ticks par seconde).
La formule devient pour une seconde: (V1 - V0) / ((T1 - T0) / F).

Dans la pratique, on indique seulement les valeurs Vx et en fonction des temps pendant lesquels on a indiqué chaque valeur, le compteur connait les différentes valeurs Tx. De même F étant connu, le compteur applique la formule précédente pour obtenir le taux par seconde.

Précisions sur le type AverageTimer32

Les grandeurs de type AverageTimer32 indique un nombre moyen par seconde.
Donc si on considère une valeur V0 lue au temps T0 et une autre valeur V1 lue au temps T1.
La formule est: (V1 - V0) / (T1 - T0).

De même que précédemment, la formule (V1 - V0) / (T1 - T0) permet de calculer le nombre d’éléments traités V1 – V0 pour la durée T1 – T0 en ticks.
Si on veut calculer la moyenne d’éléments traités en un seconde, il faut utiliser le nombre de ticks par seconde 1/F (F étant la fréquence de ticks par seconde).
La formule devient pour une seconde: ((V1 - V0) / F) / (T1 - T0).

Dans la pratique, on indique les valeurs Vx mais aussi les valeurs Tx. Les valeurs Vx sont indiquées en utilisant le compteur dont le nom est AverageTimer32. Les valeurs Tx sont indiquées en utilisant le compteur correspondant pour le dénominateur de la formule c’est-à-dire AverageBase.
Les équivalences sont les mêmes pour AverageCount32 et AverageBase.

Utiliser les compteurs de performance

3 méthodes sont possibles:

  • Performance Monitor: outil de monitoring qui permet d’afficher les compteurs de performance sous forme de graphique.
  • Avec Visual Studio: on peut directement créer des catégories avec Visual Studio à condition d’avoir les droits administrateur.
  • Par programmation: on peut créer des catégories mais aussi alimenter des compteurs de performance par code.

2. Performance Monitor

Le performance monitor permet de visualiser les compteurs de performance configurés sur la machine locale ou une machine distante. On peut afficher les compteurs livrés avec Windows ou des compteurs personnalisés.

Pour ouvrir Performance Monitor, il faut taper à la ligne de commandes perfmon.

Visualiser un compteur de performance

Avec Performance Monitor, il suffit d’ajouter un compteur en cliquant sur “Add Counters”.
Il faut généralement affiner l’échelle en se référant à la dernière valeur courante car la courbe est souvent en dehors du graphique.

Profiler une fuite mémoire

Performance monitor permet de facilement visualiser une fuite mémoire en affichant les différentes mémoires utilisées par un processus. La mémoire d’un processus .NET est partagées entre mémoire managée et mémoire non managée.

Grâce à Performance Monitor, il est possible d’afficher la mémoire totale consommée par le processus et la mémoire managée. On peut en déduire la quantité de mémoire non managée utilisée:

  • La mémoire totale du processur .NET: on peut afficher cette grandeur en allant dans la catégorie de compteur “Process” puis en sélectionnant le compteur “Private Bytes”.
  • La mémoire managée: cette grandeur est accessible dans la catégorie “.NET CLR Memory” et en sélectionnant le compteur “# Bytes en all Heaps”.

L’écart entre les 2 grandeurs précédentes permettent de déterminer la quantité de mémoire non-managée utilisée. Ainsi si la mémoire managée reste constante mais que la mémoire totale du processus ne cesse d’augmenter, on peut en déduire qu’il y a bien une fuite mémoire dans la mémoire non managée.

3. Visual Studio

Visual Studio permet de créer des catégories de compteurs de performance et des compteurs de performance.

Création d’une catégorie de compteur de performance

Il faut démarrer Visual Studio avec les droits administrateur:

  1. Cliquer sur le menu “View”,
  2. Cliquer sur “Server-Explorer”.
  3. Dépliant le noeud correspondant à un serveur et déplier “Performance Counters”.
  4. Clique droit sur “Performance Counters”
  5. Cliquer sur “Create New Category”.

Création d’un compteur de performance

Un compteur doit appartenir à une catégorie. Sur le panneau d’une catégorie, on peut ajouter ou supprimer des compteurs de performance. La création d’un compteur de performance se fait en indiquant son type, son nom et une description.

4. Utiliser les compteurs de performance pour programmation

On peut ajouter une nouvelle catégorie par si le processus est exécuté avec les droits administrateur. De même, on peut aussi créer et alimenter les compteurs de performance.

Les classes nécessaires à la manipulation des compteurs de performance se trouvent dans le namespace System.Diagnostics.

Création d’une catégorie de compteur de performance

Un compteur de performance doit obligatoirement se trouver dans une catégorie. Si la catégorie dans laquelle on veut ranger le compteur n’existe pas, il faut la créer. Une fois créée, il n’est pas nécessaire de la recréer pour l’utiliser.

Pour exécuter le code de création d’une catégorie, il faut que le processus soit exécuté avec les droits administrateur sinon une exception survient.

La classe PerformanceCounterCategory permet de vérifier que la catégorie existe et de créer la catégorie ainsi que les compteurs de performance qui s’y trouvent:

if (!PerformanceCounterCategory.Exists("Counter custom category")) 
{ 
    CounterCreationDataCollection counters = new CounterCreationDataCollection(); 
 
    // Creating counter with type NumberOfItems32 
    CounterCreationData totalOperations = new CounterCreationData(); 
    totalOperations.CounterName = "# operations executed"; 
    totalOperations.CounterHelp = "Total number of operations executed"; 
    totalOperations.CounterType = PerformanceCounterType.NumberOfItems32; 
    counters.Add(totalOperations); 
 
    PerformanceCounterCategory.Create("Counter custom category",  
        "Category help message", counters); 
}

Ce code permet de créer un compteur de performance dans le type est NumberOfItems32 et de le ranger dans la catégorie “Counter custom category”. Le nom du compteur “# operations executed” est le seul moyen d’identifier le compteur.
A ce moment le compteur n’est pas alimenté toutefois la catégorie et le compteur sont visibles dans Performance Monitor ou dans Visual Studio.

Alimenter un compteur de performance

Après avoir créé le compteur avec l’étapé précédente, on peut l’alimenter en valeurs avec le code suivant:

var customPerformanceCounter = new PerformanceCounter("Counter custom category", "# operations executed"); 
customPerformanceCounter.ReadOnly = false; 
CustomPerformanceCounter.RawValue = 0;

Cette surcharge permet d’utiliser le compteur de performance “# operations executed” sur la machine locale. On indique ensuite qu’on souhaite l’alimenter avec

customPerformanceCounter.ReadOnly = false;

On peut indiquer qu’on souhaite lire ou écrire les valeurs d’un compteur distant en précisant le nom de la machine:

customPerformanceCounter.MachineName = "FRXXXXXX";

On peut indiquer de nouvelles valeurs en exécutant:

var customPerformanceCounter = new PerformanceCounter("Counter custom category", "# operations executed");  
customPerformanceCounter.ReadOnly = false;  
customPerformanceCounter.RawValue = 0; 
Random random = new Random(); 
while (true) 
{ 
    int newValue = random.Next(0, 5); 
    customPerformanceCounter.IncrementBy(newValue); 
    Thread.Sleep(1000); 
}

Dans cette exemple, on ne précise qu’une seule valeur car avec un compteur de type NumberOfItems32, la valeur est utilisée directement (voir plus haut).

Les méthodes de la classe System.Diagnostics.PerformanceCounter permettant d’ajouter des valeurs sont:

  • Increment(): incrémente la valeur d’une unité.
  • IncrementBy(): incrémente ou décrémente la valeur d’une certaine valeur.
  • Decrement(): décrémente la valeur d’une unité.
  • RawValue: indique une valeur brute.
Utilisation des PerformanceCounters en terme de performances

L’utilisation de PerformanceCounter n’est pas anodin sur les performances du processus. Les méthodes Increment(), IncrementBy() et Decrement() utilisent des “locks” pour éviter les accès concurrents, ce qui ralentit l’exécution.
L’utilisation de PerformanceCounter.RawValue est plus rapide car l’affectation n’utilise pas de “lock”.

Enfin si plusieurs processus alimentent des valeurs dans le compteur de performance en utilisant PerformanceCounter.Increment() ou PerformanceCounter.Decrement(), les différentes contributions de valeurs se cumulent.

RatesOfCountsPerSecond32

Comme indiqué précédemment (voir plus haut), ce type de compteur permet de préciser un nombre d’éléments par seconde toutefois on ne précise qu’une seule valeur.

Si on crée le compteur de cette façon:

CounterCreationData operationsPerSec = new CounterCreationData(); 
operationsPerSec.CounterName = "# operations per second executed"; 
operationsPerSec.CounterHelp = "Number of operations per second executed"; 
operationsPerSec.CounterType = PerformanceCounterType.RatesOfCountsPerSecond32; 
counters.Add(operationsPerSec);

On peut l’utiliser de cette façon:

var operationsPerSecCounter = new PerformanceCounter("Counter custom category",  
    "# operations per second executed");  
operationsPerSecCounter.ReadOnly = false;  
operationsPerSecCounter.RawValue = 0; 
Random random = new Random(); 
while (true) 
{ 
    int newValue = random.Next(0, 5); 
    operationsPerSecCounter.IncrementBy(newValue); 
    Thread.Sleep(50); 
}

AverageTimer32

Comme indiqué précédemment (voir plus haut), les compteurs de type AverageTimer32 sont couplés avec un compteur de type AverageBase contenant les valeurs du dénominateur dans la formule de calcul des valeurs affichées.
On doit donc préciser 2 valeurs en utilisant le compteur de type AverageTimer32 et le compteur de type AverageBase.

Si on crée les compteurs de cette façon:

CounterCreationData averageOperationsPerSec = new CounterCreationData(); 
averageOperationsPerSec.CounterName = "Average operations per second executed"; 
averageOperationsPerSec.CounterHelp = "Average of operations per second executed"; 
averageOperationsPerSec.CounterType = PerformanceCounterType.AverageTimer32; 
counters.Add(averageOperationsPerSec); 

CounterCreationData averageOperationsPerSecBase = new CounterCreationData(); 
averageOperationsPerSecBase.CounterName = "Base average operations per second executed"; 
averageOperationsPerSecBase.CounterHelp = "Base average of operations per second executed"; 
averageOperationsPerSecBase.CounterType = PerformanceCounterType.AverageBase; 
counters.Add(averageOperationsPerSecBase);

On peut les utiliser de cette façon:

var averageOperationsPerSec = new PerformanceCounter("Counter custom category",  
    "Average operations per second executed", false);  
var averageOperationsPerSecBase = new PerformanceCounter("Counter custom category",  
    "Base average operations per second executed", false);  
 
Random random = new Random(); 
while (true) 
{ 
    int newValue = random.Next(0, 5); 
    averageOperationsPerSec.RawValue = newValue; 
    averageOperationsPerSecBase.IncrementBy(10); 
    Thread.Sleep(1000); 
}

Lire un compteur de performance

Pour lire les mesures d’un compteur de performance, il faut définir le compteur comme précédemment et utiliser les fonctions PerformanceCounter.NextSample() ou PerformanceCounter.NextValue():

var customPerformanceCounter = new PerformanceCounter("Counter custom category", "# operations executed");   
while (true)  
{  
    CounterSample sample = customPerformanceCounter.NextSample();  
    Thread.Sleep(1000);  
}

PerformanceCounter.NextSample() permet d’obtenir un objet de type CounterSample contenant des propriétés contenant les valeurs comme CounterSample.RawValue pour obtenir la valeur brute.
On peut aussi obtenir cette valeur directement avec:

Float nextValue = CounterSampler.NextValue();

QueryPerformanceCounter

Utiliser QueryPerformanceCounter plutôt que System.DateTime.Now est plus précis pour mésurer des temps d’exécution. QueryPerformanceCounter est une fonction de l’API Win32.

Pour l’utiliser, il faut faire un appel en utilisant Platform Invoke:

[DllImport("Kernel32.dll")] 
public static extern void QueryPerformanceCounter(ref long ticks); 
 
// ...
 
long startTime = 0; 
long endTime = 0; 
 
// Getting start time 
QueryPerformanceCounter(ref startTime); 
 
// Simulating processing time 
Thread.Sleep(40); 
 
// Getting end time 
QueryPerformanceCounter(ref endTime); 
 
long executionTime = endTime - startTime; 

Leave a Reply