Freigeben über


Debuggen der hohen CPU-Auslastung in .NET Core

Dieser Artikel bezieht sich auf: ✔️ .NET Core 3.1 SDK und höhere Versionen

In diesem Lernprogramm erfahren Sie, wie Sie ein übermäßiges CPU-Auslastungsszenario debuggen. Mithilfe des bereitgestellten Beispiels ASP.NET Core Web App Quellcode-Repository können Sie absichtlich zu einem Deadlock führen. Der Endpunkt reagiert nicht mehr und erlebt die Threadakkumulation. Sie erfahren, wie Sie verschiedene Tools verwenden können, um dieses Szenario mit mehreren wichtigen Diagnosedaten zu diagnostizieren.

In diesem Lernprogramm wird Folgendes vermittelt:

Voraussetzungen

Das Lernprogramm verwendet:

CPU-Leistungsindikatoren

Bevor Sie dieses Lernprogramm versuchen, installieren Sie bitte die neueste Version von dotnet-counters:

dotnet tool install --global dotnet-counters

Wenn Ihre App eine version von .NET ausführt, die älter als .NET 9 ist, sieht die Ausgabebenutzeroberfläche von dotnet-counters etwas anders aus. Weitere Informationen finden Sie unter dotnet-counters .

Bevor Sie versuchen, Diagnosedaten zu sammeln, müssen Sie eine hohe CPU-Bedingung beobachten. Führen Sie die Beispielanwendung mit dem folgenden Befehl aus dem Projektstammverzeichnis aus.

dotnet run

Verwenden Sie den Befehl "dotnet-counters ", um die aktuelle CPU-Auslastung zu überprüfen:

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

Die Ausgabe sollte etwa wie folgt aussehen:

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

Wenn Sie sich auf die Last Delta Werte dotnet.process.cpu.timekonzentrieren, teilen sie uns mit, wie viele Sekunden innerhalb des Aktualisierungszeitraums (derzeit auf den Standardwert von 1 s festgelegt) die CPU aktiv war. Wenn die Web-App unmittelbar nach dem Start ausgeführt wird, wird die CPU überhaupt nicht verbraucht, und diese Deltas sind beide 0. Navigieren Sie zur api/diagscenario/highcpu Route mit 60000 dem Parameter "Route":

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

Führen Sie nun den Befehl "dotnet-counters" erneut aus.

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

Wie unten dargestellt, sollte eine Zunahme der CPU-Auslastung angezeigt werden (je nach Hostcomputer erwarten Sie unterschiedliche CPU-Auslastung):

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

Während der gesamten Dauer der Anforderung wird die CPU-Auslastung auf den erhöhten Wert zeigen.

Tipp

Um eine noch höhere CPU-Auslastung zu visualisieren, können Sie diesen Endpunkt gleichzeitig auf mehreren Browserregisterkarten ausführen.

An diesem Punkt können Sie sicher sagen, dass die CPU höher ist als erwartet. Die Identifizierung der Auswirkungen eines Problems ist entscheidend, um die Ursache zu finden. Wir verwenden zusätzlich zu Diagnosetools die Auswirkungen des hohen CPU-Verbrauchs, um die Ursache des Problems zu finden.

Analysieren hoher CPU mit Profiler

Bei der Analyse einer App mit hoher CPU-Auslastung benötigen Sie ein Diagnosetool, das Einblicke in die Funktionsweise des Codes liefern kann. Die übliche Wahl ist ein Profiler, und es gibt verschiedene Profileroptionen, aus denen Sie wählen können. dotnet-trace kann jedoch auf allen Betriebssystemen verwendet werden, die Einschränkungen von Safe-Point Bias und managed-only Callstacks führen zu allgemeineren Informationen im Vergleich zu einem kernelfähigen Profiler wie "perf" für Linux oder ETW für Windows. Wenn Ihre Leistungsuntersuchung nur verwalteten Code umfasst, reicht dies im Allgemeinen dotnet-trace aus.

Das perf Tool kann zum Generieren von .NET Core-App-Profilen verwendet werden. Wir zeigen dieses Tool, obwohl auch dotnet-trace verwendet werden könnte. Beenden Sie die vorherige Instanz des Beispieldebugziels.

Legen Sie die DOTNET_PerfMapEnabled Umgebungsvariable fest, damit die .NET-App eine map Datei im /tmp Verzeichnis erstellt. Diese map Datei wird verwendet perf , um CPU-Adressen jiT-generierten Funktionen nach Namen zuzuordnen. Weitere Informationen finden Sie unter Exportieren von perf-Karten und Jit-Dumps.

Hinweis

.NET 6 standardisiert das Präfix DOTNET_ anstelle von COMPlus_ für Umgebungsvariablen, die das .NET-Laufzeitverhalten konfigurieren. Das Präfix COMPlus_ funktioniert jedoch weiterhin. Wenn Sie eine frühere Version der .NET-Laufzeit verwenden, sollten Sie weiterhin das präfix COMPlus_ für Umgebungsvariablen verwenden.

Führen Sie das Beispieldebugziel in derselben Terminalsitzung aus.

export DOTNET_PerfMapEnabled=1
dotnet run

Üben Sie den endpunkt der hohen CPU-API (https://localhost:5001/api/diagscenario/highcpu/60000) erneut aus. Während sie innerhalb der 1-minütigen Anforderung ausgeführt wird, führen Sie den perf Befehl mit Ihrer Prozess-ID aus:

sudo perf record -p 2266 -g

Der perf Befehl startet den Prozess der Leistungsauflistung. Lassen Sie die Ausführung etwa 20 bis 30 Sekunden dauern, und drücken Sie dann STRG+C , um den Sammlungsprozess zu beenden. Sie können denselben perf Befehl verwenden, um die Ausgabe der Ablaufverfolgung anzuzeigen.

sudo perf report -f

Sie können auch ein Flammendiagramm mithilfe der folgenden Befehle generieren:

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

Dieser Befehl generiert einen flamegraph.svg , den Sie im Browser anzeigen können, um das Leistungsproblem zu untersuchen:

Flammdiagramm SVG-Bild

Analysieren hoher CPU-Daten mit Visual Studio

Alle *.nettrace-Dateien können in Visual Studio analysiert werden. Um eine Linux *.nettrace-Datei in Visual Studio zu analysieren, übertragen Sie die Datei *.nettrace zusätzlich zu den anderen erforderlichen Dokumenten auf einen Windows-Computer, und öffnen Sie dann die Datei *.nettrace in Visual Studio. Weitere Informationen finden Sie unter "Analysieren von CPU-Auslastungsdaten".

Siehe auch

Nächste Schritte