本文适用于:✔️ .NET Core 3.1 SDK 及更高版本
本教程介绍如何调试过多的 CPU 使用率方案。 使用提供的示例 ASP.NET Core Web 应用 源代码存储库,可以有意造成死锁。 终结点将停止响应并体验线程累积。 你将了解如何使用各种工具来诊断此方案,其中包含多个关键诊断数据。
在本教程中,你将:
- 调查高 CPU 使用率
- 使用 dotnet-counters 确定 CPU 使用率
- 使用 dotnet-trace 生成跟踪
- PerfView 中的配置文件性能
- 诊断并解决 CPU 使用率过高的问题
先决条件
本教程使用:
- .NET Core 3.1 SDK 或更高版本。
- 触发方案的示例调试目标。
- 用于列出进程并生成配置文件的 dotnet-trace。
- 用于监视 CPU 使用率的 dotnet-counters。
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 可在浏览器中查看以调查性能问题:
使用 Visual Studio 分析高 CPU 数据
可以在 Visual Studio 中分析所有 *.nettrace 文件。 若要在 Visual Studio 中分析 Linux *.nettrace 文件,请将 *.nettrace 文件(除了其他必要文档)传输到 Windows 计算机,然后在 Visual Studio 中打开 *.nettrace 文件。 有关详细信息,请参阅 分析 CPU 使用情况数据。
另请参阅
- 用于列出进程的 dotnet-trace
- 用于检查托管内存使用情况的 dotnet-counters
- 用于收集和分析转储文件的 dotnet-dump
- dotnet/diagnostics