Skip to content

Commit 3018ac8

Browse files
Merge pull request #299 from erikdarlingdata/dev
Release v2.0.0
2 parents 1de2414 + 2485799 commit 3018ac8

400 files changed

Lines changed: 20590 additions & 3536 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ A clear description of the change and why it's being made.
55
## Which component(s) does this affect?
66

77
- [ ] Full Dashboard
8-
- [ ] Lite
8+
- [ ] Lite Dashboard
9+
- [ ] Lite Tests
910
- [ ] SQL collection scripts
10-
- [ ] Installer
11+
- [ ] CLI Installer
12+
- [ ] GUI Installer
1113
- [ ] Documentation
1214

1315
## How was this tested?

.github/sql/ci_validate_installation.sql

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ IF SCHEMA_ID(N'report') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: sche
3030
PRINT '';
3131

3232
/*
33-
Procedures in collect schema (37)
33+
Procedures in collect schema (36)
3434
*/
3535
PRINT 'Checking collect procedures...';
3636

@@ -65,7 +65,6 @@ IF OBJECT_ID(N'collect.tempdb_stats_collector', N'P') IS NULL BEGIN SE
6565
IF OBJECT_ID(N'collect.plan_cache_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.plan_cache_stats_collector'; END; SET @checked += 1;
6666
IF OBJECT_ID(N'collect.session_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.session_stats_collector'; END; SET @checked += 1;
6767
IF OBJECT_ID(N'collect.waiting_tasks_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.waiting_tasks_collector'; END; SET @checked += 1;
68-
IF OBJECT_ID(N'collect.session_wait_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.session_wait_stats_collector'; END; SET @checked += 1;
6968
IF OBJECT_ID(N'collect.server_configuration_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.server_configuration_collector'; END; SET @checked += 1;
7069
IF OBJECT_ID(N'collect.database_configuration_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.database_configuration_collector'; END; SET @checked += 1;
7170
IF OBJECT_ID(N'collect.configuration_issues_analyzer', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.configuration_issues_analyzer'; END; SET @checked += 1;
@@ -142,7 +141,6 @@ IF OBJECT_ID(N'report.blocking_chain_analysis', N'V') IS NULL BEGIN
142141
IF OBJECT_ID(N'report.tempdb_contention_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.tempdb_contention_analysis'; END; SET @checked += 1;
143142
IF OBJECT_ID(N'report.parameter_sensitivity_detection', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.parameter_sensitivity_detection'; END; SET @checked += 1;
144143
IF OBJECT_ID(N'report.scheduler_cpu_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.scheduler_cpu_analysis'; END; SET @checked += 1;
145-
IF OBJECT_ID(N'report.session_wait_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.session_wait_analysis'; END; SET @checked += 1;
146144
IF OBJECT_ID(N'report.critical_issues', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.critical_issues'; END; SET @checked += 1;
147145
IF OBJECT_ID(N'report.memory_usage_trends', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_usage_trends'; END; SET @checked += 1;
148146
IF OBJECT_ID(N'report.running_jobs', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.running_jobs'; END; SET @checked += 1;
@@ -182,7 +180,7 @@ WHERE OBJECT_SCHEMA_NAME(t.object_id) = N'config';
182180
PRINT ' collect schema tables: ' + CONVERT(varchar(10), @collect_tables);
183181
PRINT ' config schema tables: ' + CONVERT(varchar(10), @config_tables);
184182

185-
IF @collect_tables < 20 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 20 collect tables, found ' + CONVERT(varchar(10), @collect_tables); END; SET @checked += 1;
183+
IF @collect_tables < 19 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 20 collect tables, found ' + CONVERT(varchar(10), @collect_tables); END; SET @checked += 1;
186184
IF @config_tables < 5 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 5 config tables, found ' + CONVERT(varchar(10), @config_tables); END; SET @checked += 1;
187185

188186
PRINT '';

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
run: dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release
4848

4949
- name: Publish GUI Installer
50-
run: dotnet publish InstallerGui/InstallerGui.csproj -c Release -r win-x64 --self-contained
50+
run: dotnet publish InstallerGui/InstallerGui.csproj -c Release
5151

5252
- name: Package release artifacts
5353
if: github.event_name == 'release'
@@ -69,7 +69,7 @@ jobs:
6969
New-Item -ItemType Directory -Force -Path "$instDir/upgrades"
7070
7171
Copy-Item 'Installer/bin/Release/net8.0/win-x64/publish/PerformanceMonitorInstaller.exe' $instDir
72-
Copy-Item 'InstallerGui/bin/Release/net8.0-windows/win-x64/publish/InstallerGui.exe' $instDir -ErrorAction SilentlyContinue
72+
Copy-Item 'InstallerGui/bin/Release/net8.0-windows/win-x64/publish/PerformanceMonitorInstallerGui.exe' $instDir -ErrorAction SilentlyContinue
7373
Copy-Item 'install/*.sql' "$instDir/install/"
7474
if (Test-Path 'upgrades') { Copy-Item 'upgrades/*' "$instDir/upgrades/" -Recurse -ErrorAction SilentlyContinue }
7575
if (Test-Path 'README.md') { Copy-Item 'README.md' $instDir }

CHANGELOG.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,67 @@ 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.0.0] - 2026-02-25
9+
10+
### Important
11+
12+
- **Schema upgrade**: The `collect.memory_grant_stats` table gains new delta columns and drops unused warning columns. The `collect.session_wait_stats` table, its collector procedure, reporting view, and schedule entry are removed (zero UI coverage). Upgrade scripts run automatically via the CLI/GUI installer and use idempotent checks.
13+
14+
### Added
15+
16+
- **Graphical query plan viewer** — native ShowPlan rendering in both Dashboard and Lite with SSMS-parity operator icons, properties panel, tooltips, warning/parallelism badges, and tabbed plan display ([#220])
17+
- **Actual execution plan support** — execute queries with SET STATISTICS XML ON to capture actual plans, with loading indicator and confirmation dialog ([#233])
18+
- **PlanAnalyzer** — automated plan analysis with rules for missing indexes, eager spools, key lookups, implicit conversions, memory grants, and more
19+
- **Current Active Queries live snapshot** — real-time view of running queries with estimated/live plan download ([#149])
20+
- **Memory clerks tab** in Lite with picker-driven chart ([#145])
21+
- **Current Waits charts** in Blocking tab for both Dashboard and Lite ([#280])
22+
- **File I/O throughput charts** — read/write throughput trends, file-level latency breakdown, queued I/O overlay ([#281])
23+
- **Memory grant stats charts** — standardized collection with delta framework integration and trend visualization ([#281])
24+
- **CPU scheduler pressure status** — real-time scheduler, worker, runnable task counts with color-coded pressure level below CPU chart
25+
- **Collection log drill-down** and daily summary in Lite ([#138])
26+
- **Collector duration trends chart** in Dashboard Collection Health ([#138])
27+
- **Themed perfmon counter packs** — 14 new counters with organized themed groups ([#255])
28+
- **User-configurable connection timeout** setting ([#236])
29+
- **Per-collector retention** — uses per-collector retention from `config.collection_schedule` in data retention ([#237])
30+
- **Query identifiers** in drill-down windows — query hash, plan hash, SQL handle visible for identification ([#268])
31+
- **Trace pattern drill-down** with missing columns and query text tooltips ([#273])
32+
- **Query Store Regressions drill-down** with TVF rewrite for performance ([#274])
33+
- **CLI `--help` flag** for installer ([#111])
34+
- Sort arrows, right-aligned numerics, and initial sort indicators across all grids ([#110])
35+
- Copyable plan viewer properties ([#269])
36+
- Standardized chart save/export filenames between Dashboard and Lite ([#284])
37+
- Full Dashboard column parity for query_stats, procedure_stats, and query_store_stats
38+
- Min/max extremes surfaced in both apps — physical reads, rows, grant KB, spills, CLR time, log bytes ([#281])
39+
40+
### Changed
41+
42+
- Query Store detection uses `sys.database_query_store_options` instead of `sys.databases.is_query_store_on` for Azure SQL DB compatibility ([#287])
43+
- Config tab consolidation, DB drop on server remove, DuckDB-first plan lookups, procedure stats parity
44+
- Collector health status now detects consecutive recent failures — 5+ consecutive errors = FAILING, 3+ = WARNING
45+
- Plan buttons now show a MessageBox when no plan is available instead of silently doing nothing
46+
- CSV export uses locale-appropriate separators for non-US locales ([#240])
47+
- Query Store Regressions and Query Trace Patterns migrated to popup grid filtering ([#260])
48+
- NuGet packages updated; xUnit v3 migration
49+
50+
### Fixed
51+
52+
- **DuckDB file corruption** during maintenance — ReaderWriterLockSlim coordination, archive-all-and-reset at 512MB replaces compaction ([#218])
53+
- Archive view column mismatch, wait_stats thread-safety, and percent_complete type cast ([#234])
54+
- Collector health status bar text color ([#234])
55+
- View Plan for Query Store and Query Store Regressions tabs ([#261])
56+
- Query Store drill-down time filter alignment with main view ([#263])
57+
- Execution count mismatches between main views and drill-downs
58+
- Drill-down chart UX — sparse data markers, hover tooltips, window sizing ([#271])
59+
- Truncated status text in Add Server dialog ([#257])
60+
- Scrollbar visibility, self-filtering artifacts, missing columns, and context menus ([#245], [#246], [#247], [#248])
61+
- query_stats and procedure_stats collectors ignoring recent queries
62+
- Blank tooltips on warning and parallel badge icons
63+
- Missing chart context menu on File I/O Throughput charts in Lite
64+
65+
### Removed
66+
67+
- `collect.session_wait_stats` table, `collect.session_wait_stats_collector` procedure, `report.session_wait_analysis` view, and schedule entry — zero UI coverage, never surfaced in Dashboard or Lite ([#281])
68+
869
## [1.3.0] - 2026-02-20
970

1071
### Important
@@ -119,6 +180,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
119180
- Delta normalization for per-second rate calculations
120181
- Dark theme UI
121182

183+
[2.0.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.3.0...v2.0.0
122184
[1.3.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.2.0...v1.3.0
123185
[1.2.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.1.0...v1.2.0
124186
[1.1.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.0.0...v1.1.0
@@ -187,3 +249,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
187249
[#206]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/206
188250
[#210]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/210
189251
[#214]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/214
252+
[#218]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/218
253+
[#220]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/220
254+
[#233]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/233
255+
[#234]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/234
256+
[#236]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/236
257+
[#237]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/237
258+
[#240]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/240
259+
[#245]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/245
260+
[#246]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/246
261+
[#247]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/247
262+
[#248]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/248
263+
[#255]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/255
264+
[#257]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/257
265+
[#260]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/260
266+
[#261]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/261
267+
[#263]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/263
268+
[#268]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/268
269+
[#269]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/269
270+
[#271]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/271
271+
[#273]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/273
272+
[#274]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/274
273+
[#280]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/280
274+
[#281]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/281
275+
[#284]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/284
276+
[#287]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/287

Dashboard/CollectionLogWindow.xaml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
Title="Collection History" Height="600" Width="1000"
77
WindowStartupLocation="CenterOwner"
88
Background="{DynamicResource BackgroundBrush}">
9+
<Window.Resources>
10+
<ContextMenu x:Key="DataGridContextMenu">
11+
<MenuItem Header="Copy Cell" Click="CopyCell_Click"/>
12+
<MenuItem Header="Copy Row" Click="CopyRow_Click"/>
13+
<MenuItem Header="Copy All Rows" Click="CopyAllRows_Click"/>
14+
<Separator/>
15+
<MenuItem Header="Export to CSV..." Click="ExportToCsv_Click"/>
16+
</ContextMenu>
17+
</Window.Resources>
918
<Grid>
1019
<Grid.RowDefinitions>
1120
<RowDefinition Height="Auto"/>
@@ -28,7 +37,8 @@
2837
CanUserSortColumns="True"
2938
AlternatingRowBackground="{DynamicResource BackgroundLightBrush}"
3039
GridLinesVisibility="All"
31-
Margin="10">
40+
Margin="10"
41+
ContextMenu="{StaticResource DataGridContextMenu}">
3242
<DataGrid.Columns>
3343
<DataGridTextColumn Binding="{Binding CollectionTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150">
3444
<DataGridTextColumn.Header>
@@ -46,15 +56,15 @@
4656
</StackPanel>
4757
</DataGridTextColumn.Header>
4858
</DataGridTextColumn>
49-
<DataGridTextColumn Binding="{Binding RowsCollected}" Width="120">
59+
<DataGridTextColumn Binding="{Binding RowsCollected}" ElementStyle="{StaticResource NumericCell}" Width="120">
5060
<DataGridTextColumn.Header>
5161
<StackPanel Orientation="Horizontal">
5262
<Button x:Name="RowsCollectedFilterButton" Style="{DynamicResource ColumnFilterButtonStyle}" Tag="RowsCollected" Click="LogFilter_Click" Margin="0,0,4,0"/>
5363
<TextBlock Text="Rows Collected" FontWeight="Bold" VerticalAlignment="Center"/>
5464
</StackPanel>
5565
</DataGridTextColumn.Header>
5666
</DataGridTextColumn>
57-
<DataGridTextColumn Binding="{Binding DurationMs, StringFormat='{}{0:N0}'}" Width="110">
67+
<DataGridTextColumn Binding="{Binding DurationMs, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="110">
5868
<DataGridTextColumn.Header>
5969
<StackPanel Orientation="Horizontal">
6070
<Button x:Name="DurationFilterButton" Style="{DynamicResource ColumnFilterButtonStyle}" Tag="DurationMs" Click="LogFilter_Click" Margin="0,0,4,0"/>

Dashboard/CollectionLogWindow.xaml.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,80 @@ private void UpdateLogFilterButtonStyles()
189189
}
190190
}
191191

192+
private void CopyCell_Click(object sender, RoutedEventArgs e)
193+
{
194+
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
195+
{
196+
var dataGrid = Helpers.TabHelpers.FindDataGridFromContextMenu(contextMenu);
197+
if (dataGrid != null && dataGrid.CurrentCell.Item != null)
198+
{
199+
var cellContent = Helpers.TabHelpers.GetCellContent(dataGrid, dataGrid.CurrentCell);
200+
if (!string.IsNullOrEmpty(cellContent))
201+
Clipboard.SetDataObject(cellContent, false);
202+
}
203+
}
204+
}
205+
206+
private void CopyRow_Click(object sender, RoutedEventArgs e)
207+
{
208+
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
209+
{
210+
var dataGrid = Helpers.TabHelpers.FindDataGridFromContextMenu(contextMenu);
211+
if (dataGrid?.SelectedItem != null)
212+
Clipboard.SetDataObject(Helpers.TabHelpers.GetRowAsText(dataGrid, dataGrid.SelectedItem), false);
213+
}
214+
}
215+
216+
private void CopyAllRows_Click(object sender, RoutedEventArgs e)
217+
{
218+
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
219+
{
220+
var dataGrid = Helpers.TabHelpers.FindDataGridFromContextMenu(contextMenu);
221+
if (dataGrid != null && dataGrid.Items.Count > 0)
222+
{
223+
var sb = new System.Text.StringBuilder();
224+
var headers = new List<string>();
225+
foreach (var column in dataGrid.Columns)
226+
headers.Add(Helpers.DataGridClipboardBehavior.GetHeaderText(column));
227+
sb.AppendLine(string.Join("\t", headers));
228+
foreach (var item in dataGrid.Items)
229+
sb.AppendLine(Helpers.TabHelpers.GetRowAsText(dataGrid, item));
230+
Clipboard.SetDataObject(sb.ToString(), false);
231+
}
232+
}
233+
}
234+
235+
private void ExportToCsv_Click(object sender, RoutedEventArgs e)
236+
{
237+
if (sender is MenuItem menuItem && menuItem.Parent is ContextMenu contextMenu)
238+
{
239+
var dataGrid = Helpers.TabHelpers.FindDataGridFromContextMenu(contextMenu);
240+
if (dataGrid != null && dataGrid.Items.Count > 0)
241+
{
242+
var dialog = new Microsoft.Win32.SaveFileDialog
243+
{
244+
FileName = $"collection_log_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
245+
DefaultExt = ".csv",
246+
Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*"
247+
};
248+
if (dialog.ShowDialog() == true)
249+
{
250+
var sb = new System.Text.StringBuilder();
251+
var headers = new List<string>();
252+
foreach (var column in dataGrid.Columns)
253+
headers.Add(Helpers.TabHelpers.EscapeCsvField(Helpers.DataGridClipboardBehavior.GetHeaderText(column)));
254+
sb.AppendLine(string.Join(",", headers));
255+
foreach (var item in dataGrid.Items)
256+
{
257+
var values = Helpers.TabHelpers.GetRowValues(dataGrid, item);
258+
sb.AppendLine(string.Join(",", values.Select(v => Helpers.TabHelpers.EscapeCsvField(v))));
259+
}
260+
System.IO.File.WriteAllText(dialog.FileName, sb.ToString());
261+
}
262+
}
263+
}
264+
}
265+
192266
private void Close_Click(object sender, RoutedEventArgs e)
193267
{
194268
Close();

0 commit comments

Comments
 (0)