在 .NET Core 中调试高 CPU 使用率

本文适用于:✔️ .NET Core 3.1 SDK 及更高版本

本教程介绍如何调试过多的 CPU 使用率方案。 使用提供的示例 ASP.NET Core Web 应用 源代码存储库,可以有意造成死锁。 终结点将停止响应并体验线程累积。 你将了解如何使用各种工具来诊断此方案,其中包含多个关键诊断数据。

在本教程中,你将:

  • 调查高 CPU 使用率
  • 使用 dotnet-counters 确定 CPU 使用率
  • 使用 dotnet-trace 生成跟踪
  • PerfView 中的配置文件性能
  • 诊断并解决 CPU 使用率过高的问题

先决条件

本教程使用:

CPU 计数器

在尝试本教程之前,请安装最新版本的 dotnet-counters:

dotnet tool install --global dotnet-counters

如果应用运行的 .NET 版本低于 .NET 9,则 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

专注于 Last Delta 其值 dotnet.process.cpu.time,这些指示刷新期间(当前设置为默认值为 1 秒)中 CPU 处于活动状态的秒数。 在 Web 应用运行时,在启动后立即消耗 CPU,并且这些增量都是 0。 以路由参数的形式导航到 api/diagscenario/highcpu 路由 60000

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 使用率较高的效果来找出问题的原因。

使用探查器分析高 CPU

分析 CPU 使用率较高的应用时,需要一个诊断工具,以便深入了解代码正在执行的作。 通常的选择是探查器,并且有不同的探查器选项可供选择。 dotnet-trace 可以在所有作系统上使用,但是,与 Linux 的“perf”或适用于 Windows 的 ETW 等内核感知探查器相比,其安全点偏差和仅托管调用堆栈的限制会导致更常规的信息。 如果性能调查仅涉及托管代码,通常 dotnet-trace 就足够了。

该工具 perf 可用于生成 .NET Core 应用配置文件。 我们将演示此工具,尽管也可以使用 dotnet-trace。 退出 示例调试目标的上一个实例。

设置DOTNET_PerfMapEnabled环境变量,使 .NET 应用在/tmp目录中创建map文件。 此 map 文件用于 perf 按名称将 CPU 地址映射到 JIT 生成的函数。 有关详细信息,请参阅 导出性能映射和 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 数据

可以在 Visual Studio 中分析所有 *.nettrace 文件。 若要在 Visual Studio 中分析 Linux *.nettrace 文件,请将 *.nettrace 文件(除了其他必要文档)传输到 Windows 计算机,然后在 Visual Studio 中打开 *.nettrace 文件。 有关详细信息,请参阅 分析 CPU 使用情况数据

另请参阅

后续步骤