mcpField
D
dotnet-profiling

dotnet-profiling

github.com/wshaddix开发8677 次安装35 周前更新
代码仓库

关于 dotnet-profiling

dotnet-profiling 是用于诊断 .NET 应用性能问题的技能。它讲解 dotnet-counters、dotnet-trace 和 dotnet-dump 的使用,重点在于解读指标、火焰图和堆转储。

主要功能

  1. 01使用 dotnet-counters 监控运行时指标
  2. 02采集事件跟踪和 CPU 配置文件
  3. 03转换跟踪数据以分析火焰图
  4. 04跟踪分配并关联 GC 数据
  5. 05捕获并检查托管内存转储

适用场景

  1. 01判断性能问题受 CPU、内存还是 I/O 限制
  2. 02从 CPU 火焰图定位热点路径
  3. 03调查分配压力和 GC 活动
  4. 04追踪疑似内存泄漏中的对象引用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-profiling

安装后在 Claude Code 中输入/dotnet-profiling确认已加载。

详细介绍

dotnet-profiling

用于调查 .NET 性能问题的诊断工具指南。涵盖使用 dotnet-counters 实时监控指标、使用 dotnet-trace 进行事件跟踪并生成火焰图,以及使用 dotnet-dump 捕获和分析内存转储。重点介绍如何解读性能分析数据(读取火焰图、分析堆转储、关联 GC 指标),而不只是调用工具。

**版本假设:**以 .NET SDK 8.0+ 为基准。所有三种诊断工具(dotnet-counters、dotnet-trace、dotnet-dump)均随 .NET SDK 提供,无需单独安装。

**不在范围内:**OpenTelemetry 指标收集和分布式跟踪设置——请参阅 [skill:dotnet-observability]。微基准测试设置(BenchmarkDotNet)由本 Epic 的配套技能负责——请参阅 [skill:dotnet-benchmarkdotnet]。性能架构模式(Span<T>、ArrayPool、sealed 去虚拟化)由本 Epic 的配套技能负责——请参阅 [skill:dotnet-performance-patterns]。在 CI 中持续检测基准测试回归——请参阅 [skill:dotnet-ci-benchmarking]。架构模式(缓存、弹性)——请参阅 [skill:dotnet-architecture-patterns]。

交叉引用:[skill:dotnet-observability],了解 GC/线程池指标解读和 OpenTelemetry 关联;[skill:dotnet-benchmarkdotnet],了解在性能分析确定热点路径后进行结构化基准测试;[skill:dotnet-performance-patterns],了解如何根据性能分析结果应用优化模式。


dotnet-counters -- 实时指标监控

概述

dotnet-counters 无需修改应用程序代码,即可实时监控 .NET 运行时指标。可将其用作初步分诊工具,在使用更复杂的检测工具之前,先判断性能问题是受 CPU、内存还是 I/O 限制。

监控正在运行的进程
# List running .NET processes
dotnet-counters ps

# Monitor default runtime counters for a process
dotnet-counters monitor --process-id <PID>

# Monitor with a specific refresh interval (seconds)
dotnet-counters monitor --process-id <PID> --refresh-interval 2
主要内置计数器提供程序
提供程序计数器可反映的信息
System.RuntimeCPU 使用率、GC 堆大小、Gen 0/1/2 回收次数、线程池队列长度、异常计数运行时整体状况
Microsoft.AspNetCore.Hosting请求速率、请求持续时间、活动请求数HTTP 请求吞吐量和延迟
Microsoft.AspNetCore.Http.Connections连接持续时间、当前连接数WebSocket/SignalR 连接负载
System.Net.Http已启动/失败的请求数、活动请求数、连接池大小出站 HTTP 客户端行为
System.Net.Sockets已发送/接收的字节数、数据报、连接数网络 I/O 量
监控特定提供程序
# Monitor runtime and ASP.NET counters together
dotnet-counters monitor --process-id <PID> \
  --counters System.Runtime,Microsoft.AspNetCore.Hosting

# Monitor only GC-related counters
dotnet-counters monitor --process-id <PID> \
  --counters System.Runtime[gc-heap-size,gen-0-gc-count,gen-1-gc-count,gen-2-gc-count]
自定义 EventCounters

应用程序可以发布自定义计数器,用于领域特定指标:

using System.Diagnostics.Tracing;

[EventSource(Name = "MyApp.Orders")]
public sealed class OrderMetrics : EventSource
{
    public static readonly OrderMetrics Instance = new();

    private EventCounter? _orderProcessingTime;
    private IncrementingEventCounter? _ordersProcessed;

