
Profiling
- 16 installs
- 466 repo stars
- Updated July 25, 2026
- managedcode/dotnet-skills
Helps with ai & agent building tasks.
About
profiling is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- profiling
- AI & Agent Building
- AI-coding skill
Profiling by the numbers
- 16 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #11,040 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/managedcode/dotnet-skills --skill profilingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 16 |
|---|---|
| repo stars | ★ 466 |
| Last updated | July 25, 2026 |
| Repository | managedcode/dotnet-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
.NET Profiling
Trigger On
- the repo needs performance or runtime profiling for a .NET application
- the user asks about slow code, high CPU, GC pressure, allocation growth, exception storms, lock contention, or startup diagnostics
- the team wants official CLI-based diagnostics without depending on
dnx
Value
- produce a concrete project delta: code, docs, config, tests, CI, or review artifact
- reduce ambiguity through explicit planning, verification, and final validation skills
- leave reusable project context so future tasks are faster and safer
Do Not Use For
- replacing realistic performance tests or load tests with ad-hoc tracing alone
- production heap collection when the pause risk has not been accepted
- GUI-only workflows that the repo cannot automate or document
Inputs
- the nearest
AGENTS.md - target application, process, or startup path
- the symptom being investigated: CPU, memory, GC, contention, exceptions, or startup
Quick Start
1. Read the nearest AGENTS.md and confirm scope and constraints. 2. Run this skill's Workflow through the Ralph Loop until outcomes are acceptable. 3. Return the Required Result Format with concrete artifacts and verification evidence.
Workflow
1. Build and run a realistic target first:
- prefer
Release - prefer realistic config, inputs, and data volume
2. Start with the lightest useful tool:
dotnet-countersfor live health signalsdotnet-tracefor CPU, exception, contention, GC, and startup tracesdotnet-gcdumpfor managed heap inspection when memory shape matters
3. Prefer installed CLI tools over dnx one-shot execution so the repo commands stay stable and reproducible. 4. Capture one focused profile at a time instead of mixing every signal into one run. 5. For CPU and general runtime hotspots, start with dotnet-trace collect. 6. For live triage, start with dotnet-counters monitor on System.Runtime. 7. For heap analysis, use dotnet-gcdump carefully and document the pause risk. 8. After each change, rerun the same measurement path and compare before versus after.
Bootstrap When Missing
If official .NET profiling tools are not available yet:
1. Detect current state:
dotnet --infodotnet tool list --globalcommand -v dotnet-counterscommand -v dotnet-tracecommand -v dotnet-gcdump
2. Choose the install path deliberately:
- preferred machine-level install:
dotnet tool install --global dotnet-countersdotnet tool install --global dotnet-tracedotnet tool install --global dotnet-gcdump- direct-download fallback when global tools are not suitable:
- use the official Microsoft Learn download links for
dotnet-counters,dotnet-trace, anddotnet-gcdump
3. Verify the installed tools resolve correctly:
dotnet-counters --versiondotnet-trace --versiondotnet-gcdump --version
4. Record exact profiling commands in AGENTS.md, for example:
dotnet-counters monitor --process-id PID --counters System.Runtimedotnet-trace collect --process-id PID --profile dotnet-common,dotnet-sampled-thread-time -o trace.nettracedotnet-gcdump collect --process-id PID --output heap.gcdump
5. Run one bounded command and return status: configured or status: improved. 6. If the repo intentionally standardizes on another profiling stack and does not want these tools, return status: not_applicable.
Deliver
- explicit official .NET profiling commands
- a clear profiling path for CPU, counters, and heap inspection
- reproducible diagnostics commands that humans and agents can rerun
Validate
- the chosen tool matches the actual symptom
- commands target a realistic process and configuration
- before/after comparisons use the same scenario
- heap collection warnings are explicit when
dotnet-gcdumpis used
Ralph Loop
Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work.
1. Plan first (mandatory):
- analyze current state
- define target outcome, constraints, and risks
- write a detailed execution plan
- list final validation skills to run at the end, with order and reason
2. Execute one planned step and produce a concrete delta. 3. Review the result and capture findings with actionable next fixes. 4. Apply fixes in small batches and rerun the relevant checks or review steps. 5. Update the plan after each iteration. 6. Repeat until outcomes are acceptable or only explicit exceptions remain. 7. If a dependency is missing, bootstrap it or return status: not_applicable with explicit reason and fallback path.
Required Result Format
status:complete|clean|improved|configured|not_applicable|blockedplan: concise plan and current iteration stepactions_taken: concrete changes madevalidation_skills: final skills run, or skipped with reasonsverification: commands, checks, or review evidence summaryremaining: top unresolved items ornone
For setup-only requests with no execution, return status: configured and exact next commands.
Load References
- references/commands.md
- references/patterns.md
- references/profiling.md
Example Requests
- "Profile this .NET app for CPU hotspots."
- "Investigate GC pressure in this service."
- "Capture counters and a trace from startup."
- "Set up official .NET profiling tools for local investigations."
{
"version": "1.0.0",
"category": "Metrics"
}
.NET Profiling CLI Commands Reference
This reference covers the official .NET diagnostics CLI tools for profiling and runtime investigation.
Tool Installation
# Install all profiling tools globally
dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-dump
dotnet tool install --global dotnet-gcdump
# Verify installations
dotnet-counters --version
dotnet-trace --version
dotnet-dump --version
dotnet-gcdump --version---
dotnet-counters
Real-time monitoring of .NET runtime metrics and custom counters.
List Available Counters
# List all well-known counters
dotnet-counters list
# List counters for a specific process
dotnet-counters list --process-id <PID>Monitor Live Counters
# Monitor System.Runtime counters
dotnet-counters monitor --process-id <PID> --counters System.Runtime
# Monitor with custom refresh interval (in seconds)
dotnet-counters monitor --process-id <PID> --refresh-interval 2
# Monitor multiple counter providers
dotnet-counters monitor --process-id <PID> \
--counters System.Runtime,Microsoft.AspNetCore.Hosting
# Monitor specific counters from a provider
dotnet-counters monitor --process-id <PID> \
--counters "System.Runtime[cpu-usage,working-set,gc-heap-size]"
# Monitor by process name (attach to first match)
dotnet-counters monitor --name MyApp --counters System.RuntimeCollect Counters to File
# Collect counters to CSV
dotnet-counters collect --process-id <PID> \
--counters System.Runtime \
--output counters.csv \
--format csv
# Collect counters to JSON
dotnet-counters collect --process-id <PID> \
--counters System.Runtime \
--output counters.json \
--format json
# Collect for a specific duration (in seconds)
dotnet-counters collect --process-id <PID> \
--counters System.Runtime \
--output counters.csv \
--format csv \
--duration 60Common Counter Providers
| Provider | Description |
|---|---|
System.Runtime | GC, thread pool, exception, and general runtime metrics |
Microsoft.AspNetCore.Hosting | ASP.NET Core request metrics |
Microsoft.AspNetCore.Http.Connections | SignalR connection metrics |
System.Net.Http | HTTP client metrics |
System.Net.Sockets | Socket-level metrics |
Microsoft.EntityFrameworkCore | EF Core query and save metrics |
Key System.Runtime Counters
| Counter | Description |
|---|---|
cpu-usage | CPU usage percentage |
working-set | Working set memory in MB |
gc-heap-size | GC heap size in MB |
gen-0-gc-count | Generation 0 GC count |
gen-1-gc-count | Generation 1 GC count |
gen-2-gc-count | Generation 2 GC count |
threadpool-thread-count | Thread pool thread count |
threadpool-queue-length | Thread pool work item queue length |
exception-count | Number of exceptions thrown |
alloc-rate | Allocation rate in bytes per second |
---
dotnet-trace
Collect detailed performance traces for CPU profiling, events, and diagnostics.
List Running .NET Processes
dotnet-trace psList Available Profiles
dotnet-trace list-profilesCollect Traces
# Collect with default profile (cpu-sampling)
dotnet-trace collect --process-id <PID>
# Collect with specific profile
dotnet-trace collect --process-id <PID> --profile cpu-sampling
# Collect with multiple profiles
dotnet-trace collect --process-id <PID> \
--profile cpu-sampling \
--profile gc-verbose
# Collect for a specific duration (in seconds)
dotnet-trace collect --process-id <PID> \
--duration 00:00:30
# Collect with custom output path
dotnet-trace collect --process-id <PID> \
--output mytrace.nettrace
# Collect with specific providers
dotnet-trace collect --process-id <PID> \
--providers "Microsoft-DotNETCore-SampleProfiler,Microsoft-Windows-DotNETRuntime"
# Collect with high-frequency CPU sampling
dotnet-trace collect --process-id <PID> \
--profile cpu-sampling \
--clrevents gc,jit,exception,contention
# Attach to process by name
dotnet-trace collect --name MyAppBuilt-in Profiles
| Profile | Description |
|---|---|
cpu-sampling | CPU sampling for hotspot analysis |
gc-verbose | Detailed GC events |
gc-collect | GC collection events only |
none | No predefined providers (use --providers) |
Convert Trace Format
# Convert to SpeedScope format for web viewer
dotnet-trace convert mytrace.nettrace --format Speedscope
# Convert to Chromium format
dotnet-trace convert mytrace.nettrace --format Chromium
# Convert with custom output
dotnet-trace convert mytrace.nettrace \
--format Speedscope \
--output mytrace.speedscope.jsonCommon Provider Keywords
# Detailed GC events
--providers "Microsoft-Windows-DotNETRuntime:0x1:5"
# JIT compilation events
--providers "Microsoft-Windows-DotNETRuntime:0x10:5"
# Exception events
--providers "Microsoft-Windows-DotNETRuntime:0x8000:5"
# Contention events
--providers "Microsoft-Windows-DotNETRuntime:0x4000:5"
# Thread pool events
--providers "Microsoft-Windows-DotNETRuntime:0x10000:5"
# All runtime events (verbose)
--providers "Microsoft-Windows-DotNETRuntime:0xFFFFFFFFFFFFFFFF:5"---
dotnet-dump
Capture and analyze memory dumps for debugging crashes and memory issues.
Collect Dumps
# Collect a full memory dump
dotnet-dump collect --process-id <PID>
# Collect with specific dump type
dotnet-dump collect --process-id <PID> --type Full
dotnet-dump collect --process-id <PID> --type Heap
dotnet-dump collect --process-id <PID> --type Mini
# Collect with custom output path
dotnet-dump collect --process-id <PID> \
--output mydump.dmp
# Collect from process by name
dotnet-dump collect --name MyAppDump Types
| Type | Description |
|---|---|
Full | Complete process memory (largest) |
Heap | GC heap and type information |
Mini | Minimal dump with stack traces |
Analyze Dumps
# Start interactive analysis
dotnet-dump analyze mydump.dmpAnalysis Commands (Interactive)
# Show all managed threads
clrthreads
# Show call stacks for all threads
clrstack -all
# Show call stack for current thread
clrstack
# Show exceptions on all threads
pe -all
# Dump heap statistics
dumpheap -stat
# Dump heap by type
dumpheap -type System.String
# Dump specific object
dumpobj <address>
# Find GC roots for an object
gcroot <address>
# Dump method table
dumpmt <address>
# Dump module information
dumpmodule <address>
# Show GC heap information
gcheapstat
# Show finalizer queue
finalizequeue
# Show sync blocks (locks)
syncblk
# Exit analysis
exitScripted Analysis
# Run commands from a script
dotnet-dump analyze mydump.dmp --command "clrthreads" --command "dumpheap -stat"
# Output to file
dotnet-dump analyze mydump.dmp --command "dumpheap -stat" > heap-stats.txt---
dotnet-gcdump
Capture GC heap snapshots for memory analysis without full dumps.
Collect GC Dumps
# Collect GC dump
dotnet-gcdump collect --process-id <PID>
# Collect with custom output
dotnet-gcdump collect --process-id <PID> \
--output myheap.gcdump
# Collect with timeout (in seconds)
dotnet-gcdump collect --process-id <PID> \
--timeout 60
# Collect by process name
dotnet-gcdump collect --name MyAppGenerate Heap Reports
# Generate report to console
dotnet-gcdump report myheap.gcdump
# Generate report with specific type filter
dotnet-gcdump report myheap.gcdump --type System.StringImportant Notes
- GC dumps cause a GC and briefly pause the process
- Smaller than full memory dumps
- Captures type information and reference graphs
- Open
.gcdumpfiles in Visual Studio or PerfView for analysis
---
Process Discovery
Find .NET Process IDs
# List all .NET processes with dotnet-trace
dotnet-trace ps
# List all .NET processes with dotnet-counters
dotnet-counters ps
# Platform-specific alternatives
# Linux/macOS
ps aux | grep dotnet
# Windows PowerShell
Get-Process | Where-Object { $_.ProcessName -like "*dotnet*" }---
Environment Variables for Diagnostics
# Enable diagnostic port (for sidecar collection)
export DOTNET_DiagnosticPorts=/tmp/diag.sock
# Enable startup diagnostics
export DOTNET_StartupHooks=/path/to/hook.dll
# Force GC server mode
export DOTNET_gcServer=1
# Enable GC stress testing
export DOTNET_GCStress=0x3
# Enable ETW events on Windows
export DOTNET_PerfMapEnabled=1
# Enable perf maps on Linux
export DOTNET_EnableEventLog=1---
CI/CD Integration
Capture Startup Trace
# Start app with trace collection from launch
dotnet-trace collect \
--output startup-trace.nettrace \
-- dotnet run --project MyApp.csprojAutomated Counter Collection
#!/bin/bash
# Collect counters during load test
APP_PID=$(dotnet-trace ps | grep MyApp | awk '{print $1}')
dotnet-counters collect \
--process-id $APP_PID \
--counters System.Runtime,Microsoft.AspNetCore.Hosting \
--output load-test-counters.csv \
--format csv \
--duration 300Automated Dump on Crash
# Configure automatic dump collection
export DOTNET_DbgEnableMiniDump=1
export DOTNET_DbgMiniDumpType=4
export DOTNET_DbgMiniDumpName=/tmp/coredump.%p
# Run application
dotnet run.NET Profiling Patterns Reference
This reference covers common profiling patterns for CPU, memory, GC, and performance analysis in .NET applications.
---
CPU Profiling Patterns
Pattern: CPU Hotspot Analysis
When to use: Application is slow or using excessive CPU.
Approach: 1. Ensure the application is running under realistic load 2. Collect a CPU sampling trace 3. Analyze the flame graph for time-consuming methods 4. Focus on your code, not framework internals
# Collect CPU sampling trace
dotnet-trace collect --process-id <PID> --profile cpu-sampling --duration 00:00:30
# Convert for visualization
dotnet-trace convert trace.nettrace --format SpeedscopeAnalysis checklist:
- [ ] Identify top 5 methods by inclusive time
- [ ] Look for unexpected methods in hot paths
- [ ] Check for string operations in loops
- [ ] Check for repeated allocations causing GC
- [ ] Check for synchronous I/O blocking threads
Pattern: Method-Level Timing
When to use: Need precise timing for specific operations.
Approach: 1. Add System.Diagnostics.Activity or Stopwatch instrumentation 2. Use EventCounters for custom metrics 3. Collect with dotnet-counters
// Custom EventCounter for method timing
public class OperationMetrics : EventSource
{
public static readonly OperationMetrics Instance = new();
private readonly IncrementingEventCounter _operationCount;
private readonly EventCounter _operationDuration;
public OperationMetrics()
{
_operationCount = new IncrementingEventCounter("operation-count", this);
_operationDuration = new EventCounter("operation-duration-ms", this);
}
public void RecordOperation(double durationMs)
{
_operationCount.Increment();
_operationDuration.WriteMetric(durationMs);
}
}# Monitor custom counters
dotnet-counters monitor --process-id <PID> \
--counters "MyApp.OperationMetrics[operation-count,operation-duration-ms]"Pattern: Thread Pool Starvation Detection
When to use: Application becomes unresponsive under load.
Symptoms:
- High thread pool queue length
- Increasing response times
- Thread count growing continuously
# Monitor thread pool health
dotnet-counters monitor --process-id <PID> \
--counters "System.Runtime[threadpool-thread-count,threadpool-queue-length,threadpool-completed-items-count]"Thresholds to watch:
threadpool-queue-length> 0 sustained indicates starvationthreadpool-thread-countgrowing continuously indicates blocking calls- High
threadpool-completed-items-countwith high queue length indicates throughput issues
Common causes:
- Sync-over-async (calling
.Resultor.Wait()) - Blocking I/O on thread pool threads
- Long-running synchronous work on thread pool
- Too many concurrent operations
---
Memory Profiling Patterns
Pattern: Memory Leak Detection
When to use: Memory grows continuously over time.
Approach: 1. Establish baseline memory usage 2. Run under load for extended period 3. Take periodic GC dumps 4. Compare heap snapshots
# Baseline snapshot
dotnet-gcdump collect --process-id <PID> --output baseline.gcdump
# After load test
dotnet-gcdump collect --process-id <PID> --output afterload.gcdump
# Generate reports
dotnet-gcdump report baseline.gcdump > baseline-report.txt
dotnet-gcdump report afterload.gcdump > afterload-report.txtAnalysis steps: 1. Compare type counts between snapshots 2. Look for types with growing instance counts 3. Identify retained object graphs 4. Check for event handler subscriptions 5. Check for static collections
Pattern: Large Object Heap (LOH) Analysis
When to use: GC pauses are long or memory fragmentation suspected.
Symptoms:
- Gen 2 GC frequency increasing
- Long GC pause times
- High memory usage despite low live object count
# Collect GC verbose trace
dotnet-trace collect --process-id <PID> --profile gc-verbose --duration 00:01:00Common LOH issues:
- Arrays > 85,000 bytes allocated frequently
- String concatenation creating large strings
- Large buffers not pooled
- Byte arrays from serialization
Mitigations:
// Use ArrayPool for large buffers
var buffer = ArrayPool<byte>.Shared.Rent(100000);
try
{
// Use buffer
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
// Use string pooling
var pooledString = string.Intern(frequentlyUsedString);
// Use Span<T> to avoid allocations
ReadOnlySpan<byte> span = stackalloc byte[256];Pattern: Allocation Rate Analysis
When to use: GC running too frequently.
# Monitor allocation rate
dotnet-counters monitor --process-id <PID> \
--counters "System.Runtime[alloc-rate,gc-heap-size,gen-0-gc-count,gen-1-gc-count,gen-2-gc-count]"Thresholds to watch:
alloc-rate> 100 MB/s may cause GC pressure- Frequent Gen 0 collections (> 10/sec) indicate high allocation
- Gen 2 collections > 1/min may indicate LOH issues or leaks
Allocation hotspot trace:
# Collect allocation events
dotnet-trace collect --process-id <PID> \
--providers "Microsoft-Windows-DotNETRuntime:0x80000:4"---
GC Analysis Patterns
Pattern: GC Pause Analysis
When to use: Application has latency spikes correlated with GC.
# Collect detailed GC events
dotnet-trace collect --process-id <PID> \
--profile gc-verbose \
--duration 00:02:00Key metrics to extract:
- GC pause duration per generation
- GC frequency per generation
- Promoted bytes per collection
- Fragmentation percentage
Pattern: GC Mode Selection
Server GC vs Workstation GC:
| Characteristic | Server GC | Workstation GC |
|---|---|---|
| Threads | One per CPU core | Single thread |
| Heap | Segmented per core | Single heap |
| Latency | Higher pause times | Lower pause times |
| Throughput | Higher throughput | Lower throughput |
| Memory | Higher memory usage | Lower memory usage |
| Best for | Server workloads | Desktop apps |
<!-- Enable server GC in .csproj -->
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>// Or in runtimeconfig.json
{
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
}
}Pattern: Concurrent vs Background GC Analysis
# Monitor GC behavior
dotnet-counters monitor --process-id <PID> \
--counters "System.Runtime[gc-heap-size,gen-0-gc-count,gen-1-gc-count,gen-2-gc-count,time-in-gc]"time-in-gc interpretation:
- < 5%: Healthy
- 5-10%: Monitor closely
- > 10%: GC tuning needed
Pattern: Pinned Object Analysis
When to use: Suspected fragmentation from pinned objects.
# Analyze pinned objects in dump
dotnet-dump analyze mydump.dmp
> gcheapstat
> dumpheap -statCommon pinning issues:
- Interop callbacks holding managed references
- Async I/O buffers
- Native code integration
---
Contention Profiling Patterns
Pattern: Lock Contention Analysis
When to use: Application throughput limited by synchronization.
# Collect contention events
dotnet-trace collect --process-id <PID> \
--providers "Microsoft-Windows-DotNETRuntime:0x4000:4"Analysis steps: 1. Identify frequently contended locks 2. Analyze lock hold times 3. Look for lock ordering issues 4. Check for unnecessary locking
Pattern: Thread Synchronization Overhead
# Monitor thread-related counters
dotnet-counters monitor --process-id <PID> \
--counters "System.Runtime[monitor-lock-contention-count,threadpool-thread-count]"High contention indicators:
monitor-lock-contention-countgrowing rapidly- Thread count higher than expected
- CPU usage lower than expected under load
Mitigations:
// Use lock-free collections
var dict = new ConcurrentDictionary<string, int>();
// Use reader-writer locks for read-heavy workloads
var rwLock = new ReaderWriterLockSlim();
// Use Interlocked for simple operations
Interlocked.Increment(ref counter);
// Use async semaphores
var semaphore = new SemaphoreSlim(maxConcurrency);
await semaphore.WaitAsync();---
Exception Profiling Patterns
Pattern: Exception Rate Analysis
When to use: Performance impacted by exception handling.
# Monitor exception count
dotnet-counters monitor --process-id <PID> \
--counters "System.Runtime[exception-count]"
# Collect exception events with stack traces
dotnet-trace collect --process-id <PID> \
--providers "Microsoft-Windows-DotNETRuntime:0x8000:4"Thresholds:
- < 10 exceptions/sec: Normal
- 10-100 exceptions/sec: Review exception usage
- > 100 exceptions/sec: Performance impact likely
Common exception anti-patterns:
// Anti-pattern: Using exceptions for flow control
try
{
value = dict[key];
}
catch (KeyNotFoundException)
{
value = default;
}
// Better: Use TryGetValue
if (!dict.TryGetValue(key, out value))
{
value = default;
}Pattern: First-Chance Exception Analysis
# Collect first-chance exceptions
dotnet-trace collect --process-id <PID> \
--providers "Microsoft-Windows-DotNETRuntime:0x8000:5"Analyze the trace to find:
- Exception types thrown frequently
- Exception origins (your code vs framework)
- Exception handling patterns
---
Startup Profiling Patterns
Pattern: Cold Start Analysis
When to use: Application startup is slow.
# Trace from application launch
dotnet-trace collect \
--output startup-trace.nettrace \
-- dotnet run --project MyApp.csproj
# Or with specific configuration
dotnet-trace collect \
--output startup-trace.nettrace \
-- dotnet run -c Release --project MyApp.csprojStartup phases to analyze: 1. CLR initialization 2. Assembly loading 3. JIT compilation 4. Type initialization (static constructors) 5. Dependency injection setup 6. Application initialization
Pattern: Assembly Loading Analysis
# Collect loader events
dotnet-trace collect --process-id <PID> \
--providers "Microsoft-Windows-DotNETRuntime:0x8:4"Optimization strategies:
- Use ReadyToRun (R2R) compilation
- Lazy-load optional assemblies
- Use trimming for single-file apps
- Profile and defer non-critical initialization
<!-- Enable ReadyToRun -->
<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>Pattern: JIT Compilation Analysis
# Collect JIT events
dotnet-trace collect --process-id <PID> \
--providers "Microsoft-Windows-DotNETRuntime:0x10:4"JIT optimization strategies:
- Use tiered compilation (default in .NET Core 3.0+)
- Pre-JIT critical paths with warmup
- Use AOT/R2R for startup-sensitive scenarios
---
Production Profiling Patterns
Pattern: Low-Overhead Production Monitoring
Approach: Use dotnet-counters for continuous, low-impact monitoring.
# Lightweight monitoring script
#!/bin/bash
while true; do
dotnet-counters collect \
--process-id $APP_PID \
--counters "System.Runtime[cpu-usage,working-set,gc-heap-size,exception-count]" \
--output metrics-$(date +%Y%m%d-%H%M%S).csv \
--format csv \
--duration 60
donePattern: Triggered Diagnostics Collection
Approach: Collect detailed diagnostics only when anomalies detected.
# Collect trace when CPU exceeds threshold
CPU_THRESHOLD=80
while true; do
CPU=$(dotnet-counters monitor --process-id $APP_PID \
--counters "System.Runtime[cpu-usage]" \
--duration 5 2>/dev/null | grep cpu-usage | awk '{print $2}')
if (( $(echo "$CPU > $CPU_THRESHOLD" | bc -l) )); then
dotnet-trace collect --process-id $APP_PID \
--profile cpu-sampling \
--duration 00:00:30 \
--output high-cpu-$(date +%Y%m%d-%H%M%S).nettrace
fi
sleep 60
donePattern: Memory Dump on OOM
# Configure automatic dump on OOM
export DOTNET_DbgEnableMiniDump=1
export DOTNET_DbgMiniDumpType=4
export DOTNET_DbgMiniDumpName=/var/dumps/oom-%p-%t.dmp
# Run application
dotnet MyApp.dll---
Comparison and Benchmarking Patterns
Pattern: Before/After Comparison
Approach: Collect identical metrics before and after changes.
# Baseline collection
dotnet-counters collect --process-id $BASELINE_PID \
--counters System.Runtime \
--output baseline-counters.csv \
--format csv \
--duration 300
# After optimization
dotnet-counters collect --process-id $OPTIMIZED_PID \
--counters System.Runtime \
--output optimized-counters.csv \
--format csv \
--duration 300Comparison checklist:
- [ ] Same workload profile
- [ ] Same duration
- [ ] Same warmup period
- [ ] Same hardware/environment
- [ ] Multiple runs for statistical significance
Pattern: Regression Detection
Approach: Establish performance baselines in CI.
# CI performance check script
#!/bin/bash
set -e
# Run app with load
./start-load-test.sh &
LOAD_PID=$!
# Collect metrics
dotnet-counters collect --process-id $APP_PID \
--counters System.Runtime \
--output ci-metrics.csv \
--format csv \
--duration 60
# Stop load test
kill $LOAD_PID
# Compare against baseline (example threshold check)
ALLOC_RATE=$(grep alloc-rate ci-metrics.csv | awk -F',' '{sum+=$2; count++} END {print sum/count}')
BASELINE_ALLOC_RATE=50000000 # 50 MB/s
if (( $(echo "$ALLOC_RATE > $BASELINE_ALLOC_RATE * 1.2" | bc -l) )); then
echo "REGRESSION: Allocation rate increased by >20%"
exit 1
fi---
Tool Selection Guide
| Symptom | Primary Tool | Secondary Tool |
|---|---|---|
| High CPU | dotnet-trace (cpu-sampling) | dotnet-counters |
| Memory growth | dotnet-gcdump | dotnet-dump |
| GC pauses | dotnet-trace (gc-verbose) | dotnet-counters |
| Slow startup | dotnet-trace (from launch) | - |
| Lock contention | dotnet-trace (contention) | dotnet-counters |
| Exception storms | dotnet-counters | dotnet-trace |
| Crash analysis | dotnet-dump | - |
| Live health | dotnet-counters | - |
Official .NET Profiling Tools
What This Skill Uses
This skill standardizes on official CLI-based .NET diagnostics tools:
dotnet-countersfor live metrics and exported countersdotnet-tracefor trace capture and hotspot investigationdotnet-gcdumpfor managed heap snapshots
It intentionally avoids dnx-only flows so the commands remain explicit and durable in repo docs.
Installation Paths
Preferred install path for frequent local diagnostics:
dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-gcdumpVerify the tools:
dotnet-counters --version
dotnet-trace --version
dotnet-gcdump --versionIf global tools are not suitable, use the official Microsoft Learn direct-download links for each tool:
dotnet-countersdotnet-tracedotnet-gcdump
Release-First Rule
Profile realistic builds and realistic scenarios:
dotnet build -c Release
dotnet run -c Release --project ./src/MyApp/MyApp.csprojAvoid profiling Debug builds unless the debugging overhead itself is the question.
First-Line Live Triage with dotnet-counters
Use dotnet-counters first when you need fast feedback about:
- CPU usage
- GC activity
- allocation growth
- exception rate
- working set
- thread pool pressure
List candidate processes:
dotnet-counters psMonitor runtime counters:
dotnet-counters monitor --process-id PID --counters System.RuntimeExport counters for later comparison:
dotnet-counters collect --process-id PID --counters System.Runtime --format json -o counters.jsonFor startup diagnostics:
dotnet-counters monitor --counters System.Runtime -- dotnet exec ./bin/Release/net10.0/MyApp.dllCPU and Runtime Tracing with dotnet-trace
Use dotnet-trace when counters show that deeper investigation is needed.
List candidate processes:
dotnet-trace psCapture a focused general-purpose trace:
dotnet-trace collect --process-id PID --profile dotnet-common,dotnet-sampled-thread-time -o trace.nettraceCapture GC-heavy detail:
dotnet-trace collect --process-id PID --profile gc-verbose -o gc.nettraceTrace startup directly:
dotnet-trace collect -- dotnet exec ./bin/Release/net10.0/MyApp.dllGet a top-method summary from a captured trace:
dotnet-trace report trace.nettrace topN --number 20Convert for external viewers when needed:
dotnet-trace convert trace.nettrace --format SpeedscopeExceptions, Contention, and JIT Clues
dotnet-trace is the main CLI path when you need:
- exception-heavy traces
- contention signals
- JIT and runtime event visibility
Keep the run focused and compare the same scenario before and after each fix.
Heap Investigation with dotnet-gcdump
Use dotnet-gcdump when you need managed heap composition rather than CPU stacks.
List candidate processes:
dotnet-gcdump psCapture a heap snapshot:
dotnet-gcdump collect --process-id PID --output heap.gcdumpGet a heap summary report:
dotnet-gcdump report heap.gcdumpImportant warning:
dotnet-gcdump collecttriggers a full Gen 2 GC- this can pause the target process for a long time on large heaps
- do not use it casually on latency-sensitive production paths
Practical Investigation Order
1. Reproduce the issue in a realistic Release scenario. 2. Start with dotnet-counters monitor. 3. If the symptom is CPU or startup related, capture dotnet-trace. 4. If the symptom is memory-shape related, capture dotnet-gcdump. 5. Apply one fix at a time. 6. Rerun the same command set and compare.
Useful Guardrails
- use the same user as the target process, or root where required
- on Linux and macOS, the tool and target process may need the same
TMPDIR - prefer
dotnet execor direct app launch overdotnet runfor startup tracing, becausedotnet runmay spawn extra child processes - keep artifacts named and stored predictably so comparisons are easy