Partager via


Déboguer une utilisation élevée du processeur dans .NET Core

Cet article s’applique au ✔️ Kit de développement logiciel (SDK) .NET Core 3.1 et versions ultérieures

Dans ce tutoriel, vous allez apprendre à déboguer un scénario d’utilisation excessive du processeur. En utilisant le référentiel de code source fourni de l'application web ASP.NET Core, vous pouvez provoquer intentionnellement un blocage. Le point de terminaison cesse de répondre et subit une accumulation de threads. Vous découvrirez comment utiliser différents outils pour diagnostiquer ce scénario avec plusieurs éléments clés de données de diagnostic.

Dans ce didacticiel, vous allez :

  • Examiner l’utilisation élevée du processeur
  • Déterminer l’utilisation du processeur avec dotnet-counters
  • Utiliser dotnet-trace pour la génération de trace
  • Performances de profil dans PerfView
  • Diagnostiquer et résoudre une utilisation excessive du processeur

Prerequisites

Le tutoriel utilise :

Compteurs d’UC

Avant d’essayer ce tutoriel, installez la dernière version de dotnet-counters :

dotnet tool install --global dotnet-counters

Si votre application exécute une version de .NET antérieure à .NET 9, l’interface utilisateur de sortie des compteurs dotnet est légèrement différente ; pour plus d’informations, consultez dotnet-counters .

Avant de tenter de collecter des données de diagnostic, vous devez observer une forte utilisation du processeur. Exécutez l’exemple d’application à l’aide de la commande suivante à partir du répertoire racine du projet.

dotnet run

Pour vérifier l’utilisation actuelle du processeur, utilisez la commande de l’outil dotnet-counters :

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

La sortie doit être similaire à ce qui suit :

Press p to pause, r to resume, q to quit.
    Status: Running

Name                                                            Current Value      Last Delta
[System.Runtime]
    dotnet.assembly.count ({assembly})                               111               0
    dotnet.gc.collections ({collection})
        gc.heap.generation
        ------------------
        gen0                                                           8               0
        gen1                                                           1               0
        gen2                                                           0               0
    dotnet.gc.heap.total_allocated (By)                        4,042,656          24,512
    dotnet.gc.last_collection.heap.fragmentation.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     801,728               0
        gen1                                                       6,048               0
        gen2                                                           0               0
        loh                                                            0               0
        poh                                                            0               0
    dotnet.gc.last_collection.heap.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     811,512               0
        gen1                                                     562,024               0
        gen2                                                   1,095,056               0
        loh                                                       98,384               0
        poh                                                       24,528               0
    dotnet.gc.last_collection.memory.committed_size (By)       5,623,808               0
    dotnet.gc.pause.time (s)                                           0.019           0
    dotnet.jit.compilation.time (s)                                    0.582           0
    dotnet.jit.compiled_il.size (By)                             138,895               0
    dotnet.jit.compiled_methods ({method})                         1,470               0
    dotnet.monitor.lock_contentions ({contention})                     4               0
    dotnet.process.cpu.count ({cpu})                                  22               0
    dotnet.process.cpu.time (s)
        cpu.mode
        --------
        system                                                         0.109           0
        user                                                           0.453           0
    dotnet.process.memory.working_set (By)                    65,515,520               0
    dotnet.thread_pool.queue.length ({work_item})                      0               0
    dotnet.thread_pool.thread.count ({thread})                         0               0
    dotnet.thread_pool.work_item.count ({work_item})                   6               0
    dotnet.timer.count ({timer})                                       0               0

En mettant l’accent sur les valeurs de dotnet.process.cpu.time, celles-ci nous indiquent combien de secondes l’UC a été active pendant la période d’actualisation (actuellement définie sur la valeur par défaut de 1 s). Avec l’application web en cours d’exécution, immédiatement après le démarrage, l’UC n'est pas du tout sollicitée et ces deltas sont tous deux 0. Accédez à l’itinéraire api/diagscenario/highcpu avec 60000 comme paramètre d’itinéraire :

https://localhost:5001/api/diagscenario/highcpu/60000

À présent, réexécutez la commande dotnet-counters .

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

Vous devriez voir une augmentation de l’utilisation du processeur, comme indiqué ci-dessous (en fonction de l’ordinateur hôte, attendez-vous à une utilisation variable de l’UC) :

Press p to pause, r to resume, q to quit.
    Status: Running

Name                                                            Current Value      Last Delta
[System.Runtime]
    dotnet.assembly.count ({assembly})                               111               0
    dotnet.gc.collections ({collection})
        gc.heap.generation
        ------------------
        gen0                                                           8               0
        gen1                                                           1               0
        gen2                                                           0               0
    dotnet.gc.heap.total_allocated (By)                        4,042,656          24,512
    dotnet.gc.last_collection.heap.fragmentation.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     801,728               0
        gen1                                                       6,048               0
        gen2                                                           0               0
        loh                                                            0               0
        poh                                                            0               0
    dotnet.gc.last_collection.heap.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     811,512               0
        gen1                                                     562,024               0
        gen2                                                   1,095,056               0
        loh                                                       98,384               0
        poh                                                       24,528               0
    dotnet.gc.last_collection.memory.committed_size (By)       5,623,808               0
    dotnet.gc.pause.time (s)                                           0.019           0
    dotnet.jit.compilation.time (s)                                    0.582           0
    dotnet.jit.compiled_il.size (By)                             138,895               0
    dotnet.jit.compiled_methods ({method})                         1,470               0
    dotnet.monitor.lock_contentions ({contention})                     4               0
    dotnet.process.cpu.count ({cpu})                                  22               0
    dotnet.process.cpu.time (s)
        cpu.mode
        --------
        system                                                         0.344           0.013
        user                                                          14.203           0.963
    dotnet.process.memory.working_set (By)                    65,515,520               0
    dotnet.thread_pool.queue.length ({work_item})                      0               0
    dotnet.thread_pool.thread.count ({thread})                         0               0
    dotnet.thread_pool.work_item.count ({work_item})                   6               0
    dotnet.timer.count ({timer})                                       0               0

