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 lernen Sie Folgendes:

  • Untersuchen der hohen CPU-Auslastung
  • Ermitteln der CPU-Auslastung mit dotnet-counters
  • Verwenden Sie dotnet-trace zur Erstellung von Ablaufverfolgungen.
  • Profilleistung in PerfView
  • Diagnostizieren und Lösen einer übermäßigen CPU-Auslastung

Voraussetzungen

Das Tutorial 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-Auslastung feststellen. 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 um den erhöhten Wert schwanken.

Tipp

Um eine noch höhere CPU-Auslastung anzuzeigen, können Sie diesen Endpunkt gleichzeitig in mehreren Browser-Registerkarten nutzen.

An diesem Punkt können Sie sicher sagen, dass die CPU über den Erwartungen läuft. 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 hohe CPU-Auslastung mit dem Profiler

Verwenden Sie beim Analysieren einer App mit hoher CPU-Auslastung einen Profiler, um zu verstehen, was der Code tut. dotnet-trace collect funktioniert auf allen Betriebssystemen, aber Safe-Point-Bias und nur verwaltete Callstacks beschränken sie auf allgemeinere Informationen als ein kernel-fähiger Profiler wie ETW für Windows oder perf für Linux. Je nach Betriebssystem und .NET-Version stehen möglicherweise verbesserte Profilerstellungsfunktionen zur Verfügung. Die plattformspezifischen Registerkarten finden Sie in den folgenden detaillierten Anleitungen.

Nutzen Sie dotnet-trace collect-linux (.NET 10+)

Auf .NET 10 und höher dotnet-trace collect-linux ist der empfohlene Profilerstellungsansatz unter Linux. Es kombiniert EventPipe mit perf_events auf Betriebssystemebene, um eine einzelne einheitliche Ablaufverfolgung zu erzeugen, die sowohl verwaltete als auch systemeigene Aufrufstapel umfasst, ohne dass ein Prozessneustart erforderlich ist. Dies erfordert Stammberechtigungen und Linux-Kernel 6.4+ mit CONFIG_USER_EVENTS=y. Siehe 'Collect-Linux'-Voraussetzungen für die vollständigen Anforderungen.

Stellen Sie sicher, dass das Beispieldebugziel für .NET 10 oder höher konfiguriert ist, führen Sie es aus, und üben Sie den hohen CPU-Endpunkt (https://localhost:5001/api/diagscenario/highcpu/60000) erneut aus. Während sie innerhalb der 1-minütigen Anforderung ausgeführt wird, führen Sie die Ausführung dotnet-trace collect-linux aus, um eine computerweite Ablaufverfolgung zu erfassen:

sudo dotnet-trace collect-linux

Lassen Sie die Ausführung etwa 20 bis 30 Sekunden dauern, und drücken Sie dann STRG+C , oder drücken Sie die EINGABETASTE , um die Sammlung zu beenden. Das Ergebnis ist eine .nettrace Datei, die sowohl verwaltete als auch systemeigene Aufrufstapel enthält.

Öffnen Sie die .nettrace mit PerfView und verwenden Sie die Ansicht CPU-Stapel, um die Methoden zu identifizieren, die die meiste CPU-Zeit verbrauchen.

Informationen zum Auflösen nativer Laufzeitsymbole in der Ablaufverfolgung finden Sie unter Abrufen von Symbolen für native Laufzeit-Frames.

perf verwenden

Das perf Tool kann auch zum Generieren von .NET Core-App-Profilen verwendet werden. 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 von perf verwendet, um CPU-Adressen JIT-generierten Funktionen nach Namen zuzuordnen. Weitere Informationen finden Sie unter Exportieren von Perf-Kartendateien und JIT-Dumps.

Führen Sie das Beispieldebugziel in derselben Terminalsitzung aus.

export DOTNET_PerfMapEnabled=1
dotnet run

Rufen Sie den API-Endpunkt für hohe CPU (https://localhost:5001/api/diagscenario/highcpu/60000) erneut auf. 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 Trace-Ausgabe 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