    private OrderMetrics()
    {
        _orderProcessingTime = new EventCounter("order-processing-time", this)
        {
            DisplayName = "Order Processing Time (ms)",
            DisplayUnits = "ms"
        };
        _ordersProcessed = new IncrementingEventCounter("orders-processed", this)
        {
            DisplayName = "Orders Processed",
            DisplayRateTimeScale = TimeSpan.FromSeconds(1)
        };
    }

    public void RecordProcessingTime(double milliseconds)
        => _orderProcessingTime?.WriteMetric(milliseconds);

    public void RecordOrderProcessed()
        => _ordersProcessed?.Increment();

    protected override void Dispose(bool disposing)
    {
        _orderProcessingTime?.Dispose();
        _ordersProcessed?.Dispose();
        base.Dispose(disposing);
    }
}

监控自定义计数器:

dotnet-counters monitor --process-id <PID> --counters MyApp.Orders
解读计数器数据

利用计数器值指导后续调查。关于如何将这些运行时指标与 OpenTelemetry 跟踪关联,请参阅 [skill:dotnet-observability]:

症状计数器证据下一步
CPU 使用率高cpu-usage > 80%,threadpool-queue-length 较低使用 dotnet-trace 进行 CPU 性能分析
内存增长gc-heap-size 持续增加,频繁发生 Gen 2 GC使用 dotnet-dump 获取内存转储
线程饥饿threadpool-queue-length 持续增长,threadpool-thread-count 达到最大值检查是否存在 sync-over-async 或阻塞调用
请求延迟request-duration 较高,active-requests 正常使用 dotnet-trace 跟踪单个请求
GC 暂停gen-2-gc-count 较高,time-in-gc > 10%使用 dotnet-trace gc-collect 进行分配性能分析
导出计数器数据
# Export to CSV for analysis
dotnet-counters collect --process-id <PID> \
  --format csv \
  --output counters.csv \
  --counters System.Runtime

# Export to JSON for programmatic consumption
dotnet-counters collect --process-id <PID> \
  --format json \
  --output counters.json

dotnet-trace -- 事件跟踪与火焰图

概述

dotnet-trace 可捕获正在运行的 .NET 进程的详细事件跟踪。可将跟踪数据分析为火焰图,以识别 CPU 热点路径;也可配置为跟踪内存分配,以找出 GC 压力的来源。

CPU 采样

CPU 采样以固定间隔记录堆栈帧,从而构建应用程序耗时分布的统计画像:

# Collect a CPU sampling trace (default profile)
dotnet-trace collect --process-id <PID> --duration 00:00:30

# Collect with the cpu-sampling profile (explicit)
dotnet-trace collect --process-id <PID> \
  --profile cpu-sampling \
  --output cpu-trace.nettrace
CPU 采样与插桩
方式开销最适合的场景工具
CPU 采样低(约 2-5%)在生产环境中查找 CPU 热点路径dotnet-trace --profile cpu-sampling
插桩高(10-50%+)精确统计调用次数、方法进入/退出时长Rider/VS 性能分析器、PerfView

由于开销低,CPU 采样适合在生产环境中使用。请将其作为默认方式。仅在需要精确统计调用次数的开发环境中使用插桩。

生成火焰图

必须将跟踪文件(.nettrace)转换为火焰图格式,才能进行可视化分析:

使用 Speedscope(基于浏览器,推荐):

# Convert to Speedscope format
dotnet-trace convert cpu-trace.nettrace --format Speedscope

# Opens cpu-trace.speedscope.json -- load at https://www.speedscope.app/

使用 PerfView(Windows,深度集成 .NET):

# Convert to Chromium trace format (also viewable in chrome://tracing)
dotnet-trace convert cpu-trace.nettrace --format Chromium
阅读火焰图

火焰图展示调用堆栈,其中:

  • 宽度表示某个函数占总采样时间的比例(越宽 = 耗时越长)
  • 高度表示调用堆栈深度(堆栈越高 = 调用链越深)
  • 颜色通常是任意的(没有实际含义),除非工具使用了特定的配色方案

分析工作流:

  1. 查找宽阔的平台——占用大量采样时间的函数
  2. 沿最宽的帧向上查找耗时最多的调用方
  3. 识别异常宽度——本应快速执行的框架方法如果显示较宽,说明存在误用
  4. 对比优化前后的跟踪结果,验证目标函数的宽度是否有所缩减

.NET 火焰图中的常见模式:

模式可能原因调查方法
较宽的 System.Linq 帧热点路径大量使用 LINQ,并产生委托开销替换为 foreach 循环或基于 Span 的处理
较宽的 JIT_New / gc_heap::allocate分配过多,导致触发 GC使用 --profile gc-collect 进行分配分析
较宽的 Monitor.Enter / SpinLock锁竞争检查同步策略
较宽的 System.Text.RegularExpressions正则表达式回溯使用 RegexOptions.NonBacktracking 或编译正则表达式
深层异步状态机帧紧密循环中的异步开销考虑为 CPU 密集型工作使用同步路径
使用 gc-collect 配置文件跟踪分配

