Documentation
Metreja · A .NET profiler built for AI coding agents
Overview
Metreja is a CLI-first .NET profiler designed to be driven by AI coding agents. Every operation is a command. Every output is machine-readable NDJSON. The full measure-analyze-fix loop runs without human intervention.
It consists of two components: a native C++ profiler DLL that attaches to the .NET runtime via COR_PROFILER environment variables, and a C# CLI tool for session management and trace analysis.
| Command | What it does |
|---|---|
| metreja hotspots | Rank methods by self time, inclusive time, call count, or allocations |
| metreja calltree | Expand a slow method into its full call tree with timing at every level |
| metreja callers | Find who calls a method and how much time each caller contributes |
| metreja memory | GC counts by generation, pause times, per-type allocation hotspots |
| metreja analyze-diff | Compare two traces to verify a fix actually improved performance |
| metreja summary | Trace overview: event counts, wall-clock duration, threads, methods |
| metreja exceptions | Rank exception types by frequency with throw-site methods |
| metreja timeline | Chronological event listing with tid, event type, and method filtering |
| metreja threads | Per-thread breakdown: call counts, root time, activity windows |
| metreja trend | Method performance trend across periodic stats flush intervals |
| metreja check | CI regression gate: compare two traces, exit non-zero on regression |
| metreja run | Launch an executable with profiler attached (--detach for GUI apps) |
| metreja flush | Trigger manual stats flush on a running profiled process |
| metreja list | List existing profiling sessions |
| metreja merge | Combine multiple trace files into one sorted by timestamp |
| metreja export | Convert traces to speedscope or CSV format for visualization/analysis |
Installation
Prerequisites: .NET 8 SDK or later. Windows 10/11 (x64) or macOS 14+ (Apple Silicon).
dotnet tool install -g Metreja.ToolAfter installation, the metreja command is available globally in your terminal.
Quick Start
Five commands from zero to actionable hotspot data:
# 1. Create a session
SESSION=$(metreja init --scenario "baseline")
# 2. Tell it what to trace (your code, not the framework)
metreja add include -s $SESSION --assembly MyApp
metreja add exclude -s $SESSION --assembly "System.*"
metreja add exclude -s $SESSION --assembly "Microsoft.*"
# 3. Generate the profiler environment and run your app
metreja generate-env -s $SESSION --format shell > env.sh
source env.sh && dotnet run --project src/MyApp -c Release
# 4. Find the bottleneck
metreja hotspots .metreja/output/*.ndjson --top 10
# 5. Drill into the slowest method
metreja calltree .metreja/output/*.ndjson --method "ValidateInventory"Commands
Analysis commands read NDJSON trace files produced by the profiler. Pass a file path or a glob pattern as the first argument.
hotspots
Per-method timing ranked by self time, inclusive time, call count, or allocation count.
| Option | Type | Default | Description |
|---|---|---|---|
| file | string | — | Required. NDJSON trace file |
| --top | int | 20 | Number of methods to show |
| --min-ms | double | 0.0 | Minimum time threshold (ms) |
| --sort | string | self | self, inclusive, calls, or allocs |
| --filter | string[] | — | Filter by method/class/namespace pattern |
metreja hotspots trace.ndjson --top 10 --sort self
metreja hotspots trace.ndjson --filter "MyApp.Services" --min-ms 1calltree
Call tree for a specific method invocation, slowest first.
| Option | Type | Default | Description |
|---|---|---|---|
| file | string | — | Required. NDJSON trace file |
| --method | string | — | Required. Method name or pattern |
| --tid | long | — | Filter by thread ID |
| --occurrence | int | 1 | Which invocation (1 = slowest) |
metreja calltree trace.ndjson --method "ProcessOrder"
metreja calltree trace.ndjson --method "ProcessOrder" --occurrence 2callers
Who calls a method, with call count and timing per caller.
| Option | Type | Default | Description |
|---|---|---|---|
| file | string | — | Required. NDJSON trace file |
| --method | string | — | Required. Method name or pattern |
| --top | int | 20 | Number of callers to show |
metreja callers trace.ndjson --method "SaveChanges" --top 10memory
GC summary (generation counts, pause times) and per-type allocation hotspots.
| Option | Type | Default | Description |
|---|---|---|---|
| file | string | — | Required. NDJSON trace file |
| --top | int | 20 | Number of allocation types |
| --filter | string[] | — | Filter by class name pattern |
metreja memory trace.ndjson --top 20
metreja memory trace.ndjson --filter "System.String"analyze-diff
Compare two traces. Shows per-method timing delta (base vs. compare).
| Argument | Type | Description |
|---|---|---|
| base | string | Required. Base NDJSON file |
| compare | string | Required. Comparison NDJSON file |
metreja analyze-diff baseline.ndjson optimized.ndjsonsummary
High-level trace overview: total event count, wall-clock duration, thread count, and unique method count.
metreja summary trace.ndjsonexceptions
Rank exception types by throw frequency, with the top throw-site methods for each type.
| Option | Type | Default | Description |
|---|---|---|---|
| file | string | — | Required. NDJSON trace file |
| --top | int | 20 | Number of exception types to show |
metreja exceptions trace.ndjson --top 10timeline
Chronological listing of profiler events with thread ID, event type, and optional method/type filtering.
| Option | Type | Default | Description |
|---|---|---|---|
| file | string | — | Required. NDJSON trace file |
| --tid | long | — | Filter by thread ID |
| --type | string | — | Filter by event type |
| --method | string | — | Filter by method name pattern |
| --top | int | 100 | Maximum events to show |
metreja timeline trace.ndjson --tid 1 --top 50threads
Per-thread breakdown: call counts, root time, and activity windows.
metreja threads trace.ndjsontrend
Method performance trend across periodic stats flush intervals. Shows how a method's timing evolves over the profiling run.
| Option | Type | Default | Description |
|---|---|---|---|
| file | string | — | Required. NDJSON trace file |
| --method | string | — | Required. Method name or pattern |
metreja trend trace.ndjson --method "ValidateInventory"check
CI regression gate. Compares two traces and exits non-zero when any method regresses beyond the threshold. Designed to run in CI pipelines.
| Argument / Option | Type | Description |
|---|---|---|
| base | string | Required. Baseline NDJSON file |
| compare | string | Required. Comparison NDJSON file |
| --threshold | double | Regression threshold as percentage (default 10) |
metreja check baseline.ndjson pr-build.ndjson --threshold 10
# Exit 0 = no regressions, Exit 1 = regression detectedrun
Launch an executable with the profiler automatically attached. Useful for profiling GUI applications or long-running services.
metreja run -s a1b2c3 --detach -- "C:\Program Files\MyApp\app.exe" --config settings.jsonflush
Trigger an on-demand stats flush on a running profiled process. Useful when the profiled process may be force-killed before a periodic flush occurs, or when manual-only flush mode is enabled.
| Option | Type | Description |
|---|---|---|
| --pid | int | Required. Process ID of the running profiled process |
metreja flush --pid 12345The profiler creates a named Windows event MetrejaFlush_{pid} for IPC signaling. The flush thread responds to both periodic timeouts and manual flush signals.
list
List all existing profiling sessions with their IDs and scenario names.
metreja listmerge
Combine multiple NDJSON trace files into a single file sorted by timestamp.
metreja merge trace1.ndjson trace2.ndjson --output merged.ndjsonexport
Convert a trace to speedscope format for visual flame graph exploration, or to CSV for spreadsheet analysis.
metreja export trace.ndjson --output profile.speedscope.json
metreja export trace.ndjson --format csv --output analysis.csvreport
Report an issue to the GitHub repository. Requires the GitHub CLI (gh) installed and authenticated.
| Option | Type | Description |
|---|---|---|
| -t, --title | string | Required. Issue title |
| -d, --description | string | Required. Issue body/description |
metreja report --title "Bug: crash on empty trace" --description "Detailed description."Exit codes: 0 success · 1 failed · 2 gh not installed · 3 gh not authenticated
Session Management
Sessions store profiler configuration in .metreja/sessions/{sessionId}.json. Each session has a unique 6-hex-char ID.
init
Create a new profiling session. Returns a random session ID.
| Option | Type | Default | Description |
|---|---|---|---|
| --scenario | string | — | Optional scenario name |
metreja init
metreja init --scenario "before-refactor"add include / add exclude
Add filter rules controlling which methods get traced. Supports wildcards (*).
| Option | Type | Default | Description |
|---|---|---|---|
| -s, --session | string | — | Required. Session ID |
| --assembly | string[] | * | Assembly name pattern |
| --namespace | string[] | * | Namespace pattern |
| --class | string[] | * | Class name pattern |
| --method | string[] | * | Method name pattern |
metreja add include -s a1b2c3 --assembly MyApp
metreja add include -s a1b2c3 --assembly MyApp --namespace "MyApp.Core"
metreja add exclude -s a1b2c3 --assembly "System.*"
metreja add exclude -s a1b2c3 --class "*Generated*"generate-env
Generate a script that sets the environment variables to attach the profiler to your app.
| Option | Type | Default | Description |
|---|---|---|---|
| -s, --session | string | — | Required. Session ID |
| --format | string | batch | batch, powershell, or shell |
| --force | bool | false | Generate even if profiler DLL is not found |
metreja generate-env -s a1b2c3 # Windows batch
metreja generate-env -s a1b2c3 --format powershell # PowerShell
metreja generate-env -s a1b2c3 --format shell # bash/shset
Configure session settings via subcommands.
| Subcommand | Arguments | Description |
|---|---|---|
| set metadata | -s ID "scenario-name" | Update scenario name |
| set output | -s ID "path/pattern.ndjson" | Set output path (supports {sessionId}, {pid} tokens) |
| set max-events | -s ID 50000 | Cap event count (0 = unlimited) |
| set compute-deltas | -s ID true | Enable delta timing for performance analysis |
validate
Check session configuration for errors. Exits with code 1 on failure.
metreja validate -s a1b2c3flush
Trigger an on-demand stats flush on a running profiled process. Useful when the profiled process may be force-killed before a periodic flush occurs, or when statsFlushIntervalSeconds is set to 0 (manual-only mode).
| Option | Type | Description |
|---|---|---|
| --pid | int | Required. Process ID of the running profiled process |
metreja flush --pid 12345The profiler creates a named Windows event MetrejaFlush_{pid} for IPC signaling. The flush thread responds to both periodic timeouts and manual flush signals.
clear
Delete profiling sessions.
metreja clear -s a1b2c3 # delete one session
metreja clear --all # delete all sessionsSession Config Format
Stored at .metreja/sessions/{sessionId}.json:
{
"sessionId": "a1b2c3",
"metadata": { "scenario": "baseline" },
"instrumentation": {
"maxEvents": 0,
"computeDeltas": true,
"statsFlushIntervalSeconds": 30,
"includes": [
{ "assembly": "MyApp", "namespace": "*", "class": "*", "method": "*" }
],
"excludes": []
},
"output": {
"path": ".metreja/output/{sessionId}_{pid}.ndjson"
}
}| Field | Type | Default | Description |
|---|---|---|---|
| maxEvents | int | 0 | Event cap per session (0 = unlimited) |
| computeDeltas | bool | true | Include delta timing on leave events |
| statsFlushIntervalSeconds | int | 30 | Periodic stats flush interval in seconds (0 = disabled / manual-only mode) |
Output path tokens:
| Token | Replaced With |
|---|---|
| {sessionId} | Session ID from config |
| {pid} | Process ID of the profiled app |
Architecture
Metreja is a two-component system:
Native C++ DLL implementing ICorProfilerCallback3 with ELT3 hooks. Attaches to the .NET runtime via COR_PROFILER environment variables, hooks method enter/leave, and writes NDJSON traces.
C# CLI for session management and trace analysis. Creates session configs, generates profiler environment scripts, provides analysis commands (hotspots, calltree, callers, memory, diff), and the flush command for on-demand stats flushing on running processes.
Data flow: CLI creates session config → generate-env sets profiler env vars → profiled app loads DLL → DLL writes NDJSON → CLI analysis commands read NDJSON.
Building from Source
Windows prerequisites: Windows 10/11 (x64), .NET 10 SDK (multi-targets .NET 8/9/10), Visual Studio 2022 Build Tools with "Desktop development with C++" workload.
Windows:
# Build CLI
dotnet build src/Metreja.Tool/Metreja.Tool.csproj -c Release
# Build profiler DLL
msbuild src/Metreja.Profiler/Metreja.Profiler.vcxproj /p:Configuration=Release /p:Platform=x64 "/p:SolutionDir=%CD%\\"
# Run integration tests
dotnet test test/Metreja.IntegrationTests/Metreja.IntegrationTests.csproj -c Release
# Format C++ code
scripts/format-cpp.batmacOS (Apple Silicon): Requires Xcode Command Line Tools, CMake (brew install cmake), and .NET 10 SDK.
# Build CLI
dotnet build src/Metreja.Tool/Metreja.Tool.csproj -c Release
# Build profiler dylib
scripts/build-macos.sh
# Run integration tests
dotnet test test/Metreja.IntegrationTests/Metreja.IntegrationTests.csproj -c Release
# Format C++ code
scripts/format-cpp.shBuild outputs:
| Output | Path |
|---|---|
| CLI (Windows) | src/Metreja.Tool/bin/Release/net10.0/metreja.exe |
| CLI (macOS) | src/Metreja.Tool/bin/Release/net10.0/metreja |
| Profiler DLL (Windows) | bin/Release/Metreja.Profiler.dll |
| Profiler dylib (macOS) | bin/Release/libMetreja.Profiler.dylib |
Claude Code Plugin
The metreja-profiler plugin for Claude Code enables fully automated profiling sessions. Install it and ask questions in plain English — Claude handles session setup, profiling, analysis, and fix suggestions automatically.
/plugin marketplace add kodroi/metreja-profiler-marketplace
/plugin install metreja-profiler@metreja-profiler-marketplaceAfter installation, just ask: "This endpoint takes 3 seconds, find out why" or "Where am I wasting memory?"
Requires Metreja.Tool to be installed globally (dotnet tool install -g Metreja.Tool).