📊 Real-time monitoring for .NET applications in VS Code — CPU load, memory, garbage collection, counters, metrics and environment variables. The monitor opens by itself when you start debugging. No web dashboard, no extra services — everything runs locally.
🇷🇺 Русская версия: README.ru.md · 📋 Changelog: CHANGELOG.md
| Feature | What you get | |
|---|---|---|
| 📈 | Performance charts | CPU load, garbage collection and memory drawn live on canvas |
| 🗑️ | GC timeline | Collection markers coloured by generation, with reason and pause duration |
| 🧮 | Range analysis | Drag a chart to get min / avg / max, GC count and average pause for the range |
| 🩺 | Issue detection | Suspicious intervals found automatically, as a native list and as bands on the charts |
| 🧾 | EventCounters | Classic .NET counters grouped by provider |
| 📐 | Metrics | System.Diagnostics.Metrics instruments grouped by meter |
| 🌱 | Environment | Environment variables and system properties of the process |
| 🐞 | Debug integration | Monitoring starts automatically on .NET debug sessions: coreclr, C# Dev Kit and Aspire |
| 🎯 | Process picker | Choose which running process to monitor when several applications are launched together |
| 🔌 | Attach to process | Connect to any running .NET process in two clicks |
| 🧵 | Call stack | Snapshot the call stack of the running process |
| 💾 | Memory snapshot | Collect a GCDump and reveal it in the file manager |
| 🧠 | Dump analysis | Run dotnet-dump SOS commands on a .dmp and get a graphical report with charts |
| 🗂️ | Dumps view | Every .dmp and .gcdump from the output folder in one list: open one for analysis or compare two files |
| 🧠 | Snapshot analysis | dotnet-gcdump report for a .gcdump: heap by type in the same panel, with comparison between two snapshots |
| ⏺️ | Profiling | Record a dotnet-trace profile in the Speedscope format, with a profile and a time limit |
| 🔄 | Trace files | Convert an existing .nettrace to Speedscope or show its hottest methods |
| 🌍 | Two languages | English and Russian, following the VS Code display language |
| 📟 | Status bar | CPU load and private memory always in sight |
| Requirement | Why it is needed | |
|---|---|---|
| 🖥️ | VS Code 1.100 or newer | Webview, localization and tree APIs in use. The minimum is enforced: @types/vscode is pinned to 1.100.0, so the build fails if an API newer than 1.100 is used |
| 🟣 | .NET 10 runtime | The bundled bridge that reads EventPipe is a net10.0 app, and it is started through dotnet. dotnet must be on PATH, otherwise set monitor-dotnet.dotnetPath |
| 🔬 | dotnet-stack, dotnet-gcdump, dotnet-trace, dotnet-dump, dotnet-symbol, dotnet-counters (optional) |
Needed for a call stack snapshot, memory snapshots, profiling, dumps, symbols and counter export. The extension offers to install a missing tool with one command |
For Aspire projects the Aspire CLI has to be installed: it is the CLI that starts the AppHost project. The Aspire extension for VS Code is optional — monitoring attaches to the same process as the debugger.
The application you monitor does not have to be .NET 10: EventPipe has been available since .NET Core 3.0, so .NET 5 apps and newer work as well. Windows, Linux and macOS are supported — private memory, environment variables and the command line are read through platform APIs. Mono and Unity are not supported.
- 📦 Install the extension (see Build from source below for a local build).
- ⌨️ Press
F5on any .NET project, or run Monitor Dotnet: Start Monitoring from the command palette. - 📊 The Monitor Dotnet panel appears in the activity bar with live charts and counters.
| How the application is started | Debug session type | Which process is monitored |
|---|---|---|
coreclr launch of an assembly (App.dll) |
coreclr |
The application process (apphost or dotnet host) |
C# Dev Kit project launch (projectPath in the configuration) |
dotnet |
The project's application process, not the dotnet run wrapper |
Aspire AppHost (aspire run under the debugger) |
aspire |
The AppHost process — the same one the debugger is attached to. Services started by Aspire are not monitored automatically: attach to one with Attach to .NET Process… |
Sessions of other .NET types (clr for .NET Framework, dotnet-core) are recognised as well. In an Aspire
solution the dashboard, the orchestrator and the CLI are never offered as targets.
F# projects work the same way: Ionide debugs them through the coreclr session and puts the built
assembly into program, and the same rules pick the application process. .fsproj is understood wherever
a project file can be given. Profiling itself is runtime-level — the bridge reads EventPipe — so the
language of the application does not matter.
When several applications run together — an Aspire AppHost with its services, or a compound launch — you pick which process to monitor. Process in the panel header lists everything running:
| Where the list lives | What it shows |
|---|---|
| Dropdown in the Performance panel | Debug sessions first, then other .NET processes, with the monitored one marked with a check mark |
| Monitor Dotnet: Select Process to Monitor… in the command palette (or the panel toolbar button) | The same list with PID and command line, so you can also attach to a process started without a debugger |
The process detected for a session is remembered, so switching is instant. Monitoring is never switched behind your back: when a second debug session starts, the first one keeps being monitored and the extension offers to switch. When the monitored session ends, the extension suggests another running process.
Three charts update once per second, plus a row of current-value tiles.
| Chart | What it shows |
|---|---|
| 📈 CPU load | Processor usage of the application; 100 % means all cores fully loaded |
| 🗑️ Garbage collection | Allocation rate with markers for every collection: colour = generation, tooltip = reason and pause |
| 🧠 Memory | Private memory (area), managed heap and working set |
| Action | Result |
|---|---|
| 🖱️ Hover | Tooltip with all series values and GC details in that moment |
| Select a time range; a summary appears below with min / avg / max, GC count and average pause | |
⌨️ Esc or double click |
Reset the selection |
| ⏸️ Pause | Freeze the view to inspect data; Resume pulls a fresh snapshot |
Colours come from the current theme, so the panel looks native in light, dark and high-contrast themes.
The extension watches the charts and marks suspicious time ranges on its own:
| Issue | When it is reported |
|---|---|
| 🔥 CPU saturation | Load stayed at 90 % or more for several refreshes in a row |
| 🗑️ Long GC pause | A single pause of 100 ms or more, 300 ms and above is critical |
| 🌊 GC pressure | 10 or more collections with 200 ms or more of pauses inside a 10-second window |
| 📈 Allocation spike | Allocation rate three times above the session median and at least 1 MB/s |
Every issue gets a coloured band on the matching chart and an entry in the list above the charts: the
severity badge (ℹ️ info, monitor-dotnet.enableIssueDetection.
The same list lives in the native Issues view next to the charts, with a theme icon per severity and the interval in the description. It works even with the panel closed: clicking an issue opens the charts and selects that interval.
The list is a report of the run, not a slice of the current data. Problems are kept per process: the chart panel and its bands always belong to the process shown on the charts, while the Issues view keeps the history and groups it by process (name and PID, with the monitored one marked, the monitored group expanded first).
Every issue keeps its own data. When the live charts have already moved past an interval — or it belongs to another process — clicking the issue shows that saved interval: the charts are drawn from the stored points with some context around it, a band marks the exact range, the summary below gives min / avg / max and GC numbers, and a banner names the process and offers Back to live data. So an old issue can still be analysed; only the points that were never collected are lost. The next debug session starts the list over, and Clear Collected Data forgets it as well.
The Counters view lists two kinds of data:
- 🧾 EventCounters — classic counters grouped by provider (
System.Runtime,Microsoft.AspNetCore.Hosting, your ownEventSource) - 📐 Metrics —
System.Diagnostics.Metricsinstruments grouped by meter, including tagged series
Pick what to collect in the settings:
Right-click any counter, meter or group and choose Add to Collection Filter: the extension updates the matching setting for you and restarts the bridge to apply it.
The Environment view shows what the application was started with:
- 🔤 environment variables of the process
- 🖥️ system properties: .NET version, startup flags, command line, OS, architecture
- 🔍 filter by name or value, and copy any value to the clipboard
Buttons in the Performance view title bar:
| Button | Tool | Result |
|---|---|---|
| 🧵 Collect Call Stack | dotnet-stack |
Call stack of every thread in a new editor tab |
| 💾 Get Memory Snapshot (GCDump) | dotnet-gcdump |
.gcdump file, revealed in the file manager; Analyse Memory Snapshot reads it back as a heap table |
| 🗄️ Take Memory Dump | dotnet-dump |
.dmp file of type Heap: stacks and managed memory for dotnet-dump analyze |
| ⏺️ Start / Stop Performance Profiling | dotnet-trace |
.nettrace and a ready .speedscope.json next to it; the profile and duration come from the settings |
| 🔄 Convert Trace to Speedscope… | dotnet-trace |
Converts a .nettrace from the output folder without recording a new one |
| 🔝 Show Top Methods in Trace… | dotnet-trace |
report topN: the methods that stayed on the call stack the longest |
| 📤 Record Counters to File | dotnet-counters |
CSV or JSON for 30 seconds, 1 minute or 5 minutes |
| 🏷️ Add Line Numbers to Stack Trace | dotnet-stack |
symbolicate: file and line for a stack trace from logs, taken from the editor |
| 🔎 Open Speedscope Profile | — | Opens the newest .speedscope.json from the output folder (a picker appears if there are several) |
| ⬇️ Download Symbols for Dumps | dotnet-symbol |
PDB/DBG files next to the dumps, so stack traces show function names |
Results go to the .monitor-dotnet folder of the project, together with a .gitignore that keeps them
out of the repository. Set monitor-dotnet.profilingOutputDirectory to put them somewhere else.
If a tool is missing, the extension offers to install it with one command (dotnet tool install --global …).
Dumps and snapshots are analysed from the Dumps view: click a file there (or right-click it in the Explorer) and the graphical report opens.
Dump analysis runs dotnet-dump analyze in non-interactive mode on the newest .dmp from the output folder
(or on the file you right-clicked in the Explorer). Pick one or more presets — Overview,
Thread stacks, Heap by type, Async and thread pool, Exceptions, Locks and Finalization queue —
or Custom SOS commands to type any command, one per line. The dump type is set by monitor-dotnet.dumpType
(Full, Heap, Mini, Triage), and monitor-dotnet.dumpCrashReport adds a crash report to it.
The result opens in a graphical panel styled like VS Code: overview metrics, automatic findings and a separate
interactive panel for every command: a thread table from clrthreads, GC segment sizes from eeheap -gc, a
filterable heap table, sync blocks, expandable thread stacks with application frames highlighted, thread pool
meters, exception cards with their stacks and the finalization queue grouped by type. Commands shared by
several presets are shown once. Any panel can be collapsed with a click, and long lists have search and
paging. The Details section keeps only the raw SOS output, and
Open text report shows the complete report in an editor tab.
A dump is a single moment in time, not a time range: the panel shows the process state at the moment the dump was taken. The header shows the dump file time and size separately from the report creation time. The live CPU and memory charts in the Performance panel are the time series; the dump page is one snapshot.
The Dumps view lists every .dmp and .gcdump from the output folder. Click a file to analyse it, or use
Compare Dumps… / Compare Memory Snapshots… to pick two files: the report shows what changed in the heap,
threads, async state machines and exception objects, with the types that grew or shrank the most.
A snapshot is read with dotnet-gcdump report: the same panel, but only heap data — the tool lists objects
grouped by exact size, and the extension sums them per type.
When you open a profile for the first time, the extension offers to install
Speedscope in VS Code and to add
the editor association that makes .speedscope.json files open in that viewer:
"workbench.editorAssociations": {
"*.speedscope.json": "speedscope-in-vscode.speedscope"
}Both steps are optional — without the viewer the file opens as plain JSON, and the association can be added later by hand.
| Setting | Default | Description |
|---|---|---|
monitor-dotnet.enableMonitoring |
onDebugOrRun |
When to start monitoring: onDebugOrRun, onDebug, never |
monitor-dotnet.autoReveal |
true |
Open the panel when a debug session starts |
monitor-dotnet.refreshIntervalSeconds |
1 |
How often counters and metrics are refreshed |
monitor-dotnet.chartWindowSeconds |
120 |
How many seconds of data the charts show |
monitor-dotnet.enableIssueDetection |
true |
Look for suspicious intervals on the charts |
monitor-dotnet.counterProviders |
base .NET set | EventCounters providers |
monitor-dotnet.metricProviders |
["*"] |
Meter filters |
monitor-dotnet.dotnetPath |
dotnet |
Path to the dotnet executable |
monitor-dotnet.bridgePath |
empty | Bridge assembly; empty means the bundled copy |
monitor-dotnet.processNameFilter |
empty | Extra substring to find the debuggee process |
monitor-dotnet.profilingOutputDirectory |
empty | Where profile and snapshot files are written |
monitor-dotnet.traceProfile |
default |
dotnet-trace profile: dotnet-common, dotnet-sampled-thread-time, gc-verbose, gc-collect or database |
monitor-dotnet.traceDurationSeconds |
0 |
Record the profile for this many seconds; 0 records until you stop it |
monitor-dotnet.dumpType |
Heap |
Dump type for dotnet-dump: Full, Heap, Mini or Triage |
monitor-dotnet.dumpCrashReport |
false |
Add a crash report to the memory dump |
The extension speaks the language of your VS Code interface.
- 🇬🇧 English — source language of every string
- 🇷🇺 Russian — complete translation
| What | Where the translation lives |
|---|---|
| Code strings: notifications, hints, log | l10n/bundle.l10n.<language>.json |
| Manifest: commands, views, settings | package.nls.<language>.json |
| Charts panel (webview) | texts are sent by the extension together with data |
┌────────────────────────── VS Code ──────────────────────────┐
│ Monitor Dotnet extension (TypeScript) │
│ │
│ extension.ts ──► MonitorController ──► Bridge process │
│ │ │ NDJSON (stdout)│
│ ▼ ▼ │
│ MonitorState (series, counters, metrics) BridgeLocator │
│ │ │
│ ├─► Performance panel (canvas charts, webview) │
│ ├─► Counters view (tree) │
│ ├─► Environment view (tree) │
│ └─► Status bar (CPU and memory) │
└──────────────────────────────────────────────────────────────┘
▲
│ one JSON message per line
▼
┌─────────── Bridge (MonitorDotnet.Bridge, .NET 10) ───────────┐
│ EventPipe session: │
│ • EventCounters → provider counters │
│ • System.Diagnostics.Metrics → meters │
│ • Microsoft-Windows-DotNETRuntime → GC events, heap stats │
│ Operating system: private memory, environment, command line │
└──────────────────────────────────────────────────────────────┘
A VS Code extension cannot read EventPipe directly, so collection happens in a small .NET process. It attaches to the target process, parses the event stream and sends ready-made samples to the extension.
| Path | Purpose |
|---|---|
src/core |
Framework-free logic: protocol, state, formatting, chart data, debuggee matching |
src/monitor |
Bridge lifecycle, .NET process discovery, profiling tool wrappers |
src/views |
Panels: Performance (webview provider), Counters and Environment (trees), status bar |
src/webview |
Code that runs inside the webview: canvas rendering and message handling |
src/extension.ts |
Activation, commands, debug session handling, context keys |
bridge/MonitorDotnet.Bridge |
.NET 10 bridge: EventPipe session, event parsing, process statistics |
bridge/MonitorDotnet.Bridge.Tests |
Bridge tests, including an end-to-end run against a real process |
samples/SampleApp |
Demo application with counters, metrics and load |
samples/WebApi |
ASP.NET Core web app for checking the coreclr and C# Dev Kit launch styles |
samples/AspireApp |
Aspire AppHost with one service, for checking the aspire debug session |
scripts/build.ts |
Builds the extension and the webview bundle with esbuild |
l10n, package.nls.* |
Translation dictionaries |
test |
Extension tests: activation, localization, end-to-end data flow |
Requirements: .NET SDK 10, Node.js 20+, VS Code 1.100+.
npm install
npm run bridge:build # publish the .NET bridge to media/bridge and dist/bridge
npm run build # bundle the extension and the charts panel
npm run package # create monitor-dotnet-1.2.0.vsix
code --install-extension monitor-dotnet-1.2.0.vsixUseful commands:
| Command | What it does |
|---|---|
npm run watch |
Rebuild the extension and the panel on change |
npm run typecheck |
Type check with TypeScript 7 (extension and webview separately) |
npm test |
Extension tests, including dictionary consistency |
npm run test:bridge |
Bridge tests with xunit |
MONITOR_DOTNET_E2E=1 npm test |
End-to-end checks on real processes: samples/SampleApp --stress (charts and issue detection), plus samples/WebApi and samples/AspireApp for the debug session types. The CPU overload check needs a free machine: on a busy one it reports that the load never reached the threshold instead of failing |
- 🚫 No telemetry, no accounts, nothing is uploaded: the extension collects data locally and never sends it anywhere. The only network access happens when you confirm it — installing a missing diagnostic tool with
dotnet tool installor opening the .NET diagnostics documentation. - 🏠 All data is collected locally by the bridge process and shown only inside VS Code.
- 🗂️ Profile files and memory snapshots are written where you point them (the temporary directory by default).
MIT — full text in LICENSE. © 2026 Dmitry Sorokin (dimanrus).


