🔍 Monitor Dotnet

📊 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
✨ Features
|
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 |
| ⏺️ |
Profiling |
Record a dotnet-trace profile in the Speedscope format |
| 🌍 |
Two languages |
English and Russian, following the VS Code display language |
| 📟 |
Status bar |
CPU load and private memory always in sight |
✅ Requirements
|
Requirement |
Why it is needed |
| 🖥️ |
VS Code 1.100 or newer |
Current webview, localization and tree APIs |
| 🟣 |
.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 (optional) |
Needed for a call stack snapshot, a memory snapshot and profiling. 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.
🚀 Quick start
- 📦 Install the extension (see Build from source below for a local build).
- ⌨️ Press
F5 on 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.
🐞 Debug integration
| 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.
🎯 Choosing the process
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 |
| ↔️ Drag |
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.
🩺 Automatic issue detection
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, ⚠️ warning, 🔴 critical), the moment it started and a short explanation. Click an
entry to select its time range — the charts zoom out to fit it and the range summary appears underneath.
Turn the whole thing off with 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.
🔢 Counters and metrics
The Counters view lists two kinds of data:
- 🧾 EventCounters — classic counters grouped by provider (
System.Runtime, Microsoft.AspNetCore.Hosting, your own EventSource)
- 📐 Metrics —
System.Diagnostics.Metrics instruments grouped by meter, including tagged series
Pick what to collect in the settings:
"monitor-dotnet.counterProviders": [
"System.Runtime", // every counter of the provider
"MyProvider[Counter1,Counter2]" // only the listed counters
],
"monitor-dotnet.metricProviders": ["*"] // all meters, or "MyMeter", or "MyMeter\\MyInstrument"
🌱 Environment
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
🛠 Profiling on demand
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 |
| ⏺️ Start / Stop Performance Profiling |
dotnet-trace |
.nettrace in Speedscope format |
If a tool is missing, the extension offers to install it with one command (dotnet tool install --global …).
⚙️ Settings
| 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 |
🌍 Languages
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 |
🏗️ How it works
┌────────────────────────── 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.
📁 Project structure
| 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 |
🧪 Build from source
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.0.0.vsix
code --install-extension monitor-dotnet-1.0.0.vsix
Useful 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 |
🔐 Privacy
- 🚫 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 install or 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).
📄 License
MIT — full text in LICENSE. © 2026 Dmitry Sorokin (dimanrus).