次の方法で共有


.NET Core での CPU 使用率の高いデバッグ

この記事の対象: ✔️ .NET Core 3.1 SDK 以降のバージョン

このチュートリアルでは、過剰な CPU 使用率シナリオをデバッグする方法について説明します。 Core Web アプリのソース コード リポジトリ ASP.NET 例を使用すると、意図的にデッドロックが発生する可能性があります。 エンドポイントは応答を停止し、スレッドの蓄積が発生します。 さまざまなツールを使用して、いくつかの主要な診断データを使用してこのシナリオを診断する方法について説明します。

このチュートリアルの内容:

  • CPU 使用率の高い調査
  • dotnet-counters を使用して CPU 使用率を決定する
  • トレース生成に dotnet-trace を使用する
  • PerfView でのプロファイル パフォーマンス
  • 過剰な CPU 使用率を診断して解決する

[前提条件]

このチュートリアルでは、次の内容を使用します。

CPU カウンター

このチュートリアルを試す前に、最新バージョンの dotnet-counters をインストールしてください。

dotnet tool install --global dotnet-counters

アプリで .NET 9 より古いバージョンの .NET を実行している場合、dotnet-counters の出力 UI は若干異なって表示されます。詳細については 、dotnet-counters を参照してください。

診断データの収集を試みる前に、CPU の状態が高い状態を観察する必要があります。 プロジェクト のルート ディレクトリから次のコマンドを使用して 、サンプル アプリケーション を実行します。

dotnet run

現在の CPU 使用率を確認するには、 dotnet-counters ツール コマンドを使用します。

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

出力は次のようになります。

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

dotnet.process.cpu.timeLast Delta値に焦点を当てて、CPU がアクティブになっている更新期間内 (現在は既定値の 1 秒に設定) の秒数が示されます。 Web アプリを実行すると、起動直後に CPU がまったく消費されず、これらの差分が両方とも 060000をルート パラメーターとして使用して、api/diagscenario/highcpu ルートに移動します。

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

次に、 dotnet-counters コマンドを 再実行します。

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

次に示すように、CPU 使用率の増加が表示されます (ホスト コンピューターによっては、さまざまな CPU 使用率が予想されます)。

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

要求の期間中、CPU 使用率は増加した値の周りにホバーします。

ヒント

さらに高い CPU 使用率を視覚化するには、複数のブラウザー タブで同時にこのエンドポイントを実行できます。

この時点で、CPU が予想以上に高く実行されていることを安全に言うことができます。 問題の影響を特定することは、原因を見つける上で重要です。 診断ツールに加えて、高い CPU 消費量の影響を使用して、問題の原因を見つけます。

Profiler を使用して高 CPU を分析する

CPU 使用率が高いアプリを分析する場合は、コードの動作に関する分析情報を提供できる診断ツールが必要です。 通常の選択はプロファイラーであり、選択できるプロファイラー のオプションは異なります。 dotnet-trace は、すべてのオペレーティング システムで使用できますが、セーフポイント バイアスとマネージドのみの呼び出し履歴の制限により、Linux 用の "perf" や Windows 用 ETW などのカーネル対応プロファイラーと比較して、より一般的な情報が得られます。 パフォーマンス調査にマネージド コードのみが含まれる場合は、通常、 dotnet-trace で十分です。

perf ツールを使用して、.NET Core アプリ プロファイルを生成できます。 このツールを示しますが、dotnet-trace も使用できます。 サンプル デバッグ ターゲットの前のインスタンスを終了します。

DOTNET_PerfMapEnabled環境変数を設定して、.NET アプリが /tmp ディレクトリにmap ファイルを作成するようにします。 この map ファイルは、名前によって JIT で生成された関数に CPU アドレスをマップするために、 perf によって使用されます。 詳細については、「 パフォーマンス マップと JIT ダンプのエクスポート」を参照してください。

.NET 6 では、.NET ランタイム動作を構成する環境変数の DOTNET_ ではなく、プレフィックス COMPlus_ が標準化されています。 ただし、COMPlus_ プレフィックスは引き続き機能します。 以前のバージョンの .NET ランタイムを使用している場合でも、環境変数には COMPlus_ プレフィックスを使用する必要があります。

同じターミナル セッションで サンプル デバッグ ターゲット を実行します。

export DOTNET_PerfMapEnabled=1
dotnet run

高 CPU API エンドポイント (https://localhost:5001/api/diagscenario/highcpu/60000) をもう一度実行します。 1 分間の要求内で実行されている間は、プロセス ID で perf コマンドを実行します。

sudo perf record -p 2266 -g

perf コマンドは、パフォーマンス収集プロセスを開始します。 約 20 ~ 30 秒間実行し、 Ctrl キーを押しながら C キーを押してコレクション プロセスを終了します。 同じ perf コマンドを使用して、トレースの出力を確認できます。

sudo perf report -f

次のコマンドを使用して 、炎グラフ を生成することもできます。

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

このコマンドを実行すると、パフォーマンスの問題を調査するためにブラウザーで表示できる flamegraph.svg が生成されます。

炎グラフ SVG 画像

Visual Studio を使用した高 CPU データの分析

すべての *.nettrace ファイルは、Visual Studio で分析できます。 Visual Studio で Linux の *.nettrace ファイルを分析するには、他の必要なドキュメントに加えて 、*.nettrace ファイルを Windows コンピューターに転送し、Visual Studio で *.nettrace ファイルを開きます。 詳細については、「 CPU 使用率データの分析」を参照してください。

こちらも参照ください

次のステップ