dotnet-profiling
Use the free official .NET diagnostics CLI tools for profiling and runtime investigation in .NET repositories. Use when a repo needs CPU tracing, live counters, GC and allocation investigation, exception or contention tracing, heap snapshots, or startup diagnostics without GUI-on
Install
npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-profiling
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
.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
- Read the nearest
AGENTS.mdand confirm scope and constraints. - Run this skill's
Workflowthrough theRalph Loopuntil outcomes are acceptable. - Return the
Required Result Formatwith concrete artifacts and verification evidence.
Workflow
- Build and run a realistic target first:
- prefer
Release - prefer realistic config, inputs, and data volume
- prefer
- 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
- Prefer installed CLI tools over
dnxone-shot execution so the repo commands stay stable and reproducible. - Capture one focused profile at a time instead of mixing every signal into one run.
- For CPU and general runtime hotspots, start with
dotnet-trace collect. - For live triage, start with
dotnet-counters monitoronSystem.Runtime. - For heap analysis, use
dotnet-gcdumpcarefully and document the pause risk. - 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:
- Detect current state:
dotnet --infodotnet tool list --globalcommand -v dotnet-counterscommand -v dotnet-tracecommand -v dotnet-gcdump
- 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
- use the official Microsoft Learn download links for
- preferred machine-level install:
- Verify the installed tools resolve correctly:
dotnet-counters --versiondotnet-trace --versiondotnet-gcdump --version
- 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
- Run one bounded command and return
status: configuredorstatus: improved. - 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.
- 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
- Execute one planned step and produce a concrete delta.
- Review the result and capture findings with actionable next fixes.
- Apply fixes in small batches and rerun the relevant checks or review steps.
- Update the plan after each iteration.
- Repeat until outcomes are acceptable or only explicit exceptions remain.
- If a dependency is missing, bootstrap it or return
status: not_applicablewith 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.mdreferences/patterns.mdreferences/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."
Files (dotnet-skills)
-
references
-
commands.md 9.2 KB
# .NET Profiling CLI Commands Reference This reference covers the official .NET diagnostics CLI tools for profiling and runtime investigation. ## Tool Installation ```bash # 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 ```bash # List all well-known counters dotnet-counters list # List counters for a specific process dotnet-counters list --process-id <PID> ``` ### Monitor Live Counters ```bash # 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.Runtime ``` ### Collect Counters to File ```bash # 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 60 ``` ### Common 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 ```bash dotnet-trace ps ``` ### List Available Profiles ```bash dotnet-trace list-profiles ``` ### Collect Traces ```bash # 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 MyApp ``` ### Built-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 ```bash # 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.json ``` ### Common Provider Keywords ```bash # 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 ```bash # 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 MyApp ``` ### Dump Types | Type | Description | |------|-------------| | `Full` | Complete process memory (largest) | | `Heap` | GC heap and type information | | `Mini` | Minimal dump with stack traces | ### Analyze Dumps ```bash # Start interactive analysis dotnet-dump analyze mydump.dmp ``` ### Analysis Commands (Interactive) ```bash # 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 exit ``` ### Scripted Analysis ```bash # 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 ```bash # 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 MyApp ``` ### Generate Heap Reports ```bash # Generate report to console dotnet-gcdump report myheap.gcdump # Generate report with specific type filter dotnet-gcdump report myheap.gcdump --type System.String ``` ### Important Notes - GC dumps cause a GC and briefly pause the process - Smaller than full memory dumps - Captures type information and reference graphs - Open `.gcdump` files in Visual Studio or PerfView for analysis --- ## Process Discovery ### Find .NET Process IDs ```bash # 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 ```bash # 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 ```bash # Start app with trace collection from launch dotnet-trace collect \ --output startup-trace.nettrace \ -- dotnet run --project MyApp.csproj ``` ### Automated Counter Collection ```bash #!/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 300 ``` ### Automated Dump on Crash ```bash # Configure automatic dump collection export DOTNET_DbgEnableMiniDump=1 export DOTNET_DbgMiniDumpType=4 export DOTNET_DbgMiniDumpName=/tmp/coredump.%p # Run application dotnet run ``` -
patterns.md 13.9 KB
# .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 ```bash # 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 Speedscope ``` **Analysis 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 ```csharp // 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); } } ``` ```bash # 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 ```bash # 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 starvation - `threadpool-thread-count` growing continuously indicates blocking calls - High `threadpool-completed-items-count` with high queue length indicates throughput issues **Common causes**: - Sync-over-async (calling `.Result` or `.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 ```bash # 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.txt ``` **Analysis 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 ```bash # Collect GC verbose trace dotnet-trace collect --process-id <PID> --profile gc-verbose --duration 00:01:00 ``` **Common LOH issues**: - Arrays > 85,000 bytes allocated frequently - String concatenation creating large strings - Large buffers not pooled - Byte arrays from serialization **Mitigations**: ```csharp // 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. ```bash # 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**: ```bash # 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. ```bash # Collect detailed GC events dotnet-trace collect --process-id <PID> \ --profile gc-verbose \ --duration 00:02:00 ``` **Key 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 | ```xml <!-- Enable server GC in .csproj --> <PropertyGroup> <ServerGarbageCollection>true</ServerGarbageCollection> </PropertyGroup> ``` ```json // Or in runtimeconfig.json { "runtimeOptions": { "configProperties": { "System.GC.Server": true } } } ``` ### Pattern: Concurrent vs Background GC Analysis ```bash # 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. ```bash # Analyze pinned objects in dump dotnet-dump analyze mydump.dmp > gcheapstat > dumpheap -stat ``` **Common 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. ```bash # 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 ```bash # 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-count` growing rapidly - Thread count higher than expected - CPU usage lower than expected under load **Mitigations**: ```csharp // 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. ```bash # 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**: ```csharp // 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 ```bash # 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. ```bash # 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.csproj ``` **Startup 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 ```bash # 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 ```xml <!-- Enable ReadyToRun --> <PropertyGroup> <PublishReadyToRun>true</PublishReadyToRun> </PropertyGroup> ``` ### Pattern: JIT Compilation Analysis ```bash # 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. ```bash # 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 done ``` ### Pattern: Triggered Diagnostics Collection **Approach**: Collect detailed diagnostics only when anomalies detected. ```bash # 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 done ``` ### Pattern: Memory Dump on OOM ```bash # 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. ```bash # 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 300 ``` **Comparison 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. ```bash # 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 | - | -
profiling.md 4 KB
# Official .NET Profiling Tools ## What This Skill Uses This skill standardizes on official CLI-based .NET diagnostics tools: - `dotnet-counters` for live metrics and exported counters - `dotnet-trace` for trace capture and hotspot investigation - `dotnet-gcdump` for 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: ```bash dotnet tool install --global dotnet-counters dotnet tool install --global dotnet-trace dotnet tool install --global dotnet-gcdump ``` Verify the tools: ```bash dotnet-counters --version dotnet-trace --version dotnet-gcdump --version ``` If global tools are not suitable, use the official Microsoft Learn direct-download links for each tool: - `dotnet-counters` - `dotnet-trace` - `dotnet-gcdump` ## Release-First Rule Profile realistic builds and realistic scenarios: ```bash dotnet build -c Release dotnet run -c Release --project ./src/MyApp/MyApp.csproj ``` Avoid 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: ```bash dotnet-counters ps ``` Monitor runtime counters: ```bash dotnet-counters monitor --process-id PID --counters System.Runtime ``` Export counters for later comparison: ```bash dotnet-counters collect --process-id PID --counters System.Runtime --format json -o counters.json ``` For startup diagnostics: ```bash dotnet-counters monitor --counters System.Runtime -- dotnet exec ./bin/Release/net10.0/MyApp.dll ``` ## CPU and Runtime Tracing with dotnet-trace Use `dotnet-trace` when counters show that deeper investigation is needed. List candidate processes: ```bash dotnet-trace ps ``` Capture a focused general-purpose trace: ```bash dotnet-trace collect --process-id PID --profile dotnet-common,dotnet-sampled-thread-time -o trace.nettrace ``` Capture GC-heavy detail: ```bash dotnet-trace collect --process-id PID --profile gc-verbose -o gc.nettrace ``` Trace startup directly: ```bash dotnet-trace collect -- dotnet exec ./bin/Release/net10.0/MyApp.dll ``` Get a top-method summary from a captured trace: ```bash dotnet-trace report trace.nettrace topN --number 20 ``` Convert for external viewers when needed: ```bash dotnet-trace convert trace.nettrace --format Speedscope ``` ## Exceptions, 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: ```bash dotnet-gcdump ps ``` Capture a heap snapshot: ```bash dotnet-gcdump collect --process-id PID --output heap.gcdump ``` Get a heap summary report: ```bash dotnet-gcdump report heap.gcdump ``` Important warning: - `dotnet-gcdump collect` triggers 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 exec` or direct app launch over `dotnet run` for startup tracing, because `dotnet run` may spawn extra child processes - keep artifacts named and stored predictably so comparisons are easy
-
-
SKILL.md 5.8 KB
--- name: dotnet-profiling version: "1.0.0" category: "Metrics" description: "Use the free official .NET diagnostics CLI tools for profiling and runtime investigation in .NET repositories. Use when a repo needs CPU tracing, live counters, GC and allocation investigation, exception or contention tracing, heap snapshots, or startup diagnostics without GUI-only tooling." compatibility: "Requires a .NET app or process to inspect; respects the repo's `AGENTS.md` commands first." --- # .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-counters` for live health signals - `dotnet-trace` for CPU, exception, contention, GC, and startup traces - `dotnet-gcdump` for 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 --info` - `dotnet tool list --global` - `command -v dotnet-counters` - `command -v dotnet-trace` - `command -v dotnet-gcdump` 2. Choose the install path deliberately: - preferred machine-level install: - `dotnet tool install --global dotnet-counters` - `dotnet tool install --global dotnet-trace` - `dotnet 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`, and `dotnet-gcdump` 3. Verify the installed tools resolve correctly: - `dotnet-counters --version` - `dotnet-trace --version` - `dotnet-gcdump --version` 4. Record exact profiling commands in `AGENTS.md`, for example: - `dotnet-counters monitor --process-id PID --counters System.Runtime` - `dotnet-trace collect --process-id PID --profile dotnet-common,dotnet-sampled-thread-time -o trace.nettrace` - `dotnet-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-gcdump` is 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` | `blocked` - `plan`: concise plan and current iteration step - `actions_taken`: concrete changes made - `validation_skills`: final skills run, or skipped with reasons - `verification`: commands, checks, or review evidence summary - `remaining`: top unresolved items or `none` 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."
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.