# dotTrace Command-Line Profiler ## Installation Installed as a .NET global tool: ```bash dotnet tool install --global JetBrains.dotTrace.GlobalTools ``` Update to latest: ```bash dotnet tool update --global JetBrains.dotTrace.GlobalTools ``` Current version: **2025.3.3** ## Quick Start ### Profile the NATS server (sampling, 30 seconds) ```bash dottrace start --framework=NetCore --profiling-type=Sampling \ --timeout=30s --save-to=./snapshots/nats-sampling.dtp \ -- dotnet run --project src/NATS.Server.Host -- -p 14222 ``` ### Profile the NATS server (timeline, with async/TPL info) ```bash dottrace start --framework=NetCore --profiling-type=Timeline \ --timeout=30s --save-to=./snapshots/nats-timeline.dtt \ -- dotnet run --project src/NATS.Server.Host -- -p 14222 ``` ### Attach to a running server by PID ```bash dottrace attach --profiling-type=Sampling \ --timeout=30s --save-to=./snapshots/nats-attach.dtp ``` ### Attach by process name ```bash dottrace attach NATS.Server.Host --profiling-type=Sampling \ --timeout=30s --save-to=./snapshots/nats-attach.dtp ``` ## Profiling Types | Type | Flag | Snapshot Extension | Use Case | |------|------|--------------------|----------| | Sampling | `--profiling-type=Sampling` | `.dtp` | Low overhead, CPU hotspots (default) | | Timeline | `--profiling-type=Timeline` | `.dtt` | Thread activity, async/await, TPL tasks | | Tracing | `--profiling-type=Tracing` | `.dtp` | Exact call counts, higher overhead | | Line-by-Line | `--profiling-type=LineByLine` | `.dtp` | Per-line timing (not available for attach) | ### Sampling options ```bash # Use thread time instead of CPU instructions --time-measurement=ThreadTime # Default (CPU instruction count) --time-measurement=CpuInstruction ``` ### Timeline options ```bash # Disable TPL data collection for better performance --disable-tpl ``` ## Common Options | Option | Description | |--------|-------------| | `--framework=NetCore` | Required for .NET Core / .NET 5+ apps | | `--save-to=` | Snapshot output path (file or directory) | | `--overwrite` | Overwrite existing snapshot files | | `--timeout=` | Auto-stop after duration (e.g., `30s`, `5m`, `1h`) | | `--propagate-exit-code` | Return the profiled app's exit code instead of dotTrace's | | `--profile-child` | Also profile child processes | | `--profile-child=` | Profile matching child processes (e.g., `dotnet`) | | `--work-dir=` | Set working directory for the profiled app | | `--collect-data-from-start=off` | Don't collect until explicitly started via service messages | ## Interactive Profiling with Service Messages For fine-grained control over when data is collected, use `--service-input=stdin`: ```bash dottrace start --framework=NetCore --service-input=stdin \ --save-to=./snapshots/nats-interactive.dtp \ -- dotnet run --project src/NATS.Server.Host -- -p 14222 ``` Then type these commands into stdin (each must start on a new line and end with a carriage return): | Command | Effect | |---------|--------| | `##dotTrace["start"]` | Start collecting performance data | | `##dotTrace["get-snapshot"]` | Save snapshot and stop collecting | | `##dotTrace["drop"]` | Discard collected data and stop | | `##dotTrace["disconnect"]` | Detach/stop profiler | Stdout will emit status messages like: ``` ##dotTrace["ready"] ##dotTrace["connected", {pid: 1234, path:"dotnet"}] ##dotTrace["started", {pid: 1234, path:"dotnet"}] ##dotTrace["snapshot-saved", {pid: 1234, filename:"./snapshots/nats-interactive.dtp"}] ``` ## Example Workflows ### Profile a benchmark run ```bash dottrace start --framework=NetCore --profiling-type=Sampling \ --save-to=./snapshots/bench.dtp \ -- dotnet run --project tests/NATS.Server.Benchmarks -c Release ``` ### Profile tests ```bash dottrace start --framework=NetCore --profiling-type=Sampling \ --timeout=2m --save-to=./snapshots/tests.dtp \ -- dotnet test tests/NATS.Server.Core.Tests --filter "FullyQualifiedName~PubSub" ``` ### Profile with child processes (e.g., server spawns workers) ```bash dottrace start --framework=NetCore --profile-child \ --timeout=30s --save-to=./snapshots/nats-children.dtp \ -- dotnet run --project src/NATS.Server.Host ``` ## Exporting Reports dotTrace's XML report tool (Reporter.exe) is Windows-only. On macOS, use `dotnet-trace` for profiling with exportable formats: ```bash # Install dotnet-trace dotnet tool install --global dotnet-trace # Collect a trace from a running process (nettrace format) dotnet-trace collect --process-id --duration 00:00:30 # Collect directly in speedscope format dotnet-trace collect --process-id --format speedscope --duration 00:00:30 # Convert an existing .nettrace file to speedscope dotnet-trace convert --format speedscope trace.nettrace ``` Speedscope files can be visualized at [speedscope.app](https://www.speedscope.app) — a web-based flame graph viewer that works on any platform. #### dotnet-trace output formats | Format | Extension | Viewer | |--------|-----------|--------| | `nettrace` (default) | `.nettrace` | PerfView, Visual Studio, Rider | | `speedscope` | `.speedscope.json` | [speedscope.app](https://www.speedscope.app) | | `chromium` | `.chromium.json` | Chrome DevTools (`chrome://tracing`) | #### Example: profile NATS server and export flame graph ```bash # Start the server dotnet run --project src/NATS.Server.Host -- -p 14222 & SERVER_PID=$! # Collect a 30-second trace in speedscope format dotnet-trace collect --process-id $SERVER_PID --format speedscope \ --duration 00:00:30 --output ./snapshots/nats-trace # Open the flame graph open ./snapshots/nats-trace.speedscope.json # opens in default browser at speedscope.app ``` ## Viewing Snapshots Open `.dtp` / `.dtt` snapshot files in: - **dotTrace GUI** (`/Users/dohertj2/Applications/dotTrace.app`) - **JetBrains Rider** (built-in profiler viewer) ```bash open /Users/dohertj2/Applications/dotTrace.app --args ./snapshots/nats-sampling.dtp ``` ## Parsing Raw `.dtp` Snapshots To JSON The repository includes a Python-first parser for raw dotTrace sampling and tracing snapshots: - Python entrypoint: [tools/dtp_parse.py](/Users/dohertj2/Desktop/natsdotnet/tools/dtp_parse.py) - .NET helper: [tools/DtpSnapshotExtractor/Program.cs](/Users/dohertj2/Desktop/natsdotnet/tools/DtpSnapshotExtractor/Program.cs) The parser starts from the raw `.dtp` snapshot family and emits machine-readable JSON for call-tree and hotspot analysis. It uses the locally installed dotTrace assemblies to decode the snapshot format. ### Prerequisites - `python3` - `.NET 10 SDK` - dotTrace installed at `/Users/dohertj2/Applications/dotTrace.app` If dotTrace is installed elsewhere, set `DOTTRACE_APP_DIR` to the `Contents/DotFiles` directory: ```bash export DOTTRACE_APP_DIR="/path/to/dotTrace.app/Contents/DotFiles" ``` ### Print JSON to stdout ```bash python3 tools/dtp_parse.py snapshots/js-ordered-consume.dtp --stdout ``` ### Write JSON to a file ```bash python3 tools/dtp_parse.py snapshots/js-ordered-consume.dtp \ --out /tmp/js-ordered-consume-calltree.json ``` ### Output shape The generated JSON contains: - `snapshot` — source path, payload type, thread count, node count - `threadRoots` — top-level thread roots with inclusive time - `callTree` — nested call tree rooted at a synthetic `` - `hotspots` — flat `inclusive` and `exclusive` method lists Hotspot entries are method-first. Synthetic frames such as thread roots are excluded from the hotspot lists so the output is easier to feed into an LLM for slowdown analysis. ### Typical analysis workflow 1. Capture a snapshot with `dottrace`. 2. Convert the raw `.dtp` snapshot to JSON: ```bash python3 tools/dtp_parse.py snapshots/nats-sampling.dtp \ --out /tmp/nats-sampling-calltree.json ``` 3. Inspect the top hotspots: ```bash python3 - <<'PY' import json with open('/tmp/nats-sampling-calltree.json') as f: data = json.load(f) print('Top inclusive:', data['hotspots']['inclusive'][0]['name']) print('Top exclusive:', data['hotspots']['exclusive'][0]['name']) PY ``` 4. Feed the JSON into downstream tooling or an LLM to walk the call tree and identify expensive paths. ### Verification Run the parser test with: ```bash python3 -m unittest tools.tests.test_dtp_parser -v ``` ## Exit Codes | Code | Meaning | |------|---------| | 0 | Success | | 65 | Profiling failure | ## Notes - Snapshots consist of multiple files: `*.dtp`, `*.dtp.0000`, `*.dtp.0001`, etc. Keep them together. - Attach on macOS requires .NET 5 or later. - Use `--` before the executable path if arguments start with `-`. - The `snapshots/` directory is not tracked in git. Create it before profiling: ```bash mkdir -p snapshots ``` - The parser currently targets raw `.dtp` snapshots. Timeline `.dtt` snapshots are still intended for the GUI viewer.