Thank you for your interest in contributing to the SQL Server Performance Monitor! This guide will help you understand the project structure, set up your development environment, and submit high-quality contributions.
- Project Overview
- Development Setup
- Architecture Overview
- Contribution Paths
- Code Style Guidelines
- Pull Request Process
- Testing Guidelines
This repository contains two editions of the SQL Server Performance Monitor:
Full Edition — server-installed collectors with a separate dashboard:
| Folder | Description |
|---|---|
install/ |
50+ T-SQL scripts that create the PerformanceMonitor database |
Installer/ |
CLI installer for the Full Edition database and collectors |
Installer.Core/ |
Shared installation library (used by CLI installer and Dashboard) |
Dashboard/ |
WPF dashboard — connects to PerformanceMonitor database, can also install/upgrade via Add Server |
Lite Edition — standalone desktop app, nothing installed on the target server:
| Folder | Description |
|---|---|
Lite/ |
Standalone WPF app with embedded DuckDB, collects directly from DMVs over the network |
- Windows 10/11 (required for WPF)
- .NET 8.0 SDK (download)
- Visual Studio 2022 or VS Code with C# extension
- SQL Server (2016 or later) for testing
- Git for version control
# Clone the repository
git clone https://github.com/erikdarlingdata/PerformanceMonitor.git
cd PerformanceMonitor
# Build Full Dashboard
dotnet build Dashboard/Dashboard.csproj
# Build Lite Edition
dotnet build Lite/PerformanceMonitorLite.csproj
# Build CLI Installer (self-contained)
dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release
Full Dashboard:
- Install the database on a SQL Server instance using the installer
- Run
Dashboard/bin/Debug/net8.0-windows/Dashboard.exe - Add your server connection and start monitoring
Lite Edition:
- Run
Lite/bin/Debug/net8.0-windows/PerformanceMonitorLite.exe - Add a SQL Server connection (requires VIEW SERVER STATE permission)
- Data collection begins automatically
The Full Dashboard has a clean separation between data collection and display:
┌─────────────────────────────────────────────────────────────────┐
│ Monitored SQL Server │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PerformanceMonitor Database │ │
│ │ ├── collect.* tables (raw collected data) │ │
│ │ ├── dbo.collect_* procedures (29 collectors) │ │
│ │ ├── report.* views (reporting layer) │ │
│ │ └── config.* tables (schedules, retention, logs) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ SQL Agent Jobs (every 1 min) │
└───────────────────────────┼─────────────────────────────────────┘
│
│ SQL queries
▼
┌─────────────────────────────────────────────────────────────────┐
│ Dashboard Application │
│ ├── Services/DatabaseService.cs (data access layer) │
│ ├── Models/*.cs (data transfer objects) │
│ ├── ServerTab.xaml (main monitoring UI) │
│ └── Mcp/*.cs (MCP server for LLM integration) │
└─────────────────────────────────────────────────────────────────┘
Key insight: T-SQL collectors run independently from the C# Dashboard. You can modify collectors without touching C# code, and vice versa.
Warning: Lite has significantly more architectural complexity. A single new collector touches multiple layers:
┌─────────────────────────────────────────────────────────────────┐
│ Remote SQL Server │
│ (No installation required - just VIEW SERVER STATE) │
└───────────────────────────────────────────────────────────────┬─┘
│ DMV queries │
▼ │
┌─────────────────────────────────────────────────────────────────┐
│ Lite Application │
│ │
│ 1. RemoteCollectorService.*.cs │
│ └── Queries SQL Server DMVs, stores in DuckDB │
│ │
│ 2. Database/Schema.cs │
│ └── DuckDB table definitions (must match collector output) │
│ │
│ 3. LocalDataService.*.cs │
│ └── Reads from DuckDB for UI and MCP │
│ │
│ 4. Controls/ServerTab.xaml.cs │
│ └── UI display, charts, data grids │
│ │
│ 5. Services/ArchiveService.cs + RetentionService.cs │
│ └── Parquet archival and data cleanup │
│ │
│ 6. ScheduleManager.cs │
│ └── Controls when collectors run │
│ │
│ 7. Mcp/*.cs │
│ └── Exposes data to LLM clients │
└─────────────────────────────────────────────────────────────────┘
There is no clean single-layer contribution path for Lite collectors. Adding a new collector requires changes across 5-7 files with careful coordination.
| Area | Complexity | Notes |
|---|---|---|
| Full Dashboard T-SQL collectors | Low | Clean, self-contained stored procedures |
| Full Dashboard reporting views | Low | SQL views in install/47_create_reporting_views.sql |
| Dashboard UI improvements | Medium | WPF/C# in Dashboard/ServerTab.xaml* |
| Lite UI improvements | Medium | WPF/C# in Lite/Controls/ServerTab.xaml* |
| Bug fixes (either edition) | Varies | Always welcome |
| Documentation | Low | README, comments, troubleshooting guides |
| MCP tool improvements | Medium | Dashboard/Mcp/ or Lite/Mcp/ |
| New Lite collectors | High | See warning below |
This is the cleanest contribution path. You need to:
- Create the collection table in
install/02_create_tables.sql - Create the collector procedure in a new file
install/XX_collect_your_collector.sql - Register in the schedule (handled automatically by
install/41_schedule_management.sql) - Optionally add reporting views in
install/47_create_reporting_views.sql
See the existing collectors as templates. Each collector is self-contained.
Be aware: This is a significant undertaking. You must modify:
Lite/Database/Schema.cs- Add DuckDB table definitionLite/Services/RemoteCollectorService.{YourCollector}.cs- SQL query + DuckDB insert logicLite/Services/LocalDataService.{YourCollector}.cs- Query methods for UI/MCPLite/Controls/ServerTab.xamland.cs- UI elements, charts, gridsLite/Models/- Any new data transfer objectsLite/config/collection_schedule.json- Default schedule entryLite/Mcp/Mcp{Category}Tools.cs- MCP tool exposure (if applicable)
All of these must be coordinated. The schema must match what the collector inserts, the LocalDataService must query what the schema defines, the UI must display what LocalDataService returns, etc.
If you want to contribute a new Lite collector, please open an issue first to discuss the approach.
All T-SQL code must follow the project's coding standards. Key points:
- Keywords: UPPERCASE (
SELECT,FROM,WHERE) - Data types: lowercase, never abbreviated (
integernotint,nvarchar(max)notnvarchar(MAX)) - Object names: Use
sysnamefor SQL Server identifiers - Indentation: 4 spaces (never tabs)
- Table aliases: Always use
ASkeyword (FROM dbo.table AS t) - Column aliases: Use
column_name = expressionpattern - Commas: Trailing commas on multi-line lists
- Comments: Use
/* ... */block comments, never-- - Functions: Use
COUNT_BIG()notCOUNT(),ROWCOUNT_BIG()not@@ROWCOUNT
Example:
SELECT
database_name = d.name,
index_count = COUNT_BIG(i.index_id),
total_size_mb = SUM(a.total_pages) * 8 / 1024
FROM sys.databases AS d
JOIN sys.indexes AS i
ON i.database_id = d.database_id
WHERE d.database_id > 4
AND d.state_desc = N'ONLINE'
GROUP BY
d.name
ORDER BY
total_size_mb DESC
OPTION(RECOMPILE);The full T-SQL style guide is in CLAUDE.md.
Follow standard C# conventions:
- Naming: PascalCase for public members, _camelCase for private fields
- Async/await: Use for all I/O operations
- Null handling: Use nullable reference types, check for null appropriately
- Comments: XML documentation for public APIs
- File organization: One class per file, partial classes for large services
WPF-specific:
- Use data binding where practical
- Keep code-behind focused on UI logic
- Services handle data access and business logic
- Check existing issues - Your idea may already be discussed
- Open an issue first for significant changes
- Fork the repository and create a feature branch
Use descriptive branch names:
feature/add-memory-pressure-collectorfix/dashboard-connection-timeoutdocs/update-readme-lite-section
Write clear, descriptive commit messages:
Add memory pressure event collector
- Created collect.memory_pressure_events table
- Added dbo.collect_memory_pressure procedure
- Queries sys.dm_os_ring_buffers for memory broker events
- Runs every 5 minutes on default schedule
Before submitting:
- Code follows the style guidelines above
- T-SQL has been tested with
@debug = 1 - C# code compiles without warnings
- No hardcoded paths or credentials
- Changes are documented (comments, README if applicable)
- New files have copyright headers
- Submit your PR with a clear description
- Maintainers will review within a few days
- Address any feedback
- Once approved, your PR will be merged
Run collectors with debug output:
/* Test the collector */
EXECUTE dbo.collect_your_collector
@debug = 1;
/* Verify data was collected */
SELECT TOP (10) *
FROM collect.your_table
ORDER BY collection_time DESC;
/* Check collection log for errors */
SELECT TOP (10) *
FROM config.collection_log
WHERE collector_name = N'your_collector'
ORDER BY collection_time DESC;Use the troubleshooting scripts:
install/99_installer_troubleshooting.sql- Quick health checkinstall/99_user_troubleshooting.sql- Detailed diagnostics
- Build and run the Dashboard
- Connect to a test SQL Server with PerformanceMonitor installed
- Verify your changes appear correctly
- Test with different time ranges
- Check that CSV export still works (right-click any grid)
- Build and run the Lite application
- Connect to a test SQL Server (VIEW SERVER STATE permission required)
- Wait for initial collection (1-5 minutes)
- Verify data appears in the appropriate tab
- Test auto-refresh functionality
- If you added MCP tools, test with Claude Code:
claude mcp add --transport http --scope user sql-monitor http://localhost:5151/
Test against multiple versions if possible:
- SQL Server 2016 (minimum supported)
- SQL Server 2019
- SQL Server 2022
- Azure SQL Database (Lite only)
Some DMVs behave differently across versions. Handle version differences gracefully.
- Bug reports: Open a GitHub issue using the bug report template
- Feature requests: Open a GitHub issue using the feature request template
- General questions: Start a GitHub Discussion
Thank you for contributing!