Udostępnij za pośrednictwem


Debugowanie wysokiego użycia procesora CPU na platformie .NET Core

Ten artykuł dotyczy: ✔️ .NET Core 3.1 SDK i nowszych wersji

Z tego samouczka dowiesz się, jak debugować nadmierny scenariusz użycia procesora CPU. Korzystając z podanego przykładu repozytorium kodu źródłowego ASP.NET Core aplikacji internetowej, możesz celowo spowodować zakleszczenie. Punkt końcowy przestanie odpowiadać i wystąpi nagromadzenie wątków. Dowiesz się, jak można używać różnych narzędzi do diagnozowania tego scenariusza z kilkoma kluczowymi elementami danych diagnostycznych.

Ten samouczek obejmuje następujące kroki:

  • Badanie wysokiego użycia CPU
  • Określanie użycia procesora CPU za pomocą dotnet-counters
  • Użyj dotnet-trace do generowania śladów
  • Wydajność profilu w programie PerfView
  • Diagnozowanie i rozwiązywanie nadmiernego użycia procesora

Wymagania wstępne

Samouczek wykorzystuje:

Liczniki procesora CPU

Przed podjęciem próby przeprowadzenia tego samouczka zainstaluj najnowszą wersję liczników dotnet::

dotnet tool install --global dotnet-counters

Jeśli aplikacja korzysta z wersji platformy .NET starszej niż .NET 9, wyjściowy interfejs użytkownika liczników dotnet-counter będzie wyglądać nieco inaczej; Aby uzyskać szczegółowe informacje, zobacz dotnet-counters .

Przed podjęciem próby zebrania danych diagnostycznych należy obserwować wysokie użycie CPU. Uruchom przykładową aplikację przy użyciu następującego polecenia z katalogu głównego projektu.

dotnet run

Aby sprawdzić bieżące użycie procesora CPU, użyj polecenia narzędzia dotnet-counters :

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

Dane wyjściowe powinny być podobne do następujących:

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

Skupienie się na wartościach Last Delta informuje nas, ile sekund w okresie odświeżania (obecnie ustawionym na wartość domyślną 1 s) procesor był aktywny. Po uruchomieniu aplikacji internetowej natychmiast po starcie procesor nie jest używany w ogóle, a te delty są zarówno 0. Przejdź do ścieżki api/diagscenario/highcpu z 60000 jako parametrem ścieżki.

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

Teraz uruchom ponownie polecenie dotnet-counters .

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

Powinien zostać wyświetlony wzrost użycia procesora, co pokazano poniżej (zależnie od maszyny hosta, oczekuj różnego użycia procesora):

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

przez cały czas trwania żądania użycie procesora będzie oscylować wokół zwiększonej wartości.

Wskazówka

Aby zwizualizować jeszcze wyższe użycie procesora CPU, możesz wykonać ten punkt końcowy na wielu kartach przeglądarki jednocześnie.

W tym momencie można stwierdzić, że procesor działa na wyższych obrotach niż się spodziewano. Identyfikowanie skutków problemu jest kluczem do znalezienia przyczyny. Oprócz narzędzi diagnostycznych użyjemy efektu wysokiego użycia procesora CPU, aby znaleźć przyczynę problemu.

Analizowanie wysokiego użycia CPU za pomocą profilera

Podczas analizowania aplikacji z wysokim użyciem procesora CPU użyj profilera, aby zrozumieć, co robi kod. dotnet-trace collect działa na wszystkich systemach operacyjnych, ale stronnicze punkty kontrolne i tylko zarządzane stosy wywołań ograniczają je do bardziej ogólnych informacji niż profiler obsługujący jądro, taki jak ETW dla systemu Windows lub perf dla systemu Linux. W zależności od systemu operacyjnego i wersji platformy .NET mogą być dostępne ulepszone możliwości profilowania — zapoznaj się z kartami specyficznymi dla platformy, aby uzyskać szczegółowe wskazówki.