gc-collect 配置文件会捕获分配事件,以识别哪些代码路径分配的内存最多:

# Collect allocation data
dotnet-trace collect --process-id <PID> \
  --profile gc-collect \
  --duration 00:00:30 \
  --output alloc-trace.nettrace

生成的跟踪文件会显示:

  • 分配字节数最多的方法
  • 分配频率最高的类型
  • 分配大小及触发分配的调用堆栈

将分配数据与 dotnet-counters 中的 GC 计数器证据关联起来。如果 gen-2-gc-count 较高,分配跟踪会显示哪些代码路径生成了存活时间较长、能存活到 Gen 2 的对象。请参阅 [skill:dotnet-performance-patterns],了解识别出分配热点后可应用的零分配模式。

自定义跟踪提供程序

针对特定事件提供程序进行跟踪,以聚焦于特定内容:

# Trace specific providers with keywords and verbosity
dotnet-trace collect --process-id <PID> \
  --providers "Microsoft-Diagnostics-DiagnosticSource:::FilterAndPayloadSpecs=[AS]System.Net.Http"

# Trace EF Core queries (useful with [skill:dotnet-efcore-patterns])
dotnet-trace collect --process-id <PID> \
  --providers Microsoft.EntityFrameworkCore

# Trace ASP.NET Core request processing
dotnet-trace collect --process-id <PID> \
  --providers Microsoft.AspNetCore
