Minecraft-Console-Client/.skills/dotnet-performance-profiling-and-optimization/references/measurement-guide.md
2026-06-25 23:02:43 +02:00

6.7 KiB

description metadata
Performance measurement guide for dotnet-performance skill. Covers tool selection per category, KPI targets, BenchmarkDotNet, k6 load testing, CI/CD integration, and live process CLI commands.
tags
measurement
benchmarkdotnet
k6
dotnet-counters
kpi

Measurement Guide

How to measure performance before and after applying optimizations. Never optimize without baseline data.


Tool Selection Decision Table

Map each optimization category to the appropriate measurement tools:

Category Primary Tool Secondary Tool What to Measure
MEM dotnet-counters (gc-heap-size, alloc-rate) dotnet-gcdump comparison Allocation rate reduction, GC collection frequency
ASYNC dotnet-counters (threadpool-queue-length, thread-count) App Insights dependency tracking Thread pool starvation, blocked threads
LINQ BenchmarkDotNet [MemoryDiagnoser] dotnet-trace hot path Allocation per operation, throughput
DB response.RequestCharge logging App Insights DB dependency RU cost per operation, query latency
JSON BenchmarkDotNet serialization benchmark dotnet-counters alloc-rate Throughput (ops/sec), bytes allocated
CACHE App Insights dependency duration Custom hit ratio counter Cache hit rate, dependency call reduction
DI dotnet-counters alloc-rate Load test comparison Object creation overhead
CONC dotnet-counters (monitor-lock-contention-count) dotnet-trace contention events Lock wait time, throughput under load
HTTP dotnet-counters Microsoft.AspNetCore.Hosting k6/NBomber load test Request duration, throughput
EXC dotnet-counters exception-count App Insights exceptions Exception rate per interval
RESP Network tab / curl with timing k6 response size check Response size (bytes), transfer time
STR BenchmarkDotNet [MemoryDiagnoser] dotnet-counters alloc-rate String allocations per operation
STARTUP Startup time measurement dotnet-trace startup events Time to first request, cold start latency
METRICS MetricCollector<T> in tests Prometheus/Grafana dashboard Metric emission, cardinality

KPI Targets

Standard targets for ASP.NET Core APIs. Use as thresholds when evaluating optimization impact:

Metric Target Red Flag
P50 response time < 100ms > 200ms
P95 response time < 500ms > 1000ms
P99 response time < 1000ms > 2000ms
Error rate (5xx) < 0.1% > 1%
CPU utilization < 70% sustained > 85%
Memory working set < 80% > 90%
Thread pool queue length < 10 sustained > 50
GC time percentage < 10% > 20%
Allocation rate Trend down after optimization Sustained increase

BenchmarkDotNet Guidance

Use for micro-optimizations on hot paths (MEM, LINQ, JSON, STR categories).

When to benchmark: Hot-path changes where the difference is in nanoseconds or bytes allocated. Not needed for architectural changes (caching, DI lifetime) — use load testing instead.

Minimum setup:

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
public class MyBenchmark
{
    [Benchmark(Baseline = true)]
    public void Original() { /* original code */ }

    [Benchmark]
    public void Optimized() { /* optimized code */ }
}

Run command: dotnet run -c Release --project path/to/benchmark

Common pitfalls:

  • Running in Debug mode (JIT optimizations disabled, results meaningless)
  • Not returning computed values (JIT eliminates dead code)
  • Ignoring allocation metrics (throughput may improve but allocations increase)
  • Benchmarking with a debugger attached
  • Including setup costs in the measured method

Load Testing

For HIGH-impact optimizations, perform before/after load testing to validate real-world improvement.

k6 template:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
    stages: [
        { duration: '30s', target: 20 },
        { duration: '1m', target: 20 },
        { duration: '10s', target: 0 },
    ],
    thresholds: {
        http_req_duration: ['p(50)<100', 'p(95)<500', 'p(99)<1000'],
        http_req_failed: ['rate<0.01'],
    },
};

export default function () {
    const res = http.get('http://localhost:5000/your-endpoint');
    check(res, {
        'status is 200': (r) => r.status === 200,
        'p95 under 500ms': (r) => r.timings.duration < 500,
    });
    sleep(1);
}

While load testing, monitor simultaneously:

dotnet-counters monitor -n <ProcessName> --counters System.Runtime,Microsoft.AspNetCore.Hosting

CI/CD Integration

For PR regression detection, use benchmark-action/github-action-benchmark:

- uses: benchmark-action/github-action-benchmark@v1
  with:
    tool: 'benchmarkdotnet'
    output-file-path: BenchmarkDotNet.Artifacts/results/*.json
    alert-threshold: '150%'
    comment-on-alert: true
    fail-on-alert: true

This fails the PR if any benchmark regresses by more than 50% compared to the baseline.


Code Review Mode: Quick Reference Commands

# Baseline runtime health
dotnet-counters monitor -n <ProcessName> --counters System.Runtime

# ASP.NET Core request metrics
dotnet-counters monitor -n <ProcessName> --counters Microsoft.AspNetCore.Hosting

# Full monitoring (runtime + HTTP + custom meters)
dotnet-counters monitor -n <ProcessName> --counters System.Runtime,Microsoft.AspNetCore.Hosting,Microsoft.AspNetCore.Server.Kestrel

# GC heap snapshot for before/after comparison
dotnet-gcdump collect -n <ProcessName> -o before.gcdump
# ... apply optimization ...
dotnet-gcdump collect -n <ProcessName> -o after.gcdump

# 30-second CPU trace
dotnet-trace collect -n <ProcessName> --duration 00:00:30
dotnet-trace convert trace.nettrace --format speedscope

Diagnostic Mode: Full CLI Commands

When profiling a live process (Mode A), use these commands by investigation stage:

# Stage 1: Live triage
dotnet-counters monitor -p <PID> --counters System.Runtime
dotnet-counters monitor -n <ProcessName> --counters System.Runtime,Microsoft.AspNetCore.Hosting,Microsoft.AspNetCore.Server.Kestrel

# Stage 2: Stuck/hung process — get stacks immediately
dotnet-stack report -p <PID>

# Stage 3: CPU + allocation hot paths
dotnet-trace collect -p <PID> --duration 00:00:30
dotnet-trace report <trace.nettrace> topN

# Stage 4: Heap composition
dotnet-gcdump collect -p <PID> -o before.gcdump
# ... apply optimization ...
dotnet-gcdump collect -p <PID> -o after.gcdump
dotnet-gcdump report <file.gcdump>

# Stage 5: Full dump for SOS analysis
dotnet-dump collect -p <PID> --type Heap
dotnet-dump analyze <dump> -c "dumpheap -stat" -c "exit"