Użyj dotnet-trace collect-linux (.NET 10+)

W programie .NET 10 lub nowszym dotnet-trace collect-linux zalecane jest podejście profilowania w systemie Linux. Łączy EventPipe z perf_events na poziomie systemu operacyjnego, aby utworzyć jedną spójną trasę, która obejmuje zarówno zarządzane, jak i natywne stosy wywołań, bez konieczności ponownego uruchamiania procesu. Wymaga to uprawnień głównych i jądra systemu Linux w wersji 6.4 lub nowszej z programem CONFIG_USER_EVENTS=y. Zobacz wymagania wstępne collect-linux dla pełnych wymagań.

Upewnij się, że przykładowy cel debugowania został skonfigurowany do docelowego środowiska .NET 10 lub nowszego, a następnie uruchom go i ponownie przećwicz punkt końcowy obciążający procesor (https://localhost:5001/api/diagscenario/highcpu/60000). Gdy jest ono uruchamiane w ciągu 1-minutowego żądania, uruchom polecenie dotnet-trace collect-linux , aby przechwycić ślad obejmujący całą maszynę:

sudo dotnet-trace collect-linux

Poczekaj około 20–30 sekund, a następnie naciśnij klawisze Ctrl+C lub Enter , aby zatrzymać kolekcję. Wynikiem jest .nettrace plik zawierający zarówno zarządzane, jak i natywne stosy wywołań.

Otwórz element .nettrace za pomocą PerfView i użyj widoku Stosy CPU aby zidentyfikować metody zużywające najwięcej czasu CPU.

Aby uzyskać informacje na temat rozpoznawania natywnych symboli środowiska uruchomieniowego w śladzie, zobacz Pobieranie symboli dla natywnych ramek środowiska uruchomieniowego.

Użyj perf

Narzędzie perf może również służyć do generowania profilów aplikacji platformy .NET Core. Zamknij poprzednie wystąpienie przykładowego celu debugowania.

Ustaw zmienną DOTNET_PerfMapEnabled środowiskową, aby spowodować utworzenie pliku w map katalogu przez aplikację /tmp .NET. Ten map plik jest używany przez perf do mapowania adresów CPU na funkcje generowane przez JIT według nazwy. Aby uzyskać więcej informacji, sprawdź Eksportowanie map wydajności i zrzutów JIT.

Uruchom przykładowy element docelowy debugowania w tej samej sesji terminalu.

export DOTNET_PerfMapEnabled=1
dotnet run

Ponownie przetestować punkt końcowy API obciążającego procesor (https://localhost:5001/api/diagscenario/highcpu/60000). Podczas wykonywania żądania o czasie trwania jednej minuty, uruchom polecenie perf z użyciem identyfikatora procesu:

sudo perf record -p 2266 -g

Polecenie perf uruchamia proces zbierania wydajności. Poczekaj około 20–30 sekund, a następnie naciśnij Ctrl+C , aby zakończyć proces zbierania. Możesz użyć tego samego polecenia perf, aby wyświetlić wynik śladu.

sudo perf report -f

Możesz również wygenerować graf płomienia przy użyciu następujących poleceń:

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

To polecenie generuje element flamegraph.svg , który można wyświetlić w przeglądarce w celu zbadania problemu z wydajnością:

Obraz SVG wykresu płomieni

Analizowanie danych wysokiego użycia procesora CPU za pomocą programu Visual Studio

Wszystkie pliki *.nettrace można analizować w programie Visual Studio. Aby przeanalizować plik *.nettrace systemu Linux w programie Visual Studio, przenieś plik *.nettrace, oprócz innych niezbędnych dokumentów, na maszynę z systemem Windows, a następnie otwórz plik *.nettrace w programie Visual Studio. Aby uzyskać więcej informacji, zobacz Analizowanie danych użycia procesora CPU.

Zobacz także

Dalsze kroki