Skip to content

Commit a2c0b93

Browse files
Merge pull request #838 from erikdarlingdata/release/v2.7.0
Release v2.7.0
2 parents 33ae14b + 87cdbaf commit a2c0b93

7 files changed

Lines changed: 108 additions & 87 deletions

File tree

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.7.0] - 2026-04-13
9+
10+
### Added
11+
12+
- **Host OS column** in Server Inventory for both Dashboard and Lite ([#748], [#823])
13+
- **Offline community script support** via `community/` directory for user-contributed scripts ([#814], [#822])
14+
- **MultiSubnetFailover connection option** in Dashboard and Lite for Always On availability groups ([#813], [#821])
15+
16+
### Changed
17+
18+
- **PlanAnalyzer and ShowPlanParser** synced from PerformanceStudio with latest improvements ([#816])
19+
- **MCP query tools** optimized for large databases ([#826])
20+
- **Add Server dialog UX** improved with inline connection status and full-height window
21+
- **"CPUs" renamed to "Logical CPUs"** for clarity in Lite ([#825])
22+
23+
### Fixed
24+
25+
- **Dashboard auto-refresh stalling under load** — replaced DispatcherTimer with async Task.Delay loop to prevent priority starvation during heavy chart rendering ([#833], [#834])
26+
- **Lite auto-refresh silently skipping** every tick ([#824])
27+
- **Deadlock count not resetting** between collections ([#803], [#820])
28+
- **Upgrade filter skipping patch versions** during version comparison ([#817], [#819])
29+
- **Upgrade script executing against master** instead of PerformanceMonitor database ([#828])
30+
- **Duplicate release builds** triggering on both created and published events
31+
32+
[#748]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/748
33+
[#803]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/803
34+
[#813]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/813
35+
[#814]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/814
36+
[#816]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/816
37+
[#817]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/817
38+
[#819]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/819
39+
[#820]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/820
40+
[#821]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/821
41+
[#822]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/822
42+
[#823]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/823
43+
[#824]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/824
44+
[#825]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/825
45+
[#826]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/826
46+
[#828]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/828
47+
[#833]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/833
48+
[#834]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/834
49+
850
## [2.6.0] - 2026-04-08
951

1052
### Added

Dashboard/Dashboard.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
<StartupObject>PerformanceMonitorDashboard.Program</StartupObject>
88
<AssemblyName>PerformanceMonitorDashboard</AssemblyName>
99
<Product>SQL Server Performance Monitor Dashboard</Product>
10-
<Version>2.6.0</Version>
11-
<AssemblyVersion>2.6.0.0</AssemblyVersion>
12-
<FileVersion>2.6.0.0</FileVersion>
13-
<InformationalVersion>2.6.0</InformationalVersion>
10+
<Version>2.7.0</Version>
11+
<AssemblyVersion>2.7.0.0</AssemblyVersion>
12+
<FileVersion>2.7.0.0</FileVersion>
13+
<InformationalVersion>2.7.0</InformationalVersion>
1414
<Company>Darling Data, LLC</Company>
1515
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
1616
<ApplicationIcon>EDD.ico</ApplicationIcon>

Dashboard/ServerTab.xaml.cs

Lines changed: 47 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Windows.Data;
66
using System.Text;
77
using System.Collections.Generic;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using System.Windows;
1011
using System.Windows.Controls;
@@ -46,6 +47,7 @@ public partial class ServerTab : UserControl
4647

4748
private readonly UserPreferencesService _preferencesService;
4849
private DispatcherTimer? _autoRefreshTimer;
50+
private CancellationTokenSource? _autoRefreshCts;
4951
private bool _isRefreshing;
5052
private DateTime _refreshStartedUtc;
5153
private bool _suppressPickerUpdates;
@@ -127,7 +129,6 @@ public ServerTab(ServerConnection serverConnection, int utcOffsetMinutes = 0)
127129

128130
InitializeDefaultTimeRanges();
129131
SetupChartContextMenus();
130-
SetupAutoRefresh();
131132
SetupSubTabContextMenus();
132133

133134
BlockingSlicer.RangeChanged += OnBlockingSlicerChanged;
@@ -343,42 +344,62 @@ private void SetupAutoRefresh()
343344

344345
if (prefs.AutoRefreshEnabled)
345346
{
346-
_autoRefreshTimer = new DispatcherTimer
347-
{
348-
Interval = TimeSpan.FromSeconds(prefs.AutoRefreshIntervalSeconds)
349-
};
350-
_autoRefreshTimer.Tick += async (s, e) =>
347+
StartAutoRefreshLoop(prefs.AutoRefreshIntervalSeconds);
348+
AutoRefreshToggle.IsChecked = true;
349+
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
350+
}
351+
else
352+
{
353+
AutoRefreshToggle.IsChecked = false;
354+
AutoRefreshToggle.Content = "Auto-Refresh: Off";
355+
}
356+
}
357+
358+
/// <summary>
359+
/// Async loop that replaces DispatcherTimer for auto-refresh. Task.Delay is not
360+
/// subject to Dispatcher priority starvation under heavy UI load (chart rendering,
361+
/// data binding) that can indefinitely defer Background-priority DispatcherTimer ticks.
362+
/// </summary>
363+
private async void StartAutoRefreshLoop(int intervalSeconds)
364+
{
365+
if (_autoRefreshCts != null && !_autoRefreshCts.IsCancellationRequested)
366+
return;
367+
368+
_autoRefreshCts?.Cancel();
369+
var cts = new CancellationTokenSource();
370+
_autoRefreshCts = cts;
371+
372+
try
373+
{
374+
while (!cts.Token.IsCancellationRequested)
351375
{
352-
_autoRefreshTimer?.Stop();
376+
await Task.Delay(TimeSpan.FromSeconds(intervalSeconds), cts.Token);
377+
if (cts.Token.IsCancellationRequested) break;
378+
353379
try
354380
{
381+
var sw = System.Diagnostics.Stopwatch.StartNew();
355382
await RefreshVisibleTabAsync();
356383
StatusText.Text = "Ready";
357384
FooterText.Text = $"Last refresh: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | Server: {_serverConnection.DisplayName}";
385+
Logger.Info($"Auto-refresh completed in {sw.ElapsedMilliseconds}ms for {_serverConnection.DisplayName}");
358386
}
359-
catch (Exception ex)
387+
catch (Exception ex) when (ex is not OperationCanceledException)
360388
{
361389
Logger.Error($"Auto-refresh error: {ex.Message}", ex);
362390
StatusText.Text = "Auto-refresh error";
363391
}
364-
finally
365-
{
366-
_autoRefreshTimer?.Start();
367-
}
368-
};
369-
_autoRefreshTimer.Start();
370-
AutoRefreshToggle.IsChecked = true;
371-
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
392+
}
372393
}
373-
else
394+
catch (OperationCanceledException)
374395
{
375-
AutoRefreshToggle.IsChecked = false;
376-
AutoRefreshToggle.Content = "Auto-Refresh: Off";
396+
// Normal shutdown
377397
}
378398
}
379399

380400
private void ServerTab_Unloaded(object sender, RoutedEventArgs e)
381401
{
402+
_autoRefreshCts?.Cancel();
382403
_autoRefreshTimer?.Stop();
383404
_autoRefreshTimer = null;
384405

@@ -448,7 +469,9 @@ private void OnThemeChanged(string _)
448469

449470
public void RefreshAutoRefreshSettings()
450471
{
451-
// Stop existing timer
472+
// Stop existing loop and timer
473+
_autoRefreshCts?.Cancel();
474+
_autoRefreshCts = null;
452475
_autoRefreshTimer?.Stop();
453476
_autoRefreshTimer = null;
454477

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

458481
if (prefs.AutoRefreshEnabled)
459482
{
460-
_autoRefreshTimer = new DispatcherTimer
461-
{
462-
Interval = TimeSpan.FromSeconds(prefs.AutoRefreshIntervalSeconds)
463-
};
464-
_autoRefreshTimer.Tick += async (s, e) =>
465-
{
466-
_autoRefreshTimer?.Stop();
467-
try
468-
{
469-
await RefreshVisibleTabAsync();
470-
StatusText.Text = "Ready";
471-
FooterText.Text = $"Last refresh: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | Server: {_serverConnection.DisplayName}";
472-
}
473-
catch (Exception ex)
474-
{
475-
Logger.Error($"Auto-refresh error: {ex.Message}", ex);
476-
StatusText.Text = "Auto-refresh error";
477-
}
478-
finally
479-
{
480-
_autoRefreshTimer?.Start();
481-
}
482-
};
483-
_autoRefreshTimer.Start();
483+
StartAutoRefreshLoop(prefs.AutoRefreshIntervalSeconds);
484484
AutoRefreshToggle.IsChecked = true;
485485
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
486486
}
@@ -506,30 +506,7 @@ private void AutoRefreshToggle_Click(object sender, RoutedEventArgs e)
506506
prefs.AutoRefreshEnabled = true;
507507
_preferencesService.SavePreferences(prefs);
508508

509-
_autoRefreshTimer = new DispatcherTimer
510-
{
511-
Interval = TimeSpan.FromSeconds(prefs.AutoRefreshIntervalSeconds)
512-
};
513-
_autoRefreshTimer.Tick += async (s, args) =>
514-
{
515-
_autoRefreshTimer?.Stop();
516-
try
517-
{
518-
await RefreshVisibleTabAsync();
519-
StatusText.Text = "Ready";
520-
FooterText.Text = $"Last refresh: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | Server: {_serverConnection.DisplayName}";
521-
}
522-
catch (Exception ex)
523-
{
524-
Logger.Error($"Auto-refresh error: {ex.Message}", ex);
525-
StatusText.Text = "Auto-refresh error";
526-
}
527-
finally
528-
{
529-
_autoRefreshTimer?.Start();
530-
}
531-
};
532-
_autoRefreshTimer.Start();
509+
StartAutoRefreshLoop(prefs.AutoRefreshIntervalSeconds);
533510
AutoRefreshToggle.Content = $"Auto-Refresh: {prefs.AutoRefreshIntervalSeconds}s";
534511
}
535512
else
@@ -538,8 +515,7 @@ private void AutoRefreshToggle_Click(object sender, RoutedEventArgs e)
538515
prefs.AutoRefreshEnabled = false;
539516
_preferencesService.SavePreferences(prefs);
540517

541-
_autoRefreshTimer?.Stop();
542-
_autoRefreshTimer = null;
518+
_autoRefreshCts?.Cancel();
543519
AutoRefreshToggle.Content = "Auto-Refresh: Off";
544520
}
545521
}
@@ -643,6 +619,7 @@ private async void ServerTab_Loaded(object sender, RoutedEventArgs e)
643619
DefaultTraceTab.SetTimeRange(_globalHoursBack, _globalFromDate, _globalToDate);
644620

645621
await LoadDataAsync();
622+
SetupAutoRefresh();
646623
}
647624
catch (Exception ex)
648625
{

Installer.Core/Installer.Core.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
<RootNamespace>Installer.Core</RootNamespace>
88
<AssemblyName>Installer.Core</AssemblyName>
99
<Product>SQL Server Performance Monitor Installer Core</Product>
10-
<Version>2.6.0</Version>
11-
<AssemblyVersion>2.6.0.0</AssemblyVersion>
12-
<FileVersion>2.6.0.0</FileVersion>
13-
<InformationalVersion>2.6.0</InformationalVersion>
10+
<Version>2.7.0</Version>
11+
<AssemblyVersion>2.7.0.0</AssemblyVersion>
12+
<FileVersion>2.7.0.0</FileVersion>
13+
<InformationalVersion>2.7.0</InformationalVersion>
1414
<Company>Darling Data, LLC</Company>
1515
<Copyright>Copyright (c) 2026 Darling Data, LLC</Copyright>
1616
<EnableNETAnalyzers>true</EnableNETAnalyzers>

Installer/PerformanceMonitorInstaller.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
<!-- Application metadata -->
2121
<AssemblyName>PerformanceMonitorInstaller</AssemblyName>
2222
<Product>SQL Server Performance Monitor Installer</Product>
23-
<Version>2.6.0</Version>
24-
<AssemblyVersion>2.6.0.0</AssemblyVersion>
25-
<FileVersion>2.6.0.0</FileVersion>
26-
<InformationalVersion>2.6.0</InformationalVersion>
23+
<Version>2.7.0</Version>
24+
<AssemblyVersion>2.7.0.0</AssemblyVersion>
25+
<FileVersion>2.7.0.0</FileVersion>
26+
<InformationalVersion>2.7.0</InformationalVersion>
2727
<Company>Darling Data, LLC</Company>
2828
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
2929
<Description>Installation utility for SQL Server Performance Monitor - Supports SQL Server 2016-2025</Description>

Lite/PerformanceMonitorLite.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
<AssemblyName>PerformanceMonitorLite</AssemblyName>
99
<RootNamespace>PerformanceMonitorLite</RootNamespace>
1010
<Product>SQL Server Performance Monitor Lite</Product>
11-
<Version>2.6.0</Version>
12-
<AssemblyVersion>2.6.0.0</AssemblyVersion>
13-
<FileVersion>2.6.0.0</FileVersion>
14-
<InformationalVersion>2.6.0</InformationalVersion>
11+
<Version>2.7.0</Version>
12+
<AssemblyVersion>2.7.0.0</AssemblyVersion>
13+
<FileVersion>2.7.0.0</FileVersion>
14+
<InformationalVersion>2.7.0</InformationalVersion>
1515
<Company>Darling Data, LLC</Company>
1616
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
1717
<Description>Lightweight SQL Server performance monitoring - no installation required on target servers</Description>

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Data starts flowing within 1–5 minutes. That's it. No installation on your ser
9999

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

102-
**Always On AG?** Enable **ReadOnlyIntent** in the connection settings to route Lite's monitoring queries to a readable secondary, keeping the primary clear.
102+
**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.
103103

104104
### Lite Collectors
105105

@@ -191,6 +191,8 @@ PerformanceMonitorInstaller.exe YourServerName sa YourPassword --uninstall
191191

192192
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.
193193

194+
**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.
195+
194196
### CLI Installer Options
195197

196198
| Option | Description |

0 commit comments

Comments
 (0)