Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.7.0] - 2026-04-13

### Added

- **Host OS column** in Server Inventory for both Dashboard and Lite ([#748], [#823])
- **Offline community script support** via `community/` directory for user-contributed scripts ([#814], [#822])
- **MultiSubnetFailover connection option** in Dashboard and Lite for Always On availability groups ([#813], [#821])

### Changed

- **PlanAnalyzer and ShowPlanParser** synced from PerformanceStudio with latest improvements ([#816])
- **MCP query tools** optimized for large databases ([#826])
- **Add Server dialog UX** improved with inline connection status and full-height window
- **"CPUs" renamed to "Logical CPUs"** for clarity in Lite ([#825])

### Fixed

- **Dashboard auto-refresh stalling under load** — replaced DispatcherTimer with async Task.Delay loop to prevent priority starvation during heavy chart rendering ([#833], [#834])
- **Lite auto-refresh silently skipping** every tick ([#824])
- **Deadlock count not resetting** between collections ([#803], [#820])
- **Upgrade filter skipping patch versions** during version comparison ([#817], [#819])
- **Upgrade script executing against master** instead of PerformanceMonitor database ([#828])
- **Duplicate release builds** triggering on both created and published events

[#748]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/748
[#803]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/803
[#813]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/813
[#814]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/814
[#816]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/816
[#817]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/817
[#819]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/819
[#820]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/820
[#821]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/821
[#822]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/822
[#823]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/823
[#824]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/824
[#825]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/825
[#826]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/826
[#828]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/828
[#833]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/833
[#834]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/834

## [2.6.0] - 2026-04-08

### Added
Expand Down
8 changes: 4 additions & 4 deletions Dashboard/Dashboard.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
<StartupObject>PerformanceMonitorDashboard.Program</StartupObject>
<AssemblyName>PerformanceMonitorDashboard</AssemblyName>
<Product>SQL Server Performance Monitor Dashboard</Product>
<Version>2.6.0</Version>
<AssemblyVersion>2.6.0.0</AssemblyVersion>
<FileVersion>2.6.0.0</FileVersion>
<InformationalVersion>2.6.0</InformationalVersion>
<Version>2.7.0</Version>
<AssemblyVersion>2.7.0.0</AssemblyVersion>
<FileVersion>2.7.0.0</FileVersion>
<InformationalVersion>2.7.0</InformationalVersion>
<Company>Darling Data, LLC</Company>
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
<ApplicationIcon>EDD.ico</ApplicationIcon>
Expand Down
117 changes: 47 additions & 70 deletions Dashboard/ServerTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Windows.Data;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
Expand Down Expand Up @@ -46,6 +47,7 @@ public partial class ServerTab : UserControl

private readonly UserPreferencesService _preferencesService;
private DispatcherTimer? _autoRefreshTimer;
private CancellationTokenSource? _autoRefreshCts;
private bool _isRefreshing;
private DateTime _refreshStartedUtc;
private bool _suppressPickerUpdates;
Expand Down Expand Up @@ -127,7 +129,6 @@ public ServerTab(ServerConnection serverConnection, int utcOffsetMinutes = 0)

InitializeDefaultTimeRanges();
SetupChartContextMenus();
SetupAutoRefresh();
SetupSubTabContextMenus();

BlockingSlicer.RangeChanged += OnBlockingSlicerChanged;
Expand Down Expand Up @@ -343,42 +344,62 @@ private void SetupAutoRefresh()

if (prefs.AutoRefreshEnabled)
{
_autoRefreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(prefs.AutoRefreshIntervalSeconds)
};
_autoRefreshTimer.Tick += async (s, e) =>
StartAutoRefreshLoop(prefs.AutoRefreshIntervalSeconds);
AutoRefreshToggle.IsChecked = true;
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
}
else
{
AutoRefreshToggle.IsChecked = false;
AutoRefreshToggle.Content = "Auto-Refresh: Off";
}
}

/// <summary>
/// Async loop that replaces DispatcherTimer for auto-refresh. Task.Delay is not
/// subject to Dispatcher priority starvation under heavy UI load (chart rendering,
/// data binding) that can indefinitely defer Background-priority DispatcherTimer ticks.
/// </summary>
private async void StartAutoRefreshLoop(int intervalSeconds)
{
if (_autoRefreshCts != null && !_autoRefreshCts.IsCancellationRequested)
return;

_autoRefreshCts?.Cancel();
var cts = new CancellationTokenSource();
_autoRefreshCts = cts;

try
{
while (!cts.Token.IsCancellationRequested)
{
_autoRefreshTimer?.Stop();
await Task.Delay(TimeSpan.FromSeconds(intervalSeconds), cts.Token);
if (cts.Token.IsCancellationRequested) break;

try
{
var sw = System.Diagnostics.Stopwatch.StartNew();
await RefreshVisibleTabAsync();
StatusText.Text = "Ready";
FooterText.Text = $"Last refresh: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | Server: {_serverConnection.DisplayName}";
Logger.Info($"Auto-refresh completed in {sw.ElapsedMilliseconds}ms for {_serverConnection.DisplayName}");
}
catch (Exception ex)
catch (Exception ex) when (ex is not OperationCanceledException)
{
Logger.Error($"Auto-refresh error: {ex.Message}", ex);
StatusText.Text = "Auto-refresh error";
}
finally
{
_autoRefreshTimer?.Start();
}
};
_autoRefreshTimer.Start();
AutoRefreshToggle.IsChecked = true;
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
}
}
else
catch (OperationCanceledException)
{
AutoRefreshToggle.IsChecked = false;
AutoRefreshToggle.Content = "Auto-Refresh: Off";
// Normal shutdown
}
}

private void ServerTab_Unloaded(object sender, RoutedEventArgs e)
{
_autoRefreshCts?.Cancel();
_autoRefreshTimer?.Stop();
_autoRefreshTimer = null;

Expand Down Expand Up @@ -448,7 +469,9 @@ private void OnThemeChanged(string _)

public void RefreshAutoRefreshSettings()
{
// Stop existing timer
// Stop existing loop and timer
_autoRefreshCts?.Cancel();
_autoRefreshCts = null;
_autoRefreshTimer?.Stop();
_autoRefreshTimer = null;

Expand All @@ -457,30 +480,7 @@ public void RefreshAutoRefreshSettings()

if (prefs.AutoRefreshEnabled)
{
_autoRefreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(prefs.AutoRefreshIntervalSeconds)
};
_autoRefreshTimer.Tick += async (s, e) =>
{
_autoRefreshTimer?.Stop();
try
{
await RefreshVisibleTabAsync();
StatusText.Text = "Ready";
FooterText.Text = $"Last refresh: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | Server: {_serverConnection.DisplayName}";
}
catch (Exception ex)
{
Logger.Error($"Auto-refresh error: {ex.Message}", ex);
StatusText.Text = "Auto-refresh error";
}
finally
{
_autoRefreshTimer?.Start();
}
};
_autoRefreshTimer.Start();
StartAutoRefreshLoop(prefs.AutoRefreshIntervalSeconds);
AutoRefreshToggle.IsChecked = true;
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
}
Expand All @@ -506,30 +506,7 @@ private void AutoRefreshToggle_Click(object sender, RoutedEventArgs e)
prefs.AutoRefreshEnabled = true;
_preferencesService.SavePreferences(prefs);

_autoRefreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(prefs.AutoRefreshIntervalSeconds)
};
_autoRefreshTimer.Tick += async (s, args) =>
{
_autoRefreshTimer?.Stop();
try
{
await RefreshVisibleTabAsync();
StatusText.Text = "Ready";
FooterText.Text = $"Last refresh: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | Server: {_serverConnection.DisplayName}";
}
catch (Exception ex)
{
Logger.Error($"Auto-refresh error: {ex.Message}", ex);
StatusText.Text = "Auto-refresh error";
}
finally
{
_autoRefreshTimer?.Start();
}
};
_autoRefreshTimer.Start();
StartAutoRefreshLoop(prefs.AutoRefreshIntervalSeconds);
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
}
else
Expand All @@ -538,8 +515,7 @@ private void AutoRefreshToggle_Click(object sender, RoutedEventArgs e)
prefs.AutoRefreshEnabled = false;
_preferencesService.SavePreferences(prefs);

_autoRefreshTimer?.Stop();
_autoRefreshTimer = null;
_autoRefreshCts?.Cancel();
AutoRefreshToggle.Content = "Auto-Refresh: Off";
}
}
Expand Down Expand Up @@ -643,6 +619,7 @@ private async void ServerTab_Loaded(object sender, RoutedEventArgs e)
DefaultTraceTab.SetTimeRange(_globalHoursBack, _globalFromDate, _globalToDate);

await LoadDataAsync();
SetupAutoRefresh();
}
catch (Exception ex)
{
Expand Down
8 changes: 4 additions & 4 deletions Installer.Core/Installer.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
<RootNamespace>Installer.Core</RootNamespace>
<AssemblyName>Installer.Core</AssemblyName>
<Product>SQL Server Performance Monitor Installer Core</Product>
<Version>2.6.0</Version>
<AssemblyVersion>2.6.0.0</AssemblyVersion>
<FileVersion>2.6.0.0</FileVersion>
<InformationalVersion>2.6.0</InformationalVersion>
<Version>2.7.0</Version>
<AssemblyVersion>2.7.0.0</AssemblyVersion>
<FileVersion>2.7.0.0</FileVersion>
<InformationalVersion>2.7.0</InformationalVersion>
<Company>Darling Data, LLC</Company>
<Copyright>Copyright (c) 2026 Darling Data, LLC</Copyright>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
Expand Down
8 changes: 4 additions & 4 deletions Installer/PerformanceMonitorInstaller.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
<!-- Application metadata -->
<AssemblyName>PerformanceMonitorInstaller</AssemblyName>
<Product>SQL Server Performance Monitor Installer</Product>
<Version>2.6.0</Version>
<AssemblyVersion>2.6.0.0</AssemblyVersion>
<FileVersion>2.6.0.0</FileVersion>
<InformationalVersion>2.6.0</InformationalVersion>
<Version>2.7.0</Version>
<AssemblyVersion>2.7.0.0</AssemblyVersion>
<FileVersion>2.7.0.0</FileVersion>
<InformationalVersion>2.7.0</InformationalVersion>
<Company>Darling Data, LLC</Company>
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
<Description>Installation utility for SQL Server Performance Monitor - Supports SQL Server 2016-2025</Description>
Expand Down
8 changes: 4 additions & 4 deletions Lite/PerformanceMonitorLite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
<AssemblyName>PerformanceMonitorLite</AssemblyName>
<RootNamespace>PerformanceMonitorLite</RootNamespace>
<Product>SQL Server Performance Monitor Lite</Product>
<Version>2.6.0</Version>
<AssemblyVersion>2.6.0.0</AssemblyVersion>
<FileVersion>2.6.0.0</FileVersion>
<InformationalVersion>2.6.0</InformationalVersion>
<Version>2.7.0</Version>
<AssemblyVersion>2.7.0.0</AssemblyVersion>
<FileVersion>2.7.0.0</FileVersion>
<InformationalVersion>2.7.0</InformationalVersion>
<Company>Darling Data, LLC</Company>
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
<Description>Lightweight SQL Server performance monitoring - no installation required on target servers</Description>
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Data starts flowing within 1–5 minutes. That's it. No installation on your ser

**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.
**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.

### Lite Collectors

Expand Down Expand Up @@ -191,6 +191,8 @@ PerformanceMonitorInstaller.exe YourServerName sa YourPassword --uninstall

The installer automatically tests the connection, checks the SQL Server version (2016+ required), executes SQL scripts, downloads community dependencies, creates SQL Agent jobs, and runs initial data collection. You can also install directly from the Dashboard's Add Server dialog.

**Air-gapped environments?** Place pre-downloaded community scripts (`sp_WhoIsActive.sql`, `DarlingData.sql`, `Install-All-Scripts.sql`) in a `community/` directory next to the installer. The installer uses local files when present and falls back to GitHub downloads otherwise.

### CLI Installer Options

| Option | Description |
Expand Down
Loading