Pendant toute la durée de la requête, l'utilisation du processeur se maintiendra autour du niveau élevé.

Conseil / Astuce

Pour visualiser une utilisation encore plus élevée du processeur, vous pouvez exercer ce point de terminaison dans plusieurs onglets de navigateur simultanément.

À ce stade, vous pouvez affirmer sans risque que le processeur fonctionne plus rapidement que prévu. Identifier les effets d’un problème est essentiel pour trouver la cause. Nous allons utiliser l’effet d’une consommation élevée du processeur en plus des outils de diagnostic pour trouver la cause du problème.

Analyser le processeur élevé avec Profiler

Lors de l’analyse d’une application avec une utilisation élevée du processeur, utilisez un profileur pour comprendre ce que fait le code. dotnet-trace collect fonctionne sur tous les systèmes d’exploitation, mais les biais de point sûr et les piles d’appels managées le limitent à des informations plus générales qu’un profileur prenant en charge le noyau comme ETW pour Windows ou perf pour Linux. Selon votre système d’exploitation et votre version .NET, des fonctionnalités de profilage améliorées peuvent être disponibles : consultez les onglets spécifiques à la plateforme qui suivent pour obtenir des instructions détaillées.

Utiliser dotnet-trace collect-linux (.NET 10+)

Sur .NET 10 et versions ultérieures, dotnet-trace collect-linux il s’agit de l’approche de profilage recommandée sur Linux. Il combine EventPipe avec des perf_events au niveau du système d’exploitation pour produire une seule trace unifiée qui inclut à la fois des piles d’appels managées et natives, sans nécessiter de redémarrage de processus. Cela nécessite des droits root et le noyau Linux 6.4+ avec CONFIG_USER_EVENTS=y. Consultez les conditions préalables à collect-linux pour connaître les exigences complètes.

Vérifiez que l’exemple de cible de débogage est configuré pour cibler .NET 10 ou une version ultérieure, puis exécutez-le et effectuez à nouveau l’exercice du point de terminaison processeur élevé (https://localhost:5001/api/diagscenario/highcpu/60000). Pendant l'exécution de la requête d'une minute, exécutez dotnet-trace collect-linux pour capturer une trace au niveau de la machine :

sudo dotnet-trace collect-linux

Laissez-le s’exécuter pendant environ 20 à 30 secondes, puis appuyez sur Ctrl+C ou Entrée pour arrêter la collection. Le résultat est un .nettrace fichier qui inclut des piles d’appels managées et natives.

Ouvrez .nettrace avec PerfView et utilisez la vue Piles du processeur pour identifier les méthodes qui consomment le plus de temps CPU.

Pour obtenir plus d’informations sur la résolution des symboles d’exécution natifs dans la trace, consultez Obtenir des symboles pour les cadres d'exécution natives.

Utilisez perf.

L’outil perf peut également être utilisé pour générer des profils d’application .NET Core. Quittez l’instance précédente de l’échantillon de cible de débogage.

Définissez la DOTNET_PerfMapEnabled variable d’environnement pour que l’application .NET crée un map fichier dans le /tmp répertoire. Ce map fichier est utilisé par perf pour mapper les adresses CPU aux fonctions générées par JIT, par leur nom. Pour plus d’informations, consultez Exporter les cartes perf et les vidages jit.

Exécutez l’exemple de cible de débogage dans la même session de terminal.

export DOTNET_PerfMapEnabled=1
dotnet run

Utilisez à nouveau le point de terminaison de l'API de haute utilisation CPU (https://localhost:5001/api/diagscenario/highcpu/60000). Pendant qu’elle s’exécute dans la requête de 1 minute, exécutez la commande avec votre perf ID de processus :

sudo perf record -p 2266 -g

La perf commande démarre le processus de collecte des performances. Laissez-le s’exécuter pendant environ 20 à 30 secondes, puis appuyez sur Ctrl+C pour quitter le processus de collecte. Vous pouvez utiliser la même perf commande pour afficher la sortie de la trace.

sudo perf report -f

Vous pouvez également générer un graphe de flamme à l’aide des commandes suivantes :

git clone --depth=1 https://github.com/BrendanGregg/FlameGraph
sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

Cette commande génère un flamegraph.svg que vous pouvez voir dans le navigateur pour examiner le problème de performance.

Image SVG de graphe de flamme

Analyse de données processeur élevées avec Visual Studio

Tous les fichiers *.nettrace peuvent être analysés dans Visual Studio. Pour analyser un fichier *.nettrace Linux dans Visual Studio, transférez le fichier *.nettrace, en plus des autres documents nécessaires, vers une machine Windows, puis ouvrez le fichier *.nettrace dans Visual Studio. Pour plus d’informations, consultez Analyser les données d’utilisation du processeur.

Voir aussi

Étapes suivantes