Skip to content

Commit 6e199c8

Browse files
v2.1.0 release prep: changelog, README, upgrade scripts, version properties, installer validation fix (#400)
- CHANGELOG.md: full 2.1.0 entry (light themes, plan viewer, optional query/plan collection, timezone display, CLI flags) - README.md: updated feature descriptions, collector counts, edition comparison table - Upgrade script: added 02_default_trace_events_new_columns.sql for duration_us/end_time columns missing on upgrade from 2.0.0 - Version properties: added explicit AssemblyVersion/FileVersion/InformationalVersion to Dashboard, Lite, InstallerGui csprojs - Installer validation fix: both CLI and GUI installers now filter collection_log errors by validation start time instead of reporting all historical errors Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1dc5eab commit 6e199c8

9 files changed

Lines changed: 208 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,57 @@ 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.1.0] - 2026-03-04
9+
10+
### Important
11+
12+
- **Schema upgrade**: The `config.collection_schedule` table gains two new columns (`collect_query`, `collect_plan`) for optional query text and execution plan collection. Both default to enabled to preserve existing behavior. Upgrade scripts run automatically via the CLI/GUI installer and use idempotent checks.
13+
14+
### Added
15+
16+
- **Light theme and "Cool Breeze" theme** — full light mode support for both Dashboard and Lite with live preview in settings ([#347])
17+
- **Standalone Plan Viewer** — open, paste (Ctrl+V), or drag & drop `.sqlplan` files independent of any server connection, with tabbed multi-plan support ([#359])
18+
- **Time display mode toggle** — show timestamps in Server Time, Local Time, or UTC with timezone labels across all grids and tooltips ([#17])
19+
- **30 PlanAnalyzer rules** — expanded from 12 to 30 rules covering implicit conversions, GetRangeThroughConvert, lazy spools, OR expansion, exchange spills, RID lookups, and more ([#327], [#349], [#356], [#379])
20+
- **Wait stats banner** in plan viewer showing top waits for the query ([#373])
21+
- **UDF runtime details** — CPU and elapsed time shown in Runtime Summary pane when UDFs are present ([#382])
22+
- **Sortable statement grid** and canvas panning in plan viewer ([#331])
23+
- **Comma-separated column filters** — enter multiple values separated by commas in text filters ([#348])
24+
- **Optional query text and plan collection** — per-collector flags in `config.collection_schedule` to disable query text or plan capture ([#337])
25+
- **`--preserve-jobs` installer flag** — keep existing SQL Agent job schedules during upgrade ([#326])
26+
- **Copy Query Text** context menu on Dashboard statements grid ([#367])
27+
- **Server list sorting** by display name in both Dashboard and Lite ([#30])
28+
- **Warning status icon** in server health indicators ([#355])
29+
- Reserved threads and 10 missing ShowPlan XML attributes in plan viewer ([#378])
30+
- Nightly build workflow for CI ([#332])
31+
32+
### Changed
33+
34+
- PlanAnalyzer warning messages rewritten to be actionable with expert-guided per-rule advice ([#370], [#371])
35+
- PlanAnalyzer rule tuning: time-based spill analysis (Rule 7), lowered parallel skew thresholds (Rule 8), memory grant floor raised to 1GB/4GB (Rule 9), skip PROBE-only bitmap predicates (Rule 11) ([#341], [#342], [#343], [#358])
36+
- First-run collector lookback reduced from 3-7 days to 1 hour for faster initial data ([#335])
37+
- Plan canvas aligns top-left and resets scroll on statement switch ([#366])
38+
- Plan viewer polish: index suggestions, property panel improvements, muted brush audit ([#365])
39+
- Add Server dialog visual parity between Dashboard and Lite with theme-driven PasswordBox styling ([#289])
40+
41+
### Fixed
42+
43+
- **OverflowException** on wait stats page with large decimal values — SQL Server `decimal(38,24)` exceeding .NET precision ([#395])
44+
- **SQL dumps** on mirroring passive servers with RESTORING databases ([#384])
45+
- **UI hang** when adding first server to Dashboard ([#387])
46+
- **UTC/local timezone mismatch** in blocked process XML processor ([#383])
47+
- **AG secondary filter** skipping all inaccessible databases in cross-database collectors ([#325])
48+
- DuckDB column aliases in long-running queries ([#391])
49+
- sp_server_diagnostics and WAITFOR excluded from long-running query alerts ([#362])
50+
- UDF timing units corrected: microseconds to milliseconds ([#338])
51+
- DuckDB migration ordering after archive-and-reset ([#314])
52+
- Int16 cast error in long-running query alerts ([#313])
53+
- Missing dark mode on 19 SystemEventsContent charts ([#321])
54+
- Missing tooltips on charts after theme changes ([#319])
55+
- Operator time per-thread calculation synced across all plan viewers ([#392])
56+
- Theme StaticResource/DynamicResource binding fix for runtime theme switching
57+
- Memory grant MB display, missing index quality scoring, wildcard LIKE detection ([#393])
58+
859
## [2.0.0] - 2026-02-25
960

1061
### Important
@@ -180,6 +231,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
180231
- Delta normalization for per-second rate calculations
181232
- Dark theme UI
182233

234+
[2.1.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v2.0.0...v2.1.0
183235
[2.0.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.3.0...v2.0.0
184236
[1.3.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.2.0...v1.3.0
185237
[1.2.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.1.0...v1.2.0
@@ -274,3 +326,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
274326
[#281]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/281
275327
[#284]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/284
276328
[#287]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/287
329+
[#313]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/313
330+
[#314]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/314
331+
[#17]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/17
332+
[#30]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/30
333+
[#319]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/319
334+
[#321]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/321
335+
[#325]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/325
336+
[#326]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/326
337+
[#327]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/327
338+
[#331]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/331
339+
[#332]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/332
340+
[#335]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/335
341+
[#337]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/337
342+
[#338]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/338
343+
[#341]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/341
344+
[#342]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/342
345+
[#343]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/343
346+
[#347]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/347
347+
[#348]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/348
348+
[#349]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/349
349+
[#355]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/355
350+
[#356]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/356
351+
[#358]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/358
352+
[#359]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/359
353+
[#362]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/362
354+
[#365]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/365
355+
[#366]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/366
356+
[#367]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/367
357+
[#370]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/370
358+
[#371]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/371
359+
[#373]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/373
360+
[#378]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/378
361+
[#379]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/379
362+
[#382]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/382
363+
[#383]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/383
364+
[#384]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/384
365+
[#387]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/387
366+
[#391]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/391
367+
[#392]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/392
368+
[#393]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/393
369+
[#289]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/289
370+
[#395]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/395

Dashboard/Dashboard.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
<AssemblyName>PerformanceMonitorDashboard</AssemblyName>
88
<Product>SQL Server Performance Monitor Dashboard</Product>
99
<Version>2.1.0</Version>
10+
<AssemblyVersion>2.1.0.0</AssemblyVersion>
11+
<FileVersion>2.1.0.0</FileVersion>
12+
<InformationalVersion>2.1.0</InformationalVersion>
1013
<Company>Darling Data, LLC</Company>
1114
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
1215
<ApplicationIcon>EDD.ico</ApplicationIcon>

Installer/Program.cs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,14 @@ This validates the installation and creates dynamically-generated tables
775775
{
776776
await connection.OpenAsync().ConfigureAwait(false);
777777

778+
/*Capture timestamp before running so we only check errors from this run.
779+
Use SYSDATETIME() (local) because collection_time is stored in server local time.*/
780+
DateTime validationStart;
781+
using (var command = new SqlCommand("SELECT SYSDATETIME();", connection))
782+
{
783+
validationStart = (DateTime)(await command.ExecuteScalarAsync().ConfigureAwait(false))!;
784+
}
785+
778786
/*Run master collector once with @force_run_all to collect everything immediately*/
779787
Console.Write("Executing master collector... ");
780788
using (var command = new SqlCommand("EXECUTE PerformanceMonitor.collect.scheduled_master_collector @force_run_all = 1, @debug = 0;", connection))
@@ -785,37 +793,46 @@ This validates the installation and creates dynamically-generated tables
785793
Console.WriteLine("✓ Success");
786794

787795
/*
788-
Verify data was collected
796+
Verify data was collected — only from this validation run, not historical errors
789797
*/
790798
Console.WriteLine();
791799
Console.Write("Verifying data collection... ");
792800

793-
/* First check total count in collection_log */
794-
int totalLogEntries = 0;
795-
using (var command = new SqlCommand(@"
796-
SELECT COUNT(*) FROM PerformanceMonitor.config.collection_log;", connection))
797-
{
798-
totalLogEntries = (int)(await command.ExecuteScalarAsync().ConfigureAwait(false) ?? 0);
799-
}
800-
801-
/* Check successful collections (all time - we just installed) */
801+
/* Check successful collections from this run */
802802
int collectedCount = 0;
803803
using (var command = new SqlCommand(@"
804804
SELECT
805805
COUNT(DISTINCT collector_name)
806806
FROM PerformanceMonitor.config.collection_log
807-
WHERE collection_status = 'SUCCESS';", connection))
807+
WHERE collection_status = 'SUCCESS'
808+
AND collection_time >= @validation_start;", connection))
808809
{
810+
command.Parameters.AddWithValue("@validation_start", validationStart);
809811
collectedCount = (int)(await command.ExecuteScalarAsync().ConfigureAwait(false) ?? 0);
810812
}
811813

814+
/* Total log entries from this run */
815+
int totalLogEntries = 0;
816+
using (var command = new SqlCommand(@"
817+
SELECT COUNT(*)
818+
FROM PerformanceMonitor.config.collection_log
819+
WHERE collection_time >= @validation_start;", connection))
820+
{
821+
command.Parameters.AddWithValue("@validation_start", validationStart);
822+
totalLogEntries = (int)(await command.ExecuteScalarAsync().ConfigureAwait(false) ?? 0);
823+
}
824+
812825
Console.WriteLine($"✓ {collectedCount} collectors ran successfully (total log entries: {totalLogEntries})");
813826

814-
/* Show failed collectors if any */
827+
/* Show failed collectors from this run */
815828
int errorCount = 0;
816829
using (var command = new SqlCommand(@"
817-
SELECT COUNT(*) FROM PerformanceMonitor.config.collection_log WHERE collection_status = 'ERROR';", connection))
830+
SELECT COUNT(*)
831+
FROM PerformanceMonitor.config.collection_log
832+
WHERE collection_status = 'ERROR'
833+
AND collection_time >= @validation_start;", connection))
818834
{
835+
command.Parameters.AddWithValue("@validation_start", validationStart);
819836
errorCount = (int)(await command.ExecuteScalarAsync().ConfigureAwait(false) ?? 0);
820837
}
821838

@@ -829,8 +846,10 @@ FROM PerformanceMonitor.config.collection_log
829846
error_message
830847
FROM PerformanceMonitor.config.collection_log
831848
WHERE collection_status = 'ERROR'
849+
AND collection_time >= @validation_start
832850
ORDER BY collection_time DESC;", connection))
833851
{
852+
command.Parameters.AddWithValue("@validation_start", validationStart);
834853
using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false))
835854
{
836855
while (await reader.ReadAsync().ConfigureAwait(false))
@@ -857,8 +876,10 @@ SELECT TOP 5
857876
error_message
858877
FROM PerformanceMonitor.config.collection_log
859878
WHERE collection_status = 'SUCCESS'
879+
AND collection_time >= @validation_start
860880
ORDER BY collection_time DESC;", connection))
861881
{
882+
command.Parameters.AddWithValue("@validation_start", validationStart);
862883
using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false))
863884
{
864885
while (await reader.ReadAsync().ConfigureAwait(false))

InstallerGui/InstallerGui.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
<RootNamespace>PerformanceMonitorInstallerGui</RootNamespace>
1010
<Product>SQL Server Performance Monitor Installer</Product>
1111
<Version>2.1.0</Version>
12+
<AssemblyVersion>2.1.0.0</AssemblyVersion>
13+
<FileVersion>2.1.0.0</FileVersion>
14+
<InformationalVersion>2.1.0</InformationalVersion>
1215
<Company>Darling Data, LLC</Company>
1316
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
1417
<ApplicationIcon>EDD.ico</ApplicationIcon>

InstallerGui/Services/InstallationService.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,14 @@ public async Task<int> InstallDependenciesAsync(
659659
using var connection = new SqlConnection(connectionString);
660660
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
661661

662+
/*Capture timestamp before running so we only check errors from this run.
663+
Use SYSDATETIME() (local) because collection_time is stored in server local time.*/
664+
DateTime validationStart;
665+
using (var command = new SqlCommand("SELECT SYSDATETIME();", connection))
666+
{
667+
validationStart = (DateTime)(await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false))!;
668+
}
669+
662670
/*Run master collector with @force_run_all*/
663671
progress?.Report(new InstallationProgress
664672
{
@@ -680,16 +688,18 @@ public async Task<int> InstallDependenciesAsync(
680688
Status = "Success"
681689
});
682690

683-
/*Check results*/
691+
/*Check results — only from this validation run, not historical errors*/
684692
int successCount = 0;
685693
int errorCount = 0;
686694

687695
using (var command = new SqlCommand(@"
688696
SELECT
689697
success_count = COUNT_BIG(DISTINCT CASE WHEN collection_status = 'SUCCESS' THEN collector_name END),
690698
error_count = SUM(CASE WHEN collection_status = 'ERROR' THEN 1 ELSE 0 END)
691-
FROM PerformanceMonitor.config.collection_log;", connection))
699+
FROM PerformanceMonitor.config.collection_log
700+
WHERE collection_time >= @validation_start;", connection))
692701
{
702+
command.Parameters.AddWithValue("@validation_start", validationStart);
693703
using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
694704
if (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
695705
{
@@ -711,7 +721,9 @@ public async Task<int> InstallDependenciesAsync(
711721
SELECT collector_name, error_message
712722
FROM PerformanceMonitor.config.collection_log
713723
WHERE collection_status = 'ERROR'
724+
AND collection_time >= @validation_start
714725
ORDER BY collection_time DESC;", connection);
726+
command.Parameters.AddWithValue("@validation_start", validationStart);
715727

716728
using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
717729
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))

Lite/PerformanceMonitorLite.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
<RootNamespace>PerformanceMonitorLite</RootNamespace>
99
<Product>SQL Server Performance Monitor Lite</Product>
1010
<Version>2.1.0</Version>
11+
<AssemblyVersion>2.1.0.0</AssemblyVersion>
12+
<FileVersion>2.1.0.0</FileVersion>
13+
<InformationalVersion>2.1.0</InformationalVersion>
1114
<Company>Darling Data, LLC</Company>
1215
<Copyright>Copyright © 2026 Darling Data, LLC</Copyright>
1316
<Description>Lightweight SQL Server performance monitoring - no installation required on target servers</Description>

0 commit comments

Comments
 (0)