跟踪文件管理
格式扩展名查看器跨平台
NetTrace.nettracePerfView、VS、dotnet-trace convert是(采集);Windows(PerfView)
Speedscope.speedscope.jsonhttps://www.speedscope.app/是
Chromium.chromium.jsonChrome DevTools (chrome://tracing)是

dotnet-dump — 内存转储分析

概述

dotnet-dump 用于捕获和分析进程内存转储。可用于调查内存泄漏、大对象堆碎片以及对象引用链。与 dotnet-trace 不同,转储会捕获整个托管堆在某一时刻的快照。

捕获转储
# Capture a full heap dump
dotnet-dump collect --process-id <PID> --output app-dump.dmp

# Capture a minimal dump (faster, smaller, but less detail)
dotnet-dump collect --process-id <PID> --type Mini --output app-mini.dmp

适用情况:

  • 内存使用量已超过预期基线(与 dotnet-counters gc-heap-size 进行比较)
  • 应用程序即将出现 OOM
  • 负载测试后怀疑存在内存泄漏
  • 调查终结器队列积压
使用 SOS 命令分析转储

在交互式分析器中打开转储:

dotnet-dump analyze app-dump.dmp
!dumpheap -- 堆对象摘要

按类型对托管堆上的对象进行分组,并按总大小排序:

> dumpheap -stat

Statistics:
              MT    Count    TotalSize Class Name
00007fff2c6a4320      125        4,000 System.String[]
00007fff2c6a1230    8,432      269,824 System.String
00007fff2c7b5640    2,100      504,000 MyApp.Models.OrderEntity
00007fff2c6a0988   15,230    1,218,400 System.Byte[]

分析方法:

  1. 查找数量或大小异常高的应用程序类型
  2. 将数量与预期基数进行比较(例如,有 2,100 个 OrderEntity 对象——这是否符合当前负载的预期?)
  3. 大量 System.Byte[] 通常表示存在无界缓冲或流处理问题

按类型筛选:

> dumpheap -type MyApp.Models.OrderEntity
> dumpheap -type System.Byte[] -min 85000

-min 85000 筛选器会显示大对象堆条目(对象大小 >= 85,000 字节,会造成第 2 代 GC 压力)。

!gcroot -- 查找对象保留链

追踪从 GC 根到特定对象的引用链,说明该对象为何未被回收:

> gcroot 00007fff3c4a2100

HandleTable:
    00007fff3c010010 (strong handle)
        -> 00007fff3c3a1000 MyApp.Services.CacheService
            -> 00007fff3c3a1020 System.Collections.Generic.Dictionary`2
                -> 00007fff3c4a2100 MyApp.Models.OrderEntity

Found 1 unique root(s).

常见根类型及其含义:

根类型含义可能的问题
strong handle静态字段或 GC 句柄静态集合持续增长且未进行清理
pinned handle为原生互操作而固定缓冲区固定时间过长
async state machine在异步闭包中捕获长时间运行的异步操作持有引用
finalizer queue等待终结器线程处理终结器队列积压,阻碍回收
threadpool从线程本地存储中引用线程静态缓存未清理
!finalizequeue -- 终结器队列分析

显示等待终结的对象,这会使它们至少延迟一个 GC 周期才被回收:

> finalizequeue

SyncBlocks to be cleaned up: 0
Free-Threaded Interfaces to be released: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 12 finalizable objects
generation 1 has 45 finalizable objects
generation 2 has 230 finalizable objects
Ready for finalization 8 objects

关键指标:

  • “Ready for finalization”中的数量较高,表示终结器线程处理速度跟不上
  • 第 2 代可终结对象列表中的对象开销较大——它们至少要经历两个 GC 周期(一个用于安排终结,另一个用于终结器运行后进行回收)
  • 未调用 IDisposable.Dispose() 的 ~Destructor() 实现是主要原因
用于堆分析的其他 SOS 命令
命令用途适用情况
dumpobj <address>显示特定对象的字段值使用 dumpheap 找到对象后检查其状态
dumparray <address>显示数组内容调查堆统计中发现的大型数组
eeheap -gc显示 GC 堆段布局调查 LOH 碎片
gcwhere <address>显示对象所在的 GC 代确定对象是否已固定或位于 LOH
dumpmt <MT>显示方法表详细信息调查类型元数据
threads列出所有托管线程及其堆栈跟踪识别死锁或阻塞
clrstack显示当前线程的托管调用堆栈将线程状态与堆数据关联起来
内存泄漏调查工作流
  1. 基线: 在应用程序启动并完成初始预热后捕获转储
  2. 负载: 运行疑似导致泄漏的工作负载场景
  3. 比较: 工作负载完成后捕获第二份转储
  4. 差异: 比较两份转储中的 dumpheap -stat 输出——查找数量或总大小显著增长的类型
  5. 根因: 对增长类型的实例使用 gcroot 查找保留链
  6. 修复: 断开保留链(从静态集合中移除、释放事件订阅、修复异步生命周期问题)
# Tip: save dumpheap output for comparison
# In dump 1:
> dumpheap -stat > /tmp/heap-before.txt
# In dump 2:
> dumpheap -stat > /tmp/heap-after.txt
# Compare externally:
# diff /tmp/heap-before.txt /tmp/heap-after.txt

性能分析工作流总结

按照结构化的调查工作流使用诊断工具:

1. dotnet-counters (triage)
   ├── CPU high?         → dotnet-trace --profile cpu-sampling
   │                       → Convert to flame graph (Speedscope)
   │                       → Identify hot methods
   ├── Memory growing?   → dotnet-dump collect
   │                       → dumpheap -stat (find large/numerous types)
   │                       → gcroot (find retention chains)
   │                       → Fix retention + verify with second dump
   ├── GC pressure?      → dotnet-trace --profile gc-collect
   │                       → Identify allocation hot paths
   │                       → Apply zero-alloc patterns [skill:dotnet-performance-patterns]
   └── Thread starvation? → dotnet-dump analyze
                            → threads (list all managed threads)
                            → clrstack (check for blocking calls)

性能分析确定瓶颈后,使用 [skill:dotnet-benchmarkdotnet] 创建有针对性的基准测试,量化修复带来的改进。


Agent 常见问题

  1. 从 dotnet-counters 开始,而不是 dotnet-trace —— 计数器的开销几乎为零,并能识别问题类别(CPU、内存、线程)。只有在计数器缩小调查范围后,才使用 trace 或 dump。
  2. 在生产环境中使用 CPU 采样(而不是插桩) —— 采样开销为 2-5%,适用于生产环境。插桩会增加 10-50% 以上的开销,应仅限于开发环境。
  3. 始终将 trace 转换为火焰图进行分析 —— 阅读原始 .nettrace 事件日志并不现实。使用 dotnet-trace convert --format Speedscope,并在 https://www.speedscope.app/ 中打开进行可视化分析。
  4. 调查内存泄漏时捕获两份 dump —— 单份 dump 只能显示当前状态,无法区分正常驻留对象与泄漏对象。比较疑似泄漏场景发生前后捕获的两份 dump 的堆统计信息。
  5. 使用 -min 85000 筛选 dumpheap,以查找 LOH 对象 —— 大于等于 85,000 字节的对象会进入大型对象堆(LOH),该堆只会在 Gen 2 GC 时回收。LOH 对象数量较多可能表明存在内存碎片。
  6. 结合 [skill:dotnet-observability] 解读 GC 计数器数据 —— 运行时 GC/线程池计数器与 OpenTelemetry 指标有所重叠。使用可观测性技能,将性能分析结果与分布式 trace 上下文关联起来。
  7. 不要混淆 dotnet-trace gc-collect 和 dotnet-dump —— gc-collect 会跟踪一段时间内的分配事件(哪些方法执行了分配);dotnet-dump 会捕获某一时刻的堆快照(存在哪些对象)。使用 gc-collect 分析分配速率;使用 dotnet-dump 分析对象保留情况和内存泄漏。

评论

支持 Markdown