Claude Skill

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

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-profiling-bfa4ebd.zip · 11 KB
Part of postpartum-genushyacinthus29/dotnet-skills — 80 skills

Install

skills CLI npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-profiling
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git 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

  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."
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.

No comments yet.

Reviews (0)

No reviews yet.

Related