Free, open-source monitoring that replaces the tools charging you thousands per server per year. Specialized collectors, real-time alerts, and a built-in MCP server for AI analysis. Nothing phones home. Your data stays on your server and your machine.
Supported: SQL Server 2016–2025 | Azure SQL Managed Instance | AWS RDS for SQL Server | Azure SQL Database (Lite and Darling)
Pick by how you want collection to run — the monitoring brain (collectors, alert engine, plan analysis, MCP tools) is shared across all three at the library level.
| Lite — flagship | Darling — headless | Dashboard — deprecated | |
|---|---|---|---|
| How it runs | Single desktop app monitors remotely, on demand | Windows service collects 24/7 into a central store; detached viewer reads it from any seat | SQL-Server-installed database + Agent collectors, separate viewer app |
| Installs on your server? | No | No | Yes (a PerformanceMonitor database) |
| Stores data | Local DuckDB + Parquet | Bundled PostgreSQL + TimescaleDB | In the target SQL Server |
| Best for | Quick triage, Azure SQL DB, locked-down servers, consultants, firefighting | Always-on monitoring of many servers from one service | Existing installs only — new deployments should use Lite or Darling |
| Requires | VIEW SERVER STATE (permissions) |
VIEW SERVER STATE + a place to run the service |
SQL Agent (Dashboard docs) |
⚠️ The "Full" Dashboard edition is deprecated. It still ships and is supported for existing users, but it is no longer the recommended path. New deployments should use Lite or Darling. Its docs now live with the code: Dashboard/README.md (the app, tabs, permissions) and Installer/README.md (the CLI database installer). The Dashboard installer remains in the release assets.
👉 Not sure? Start with Lite. One download, nothing installed on your server, data flowing in under 5 minutes.
All editions include real-time alerts (system tray + email + webhooks), charts and graphs, dark and light themes, CSV export, and a built-in MCP server for AI-powered analysis with tools like Claude. All release binaries are digitally signed via SignPath — no more Windows SmartScreen warnings.
"You guys make us DBAs look like absolute rockstars. I'm over here getting showered with praise, and all I do is use your scripts and follow your advice."
"replaced SentryOne and had it running in 10 minutes"
"I've had enough time to gather data and converse with Claude on this. It helped a lot to zone in on CPU starvation from the hypervisor on which the VM runs. IT team currently investigating the host configuration."
🔍 Specialized collectors on configurable schedules — wait stats, query performance, blocking chains, deadlock graphs, memory grants, file I/O, tempdb, perfmon counters, FinOps/capacity, and more. Query text and execution plan collection can be disabled per-collector for sensitive environments.
🚨 Real-time alerts for blocking, deadlocks, and high CPU — system tray notifications, styled HTML emails with full XML attachments, and webhook notifications for external integrations
📊 NOC-style overview with green/yellow/red health cards, auto-refresh, configurable time ranges, and dark/light themes
📋 Graphical plan viewer with native ShowPlan rendering, 30-rule PlanAnalyzer, operator-level cost breakdown, and a standalone mode for opening .sqlplan files without a server connection
💡 Recommendations engine (advise-and-act) — a dedicated Recommendations tab surfaces prioritized findings from your own monitoring data with the reasoning behind each one, and can apply selected fixes directly. Destructive changes (like enabling Read Committed Snapshot Isolation) are gated behind an informed-consent dialog that spells out both the risk of acting and the risk of doing nothing.
🤖 Built-in MCP server with read-only tools for AI analysis — ask Claude Code or Cursor "what are the top wait types on my server?" and get answers from your actual monitoring data
🧰 Community tools installed automatically — sp_WhoIsActive, sp_BlitzLock, sp_HealthParser, sp_HumanEventsBlockViewer
🔒 Your data never leaves — no telemetry, no cloud dependency, no phoning home. Credentials stored in Windows Credential Manager with OS-level encryption.
- Download
PerformanceMonitorLite-win-Setup.exe(requires .NET 10 Desktop Runtime) - Run the installer — it installs to
%LocalAppData%\PerformanceMonitorLite, adds Start Menu and Desktop shortcuts, and registers the app under Apps & Features so it shows up in Windows search and can be uninstalled normally. Auto-update is wired in. - Launch from the Start Menu or Desktop shortcut.
- Click + Add Server, enter connection details, test, save.
- Double-click the server in the sidebar to connect.
Data starts flowing within 1–5 minutes. That's it. No installation on your server, no Agent jobs, no sysadmin required.
Upgrading from zip? Click Import Settings then Import Data in the sidebar and point both at your old Lite folder. Settings imports server connections, alert thresholds, SMTP config, and schedules. Data imports historical DuckDB + Parquet archives. Auto-update users (installed via Setup.exe) get updates automatically — no manual import needed.
Always On AG? Enable ReadOnlyIntent in the connection settings to route Lite's monitoring queries to a readable secondary, keeping the primary clear. Enable MultiSubnetFailover for multi-subnet failover scenarios.
36 collectors run on independent, configurable schedules (the long-running-query completion trace is opt-in and ships disabled):
| Collector | Default | Source |
|---|---|---|
| query_snapshots | 1 min | sys.dm_exec_requests + sys.dm_exec_sessions |
| blocked_process_report | 1 min | XE ring buffer session |
| waiting_tasks | 1 min | sys.dm_os_waiting_tasks |
| wait_stats | 1 min | sys.dm_os_wait_stats (deltas) |
| latch_stats | 1 min | sys.dm_os_latch_stats (deltas) |
| spinlock_stats | 1 min | sys.dm_os_spinlock_stats (deltas) |
| cpu_scheduler_stats | 1 min | sys.dm_os_schedulers runnable/blocked/queued task counts (not Azure SQL DB) |
| long_query_completions | 1 min (opt-in, ships off) | dedicated completion-trace XE (rpc_completed/sql_batch_completed over a duration threshold, plus attention) |
| query_stats | 1 min | sys.dm_exec_query_stats (deltas) |
| procedure_stats | 1 min | sys.dm_exec_procedure_stats (deltas) |
| cpu_utilization | 1 min | sys.dm_os_ring_buffers scheduler monitor |
| file_io_stats | 1 min | sys.dm_io_virtual_file_stats (deltas) |
| memory_stats | 1 min | sys.dm_os_sys_memory + memory counters |
| memory_grant_stats | 1 min | sys.dm_exec_query_memory_grants |
| tempdb_stats | 1 min | sys.dm_db_file_space_usage |
| perfmon_stats | 1 min | sys.dm_os_performance_counters (deltas) |
| deadlocks | 1 min | dedicated PerformanceMonitor_Deadlock XE session (xml_deadlock_report; database_xml_deadlock_report on Azure SQL DB) |
| dmv_blocking_snapshot | 1 min | sys.dm_os_waiting_tasks + sys.dm_exec_* (always-on blocking fallback when the blocked-process-report XE is unavailable) |
| session_stats | 5 min | sys.dm_exec_sessions active session tracking |
| session_summary_stats | 5 min | sys.dm_exec_sessions top app/host/database summary |
| memory_clerks | 5 min | sys.dm_os_memory_clerks |
| memory_pressure_events | 5 min | sys.dm_os_ring_buffers RING_BUFFER_RESOURCE_MONITOR |
| query_store | 5 min | Query Store DMVs (per database) |
| plan_cache_stats | 5 min | sys.dm_exec_cached_plans (single-use vs reused plan-cache bloat) |
| system_health_events | 5 min | system_health XE ring buffer (not Azure SQL DB) |
| default_trace_events | 5 min | default trace via sys.fn_trace_gettable |
| job_history | 5 min | msdb.dbo.sysjobhistory retained job-run history (not Azure SQL DB) |
| agent_status | 5 min | sys.dm_server_services + msdb.dbo.sysjobschedules (not Azure SQL DB / RDS) |
| running_jobs | 5 min | msdb job history with duration vs avg/p95 |
| database_size_stats | 1 hour | sys.master_files + FILEPROPERTY + dm_os_volume_stats |
| server_properties | on connect | SERVERPROPERTY() hardware and licensing metadata |
| index_object_stats | Daily | sys.dm_db_partition_stats + sys.dm_db_index_usage_stats + sys.dm_db_index_operational_stats |
| server_config | On connect | sys.configurations |
| database_config | On connect | sys.databases |
| database_scoped_config | On connect | Database-scoped configurations |
| trace_flags | On connect | DBCC TRACESTATUS |
Darling runs this same shared collector set across a fleet of servers (latch stats, spinlock stats, CPU scheduler, plan cache, and system_health parsing are now part of the shared catalog above, collected by Lite too) — see the Darling collector reference.
All data is stored in %LOCALAPPDATA%\PerformanceMonitorLite\ — separate from the executable, so auto-updates don't affect your data.
- Hot data in DuckDB 1.5.2 — non-blocking checkpoints, free block reuse, stable file size without periodic resets
- Archive to Parquet with ZSTD compression (~10x reduction) — automatic monthly compaction keeps file count low (~75 files vs thousands)
- Retention: 3-month calendar-month rolling window
- Typical size: ~50–200 MB per server per week
| File | Location | Purpose |
|---|---|---|
servers.json |
%ProgramData%\PerformanceMonitorLite\config\ (machine-wide) |
Server connections, shared across all Windows users on the machine. Passwords stay per-user in Windows Credential Manager. Optional Utility Database per server for community procs installed outside master. |
settings.json |
%LOCALAPPDATA%\PerformanceMonitorLite\config\ (per-user) |
Retention, MCP server, startup behavior, alert thresholds, SMTP configuration |
collection_schedule.json |
%LOCALAPPDATA%\PerformanceMonitorLite\config\ (per-user) |
Per-collector enable/disable and frequency |
ignored_wait_types.json |
%LOCALAPPDATA%\PerformanceMonitorLite\config\ (per-user) |
124 benign wait types excluded by default |
When a second Windows user on the same machine launches Lite, they see the shared servers.json immediately. SQL Auth and Entra MFA passwords are scoped to each user's own Credential Manager, so they'll be prompted once per server; Windows Auth works without any prompt.
Darling is the always-on edition for teams that want 24/7 collection without a desktop app driving it: a Windows service collects from your servers around the clock into a central PostgreSQL store (TimescaleDB is detected and adopted automatically for compression and chunk-based retention), and a detached WPF viewer reads that store from any seat. It runs the same monitoring brain as Lite — collectors, alert engine, and analysis pipeline shared at the library level — with alerts over email and Teams/Slack webhooks and the same MCP tool surface available on request. An optional built-in read-only web dashboard (off by default, its own port 5153) serves the fleet overview, per-server drill-down, and alert history to any browser, so operators can watch the fleet without installing the viewer.
- Download
PerformanceMonitorDarling-<version>.zipfrom the latest release — the signed service and viewer with the bundled PostgreSQL + TimescaleDB runtime beside the service exe, so a from-zero install needs no database provisioning. - Copy
darling.sample.jsontodarling.jsonand add your servers (and optional SMTP / webhook delivery). In managed mode the service unpacks and runs its own PostgreSQL — no external database to set up. - Run the service (console for a trial, or install it as a Windows service). It seeds the store and begins collecting on the same default cadences and retention horizons as a fresh Lite install.
- Open the viewer and point it at the store to browse the fleet.
Configuration is a single JSON file with no schedule knobs. See the Darling operator guide for the full quick start, configuration reference, permissions, and operations.
| Capability | Lite | Darling | Dashboard (deprecated) |
|---|---|---|---|
| Target server installation | None | None | Required |
| Runs collection | On-demand desktop app | 24/7 Windows service | SQL Agent on the target |
| Multi-server from one seat | Built-in | Built-in (central store) | Per-server install |
| Data storage | DuckDB + Parquet (local) | PostgreSQL + TimescaleDB (bundled) | SQL Server (on target) |
| Azure SQL Database | Supported | Supported | Not supported |
| Azure SQL MI / AWS RDS | Supported | Supported | Supported |
| Graphical plan viewer | Built-in, 30-rule PlanAnalyzer | Built-in, 30-rule PlanAnalyzer | Built-in, 30-rule PlanAnalyzer |
| Standalone plan viewer | Open/paste/drag .sqlplan |
Open/paste/drag .sqlplan |
Open/paste/drag .sqlplan |
| Alerts (tray + email + webhooks) | Yes | Email + webhooks (headless) | Yes |
| Themes | Dark and light | Dark and light | Dark and light |
| Portability | Single executable | Portable service + viewer zip | Server-bound |
| MCP server (LLM integration) | Built-in (74 tools) | On request | Built into Dashboard (66 tools) |
The Lite app and the Darling viewer share the same tab layout (the viewer is Lite's front end reading a Postgres store instead of local DuckDB):
| Tab | Contents |
|---|---|
| Overview | 2x2 resource chart grid (CPU, Memory, Wait Stats, TempDB) with drill-down |
| Active Queries | Running queries with session details, wait types, blocking, DOP, memory grants |
| Wait Stats | Filterable wait statistics chart with delta calculations |
| CPU | SQL Server CPU vs Other Processes over time |
| Memory | Physical memory overview, SQL Server memory trend, memory clerk breakdown, memory pressure events |
| Queries | Performance trends, top queries and procedures by duration, Query Store integration, query heatmap |
| File I/O | Read/write I/O trends per database file |
| TempDB | Space usage breakdown and TempDB file I/O |
| Blocking | Blocking/deadlock trends, blocked process reports, deadlock history, visual block-chain & deadlock-graph viewers |
| Perfmon | Selectable SQL Server performance counters over time |
| Configuration | Server configuration, database configuration, scoped configuration, trace flags |
| FinOps | Utilization & provisioning analysis, database resource breakdown, storage growth (7d/30d), idle database detection, index analysis via sp_IndexCleanup, per-object table/index size, growth, usage, and locking/contention analysis, application connections, server inventory, cost optimization recommendations, column-level filtering on all grids |
| Recommendations | Prioritized findings drawn from collected metrics, grouped into incidents, each card showing the affected database, the recommendation, the reasoning behind it, and a copyable MCP investigation prompt |
Both feature auto-refresh, configurable time ranges, chart drill-down to Active Queries, right-click CSV export, system tray integration, dark and light themes, and timezone display options (server time, local time, or UTC). The Darling viewer adds a fleet sidebar and per-server tabs; see Darling/README.md. The deprecated Dashboard's six-tab-group layout is documented in Dashboard/README.md.
Every edition includes a real-time alert engine that monitors for performance issues and sends notifications via system tray balloons (Lite/Dashboard), email, and webhooks.
| Metric | Default Threshold | Description |
|---|---|---|
| Blocking | 5 seconds | Fires when the longest blocked session exceeds the threshold |
| Deadlocks | 1 | Fires when new deadlocks are detected since the last check |
| Poison waits | 100 ms avg | Fires when any poison wait type exceeds the average-ms-per-wait threshold |
| Long-running queries | 5 minutes | Fires when any query exceeds the elapsed-time threshold |
| TempDB space | 80% | Fires when TempDB usage exceeds the percentage threshold |
| Long-running agent jobs | 3× average | Fires when a job's current duration exceeds a multiple of its historical average |
| High CPU | 80% | Fires when total CPU (SQL + other) exceeds the threshold |
| Volume free space | 10% or 5 GB free | Fires when a monitored volume's free space drops below the percentage or absolute threshold (either check can be disabled). Never fires on Azure SQL Database. |
| Failed agent job | 60-minute lookback | Fires when a SQL Agent job run fails within the lookback window. Skipped on Azure SQL Database. |
| Server unreachable | N/A | Fires when a monitored server goes offline or comes back online |
| Collection stopped | No run in 30 min | Fires when collection stalls (Agent/service stopped or collectors erroring). App-computed, so it survives the collector being off; clears with a "Collection Resumed" notice. Never fires on Azure SQL Database; degrades gracefully where msdb is restricted (e.g. AWS RDS). |
All thresholds are configurable in Settings.
Poison wait types monitored: THREADPOOL (worker thread exhaustion), RESOURCE_SEMAPHORE (memory grant pressure), and RESOURCE_SEMAPHORE_QUERY_COMPILE (compilation memory pressure). These waits indicate severe resource starvation and should never occur under normal operation.
- System tray — balloon notifications with a configurable per-metric cooldown (default: 5 minutes)
- Email (SMTP) — styled HTML emails with a configurable per-metric cooldown (default: 15 minutes), plus configurable SMTP settings (server, port, SSL, authentication, recipients)
- Webhook — HTTP POST to a configurable endpoint for integration with external alerting systems (Slack, Teams, PagerDuty, etc.)
All cooldown periods are independently configurable in Settings under the Performance Alerts section.
Alert emails include:
- Metric summary — what triggered the alert, current value vs threshold
- Detail section — recent blocking chains or deadlock participants with query text, wait times, lock modes, database names, and client application
- XML attachment — full
blocked_process_report.xmlordeadlock_graph.xmlfor offline analysis
- Resolved notifications — when a condition clears (e.g., blocking ends), a "Cleared" notification fires
- Server silencing — right-click a server to acknowledge alerts, silence all alerts, or unsilence
- Always-on — the alert engine runs independently of which tab is active, including when minimized to the system tray
- Alert history — Lite logs alerts to DuckDB (
config_alert_log); Darling logs to its Postgres store; both are accessible via MCP - Alert muting — create rules to suppress specific recurring alerts while still logging them. Rules match on server name, metric type, database, query text, wait type, or job name (AND logic across fields). Access via Settings → Manage Mute Rules, or right-click an alert in the Alert History tab. The context menu offers Mute This Alert (pre-fills server + metric) and Mute Similar Alerts (pre-fills metric only, matching across all servers). Muted alerts appear grayed out and are still recorded for auditability. Rules support optional expiration (1h, 24h, 7 days, or permanent).
- Alert details — right-click any alert in the Alert History tab and choose View Details for core fields (time, server, metric, value, threshold, notification type, status) plus context-sensitive details that vary by metric.
Every edition monitors currently running SQL Agent jobs and flags jobs running longer than expected.
| Metric | How It Works |
|---|---|
| Current duration | Elapsed time since the job started |
| Average duration | Historical mean from successful completions in msdb.dbo.sysjobhistory |
| p95 duration | 95th percentile from historical completions |
| Running long flag | Set when current duration exceeds the p95 threshold |
Lite and Darling query msdb directly on each collection cycle; all editions expose this data through the MCP get_running_jobs tool. Gracefully skipped on Azure SQL Database, AWS RDS for SQL Server, and environments without SQL Server Agent.
Every edition includes an embedded Model Context Protocol server that exposes monitoring data to LLM clients like Claude Code and Cursor.
- Enable the MCP server in Settings (checkbox + port, default
5151)- The port must be between 1024 and 65535. Ports 0–1023 are well-known privileged ports reserved by the operating system.
- On save, the app checks whether the chosen port is already in use and warns you if there is a conflict.
- On startup, the app verifies the port is available before starting the MCP server.
- Register with Claude Code:
claude mcp add --transport http --scope user sql-monitor http://localhost:5151/
- Open a new Claude Code session and ask questions like:
- "What servers are being monitored?"
- "What are the top wait types on my server?"
- "Are there any blocking or deadlock issues?"
- "Show me CPU utilization for the last 4 hours"
- "What are the most expensive queries by CPU?"
Lite exposes 74 tools; Darling exposes the analysis + data-read surface on request; the deprecated Dashboard exposes 66 (see Dashboard/README.md). Core tools are shared.
| Category | Tools |
|---|---|
| Discovery | list_servers |
| Health | get_server_summary, get_collection_health, get_daily_summary |
| Alerts | get_alert_history, get_alert_settings, get_mute_rules |
| Waits | get_wait_stats, get_wait_types, get_wait_trend, get_waiting_tasks |
| Queries | get_top_queries_by_cpu, get_top_procedures_by_cpu, get_query_store_top, get_query_duration_trend, get_query_trend |
| Active Queries | get_active_queries |
| CPU | get_cpu_utilization |
| Memory | get_memory_stats, get_memory_trend, get_memory_clerks, get_memory_grants, get_resource_semaphore |
| Blocking | get_deadlocks, get_deadlock_detail, get_blocked_process_reports, get_blocked_process_xml, get_blocking_trend, get_deadlock_trend |
| I/O | get_file_io_stats, get_file_io_trend |
| TempDB | get_tempdb_trend |
| Perfmon | get_perfmon_stats, get_perfmon_trend |
| Jobs | get_running_jobs |
| Configuration | get_server_config, get_database_config, get_database_scoped_config, get_trace_flags |
| Server Info | get_server_properties, get_database_sizes |
| Object/Index Stats | get_table_index_sizes, get_index_usage, get_object_locking |
| Sessions | get_session_stats |
| System Events | get_memory_pressure_events |
| Latches & Spinlocks | get_latch_stats, get_spinlock_stats |
| Plan Cache & Scheduler | get_plan_cache_bloat, get_cpu_scheduler_pressure |
| Long Queries | get_long_query_completions |
| Default Trace | get_default_trace_events |
| Config Changes | get_server_config_changes, get_database_config_changes, get_trace_flag_changes |
| Health Parser | get_health_parser_system_health, get_health_parser_severe_errors, get_health_parser_io_issues, get_health_parser_scheduler_issues, get_health_parser_memory_conditions, get_health_parser_cpu_tasks, get_health_parser_memory_broker, get_health_parser_memory_node_oom |
| Plan Analysis | analyze_query_plan, analyze_procedure_plan, analyze_query_store_plan, analyze_plan_xml, get_plan_xml |
| Diagnostic Analysis | analyze_server, get_analysis_facts, compare_analysis, audit_config, get_analysis_findings, mute_analysis_finding |
Most tools accept optional server_name and hours_back parameters. If only one server is configured, server_name is auto-resolved. The MCP server binds to localhost only and does not accept remote connections. (Darling adds windowed-trend and fleet-overview tools plus agent-driven write tools — Custom Views authoring, alert-settings and mute-rule tuning, and bulk add/remove servers — and supports an opt-in LAN endpoint — see Darling/README.md.)
- All queries use
READ UNCOMMITTEDisolation - Configurable collection intervals
- Lite: max 7 concurrent SQL connections, 30-second command timeout
- Darling: the service collects on the shared default cadences; TimescaleDB compresses the store
- DuckDB: ~50–200 MB per server per week
- Parquet archives: ~10x compression with ZSTD
- ScottPlot charts use hardware-accelerated rendering
Application logs are written to the logs/ folder. Collection success/failure is also logged to the collection_log table in DuckDB.
Common issues:
- No data after connecting — Wait for the first collection cycle (1–5 minutes). Check logs for connection errors.
- Query Store tab empty — Query Store must be enabled on the target database (
ALTER DATABASE [YourDB] SET QUERY_STORE = ON). - Blocked process reports empty — Lite attempts to auto-configure the blocked process threshold to 5 seconds via
sp_configure. On AWS RDS,sp_configureis not available — setblocked process threshold (s)through an RDS Parameter Group (see Platform Notes below). On Azure SQL Database, the threshold is fixed at 20 seconds and cannot be changed. If you still see no data on other platforms, verify the login hasALTER SETTINGSpermission. - Connection failures — Verify network connectivity, firewall rules, and that the login has the required permissions. For Azure SQL Database, use a contained database user with
VIEW DATABASE STATE. - FinOps Index Analysis hangs, times out, or returns
Msg 229onsql_expression_dependencies— see FinOps Index Analysis below for the full per-database grant set that fixes both failure modes.
Darling troubleshooting (service logs, store connectivity, permissions) is in the Darling operator guide. Dashboard (Full edition) troubleshooting is in Dashboard/README.md.
Every edition supports five authentication types, defined once in PerformanceMonitor.Common.AuthenticationTypes and shared by Lite, Darling, the Dashboard, and the CLI installer:
| Type | Interactive? | Credential stored? | Where |
|---|---|---|---|
| Windows | No | None | — |
| SQL Server | No | Password | Windows Credential Manager |
| Entra ID (MFA) | Yes, once per session | None | — |
| Service Principal | No | Client secret | Windows Credential Manager |
| Managed Identity | No | None | — |
Managed Identity and Service Principal are non-interactive Azure AD (Entra ID) authentication modes, added for fleet onboarding of Azure SQL Database / Managed Instance without a per-server interactive MFA prompt (see #1038). Both map directly to Microsoft.Data.SqlClient's native SqlAuthenticationMethod (ActiveDirectoryServicePrincipal / ActiveDirectoryManagedIdentity) — PerformanceMonitor never acquires, caches, or stores a token itself; the official Microsoft driver handles that internally.
- Managed Identity requires the machine running the app/service to itself be an Azure resource (VM, App Service, etc.) with a system- or user-assigned managed identity. That identity is then provisioned as a user directly on each target database (see Permissions below). Nothing is stored locally.
- Service Principal uses an Entra app registration's client id + secret. The client id is non-secret and stored in config; the secret is stored only in Windows Credential Manager, same as a SQL auth password.
For a fleet of servers sharing one identity — one managed identity or one service principal used across many Azure SQL databases — Lite has Credential Profiles: a named, reusable credential that any number of server entries can reference instead of each one carrying its own inline auth. Create one under Manage Servers → Credential Profiles…, then point server entries at it. Profiles live in profiles.json alongside servers.json; a Managed Identity profile stores no secret, and Service Principal / SQL Server profile secrets live in Windows Credential Manager, never in the JSON file.
Nothing is installed on the target server. The login only needs:
USE [master];
GRANT VIEW SERVER STATE TO [YourLogin];
-- Optional: for SQL Agent job monitoring
USE [msdb];
CREATE USER [YourLogin] FOR LOGIN [YourLogin];
ALTER ROLE [SQLAgentReaderRole] ADD MEMBER [YourLogin];Darling uses the same target-server grants; its bundled PostgreSQL store and service account are covered in the Darling operator guide. The deprecated Full edition's install/least-privilege grants are in Dashboard/README.md.
Applies to all editions. The FinOps Index Analysis tab runs sp_IndexCleanup against each user database you ask it to inspect, executing as your app login. The grants above (VIEW SERVER STATE, plus optional SQLAgentReaderRole on msdb) are not sufficient on their own — the login also needs a user mapping in every user database it will analyze, plus VIEW DATABASE STATE, VIEW DEFINITION, and SELECT on sys.sql_expression_dependencies in each.
The third grant is the easy one to miss: by default only members of db_owner have SELECT on sys.sql_expression_dependencies, and VIEW DEFINITION does not include it. sp_IndexCleanup queries that catalog view (via three-part name to the target database) when checking for computed columns and check constraints that reference UDFs, so the failure only surfaces on databases that actually have those — which is why a smoke-test database may pass and a real workload database fails with Msg 229.
For each target user database:
USE [YourTargetDatabase];
CREATE USER [SQLServerPerfMon] FOR LOGIN [SQLServerPerfMon];
GRANT VIEW DATABASE STATE TO [SQLServerPerfMon];
GRANT VIEW DEFINITION TO [SQLServerPerfMon];
GRANT SELECT ON sys.sql_expression_dependencies TO [SQLServerPerfMon];Or apply broadly with sp_MSforeachdb:
EXEC sp_MSforeachdb N'
USE [?];
IF DB_ID() > 4 AND DATABASEPROPERTYEX(DB_NAME(), ''Updateability'') = ''READ_WRITE''
BEGIN
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''SQLServerPerfMon'')
CREATE USER [SQLServerPerfMon] FOR LOGIN [SQLServerPerfMon];
GRANT VIEW DATABASE STATE TO [SQLServerPerfMon];
GRANT VIEW DEFINITION TO [SQLServerPerfMon];
GRANT SELECT ON sys.sql_expression_dependencies TO [SQLServerPerfMon];
END';Symptoms if missing. Two distinct failure modes depending on which grant is absent:
- No user mapping in the target database —
sp_IndexCleanupcan hang at 100% CPU with no waits and never return, instead of failing fast withMsg 916. It's a SQL Server engine bug where a permission check at execute time is misclassified as "this plan needs to be recompiled," producing an infinite recompile loop. Reproduces on SQL Server 2016 SP3 through 2025 CU4. - User is mapped with
VIEW DATABASE STATE+VIEW DEFINITIONbut noSELECTonsys.sql_expression_dependencies— fails fast withMsg 229, Level 14, State 5: The SELECT permission was denied on the object 'sql_expression_dependencies'the moment a database with a UDF-bound computed column or check constraint is reached.
Adding all three grants eliminates both. See issue #915 for the full diagnosis.
Azure SQL Database doesn't support server-level logins. Create a contained database user directly on the target database:
-- Connect to your target database (not master)
CREATE USER [SQLServerPerfMon] WITH PASSWORD = 'YourStrongPassword';
GRANT VIEW DATABASE STATE TO [SQLServerPerfMon];For Managed Identity or Service Principal authentication, create the contained user from the identity's display name instead of a password — an Azure AD admin must already be configured on the logical server:
-- Connect to your target database (not master)
CREATE USER [your-managed-identity-or-app-registration-name] FROM EXTERNAL PROVIDER;
GRANT VIEW DATABASE STATE TO [your-managed-identity-or-app-registration-name];For a large fleet, grant an Entra group instead of provisioning each identity individually where your architecture allows it. SQL Agent and msdb are not available on Azure SQL Database — those collectors are skipped automatically.
VIEW DATABASE STATEis what lets thesys.dm_os_*DMVs return server hardware inventory (CPU count, physical memory, socket and core topology) and memory metrics on each monitored database. If the contained user lacks it, edition, version, and storage still resolve from permission-free scalars, so the FinOps Server Inventory grid keeps the server's row and shows a non-alarming "Hardware Note" that hardware inventory is unavailable, instead of dropping the entire row (#1535).
Works like on-premises. Use server-level logins with VIEW SERVER STATE. SQL Agent is available.
For ongoing collection, VIEW SERVER STATE and msdb access work the same as on-premises, but sp_configure is not available (use RDS Parameter Groups instead — see Platform Notes).
sp_configure is not available on AWS RDS for SQL Server. Features that depend on server-level configuration must be set through AWS RDS Parameter Groups instead.
Blocked process threshold — Enables blocked-process-report collection (the richer XE-sourced blocking detail). Without it the blocked-process-report XE will not fire on RDS, but blocking is still captured by the always-on dmv_blocking_snapshot collector, so the blocking grid and block-chain viewer stay populated regardless.
- Open the AWS RDS Console and navigate to Parameter groups
- Create a new parameter group (or modify the one attached to your instance) — Family
sqlserver-ee-16.0(or your edition/version), Type DB Parameter Group - Search for
blocked process threshold (s)and set it to5(seconds) - Apply the parameter group to your RDS instance (may require a reboot if the parameter is static)
- Verify:
SELECT c.name, c.value_in_use FROM sys.configurations AS c WHERE c.name = N'blocked process threshold (s)';
Deadlocks — No parameter group configuration is required. The SQL Server deadlock monitor runs automatically on all platforms, and the xml_deadlock_report Extended Event fires without any threshold setting.
Azure SQL Database — The blocked process threshold is fixed at 20 seconds and cannot be changed. The blocked_process_report event fires automatically when blocking exceeds this duration.
PerformanceMonitor/
│
│ Lite Edition — standalone desktop app, nothing installed on server
├── Lite/ # Lite desktop application (WPF)
│
│ Darling Edition — headless service + bundled Postgres + viewer
├── Darling/ # Service, storage, analysis, viewer (see Darling/README.md)
│
│ Shared monitoring brain (collectors, alerting, analysis, MCP, UI)
├── PerformanceMonitor.*/ # Shared libraries used by every edition
│
│ Full Edition (deprecated) — server-installed collectors + separate dashboard
├── install/ # SQL installation scripts (Full edition)
├── upgrades/ # Version-specific upgrade scripts (Full edition)
├── Installer/ # CLI installer for the Full edition database (see Installer/README.md)
├── Installer.Core/ # Shared installation library (CLI + Dashboard)
├── Dashboard/ # Full edition dashboard app (see Dashboard/README.md)
│
└── README.md # This file
All projects target .NET 10.0.
# Lite Edition
dotnet build Lite/PerformanceMonitorLite.csproj
# Darling Edition (service + viewer)
dotnet build Darling/PerformanceMonitor.Darling.Service/PerformanceMonitor.Darling.Service.csproj
dotnet build Darling/PerformanceMonitor.Darling.Viewer/PerformanceMonitor.Darling.Viewer.csproj
# Full Edition (deprecated) — Dashboard app + CLI installer
dotnet build Dashboard/Dashboard.csproj
dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release
This project is free and open source under the MIT License. The software is fully functional with no features withheld — every user gets the same tool, same collectors, same MCP integration.
However, some organizations have procurement or compliance policies that require a formal vendor relationship, a support agreement, or an invoice on file before software can be deployed to production. If that sounds familiar, two commercial support tiers are available:
| Tier | Annual Cost | What You Get |
|---|---|---|
| Supported | $500/year | Email support (2-business-day response), compatibility guarantees for new SQL Server versions, vendor agreement and invoices for compliance, unlimited instances |
| Priority | $2,500/year | Next-business-day email response, quarterly live Q&A sessions, early access to new features, roadmap input, unlimited instances |
Both tiers cover unlimited SQL Server instances. The software itself is identical — commercial support is about the relationship, not a feature gate.
Read more about the free tool and commercial options | Purchase a support subscription
If you find the project valuable, you can also support continued development:
| Sponsor on GitHub | Become a sponsor to fund new features, ongoing maintenance, and SQL Server version support. |
| Consulting Services | Hire me for hands-on consulting if you need help analyzing the data this tool collects, or expert assistance fixing the issues it uncovers. |
Neither sponsorship nor consulting is required — use the tool freely.
- Author: Adam Machanic | License: GPLv3
- Repository: https://github.com/amachanic/sp_whoisactive
- Author: Erik Darling (Darling Data, LLC) | License: MIT
- Repository: https://github.com/erikdarlingdata/DarlingData
- Author: Brent Ozar Unlimited | License: MIT
- Repository: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit
See THIRD_PARTY_NOTICES.md for complete license texts.
| Free code signing on Windows provided by SignPath.io, certificate by SignPath Foundation |
Copyright (c) 2026 Darling Data, LLC. Licensed under the MIT License. See LICENSE for details.
Erik Darling — erikdarling.com — Darling Data, LLC




