dotnet-profiling
Diagnostic tool guidance for investigating .NET performance problems. Covers real-time metric monitoring with
dotnet-counters, event tracing and flame graph generation with dotnet-trace, and memory dump capture and analysis with
dotnet-dump. Focuses on interpreting profiling data (reading flame graphs, analyzing heap dumps, correlating GC metrics)
rather than just invoking tools.
Version assumptions: .NET SDK 8.0+ baseline. All three diagnostic tools (dotnet-counters, dotnet-trace, dotnet-dump)
ship with the .NET SDK -- no separate installation required.
Scope
- Real-time metric monitoring with dotnet-counters
- Event tracing and flame graph generation with dotnet-trace
- Memory dump capture and analysis with dotnet-dump
- Interpreting profiling data (flame graphs, heap dumps, GC metrics)
Out of scope
- OpenTelemetry metrics and distributed tracing -- see [skill:dotnet-observability]
- Microbenchmarking setup (BenchmarkDotNet) -- see [skill:dotnet-benchmarkdotnet]
- Performance architecture patterns (Span, ArrayPool, sealed) -- see [skill:dotnet-performance-patterns]
- Continuous benchmark regression detection in CI -- see [skill:dotnet-ci-benchmarking]
- Architecture patterns (caching, resilience) -- see [skill:dotnet-architecture-patterns]
Cross-references: [skill:dotnet-observability] for GC/threadpool metrics interpretation and OpenTelemetry correlation,
[skill:dotnet-benchmarkdotnet] for structured benchmarking after profiling identifies hot paths,
[skill:dotnet-performance-patterns] for optimization patterns to apply based on profiling results.
dotnet-counters -- Real-Time Metric Monitoring
Overview
dotnet-counters provides real-time monitoring of .NET runtime metrics without modifying application code. Use it as a
first-pass triage tool to identify whether a performance problem is CPU-bound, memory-bound, or I/O-bound before
reaching for heavier instrumentation.
Monitoring Running Processes
# 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
```text
### Key Built-In Counter Providers
| Provider | Counters | What It Tells You |
|----------|----------|-------------------|
| `System.Runtime` | CPU usage, GC heap size, Gen 0/1/2 collections, threadpool queue length, exception count | Overall runtime health |
| `Microsoft.AspNetCore.Hosting` | Request rate, request duration, active requests | HTTP request throughput and latency |
| `Microsoft.AspNetCore.Http.Connections` | Connection duration, current connections | WebSocket/SignalR connection load |
| `System.Net.Http` | Requests started/failed, active requests, connection pool size | Outbound HTTP client behavior |
| `System.Net.Sockets` | Bytes sent/received, datagrams, connections | Network I/O volume |
### Monitoring Specific Providers
```bash
# 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]
```text
### Custom EventCounters
Applications can publish custom counters for domain-specific metrics:
```csharp
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);
}
}
```text
Monitor custom counters:
```bash
dotnet-counters monitor --process-id <PID> --counters MyApp.Orders
```bash
### Interpreting Counter Data
Use counter values to direct further investigation. See [skill:dotnet-observability] for correlating these runtime metrics with OpenTelemetry traces:
| Symptom | Counter Evidence | Next Step |
|---------|------------------|-----------|
| High CPU usage | `cpu-usage` > 80%, `threadpool-queue-length` low | CPU profiling with dotnet-trace |
| Memory growth | `gc-heap-size` increasing, frequent Gen 2 GC | Memory dump with dotnet-dump |
| Thread starvation | `threadpool-queue-length` growing, `threadpool-thread-count` at max | Check for sync-over-async or blocking calls |
| Request latency | `request-duration` high, `active-requests` normal | Trace individual requests with dotnet-trace |
| GC pauses | High `gen-2-gc-count`, `time-in-gc` > 10% | Allocation profiling with dotnet-trace gc-collect |
### Exporting Counter Data
```bash
# 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
```json
---
## dotnet-trace -- Event Tracing and Flame Graphs
### Overview
`dotnet-trace` captures detailed event traces from a running .NET process. Traces can be analyzed as flame graphs to identify CPU hot paths, or configured for allocation tracking to find GC pressure sources.
### CPU Sampling
CPU sampling records stack frames at a fixed interval to build a statistical profile of where the application spends time:
```bash
# 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
```text
### CPU Sampling vs Instrumentation
| Approach | Overhead | Best For | Tool |
|----------|----------|----------|------|
| CPU sampling | Low (~2-5%) | Finding CPU hot paths in production | dotnet-trace `--profile cpu-sampling` |
| Instrumentation | High (10-50%+) | Exact call counts, method entry/exit timing | Rider/VS profiler, PerfView |
CPU sampling is safe for production use due to low overhead. Use it as the default approach. Reserve instrumentation for development environments where exact call counts matter.
### Flame Graph Generation
Trace files (`.nettrace`) must be converted to a flame graph format for visual analysis:
**Using Speedscope (browser-based, recommended):**
```bash
# Convert to Speedscope format
dotnet-trace convert cpu-trace.nettrace --format Speedscope
# Opens cpu-trace.speedscope.json -- load at https://www.speedscope.app/
```json
**Using PerfView (Windows, deep .NET integration):**
```bash
# Convert to Chromium trace format (also viewable in chrome://tracing)
dotnet-trace convert cpu-trace.nettrace --format Chromium
```bash
### Reading Flame Graphs
Flame graphs display call stacks where:
- **Width** of a frame represents the proportion of total sample time spent in that function (wider = more time)
- **Height** represents call stack depth (taller stacks = deeper call chains)
- **Color** is typically arbitrary (not meaningful) unless the tool uses a specific color scheme
**Analysis workflow:**
1. Look for **wide plateaus** -- functions that consume a large proportion of samples
2. Follow the widest frames **upward** to find which callers contribute the most time
3. Identify **unexpected width** -- framework methods that should be fast appearing wide indicate misuse
4. Compare **before/after** traces to validate optimizations reduced the width of target functions
**Common patterns in .NET flame graphs:**
| Pattern | Likely Cause | Investigation |
|---------|-------------|---------------|
| Wide `System.Linq` frames | LINQ-heavy hot path with delegate overhead | Replace with foreach loops or Span-based processing |
| Wide `JIT_New` / `gc_heap::allocate` | Excessive allocations triggering GC | Allocation profiling with `--profile gc-collect` |
| Wide `Monitor.Enter` / `SpinLock` | Lock contention | Review synchronization strategy |
| Wide `System.Text.RegularExpressions` | Regex backtracking | Use `RegexOptions.NonBacktracking` or compile regex |
| Deep async state machine frames | Async overhead in tight loops | Consider sync path for CPU-bound work |
### Allocation Tracking with gc-collect Profile
The `gc-collect` profile captures allocation events to identify what code paths allocate the most memory:
```bash
# Collect allocation data
dotnet-trace collect --process-id <PID> \
--profile gc-collect \
--duration 00:00:30 \
--output alloc-trace.nettrace
```text
This produces a trace that shows:
- Which methods allocate the most bytes
- Which types are allocated most frequently
- Allocation sizes and the call stacks that trigger them
Correlate allocation data with GC counter evidence from dotnet-counters. If `gen-2-gc-count` is high, the allocation trace shows which code paths produce long-lived objects that survive to Gen 2. See [skill:dotnet-performance-patterns] for zero-allocation patterns to apply once hot allocation sites are identified.
### Custom Trace Providers
Target specific event providers for focused tracing:
```bash
# 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
```text
### Trace File Management
| Format | Extension | Viewer | Cross-Platform |
|--------|-----------|--------|----------------|
| NetTrace | `.nettrace` | PerfView, VS, dotnet-trace convert | Yes (capture); Windows (PerfView) |
| Speedscope | `.speedscope.json` | https://www.speedscope.app/ | Yes |
| Chromium | `.chromium.json` | Chrome DevTools (chrome://tracing) | Yes |
---
## dotnet-dump -- Memory Dump Analysis
### Overview
`dotnet-dump` captures and analyzes process memory dumps. Use it to investigate memory leaks, large object heap fragmentation, and object reference chains. Unlike dotnet-trace, dumps capture a point-in-time snapshot of the entire managed heap.
### Capturing Dumps
```bash
# 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
```text
**When to capture:**
- Memory usage has grown beyond expected baseline (compare against dotnet-counters `gc-heap-size`)
- Application is approaching OOM conditions
- Suspected memory leak after load testing
- Investigating finalizer queue backlog
### Analyzing Dumps with SOS Commands
Open the dump in the interactive analyzer:
```bash
dotnet-dump analyze app-dump.dmp
```bash
### !dumpheap -- Heap Object Summary
Lists objects on the managed heap grouped by type, sorted by total size:
```text
> 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[]
```text
**Analysis approach:**
1. Look for unexpectedly high counts or sizes for application types
2. Compare counts against expected cardinality (e.g., 2,100 OrderEntity objects -- is that expected for current load?)
3. Large `System.Byte[]` counts often indicate unbounded buffering or stream handling issues
Filter by type:
```text
> dumpheap -type MyApp.Models.OrderEntity
> dumpheap -type System.Byte[] -min 85000
```text
The `-min 85000` filter shows Large Object Heap entries (objects >= 85,000 bytes that cause Gen 2 GC pressure).
### !gcroot -- Finding Object Retention
Traces the reference chain from a GC root to a specific object, explaining why it is not collected:
```text
> gcroot 00007fff3c4a2100
HandleTable:
00007fff3c010010 (strong handle)
-> 00007fff3c3a1000 MyApp.Services.CacheService
-> 00007fff3c3a1020 System.Collections.Generic.Dictionary`2
-> 00007fff3c4a2100 MyApp.Models.OrderEntity
## Detailed Examples
See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.
1---2name: dotnet-profiling3category: performance4subcategory: profiling5description: Diagnoses .NET performance issues. dotnet-counters, dotnet-trace, dotnet-dump, flame graphs.6license: MIT7targets: ['*']8tags: [foundation, dotnet, skill]9version: '0.0.1'10author: 'dotnet-agent-harness'11invocable: true12claudecode:13 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']14codexcli:15 short-description: '.NET skill guidance for foundation tasks'16opencode:17 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']18copilot: {}19geminicli: {}20antigravity: {}21---2223# dotnet-profiling2425Diagnostic tool guidance for investigating .NET performance problems. Covers real-time metric monitoring with26dotnet-counters, event tracing and flame graph generation with dotnet-trace, and memory dump capture and analysis with27dotnet-dump. Focuses on interpreting profiling data (reading flame graphs, analyzing heap dumps, correlating GC metrics)28rather than just invoking tools.2930**Version assumptions:** .NET SDK 8.0+ baseline. All three diagnostic tools (dotnet-counters, dotnet-trace, dotnet-dump)31ship with the .NET SDK -- no separate installation required.3233## Scope3435- Real-time metric monitoring with dotnet-counters36- Event tracing and flame graph generation with dotnet-trace37- Memory dump capture and analysis with dotnet-dump38- Interpreting profiling data (flame graphs, heap dumps, GC metrics)3940## Out of scope4142- OpenTelemetry metrics and distributed tracing -- see [skill:dotnet-observability]43- Microbenchmarking setup (BenchmarkDotNet) -- see [skill:dotnet-benchmarkdotnet]44- Performance architecture patterns (Span<T>, ArrayPool, sealed) -- see [skill:dotnet-performance-patterns]45- Continuous benchmark regression detection in CI -- see [skill:dotnet-ci-benchmarking]46- Architecture patterns (caching, resilience) -- see [skill:dotnet-architecture-patterns]4748Cross-references: [skill:dotnet-observability] for GC/threadpool metrics interpretation and OpenTelemetry correlation,49[skill:dotnet-benchmarkdotnet] for structured benchmarking after profiling identifies hot paths,50[skill:dotnet-performance-patterns] for optimization patterns to apply based on profiling results.5152---5354## dotnet-counters -- Real-Time Metric Monitoring5556### Overview5758`dotnet-counters` provides real-time monitoring of .NET runtime metrics without modifying application code. Use it as a59first-pass triage tool to identify whether a performance problem is CPU-bound, memory-bound, or I/O-bound before60reaching for heavier instrumentation.6162### Monitoring Running Processes6364````bash6566# List running .NET processes67dotnet-counters ps6869# Monitor default runtime counters for a process70dotnet-counters monitor --process-id <PID>7172# Monitor with a specific refresh interval (seconds)73dotnet-counters monitor --process-id <PID> --refresh-interval 27475```text7677### Key Built-In Counter Providers7879| Provider | Counters | What It Tells You |80|----------|----------|-------------------|81| `System.Runtime` | CPU usage, GC heap size, Gen 0/1/2 collections, threadpool queue length, exception count | Overall runtime health |82| `Microsoft.AspNetCore.Hosting` | Request rate, request duration, active requests | HTTP request throughput and latency |83| `Microsoft.AspNetCore.Http.Connections` | Connection duration, current connections | WebSocket/SignalR connection load |84| `System.Net.Http` | Requests started/failed, active requests, connection pool size | Outbound HTTP client behavior |85| `System.Net.Sockets` | Bytes sent/received, datagrams, connections | Network I/O volume |8687### Monitoring Specific Providers8889```bash9091# Monitor runtime and ASP.NET counters together92dotnet-counters monitor --process-id <PID> \93 --counters System.Runtime,Microsoft.AspNetCore.Hosting9495# Monitor only GC-related counters96dotnet-counters monitor --process-id <PID> \97 --counters System.Runtime[gc-heap-size,gen-0-gc-count,gen-1-gc-count,gen-2-gc-count]9899```text100101### Custom EventCounters102103Applications can publish custom counters for domain-specific metrics:104105```csharp106107using System.Diagnostics.Tracing;108109[EventSource(Name = "MyApp.Orders")]110public sealed class OrderMetrics : EventSource111{112 public static readonly OrderMetrics Instance = new();113114 private EventCounter? _orderProcessingTime;115 private IncrementingEventCounter? _ordersProcessed;116117 private OrderMetrics()118 {119 _orderProcessingTime = new EventCounter("order-processing-time", this)120 {121 DisplayName = "Order Processing Time (ms)",122 DisplayUnits = "ms"123 };124 _ordersProcessed = new IncrementingEventCounter("orders-processed", this)125 {126 DisplayName = "Orders Processed",127 DisplayRateTimeScale = TimeSpan.FromSeconds(1)128 };129 }130131 public void RecordProcessingTime(double milliseconds)132 => _orderProcessingTime?.WriteMetric(milliseconds);133134 public void RecordOrderProcessed()135 => _ordersProcessed?.Increment();136137 protected override void Dispose(bool disposing)138 {139 _orderProcessingTime?.Dispose();140 _ordersProcessed?.Dispose();141 base.Dispose(disposing);142 }143}144145```text146147Monitor custom counters:148149```bash150151dotnet-counters monitor --process-id <PID> --counters MyApp.Orders152153```bash154155### Interpreting Counter Data156157Use counter values to direct further investigation. See [skill:dotnet-observability] for correlating these runtime metrics with OpenTelemetry traces:158159| Symptom | Counter Evidence | Next Step |160|---------|------------------|-----------|161| High CPU usage | `cpu-usage` > 80%, `threadpool-queue-length` low | CPU profiling with dotnet-trace |162| Memory growth | `gc-heap-size` increasing, frequent Gen 2 GC | Memory dump with dotnet-dump |163| Thread starvation | `threadpool-queue-length` growing, `threadpool-thread-count` at max | Check for sync-over-async or blocking calls |164| Request latency | `request-duration` high, `active-requests` normal | Trace individual requests with dotnet-trace |165| GC pauses | High `gen-2-gc-count`, `time-in-gc` > 10% | Allocation profiling with dotnet-trace gc-collect |166167### Exporting Counter Data168169```bash170171# Export to CSV for analysis172dotnet-counters collect --process-id <PID> \173 --format csv \174 --output counters.csv \175 --counters System.Runtime176177# Export to JSON for programmatic consumption178dotnet-counters collect --process-id <PID> \179 --format json \180 --output counters.json181182```json183184---185186## dotnet-trace -- Event Tracing and Flame Graphs187188### Overview189190`dotnet-trace` captures detailed event traces from a running .NET process. Traces can be analyzed as flame graphs to identify CPU hot paths, or configured for allocation tracking to find GC pressure sources.191192### CPU Sampling193194CPU sampling records stack frames at a fixed interval to build a statistical profile of where the application spends time:195196```bash197198# Collect a CPU sampling trace (default profile)199dotnet-trace collect --process-id <PID> --duration 00:00:30200201# Collect with the cpu-sampling profile (explicit)202dotnet-trace collect --process-id <PID> \203 --profile cpu-sampling \204 --output cpu-trace.nettrace205206```text207208### CPU Sampling vs Instrumentation209210| Approach | Overhead | Best For | Tool |211|----------|----------|----------|------|212| CPU sampling | Low (~2-5%) | Finding CPU hot paths in production | dotnet-trace `--profile cpu-sampling` |213| Instrumentation | High (10-50%+) | Exact call counts, method entry/exit timing | Rider/VS profiler, PerfView |214215CPU sampling is safe for production use due to low overhead. Use it as the default approach. Reserve instrumentation for development environments where exact call counts matter.216217### Flame Graph Generation218219Trace files (`.nettrace`) must be converted to a flame graph format for visual analysis:220221**Using Speedscope (browser-based, recommended):**222223```bash224225# Convert to Speedscope format226dotnet-trace convert cpu-trace.nettrace --format Speedscope227228# Opens cpu-trace.speedscope.json -- load at https://www.speedscope.app/229230```json231232**Using PerfView (Windows, deep .NET integration):**233234```bash235236# Convert to Chromium trace format (also viewable in chrome://tracing)237dotnet-trace convert cpu-trace.nettrace --format Chromium238239```bash240241### Reading Flame Graphs242243Flame graphs display call stacks where:244245- **Width** of a frame represents the proportion of total sample time spent in that function (wider = more time)246- **Height** represents call stack depth (taller stacks = deeper call chains)247- **Color** is typically arbitrary (not meaningful) unless the tool uses a specific color scheme248249**Analysis workflow:**2502511. Look for **wide plateaus** -- functions that consume a large proportion of samples2522. Follow the widest frames **upward** to find which callers contribute the most time2533. Identify **unexpected width** -- framework methods that should be fast appearing wide indicate misuse2544. Compare **before/after** traces to validate optimizations reduced the width of target functions255256**Common patterns in .NET flame graphs:**257258| Pattern | Likely Cause | Investigation |259|---------|-------------|---------------|260| Wide `System.Linq` frames | LINQ-heavy hot path with delegate overhead | Replace with foreach loops or Span-based processing |261| Wide `JIT_New` / `gc_heap::allocate` | Excessive allocations triggering GC | Allocation profiling with `--profile gc-collect` |262| Wide `Monitor.Enter` / `SpinLock` | Lock contention | Review synchronization strategy |263| Wide `System.Text.RegularExpressions` | Regex backtracking | Use `RegexOptions.NonBacktracking` or compile regex |264| Deep async state machine frames | Async overhead in tight loops | Consider sync path for CPU-bound work |265266### Allocation Tracking with gc-collect Profile267268The `gc-collect` profile captures allocation events to identify what code paths allocate the most memory:269270```bash271272# Collect allocation data273dotnet-trace collect --process-id <PID> \274 --profile gc-collect \275 --duration 00:00:30 \276 --output alloc-trace.nettrace277278```text279280This produces a trace that shows:281282- Which methods allocate the most bytes283- Which types are allocated most frequently284- Allocation sizes and the call stacks that trigger them285286Correlate allocation data with GC counter evidence from dotnet-counters. If `gen-2-gc-count` is high, the allocation trace shows which code paths produce long-lived objects that survive to Gen 2. See [skill:dotnet-performance-patterns] for zero-allocation patterns to apply once hot allocation sites are identified.287288### Custom Trace Providers289290Target specific event providers for focused tracing:291292```bash293294# Trace specific providers with keywords and verbosity295dotnet-trace collect --process-id <PID> \296 --providers "Microsoft-Diagnostics-DiagnosticSource:::FilterAndPayloadSpecs=[AS]System.Net.Http"297298# Trace EF Core queries (useful with [skill:dotnet-efcore-patterns])299dotnet-trace collect --process-id <PID> \300 --providers Microsoft.EntityFrameworkCore301302# Trace ASP.NET Core request processing303dotnet-trace collect --process-id <PID> \304 --providers Microsoft.AspNetCore305306```text307308### Trace File Management309310| Format | Extension | Viewer | Cross-Platform |311|--------|-----------|--------|----------------|312| NetTrace | `.nettrace` | PerfView, VS, dotnet-trace convert | Yes (capture); Windows (PerfView) |313| Speedscope | `.speedscope.json` | https://www.speedscope.app/ | Yes |314| Chromium | `.chromium.json` | Chrome DevTools (chrome://tracing) | Yes |315316---317318## dotnet-dump -- Memory Dump Analysis319320### Overview321322`dotnet-dump` captures and analyzes process memory dumps. Use it to investigate memory leaks, large object heap fragmentation, and object reference chains. Unlike dotnet-trace, dumps capture a point-in-time snapshot of the entire managed heap.323324### Capturing Dumps325326```bash327328# Capture a full heap dump329dotnet-dump collect --process-id <PID> --output app-dump.dmp330331# Capture a minimal dump (faster, smaller, but less detail)332dotnet-dump collect --process-id <PID> --type Mini --output app-mini.dmp333334```text335336**When to capture:**337338- Memory usage has grown beyond expected baseline (compare against dotnet-counters `gc-heap-size`)339- Application is approaching OOM conditions340- Suspected memory leak after load testing341- Investigating finalizer queue backlog342343### Analyzing Dumps with SOS Commands344345Open the dump in the interactive analyzer:346347```bash348349dotnet-dump analyze app-dump.dmp350351```bash352353### !dumpheap -- Heap Object Summary354355Lists objects on the managed heap grouped by type, sorted by total size:356357```text358359> dumpheap -stat360361Statistics:362 MT Count TotalSize Class Name36300007fff2c6a4320 125 4,000 System.String[]36400007fff2c6a1230 8,432 269,824 System.String36500007fff2c7b5640 2,100 504,000 MyApp.Models.OrderEntity36600007fff2c6a0988 15,230 1,218,400 System.Byte[]367368```text369370**Analysis approach:**3713721. Look for unexpectedly high counts or sizes for application types3732. Compare counts against expected cardinality (e.g., 2,100 OrderEntity objects -- is that expected for current load?)3743. Large `System.Byte[]` counts often indicate unbounded buffering or stream handling issues375376Filter by type:377378```text379380> dumpheap -type MyApp.Models.OrderEntity381> dumpheap -type System.Byte[] -min 85000382383```text384385The `-min 85000` filter shows Large Object Heap entries (objects >= 85,000 bytes that cause Gen 2 GC pressure).386387### !gcroot -- Finding Object Retention388389Traces the reference chain from a GC root to a specific object, explaining why it is not collected:390391```text392393> gcroot 00007fff3c4a2100394395HandleTable:396 00007fff3c010010 (strong handle)397 -> 00007fff3c3a1000 MyApp.Services.CacheService398 -> 00007fff3c3a1020 System.Collections.Generic.Dictionary`2399 -> 00007fff3c4a2100 MyApp.Models.OrderEntity400401402## Detailed Examples403404See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.