Garbage Collector en .NET - Partie 5
2021-04-18 par Badre BSAILA
Re-bienvenue à la série de gestion mémoire en .NET. J’ai du l’arrêter à cause de quelques événements majeurs dans ma vie perso/professionnelle et je m’en excuse. Sans plus d’attente, let’s GO. Dans cette partie, je vais parler des 2 manières pour mesurer l’évolution de la mémoire d’une application dans le temps : les compteurs de performance (pour Windows) et les événements ETW (CrossPlatform). Stay tuned.
Retrouvez aussi dans la série :
- Partie 1 - Tas managé, LOH, GC, Collection, Générations et Ségments.
- Partie 2 - Démonstration avec windbg.
- Partie 3 - Graphe des objets et références.
- Partie 4 - Workstation/Server GC et arrière-plan GC.
- Partie 5 - Compteurs de performance et événements ETW (article courant).
Les compteurs de performance
On peut les consulter à travers l’outil Windows PerfMon.exe.
Ils permettent de mesurer les performances de l’OS : utilisation de CPU/RAM, lectures/écritures depuis/vers le disque, flux de données entrant depuis les interfaces réseaux,…. Historiquement, on avait juste cette possiblité pour monitorer la mémoire. Il faudra pour ceci lancer PerfMon depuis le run ou en cherchant Performance Monitor dans Start. Dans l’onglet gauche de sa fenêtre, cherchez Performance -> Monitoring Tools -> Performance Monitor, un graphe s’affichera par défaut contenant le pourcentage d’utilisation du CPU sur votre machine et au dessous une liste des compteurs affichés.
Clique droit sur la liste des compteurs -> Add Counters. Choisissez dans la liste des compteurs disponibles par exemple : .NET CLR Memory -> # Total committed Bytes, cliquez sur le bouton Add et ça affichera l’évolution des bytes réservés sur la mémoire virtuelle de l’OS et remplis physiquement sur RAM/Disque par le GC.
Monitorer la mémoire d’un processus .NET
Pour mesurer l’évolution de la mémoire allouée uniquement à un processus spécifique (pas nécessairement .NET), ajoutez un nouveau compteur de type Process -> Virtual Bytes et puis choisissez le bon process :
En cliquant sur OK, vous obtenez un graphe semblable au suivant :
Voici aussi une liste non-exhaustive des métriques utiles pour investiguer les issues mémoire :
-
.NET CLR Memory -> % Time in GC : le pourcentage de temps CPU consommé sur une collection depuis la dernière collection. Exemples: si 100 cycles ont été consommés depuis la dernière collection et que cette dernière a consommé 25, le graph affichera la valeur 25%.
-
.NET CLR Memory -> Allocated Bytes/sec : affiche la fréquence d’allocation mémoire. La métrique est précise uniquement quand la fréquence d’allocation est importante, étant mise à jour uniquement durant une collection.
-
.NET CLR Memory -> Large Object Heap size : affiche la taille de la LOH. La métrique est de même mise à jour uniquement durant une collection.
-
.NET CLR Memory -> # Gen X Collections : affiche le nombre de collections de la génération X depuis le début du processus.
-
.NET CLR Memory -> Promoted Memory from Gen X : affiche le nombre de bytes qui survivent une collection de génération X et sont de ce fait promu à la génération X + 1.
-
.NET CLR Memory -> Gen X heap size : affiche la taille de la génération X.
Fiabilité des compteurs de performance
Les compteurs de performance ne sont pas des mesures exhaustives de l’évolution d’une métrique, mais plutôt des échantillons de mesures. En terme réel, vous pourriez par exemple avec Process -> Virtual Bytes obtenir des valeurs minimes si PerfMon échantillone les mesures après les collections. De ce fait, il faut être très prudent avec les mesures, ne pas sauter vers des conclusions hâtives et souvent croiser les mesures avec celles d’autres métriques pour s’assurer que les chiffres sont bien représentatifs.
Pour configurer l’échantillonage d’un graphe, clique droit sur l’élément correspondant dans la liste des graphes (en bas de la fenêtre de PerfMon) -> Properties -> Onglet General.
Evénements ETW
ETW : Event Tracing for Windows est un système de traçage qui permet de tracer des événements prédéfinis côté noyau ou applicatif. On peut consommer les événements en temps réel ou depuis un fichier de log. ETW permet d’activer/désactiver le traçage dynamiquement dans les environnements de production sans la nécessité de redémarrer les machines ou les applications.
L’api de ETW est composé de 3 composants distincts :
- Contrôleurs : permettent d’arrêter/redémarrer les sessions de traçage et activent les producteurs.
- Producteurs : fournissent les événements à proprement dire.
- Consommateurs : consomment ces derniers.
Collecter les événements ETW relatifs à la mémoire
Sur ce lien, téléchargez le zip contenant l’exécutable de perfview. Ajoutez le au PATH de Windows comme ça on peut le lancer depuis l’invite de commande. Pour collecter les événements ETW liés au GC, exécutez la commande :
perfview /GCCollectOnly /AcceptEULA /nogui collect
On peut la laisser tourner ainsi jusqu’à ce qu’on collecte suffisament de données et l’arrêter avec la clé de clavier ‘s’. Sinon on peut fixer le temps max de collection en secondes :
perfview /GCCollectOnly /AcceptEULA /nogui /MaxCollectSec:600 collect
Une fois la collection terminée 2 fichiers sont produits :
- PerfViewGCCollectOnly.etl.zip : contenant les événements collectés
- PerfViewGCCollectOnly.log.txt : détaillant ce qu’a fait perfview pour obtenir ces derniers.
Finalement, ouvrons le fichier des événements avec la commande :
perfview64 PerfViewGCCollectOnly.etl.zip
Analyser les résultats
Sur le menu gauche, cliquez sur PerfViewGCCollectOnly.etl.zip -> Memory Group -> GCStats, une fenêtre s’ouvrira avec la liste des processus .NET + leurs identifiants respectifs :
Sommaire
Cliquez sur le processus pour qui vous voulez analyser la mémoire, vous allez être redirigé sur un sommaire ressemblant à ceci :
•CommandLine: "powershell.exe" "-Command $version='1.0.0.0' $application='CitrixVPN' $path=[Environment]::GetFolderPath('CommonApplicationData') $logfile=\"$path\citrix\agee\intune.log\" $jsfile=\"$path\citrix\agee\intune.js\" class conn{[string]$name; [string]$url; [string]$cert} class config{[long]$btime[bool]$auto_open_homepage[conn[]]$connections[bool]$debug_logging[string]$language[string]$userAgent[bool]$local_lan_access[bool]$disableL3[long]$etime } function Write-Log([string]$l
•Runtime Version: V 4.0.30319.0 (built on 08/10/2020 02:46:38)
•CLR Startup Flags: LOADER_OPTIMIZATION_MULTI_DOMAIN
•Total CPU Time: 0 msec
•Total GC CPU Time: 0 msec
•Total Allocs : 192,012 MB
•GC CPU MSec/MB Alloc : 0,000 MSec/MB
•Total GC Pause: 95,1 msec
•% Time paused for Garbage Collection: 3,1%
•% CPU Time spent Garbage Collecting: NaN%
•Max GC Heap Size: 82,192 MB
Vous y retrouverez notamment des informations sur :
- CommandLine : la commande ayant lancé votre application.
- Runtime Version : La version du framework .NET utilisé.
-
CLR Startup Flags :
- Si elle contient CONCURRENT_GC : ça voudrait dire que vos collections sont concurentes (arrière plan-GC).
- Si elle contient SERVER_GC : ça voudrait dire que server GC est actif.
- Si aucune des deux dernières n’est présente, ça veut dire que GC travaille en mode Workstation avec collection concurrente désactivée.
- Total Allocs : le total des allocations mémoire faites par l’application.
- Total GC pause : le total des temps de pause durant les collections du GC.
- % Time paused for Garbage Collection : le total des temps de pause durant les collections du GC / le temps d’exécution total.
- Max GC Heap Size : la taille maximale du tas.
- Total CPU Time/Total GC CPU Time/GC CPU MSec/MB Alloc/% CPU Time spent Garbage Collecting devront être à zéro car on ne collecte pas d’événements CPU (flag /GCCollectOnly).
Métriques par génération
Vous allez trouver un tableau portant le nom GC Rollup By Generation affichant des métriques par génération :
- Gen : le numéro de la génération
- Count : le compteur du nombre des collections faites par génération.
- Max pause : le max des temps d’arrêt d’une application pour faire la collection de la génération X.
- Mean pause : la moyenne des temps d’arrêt d’une application pour faire la collection de la génération X.
- Total pause : la somme des temps d’arrêt d’une application pour faire les collections de la génération X.
- Total Alloc MB : le total des allocations mémoire faites pour la génération X.
-
Induced : le total des collections induites (une collection est induite quand c’est le code qui la déclenche à travers l’appel à
GC.Collect
).
Les événements de collection
Vous allez aussi trouver un tableau portant le nom GC Events by Time résumant les différentes collections qui se sont produits durant l’exécution du programme :
-
Trigger Reason : décrit la raison du déclenchement de cette collection, une liste non exhaustive des raisons possibles :
- AllocSmall : les allocations sur la génération 0 ont dépassé la limite fixé par le GC.
- AllocLarge : les allocations sur la LOH ont dépassé la limite fixé par le GC.
- InducedLowMemory : le GC s’abonne aux événements de mémoire faible diffusés par l’OS. Quand ce dernier les notifie, le GC lance une collection.
-
Induced : la collection a été déclenché depuis le code avec
GC.Collect
. -
InducedNotForced : la collection a été déclenché depuis le code avec
GC.Collect
en utilisant la surcharge qui laisse au GC de décider si la collection est opportune et l’exécuter ou ne pas la faire sinon.
- Pause MSec : le temps durant lequel les threads ont été pausés à cause du GC.
- Suspend MSec : le temps nécessaire pour pauser tous les threads.
- GenX MB : la taille de la génération X, espace mémoire fragmenté inclus (Calculé après collection).
- GenX Surival Rate : le pourcentage d’objets de génération X ayant survécu à une collection de cette dernière.
- GenX Frag : le pourcentage d’espace fragmenté sur la génération X.
- Promoted MB : le total des bytes promus à la génération supérieur après une collection.
- Pinned Obj : le nombre d’objets épinglés dans la génération X.
- Finalizable Surv MB : quand un objet est en attente de traitement dans la queue de finalisation et qu’il survie une collection, il est promu automatiquement à la génération supérieur, cette colonne calcule la taille des données promues durant leurs finalisations.
Références
Badre BSAILA, Ingénieur d'étude et développement .NET sénior