From 5c3e096f569aac38144f7d05c2839769ed57bde9 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 29 May 2026 20:17:58 -0400 Subject: [PATCH 1/2] Add .gitattributes to normalize line endings (* text=auto eol=crlf) Repo had inconsistent per-file line endings (core.autocrlf=false, no .gitattributes), so scripted edits flipped EOLs and ballooned diffs. Normalizes text to LF-in-repo / CRLF-checkout; explicit binary markers prevent text mis-detection. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitattributes | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e71a7d8d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +* text=auto eol=crlf + +# Explicit binary markers (prevent text mis-detection / corruption): +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.dll binary +*.exe binary +*.pdb binary +*.snk binary +*.parquet binary +*.zip binary +*.duckdb binary From 0f05b065a841f8a95b0f461d24225c0928079411 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 29 May 2026 20:17:58 -0400 Subject: [PATCH 2/2] Renormalize line endings to CRLF (EOL-only, no content changes) git add --renormalize . against current dev per the new .gitattributes. EOL-only: `git diff -w --ignore-cr-at-eol` is empty. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/ISSUE_TEMPLATE/bug_report.yml | 222 +- .github/ISSUE_TEMPLATE/config.yml | 10 +- .github/ISSUE_TEMPLATE/feature_request.yml | 124 +- .github/ISSUE_TEMPLATE/question.yml | 118 +- .github/PULL_REQUEST_TEMPLATE.md | 52 +- .github/sql/ci_validate_installation.sql | 418 +- .github/workflows/build.yml | 560 +- .github/workflows/check-pr-branch.yml | 42 +- .github/workflows/nightly.yml | 290 +- .github/workflows/sql-validation.yml | 158 +- .gitignore | 128 +- CHANGELOG.md | 2022 +++--- CODE_OF_CONDUCT.md | 88 +- Dashboard/AboutWindow.xaml | 120 +- Dashboard/AboutWindow.xaml.cs | 360 +- Dashboard/Analysis/AnalysisService.cs | 654 +- .../Analysis/SqlServerDrillDownCollector.cs | 2270 +++---- Dashboard/Analysis/SqlServerFactCollector.cs | 4216 ++++++------- Dashboard/Analysis/SqlServerFindingStore.cs | 822 +-- Dashboard/Analysis/SqlServerPlanFetcher.cs | 118 +- Dashboard/App.xaml | 28 +- Dashboard/App.xaml.cs | 392 +- Dashboard/CollectionLogWindow.xaml | 202 +- Dashboard/CollectionLogWindow.xaml.cs | 546 +- Dashboard/CollectorScheduleWindow.xaml | 208 +- Dashboard/CollectorScheduleWindow.xaml.cs | 814 +-- Dashboard/ColumnFilterPopup.xaml | 108 +- Dashboard/ColumnFilterPopup.xaml.cs | 330 +- Dashboard/Controls/AlertsHistoryContent.xaml | 366 +- .../Controls/AlertsHistoryContent.xaml.cs | 1154 ++-- Dashboard/Controls/ConfigChangesContent.xaml | 588 +- .../Controls/ConfigChangesContent.xaml.cs | 928 +-- Dashboard/Controls/CriticalIssuesContent.xaml | 234 +- .../Controls/CriticalIssuesContent.xaml.cs | 796 +-- Dashboard/Controls/CurrentConfigContent.xaml | 426 +- .../Controls/CurrentConfigContent.xaml.cs | 724 +-- Dashboard/Controls/DailySummaryContent.xaml | 298 +- .../Controls/DailySummaryContent.xaml.cs | 926 +-- Dashboard/Controls/DefaultTraceContent.xaml | 284 +- .../Controls/DefaultTraceContent.xaml.cs | 522 +- Dashboard/Controls/FinOpsContent.xaml | 5362 ++++++++-------- Dashboard/Controls/FinOpsContent.xaml.cs | 2396 +++---- Dashboard/Controls/LandingPage.xaml | 256 +- Dashboard/Controls/LoadingOverlay.xaml | 96 +- Dashboard/Controls/LoadingOverlay.xaml.cs | 84 +- Dashboard/Controls/MemoryContent.xaml | 472 +- Dashboard/Controls/PlanViewerControl.xaml | 544 +- .../QueryPerformanceContent.Filters.cs | 1312 ++-- .../Controls/QueryPerformanceContent.xaml | 3978 ++++++------ .../ResourceMetricsContent.CopyExport.cs | 310 +- .../ResourceMetricsContent.FileIoLatency.cs | 326 +- .../ResourceMetricsContent.LatchStats.cs | 248 +- .../ResourceMetricsContent.PerfmonCounters.cs | 678 +- .../ResourceMetricsContent.SessionStats.cs | 404 +- .../ResourceMetricsContent.SpinlockStats.cs | 248 +- .../ResourceMetricsContent.TempdbStats.cs | 536 +- .../ResourceMetricsContent.WaitStatsDetail.cs | 728 +-- .../Controls/ResourceMetricsContent.xaml | 686 +- .../Controls/ResourceMetricsContent.xaml.cs | 814 +-- Dashboard/Controls/ServerHealthCard.xaml | 474 +- Dashboard/Controls/ServerHealthCard.xaml.cs | 382 +- .../Controls/SystemEventsContent.CPUTasks.cs | 484 +- .../SystemEventsContent.CopyExport.cs | 314 +- .../SystemEventsContent.FilterPopup.cs | 294 +- .../Controls/SystemEventsContent.IOIssues.cs | 464 +- .../SystemEventsContent.MemoryBroker.cs | 500 +- .../SystemEventsContent.MemoryConditions.cs | 260 +- .../SystemEventsContent.MemoryNodeOOM.cs | 618 +- .../SystemEventsContent.SchedulerIssues.cs | 372 +- .../SystemEventsContent.SevereErrors.cs | 352 +- .../SystemEventsContent.SystemHealth.cs | 760 +-- Dashboard/Controls/SystemEventsContent.xaml | 954 +-- .../Controls/SystemEventsContent.xaml.cs | 770 +-- .../Converters/NullToBooleanConverter.cs | 54 +- .../Converters/QueryTextCleanupConverter.cs | 94 +- Dashboard/Converters/ValueToBrushConverter.cs | 492 +- Dashboard/Helpers/DateFilterHelper.cs | 476 +- Dashboard/Helpers/Logger.cs | 250 +- Dashboard/Helpers/MethodProfiler.cs | 400 +- Dashboard/Helpers/NativeMethods.cs | 80 +- Dashboard/Helpers/NumericFilterHelper.cs | 224 +- Dashboard/Helpers/QueryLogger.cs | 444 +- Dashboard/Helpers/ServerTimeConverter.cs | 74 +- Dashboard/Helpers/ServerTimeHelper.cs | 188 +- Dashboard/Helpers/TabHelpers.cs | 1644 ++--- Dashboard/Interfaces/ICredentialService.cs | 110 +- Dashboard/Interfaces/IServerManager.cs | 130 +- .../Interfaces/IUserPreferencesService.cs | 128 +- Dashboard/MainWindow.xaml | 588 +- Dashboard/MainWindow.xaml.cs | 4412 ++++++------- Dashboard/ManageServersWindow.xaml | 226 +- Dashboard/ManageServersWindow.xaml.cs | 1004 +-- Dashboard/Mcp/McpActiveQueryTools.cs | 144 +- Dashboard/Mcp/McpAlertTools.cs | 336 +- Dashboard/Mcp/McpConfigHistoryTools.cs | 290 +- Dashboard/Mcp/McpDiagnosticTools.cs | 340 +- Dashboard/Mcp/McpHealthParserTools.cs | 514 +- Dashboard/Mcp/McpJobTools.cs | 146 +- Dashboard/Mcp/McpLatchSpinlockTools.cs | 278 +- Dashboard/Mcp/McpPlanTools.cs | 554 +- Dashboard/Mcp/McpSchedulerTools.cs | 120 +- Dashboard/Mcp/McpServerInventoryTools.cs | 200 +- Dashboard/Mcp/McpSystemEventTools.cs | 342 +- Dashboard/Models/AlertHealthResult.cs | 104 +- Dashboard/Models/AnomalousJobInfo.cs | 42 +- Dashboard/Models/BlockedSessionTrendItem.cs | 38 +- Dashboard/Models/BlockingDeadlockStatsItem.cs | 56 +- Dashboard/Models/BlockingEventItem.cs | 98 +- Dashboard/Models/CollectionHealthItem.cs | 50 +- Dashboard/Models/CollectionLogEntry.cs | 46 +- Dashboard/Models/CollectorScheduleItem.cs | 158 +- Dashboard/Models/CpuDataPoint.cs | 40 +- Dashboard/Models/CpuPressureItem.cs | 66 +- Dashboard/Models/CpuSpikeItem.cs | 32 +- Dashboard/Models/CpuUtilizationHistoryItem.cs | 38 +- Dashboard/Models/CpuUtilizationItem.cs | 28 +- Dashboard/Models/CriticalIssueItem.cs | 42 +- Dashboard/Models/CurrentServerConfigItem.cs | 86 +- Dashboard/Models/DailySummaryItem.cs | 42 +- Dashboard/Models/DatabaseConfigChangeItem.cs | 36 +- Dashboard/Models/DeadlockItem.cs | 120 +- Dashboard/Models/DefaultTraceEventItem.cs | 76 +- Dashboard/Models/DurationTrendItem.cs | 44 +- Dashboard/Models/ExecutionTrendItem.cs | 42 +- Dashboard/Models/ExpensiveQueryItem.cs | 72 +- Dashboard/Models/FileIoDataPoint.cs | 44 +- Dashboard/Models/FileIoLatencyItem.cs | 52 +- .../Models/FileIoLatencyTimeSeriesItem.cs | 56 +- Dashboard/Models/HealthParserCPUTasksItem.cs | 48 +- Dashboard/Models/HealthParserIOIssueItem.cs | 40 +- .../Models/HealthParserMemoryBrokerItem.cs | 52 +- .../Models/HealthParserMemoryConditionItem.cs | 66 +- .../Models/HealthParserMemoryNodeOOMItem.cs | 82 +- .../Models/HealthParserSchedulerIssueItem.cs | 44 +- .../Models/HealthParserSevereErrorItem.cs | 40 +- .../Models/HealthParserSystemHealthItem.cs | 58 +- Dashboard/Models/LatchStatsItem.cs | 84 +- Dashboard/Models/LiveQueryItem.cs | 90 +- Dashboard/Models/LockWaitStatsItem.cs | 44 +- Dashboard/Models/LongRunningQueryInfo.cs | 44 +- .../Models/LongRunningQueryPatternItem.cs | 46 +- Dashboard/Models/MemoryClerksItem.cs | 80 +- Dashboard/Models/MemoryDataPoint.cs | 48 +- Dashboard/Models/MemoryGrantStatsItem.cs | 64 +- Dashboard/Models/MemoryHistoryItem.cs | 44 +- Dashboard/Models/MemoryPressureEventItem.cs | 30 +- Dashboard/Models/MemoryPressureItem.cs | 54 +- Dashboard/Models/MemoryStatsItem.cs | 74 +- Dashboard/Models/PerfmonStatsItem.cs | 38 +- Dashboard/Models/PlanCacheStatsItem.cs | 78 +- Dashboard/Models/PoisonWaitDelta.cs | 32 +- .../Models/ProcedureExecutionHistoryItem.cs | 162 +- Dashboard/Models/ProcedureStatsItem.cs | 144 +- Dashboard/Models/QueryExecutionHistoryItem.cs | 190 +- Dashboard/Models/QuerySnapshotItem.cs | 170 +- Dashboard/Models/QueryStatsHistoryItem.cs | 238 +- Dashboard/Models/QueryStatsItem.cs | 148 +- Dashboard/Models/QueryStoreItem.cs | 236 +- Dashboard/Models/QueryStoreRegressionItem.cs | 62 +- Dashboard/Models/RunningJobItem.cs | 64 +- Dashboard/Models/SelectableItem.cs | 16 +- Dashboard/Models/ServerConfigChangeItem.cs | 44 +- Dashboard/Models/ServerConnection.cs | 412 +- Dashboard/Models/ServerConnectionStatus.cs | 340 +- Dashboard/Models/ServerHealthStatus.cs | 1268 ++-- Dashboard/Models/ServerListItem.cs | 326 +- Dashboard/Models/SessionStatsItem.cs | 44 +- Dashboard/Models/SpinlockStatsItem.cs | 76 +- Dashboard/Models/TempDbSpaceInfo.cs | 46 +- Dashboard/Models/TempdbStatsItem.cs | 92 +- Dashboard/Models/TraceAnalysisItem.cs | 60 +- Dashboard/Models/TraceFlagChangeItem.cs | 38 +- Dashboard/Models/TracePatternDetailItem.cs | 58 +- Dashboard/Models/UserPreferences.cs | 406 +- Dashboard/Models/WaitStatItem.cs | 48 +- Dashboard/Models/WaitStatsDataPoint.cs | 42 +- Dashboard/Models/WaitingTaskTrendItem.cs | 38 +- Dashboard/MuteRuleDialog.xaml | 226 +- Dashboard/ProcedureHistoryWindow.xaml | 880 +-- Dashboard/ProcedureHistoryWindow.xaml.cs | 976 +-- Dashboard/QueryExecutionHistoryWindow.xaml | 860 +-- Dashboard/QueryExecutionHistoryWindow.xaml.cs | 1024 +-- Dashboard/QueryStatsHistoryWindow.xaml | 1028 +-- Dashboard/QueryStatsHistoryWindow.xaml.cs | 960 +-- Dashboard/RemoveServerDialog.xaml | 78 +- Dashboard/RemoveServerDialog.xaml.cs | 66 +- Dashboard/ServerTab.xaml | 2160 +++---- Dashboard/Services/ActualPlanExecutor.cs | 220 +- Dashboard/Services/AlertStateService.cs | 810 +-- Dashboard/Services/CredentialService.cs | 382 +- Dashboard/Services/DataGridFilterService.cs | 604 +- Dashboard/Services/DatabaseService.Memory.cs | 1396 ++-- .../Services/DatabaseService.NocHealth.cs | 1706 ++--- .../Services/DatabaseService.Overview.cs | 678 +- .../DatabaseService.ResourceMetrics.cs | 4816 +++++++------- .../Services/DatabaseService.SystemEvents.cs | 2808 ++++---- Dashboard/Services/DatabaseService.cs | 768 +-- Dashboard/Services/NotificationService.cs | 570 +- Dashboard/Services/ReproScriptBuilder.cs | 890 +-- Dashboard/Services/ServerManager.cs | 1506 ++--- Dashboard/Services/UserPreferencesService.cs | 334 +- Dashboard/SettingsWindow.xaml | 1208 ++-- Dashboard/SettingsWindow.xaml.cs | 2080 +++--- Dashboard/Themes/DarkTheme.xaml | 2696 ++++---- Dashboard/TracePatternHistoryWindow.xaml | 466 +- Dashboard/TracePatternHistoryWindow.xaml.cs | 832 +-- Dashboard/WaitDrillDownWindow.xaml | 610 +- Dashboard/WaitDrillDownWindow.xaml.cs | 1046 +-- Dashboard/packages.lock.json | 1528 ++--- Dashboard/schema/tables.json | 1362 ++-- Installer.Core/packages.lock.json | 580 +- Installer.Tests/AdversarialTests.cs | 1148 ++-- Installer.Tests/FileFilteringTests.cs | 236 +- Installer.Tests/GlobalUsings.cs | 4 +- .../Helpers/TempDirectoryBuilder.cs | 240 +- Installer.Tests/Helpers/TestDatabaseHelper.cs | 288 +- Installer.Tests/IdempotencyTests.cs | 408 +- Installer.Tests/Installer.Tests.csproj | 50 +- Installer.Tests/UpgradeOrderingTests.cs | 402 +- Installer.Tests/VersionDetectionTests.cs | 286 +- Installer.Tests/packages.lock.json | 904 +-- Installer/.gitignore | 8 +- Installer/README.md | 14 +- Installer/packages.lock.json | 674 +- LICENSE | 42 +- Lite.Tests/AlertHistorySourceTests.cs | 530 +- Lite.Tests/AnalysisServiceTests.cs | 430 +- Lite.Tests/DismissReliabilityTests.cs | 582 +- Lite.Tests/DismissedArchiveSidecarTests.cs | 564 +- Lite.Tests/DuckDbSchemaTests.cs | 430 +- Lite.Tests/FactCollectorMiseryTests.cs | 1600 ++--- Lite.Tests/FactCollectorTests.cs | 976 +-- Lite.Tests/FactScorerTests.cs | 538 +- Lite.Tests/FindingStoreTests.cs | 408 +- Lite.Tests/Helpers/TestAlertDataHelper.cs | 378 +- Lite.Tests/InferenceEngineTests.cs | 380 +- Lite.Tests/Lite.Tests.csproj | 48 +- Lite.Tests/ScenarioTests.cs | 1158 ++-- Lite.Tests/packages.lock.json | 1884 +++--- Lite/Analysis/AnalysisService.cs | 654 +- Lite/Analysis/DrillDownCollector.cs | 2100 +++--- Lite/Analysis/DuckDbFactCollector.cs | 3956 ++++++------ Lite/Analysis/FindingStore.cs | 598 +- Lite/Analysis/SqlPlanFetcher.cs | 128 +- Lite/Analysis/TestDataSeeder.cs | 4718 +++++++------- Lite/Controls/AlertsHistoryTab.xaml | 376 +- Lite/Controls/AlertsHistoryTab.xaml.cs | 1186 ++-- Lite/Controls/ColumnFilterPopup.xaml | 108 +- Lite/Controls/ColumnFilterPopup.xaml.cs | 320 +- Lite/Controls/FinOpsTab.xaml | 4902 +++++++------- Lite/Controls/FinOpsTab.xaml.cs | 2180 +++---- Lite/Controls/LoadingOverlay.xaml | 82 +- Lite/Controls/LoadingOverlay.xaml.cs | 82 +- Lite/Controls/PlanViewerControl.xaml | 432 +- Lite/Controls/ServerTab.xaml | 3924 ++++++------ Lite/Database/AnalysisSchema.cs | 248 +- Lite/Database/LockedConnection.cs | 106 +- Lite/Database/Schema.cs | 1596 ++--- Lite/Helpers/ContextMenuHelper.cs | 752 +-- Lite/Helpers/MethodProfiler.cs | 304 +- Lite/Helpers/QueryLogger.cs | 326 +- Lite/Mcp/McpAlertTools.cs | 290 +- Lite/Mcp/McpAnalysisTools.cs | 1784 +++--- Lite/Mcp/McpConfigTools.cs | 376 +- Lite/Mcp/McpJobTools.cs | 126 +- Lite/Mcp/McpPlanTools.cs | 568 +- Lite/Mcp/McpServerInfoTools.cs | 204 +- Lite/Mcp/McpSessionTools.cs | 270 +- Lite/Services/ActualPlanExecutor.cs | 220 +- Lite/Services/AppLogger.cs | 290 +- Lite/Services/AppLoggerAdapter.cs | 100 +- Lite/Services/DataGridFilterManager.cs | 324 +- Lite/Services/DataGridFilterService.cs | 250 +- Lite/Services/DataImportService.cs | 498 +- .../Services/LocalDataService.AlertHistory.cs | 876 +-- .../Services/LocalDataService.DailySummary.cs | 258 +- Lite/Services/LocalDataService.RunningJobs.cs | 334 +- Lite/Services/LocalDataService.ServerInfo.cs | 382 +- Lite/Services/LocalDataService.cs | 280 +- .../RemoteCollectorService.DatabaseSize.cs | 660 +- .../RemoteCollectorService.RunningJobs.cs | 384 +- ...RemoteCollectorService.ServerProperties.cs | 346 +- .../RemoteCollectorService.SessionStats.cs | 292 +- Lite/Services/ReproScriptBuilder.cs | 886 +-- Lite/Windows/CollectionLogWindow.xaml | 196 +- Lite/Windows/CollectionLogWindow.xaml.cs | 170 +- Lite/Windows/MuteRuleDialog.xaml | 228 +- Lite/Windows/WaitDrillDownWindow.xaml | 290 +- Lite/Windows/WaitDrillDownWindow.xaml.cs | 832 +-- Lite/packages.lock.json | 1552 ++--- .../AnalysisContext.cs | 42 +- PerformanceMonitor.Analysis/AnalysisModels.cs | 330 +- .../BlockingChainReconstructor.cs | 718 +-- PerformanceMonitor.Analysis/IFactCollector.cs | 26 +- PerformanceMonitor.Analysis/IPlanFetcher.cs | 38 +- .../InferenceEngine.cs | 332 +- .../RelationshipGraph.cs | 838 +-- PerformanceMonitor.Common/Mcp/McpHelpers.cs | 148 +- .../Models/AuthenticationTypes.cs | 60 +- .../Models/ColumnFilterState.cs | 132 +- .../Models/FilterOperator.cs | 54 +- .../Models/TimeSliceBucket.cs | 66 +- .../PerformanceMonitor.Common.csproj | 44 +- .../Services/PortUtilityService.cs | 180 +- .../Services/UpdateCheckService.cs | 214 +- .../PlanLayoutEngine.cs | 202 +- PerformanceMonitor.PlanAnalysis/PlanModels.cs | 1134 ++-- PerformanceMonitor.Ui/ComparisonItemBase.cs | 174 +- .../DataGridClipboardBehavior.cs | 148 +- PerformanceMonitor.Ui/LoadingMessages.cs | 92 +- PerformanceMonitor.Ui/PerfmonPacks.cs | 220 +- PerformanceMonitor.Ui/TimeDisplayMode.cs | 34 +- PerformanceMonitor.Ui/WaitDrillDownHelper.cs | 338 +- PerformanceMonitor.sln | 382 +- SECURITY.md | 70 +- THIRD_PARTY_NOTICES.md | 806 +-- build-all.cmd | 86 +- build-dashboard.cmd | 170 +- build-lite.cmd | 96 +- install/00_uninstall.sql | 492 +- install/01_install_database.sql | 1760 +++--- install/02_create_tables.sql | 3178 +++++----- install/03_create_config_tables.sql | 1556 ++--- install/04_create_schedule_table.sql | 326 +- install/05_delta_framework.sql | 2496 ++++---- install/06_ensure_collection_table.sql | 2580 ++++---- install/07_collect_wait_stats.sql | 436 +- install/10_collect_procedure_stats.sql | 1732 ++--- install/11_collect_query_snapshots.sql | 830 +-- install/12_query_snapshots_create_views.sql | 492 +- install/13_query_snapshots_retention.sql | 382 +- install/14_collect_memory_stats.sql | 668 +- install/15_collect_memory_grant_stats.sql | 428 +- install/16_collect_memory_clerks_stats.sql | 452 +- install/17_collect_cpu_scheduler_stats.sql | 924 +-- install/18_collect_cpu_utilization_stats.sql | 478 +- install/19_collect_perfmon_stats.sql | 600 +- install/21_setup_blocked_process_xe.sql | 784 +-- install/22_collect_blocked_processes.sql | 824 +-- install/23_process_blocked_process_xml.sql | 922 +-- install/24_collect_deadlock_xml.sql | 746 +-- install/25_process_deadlock_xml.sql | 676 +- install/26_blocking_deadlock_analyzer.sql | 976 +-- install/27_collect_ring_buffer_events.sql | 436 +- install/28_collect_system_health_wrapper.sql | 558 +- install/30_collect_trace_management.sql | 1096 ++-- install/32_collect_latch_stats.sql | 394 +- install/33_collect_spinlock_stats.sql | 404 +- install/34_collect_tempdb_stats.sql | 660 +- install/35_collect_plan_cache_stats.sql | 594 +- install/36_collect_session_stats.sql | 642 +- install/38_collect_server_configuration.sql | 644 +- install/41_schedule_management.sql | 932 +-- install/42_scheduled_master_collector.sql | 938 +-- install/43_data_retention.sql | 1314 ++-- install/44_hung_job_monitor.sql | 498 +- install/45_create_agent_jobs.sql | 658 +- install/46_create_query_plan_views.sql | 1002 +-- install/47_create_reporting_views.sql | 5618 ++++++++--------- .../48_create_additional_reporting_views.sql | 582 +- install/49_health_parser_tables.sql | 944 +-- install/50_configuration_issues_analyzer.sql | 2074 +++--- install/51_collect_running_jobs.sql | 706 +-- install/52_collect_database_size_stats.sql | 822 +-- install/53_collect_server_properties.sql | 826 +-- install/54_create_finops_views.sql | 838 +-- install/97_test_procedures.sql | 3060 ++++----- install/98_validate_installation.sql | 1538 ++--- install/99_installer_troubleshooting.sql | 598 +- install/99_user_troubleshooting.sql | 1548 ++--- install/README.md | 14 +- package-release.cmd | 100 +- .../01_add_memory_stats_columns.sql | 114 +- upgrades/1.2.0-to-1.3.0/upgrade.txt | 2 +- .../01_memory_grant_stats_schema.sql | 344 +- .../02_session_wait_stats_cleanup.sql | 126 +- .../03_default_trace_schema.sql | 120 +- upgrades/1.3.0-to-2.0.0/upgrade.txt | 6 +- .../01_collection_schedule_optional_text.sql | 130 +- .../02_default_trace_events_new_columns.sql | 80 +- upgrades/2.0.0-to-2.1.0/upgrade.txt | 4 +- .../01_compress_query_stats.sql | 772 +-- .../02_compress_query_store_data.sql | 736 +-- .../03_compress_procedure_stats.sql | 650 +- .../04_create_tracking_tables.sql | 212 +- .../05_add_finops_collectors.sql | 174 +- upgrades/2.1.0-to-2.2.0/upgrade.txt | 10 +- .../01_widen_query_stats_columns.sql | 142 +- .../2.2.0-to-2.3.0/02_widen_audit_columns.sql | 312 +- upgrades/2.2.0-to-2.3.0/upgrade.txt | 6 +- upgrades/README.md | 194 +- 391 files changed, 116086 insertions(+), 116086 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2dd0a89b..4343fd4a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,111 +1,111 @@ -name: Bug Report -description: Report a problem with Performance Monitor -title: "[BUG] " -labels: ["bug"] - -body: - - type: dropdown - id: component - attributes: - label: Component - description: Which part of Performance Monitor is affected? - options: - - Full Dashboard - - Lite - - Installer (CLI) - - Installer (GUI) - - SQL collection scripts - validations: - required: true - - - type: input - id: version - attributes: - label: Performance Monitor Version - description: Check the About dialog or the release you downloaded. - placeholder: "e.g., 1.2.0" - validations: - required: true - - - type: input - id: sql-version - attributes: - label: SQL Server Version - description: "Run SELECT @@VERSION on the target server." - placeholder: "e.g., SQL Server 2019 CU25" - validations: - required: true - - - type: input - id: windows-version - attributes: - label: Windows Version - description: The OS where the Dashboard or Lite app is running. - placeholder: "e.g., Windows 11 23H2, Windows Server 2022" - validations: - required: true - - - type: textarea - id: description - attributes: - label: Describe the Bug - description: A clear description of what the bug is. - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Steps to Reproduce - description: How can we reproduce this? - placeholder: | - 1. Open the Dashboard - 2. Click on... - 3. See error - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected Behavior - description: What you expected to happen. - validations: - required: true - - - type: textarea - id: actual - attributes: - label: Actual Behavior - description: What actually happened. - validations: - required: true - - - type: textarea - id: errors - attributes: - label: Error Messages / Log Output - description: Paste any error messages or relevant log entries. Dashboard logs are in %LOCALAPPDATA%\PerformanceMonitor\Logs\. - render: text - validations: - required: false - - - type: textarea - id: screenshots - attributes: - label: Screenshots - description: If applicable, add screenshots to help explain the problem. - validations: - required: false - - - type: textarea - id: context - attributes: - label: Additional Context - description: | - Anything else that might help: - - Did this work before? If so, what changed? - - Is SQL Server Agent running? (Full Edition only) - - Are you monitoring Azure SQL DB, Azure MI, or AWS RDS? - validations: - required: false +name: Bug Report +description: Report a problem with Performance Monitor +title: "[BUG] " +labels: ["bug"] + +body: + - type: dropdown + id: component + attributes: + label: Component + description: Which part of Performance Monitor is affected? + options: + - Full Dashboard + - Lite + - Installer (CLI) + - Installer (GUI) + - SQL collection scripts + validations: + required: true + + - type: input + id: version + attributes: + label: Performance Monitor Version + description: Check the About dialog or the release you downloaded. + placeholder: "e.g., 1.2.0" + validations: + required: true + + - type: input + id: sql-version + attributes: + label: SQL Server Version + description: "Run SELECT @@VERSION on the target server." + placeholder: "e.g., SQL Server 2019 CU25" + validations: + required: true + + - type: input + id: windows-version + attributes: + label: Windows Version + description: The OS where the Dashboard or Lite app is running. + placeholder: "e.g., Windows 11 23H2, Windows Server 2022" + validations: + required: true + + - type: textarea + id: description + attributes: + label: Describe the Bug + description: A clear description of what the bug is. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: How can we reproduce this? + placeholder: | + 1. Open the Dashboard + 2. Click on... + 3. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What you expected to happen. + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What actually happened. + validations: + required: true + + - type: textarea + id: errors + attributes: + label: Error Messages / Log Output + description: Paste any error messages or relevant log entries. Dashboard logs are in %LOCALAPPDATA%\PerformanceMonitor\Logs\. + render: text + validations: + required: false + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain the problem. + validations: + required: false + + - type: textarea + id: context + attributes: + label: Additional Context + description: | + Anything else that might help: + - Did this work before? If so, what changed? + - Is SQL Server Agent running? (Full Edition only) + - Are you monitoring Azure SQL DB, Azure MI, or AWS RDS? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1e486216..530cd13e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ -blank_issues_enabled: false -contact_links: - - name: Questions & Discussion - url: https://github.com/erikdarlingdata/PerformanceMonitor/discussions - about: Ask questions and discuss Performance Monitor with the community +blank_issues_enabled: false +contact_links: + - name: Questions & Discussion + url: https://github.com/erikdarlingdata/PerformanceMonitor/discussions + about: Ask questions and discuss Performance Monitor with the community diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 10c4de58..b330d3df 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,62 +1,62 @@ -name: Feature Request -description: Suggest a new feature or enhancement -title: "[FEATURE] " -labels: ["enhancement"] - -body: - - type: checkboxes - id: component - attributes: - label: Which component(s) does this affect? - options: - - label: Full Dashboard - - label: Lite - - label: SQL collection scripts - - label: Installer - - label: Documentation - validations: - required: true - - - type: textarea - id: problem - attributes: - label: Problem Statement - description: Describe the problem you're trying to solve or the limitation you're facing. - validations: - required: true - - - type: textarea - id: solution - attributes: - label: Proposed Solution - description: Describe your proposed feature or enhancement. - validations: - required: true - - - type: textarea - id: use-case - attributes: - label: Use Case - description: How would you use this feature? Provide a specific example. - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Alternatives Considered - description: Have you considered any alternative solutions or workarounds? - validations: - required: false - - - type: textarea - id: context - attributes: - label: Additional Context - description: | - Anything else: - - Is this related to a specific SQL Server version? - - Would this require schema changes? - - How frequently would you use this feature? - validations: - required: false +name: Feature Request +description: Suggest a new feature or enhancement +title: "[FEATURE] " +labels: ["enhancement"] + +body: + - type: checkboxes + id: component + attributes: + label: Which component(s) does this affect? + options: + - label: Full Dashboard + - label: Lite + - label: SQL collection scripts + - label: Installer + - label: Documentation + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Problem Statement + description: Describe the problem you're trying to solve or the limitation you're facing. + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Describe your proposed feature or enhancement. + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Use Case + description: How would you use this feature? Provide a specific example. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Have you considered any alternative solutions or workarounds? + validations: + required: false + + - type: textarea + id: context + attributes: + label: Additional Context + description: | + Anything else: + - Is this related to a specific SQL Server version? + - Would this require schema changes? + - How frequently would you use this feature? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index dec7b93d..3c497310 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -1,59 +1,59 @@ -name: Question -description: I have a question about Performance Monitor -title: "[QUESTION] " -labels: ["question"] - -body: - - type: dropdown - id: component - attributes: - label: Which component is your question about? - options: - - Full Dashboard - - Lite - - SQL collection scripts - - Installer - - General / setup - validations: - required: true - - - type: input - id: version - attributes: - label: Performance Monitor Version - description: Check the About dialog or the release you downloaded. - placeholder: "e.g., 1.2.0" - validations: - required: true - - - type: dropdown - id: question-type - attributes: - label: Is your question about how it works, or the results? - description: Questions about results or SQL Server tuning are better suited for https://www.erikdarling.com/ or https://dba.stackexchange.com/ - options: - - How the tool works - - Setup / installation - - Results / data interpretation - - Contributing / development - validations: - required: true - - - type: textarea - id: question - attributes: - label: What's your question? - description: A clear description of what you'd like to know. - validations: - required: true - - - type: textarea - id: context - attributes: - label: Additional Context - description: | - Anything that might help: - - SQL Server version and edition - - Are you using Azure SQL DB, Azure MI, or AWS RDS? - validations: - required: false +name: Question +description: I have a question about Performance Monitor +title: "[QUESTION] " +labels: ["question"] + +body: + - type: dropdown + id: component + attributes: + label: Which component is your question about? + options: + - Full Dashboard + - Lite + - SQL collection scripts + - Installer + - General / setup + validations: + required: true + + - type: input + id: version + attributes: + label: Performance Monitor Version + description: Check the About dialog or the release you downloaded. + placeholder: "e.g., 1.2.0" + validations: + required: true + + - type: dropdown + id: question-type + attributes: + label: Is your question about how it works, or the results? + description: Questions about results or SQL Server tuning are better suited for https://www.erikdarling.com/ or https://dba.stackexchange.com/ + options: + - How the tool works + - Setup / installation + - Results / data interpretation + - Contributing / development + validations: + required: true + + - type: textarea + id: question + attributes: + label: What's your question? + description: A clear description of what you'd like to know. + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional Context + description: | + Anything that might help: + - SQL Server version and edition + - Are you using Azure SQL DB, Azure MI, or AWS RDS? + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b5936c08..6b13b4d1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,26 +1,26 @@ -## What does this PR do? - -A clear description of the change and why it's being made. - -## Which component(s) does this affect? - -- [ ] Full Dashboard -- [ ] Lite Dashboard -- [ ] Lite Tests -- [ ] SQL collection scripts -- [ ] CLI Installer -- [ ] GUI Installer -- [ ] Documentation - -## How was this tested? - -Describe the testing you've done. Include: -- SQL Server version(s) tested against -- Steps to verify the change works - -## Checklist - -- [ ] I have read the [contributing guide](https://github.com/erikdarlingdata/PerformanceMonitor/blob/main/CONTRIBUTING.md) -- [ ] My code builds with zero warnings (`dotnet build -c Debug`) -- [ ] I have tested my changes against at least one SQL Server version -- [ ] I have not introduced any hardcoded credentials or server names +## What does this PR do? + +A clear description of the change and why it's being made. + +## Which component(s) does this affect? + +- [ ] Full Dashboard +- [ ] Lite Dashboard +- [ ] Lite Tests +- [ ] SQL collection scripts +- [ ] CLI Installer +- [ ] GUI Installer +- [ ] Documentation + +## How was this tested? + +Describe the testing you've done. Include: +- SQL Server version(s) tested against +- Steps to verify the change works + +## Checklist + +- [ ] I have read the [contributing guide](https://github.com/erikdarlingdata/PerformanceMonitor/blob/main/CONTRIBUTING.md) +- [ ] My code builds with zero warnings (`dotnet build -c Debug`) +- [ ] I have tested my changes against at least one SQL Server version +- [ ] I have not introduced any hardcoded credentials or server names diff --git a/.github/sql/ci_validate_installation.sql b/.github/sql/ci_validate_installation.sql index d3b6382f..ede7b80a 100644 --- a/.github/sql/ci_validate_installation.sql +++ b/.github/sql/ci_validate_installation.sql @@ -1,209 +1,209 @@ -/* -CI Validation: Verify all expected objects exist after installation. -Run with sqlcmd -b to fail on RAISERROR. -*/ - -SET NOCOUNT ON; - -USE PerformanceMonitor; -GO - -PRINT '========================================'; -PRINT 'CI Installation Validation'; -PRINT '========================================'; -PRINT ''; - -DECLARE - @missing int = 0, - @checked int = 0; - -/* -Schemas (4) -*/ -PRINT 'Checking schemas...'; - -IF SCHEMA_ID(N'collect') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema collect'; END; SET @checked += 1; -IF SCHEMA_ID(N'analyze') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema analyze'; END; SET @checked += 1; -IF SCHEMA_ID(N'config') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema config'; END; SET @checked += 1; -IF SCHEMA_ID(N'report') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema report'; END; SET @checked += 1; - -PRINT ''; - -/* -Procedures in collect schema (38) -*/ -PRINT 'Checking collect procedures...'; - -IF OBJECT_ID(N'collect.calculate_deltas', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.calculate_deltas'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.wait_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.wait_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.query_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.query_store_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_store_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.procedure_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.procedure_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.query_snapshots_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_snapshots_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.query_snapshots_create_views', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_snapshots_create_views'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.query_snapshots_retention', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_snapshots_retention'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.memory_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.memory_grant_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_grant_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.memory_clerks_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_clerks_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.cpu_scheduler_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.cpu_scheduler_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.cpu_utilization_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.cpu_utilization_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.perfmon_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.perfmon_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.file_io_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.file_io_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.blocked_process_xml_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.blocked_process_xml_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.process_blocked_process_xml', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.process_blocked_process_xml'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.deadlock_xml_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.deadlock_xml_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.process_deadlock_xml', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.process_deadlock_xml'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.blocking_deadlock_analyzer', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.blocking_deadlock_analyzer'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.memory_pressure_events_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_pressure_events_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.system_health_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.system_health_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.default_trace_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.default_trace_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.trace_management_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.trace_management_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.trace_analysis_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.trace_analysis_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.latch_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.latch_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.spinlock_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.spinlock_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.tempdb_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.tempdb_stats_collector'; END; SET @checked += 1; -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; -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; -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; -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; -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; -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; -IF OBJECT_ID(N'collect.scheduled_master_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.scheduled_master_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.running_jobs_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.running_jobs_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.database_size_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.database_size_stats_collector'; END; SET @checked += 1; -IF OBJECT_ID(N'collect.server_properties_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.server_properties_collector'; END; SET @checked += 1; - -PRINT ''; - -/* -Procedures in config schema (8) -*/ -PRINT 'Checking config procedures...'; - -IF OBJECT_ID(N'config.ensure_config_tables', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.ensure_config_tables'; END; SET @checked += 1; -IF OBJECT_ID(N'config.ensure_collection_table', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.ensure_collection_table'; END; SET @checked += 1; -IF OBJECT_ID(N'config.update_collector_frequency', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.update_collector_frequency'; END; SET @checked += 1; -IF OBJECT_ID(N'config.set_collector_enabled', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.set_collector_enabled'; END; SET @checked += 1; -IF OBJECT_ID(N'config.apply_collection_preset', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.apply_collection_preset'; END; SET @checked += 1; -IF OBJECT_ID(N'config.show_collection_schedule', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.show_collection_schedule'; END; SET @checked += 1; -IF OBJECT_ID(N'config.data_retention', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.data_retention'; END; SET @checked += 1; -IF OBJECT_ID(N'config.check_hung_collector_job', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.check_hung_collector_job'; END; SET @checked += 1; - -PRINT ''; - -/* -Views in config schema (2) -*/ -PRINT 'Checking config views...'; - -IF OBJECT_ID(N'config.current_version', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.current_version'; END; SET @checked += 1; -IF OBJECT_ID(N'config.server_info', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.server_info'; END; SET @checked += 1; - -PRINT ''; - -/* -Views in report schema (41) -Note: report.query_snapshots and report.query_snapshots_blocking are created -dynamically by collect.query_snapshots_create_views, so they are not checked here. -*/ -PRINT 'Checking report views...'; - -IF OBJECT_ID(N'report.query_stats_with_formatted_plans', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_stats_with_formatted_plans'; END; SET @checked += 1; -IF OBJECT_ID(N'report.procedure_stats_with_formatted_plans', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.procedure_stats_with_formatted_plans'; END; SET @checked += 1; -IF OBJECT_ID(N'report.query_store_stats_with_formatted_plans', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_store_stats_with_formatted_plans'; END; SET @checked += 1; -IF OBJECT_ID(N'report.expensive_queries_today', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.expensive_queries_today'; END; SET @checked += 1; -IF OBJECT_ID(N'report.query_stats_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_stats_summary'; END; SET @checked += 1; -IF OBJECT_ID(N'report.procedure_stats_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.procedure_stats_summary'; END; SET @checked += 1; -IF OBJECT_ID(N'report.query_store_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_store_summary'; END; SET @checked += 1; -IF OBJECT_ID(N'report.collection_health', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.collection_health'; END; SET @checked += 1; -IF OBJECT_ID(N'report.top_waits_last_hour', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_waits_last_hour'; END; SET @checked += 1; -IF OBJECT_ID(N'report.memory_pressure_events', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_pressure_events'; END; SET @checked += 1; -IF OBJECT_ID(N'report.cpu_spikes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.cpu_spikes'; END; SET @checked += 1; -IF OBJECT_ID(N'report.blocking_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.blocking_summary'; END; SET @checked += 1; -IF OBJECT_ID(N'report.deadlock_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.deadlock_summary'; END; SET @checked += 1; -IF OBJECT_ID(N'report.daily_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.daily_summary'; END; SET @checked += 1; -IF OBJECT_ID(N'report.daily_summary_v2', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.daily_summary_v2'; END; SET @checked += 1; -IF OBJECT_ID(N'report.server_configuration_changes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.server_configuration_changes'; END; SET @checked += 1; -IF OBJECT_ID(N'report.database_configuration_changes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.database_configuration_changes'; END; SET @checked += 1; -IF OBJECT_ID(N'report.trace_flag_changes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.trace_flag_changes'; END; SET @checked += 1; -IF OBJECT_ID(N'report.top_latch_contention', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_latch_contention'; END; SET @checked += 1; -IF OBJECT_ID(N'report.top_spinlock_contention', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_spinlock_contention'; END; SET @checked += 1; -IF OBJECT_ID(N'report.tempdb_pressure', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.tempdb_pressure'; END; SET @checked += 1; -IF OBJECT_ID(N'report.plan_cache_bloat', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.plan_cache_bloat'; END; SET @checked += 1; -IF OBJECT_ID(N'report.top_memory_consumers', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_memory_consumers'; END; SET @checked += 1; -IF OBJECT_ID(N'report.memory_grant_pressure', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_grant_pressure'; END; SET @checked += 1; -IF OBJECT_ID(N'report.file_io_latency', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.file_io_latency'; END; SET @checked += 1; -IF OBJECT_ID(N'report.cpu_scheduler_pressure', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.cpu_scheduler_pressure'; END; SET @checked += 1; -IF OBJECT_ID(N'report.long_running_query_patterns', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.long_running_query_patterns'; END; SET @checked += 1; -IF OBJECT_ID(N'report.memory_pressure_indicators', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_pressure_indicators'; END; SET @checked += 1; -IF OBJECT_ID(N'report.file_io_wait_correlation', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.file_io_wait_correlation'; END; SET @checked += 1; -IF OBJECT_ID(N'report.blocking_chain_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.blocking_chain_analysis'; END; SET @checked += 1; -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; -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; -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; -IF OBJECT_ID(N'report.critical_issues', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.critical_issues'; END; SET @checked += 1; -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; -IF OBJECT_ID(N'report.running_jobs', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.running_jobs'; END; SET @checked += 1; -IF OBJECT_ID(N'report.finops_database_resource_usage', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_database_resource_usage'; END; SET @checked += 1; -IF OBJECT_ID(N'report.finops_utilization_efficiency', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_utilization_efficiency'; END; SET @checked += 1; -IF OBJECT_ID(N'report.finops_peak_utilization', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_peak_utilization'; END; SET @checked += 1; -IF OBJECT_ID(N'report.finops_application_resource_usage', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_application_resource_usage'; END; SET @checked += 1; - -PRINT ''; - -/* -Functions in report schema (1) -*/ -PRINT 'Checking report functions...'; - -IF OBJECT_ID(N'report.query_store_regressions', N'IF') IS NULL -AND OBJECT_ID(N'report.query_store_regressions', N'TF') IS NULL -AND OBJECT_ID(N'report.query_store_regressions', N'FN') IS NULL -BEGIN SET @missing += 1; PRINT ' MISSING: report.query_store_regressions'; END; -SET @checked += 1; - -PRINT ''; - -/* -Table count checks (minimum expected per schema) -*/ -PRINT 'Checking table counts...'; - -DECLARE - @collect_tables int, - @config_tables int; - -SELECT @collect_tables = COUNT_BIG(*) -FROM sys.tables AS t -WHERE OBJECT_SCHEMA_NAME(t.object_id) = N'collect'; - -SELECT @config_tables = COUNT_BIG(*) -FROM sys.tables AS t -WHERE OBJECT_SCHEMA_NAME(t.object_id) = N'config'; - -PRINT ' collect schema tables: ' + CONVERT(varchar(10), @collect_tables); -PRINT ' config schema tables: ' + CONVERT(varchar(10), @config_tables); - -IF @collect_tables < 21 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 21 collect tables, found ' + CONVERT(varchar(10), @collect_tables); END; SET @checked += 1; -IF @config_tables < 5 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 5 config tables, found ' + CONVERT(varchar(10), @config_tables); END; SET @checked += 1; - -PRINT ''; - -/* -Summary -*/ -PRINT '========================================'; -PRINT 'Checked ' + CONVERT(varchar(10), @checked) + ' objects'; - -IF @missing > 0 -BEGIN - PRINT 'FAILED: ' + CONVERT(varchar(10), @missing) + ' object(s) missing'; - RAISERROR('CI validation failed: %d object(s) missing', 16, 1, @missing); -END; -ELSE -BEGIN - PRINT 'PASSED: All objects present'; -END; - -PRINT '========================================'; -GO +/* +CI Validation: Verify all expected objects exist after installation. +Run with sqlcmd -b to fail on RAISERROR. +*/ + +SET NOCOUNT ON; + +USE PerformanceMonitor; +GO + +PRINT '========================================'; +PRINT 'CI Installation Validation'; +PRINT '========================================'; +PRINT ''; + +DECLARE + @missing int = 0, + @checked int = 0; + +/* +Schemas (4) +*/ +PRINT 'Checking schemas...'; + +IF SCHEMA_ID(N'collect') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema collect'; END; SET @checked += 1; +IF SCHEMA_ID(N'analyze') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema analyze'; END; SET @checked += 1; +IF SCHEMA_ID(N'config') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema config'; END; SET @checked += 1; +IF SCHEMA_ID(N'report') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: schema report'; END; SET @checked += 1; + +PRINT ''; + +/* +Procedures in collect schema (38) +*/ +PRINT 'Checking collect procedures...'; + +IF OBJECT_ID(N'collect.calculate_deltas', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.calculate_deltas'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.wait_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.wait_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.query_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.query_store_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_store_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.procedure_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.procedure_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.query_snapshots_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_snapshots_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.query_snapshots_create_views', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_snapshots_create_views'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.query_snapshots_retention', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.query_snapshots_retention'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.memory_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.memory_grant_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_grant_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.memory_clerks_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_clerks_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.cpu_scheduler_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.cpu_scheduler_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.cpu_utilization_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.cpu_utilization_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.perfmon_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.perfmon_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.file_io_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.file_io_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.blocked_process_xml_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.blocked_process_xml_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.process_blocked_process_xml', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.process_blocked_process_xml'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.deadlock_xml_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.deadlock_xml_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.process_deadlock_xml', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.process_deadlock_xml'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.blocking_deadlock_analyzer', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.blocking_deadlock_analyzer'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.memory_pressure_events_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.memory_pressure_events_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.system_health_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.system_health_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.default_trace_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.default_trace_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.trace_management_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.trace_management_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.trace_analysis_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.trace_analysis_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.latch_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.latch_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.spinlock_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.spinlock_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.tempdb_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.tempdb_stats_collector'; END; SET @checked += 1; +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; +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; +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; +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; +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; +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; +IF OBJECT_ID(N'collect.scheduled_master_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.scheduled_master_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.running_jobs_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.running_jobs_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.database_size_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.database_size_stats_collector'; END; SET @checked += 1; +IF OBJECT_ID(N'collect.server_properties_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.server_properties_collector'; END; SET @checked += 1; + +PRINT ''; + +/* +Procedures in config schema (8) +*/ +PRINT 'Checking config procedures...'; + +IF OBJECT_ID(N'config.ensure_config_tables', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.ensure_config_tables'; END; SET @checked += 1; +IF OBJECT_ID(N'config.ensure_collection_table', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.ensure_collection_table'; END; SET @checked += 1; +IF OBJECT_ID(N'config.update_collector_frequency', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.update_collector_frequency'; END; SET @checked += 1; +IF OBJECT_ID(N'config.set_collector_enabled', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.set_collector_enabled'; END; SET @checked += 1; +IF OBJECT_ID(N'config.apply_collection_preset', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.apply_collection_preset'; END; SET @checked += 1; +IF OBJECT_ID(N'config.show_collection_schedule', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.show_collection_schedule'; END; SET @checked += 1; +IF OBJECT_ID(N'config.data_retention', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.data_retention'; END; SET @checked += 1; +IF OBJECT_ID(N'config.check_hung_collector_job', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.check_hung_collector_job'; END; SET @checked += 1; + +PRINT ''; + +/* +Views in config schema (2) +*/ +PRINT 'Checking config views...'; + +IF OBJECT_ID(N'config.current_version', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.current_version'; END; SET @checked += 1; +IF OBJECT_ID(N'config.server_info', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: config.server_info'; END; SET @checked += 1; + +PRINT ''; + +/* +Views in report schema (41) +Note: report.query_snapshots and report.query_snapshots_blocking are created +dynamically by collect.query_snapshots_create_views, so they are not checked here. +*/ +PRINT 'Checking report views...'; + +IF OBJECT_ID(N'report.query_stats_with_formatted_plans', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_stats_with_formatted_plans'; END; SET @checked += 1; +IF OBJECT_ID(N'report.procedure_stats_with_formatted_plans', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.procedure_stats_with_formatted_plans'; END; SET @checked += 1; +IF OBJECT_ID(N'report.query_store_stats_with_formatted_plans', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_store_stats_with_formatted_plans'; END; SET @checked += 1; +IF OBJECT_ID(N'report.expensive_queries_today', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.expensive_queries_today'; END; SET @checked += 1; +IF OBJECT_ID(N'report.query_stats_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_stats_summary'; END; SET @checked += 1; +IF OBJECT_ID(N'report.procedure_stats_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.procedure_stats_summary'; END; SET @checked += 1; +IF OBJECT_ID(N'report.query_store_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.query_store_summary'; END; SET @checked += 1; +IF OBJECT_ID(N'report.collection_health', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.collection_health'; END; SET @checked += 1; +IF OBJECT_ID(N'report.top_waits_last_hour', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_waits_last_hour'; END; SET @checked += 1; +IF OBJECT_ID(N'report.memory_pressure_events', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_pressure_events'; END; SET @checked += 1; +IF OBJECT_ID(N'report.cpu_spikes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.cpu_spikes'; END; SET @checked += 1; +IF OBJECT_ID(N'report.blocking_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.blocking_summary'; END; SET @checked += 1; +IF OBJECT_ID(N'report.deadlock_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.deadlock_summary'; END; SET @checked += 1; +IF OBJECT_ID(N'report.daily_summary', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.daily_summary'; END; SET @checked += 1; +IF OBJECT_ID(N'report.daily_summary_v2', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.daily_summary_v2'; END; SET @checked += 1; +IF OBJECT_ID(N'report.server_configuration_changes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.server_configuration_changes'; END; SET @checked += 1; +IF OBJECT_ID(N'report.database_configuration_changes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.database_configuration_changes'; END; SET @checked += 1; +IF OBJECT_ID(N'report.trace_flag_changes', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.trace_flag_changes'; END; SET @checked += 1; +IF OBJECT_ID(N'report.top_latch_contention', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_latch_contention'; END; SET @checked += 1; +IF OBJECT_ID(N'report.top_spinlock_contention', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_spinlock_contention'; END; SET @checked += 1; +IF OBJECT_ID(N'report.tempdb_pressure', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.tempdb_pressure'; END; SET @checked += 1; +IF OBJECT_ID(N'report.plan_cache_bloat', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.plan_cache_bloat'; END; SET @checked += 1; +IF OBJECT_ID(N'report.top_memory_consumers', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.top_memory_consumers'; END; SET @checked += 1; +IF OBJECT_ID(N'report.memory_grant_pressure', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_grant_pressure'; END; SET @checked += 1; +IF OBJECT_ID(N'report.file_io_latency', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.file_io_latency'; END; SET @checked += 1; +IF OBJECT_ID(N'report.cpu_scheduler_pressure', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.cpu_scheduler_pressure'; END; SET @checked += 1; +IF OBJECT_ID(N'report.long_running_query_patterns', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.long_running_query_patterns'; END; SET @checked += 1; +IF OBJECT_ID(N'report.memory_pressure_indicators', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_pressure_indicators'; END; SET @checked += 1; +IF OBJECT_ID(N'report.file_io_wait_correlation', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.file_io_wait_correlation'; END; SET @checked += 1; +IF OBJECT_ID(N'report.blocking_chain_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.blocking_chain_analysis'; END; SET @checked += 1; +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; +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; +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; +IF OBJECT_ID(N'report.critical_issues', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.critical_issues'; END; SET @checked += 1; +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; +IF OBJECT_ID(N'report.running_jobs', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.running_jobs'; END; SET @checked += 1; +IF OBJECT_ID(N'report.finops_database_resource_usage', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_database_resource_usage'; END; SET @checked += 1; +IF OBJECT_ID(N'report.finops_utilization_efficiency', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_utilization_efficiency'; END; SET @checked += 1; +IF OBJECT_ID(N'report.finops_peak_utilization', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_peak_utilization'; END; SET @checked += 1; +IF OBJECT_ID(N'report.finops_application_resource_usage', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.finops_application_resource_usage'; END; SET @checked += 1; + +PRINT ''; + +/* +Functions in report schema (1) +*/ +PRINT 'Checking report functions...'; + +IF OBJECT_ID(N'report.query_store_regressions', N'IF') IS NULL +AND OBJECT_ID(N'report.query_store_regressions', N'TF') IS NULL +AND OBJECT_ID(N'report.query_store_regressions', N'FN') IS NULL +BEGIN SET @missing += 1; PRINT ' MISSING: report.query_store_regressions'; END; +SET @checked += 1; + +PRINT ''; + +/* +Table count checks (minimum expected per schema) +*/ +PRINT 'Checking table counts...'; + +DECLARE + @collect_tables int, + @config_tables int; + +SELECT @collect_tables = COUNT_BIG(*) +FROM sys.tables AS t +WHERE OBJECT_SCHEMA_NAME(t.object_id) = N'collect'; + +SELECT @config_tables = COUNT_BIG(*) +FROM sys.tables AS t +WHERE OBJECT_SCHEMA_NAME(t.object_id) = N'config'; + +PRINT ' collect schema tables: ' + CONVERT(varchar(10), @collect_tables); +PRINT ' config schema tables: ' + CONVERT(varchar(10), @config_tables); + +IF @collect_tables < 21 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 21 collect tables, found ' + CONVERT(varchar(10), @collect_tables); END; SET @checked += 1; +IF @config_tables < 5 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 5 config tables, found ' + CONVERT(varchar(10), @config_tables); END; SET @checked += 1; + +PRINT ''; + +/* +Summary +*/ +PRINT '========================================'; +PRINT 'Checked ' + CONVERT(varchar(10), @checked) + ' objects'; + +IF @missing > 0 +BEGIN + PRINT 'FAILED: ' + CONVERT(varchar(10), @missing) + ' object(s) missing'; + RAISERROR('CI validation failed: %d object(s) missing', 16, 1, @missing); +END; +ELSE +BEGIN + PRINT 'PASSED: All objects present'; +END; + +PRINT '========================================'; +GO diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9dae595..8a23a2e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,280 +1,280 @@ -name: Build - -on: - push: - branches: [main, dev] - pull_request: - branches: [main, dev] - release: - types: [published] - -permissions: - contents: write - id-token: write - actions: read - -jobs: - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v5 - - - name: Detect changed paths - id: filter - if: github.event_name != 'release' - uses: dorny/paths-filter@v3 - with: - # On push events, compare against the previous commit on this branch - # (github.event.before). Without this, the action defaults to comparing - # against the default branch on non-default branch pushes, which would - # match every accumulated change and defeat the filter. - base: ${{ github.event_name == 'push' && github.event.before || '' }} - filters: | - lite_analysis: - - 'Lite/Analysis/**' - - 'Lite/Services/**' - - 'Lite/DuckDb/**' - - 'Lite/Models/**' - - 'Lite.Tests/**' - - 'Lite/PerformanceMonitorLite.csproj' - installer: - - 'Installer/**' - - 'Installer.Core/**' - - 'Installer.Tests/**' - - 'install/**' - - 'upgrades/**' - # True when any non-documentation file changed. Documentation-only - # changes (e.g. a CHANGELOG or README edit) skip the build/test/ - # publish steps below — there is nothing to compile. The job still - # runs so the required 'build' check reports a result. - code: - - '**' - - '!**/*.md' - - - name: Setup .NET 10.0 - if: steps.filter.outputs.code != 'false' - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.0.x - cache: true - cache-dependency-path: '**/packages.lock.json' - - - name: Restore dependencies - if: steps.filter.outputs.code != 'false' - run: | - dotnet restore Dashboard/Dashboard.csproj --locked-mode - dotnet restore Lite/PerformanceMonitorLite.csproj --locked-mode - dotnet restore Installer/PerformanceMonitorInstaller.csproj --locked-mode - dotnet restore Lite.Tests/Lite.Tests.csproj --locked-mode - dotnet restore Installer.Tests/Installer.Tests.csproj --locked-mode - - - name: Build Lite.Tests - if: steps.filter.outputs.code != 'false' - run: dotnet build Lite.Tests/Lite.Tests.csproj -c Release --no-restore - - - name: Build Installer.Tests - if: steps.filter.outputs.code != 'false' - run: dotnet build Installer.Tests/Installer.Tests.csproj -c Release --no-restore - - - name: Run Lite fast tests - if: steps.filter.outputs.code != 'false' - run: dotnet test Lite.Tests/Lite.Tests.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName!~AnomalyDetectorTests&FullyQualifiedName!~FactCollectorTests&FullyQualifiedName!~FactCollectorMiseryTests&FullyQualifiedName!~BaselineProviderTests&FullyQualifiedName!~InferenceEngineTests&FullyQualifiedName!~ScenarioTests&FullyQualifiedName!~AnalysisServiceTests" - - - name: Run Lite analysis-heavy tests - if: steps.filter.outputs.lite_analysis == 'true' || github.event_name == 'release' - run: dotnet test Lite.Tests/Lite.Tests.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName~AnomalyDetectorTests|FullyQualifiedName~FactCollectorTests|FullyQualifiedName~FactCollectorMiseryTests|FullyQualifiedName~BaselineProviderTests|FullyQualifiedName~InferenceEngineTests|FullyQualifiedName~ScenarioTests|FullyQualifiedName~AnalysisServiceTests" - - - name: Run Installer tests - if: steps.filter.outputs.installer == 'true' || github.event_name == 'release' - run: dotnet test Installer.Tests/Installer.Tests.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName!~VersionDetectionTests&FullyQualifiedName!~IdempotencyTests&FullyQualifiedName!~AdversarialTests" - - - name: Get version - if: steps.filter.outputs.code != 'false' - id: version - shell: pwsh - run: | - $version = ([xml](Get-Content Dashboard/Dashboard.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } - echo "VERSION=$version" >> $env:GITHUB_OUTPUT - - - name: Publish Dashboard - if: steps.filter.outputs.code != 'false' - run: dotnet publish Dashboard/Dashboard.csproj -c Release -o publish/Dashboard - - - name: Publish Dashboard (self-contained for Velopack) - if: github.event_name == 'release' - run: dotnet publish Dashboard/Dashboard.csproj -c Release -r win-x64 --self-contained -o publish/Dashboard-velopack - - - name: Publish Lite - if: steps.filter.outputs.code != 'false' - run: dotnet publish Lite/PerformanceMonitorLite.csproj -c Release -o publish/Lite - - - name: Publish Lite (self-contained for Velopack) - if: github.event_name == 'release' - run: dotnet publish Lite/PerformanceMonitorLite.csproj -c Release -r win-x64 --self-contained -o publish/Lite-velopack - - - name: Publish CLI Installer - if: steps.filter.outputs.code != 'false' - run: dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release - - - name: Package release artifacts - if: github.event_name == 'release' - shell: pwsh - run: | - $version = "${{ steps.version.outputs.VERSION }}" - New-Item -ItemType Directory -Force -Path releases - - # Dashboard ZIP — portable artifact for advanced/air-gapped users. - # The README points end users at Setup.exe (Velopack) because it sets up Start Menu - # shortcuts and Apps & Features registration; this ZIP is the explicit fallback. - Compress-Archive -Path 'publish/Dashboard/*' -DestinationPath "releases/PerformanceMonitorDashboard-$version.zip" -Force - - # Lite ZIP — same rationale. - Compress-Archive -Path 'publish/Lite/*' -DestinationPath "releases/PerformanceMonitorLite-$version.zip" -Force - - # Installer ZIP (CLI + SQL scripts) — still shipped for server-side install - $instDir = 'publish/Installer' - New-Item -ItemType Directory -Force -Path $instDir - New-Item -ItemType Directory -Force -Path "$instDir/install" - New-Item -ItemType Directory -Force -Path "$instDir/upgrades" - - Copy-Item 'Installer/bin/Release/net10.0/win-x64/publish/PerformanceMonitorInstaller.exe' $instDir - Copy-Item 'install/*.sql' "$instDir/install/" - if (Test-Path 'upgrades') { Copy-Item 'upgrades/*' "$instDir/upgrades/" -Recurse -ErrorAction SilentlyContinue } - if (Test-Path 'README.md') { Copy-Item 'README.md' $instDir } - if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' $instDir } - if (Test-Path 'THIRD_PARTY_NOTICES.md') { Copy-Item 'THIRD_PARTY_NOTICES.md' $instDir } - - Compress-Archive -Path 'publish/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force - - - name: Upload Dashboard for signing - if: github.event_name == 'release' - id: upload-dashboard - uses: actions/upload-artifact@v6 - with: - name: Dashboard-unsigned - path: publish/Dashboard/ - - - name: Upload Lite for signing - if: github.event_name == 'release' - id: upload-lite - uses: actions/upload-artifact@v6 - with: - name: Lite-unsigned - path: publish/Lite/ - - - name: Upload Installer for signing - if: github.event_name == 'release' - id: upload-installer - uses: actions/upload-artifact@v6 - with: - name: Installer-unsigned - path: publish/Installer/ - - - name: Sign Dashboard - if: github.event_name == 'release' - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' - organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0' - project-slug: 'PerformanceMonitor' - signing-policy-slug: 'release-signing' - artifact-configuration-slug: 'Dashboard' - github-artifact-id: '${{ steps.upload-dashboard.outputs.artifact-id }}' - wait-for-completion: true - output-artifact-directory: 'signed/Dashboard' - - - name: Sign Lite - if: github.event_name == 'release' - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' - organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0' - project-slug: 'PerformanceMonitor' - signing-policy-slug: 'release-signing' - artifact-configuration-slug: 'Lite' - github-artifact-id: '${{ steps.upload-lite.outputs.artifact-id }}' - wait-for-completion: true - output-artifact-directory: 'signed/Lite' - - - name: Sign Installer - if: github.event_name == 'release' - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' - organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0' - project-slug: 'PerformanceMonitor' - signing-policy-slug: 'release-signing' - artifact-configuration-slug: 'Installers' - github-artifact-id: '${{ steps.upload-installer.outputs.artifact-id }}' - wait-for-completion: true - output-artifact-directory: 'signed/Installer' - - - name: Replace with signed artifacts - if: github.event_name == 'release' - shell: pwsh - run: | - $version = "${{ steps.version.outputs.VERSION }}" - # Re-zip signed files into release archives - Remove-Item "releases/PerformanceMonitorDashboard-$version.zip" -ErrorAction SilentlyContinue - Compress-Archive -Path 'signed/Dashboard/*' -DestinationPath "releases/PerformanceMonitorDashboard-$version.zip" -Force - Remove-Item "releases/PerformanceMonitorLite-$version.zip" -ErrorAction SilentlyContinue - Compress-Archive -Path 'signed/Lite/*' -DestinationPath "releases/PerformanceMonitorLite-$version.zip" -Force - Remove-Item "releases/PerformanceMonitorInstaller-$version.zip" -ErrorAction SilentlyContinue - Compress-Archive -Path 'signed/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force - - - name: Create Velopack release (Dashboard) - if: github.event_name == 'release' - shell: pwsh - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ steps.version.outputs.VERSION }} - run: | - dotnet tool install -g vpk - New-Item -ItemType Directory -Force -Path releases/velopack-dashboard - New-Item -ItemType Directory -Force -Path releases/velopack-lite - - # Dashboard: download previous + pack - vpk download github --repoUrl https://github.com/${{ github.repository }} --channel dashboard -o releases/velopack-dashboard --token $env:GH_TOKEN - vpk pack -u PerformanceMonitorDashboard -v $env:VERSION -p publish/Dashboard-velopack -e PerformanceMonitorDashboard.exe -o releases/velopack-dashboard --channel dashboard - - # Lite: download previous + pack - vpk download github --repoUrl https://github.com/${{ github.repository }} --channel lite -o releases/velopack-lite --token $env:GH_TOKEN - vpk pack -u PerformanceMonitorLite -v $env:VERSION -p publish/Lite-velopack -e PerformanceMonitorLite.exe -o releases/velopack-lite --channel lite - - - name: Generate checksums - if: github.event_name == 'release' - shell: pwsh - run: | - $checksums = Get-ChildItem releases/*.zip | ForEach-Object { - $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() - "$hash $($_.Name)" - } - $checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8 - Write-Host "Checksums:" - $checksums | ForEach-Object { Write-Host $_ } - - - name: Upload release assets - if: github.event_name == 'release' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release upload ${{ github.event.release.tag_name }} releases/*.zip releases/SHA256SUMS.txt --clobber - - - name: Upload Dashboard Velopack artifacts - if: github.event_name == 'release' - shell: pwsh - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ steps.version.outputs.VERSION }} - run: | - vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel dashboard -o releases/velopack-dashboard --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN - - - name: Upload Lite Velopack artifacts - if: github.event_name == 'release' - shell: pwsh - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ steps.version.outputs.VERSION }} - run: | - vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel lite -o releases/velopack-lite --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN +name: Build + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + release: + types: [published] + +permissions: + contents: write + id-token: write + actions: read + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v5 + + - name: Detect changed paths + id: filter + if: github.event_name != 'release' + uses: dorny/paths-filter@v3 + with: + # On push events, compare against the previous commit on this branch + # (github.event.before). Without this, the action defaults to comparing + # against the default branch on non-default branch pushes, which would + # match every accumulated change and defeat the filter. + base: ${{ github.event_name == 'push' && github.event.before || '' }} + filters: | + lite_analysis: + - 'Lite/Analysis/**' + - 'Lite/Services/**' + - 'Lite/DuckDb/**' + - 'Lite/Models/**' + - 'Lite.Tests/**' + - 'Lite/PerformanceMonitorLite.csproj' + installer: + - 'Installer/**' + - 'Installer.Core/**' + - 'Installer.Tests/**' + - 'install/**' + - 'upgrades/**' + # True when any non-documentation file changed. Documentation-only + # changes (e.g. a CHANGELOG or README edit) skip the build/test/ + # publish steps below — there is nothing to compile. The job still + # runs so the required 'build' check reports a result. + code: + - '**' + - '!**/*.md' + + - name: Setup .NET 10.0 + if: steps.filter.outputs.code != 'false' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Restore dependencies + if: steps.filter.outputs.code != 'false' + run: | + dotnet restore Dashboard/Dashboard.csproj --locked-mode + dotnet restore Lite/PerformanceMonitorLite.csproj --locked-mode + dotnet restore Installer/PerformanceMonitorInstaller.csproj --locked-mode + dotnet restore Lite.Tests/Lite.Tests.csproj --locked-mode + dotnet restore Installer.Tests/Installer.Tests.csproj --locked-mode + + - name: Build Lite.Tests + if: steps.filter.outputs.code != 'false' + run: dotnet build Lite.Tests/Lite.Tests.csproj -c Release --no-restore + + - name: Build Installer.Tests + if: steps.filter.outputs.code != 'false' + run: dotnet build Installer.Tests/Installer.Tests.csproj -c Release --no-restore + + - name: Run Lite fast tests + if: steps.filter.outputs.code != 'false' + run: dotnet test Lite.Tests/Lite.Tests.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName!~AnomalyDetectorTests&FullyQualifiedName!~FactCollectorTests&FullyQualifiedName!~FactCollectorMiseryTests&FullyQualifiedName!~BaselineProviderTests&FullyQualifiedName!~InferenceEngineTests&FullyQualifiedName!~ScenarioTests&FullyQualifiedName!~AnalysisServiceTests" + + - name: Run Lite analysis-heavy tests + if: steps.filter.outputs.lite_analysis == 'true' || github.event_name == 'release' + run: dotnet test Lite.Tests/Lite.Tests.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName~AnomalyDetectorTests|FullyQualifiedName~FactCollectorTests|FullyQualifiedName~FactCollectorMiseryTests|FullyQualifiedName~BaselineProviderTests|FullyQualifiedName~InferenceEngineTests|FullyQualifiedName~ScenarioTests|FullyQualifiedName~AnalysisServiceTests" + + - name: Run Installer tests + if: steps.filter.outputs.installer == 'true' || github.event_name == 'release' + run: dotnet test Installer.Tests/Installer.Tests.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName!~VersionDetectionTests&FullyQualifiedName!~IdempotencyTests&FullyQualifiedName!~AdversarialTests" + + - name: Get version + if: steps.filter.outputs.code != 'false' + id: version + shell: pwsh + run: | + $version = ([xml](Get-Content Dashboard/Dashboard.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } + echo "VERSION=$version" >> $env:GITHUB_OUTPUT + + - name: Publish Dashboard + if: steps.filter.outputs.code != 'false' + run: dotnet publish Dashboard/Dashboard.csproj -c Release -o publish/Dashboard + + - name: Publish Dashboard (self-contained for Velopack) + if: github.event_name == 'release' + run: dotnet publish Dashboard/Dashboard.csproj -c Release -r win-x64 --self-contained -o publish/Dashboard-velopack + + - name: Publish Lite + if: steps.filter.outputs.code != 'false' + run: dotnet publish Lite/PerformanceMonitorLite.csproj -c Release -o publish/Lite + + - name: Publish Lite (self-contained for Velopack) + if: github.event_name == 'release' + run: dotnet publish Lite/PerformanceMonitorLite.csproj -c Release -r win-x64 --self-contained -o publish/Lite-velopack + + - name: Publish CLI Installer + if: steps.filter.outputs.code != 'false' + run: dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release + + - name: Package release artifacts + if: github.event_name == 'release' + shell: pwsh + run: | + $version = "${{ steps.version.outputs.VERSION }}" + New-Item -ItemType Directory -Force -Path releases + + # Dashboard ZIP — portable artifact for advanced/air-gapped users. + # The README points end users at Setup.exe (Velopack) because it sets up Start Menu + # shortcuts and Apps & Features registration; this ZIP is the explicit fallback. + Compress-Archive -Path 'publish/Dashboard/*' -DestinationPath "releases/PerformanceMonitorDashboard-$version.zip" -Force + + # Lite ZIP — same rationale. + Compress-Archive -Path 'publish/Lite/*' -DestinationPath "releases/PerformanceMonitorLite-$version.zip" -Force + + # Installer ZIP (CLI + SQL scripts) — still shipped for server-side install + $instDir = 'publish/Installer' + New-Item -ItemType Directory -Force -Path $instDir + New-Item -ItemType Directory -Force -Path "$instDir/install" + New-Item -ItemType Directory -Force -Path "$instDir/upgrades" + + Copy-Item 'Installer/bin/Release/net10.0/win-x64/publish/PerformanceMonitorInstaller.exe' $instDir + Copy-Item 'install/*.sql' "$instDir/install/" + if (Test-Path 'upgrades') { Copy-Item 'upgrades/*' "$instDir/upgrades/" -Recurse -ErrorAction SilentlyContinue } + if (Test-Path 'README.md') { Copy-Item 'README.md' $instDir } + if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' $instDir } + if (Test-Path 'THIRD_PARTY_NOTICES.md') { Copy-Item 'THIRD_PARTY_NOTICES.md' $instDir } + + Compress-Archive -Path 'publish/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force + + - name: Upload Dashboard for signing + if: github.event_name == 'release' + id: upload-dashboard + uses: actions/upload-artifact@v6 + with: + name: Dashboard-unsigned + path: publish/Dashboard/ + + - name: Upload Lite for signing + if: github.event_name == 'release' + id: upload-lite + uses: actions/upload-artifact@v6 + with: + name: Lite-unsigned + path: publish/Lite/ + + - name: Upload Installer for signing + if: github.event_name == 'release' + id: upload-installer + uses: actions/upload-artifact@v6 + with: + name: Installer-unsigned + path: publish/Installer/ + + - name: Sign Dashboard + if: github.event_name == 'release' + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' + organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0' + project-slug: 'PerformanceMonitor' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'Dashboard' + github-artifact-id: '${{ steps.upload-dashboard.outputs.artifact-id }}' + wait-for-completion: true + output-artifact-directory: 'signed/Dashboard' + + - name: Sign Lite + if: github.event_name == 'release' + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' + organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0' + project-slug: 'PerformanceMonitor' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'Lite' + github-artifact-id: '${{ steps.upload-lite.outputs.artifact-id }}' + wait-for-completion: true + output-artifact-directory: 'signed/Lite' + + - name: Sign Installer + if: github.event_name == 'release' + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' + organization-id: '7969f8b6-d946-4a74-9bac-a55856d8b8e0' + project-slug: 'PerformanceMonitor' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'Installers' + github-artifact-id: '${{ steps.upload-installer.outputs.artifact-id }}' + wait-for-completion: true + output-artifact-directory: 'signed/Installer' + + - name: Replace with signed artifacts + if: github.event_name == 'release' + shell: pwsh + run: | + $version = "${{ steps.version.outputs.VERSION }}" + # Re-zip signed files into release archives + Remove-Item "releases/PerformanceMonitorDashboard-$version.zip" -ErrorAction SilentlyContinue + Compress-Archive -Path 'signed/Dashboard/*' -DestinationPath "releases/PerformanceMonitorDashboard-$version.zip" -Force + Remove-Item "releases/PerformanceMonitorLite-$version.zip" -ErrorAction SilentlyContinue + Compress-Archive -Path 'signed/Lite/*' -DestinationPath "releases/PerformanceMonitorLite-$version.zip" -Force + Remove-Item "releases/PerformanceMonitorInstaller-$version.zip" -ErrorAction SilentlyContinue + Compress-Archive -Path 'signed/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force + + - name: Create Velopack release (Dashboard) + if: github.event_name == 'release' + shell: pwsh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.VERSION }} + run: | + dotnet tool install -g vpk + New-Item -ItemType Directory -Force -Path releases/velopack-dashboard + New-Item -ItemType Directory -Force -Path releases/velopack-lite + + # Dashboard: download previous + pack + vpk download github --repoUrl https://github.com/${{ github.repository }} --channel dashboard -o releases/velopack-dashboard --token $env:GH_TOKEN + vpk pack -u PerformanceMonitorDashboard -v $env:VERSION -p publish/Dashboard-velopack -e PerformanceMonitorDashboard.exe -o releases/velopack-dashboard --channel dashboard + + # Lite: download previous + pack + vpk download github --repoUrl https://github.com/${{ github.repository }} --channel lite -o releases/velopack-lite --token $env:GH_TOKEN + vpk pack -u PerformanceMonitorLite -v $env:VERSION -p publish/Lite-velopack -e PerformanceMonitorLite.exe -o releases/velopack-lite --channel lite + + - name: Generate checksums + if: github.event_name == 'release' + shell: pwsh + run: | + $checksums = Get-ChildItem releases/*.zip | ForEach-Object { + $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() + "$hash $($_.Name)" + } + $checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8 + Write-Host "Checksums:" + $checksums | ForEach-Object { Write-Host $_ } + + - name: Upload release assets + if: github.event_name == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload ${{ github.event.release.tag_name }} releases/*.zip releases/SHA256SUMS.txt --clobber + + - name: Upload Dashboard Velopack artifacts + if: github.event_name == 'release' + shell: pwsh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.VERSION }} + run: | + vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel dashboard -o releases/velopack-dashboard --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN + + - name: Upload Lite Velopack artifacts + if: github.event_name == 'release' + shell: pwsh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.VERSION }} + run: | + vpk upload github --repoUrl https://github.com/${{ github.repository }} --channel lite -o releases/velopack-lite --releaseName "v$env:VERSION" --tag "v$env:VERSION" --merge --token $env:GH_TOKEN diff --git a/.github/workflows/check-pr-branch.yml b/.github/workflows/check-pr-branch.yml index 88fa6fae..1c14086f 100644 --- a/.github/workflows/check-pr-branch.yml +++ b/.github/workflows/check-pr-branch.yml @@ -1,21 +1,21 @@ -name: Check pull request target branch -on: - pull_request_target: - types: - - opened - - reopened - - synchronize - - edited -jobs: - check-branches: - runs-on: ubuntu-latest - steps: - - name: Check branches - env: - HEAD_REF: ${{ github.head_ref }} - BASE_REF: ${{ github.base_ref }} - run: | - if [ "$HEAD_REF" != "dev" ] && [ "$BASE_REF" == "main" ]; then - echo "::error::Pull requests to main are only allowed from dev. Please target the dev branch instead." - exit 1 - fi +name: Check pull request target branch +on: + pull_request_target: + types: + - opened + - reopened + - synchronize + - edited +jobs: + check-branches: + runs-on: ubuntu-latest + steps: + - name: Check branches + env: + HEAD_REF: ${{ github.head_ref }} + BASE_REF: ${{ github.base_ref }} + run: | + if [ "$HEAD_REF" != "dev" ] && [ "$BASE_REF" == "main" ]; then + echo "::error::Pull requests to main are only allowed from dev. Please target the dev branch instead." + exit 1 + fi diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index fd33e514..f886a01a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,145 +1,145 @@ -name: Nightly Build - -on: - schedule: - # 6:00 AM UTC (1:00 AM EST / 2:00 AM EDT) - - cron: '0 6 * * *' - workflow_dispatch: # manual trigger - -permissions: - contents: write - -jobs: - check: - runs-on: ubuntu-latest - outputs: - has_changes: ${{ steps.check.outputs.has_changes }} - steps: - - uses: actions/checkout@v5 - with: - ref: dev - fetch-depth: 0 - - - name: Check for new commits in last 24 hours - id: check - run: | - RECENT=$(git log --since="24 hours ago" --oneline | head -1) - if [ -n "$RECENT" ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - echo "New commits found — building nightly" - else - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "No new commits — skipping nightly build" - fi - - build: - needs: check - if: needs.check.outputs.has_changes == 'true' || github.event_name == 'workflow_dispatch' - runs-on: windows-latest - - steps: - - uses: actions/checkout@v5 - with: - ref: dev - - - name: Setup .NET 10.0 - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.0.x - cache: true - cache-dependency-path: '**/packages.lock.json' - - - name: Set nightly version - id: version - shell: pwsh - run: | - $base = ([xml](Get-Content Dashboard/Dashboard.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } - $date = Get-Date -Format "yyyyMMdd" - $nightly = "$base-nightly.$date" - echo "VERSION=$nightly" >> $env:GITHUB_OUTPUT - echo "Nightly version: $nightly" - - - name: Restore dependencies - run: | - dotnet restore Dashboard/Dashboard.csproj --locked-mode - dotnet restore Lite/PerformanceMonitorLite.csproj --locked-mode - dotnet restore Installer/PerformanceMonitorInstaller.csproj --locked-mode - dotnet restore Lite.Tests/Lite.Tests.csproj --locked-mode - - - name: Run tests - run: dotnet test Lite.Tests/Lite.Tests.csproj -c Release --verbosity normal - - - name: Publish Dashboard - run: dotnet publish Dashboard/Dashboard.csproj -c Release -o publish/Dashboard - - - name: Publish Lite - run: dotnet publish Lite/PerformanceMonitorLite.csproj -c Release -o publish/Lite - - - name: Publish CLI Installer - run: dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release - - - name: Package artifacts - shell: pwsh - run: | - $version = "${{ steps.version.outputs.VERSION }}" - New-Item -ItemType Directory -Force -Path releases - - Compress-Archive -Path 'publish/Dashboard/*' -DestinationPath "releases/PerformanceMonitorDashboard-$version.zip" -Force - Compress-Archive -Path 'publish/Lite/*' -DestinationPath "releases/PerformanceMonitorLite-$version.zip" -Force - - $instDir = 'publish/Installer' - New-Item -ItemType Directory -Force -Path $instDir - New-Item -ItemType Directory -Force -Path "$instDir/install" - New-Item -ItemType Directory -Force -Path "$instDir/upgrades" - - Copy-Item 'Installer/bin/Release/net10.0/win-x64/publish/PerformanceMonitorInstaller.exe' $instDir - Copy-Item 'install/*.sql' "$instDir/install/" - if (Test-Path 'install/templates') { Copy-Item 'install/templates' "$instDir/install/templates" -Recurse -ErrorAction SilentlyContinue } - if (Test-Path 'upgrades') { Copy-Item 'upgrades/*' "$instDir/upgrades/" -Recurse -ErrorAction SilentlyContinue } - if (Test-Path 'README.md') { Copy-Item 'README.md' $instDir } - if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' $instDir } - if (Test-Path 'THIRD_PARTY_NOTICES.md') { Copy-Item 'THIRD_PARTY_NOTICES.md' $instDir } - - Compress-Archive -Path 'publish/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force - - - name: Generate checksums - shell: pwsh - run: | - $checksums = Get-ChildItem releases/*.zip | ForEach-Object { - $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() - "$hash $($_.Name)" - } - $checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8 - Write-Host "Checksums:" - $checksums | ForEach-Object { Write-Host $_ } - - - name: Delete previous nightly release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release delete nightly --yes --cleanup-tag 2>$null; exit 0 - shell: pwsh - - - name: Create nightly release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: pwsh - run: | - $version = "${{ steps.version.outputs.VERSION }}" - $sha = git rev-parse --short HEAD - $body = @" - Automated nightly build from ``dev`` branch. - - **Version:** ``$version`` - **Commit:** ``$sha`` - **Built:** $(Get-Date -Format "yyyy-MM-dd HH:mm UTC") - - > These builds include the latest changes and may be unstable. - > For production use, download the [latest stable release](https://github.com/erikdarlingdata/PerformanceMonitor/releases/latest). - "@ - - gh release create nightly ` - --target dev ` - --title "Nightly Build ($version)" ` - --notes $body ` - --prerelease ` - releases/*.zip releases/SHA256SUMS.txt +name: Nightly Build + +on: + schedule: + # 6:00 AM UTC (1:00 AM EST / 2:00 AM EDT) + - cron: '0 6 * * *' + workflow_dispatch: # manual trigger + +permissions: + contents: write + +jobs: + check: + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check.outputs.has_changes }} + steps: + - uses: actions/checkout@v5 + with: + ref: dev + fetch-depth: 0 + + - name: Check for new commits in last 24 hours + id: check + run: | + RECENT=$(git log --since="24 hours ago" --oneline | head -1) + if [ -n "$RECENT" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "New commits found — building nightly" + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "No new commits — skipping nightly build" + fi + + build: + needs: check + if: needs.check.outputs.has_changes == 'true' || github.event_name == 'workflow_dispatch' + runs-on: windows-latest + + steps: + - uses: actions/checkout@v5 + with: + ref: dev + + - name: Setup .NET 10.0 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Set nightly version + id: version + shell: pwsh + run: | + $base = ([xml](Get-Content Dashboard/Dashboard.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } + $date = Get-Date -Format "yyyyMMdd" + $nightly = "$base-nightly.$date" + echo "VERSION=$nightly" >> $env:GITHUB_OUTPUT + echo "Nightly version: $nightly" + + - name: Restore dependencies + run: | + dotnet restore Dashboard/Dashboard.csproj --locked-mode + dotnet restore Lite/PerformanceMonitorLite.csproj --locked-mode + dotnet restore Installer/PerformanceMonitorInstaller.csproj --locked-mode + dotnet restore Lite.Tests/Lite.Tests.csproj --locked-mode + + - name: Run tests + run: dotnet test Lite.Tests/Lite.Tests.csproj -c Release --verbosity normal + + - name: Publish Dashboard + run: dotnet publish Dashboard/Dashboard.csproj -c Release -o publish/Dashboard + + - name: Publish Lite + run: dotnet publish Lite/PerformanceMonitorLite.csproj -c Release -o publish/Lite + + - name: Publish CLI Installer + run: dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release + + - name: Package artifacts + shell: pwsh + run: | + $version = "${{ steps.version.outputs.VERSION }}" + New-Item -ItemType Directory -Force -Path releases + + Compress-Archive -Path 'publish/Dashboard/*' -DestinationPath "releases/PerformanceMonitorDashboard-$version.zip" -Force + Compress-Archive -Path 'publish/Lite/*' -DestinationPath "releases/PerformanceMonitorLite-$version.zip" -Force + + $instDir = 'publish/Installer' + New-Item -ItemType Directory -Force -Path $instDir + New-Item -ItemType Directory -Force -Path "$instDir/install" + New-Item -ItemType Directory -Force -Path "$instDir/upgrades" + + Copy-Item 'Installer/bin/Release/net10.0/win-x64/publish/PerformanceMonitorInstaller.exe' $instDir + Copy-Item 'install/*.sql' "$instDir/install/" + if (Test-Path 'install/templates') { Copy-Item 'install/templates' "$instDir/install/templates" -Recurse -ErrorAction SilentlyContinue } + if (Test-Path 'upgrades') { Copy-Item 'upgrades/*' "$instDir/upgrades/" -Recurse -ErrorAction SilentlyContinue } + if (Test-Path 'README.md') { Copy-Item 'README.md' $instDir } + if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' $instDir } + if (Test-Path 'THIRD_PARTY_NOTICES.md') { Copy-Item 'THIRD_PARTY_NOTICES.md' $instDir } + + Compress-Archive -Path 'publish/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force + + - name: Generate checksums + shell: pwsh + run: | + $checksums = Get-ChildItem releases/*.zip | ForEach-Object { + $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() + "$hash $($_.Name)" + } + $checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8 + Write-Host "Checksums:" + $checksums | ForEach-Object { Write-Host $_ } + + - name: Delete previous nightly release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release delete nightly --yes --cleanup-tag 2>$null; exit 0 + shell: pwsh + + - name: Create nightly release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: pwsh + run: | + $version = "${{ steps.version.outputs.VERSION }}" + $sha = git rev-parse --short HEAD + $body = @" + Automated nightly build from ``dev`` branch. + + **Version:** ``$version`` + **Commit:** ``$sha`` + **Built:** $(Get-Date -Format "yyyy-MM-dd HH:mm UTC") + + > These builds include the latest changes and may be unstable. + > For production use, download the [latest stable release](https://github.com/erikdarlingdata/PerformanceMonitor/releases/latest). + "@ + + gh release create nightly ` + --target dev ` + --title "Nightly Build ($version)" ` + --notes $body ` + --prerelease ` + releases/*.zip releases/SHA256SUMS.txt diff --git a/.github/workflows/sql-validation.yml b/.github/workflows/sql-validation.yml index 84c817e5..330c8486 100644 --- a/.github/workflows/sql-validation.yml +++ b/.github/workflows/sql-validation.yml @@ -1,79 +1,79 @@ -name: SQL Validation - -on: - push: - branches: [dev] - paths: ['install/**', '.github/sql/**', '.github/workflows/sql-validation.yml'] - pull_request: - branches: [dev] - paths: ['install/**', '.github/sql/**', '.github/workflows/sql-validation.yml'] - -jobs: - validate-sql: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - version: '2017' - image: mcr.microsoft.com/mssql/server:2017-latest - - version: '2019' - image: mcr.microsoft.com/mssql/server:2019-latest - - version: '2022' - image: mcr.microsoft.com/mssql/server:2022-latest - - version: '2025' - image: mcr.microsoft.com/mssql/server:2025-latest - - name: SQL Server ${{ matrix.version }} - - services: - sqlserver: - image: ${{ matrix.image }} - env: - ACCEPT_EULA: Y - MSSQL_SA_PASSWORD: CI_Test#2026! - ports: - - 1433:1433 - options: >- - --health-cmd "grep -q 'SQL Server is now ready for client connections' /var/opt/mssql/log/errorlog || exit 1" - --health-interval 10s - --health-timeout 5s - --health-retries 15 - - steps: - - uses: actions/checkout@v5 - - - name: Install sqlcmd - run: | - # Ubuntu 24.04 runners have Microsoft repo pre-configured; avoid Signed-By conflicts - if ! grep -rql 'packages.microsoft.com' /etc/apt/sources.list.d/ 2>/dev/null; then - curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg - source /etc/os-release - echo "deb [arch=amd64,signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${VERSION_CODENAME} main" | sudo tee /etc/apt/sources.list.d/mssql-release.list - fi - sudo apt-get update - sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18 - - - name: Run install scripts - env: - SA_PASSWORD: CI_Test#2026! - run: | - for script in $(ls install/[0-9]*.sql | sort); do - filename=$(basename "$script") - - # Skip scripts that require SQL Agent or are test/troubleshooting - case "$filename" in - 45_*) echo "Skipping $filename (requires SQL Agent)"; continue;; - 97_*|98_*|99_*) echo "Skipping $filename (test/troubleshooting)"; continue;; - esac - - echo "Running $filename..." - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -i "$script" - echo " OK" - done - - - name: Validate installation - env: - SA_PASSWORD: CI_Test#2026! - run: | - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -i .github/sql/ci_validate_installation.sql +name: SQL Validation + +on: + push: + branches: [dev] + paths: ['install/**', '.github/sql/**', '.github/workflows/sql-validation.yml'] + pull_request: + branches: [dev] + paths: ['install/**', '.github/sql/**', '.github/workflows/sql-validation.yml'] + +jobs: + validate-sql: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - version: '2017' + image: mcr.microsoft.com/mssql/server:2017-latest + - version: '2019' + image: mcr.microsoft.com/mssql/server:2019-latest + - version: '2022' + image: mcr.microsoft.com/mssql/server:2022-latest + - version: '2025' + image: mcr.microsoft.com/mssql/server:2025-latest + + name: SQL Server ${{ matrix.version }} + + services: + sqlserver: + image: ${{ matrix.image }} + env: + ACCEPT_EULA: Y + MSSQL_SA_PASSWORD: CI_Test#2026! + ports: + - 1433:1433 + options: >- + --health-cmd "grep -q 'SQL Server is now ready for client connections' /var/opt/mssql/log/errorlog || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 15 + + steps: + - uses: actions/checkout@v5 + + - name: Install sqlcmd + run: | + # Ubuntu 24.04 runners have Microsoft repo pre-configured; avoid Signed-By conflicts + if ! grep -rql 'packages.microsoft.com' /etc/apt/sources.list.d/ 2>/dev/null; then + curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg + source /etc/os-release + echo "deb [arch=amd64,signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${VERSION_CODENAME} main" | sudo tee /etc/apt/sources.list.d/mssql-release.list + fi + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y mssql-tools18 + + - name: Run install scripts + env: + SA_PASSWORD: CI_Test#2026! + run: | + for script in $(ls install/[0-9]*.sql | sort); do + filename=$(basename "$script") + + # Skip scripts that require SQL Agent or are test/troubleshooting + case "$filename" in + 45_*) echo "Skipping $filename (requires SQL Agent)"; continue;; + 97_*|98_*|99_*) echo "Skipping $filename (test/troubleshooting)"; continue;; + esac + + echo "Running $filename..." + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -i "$script" + echo " OK" + done + + - name: Validate installation + env: + SA_PASSWORD: CI_Test#2026! + run: | + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -C -No -b -i .github/sql/ci_validate_installation.sql diff --git a/.gitignore b/.gitignore index 5af2ea72..1937a3d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,64 +1,64 @@ -################################################################################ -# This .gitignore file was automatically created by Microsoft(R) Visual Studio. -################################################################################ - -# Build output -bin/ -obj/ -publish/ -releases/ - -# Visual Studio -*.suo -*.user -*.vsidx -.vs/ - -# NuGet -packages/ -*.nupkg - -# SQLite databases -*.sqlite - -# Lock files -*.lock - -# Sensitive configuration (connection strings) -appsettings.json - -# Logs -*.log - -# OS files -Thumbs.db -.DS_Store - -# Temp and backup files -*.tmp -*.bak -*.temp -*.orig -*~ - -# Install logs (generated by installer) -PerformanceMonitor_Install_*.txt - -# Internal development files (not for public release) -.internal/ -.claude/ -CLAUDE.md -Dashboard/todo.md -Lite/TODO.md -nul - -# Lite runtime configuration (user-specific) -Lite/config/servers.json -Lite/servers.json -Lite/collection_schedule.json - -# Plans directory -plans/ - -# Community scripts (user-provided, not bundled) -community/*.sql +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +# Build output +bin/ +obj/ +publish/ +releases/ + +# Visual Studio +*.suo +*.user +*.vsidx +.vs/ + +# NuGet +packages/ +*.nupkg + +# SQLite databases +*.sqlite + +# Lock files +*.lock + +# Sensitive configuration (connection strings) +appsettings.json + +# Logs +*.log + +# OS files +Thumbs.db +.DS_Store + +# Temp and backup files +*.tmp +*.bak +*.temp +*.orig +*~ + +# Install logs (generated by installer) +PerformanceMonitor_Install_*.txt + +# Internal development files (not for public release) +.internal/ +.claude/ +CLAUDE.md +Dashboard/todo.md +Lite/TODO.md +nul + +# Lite runtime configuration (user-specific) +Lite/config/servers.json +Lite/servers.json +Lite/collection_schedule.json + +# Plans directory +plans/ + +# Community scripts (user-provided, not bundled) +community/*.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f244a2..477a493b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1011 +1,1011 @@ -# Changelog - -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). - -## [Unreleased] - -### Fixed - -- **Dashboard time labels are now consistently 24-hour** ([#1012]) — the time-range header at the top of each tab (e.g. *"Original: May 28, 11:30 PM – May 29, 1:30 AM (PST)"*) and the Query Performance heatmap x-axis tick labels used `h:mm tt`, while every other timestamp in the app (footer "Last refresh", DataGrid columns, slicer, tooltips, logs) already used 24-hour `HH:mm`/`HH:mm:ss`. The AM/PM marker was also being truncated in the column shown by the reporter. Normalized the four outliers to `HH:mm` to match the rest of the app. The Lite heatmap had the same `h:mm tt` straggler — fixed alongside -- **Lite UI no longer freezes during archival** ([#979]) — archival held DuckDB's exclusive write lock across the entire export-to-Parquet step, blocking every UI query (tab switches showed the spinning wheel, worse with more monitored servers). Export-to-Parquet only reads the database, so it now runs under a shared read lock concurrently with the UI; only the brief `DELETE` takes the exclusive write lock -- **Lite FinOps no longer recommends an edition downgrade on an Availability Group secondary** ([#980]) — the licensing recommendations suggested "downgrade to Standard to save $X/mo" for any Enterprise instance, with no AG awareness. On a secondary replica that advice is misleading — every replica in an AG must run the same edition. FinOps now detects the AG replica role and, on a secondary, shows an informational note instead of the downgrade/savings estimate -- **Lite alert emails no longer re-fire after an app restart** ([#981]) — the per-metric email cooldown lived only in memory, so restarting Lite cleared it and an alert sent minutes earlier could be sent again immediately. The cooldown is now seeded from `config_alert_log` (the most recent successful send for that server/metric) the first time each alert is evaluated, so it survives restarts -- **Dashboard alert emails no longer re-fire after an app restart** — brings Dashboard `EmailAlertService` to parity with the Lite-side persistence introduced in [#981]. The cooldown is now seeded from the in-memory alert log (loaded from `alert_history.json` on startup) the first time each `{serverId}:{metricName}` key is evaluated -- **Analysis-finding notification cooldowns now persist across restarts on both Lite and Dashboard** — the per-finding re-notification cooldown in `AnalysisNotificationService` lived only in memory, so restarting either app cleared it and a finding that had just fired (and entered its `AnalysisNotifyCooldownMinutes` cooldown) could re-notify immediately. The cooldown now seeds lazily from the alert log (Lite: `config_alert_log`; Dashboard: `alert_history.json`) on first lookup per finding, mirroring the email-cooldown pattern from #981. Entries past 2× the cooldown window are pruned on each notify cycle so the dictionary stays bounded -- **Data Retention job no longer fails with `xp_delete_file` error 22049** ([#972]) — the trace-file cleanup added in v2.11.0 passed a wildcard path to `xp_delete_file`, raising an uncatchable `Msg 22049` that failed the entire `PerformanceMonitor - Data Retention` Agent job on every run once any `Monitor_LongQueries_*.trc` files existed. `xp_delete_file` also cannot delete `.trc` files at all — it only accepts SQL Server backup files and Maintenance Plan report files — so that cleanup step has been removed from `config.data_retention` - -### Changed - -- **Plan parsing / analysis extracted to shared library `PerformanceMonitor.PlanAnalysis`** — the previously duplicated `ShowPlanParser`, `PlanAnalyzer`, `BenefitScorer`, `PlanLayoutEngine`, and `PlanModels` pairs across `Dashboard/Services` + `Dashboard/Models` and `Lite/Services` + `Lite/Models` are now one copy referenced by both apps via ``. The new library targets `net10.0` (no WPF) and has zero dependency on `PerformanceMonitor.Analysis` — the two shared libraries are independent. ~5,100 LOC of byte-equivalent duplication eliminated. The `planalyzer-sync-checker` agent is retired (no copies to sync). `ActualPlanExecutor` stays per-app this release because it calls `ReproScriptBuilder` (Class B, drifted between Lite and Dashboard); both will be extracted in a follow-up PR once `ReproScriptBuilder` is reconciled and a logging abstraction is designed -- **`PlanIconMapper` split to break a shared-library WPF dependency** — `ShowPlanParser` calls `PlanIconMapper.GetIconName` to populate `PlanNode.IconName` during parse, but the rest of `PlanIconMapper` is WPF-bound (`GetIcon` returns `BitmapImage`). The pure-data half (the `IconMap` dictionary + the `GetIconName` lookup) is now `IconNameMapper` inside `PerformanceMonitor.PlanAnalysis`. The per-app `PlanIconMapper.GetIcon(string iconName)` is unchanged; the per-app `GetIconName` forwarder is gone (`ShowPlanParser` calls `IconNameMapper.GetIconName` directly, and there were no other callers) -- **Analysis engine extracted to shared library `PerformanceMonitor.Analysis`** — the previously duplicated `FactScorer`, `RelationshipGraph`, `InferenceEngine`, `AnalysisModels`, `IFactCollector`, `IPlanFetcher`, and `BlockingChainReconstructor` pairs across `Dashboard/Analysis/` and `Lite/Analysis/` are now one copy referenced by both apps and both test projects via ``. The new library targets `net10.0` (no WPF) so it can be picked up by future non-WPF consumers without a multi-target rewrite. The `blocking-reconstructor-sync-checker` agent is retired (no copies to sync). `BlockingChainReconstructorTests` ported to `Dashboard.Tests` (10 tests) as part of the same change — Dashboard now exercises the same reconstruction coverage as Lite. `AnalysisService` and the DB-bound adapters (`*FactCollector`, `*DrillDownCollector`, `*FindingStore`, `*AnomalyDetector`, `*BaselineProvider`, `*PlanFetcher`) stay per-app because they bind to `DuckDBConnection` vs `SqlConnection`. `PlanAnalyzer` and its `planalyzer-sync-checker` are outside this extraction's scope and stay -- **Trace files are now bounded at the source** ([#972]) — `collect.trace_management_collector` creates the long-query trace with a rollover file-count cap (`@filecount`, via the new `@max_files` parameter, default 5), so SQL Server itself deletes the oldest `.trc` file as the trace rolls. The scheduled collector also now issues `START` instead of `RESTART`: it keeps one trace running rather than tearing it down and spawning a fresh timestamped trace — and a fresh batch of orphaned files — every cycle -- **Blocked-process reports expose blocker-side fields as typed columns** — `collect.blocking_BlockedProcessReport` now carries `blocking_spid`, `blocking_last_tran_started`, `blocking_status`, `blocked_sql_text`, and `blocking_sql_text` populated at insert time from `blocked_process_report_xml`. Existing rows are backfilled idempotently by the 2.11.0 → 2.12.0 upgrade script -- **Blocking-chain reconstruction now reads typed columns from `collect.blocking_BlockedProcessReport`** instead of re-parsing `blocked_process_report_xml` on every analysis cycle — eliminates up to 5000 `XElement.Parse` calls per `BLOCKING_CHAIN` fact collection. The Dashboard `BlockedProcessXmlParser` has been deleted; the Lite collection-time parser is unchanged (Lite has no SQL-side staging table and still parses once at collect time) -- **Analysis minimum-data threshold lowered to 24 hours** — `Lite/Analysis/AnalysisService.cs` and `Dashboard/Analysis/AnalysisService.cs` now require 24 hours of collected data before analysis runs, down from 72. Validated empirically as sufficient for fraction-of-period calculations, so a fresh install starts producing findings after one day instead of three - -### Added - -- **`tools/Remove-OrphanedTraceFiles.ps1`** ([#972]) — one-time cleanup script for `Monitor_LongQueries_*.trc` files left on disk by versions through 2.11.0. Run it on the SQL Server host; it skips files belonging to a running trace and files that are in use -- **`FactAdvice` and `FactRemediation` in `PerformanceMonitor.Analysis`** — new shared-library data layer that maps every scorable fact-key to a Headline / Investigation / Remediation advice block, plus a copy-paste-ready `sp_query_store_force_plan` T-SQL generator for `PLAN_REGRESSION` findings (gated to that single fact-key in v1; `PARAMETER_SENSITIVITY` deliberately does not generate plan-force T-SQL because forcing locks in the wrong plan for some parameter values). Drill-down collectors now also project `best_plan_id` (via `MAX(plan_id)` in the plan-dedup CTE) so the generated EXEC carries the integer ID `sp_query_store_force_plan` actually accepts, not just the hash. Lite's `BuildContext` now mirrors Dashboard's — both apps emit a Diagnosis card at `Details[0]` carrying Story / Severity / Notify threshold / Confidence / Facts / Database / Window before the drill-down items. The rendering surfaces that consume this data (email HTML, plain-text email, Teams + Slack webhook payloads, in-app Alert Details window) ship in a separate follow-up PR - -[#972]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/972 -[#979]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/979 -[#980]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/980 -[#981]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/981 -[#1012]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/1012 - -## [2.11.0] - 2026-05-19 - -### Important - -- **.NET 10 upgrade** — Dashboard, Lite, Installer, and Installer.Core now target `net10.0` (Windows projects target `net10.0-windows`). Building from source now requires the .NET 10 SDK; CI is pinned to 10.0.204 via `global.json` for reproducible builds. End users running prebuilt Velopack installers do not need to install anything separately — runtime is bundled ([#958]) -- **Setup.exe is now the recommended install path** for Dashboard and Lite — the README steers users to the Velopack `Setup.exe`, which installs to `%LocalAppData%`, registers the apps under Apps & Features, creates Start Menu and Desktop shortcuts, and wires up auto-update. Portable ZIPs are still produced for both apps (CI release pipeline and local build scripts) as a fallback for advanced or air-gapped users. The Installer ZIP (CLI installer + SQL scripts) is unchanged -- **Shared `servers.json` location** — Dashboard and Lite now store `servers.json` under `%ProgramData%\PerformanceMonitor{Dashboard,Lite}\` so every Windows user on the same machine shares one server list. First run migrates an existing per-user `servers.json` to the new location and grants Authenticated Users Modify on the directory. SQL credentials remain per-user in Windows Credential Manager — each DBA re-enters SQL passwords on first connect; Windows Auth works with no re-entry - -### Added - -- **One-click snooze from the alert tray popup** in Lite — snooze an alert directly from the tray notification balloon without opening the main window ([#944]) -- **Snooze hint in email and Teams/Slack alert payloads** — alert messages now show the snooze duration / scheduled wake time when an alert is fired while a snooze is active ([#944]) -- **Process memory logging per collection cycle** in Lite — the collector now logs working set and private bytes at the end of each cycle, making it easier to track memory growth in long-running sessions - -### Changed - -- **Lite compaction memory tuning** ([#933]) — multiple changes to make parquet compaction robust on wide-row tables and large datasets: - - Cap the main collector connection's `memory_limit` and raise it transiently only for the `COPY` step - - Detect compaction `EXCLUDE` columns per merge step instead of once up front - - Raise the compaction `memory_limit` floor to 4 GB - - Set DuckDB `temp_directory` explicitly so spill files don't blow the OS temp drive - - Compact parquet in size-budgeted batches instead of one mega-batch -- **Trace collectors honor `config.collector_database_exclusions`** ([#887] follow-up) — the trace-file based collectors now filter against the exclusions table, matching the behavior of the eight DMV-based per-database collectors shipped in v2.9.0 -- **InstallerGui project directory removed** — the WPF InstallerGui was retired in v2.9.0 in favor of the Dashboard's integrated Add Server dialog. The project directory has now been deleted from the repo -- **Build warnings cleaned up** across Lite, Dashboard, and Installer ([#945]) -- **GitHub Actions runners bumped** to Node 24-compatible major versions to silence deprecation warnings - -### Fixed - -- **Re-run `installation_history` column widening** for servers that crossed v2.4.0 → v2.5.0 before PR #828's fix shipped in v2.7.0. Those servers ran the original widen script as a no-op against `master`, then advanced their installer_version past 2.5, so the now-fixed script never reapplied. Adds an idempotent ALTER under an `IF EXISTS` guard checking `max_length = 510` ([#828]) -- **Mute rules preserved across size-triggered DuckDB reset** in Lite — when the local DuckDB exceeded the configured size budget and was reset, mute rules were being lost. They now survive the reset ([#938]) -- **Chart tooltips break after tab switch** — root-cause fix for the popup-wedge issue first patched in v2.10.0. Both the Memory tab handlers and `CorrelatedCrosshairManager` are now resilient to tab churn ([#916], [#937]) -- **Stale `Monitor_LongQueries_*.trc` files cleaned up** by `config.data_retention` — the trace-file cleanup step previously left old `.trc` files behind on disk ([#951]) -- **Nullability guards** added to the remaining comparison overlay tasks that were producing CS86xx warnings - -[#828]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/828 -[#887]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/887 -[#916]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/916 -[#933]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/933 -[#937]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/937 -[#938]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/938 -[#944]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/944 -[#945]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/945 -[#951]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/951 -[#958]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/958 - -## [2.10.0] - 2026-05-04 - -### Fixed - -- **Memory tab tooltip** stops working after switching away and returning to the tab. Both Dashboard and Lite Memory tab crosshair tooltip handlers now reattach correctly on tab re-entry; the same popup-wedge fix is also applied to `CorrelatedCrosshairManager` ([#916]) -- **FinOps memory recommendation** now bases sizing on a 7-day P95 of memory samples instead of a single snapshot, so recommendations no longer swing based on instantaneous workload state. Applied in both Dashboard and Lite ([#917]) - -### Changed - -- **Per-database grants for FinOps Index Analysis** documented in the README — sp_IndexCleanup-backed Index Analysis requires per-database `EXECUTE` grants on each user database you want to analyze ([#915]) - -[#915]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/915 -[#917]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/917 - -## [2.9.0] - 2026-04-29 - -### Important - -- **Breaking change to `config.data_retention`** — the `@truncate_all` parameter has been removed. Pass `@retention_days = 0` for the same behavior. `@retention_days = NULL` (default) respects per-collector retention from `config.collection_schedule` with a 30-day fallback for unscheduled tables; `@retention_days = N > 0` overrides every table to N days. Any existing Agent jobs or scripts calling `data_retention @truncate_all = 1` need to be updated ([#900]) -- **New `config.collector_database_exclusions` table** for per-database collector exclusions. Eight per-database collectors filter against this table; system databases remain hard-skipped by the collectors themselves. Existing installs get the table on the next upgrade — `install/01_install_database.sql` and `config.ensure_config_tables` both create it under an `IF OBJECT_ID … IS NULL` guard ([#887]) - -### Added - -- **Per-database collector exclusions** — exclude noisy or unimportant databases from per-database collectors. Dashboard side adds `config.collector_database_exclusions` and filters 8 collectors (`query_stats`, `query_store`, `procedure_stats`, `file_io_stats`, `waiting_tasks`, `database_configuration`, `database_size_stats`, `server_properties`). Lite side adds an `ExcludedDatabases` list per server in `servers.json` and filters 9 collectors ([#887]) -- **`Off` collection preset** — `EXECUTE config.apply_collection_preset @preset_name = N'Off'` disables every collector in one call. Pair with a second Agent job that applies a non-`Off` preset at the start of your active window for overnight / quiet-hours scoping. Non-`Off` presets now also set `enabled = 1` across the board so the switch from `Off → Balanced` reliably resumes collection ([#888]) -- **Purge Now action** in Manage Servers — confirm dialog with a mode picker (Use configured / 1 / 3 / 7 / Custom / All) drives `config.data_retention`; right-click menu on the Manage Servers grid mirrors every per-row action (Edit, Toggle Favorite, Check Server Version, Purge Now, Remove) ([#900]) -- **Total non-idle CPU on Lite Overview** — headline value shows total CPU with the SQL-only value alongside (e.g. `64% (SQL 60%)`); new `CpuAlertMode` dropdown in Settings → Alerts (Total / SqlOnly) drives both the alert evaluator and headline color; tray notifications and email alerts label the value as "Total CPU" or "SQL CPU" ([#899]) -- **Resume gap detection** — `query_stats`, `procedure_stats`, and `query_store` collectors skip the historical sweep on first run after an Off preset, Agent stoppage, or server reboot. When the last successful run is older than 5× the configured `frequency_minutes` (floored at 30 minutes), the cutoff clamps to `SYSDATETIME()` so only forward-going data is collected on resume — preventing the tempdb blowout that hit the original reporter ([#892]) -- **Right-click View Plan** on Dashboard Blocked Process Reports (View Blocked Plan + View Blocking Plan), Dashboard Deadlocks, and Lite Deadlocks grids. Plan lookup hits `sys.dm_exec_query_stats` + `sys.dm_exec_text_query_plan` on the monitored server, falling back to `executionStack/frame` entries when the process-level `sql_handle` is empty or evicted ([#880]) -- **Open Log Folder** sidebar button in Lite — opens `%LocalAppData%\PerformanceMonitorLite\logs\` in Explorer for grabbing historical logs to attach to bug reports. Sits below View Log, which retains its existing behavior of opening today's log file ([#873]) -- **Installed Version column** in the Manage Servers grid for both Dashboard and Lite. Dashboard shows the PerformanceMonitor database version on each server (probed in parallel via `GetInstalledVersionAsync`, with `Not installed` / `Unavailable` fallbacks). Lite shows the running app's own version on every row, mirroring Full's column header for consistency. -- **Lite-style server card indicators in Full** — back-ported the Ellipse-with-DataTriggers status dot (Online/Offline/Warning/Unknown) and the right-aligned favorite star from Lite to the Full Dashboard's server list, matching Lite's visual treatment. -- **Architecture overview** at `docs/how-collection-works.md` covering the minute loop, dispatcher, collector shape, `config.collection_schedule`, retention, and the Dashboard read path - -### Changed - -- **PlanIconMapper synced** with PerformanceStudio v1.9.0 improvements — columnstore storage type on scan/delete/insert/update/merge operators routes to `columnstore_index_*` icons (covers CCI and NCCI); `Parallelism` operator subtypes (Repartition Streams, Distribute Streams, Gather Streams) get their own icons -- **`Microsoft.Data.SqlClient` 6.1.4 → 7.0.1** — major-version bump. Azure/Entra dependencies were split out of the core package in 7.0; `Microsoft.Data.SqlClient.Extensions.Azure 1.0.0` added to Dashboard, Lite, and Installer.Core for `ActiveDirectoryInteractive` connections -- **`ModelContextProtocol` 0.7.0-preview.1 → 1.2.0** — off the preview tag and onto stable 1.x in Dashboard and Lite -- **`DuckDB.NET` 1.5.0 → 1.5.2** in Lite — fixes unbounded row group growth on indexed tables under repeated load+insert cycles, memory leaks and race conditions in prepared statements, WAL checkpoint marking, and Windows UTF-8/UTF-16 handling -- **`Microsoft.Extensions.*` 10.0.5 → 10.0.7**, **`System.Text.Json` 10.0.5 → 10.0.7**, **`ScottPlot.WPF` 5.1.57 → 5.1.58** — patch-level bumps with no expected behavioral change -- **Theme polish** on grids and plan viewer in Dashboard and Lite — thanks [@ClaudioESSilva](https://github.com/ClaudioESSilva) ([#889]) - -### Fixed - -- **Install loop timeout** raised from 5 minutes to 1 hour. `install/98_validate_installation.sql` runs every enabled collector with `@debug = 1` in a single batch; on large databases (reporter had 7.2M rows in `collect.query_stats`, 4.4M in `collect.query_store_data`) this took ~9 minutes and was blowing the 5-minute timeout, failing the install or upgrade ([#884]) - -[#873]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/873 -[#880]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/880 -[#884]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/884 -[#887]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/887 -[#888]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/888 -[#889]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/889 -[#892]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/892 -[#899]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/899 -[#900]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/900 - -## [2.8.0] - 2026-04-22 - -### Important - -- **New nonclustered indexes** on `collect.query_stats`, `collect.procedure_stats`, and `collect.query_store_data` to eliminate Eager Index Spools in Dashboard grid queries. On large installations these indexes may take several minutes to build; the upgrade script uses `ONLINE = ON` on Enterprise/Developer/Azure editions and falls back to offline on Standard/Web ([#835]) - -### Added - -- **Memory Pressure Events in Lite** — the collector, chart, and `get_memory_pressure_events` MCP tool previously only in the Full Edition are now available in Lite ([#865]) -- **Grid auto-scrolling** in Lite and Dashboard ([#843]) — thanks [@ClaudioESSilva](https://github.com/ClaudioESSilva) - -### Changed - -- **PlanAnalyzer and BenefitScorer** synced with PerformanceStudio's Apr 9–16 improvements -- **Query/Procedure/Query Store stats** refactored to a phased DECOMPRESS approach; removed unhelpful `WAITFOR DECOMPRESS` filters -- **Query/Procedure/Query Store grids** capped to TOP 500 to prevent UI freezes on large datasets -- **Server tabs lazy-load** — only the visible server tab loads on startup; remaining tabs load on first visit -- **Webhook URLs (Dashboard)** encrypted with DPAPI via Windows Credential Manager — Lite webhook URLs remain in plaintext settings for now -- **DuckDB queries hardened** — parameterized values, escaped paths, fixed `IsArchiving` race -- **Lite chart axes and sub-tab styling** polished, then ported to Dashboard - -### Fixed - -- **Memory Pressure Events chart filter** was dropping valid rows; added MCP interpretation guidance ([#865]) -- **FinOps recommendation severity sort order** in Lite and Dashboard ([#872]) -- **Overview crosshair** disappearing after tab switches or layout passes -- **Blocked process report plan lookup** returning the wrong plan ([#867]) -- **FinOps TDE recommendation** flagging Standard edition on SQL Server 2019+ where TDE is free ([#854]) -- **Azure SQL DB collector** falls back to single-database mode when `master` is inaccessible ([#857]) -- **Azure SQL DB query snapshots** scoped to the current database ([#857]) -- **Azure SQL DB query snapshot prefilter** — request set is narrowed into `#temp` before joining DMVs to avoid Azure-specific execution plan issues ([#857]) -- **Azure SQL DB live query plans** — now skipped gracefully instead of erroring ([#857]) -- **Azure SQL DB memory_stats collector** — dropped `sys.dm_os_schedulers` which is blocked on elastic-pool contained users regardless of DB-scoped grants ([#857]) -- **Non-transient permission denials** now stop collector retries instead of looping forever ([#857]) - -[#835]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/835 -[#843]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/843 -[#854]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/854 -[#857]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/857 -[#865]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/865 -[#867]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/867 -[#872]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/872 - -## [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 - -- **Correlated timeline lanes** on Lite Overview and Dashboard — synchronized CPU, memory, waits, and TempDB trend lanes for at-a-glance correlation ([#688]) -- **Dynamic baselines and anomaly detection** in Lite and Dashboard — automatic baseline calculation with anomaly highlighting on key metrics ([#692], [#693]) -- **Query grid comparison** — before/after comparison mode for query grids in Lite and Dashboard with global Compare dropdown ([#687]) -- **Nonclustered index count badge** on modification operators in plan viewer ([#788]) -- **Upgrade detection in Edit Server** dialog — see pending upgrades without adding a new server ([#772]) -- **CLI installer interactive mode** prompts for trust-cert and encryption settings ([#784]) -- **SignPath code signing** — release binaries are now digitally signed via the [SignPath FOSS](https://signpath.io) program - -### Changed - -- **PlanAnalyzer Rule 3 (Serial Plan)** comprehensively refined — severity demotion for TRIVIAL and 0ms plans, `CouldNotGenerateValidParallelPlan` treated as actionable, all 25 `NonParallelPlanReason` values now covered -- **PlanAnalyzer warning rules** ported from PerformanceStudio improvements -- **Text readability** — replaced all muted/dim text colors with full foreground colors for readability - -### Fixed - -- **Embedded resource upgrade discovery** broken — upgrades silently returned zero results for Dashboard installs ([#772]) -- **Archive compaction OOM** on large parquet groups -- **CLI installer argument parsing** treating flags as positional args ([#786]) -- **Lite long-running query alerts** firing on stale DuckDB snapshots -- **FinOps Enterprise feature detection** now queries all databases and filters to TDE only ([#780]) -- **Second launch error** — now brings existing window to foreground instead ([#769]) -- **Overview tab Memory Grant** showing 0 for all timestamps ([#776]) -- **Lite FinOps Enterprise features** query error on servers without `database_id` column ([#777]) -- **Collector health status** incorrect for on-load collectors -- **CSV and clipboard exports** writing `System.Windows.Controls.StackPanel` as column headers instead of actual header text ([#805]) - -[#687]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/687 -[#688]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/688 -[#692]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/692 -[#693]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/693 -[#769]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/769 -[#772]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/772 -[#776]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/776 -[#777]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/777 -[#780]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/780 -[#784]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/784 -[#786]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/786 -[#788]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/788 -[#805]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/805 - -## [2.5.0] - 2026-03-30 - -### Important - -- **InstallerGui retired**: The standalone GUI installer has been removed. Installation, upgrade, and uninstall are now handled directly from the Dashboard's Add Server dialog, powered by the new Installer.Core shared library. The CLI installer continues to work as before. ([#755]) - -### Added - -- **Dashboard integrated installer** — Add Server dialog now installs, upgrades, and uninstalls PerformanceMonitor directly, replacing the standalone InstallerGui ([#755]) -- **Installer.Core shared library** — shared installation logic used by both the CLI installer and Dashboard ([#755]) -- **Overview tab** for Lite with 2x2 resource chart grid (CPU, Memory, Wait Stats, TempDB) ([#689]) -- **Chart drill-down** on CPU, Memory, TempDB, Blocking, and Deadlock charts in both Dashboard and Lite — right-click any chart point to jump to Active Queries for that time window ([#682]) -- **Grid-to-slicer overlay** for Query Stats, Procedure Stats, and Query Store tabs — click a row to overlay its trend on the slicer chart ([#683]) -- **Query heatmap** tab in both Dashboard and Lite — visual heat map of query activity over time ([#739], [#743]) -- **Webhook notifications** for alerts — configurable webhook endpoint for alert delivery ([#725]) -- **Per-server collector schedule intervals** — customize collection frequency per server ([#703]) -- **Investigate button** in Critical Issues grid — jump directly to relevant tab from an alert ([#684]) -- **Dismiss Selected** context menu and View Log sidebar button for alert management ([#718], [#740]) -- **Alert archival awareness** — dismissed_archive_alerts sidecar table, source column for live vs archived alerts, stale-data indicator, structured telemetry ([#718]) -- **Dashboard read-only connection intent** — connections use `ApplicationIntent=ReadOnly` where supported ([#728]) -- FUNDING.yml for GitHub Sponsors ([#752]) - -### Changed - -- **Installer architecture** refactored: CLI installer is now a thin wrapper over Installer.Core ([#755]) -- **DuckDB memory capped** at 2 GB during parquet compaction to prevent out-of-memory on large archives ([#758]) -- **Text rendering** improved with `TextOptions.TextFormattingMode="Display"` for sharper text ([#710]) -- **installation_history version columns** widened from nvarchar(255) to nvarchar(512) to handle long @@VERSION strings ([#712]) - -### Fixed - -- **Memory leaks in Lite** — delta cache, event handlers, and chart helpers properly disposed ([#758]) -- **Doomed transaction errors** in delta framework and ensure_collection_table — ROLLBACK now occurs before error logging ([#756]) -- **XACT_STATE check** added after third-party stored procedure calls (sp_HumanEventsBlockViewer, sp_BlitzLock) to prevent doomed transaction errors ([#695]) -- **CREATE DATABASE failure** when model database has large default file sizes ([#676]) -- **CPU metrics mixed** for different Azure SQL databases on the same logical server ([#680]) -- **Azure SQL DB vCore** FinOps calculations incorrect for serverless/vCore tiers ([#736]) -- **Webhook alert recording** not persisting correctly ([#726]) -- **Drill-down timezone** misalignment between chart and detail view ([#747], [#750]) -- **Drill-down refresh** losing context on auto-refresh ([#744]) -- **Drill-down target** incorrectly routing Memory to Memory Grants instead of Active Queries ([#706]) -- **Heatmap colorbar stacking** when switching between servers ([#746]) -- **Display mode pickers** not reflecting current state on tab switch ([#751]) -- **Slicer custom range** handling and sub-hour display issues ([#704]) -- **Overlay selection** lost on Dashboard auto-refresh ([#683]) -- **Numeric values** in alert details treated as strings instead of numbers ([#732]) -- **FinOps VM right-sizing** query error — `PERCENTILE_CONT` missing required `OVER()` clause -- **FinOps Enterprise features** query error on AWS RDS — `database_id` column not present in `sys.dm_db_persisted_sku_features` on RDS -- **FinOps right-click copy** broken on all Dashboard FinOps grids — context menu walked to row instead of grid -- **FinOps recommendation error logs** now include server name for easier troubleshooting - -### Deprecated - -- **InstallerGui** — removed from the solution and build pipeline. Use the Dashboard or CLI installer instead. ([#755]) - -[#676]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/676 -[#680]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/680 -[#682]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/682 -[#683]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/683 -[#684]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/684 -[#689]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/689 -[#695]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/695 -[#703]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/703 -[#704]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/704 -[#706]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/706 -[#710]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/710 -[#712]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/712 -[#718]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/718 -[#725]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/725 -[#726]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/726 -[#728]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/728 -[#732]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/732 -[#736]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/736 -[#739]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/739 -[#740]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/740 -[#743]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/743 -[#744]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/744 -[#746]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/746 -[#747]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/747 -[#750]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/750 -[#751]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/751 -[#752]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/752 -[#755]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/755 -[#756]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/756 -[#758]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/758 - -## [2.4.0] - 2026-03-23 - -### Important - -- **Lite data directory moved**: Lite now stores all data (config, DuckDB, archives, logs) in `%LOCALAPPDATA%\PerformanceMonitorLite\` instead of alongside the executable. This enables auto-update support. Existing users upgrading from the zip should use **Import Settings** and **Import Data** to bring over their configuration and historical data from the old install folder. -- **Auto-update (Windows)**: Both Dashboard and Lite now include Velopack auto-update. Users who install via the new Setup.exe will receive update notifications and can download + apply updates from within the app. Existing zip distribution continues to work as before. - -### Added - -- **Velopack auto-update** for Dashboard and Lite — check on startup, download + apply from About window with confirmation dialog before restart ([#635]) -- **Per-tab time range slicers** on Dashboard and Lite query tabs — filter data directly on each tab without changing global time range ([#655], [#662]) -- **Time display picker** (Local/UTC/Server) in Dashboard and Lite toolbars ([#646]) -- **Import Settings** — renamed from "Import Connections", now also copies `settings.json`, `collection_schedule.json`, `ignored_wait_types.json`, and `alert_state.json` from a previous install -- **Alert muting improvements** — pre-fill context fields (database, query, wait type, job name) from alert detail text, configurable default expiration for new mute rules, tooltip on query text field ([#642]) -- **Missing date columns** on Query Stats and Procedure Stats tabs (`creation_time`, `last_execution_time`) ([#649], [#651], [#654]) -- **Trace pattern drill-down** now includes `CollectionTime` and `NtUserName` columns ([#663]) -- **DataGrid sort preservation** across auto-refresh — sort order no longer resets when data refreshes ([#659]) -- **CLI installer**: colored output (green/red/yellow) and version check on startup ([#639]) -- **GUI installer**: version check on startup -- **Growth rate and VLF count** columns in Database Sizes (from v2.3.0 nightly, now in upgrade path) ([#567]) -- `llms.txt` and `CITATION.cff` for project discoverability ([#630]) - -### Changed - -- **Lite data directory** moved to `%LOCALAPPDATA%\PerformanceMonitorLite\` for Velopack compatibility -- **Delta gap detection** added to all cumulative-counter collectors (file I/O, wait stats, query stats, procedure stats, memory grants) — prevents inflated spikes after app restart ([#633]) -- **File I/O NULL fallbacks** improved when `sys.master_files` is inaccessible — falls back to `DB_NAME()` and `File_{id}` instead of generic "Unknown" ([#633]) -- **Running jobs collector** skipped gracefully when login lacks msdb access ([#656]) -- NuGet packages updated to latest minor versions ([#653]) - -### Fixed - -- **Installer writing SUCCESS when files fail** — CLI tolerated 1 failure in automated mode, GUI had a similar workaround. Now any failure = not success. -- **Query stats collector causing SQL dumps** on passive mirror servers — removed `dm_exec_plan_attributes` CROSS APPLY, uses temp table of ONLINE database IDs instead ([#632]) -- **Trigger name extraction** fails when comment before `CREATE TRIGGER` contains " ON " ([#666]) -- **FinOps expensive queries** DuckDB error — query referenced `statement_start_offset` column that doesn't exist in schema -- **Imported parquet files** not recognized by archive compaction — added regex patterns for `imported_` prefix -- **Auto-refresh after Import Data** — views now refresh immediately after import completes - -[#630]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/630 -[#632]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/632 -[#633]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/633 -[#635]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/635 -[#639]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/639 -[#642]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/642 -[#646]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/646 -[#649]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/649 -[#651]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/651 -[#653]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/653 -[#654]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/654 -[#655]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/655 -[#656]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/656 -[#659]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/659 -[#662]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/662 -[#663]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/663 -[#666]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/666 -[#567]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/567 - -## [2.3.0] - 2026-03-18 - -### Important - -- **Schema upgrade**: Six columns widened across three tables (`query_stats`, `cpu_scheduler_stats`, `waiting_tasks`, `database_size_stats`) to match DMV documentation types. These are in-place ALTER COLUMN operations — fast on any table size, no data migration. Upgrade scripts run automatically via the CLI/GUI installer. -- **SQL Server version check**: Both installers now reject SQL Server 2014 and earlier before running any scripts, with a clear error message. Azure MI (EngineEdition 8) is always accepted. ([#543]) -- **Installer adversarial tests**: 35 automated tests covering upgrade failures, data survival, idempotency, version detection fallback, file filtering, restricted permissions, and more. These run as part of pre-release validation. ([#543]) - -### Added - -- **ErikAI analysis engine** — rule-based inference engine for Lite that scores server health across wait stats, CPU, memory, I/O, blocking, tempdb, and query performance. Surfaces actionable findings with severity, detail, and recommended actions. Includes anomaly detection (baseline comparison for acute deviations), bad actor detection (per-query scoring for consistently terrible queries), and CPU spike detection for bursty workloads. ([#589], [#593]) -- **ErikAI Dashboard port** — full analysis engine ported to Dashboard with SQL Server backend ([#590]) -- **FinOps cost optimization recommendations** — Phase 1-4 checks: enterprise feature audit, CPU/memory right-sizing, compression savings estimator, unused index cost quantification, dormant database detection, dev/test workload detection, VM right-sizing, storage tier optimization, reserved capacity candidates ([#564]) -- **FinOps High Impact Queries** — 80/20 analysis showing which queries consume the most resources across all dimensions ([#564]) -- **FinOps dollar-denominated cost attribution** — per-server monthly cost setting with proportional database-level breakdown ([#564]) -- **On-demand plan fetch** for bad actor and analysis findings — click to retrieve execution plans for flagged queries ([#604]) -- **Plan analysis integration** — findings include execution plan analysis when plans are available ([#594]) -- **Server unreachable email alerts** — Dashboard sends email (not just tray notification) when a monitored server goes offline or comes back online ([#529]) -- **Column filters on all FinOps DataGrids** — filter funnel icons on every column header across all 7 FinOps grids in Lite and Dashboard ([#562]) -- **Column filters on Dashboard** IdleDatabases, TempDB, and Index Analysis grids -- **Lite data import** — "Import Data" button brings in monitoring history from a previous Lite install via parquet files, preserving trend data across version upgrades ([#566]) -- **Per-server Utility Database setting** — Lite can call community stored procedures (sp_IndexCleanup) from a database other than master ([#555]) -- **SQL Server version check** in both CLI and GUI installers — rejects 2014 and earlier with a clear message ([#543]) -- **Execution plan analysis MCP tools** for both Dashboard and Lite -- **Full MCP tool coverage** — Dashboard expanded from 28 to 57 tools, Lite from 32 to 51 tools ([#576], [#577]) -- **Self-sufficient analyze_server drill-down** — MCP tool returns complete analysis, not breadcrumb trail ([#578]) -- **NuGet package dependency licenses** in THIRD_PARTY_NOTICES.md - -### Changed - -- **Azure SQL DB FinOps** — all collectors (database sizes, query stats, file I/O) now connect to each database individually instead of only querying master. Server Inventory uses dynamic SQL to avoid `sys.master_files` dependency. ([#557]) -- **Index Analysis scroll fix** — both summary and detail grids now use proportional heights instead of Auto, so they scroll independently with large result sets ([#554]) -- **Dashboard Add Server dialog** — increased MaxHeight from 700 to 850px so buttons are visible when SQL auth fields are shown -- **GUI installer** — Uninstall button now correctly enables after a successful install -- **GUI installer** — fixed encryption mapping and history logging ([#612]) -- **Dashboard visible sub-tab only refresh** on auto-refresh ticks ([#528]) -- Analysis engine decouples data maturity check from analysis window - -### Fixed - -- **Installer dropping database on every upgrade** — `00_uninstall.sql` excluded from install file list, installer aborts on upgrade failure, version detection fallback returns "1.0.0" instead of null ([#538], [#539]) -- **SQL dumps on mirroring passive servers** from FinOps collectors ([#535]) -- **RetrievedFromCache** always showing False ([#536]) -- **Arithmetic overflow** in query_stats collector for dop/thread columns ([#547]) -- **Lite perfmon chart bugs** and Dashboard ScottPlot crash handling ([#544], [#545]) -- **PLE=0 scoring bug** — was scored as harmless, now correctly flagged ([#543]) -- **PercentRank >1.0** bug in HealthCalculator -- **6 verified Lite bugs** from code review ([#611]) -- **Enterprise feature audit text** — partitioning is not Enterprise-only -- **FinOps collector scheduling**, server switch, and utilization bugs -- **Dashboard drill-down** Unicode arrow in story path split -- **Empty DataGrid scrollbar artifacts** — hide grids when empty across all FinOps tabs -- **Query preview** — truncated in row, full text in tooltip - -[#529]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/529 -[#535]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/535 -[#536]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/536 -[#538]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/538 -[#539]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/539 -[#543]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/543 -[#544]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/544 -[#545]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/545 -[#547]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/547 -[#554]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/554 -[#555]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/555 -[#557]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/557 -[#562]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/562 -[#564]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/564 -[#566]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/566 -[#576]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/576 -[#577]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/577 -[#578]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/578 -[#528]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/528 -[#589]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/589 -[#590]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/590 -[#593]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/593 -[#594]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/594 -[#604]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/604 -[#611]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/611 -[#612]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/612 - -## [2.2.0] - 2026-03-11 - -**Contributors:** [@HannahVernon](https://github.com/HannahVernon), [@ClaudioESSilva](https://github.com/ClaudioESSilva), [@dphugo](https://github.com/dphugo), [@Orestes](https://github.com/Orestes) — thank you! - -### Important - -- **Schema upgrade**: Three large collection tables (`query_stats`, `procedure_stats`, `query_store_data`) are migrated to use `COMPRESS()` for query text and plan columns. The upgrade performs a table swap (create new → migrate data → rename) which may take several minutes on large tables. A `row_hash` column is added for deduplication. Three new tracking tables are also created. Volume stats columns are added to `database_size_stats`. Upgrade scripts run automatically via the CLI/GUI installer and use idempotent checks. - - Compression results measured on a production instance: - - | Table | Compressed | Uncompressed | Ratio | - |---|---|---|---| - | query_stats | 18.0 MB | 339.0 MB | 18.8x | - | query_store_data | 13.5 MB | 258.0 MB | 19.1x | - | **Total** | **31.5 MB** | **597 MB** | **~19x** | - -### Added - -- **FinOps monitoring tab** — database size tracking, server properties, storage growth analysis (7d/30d), index analysis with unused/duplicate/compressible detection, utilization efficiency, idle database identification, and estate-level resource views ([#474]) -- **Named collection presets** — Aggressive, Balanced, and Low-Impact schedule profiles via `config.apply_collection_preset` ([#454]) -- **Entra ID interactive MFA authentication** in both CLI and GUI installers for Azure SQL MI connections ([#481]) -- **MCP port validation** — TCP port conflict detection, range validation (1024+), Auto port button, and auto-restart on settings change ([#453]) -- **Alert database exclusion filters** — filter blocking and deadlock alerts by database in both Dashboard and Lite ([#410], [#412]) -- **Configurable alert cooldown periods** for tray notifications and email alerts -- **Wait stats query drill-down** — click a wait type to see the queries causing it ([#372]) -- **Configurable long-running query settings** — max results, WAITFOR/backup/diagnostics exclusions ([#415]) -- **Uninstall option** in both CLI and GUI installers ([#431]) -- **Session stats collector** for active session tracking ([#474]) -- **LOB compression and deduplication** for query stats tables to reduce storage ([#419]) -- **Volume-level drive space** enrichment in database size stats via `dm_os_volume_stats` -- **GUI installer installation history** logging to `config.installation_history` ([#414]) -- **ReadOnlyIntent connection option** — Lite connections can set `ApplicationIntent=ReadOnly` for automatic read routing to Always On AG readable secondaries ([#515]) -- **Alert muting** — mute individual alerts or create pattern-based mute rules by server, metric, database, or application. Manage Mute Rules window with enable/disable toggle. Alert history detail view with double-click drill-down and context-sensitive detail text. Poison wait type documentation links. ([#512]) -- **SignPath code signing** — all release binaries (Dashboard, Lite, Installers) are digitally signed, eliminating Windows SmartScreen warnings ([#511]) -- CI version bump check on PRs to main -- Permissions section in README with least-privilege setup ([#421]) - -### Changed - -- **Utilization tab redesigned** — ported to Dashboard with aligned metrics between apps ([#478]) -- PlanAnalyzer rules synced from PerformanceStudio — Rule 5 message format, seek predicate parsing, spool labels, unmatched index detail ([#416], [#475], [#480]) -- Data retention now purges processed XE staging rows -- GeneratedRegex conversion for compile-time regex patterns ([#346], [#420]) -- Server health card width increased from 260 to 300 for less text truncation ([#489]) -- User's locale used for date/time formatting in WPF bindings ([#459]) -- XML processing instructions stripped from sql_command/sql_text display -- Parameterized queries in blocking/deadlock alert filtering -- **DuckDB 1.5.0 upgrade** — non-blocking checkpointing eliminates read stalls during WAL flushes, free block reuse stabilizes database file size without archive-and-reset cycles ([#516]) -- **Automatic parquet compaction** — archive files are merged into monthly files after each archive cycle, reducing file count from 2,600+ to ~75 and eliminating per-file metadata overhead on glob scans ([#516]) - - Combined with the UI responsiveness overhaul (#510), Lite's refresh cycle improved 13-26x: - - | Metric | Before | After | - |---|---|---| - | Lite `RefreshAllDataAsync` | 6-13s | < 500ms | - | Parquet files scanned per query | 233 | 19 | - | Archive-and-reset frequency | 21/day | ~0 | - | `v_wait_stats` query time | 1,700ms | 27ms | - -- **Monthly archive retention** — switched from 90-day file-age deletion to 3-month calendar-month rolling window, aligned with compacted monthly filenames ([#516]) -- **Lite status bar** shows used data size vs file size (e.g., "Database: 175.5 / 423.8 MB") via DuckDB `pragma_database_size()` ([#517]) -- **Query Store collector diagnostics** — reader/append/flush timing breakdown logged when collection exceeds 2 seconds, for identifying SQL Server DMV contention under heavy workloads ([#518]) -- SSMS-parity edge tooltips on plan viewer operator connections and ManyToMany indicator always shown for merge join operators ([#504]) -- **Lite UI responsiveness overhaul** — visible-tab-only refresh, sub-tab awareness, Query Store collector optimization (NULL plan XML + LOOP JOIN hint), and DuckDB write reduction ([#510]) - - Timer tick improvements measured under TPC-C load on SQL2022: - - | Scenario | Before | After | Improvement | - |---|---|---|---| - | Lite idle | 6-13s | 546-750ms | ~90% | - | Lite under TPC-C | 6-13s | ~3s | ~70% | - | Dashboard idle | 5.6s | 0.6-0.8s | 86% | - | Dashboard under TPC-C | 5.6s | 1.8-2.0s | 64% | - - Query Store collector specifically: - - | Metric | Before | After | - |---|---|---| - | query_store collector total | 6-18s | ~600ms | - | query_store SQL time | 374-1,104ms | ~300ms (LOOP JOIN hint) | - | query_store DuckDB write | 6-16s | ~75-230ms (NULL plan XML) | - -### Fixed - -- **UI hang** when opening Dashboard tab for offline server — replaced synchronous `.GetAwaiter().GetResult()` with proper `await` ([#477]) -- **First-collection spike** skewing PerfMon, wait stats, file I/O, memory grant, query stats, and procedure stats charts — first cumulative value now treated as baseline ([#482]) -- **Wait type filter TextBox** too small to read ([#488]) -- **Poison wait false positives** and alert log parsing ([#445], [#448]) -- **RID Lookup** analyzer rule matching new PhysicalOp label ([#429]) -- **procedure_stats** plan query using DECOMPRESS after compression migration -- **database_size_stats** InvalidCastException on compatibility_level -- **Deadlock filter** using wrong column reference in `GetFilteredDeadlockCountAsync` -- **RESTORING database** filter added to waiting_tasks collector ([#430]) -- Custom TrayToolTip crash — replaced with plain ToolTipText ([#422]) -- **Lite tab switch freeze** — added `_isRefreshing` guard to prevent tab switch handler from competing with timer ticks for DuckDB connection, eliminating "not responding" hangs ([#510]) -- DuckDB read lock acquisition resilience -- Formatted duration columns sorting alphabetically instead of numerically -- Settings window staying open on validation errors -- Deserialization clamping and validation abort issues -- **sp_IndexCleanup** summary grid column mapping off-by-one, expanded both grids to show all columns from both result sets ([#503]) -- **Rule 22 table variable** false positive on modification operators — INSERT/UPDATE/DELETE on table variables is expected ([#513]) -- **ComboBox focus steal** in plan viewer stealing keyboard focus from other controls ([#508]) -- **DOP 2 skew** false positive — parallel skew rule no longer fires at DOP 2 ([#508]) -- **ReadOnlyIntent connections** sharing server_id in DuckDB when the same server was added with and without ReadOnlyIntent ([#521]) - -[2.2.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v2.1.0...v2.2.0 - -## [2.1.0] - 2026-03-04 - -### Important - -- **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. - -### Added - -- **Light theme and "Cool Breeze" theme** — full light mode support for both Dashboard and Lite with live preview in settings ([#347]) -- **Standalone Plan Viewer** — open, paste (Ctrl+V), or drag & drop `.sqlplan` files independent of any server connection, with tabbed multi-plan support ([#359]) -- **Time display mode toggle** — show timestamps in Server Time, Local Time, or UTC with timezone labels across all grids and tooltips ([#17]) -- **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]) -- **Wait stats banner** in plan viewer showing top waits for the query ([#373]) -- **UDF runtime details** — CPU and elapsed time shown in Runtime Summary pane when UDFs are present ([#382]) -- **Sortable statement grid** and canvas panning in plan viewer ([#331]) -- **Comma-separated column filters** — enter multiple values separated by commas in text filters ([#348]) -- **Optional query text and plan collection** — per-collector flags in `config.collection_schedule` to disable query text or plan capture ([#337]) -- **`--preserve-jobs` installer flag** — keep existing SQL Agent job schedules during upgrade ([#326]) -- **Copy Query Text** context menu on Dashboard statements grid ([#367]) -- **Server list sorting** by display name in both Dashboard and Lite ([#30]) -- **Warning status icon** in server health indicators ([#355]) -- Reserved threads and 10 missing ShowPlan XML attributes in plan viewer ([#378]) -- Nightly build workflow for CI ([#332]) - -### Changed - -- PlanAnalyzer warning messages rewritten to be actionable with expert-guided per-rule advice ([#370], [#371]) -- 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]) -- First-run collector lookback reduced from 3-7 days to 1 hour for faster initial data ([#335]) -- Plan canvas aligns top-left and resets scroll on statement switch ([#366]) -- Plan viewer polish: index suggestions, property panel improvements, muted brush audit ([#365]) -- Add Server dialog visual parity between Dashboard and Lite with theme-driven PasswordBox styling ([#289]) - -### Fixed - -- **OverflowException** on wait stats page with large decimal values — SQL Server `decimal(38,24)` exceeding .NET precision ([#395]) -- **SQL dumps** on mirroring passive servers with RESTORING databases ([#384]) -- **UI hang** when adding first server to Dashboard ([#387]) -- **UTC/local timezone mismatch** in blocked process XML processor ([#383]) -- **AG secondary filter** skipping all inaccessible databases in cross-database collectors ([#325]) -- DuckDB column aliases in long-running queries ([#391]) -- sp_server_diagnostics and WAITFOR excluded from long-running query alerts ([#362]) -- UDF timing units corrected: microseconds to milliseconds ([#338]) -- DuckDB migration ordering after archive-and-reset ([#314]) -- Int16 cast error in long-running query alerts ([#313]) -- Missing dark mode on 19 SystemEventsContent charts ([#321]) -- Missing tooltips on charts after theme changes ([#319]) -- Operator time per-thread calculation synced across all plan viewers ([#392]) -- Theme StaticResource/DynamicResource binding fix for runtime theme switching -- Memory grant MB display, missing index quality scoring, wildcard LIKE detection ([#393]) -- **Installer validation** reporting historical collection errors as current failures — now filters to current run only ([#400]) -- **query_snapshots schema mismatch** after sp_WhoIsActive upgrade — collector auto-recreates daily table when column order changes ([#401]) -- **Missing upgrade script** for `default_trace_events` columns (`duration_us`, `end_time`) on 2.0.0→2.1.0 upgrade path ([#400]) - -## [2.0.0] - 2026-02-25 - -### Important - -- **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. - -### Added - -- **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]) -- **Actual execution plan support** — execute queries with SET STATISTICS XML ON to capture actual plans, with loading indicator and confirmation dialog ([#233]) -- **PlanAnalyzer** — automated plan analysis with rules for missing indexes, eager spools, key lookups, implicit conversions, memory grants, and more -- **Current Active Queries live snapshot** — real-time view of running queries with estimated/live plan download ([#149]) -- **Memory clerks tab** in Lite with picker-driven chart ([#145]) -- **Current Waits charts** in Blocking tab for both Dashboard and Lite ([#280]) -- **File I/O throughput charts** — read/write throughput trends, file-level latency breakdown, queued I/O overlay ([#281]) -- **Memory grant stats charts** — standardized collection with delta framework integration and trend visualization ([#281]) -- **CPU scheduler pressure status** — real-time scheduler, worker, runnable task counts with color-coded pressure level below CPU chart -- **Collection log drill-down** and daily summary in Lite ([#138]) -- **Collector duration trends chart** in Dashboard Collection Health ([#138]) -- **Themed perfmon counter packs** — 14 new counters with organized themed groups ([#255]) -- **User-configurable connection timeout** setting ([#236]) -- **Per-collector retention** — uses per-collector retention from `config.collection_schedule` in data retention ([#237]) -- **Query identifiers** in drill-down windows — query hash, plan hash, SQL handle visible for identification ([#268]) -- **Trace pattern drill-down** with missing columns and query text tooltips ([#273]) -- **Query Store Regressions drill-down** with TVF rewrite for performance ([#274]) -- **CLI `--help` flag** for installer ([#111]) -- Sort arrows, right-aligned numerics, and initial sort indicators across all grids ([#110]) -- Copyable plan viewer properties ([#269]) -- Standardized chart save/export filenames between Dashboard and Lite ([#284]) -- Full Dashboard column parity for query_stats, procedure_stats, and query_store_stats -- Min/max extremes surfaced in both apps — physical reads, rows, grant KB, spills, CLR time, log bytes ([#281]) - -### Changed - -- Query Store detection uses `sys.database_query_store_options` instead of `sys.databases.is_query_store_on` for Azure SQL DB compatibility ([#287]) -- Config tab consolidation, DB drop on server remove, DuckDB-first plan lookups, procedure stats parity -- Collector health status now detects consecutive recent failures — 5+ consecutive errors = FAILING, 3+ = WARNING -- Plan buttons now show a MessageBox when no plan is available instead of silently doing nothing -- CSV export uses locale-appropriate separators for non-US locales ([#240]) -- Query Store Regressions and Query Trace Patterns migrated to popup grid filtering ([#260]) -- NuGet packages updated; xUnit v3 migration - -### Fixed - -- **DuckDB file corruption** during maintenance — ReaderWriterLockSlim coordination, archive-all-and-reset at 512MB replaces compaction ([#218]) -- Archive view column mismatch, wait_stats thread-safety, and percent_complete type cast ([#234]) -- Collector health status bar text color ([#234]) -- View Plan for Query Store and Query Store Regressions tabs ([#261]) -- Query Store drill-down time filter alignment with main view ([#263]) -- Execution count mismatches between main views and drill-downs -- Drill-down chart UX — sparse data markers, hover tooltips, window sizing ([#271]) -- Truncated status text in Add Server dialog ([#257]) -- Scrollbar visibility, self-filtering artifacts, missing columns, and context menus ([#245], [#246], [#247], [#248]) -- query_stats and procedure_stats collectors ignoring recent queries -- Blank tooltips on warning and parallel badge icons -- Missing chart context menu on File I/O Throughput charts in Lite - -### Removed - -- `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]) - -## [1.3.0] - 2026-02-20 - -### Important - -- **Schema upgrade**: The `collect.memory_stats` table gains two new columns (`total_physical_memory_mb`, `committed_target_memory_mb`). The upgrade script runs automatically via the CLI/GUI installer and uses `IF NOT EXISTS` checks, so it is safe to re-run. On servers with very large `memory_stats` tables this ALTER may take a moment. - -### Added - -- Physical Memory, SQL Server Memory, and Target Memory columns in Memory Overview ([#140]) -- Current Configuration view (Server Config, Database Config, Trace Flags) in Dashboard Overview ([#143]) -- Popup column filters and right-click context menus in all drill-down history windows ([#206]) -- Consistent popup column filters across all Dashboard grids — replaced remaining TextBox-in-header filters and added filters to Trace Flags ([#200]) -- 7-day time filter option in drill-down queries ([#165]) -- Alert badge/count on sidebar Alerts button ([#109]) -- Missing poison wait defaults in wait stats picker ([#188]) - -### Changed - -- Default Trace tabs moved from Resource Metrics to Overview section ([#169]) -- Trends tab shown first in Locking section ([#171]) -- Wait stats cap raised from 20 to 30 (Dashboard) / 50 (Lite) so poison waits are never dropped ([#139]) -- Settings time range dropdown now matches dashboard button options ([#210]) -- "Total Executions" label in drill-down summaries renamed to clarify meaning ([#194]) -- WAITFOR sessions excluded from long-running query alerts ([#151]) - -### Fixed - -- Deadlock XML processor timezone mismatch — sp_BlitzLock returning 0 results because UTC dates were passed instead of local time -- Sidebar alert badge not updating when alerts dismissed from server sub-tabs ([#214]) -- Sidebar alert badge not clearing on acknowledge ([#186]) -- NOC deadlock/blocking showing "just now" for stale events instead of actual timestamp ([#187]) -- NOC deadlock severity using extended events timestamp ([#170]) -- Newly added servers not appearing on Overview until app restart ([#199]) -- Double-click on column header incorrectly triggering row drill-down ([#195]) -- Squished drill-down charts — now use proportional sizing ([#166]) -- Unreliable chart tooltips — now use X-axis proximity matching ([#167]) -- Query Trace Patterns showing empty despite data existing ([#168]) -- Drill-down windows: removed inline plan XML, added time range filtering, aggregated by collection_time ([#189]) -- Row clipping in Default Trace and Current Configuration grids ([#183], [#184]) -- Numeric filter negative range parsing ([#113]) -- MCP shutdown deadlock risk ([#112]) -- Lite DBNull cast error in database_config collector on SQL 2016 Express ([#192]) -- DuckDB concurrent file access IO errors ([#164]) - -## [1.2.0] - 2026-02-15 - -### Added - -- Alert types, alerts history view, column filtering, and dismiss/hide for alerts ([#52], [#56]) -- Average ms per wait chart toggle in both apps ([#22]) -- Collection Health tab in Lite UI ([#39]) -- Collector performance diagnostics in Lite UI ([#40]) -- Hover tooltips on all Dashboard charts ([#70]) -- Minimize-to-tray setting added to Lite ([#53]) -- Persist dismissed alerts across app restarts ([#44]) -- Locale-aware date/time formatting throughout UI ([#41]) -- 24-hour format in time range picker ([#41]) -- CI pipelines for build validation, SQL install testing, and DuckDB schema tests -- Expanded Lite database config collector to 28 sys.databases columns ([#142]) -- Parquet archive visibility and scheduled DuckDB database compaction ([#160], [#161]) -- DuckDB checkpoint optimization and collection timing accuracy -- Installer `--reset-schedule` flag to reset collection schedule on re-install - -### Fixed - -- Deadlock charts not populating data ([#73]) -- Chart X-axis double-converting custom range to server time ([#49]) -- query_cost overflow in memory grant collector ([#47]) -- XE ring buffer query timeouts on large buffers ([#37]) -- Dashboard sub-tab badge state and DuckDB migration for dismissed column -- Lite duplicate blocking/deadlock events from missing WHERE clause ([#61]) -- Procedure_stats_collector truncation on DDL triggers ([#69]) -- DataGrid row height increased from 25 to 28 to fix text clipping -- Skip offline servers during Lite collection and reduce connection timeout ([#90]) -- Mutex crash on Lite app exit ([#89]) -- Permission denied errors handled gracefully in collector health ([#150]) - -## [1.1.0] - 2026-02-13 - -### Added - -- Hover tooltips on all multi-series charts — Wait Stats, Sessions, Latch Stats, Spinlock Stats, File I/O, Perfmon, TempDB ([#21]) -- Microsoft Entra MFA authentication for Azure SQL DB connections in Lite ([#20]) -- Column-level filtering on all 11 Lite DataGrids ([#18]) -- Chart visual parity — Material Design 300 color palette, data point markers, consistent grid styling ([#16]) -- Smart Select All for wait types + expand from 12 to 20 wait types ([#12]) -- Trend chart legends always visible in Dashboard ([#11]) -- Per-server collector health in Lite status bar ([#5]) -- Server Online/Offline status in Lite overview ([#2]) -- Check for updates feature in both apps ([#1]) -- High DPI support for both Dashboard and Lite - -### Fixed - -- Query text off-by-one truncation ([#25]) -- Blocking/deadlock XML processors truncating parsed data every run ([#23]) -- WAITFOR queries appearing in top queries views ([#4]) -- Wait type Clear All not refreshing search filter in Dashboard - -## [1.0.0] - 2026-02-11 - -### Added - -- Full Edition: Dashboard + CLI/GUI Installer with 30+ automated SQL Agent collectors -- Lite Edition: Agentless monitoring with local DuckDB storage -- Support for SQL Server 2016-2025, Azure SQL DB, Azure SQL MI, AWS RDS -- Real-time charts and trend analysis for wait stats, CPU, memory, query performance, index usage, file I/O, blocking, deadlocks -- Email alerts for blocking, deadlocks, and high CPU -- MCP server integration for AI-assisted analysis -- System tray operation with background collection and alert notifications -- Data retention with configurable automatic cleanup -- Delta normalization for per-second rate calculations -- Dark theme UI - -[2.1.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v2.0.0...v2.1.0 -[2.0.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.3.0...v2.0.0 -[1.3.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/erikdarlingdata/PerformanceMonitor/releases/tag/v1.0.0 -[#1]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/1 -[#2]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2 -[#4]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/4 -[#5]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/5 -[#11]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/11 -[#12]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/12 -[#16]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/16 -[#18]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/18 -[#20]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/20 -[#21]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/21 -[#22]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/22 -[#23]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/23 -[#25]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/25 -[#37]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/37 -[#39]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/39 -[#40]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/40 -[#41]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/41 -[#44]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/44 -[#47]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/47 -[#49]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/49 -[#52]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/52 -[#53]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/53 -[#56]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/56 -[#61]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/61 -[#69]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/69 -[#70]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/70 -[#73]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/73 -[#85]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/85 -[#86]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/86 -[#89]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/89 -[#90]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/90 -[#109]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/109 -[#112]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/112 -[#113]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/113 -[#139]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/139 -[#140]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/140 -[#142]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/142 -[#143]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/143 -[#150]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/150 -[#151]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/151 -[#160]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/160 -[#161]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/161 -[#164]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/164 -[#165]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/165 -[#166]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/166 -[#167]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/167 -[#168]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/168 -[#169]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/169 -[#170]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/170 -[#171]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/171 -[#183]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/183 -[#184]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/184 -[#186]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/186 -[#187]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/187 -[#188]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/188 -[#189]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/189 -[#192]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/192 -[#194]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/194 -[#195]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/195 -[#199]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/199 -[#200]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/200 -[#206]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/206 -[#210]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/210 -[#214]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/214 -[#218]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/218 -[#220]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/220 -[#233]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/233 -[#234]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/234 -[#236]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/236 -[#237]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/237 -[#240]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/240 -[#245]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/245 -[#246]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/246 -[#247]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/247 -[#248]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/248 -[#255]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/255 -[#257]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/257 -[#260]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/260 -[#261]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/261 -[#263]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/263 -[#268]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/268 -[#269]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/269 -[#271]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/271 -[#273]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/273 -[#274]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/274 -[#280]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/280 -[#281]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/281 -[#284]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/284 -[#287]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/287 -[#313]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/313 -[#314]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/314 -[#17]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/17 -[#30]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/30 -[#319]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/319 -[#321]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/321 -[#325]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/325 -[#326]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/326 -[#327]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/327 -[#331]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/331 -[#332]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/332 -[#335]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/335 -[#337]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/337 -[#338]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/338 -[#341]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/341 -[#342]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/342 -[#343]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/343 -[#347]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/347 -[#348]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/348 -[#349]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/349 -[#355]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/355 -[#356]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/356 -[#358]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/358 -[#359]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/359 -[#362]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/362 -[#365]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/365 -[#366]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/366 -[#367]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/367 -[#370]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/370 -[#371]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/371 -[#373]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/373 -[#378]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/378 -[#379]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/379 -[#382]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/382 -[#383]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/383 -[#384]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/384 -[#387]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/387 -[#391]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/391 -[#392]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/392 -[#393]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/393 -[#289]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/289 -[#395]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/395 -[#400]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/400 -[#401]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/401 -[#410]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/410 -[#412]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/412 -[#414]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/414 -[#415]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/415 -[#416]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/416 -[#419]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/419 -[#420]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/420 -[#421]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/421 -[#422]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/422 -[#429]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/429 -[#430]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/430 -[#431]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/431 -[#445]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/445 -[#448]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/448 -[#453]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/453 -[#454]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/454 -[#459]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/459 -[#474]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/474 -[#475]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/475 -[#477]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/477 -[#478]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/478 -[#480]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/480 -[#481]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/481 -[#482]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/482 -[#488]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/488 -[#489]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/489 -[#503]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/503 -[#504]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/504 -[#508]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/508 -[#510]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/510 -[#512]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/512 -[#511]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/511 -[#513]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/513 -[#515]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/515 -[#516]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/516 -[#517]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/517 -[#518]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/518 -[#521]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/521 +# Changelog + +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). + +## [Unreleased] + +### Fixed + +- **Dashboard time labels are now consistently 24-hour** ([#1012]) — the time-range header at the top of each tab (e.g. *"Original: May 28, 11:30 PM – May 29, 1:30 AM (PST)"*) and the Query Performance heatmap x-axis tick labels used `h:mm tt`, while every other timestamp in the app (footer "Last refresh", DataGrid columns, slicer, tooltips, logs) already used 24-hour `HH:mm`/`HH:mm:ss`. The AM/PM marker was also being truncated in the column shown by the reporter. Normalized the four outliers to `HH:mm` to match the rest of the app. The Lite heatmap had the same `h:mm tt` straggler — fixed alongside +- **Lite UI no longer freezes during archival** ([#979]) — archival held DuckDB's exclusive write lock across the entire export-to-Parquet step, blocking every UI query (tab switches showed the spinning wheel, worse with more monitored servers). Export-to-Parquet only reads the database, so it now runs under a shared read lock concurrently with the UI; only the brief `DELETE` takes the exclusive write lock +- **Lite FinOps no longer recommends an edition downgrade on an Availability Group secondary** ([#980]) — the licensing recommendations suggested "downgrade to Standard to save $X/mo" for any Enterprise instance, with no AG awareness. On a secondary replica that advice is misleading — every replica in an AG must run the same edition. FinOps now detects the AG replica role and, on a secondary, shows an informational note instead of the downgrade/savings estimate +- **Lite alert emails no longer re-fire after an app restart** ([#981]) — the per-metric email cooldown lived only in memory, so restarting Lite cleared it and an alert sent minutes earlier could be sent again immediately. The cooldown is now seeded from `config_alert_log` (the most recent successful send for that server/metric) the first time each alert is evaluated, so it survives restarts +- **Dashboard alert emails no longer re-fire after an app restart** — brings Dashboard `EmailAlertService` to parity with the Lite-side persistence introduced in [#981]. The cooldown is now seeded from the in-memory alert log (loaded from `alert_history.json` on startup) the first time each `{serverId}:{metricName}` key is evaluated +- **Analysis-finding notification cooldowns now persist across restarts on both Lite and Dashboard** — the per-finding re-notification cooldown in `AnalysisNotificationService` lived only in memory, so restarting either app cleared it and a finding that had just fired (and entered its `AnalysisNotifyCooldownMinutes` cooldown) could re-notify immediately. The cooldown now seeds lazily from the alert log (Lite: `config_alert_log`; Dashboard: `alert_history.json`) on first lookup per finding, mirroring the email-cooldown pattern from #981. Entries past 2× the cooldown window are pruned on each notify cycle so the dictionary stays bounded +- **Data Retention job no longer fails with `xp_delete_file` error 22049** ([#972]) — the trace-file cleanup added in v2.11.0 passed a wildcard path to `xp_delete_file`, raising an uncatchable `Msg 22049` that failed the entire `PerformanceMonitor - Data Retention` Agent job on every run once any `Monitor_LongQueries_*.trc` files existed. `xp_delete_file` also cannot delete `.trc` files at all — it only accepts SQL Server backup files and Maintenance Plan report files — so that cleanup step has been removed from `config.data_retention` + +### Changed + +- **Plan parsing / analysis extracted to shared library `PerformanceMonitor.PlanAnalysis`** — the previously duplicated `ShowPlanParser`, `PlanAnalyzer`, `BenefitScorer`, `PlanLayoutEngine`, and `PlanModels` pairs across `Dashboard/Services` + `Dashboard/Models` and `Lite/Services` + `Lite/Models` are now one copy referenced by both apps via ``. The new library targets `net10.0` (no WPF) and has zero dependency on `PerformanceMonitor.Analysis` — the two shared libraries are independent. ~5,100 LOC of byte-equivalent duplication eliminated. The `planalyzer-sync-checker` agent is retired (no copies to sync). `ActualPlanExecutor` stays per-app this release because it calls `ReproScriptBuilder` (Class B, drifted between Lite and Dashboard); both will be extracted in a follow-up PR once `ReproScriptBuilder` is reconciled and a logging abstraction is designed +- **`PlanIconMapper` split to break a shared-library WPF dependency** — `ShowPlanParser` calls `PlanIconMapper.GetIconName` to populate `PlanNode.IconName` during parse, but the rest of `PlanIconMapper` is WPF-bound (`GetIcon` returns `BitmapImage`). The pure-data half (the `IconMap` dictionary + the `GetIconName` lookup) is now `IconNameMapper` inside `PerformanceMonitor.PlanAnalysis`. The per-app `PlanIconMapper.GetIcon(string iconName)` is unchanged; the per-app `GetIconName` forwarder is gone (`ShowPlanParser` calls `IconNameMapper.GetIconName` directly, and there were no other callers) +- **Analysis engine extracted to shared library `PerformanceMonitor.Analysis`** — the previously duplicated `FactScorer`, `RelationshipGraph`, `InferenceEngine`, `AnalysisModels`, `IFactCollector`, `IPlanFetcher`, and `BlockingChainReconstructor` pairs across `Dashboard/Analysis/` and `Lite/Analysis/` are now one copy referenced by both apps and both test projects via ``. The new library targets `net10.0` (no WPF) so it can be picked up by future non-WPF consumers without a multi-target rewrite. The `blocking-reconstructor-sync-checker` agent is retired (no copies to sync). `BlockingChainReconstructorTests` ported to `Dashboard.Tests` (10 tests) as part of the same change — Dashboard now exercises the same reconstruction coverage as Lite. `AnalysisService` and the DB-bound adapters (`*FactCollector`, `*DrillDownCollector`, `*FindingStore`, `*AnomalyDetector`, `*BaselineProvider`, `*PlanFetcher`) stay per-app because they bind to `DuckDBConnection` vs `SqlConnection`. `PlanAnalyzer` and its `planalyzer-sync-checker` are outside this extraction's scope and stay +- **Trace files are now bounded at the source** ([#972]) — `collect.trace_management_collector` creates the long-query trace with a rollover file-count cap (`@filecount`, via the new `@max_files` parameter, default 5), so SQL Server itself deletes the oldest `.trc` file as the trace rolls. The scheduled collector also now issues `START` instead of `RESTART`: it keeps one trace running rather than tearing it down and spawning a fresh timestamped trace — and a fresh batch of orphaned files — every cycle +- **Blocked-process reports expose blocker-side fields as typed columns** — `collect.blocking_BlockedProcessReport` now carries `blocking_spid`, `blocking_last_tran_started`, `blocking_status`, `blocked_sql_text`, and `blocking_sql_text` populated at insert time from `blocked_process_report_xml`. Existing rows are backfilled idempotently by the 2.11.0 → 2.12.0 upgrade script +- **Blocking-chain reconstruction now reads typed columns from `collect.blocking_BlockedProcessReport`** instead of re-parsing `blocked_process_report_xml` on every analysis cycle — eliminates up to 5000 `XElement.Parse` calls per `BLOCKING_CHAIN` fact collection. The Dashboard `BlockedProcessXmlParser` has been deleted; the Lite collection-time parser is unchanged (Lite has no SQL-side staging table and still parses once at collect time) +- **Analysis minimum-data threshold lowered to 24 hours** — `Lite/Analysis/AnalysisService.cs` and `Dashboard/Analysis/AnalysisService.cs` now require 24 hours of collected data before analysis runs, down from 72. Validated empirically as sufficient for fraction-of-period calculations, so a fresh install starts producing findings after one day instead of three + +### Added + +- **`tools/Remove-OrphanedTraceFiles.ps1`** ([#972]) — one-time cleanup script for `Monitor_LongQueries_*.trc` files left on disk by versions through 2.11.0. Run it on the SQL Server host; it skips files belonging to a running trace and files that are in use +- **`FactAdvice` and `FactRemediation` in `PerformanceMonitor.Analysis`** — new shared-library data layer that maps every scorable fact-key to a Headline / Investigation / Remediation advice block, plus a copy-paste-ready `sp_query_store_force_plan` T-SQL generator for `PLAN_REGRESSION` findings (gated to that single fact-key in v1; `PARAMETER_SENSITIVITY` deliberately does not generate plan-force T-SQL because forcing locks in the wrong plan for some parameter values). Drill-down collectors now also project `best_plan_id` (via `MAX(plan_id)` in the plan-dedup CTE) so the generated EXEC carries the integer ID `sp_query_store_force_plan` actually accepts, not just the hash. Lite's `BuildContext` now mirrors Dashboard's — both apps emit a Diagnosis card at `Details[0]` carrying Story / Severity / Notify threshold / Confidence / Facts / Database / Window before the drill-down items. The rendering surfaces that consume this data (email HTML, plain-text email, Teams + Slack webhook payloads, in-app Alert Details window) ship in a separate follow-up PR + +[#972]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/972 +[#979]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/979 +[#980]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/980 +[#981]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/981 +[#1012]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/1012 + +## [2.11.0] - 2026-05-19 + +### Important + +- **.NET 10 upgrade** — Dashboard, Lite, Installer, and Installer.Core now target `net10.0` (Windows projects target `net10.0-windows`). Building from source now requires the .NET 10 SDK; CI is pinned to 10.0.204 via `global.json` for reproducible builds. End users running prebuilt Velopack installers do not need to install anything separately — runtime is bundled ([#958]) +- **Setup.exe is now the recommended install path** for Dashboard and Lite — the README steers users to the Velopack `Setup.exe`, which installs to `%LocalAppData%`, registers the apps under Apps & Features, creates Start Menu and Desktop shortcuts, and wires up auto-update. Portable ZIPs are still produced for both apps (CI release pipeline and local build scripts) as a fallback for advanced or air-gapped users. The Installer ZIP (CLI installer + SQL scripts) is unchanged +- **Shared `servers.json` location** — Dashboard and Lite now store `servers.json` under `%ProgramData%\PerformanceMonitor{Dashboard,Lite}\` so every Windows user on the same machine shares one server list. First run migrates an existing per-user `servers.json` to the new location and grants Authenticated Users Modify on the directory. SQL credentials remain per-user in Windows Credential Manager — each DBA re-enters SQL passwords on first connect; Windows Auth works with no re-entry + +### Added + +- **One-click snooze from the alert tray popup** in Lite — snooze an alert directly from the tray notification balloon without opening the main window ([#944]) +- **Snooze hint in email and Teams/Slack alert payloads** — alert messages now show the snooze duration / scheduled wake time when an alert is fired while a snooze is active ([#944]) +- **Process memory logging per collection cycle** in Lite — the collector now logs working set and private bytes at the end of each cycle, making it easier to track memory growth in long-running sessions + +### Changed + +- **Lite compaction memory tuning** ([#933]) — multiple changes to make parquet compaction robust on wide-row tables and large datasets: + - Cap the main collector connection's `memory_limit` and raise it transiently only for the `COPY` step + - Detect compaction `EXCLUDE` columns per merge step instead of once up front + - Raise the compaction `memory_limit` floor to 4 GB + - Set DuckDB `temp_directory` explicitly so spill files don't blow the OS temp drive + - Compact parquet in size-budgeted batches instead of one mega-batch +- **Trace collectors honor `config.collector_database_exclusions`** ([#887] follow-up) — the trace-file based collectors now filter against the exclusions table, matching the behavior of the eight DMV-based per-database collectors shipped in v2.9.0 +- **InstallerGui project directory removed** — the WPF InstallerGui was retired in v2.9.0 in favor of the Dashboard's integrated Add Server dialog. The project directory has now been deleted from the repo +- **Build warnings cleaned up** across Lite, Dashboard, and Installer ([#945]) +- **GitHub Actions runners bumped** to Node 24-compatible major versions to silence deprecation warnings + +### Fixed + +- **Re-run `installation_history` column widening** for servers that crossed v2.4.0 → v2.5.0 before PR #828's fix shipped in v2.7.0. Those servers ran the original widen script as a no-op against `master`, then advanced their installer_version past 2.5, so the now-fixed script never reapplied. Adds an idempotent ALTER under an `IF EXISTS` guard checking `max_length = 510` ([#828]) +- **Mute rules preserved across size-triggered DuckDB reset** in Lite — when the local DuckDB exceeded the configured size budget and was reset, mute rules were being lost. They now survive the reset ([#938]) +- **Chart tooltips break after tab switch** — root-cause fix for the popup-wedge issue first patched in v2.10.0. Both the Memory tab handlers and `CorrelatedCrosshairManager` are now resilient to tab churn ([#916], [#937]) +- **Stale `Monitor_LongQueries_*.trc` files cleaned up** by `config.data_retention` — the trace-file cleanup step previously left old `.trc` files behind on disk ([#951]) +- **Nullability guards** added to the remaining comparison overlay tasks that were producing CS86xx warnings + +[#828]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/828 +[#887]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/887 +[#916]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/916 +[#933]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/933 +[#937]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/937 +[#938]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/938 +[#944]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/944 +[#945]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/945 +[#951]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/951 +[#958]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/958 + +## [2.10.0] - 2026-05-04 + +### Fixed + +- **Memory tab tooltip** stops working after switching away and returning to the tab. Both Dashboard and Lite Memory tab crosshair tooltip handlers now reattach correctly on tab re-entry; the same popup-wedge fix is also applied to `CorrelatedCrosshairManager` ([#916]) +- **FinOps memory recommendation** now bases sizing on a 7-day P95 of memory samples instead of a single snapshot, so recommendations no longer swing based on instantaneous workload state. Applied in both Dashboard and Lite ([#917]) + +### Changed + +- **Per-database grants for FinOps Index Analysis** documented in the README — sp_IndexCleanup-backed Index Analysis requires per-database `EXECUTE` grants on each user database you want to analyze ([#915]) + +[#915]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/915 +[#917]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/917 + +## [2.9.0] - 2026-04-29 + +### Important + +- **Breaking change to `config.data_retention`** — the `@truncate_all` parameter has been removed. Pass `@retention_days = 0` for the same behavior. `@retention_days = NULL` (default) respects per-collector retention from `config.collection_schedule` with a 30-day fallback for unscheduled tables; `@retention_days = N > 0` overrides every table to N days. Any existing Agent jobs or scripts calling `data_retention @truncate_all = 1` need to be updated ([#900]) +- **New `config.collector_database_exclusions` table** for per-database collector exclusions. Eight per-database collectors filter against this table; system databases remain hard-skipped by the collectors themselves. Existing installs get the table on the next upgrade — `install/01_install_database.sql` and `config.ensure_config_tables` both create it under an `IF OBJECT_ID … IS NULL` guard ([#887]) + +### Added + +- **Per-database collector exclusions** — exclude noisy or unimportant databases from per-database collectors. Dashboard side adds `config.collector_database_exclusions` and filters 8 collectors (`query_stats`, `query_store`, `procedure_stats`, `file_io_stats`, `waiting_tasks`, `database_configuration`, `database_size_stats`, `server_properties`). Lite side adds an `ExcludedDatabases` list per server in `servers.json` and filters 9 collectors ([#887]) +- **`Off` collection preset** — `EXECUTE config.apply_collection_preset @preset_name = N'Off'` disables every collector in one call. Pair with a second Agent job that applies a non-`Off` preset at the start of your active window for overnight / quiet-hours scoping. Non-`Off` presets now also set `enabled = 1` across the board so the switch from `Off → Balanced` reliably resumes collection ([#888]) +- **Purge Now action** in Manage Servers — confirm dialog with a mode picker (Use configured / 1 / 3 / 7 / Custom / All) drives `config.data_retention`; right-click menu on the Manage Servers grid mirrors every per-row action (Edit, Toggle Favorite, Check Server Version, Purge Now, Remove) ([#900]) +- **Total non-idle CPU on Lite Overview** — headline value shows total CPU with the SQL-only value alongside (e.g. `64% (SQL 60%)`); new `CpuAlertMode` dropdown in Settings → Alerts (Total / SqlOnly) drives both the alert evaluator and headline color; tray notifications and email alerts label the value as "Total CPU" or "SQL CPU" ([#899]) +- **Resume gap detection** — `query_stats`, `procedure_stats`, and `query_store` collectors skip the historical sweep on first run after an Off preset, Agent stoppage, or server reboot. When the last successful run is older than 5× the configured `frequency_minutes` (floored at 30 minutes), the cutoff clamps to `SYSDATETIME()` so only forward-going data is collected on resume — preventing the tempdb blowout that hit the original reporter ([#892]) +- **Right-click View Plan** on Dashboard Blocked Process Reports (View Blocked Plan + View Blocking Plan), Dashboard Deadlocks, and Lite Deadlocks grids. Plan lookup hits `sys.dm_exec_query_stats` + `sys.dm_exec_text_query_plan` on the monitored server, falling back to `executionStack/frame` entries when the process-level `sql_handle` is empty or evicted ([#880]) +- **Open Log Folder** sidebar button in Lite — opens `%LocalAppData%\PerformanceMonitorLite\logs\` in Explorer for grabbing historical logs to attach to bug reports. Sits below View Log, which retains its existing behavior of opening today's log file ([#873]) +- **Installed Version column** in the Manage Servers grid for both Dashboard and Lite. Dashboard shows the PerformanceMonitor database version on each server (probed in parallel via `GetInstalledVersionAsync`, with `Not installed` / `Unavailable` fallbacks). Lite shows the running app's own version on every row, mirroring Full's column header for consistency. +- **Lite-style server card indicators in Full** — back-ported the Ellipse-with-DataTriggers status dot (Online/Offline/Warning/Unknown) and the right-aligned favorite star from Lite to the Full Dashboard's server list, matching Lite's visual treatment. +- **Architecture overview** at `docs/how-collection-works.md` covering the minute loop, dispatcher, collector shape, `config.collection_schedule`, retention, and the Dashboard read path + +### Changed + +- **PlanIconMapper synced** with PerformanceStudio v1.9.0 improvements — columnstore storage type on scan/delete/insert/update/merge operators routes to `columnstore_index_*` icons (covers CCI and NCCI); `Parallelism` operator subtypes (Repartition Streams, Distribute Streams, Gather Streams) get their own icons +- **`Microsoft.Data.SqlClient` 6.1.4 → 7.0.1** — major-version bump. Azure/Entra dependencies were split out of the core package in 7.0; `Microsoft.Data.SqlClient.Extensions.Azure 1.0.0` added to Dashboard, Lite, and Installer.Core for `ActiveDirectoryInteractive` connections +- **`ModelContextProtocol` 0.7.0-preview.1 → 1.2.0** — off the preview tag and onto stable 1.x in Dashboard and Lite +- **`DuckDB.NET` 1.5.0 → 1.5.2** in Lite — fixes unbounded row group growth on indexed tables under repeated load+insert cycles, memory leaks and race conditions in prepared statements, WAL checkpoint marking, and Windows UTF-8/UTF-16 handling +- **`Microsoft.Extensions.*` 10.0.5 → 10.0.7**, **`System.Text.Json` 10.0.5 → 10.0.7**, **`ScottPlot.WPF` 5.1.57 → 5.1.58** — patch-level bumps with no expected behavioral change +- **Theme polish** on grids and plan viewer in Dashboard and Lite — thanks [@ClaudioESSilva](https://github.com/ClaudioESSilva) ([#889]) + +### Fixed + +- **Install loop timeout** raised from 5 minutes to 1 hour. `install/98_validate_installation.sql` runs every enabled collector with `@debug = 1` in a single batch; on large databases (reporter had 7.2M rows in `collect.query_stats`, 4.4M in `collect.query_store_data`) this took ~9 minutes and was blowing the 5-minute timeout, failing the install or upgrade ([#884]) + +[#873]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/873 +[#880]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/880 +[#884]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/884 +[#887]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/887 +[#888]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/888 +[#889]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/889 +[#892]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/892 +[#899]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/899 +[#900]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/900 + +## [2.8.0] - 2026-04-22 + +### Important + +- **New nonclustered indexes** on `collect.query_stats`, `collect.procedure_stats`, and `collect.query_store_data` to eliminate Eager Index Spools in Dashboard grid queries. On large installations these indexes may take several minutes to build; the upgrade script uses `ONLINE = ON` on Enterprise/Developer/Azure editions and falls back to offline on Standard/Web ([#835]) + +### Added + +- **Memory Pressure Events in Lite** — the collector, chart, and `get_memory_pressure_events` MCP tool previously only in the Full Edition are now available in Lite ([#865]) +- **Grid auto-scrolling** in Lite and Dashboard ([#843]) — thanks [@ClaudioESSilva](https://github.com/ClaudioESSilva) + +### Changed + +- **PlanAnalyzer and BenefitScorer** synced with PerformanceStudio's Apr 9–16 improvements +- **Query/Procedure/Query Store stats** refactored to a phased DECOMPRESS approach; removed unhelpful `WAITFOR DECOMPRESS` filters +- **Query/Procedure/Query Store grids** capped to TOP 500 to prevent UI freezes on large datasets +- **Server tabs lazy-load** — only the visible server tab loads on startup; remaining tabs load on first visit +- **Webhook URLs (Dashboard)** encrypted with DPAPI via Windows Credential Manager — Lite webhook URLs remain in plaintext settings for now +- **DuckDB queries hardened** — parameterized values, escaped paths, fixed `IsArchiving` race +- **Lite chart axes and sub-tab styling** polished, then ported to Dashboard + +### Fixed + +- **Memory Pressure Events chart filter** was dropping valid rows; added MCP interpretation guidance ([#865]) +- **FinOps recommendation severity sort order** in Lite and Dashboard ([#872]) +- **Overview crosshair** disappearing after tab switches or layout passes +- **Blocked process report plan lookup** returning the wrong plan ([#867]) +- **FinOps TDE recommendation** flagging Standard edition on SQL Server 2019+ where TDE is free ([#854]) +- **Azure SQL DB collector** falls back to single-database mode when `master` is inaccessible ([#857]) +- **Azure SQL DB query snapshots** scoped to the current database ([#857]) +- **Azure SQL DB query snapshot prefilter** — request set is narrowed into `#temp` before joining DMVs to avoid Azure-specific execution plan issues ([#857]) +- **Azure SQL DB live query plans** — now skipped gracefully instead of erroring ([#857]) +- **Azure SQL DB memory_stats collector** — dropped `sys.dm_os_schedulers` which is blocked on elastic-pool contained users regardless of DB-scoped grants ([#857]) +- **Non-transient permission denials** now stop collector retries instead of looping forever ([#857]) + +[#835]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/835 +[#843]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/843 +[#854]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/854 +[#857]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/857 +[#865]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/865 +[#867]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/867 +[#872]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/872 + +## [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 + +- **Correlated timeline lanes** on Lite Overview and Dashboard — synchronized CPU, memory, waits, and TempDB trend lanes for at-a-glance correlation ([#688]) +- **Dynamic baselines and anomaly detection** in Lite and Dashboard — automatic baseline calculation with anomaly highlighting on key metrics ([#692], [#693]) +- **Query grid comparison** — before/after comparison mode for query grids in Lite and Dashboard with global Compare dropdown ([#687]) +- **Nonclustered index count badge** on modification operators in plan viewer ([#788]) +- **Upgrade detection in Edit Server** dialog — see pending upgrades without adding a new server ([#772]) +- **CLI installer interactive mode** prompts for trust-cert and encryption settings ([#784]) +- **SignPath code signing** — release binaries are now digitally signed via the [SignPath FOSS](https://signpath.io) program + +### Changed + +- **PlanAnalyzer Rule 3 (Serial Plan)** comprehensively refined — severity demotion for TRIVIAL and 0ms plans, `CouldNotGenerateValidParallelPlan` treated as actionable, all 25 `NonParallelPlanReason` values now covered +- **PlanAnalyzer warning rules** ported from PerformanceStudio improvements +- **Text readability** — replaced all muted/dim text colors with full foreground colors for readability + +### Fixed + +- **Embedded resource upgrade discovery** broken — upgrades silently returned zero results for Dashboard installs ([#772]) +- **Archive compaction OOM** on large parquet groups +- **CLI installer argument parsing** treating flags as positional args ([#786]) +- **Lite long-running query alerts** firing on stale DuckDB snapshots +- **FinOps Enterprise feature detection** now queries all databases and filters to TDE only ([#780]) +- **Second launch error** — now brings existing window to foreground instead ([#769]) +- **Overview tab Memory Grant** showing 0 for all timestamps ([#776]) +- **Lite FinOps Enterprise features** query error on servers without `database_id` column ([#777]) +- **Collector health status** incorrect for on-load collectors +- **CSV and clipboard exports** writing `System.Windows.Controls.StackPanel` as column headers instead of actual header text ([#805]) + +[#687]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/687 +[#688]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/688 +[#692]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/692 +[#693]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/693 +[#769]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/769 +[#772]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/772 +[#776]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/776 +[#777]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/777 +[#780]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/780 +[#784]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/784 +[#786]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/786 +[#788]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/788 +[#805]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/805 + +## [2.5.0] - 2026-03-30 + +### Important + +- **InstallerGui retired**: The standalone GUI installer has been removed. Installation, upgrade, and uninstall are now handled directly from the Dashboard's Add Server dialog, powered by the new Installer.Core shared library. The CLI installer continues to work as before. ([#755]) + +### Added + +- **Dashboard integrated installer** — Add Server dialog now installs, upgrades, and uninstalls PerformanceMonitor directly, replacing the standalone InstallerGui ([#755]) +- **Installer.Core shared library** — shared installation logic used by both the CLI installer and Dashboard ([#755]) +- **Overview tab** for Lite with 2x2 resource chart grid (CPU, Memory, Wait Stats, TempDB) ([#689]) +- **Chart drill-down** on CPU, Memory, TempDB, Blocking, and Deadlock charts in both Dashboard and Lite — right-click any chart point to jump to Active Queries for that time window ([#682]) +- **Grid-to-slicer overlay** for Query Stats, Procedure Stats, and Query Store tabs — click a row to overlay its trend on the slicer chart ([#683]) +- **Query heatmap** tab in both Dashboard and Lite — visual heat map of query activity over time ([#739], [#743]) +- **Webhook notifications** for alerts — configurable webhook endpoint for alert delivery ([#725]) +- **Per-server collector schedule intervals** — customize collection frequency per server ([#703]) +- **Investigate button** in Critical Issues grid — jump directly to relevant tab from an alert ([#684]) +- **Dismiss Selected** context menu and View Log sidebar button for alert management ([#718], [#740]) +- **Alert archival awareness** — dismissed_archive_alerts sidecar table, source column for live vs archived alerts, stale-data indicator, structured telemetry ([#718]) +- **Dashboard read-only connection intent** — connections use `ApplicationIntent=ReadOnly` where supported ([#728]) +- FUNDING.yml for GitHub Sponsors ([#752]) + +### Changed + +- **Installer architecture** refactored: CLI installer is now a thin wrapper over Installer.Core ([#755]) +- **DuckDB memory capped** at 2 GB during parquet compaction to prevent out-of-memory on large archives ([#758]) +- **Text rendering** improved with `TextOptions.TextFormattingMode="Display"` for sharper text ([#710]) +- **installation_history version columns** widened from nvarchar(255) to nvarchar(512) to handle long @@VERSION strings ([#712]) + +### Fixed + +- **Memory leaks in Lite** — delta cache, event handlers, and chart helpers properly disposed ([#758]) +- **Doomed transaction errors** in delta framework and ensure_collection_table — ROLLBACK now occurs before error logging ([#756]) +- **XACT_STATE check** added after third-party stored procedure calls (sp_HumanEventsBlockViewer, sp_BlitzLock) to prevent doomed transaction errors ([#695]) +- **CREATE DATABASE failure** when model database has large default file sizes ([#676]) +- **CPU metrics mixed** for different Azure SQL databases on the same logical server ([#680]) +- **Azure SQL DB vCore** FinOps calculations incorrect for serverless/vCore tiers ([#736]) +- **Webhook alert recording** not persisting correctly ([#726]) +- **Drill-down timezone** misalignment between chart and detail view ([#747], [#750]) +- **Drill-down refresh** losing context on auto-refresh ([#744]) +- **Drill-down target** incorrectly routing Memory to Memory Grants instead of Active Queries ([#706]) +- **Heatmap colorbar stacking** when switching between servers ([#746]) +- **Display mode pickers** not reflecting current state on tab switch ([#751]) +- **Slicer custom range** handling and sub-hour display issues ([#704]) +- **Overlay selection** lost on Dashboard auto-refresh ([#683]) +- **Numeric values** in alert details treated as strings instead of numbers ([#732]) +- **FinOps VM right-sizing** query error — `PERCENTILE_CONT` missing required `OVER()` clause +- **FinOps Enterprise features** query error on AWS RDS — `database_id` column not present in `sys.dm_db_persisted_sku_features` on RDS +- **FinOps right-click copy** broken on all Dashboard FinOps grids — context menu walked to row instead of grid +- **FinOps recommendation error logs** now include server name for easier troubleshooting + +### Deprecated + +- **InstallerGui** — removed from the solution and build pipeline. Use the Dashboard or CLI installer instead. ([#755]) + +[#676]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/676 +[#680]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/680 +[#682]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/682 +[#683]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/683 +[#684]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/684 +[#689]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/689 +[#695]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/695 +[#703]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/703 +[#704]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/704 +[#706]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/706 +[#710]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/710 +[#712]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/712 +[#718]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/718 +[#725]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/725 +[#726]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/726 +[#728]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/728 +[#732]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/732 +[#736]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/736 +[#739]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/739 +[#740]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/740 +[#743]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/743 +[#744]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/744 +[#746]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/746 +[#747]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/747 +[#750]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/750 +[#751]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/751 +[#752]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/752 +[#755]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/755 +[#756]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/756 +[#758]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/758 + +## [2.4.0] - 2026-03-23 + +### Important + +- **Lite data directory moved**: Lite now stores all data (config, DuckDB, archives, logs) in `%LOCALAPPDATA%\PerformanceMonitorLite\` instead of alongside the executable. This enables auto-update support. Existing users upgrading from the zip should use **Import Settings** and **Import Data** to bring over their configuration and historical data from the old install folder. +- **Auto-update (Windows)**: Both Dashboard and Lite now include Velopack auto-update. Users who install via the new Setup.exe will receive update notifications and can download + apply updates from within the app. Existing zip distribution continues to work as before. + +### Added + +- **Velopack auto-update** for Dashboard and Lite — check on startup, download + apply from About window with confirmation dialog before restart ([#635]) +- **Per-tab time range slicers** on Dashboard and Lite query tabs — filter data directly on each tab without changing global time range ([#655], [#662]) +- **Time display picker** (Local/UTC/Server) in Dashboard and Lite toolbars ([#646]) +- **Import Settings** — renamed from "Import Connections", now also copies `settings.json`, `collection_schedule.json`, `ignored_wait_types.json`, and `alert_state.json` from a previous install +- **Alert muting improvements** — pre-fill context fields (database, query, wait type, job name) from alert detail text, configurable default expiration for new mute rules, tooltip on query text field ([#642]) +- **Missing date columns** on Query Stats and Procedure Stats tabs (`creation_time`, `last_execution_time`) ([#649], [#651], [#654]) +- **Trace pattern drill-down** now includes `CollectionTime` and `NtUserName` columns ([#663]) +- **DataGrid sort preservation** across auto-refresh — sort order no longer resets when data refreshes ([#659]) +- **CLI installer**: colored output (green/red/yellow) and version check on startup ([#639]) +- **GUI installer**: version check on startup +- **Growth rate and VLF count** columns in Database Sizes (from v2.3.0 nightly, now in upgrade path) ([#567]) +- `llms.txt` and `CITATION.cff` for project discoverability ([#630]) + +### Changed + +- **Lite data directory** moved to `%LOCALAPPDATA%\PerformanceMonitorLite\` for Velopack compatibility +- **Delta gap detection** added to all cumulative-counter collectors (file I/O, wait stats, query stats, procedure stats, memory grants) — prevents inflated spikes after app restart ([#633]) +- **File I/O NULL fallbacks** improved when `sys.master_files` is inaccessible — falls back to `DB_NAME()` and `File_{id}` instead of generic "Unknown" ([#633]) +- **Running jobs collector** skipped gracefully when login lacks msdb access ([#656]) +- NuGet packages updated to latest minor versions ([#653]) + +### Fixed + +- **Installer writing SUCCESS when files fail** — CLI tolerated 1 failure in automated mode, GUI had a similar workaround. Now any failure = not success. +- **Query stats collector causing SQL dumps** on passive mirror servers — removed `dm_exec_plan_attributes` CROSS APPLY, uses temp table of ONLINE database IDs instead ([#632]) +- **Trigger name extraction** fails when comment before `CREATE TRIGGER` contains " ON " ([#666]) +- **FinOps expensive queries** DuckDB error — query referenced `statement_start_offset` column that doesn't exist in schema +- **Imported parquet files** not recognized by archive compaction — added regex patterns for `imported_` prefix +- **Auto-refresh after Import Data** — views now refresh immediately after import completes + +[#630]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/630 +[#632]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/632 +[#633]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/633 +[#635]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/635 +[#639]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/639 +[#642]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/642 +[#646]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/646 +[#649]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/649 +[#651]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/651 +[#653]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/653 +[#654]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/654 +[#655]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/655 +[#656]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/656 +[#659]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/659 +[#662]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/662 +[#663]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/663 +[#666]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/666 +[#567]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/567 + +## [2.3.0] - 2026-03-18 + +### Important + +- **Schema upgrade**: Six columns widened across three tables (`query_stats`, `cpu_scheduler_stats`, `waiting_tasks`, `database_size_stats`) to match DMV documentation types. These are in-place ALTER COLUMN operations — fast on any table size, no data migration. Upgrade scripts run automatically via the CLI/GUI installer. +- **SQL Server version check**: Both installers now reject SQL Server 2014 and earlier before running any scripts, with a clear error message. Azure MI (EngineEdition 8) is always accepted. ([#543]) +- **Installer adversarial tests**: 35 automated tests covering upgrade failures, data survival, idempotency, version detection fallback, file filtering, restricted permissions, and more. These run as part of pre-release validation. ([#543]) + +### Added + +- **ErikAI analysis engine** — rule-based inference engine for Lite that scores server health across wait stats, CPU, memory, I/O, blocking, tempdb, and query performance. Surfaces actionable findings with severity, detail, and recommended actions. Includes anomaly detection (baseline comparison for acute deviations), bad actor detection (per-query scoring for consistently terrible queries), and CPU spike detection for bursty workloads. ([#589], [#593]) +- **ErikAI Dashboard port** — full analysis engine ported to Dashboard with SQL Server backend ([#590]) +- **FinOps cost optimization recommendations** — Phase 1-4 checks: enterprise feature audit, CPU/memory right-sizing, compression savings estimator, unused index cost quantification, dormant database detection, dev/test workload detection, VM right-sizing, storage tier optimization, reserved capacity candidates ([#564]) +- **FinOps High Impact Queries** — 80/20 analysis showing which queries consume the most resources across all dimensions ([#564]) +- **FinOps dollar-denominated cost attribution** — per-server monthly cost setting with proportional database-level breakdown ([#564]) +- **On-demand plan fetch** for bad actor and analysis findings — click to retrieve execution plans for flagged queries ([#604]) +- **Plan analysis integration** — findings include execution plan analysis when plans are available ([#594]) +- **Server unreachable email alerts** — Dashboard sends email (not just tray notification) when a monitored server goes offline or comes back online ([#529]) +- **Column filters on all FinOps DataGrids** — filter funnel icons on every column header across all 7 FinOps grids in Lite and Dashboard ([#562]) +- **Column filters on Dashboard** IdleDatabases, TempDB, and Index Analysis grids +- **Lite data import** — "Import Data" button brings in monitoring history from a previous Lite install via parquet files, preserving trend data across version upgrades ([#566]) +- **Per-server Utility Database setting** — Lite can call community stored procedures (sp_IndexCleanup) from a database other than master ([#555]) +- **SQL Server version check** in both CLI and GUI installers — rejects 2014 and earlier with a clear message ([#543]) +- **Execution plan analysis MCP tools** for both Dashboard and Lite +- **Full MCP tool coverage** — Dashboard expanded from 28 to 57 tools, Lite from 32 to 51 tools ([#576], [#577]) +- **Self-sufficient analyze_server drill-down** — MCP tool returns complete analysis, not breadcrumb trail ([#578]) +- **NuGet package dependency licenses** in THIRD_PARTY_NOTICES.md + +### Changed + +- **Azure SQL DB FinOps** — all collectors (database sizes, query stats, file I/O) now connect to each database individually instead of only querying master. Server Inventory uses dynamic SQL to avoid `sys.master_files` dependency. ([#557]) +- **Index Analysis scroll fix** — both summary and detail grids now use proportional heights instead of Auto, so they scroll independently with large result sets ([#554]) +- **Dashboard Add Server dialog** — increased MaxHeight from 700 to 850px so buttons are visible when SQL auth fields are shown +- **GUI installer** — Uninstall button now correctly enables after a successful install +- **GUI installer** — fixed encryption mapping and history logging ([#612]) +- **Dashboard visible sub-tab only refresh** on auto-refresh ticks ([#528]) +- Analysis engine decouples data maturity check from analysis window + +### Fixed + +- **Installer dropping database on every upgrade** — `00_uninstall.sql` excluded from install file list, installer aborts on upgrade failure, version detection fallback returns "1.0.0" instead of null ([#538], [#539]) +- **SQL dumps on mirroring passive servers** from FinOps collectors ([#535]) +- **RetrievedFromCache** always showing False ([#536]) +- **Arithmetic overflow** in query_stats collector for dop/thread columns ([#547]) +- **Lite perfmon chart bugs** and Dashboard ScottPlot crash handling ([#544], [#545]) +- **PLE=0 scoring bug** — was scored as harmless, now correctly flagged ([#543]) +- **PercentRank >1.0** bug in HealthCalculator +- **6 verified Lite bugs** from code review ([#611]) +- **Enterprise feature audit text** — partitioning is not Enterprise-only +- **FinOps collector scheduling**, server switch, and utilization bugs +- **Dashboard drill-down** Unicode arrow in story path split +- **Empty DataGrid scrollbar artifacts** — hide grids when empty across all FinOps tabs +- **Query preview** — truncated in row, full text in tooltip + +[#529]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/529 +[#535]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/535 +[#536]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/536 +[#538]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/538 +[#539]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/539 +[#543]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/543 +[#544]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/544 +[#545]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/545 +[#547]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/547 +[#554]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/554 +[#555]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/555 +[#557]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/557 +[#562]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/562 +[#564]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/564 +[#566]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/566 +[#576]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/576 +[#577]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/577 +[#578]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/578 +[#528]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/528 +[#589]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/589 +[#590]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/590 +[#593]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/593 +[#594]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/594 +[#604]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/604 +[#611]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/611 +[#612]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/612 + +## [2.2.0] - 2026-03-11 + +**Contributors:** [@HannahVernon](https://github.com/HannahVernon), [@ClaudioESSilva](https://github.com/ClaudioESSilva), [@dphugo](https://github.com/dphugo), [@Orestes](https://github.com/Orestes) — thank you! + +### Important + +- **Schema upgrade**: Three large collection tables (`query_stats`, `procedure_stats`, `query_store_data`) are migrated to use `COMPRESS()` for query text and plan columns. The upgrade performs a table swap (create new → migrate data → rename) which may take several minutes on large tables. A `row_hash` column is added for deduplication. Three new tracking tables are also created. Volume stats columns are added to `database_size_stats`. Upgrade scripts run automatically via the CLI/GUI installer and use idempotent checks. + + Compression results measured on a production instance: + + | Table | Compressed | Uncompressed | Ratio | + |---|---|---|---| + | query_stats | 18.0 MB | 339.0 MB | 18.8x | + | query_store_data | 13.5 MB | 258.0 MB | 19.1x | + | **Total** | **31.5 MB** | **597 MB** | **~19x** | + +### Added + +- **FinOps monitoring tab** — database size tracking, server properties, storage growth analysis (7d/30d), index analysis with unused/duplicate/compressible detection, utilization efficiency, idle database identification, and estate-level resource views ([#474]) +- **Named collection presets** — Aggressive, Balanced, and Low-Impact schedule profiles via `config.apply_collection_preset` ([#454]) +- **Entra ID interactive MFA authentication** in both CLI and GUI installers for Azure SQL MI connections ([#481]) +- **MCP port validation** — TCP port conflict detection, range validation (1024+), Auto port button, and auto-restart on settings change ([#453]) +- **Alert database exclusion filters** — filter blocking and deadlock alerts by database in both Dashboard and Lite ([#410], [#412]) +- **Configurable alert cooldown periods** for tray notifications and email alerts +- **Wait stats query drill-down** — click a wait type to see the queries causing it ([#372]) +- **Configurable long-running query settings** — max results, WAITFOR/backup/diagnostics exclusions ([#415]) +- **Uninstall option** in both CLI and GUI installers ([#431]) +- **Session stats collector** for active session tracking ([#474]) +- **LOB compression and deduplication** for query stats tables to reduce storage ([#419]) +- **Volume-level drive space** enrichment in database size stats via `dm_os_volume_stats` +- **GUI installer installation history** logging to `config.installation_history` ([#414]) +- **ReadOnlyIntent connection option** — Lite connections can set `ApplicationIntent=ReadOnly` for automatic read routing to Always On AG readable secondaries ([#515]) +- **Alert muting** — mute individual alerts or create pattern-based mute rules by server, metric, database, or application. Manage Mute Rules window with enable/disable toggle. Alert history detail view with double-click drill-down and context-sensitive detail text. Poison wait type documentation links. ([#512]) +- **SignPath code signing** — all release binaries (Dashboard, Lite, Installers) are digitally signed, eliminating Windows SmartScreen warnings ([#511]) +- CI version bump check on PRs to main +- Permissions section in README with least-privilege setup ([#421]) + +### Changed + +- **Utilization tab redesigned** — ported to Dashboard with aligned metrics between apps ([#478]) +- PlanAnalyzer rules synced from PerformanceStudio — Rule 5 message format, seek predicate parsing, spool labels, unmatched index detail ([#416], [#475], [#480]) +- Data retention now purges processed XE staging rows +- GeneratedRegex conversion for compile-time regex patterns ([#346], [#420]) +- Server health card width increased from 260 to 300 for less text truncation ([#489]) +- User's locale used for date/time formatting in WPF bindings ([#459]) +- XML processing instructions stripped from sql_command/sql_text display +- Parameterized queries in blocking/deadlock alert filtering +- **DuckDB 1.5.0 upgrade** — non-blocking checkpointing eliminates read stalls during WAL flushes, free block reuse stabilizes database file size without archive-and-reset cycles ([#516]) +- **Automatic parquet compaction** — archive files are merged into monthly files after each archive cycle, reducing file count from 2,600+ to ~75 and eliminating per-file metadata overhead on glob scans ([#516]) + + Combined with the UI responsiveness overhaul (#510), Lite's refresh cycle improved 13-26x: + + | Metric | Before | After | + |---|---|---| + | Lite `RefreshAllDataAsync` | 6-13s | < 500ms | + | Parquet files scanned per query | 233 | 19 | + | Archive-and-reset frequency | 21/day | ~0 | + | `v_wait_stats` query time | 1,700ms | 27ms | + +- **Monthly archive retention** — switched from 90-day file-age deletion to 3-month calendar-month rolling window, aligned with compacted monthly filenames ([#516]) +- **Lite status bar** shows used data size vs file size (e.g., "Database: 175.5 / 423.8 MB") via DuckDB `pragma_database_size()` ([#517]) +- **Query Store collector diagnostics** — reader/append/flush timing breakdown logged when collection exceeds 2 seconds, for identifying SQL Server DMV contention under heavy workloads ([#518]) +- SSMS-parity edge tooltips on plan viewer operator connections and ManyToMany indicator always shown for merge join operators ([#504]) +- **Lite UI responsiveness overhaul** — visible-tab-only refresh, sub-tab awareness, Query Store collector optimization (NULL plan XML + LOOP JOIN hint), and DuckDB write reduction ([#510]) + + Timer tick improvements measured under TPC-C load on SQL2022: + + | Scenario | Before | After | Improvement | + |---|---|---|---| + | Lite idle | 6-13s | 546-750ms | ~90% | + | Lite under TPC-C | 6-13s | ~3s | ~70% | + | Dashboard idle | 5.6s | 0.6-0.8s | 86% | + | Dashboard under TPC-C | 5.6s | 1.8-2.0s | 64% | + + Query Store collector specifically: + + | Metric | Before | After | + |---|---|---| + | query_store collector total | 6-18s | ~600ms | + | query_store SQL time | 374-1,104ms | ~300ms (LOOP JOIN hint) | + | query_store DuckDB write | 6-16s | ~75-230ms (NULL plan XML) | + +### Fixed + +- **UI hang** when opening Dashboard tab for offline server — replaced synchronous `.GetAwaiter().GetResult()` with proper `await` ([#477]) +- **First-collection spike** skewing PerfMon, wait stats, file I/O, memory grant, query stats, and procedure stats charts — first cumulative value now treated as baseline ([#482]) +- **Wait type filter TextBox** too small to read ([#488]) +- **Poison wait false positives** and alert log parsing ([#445], [#448]) +- **RID Lookup** analyzer rule matching new PhysicalOp label ([#429]) +- **procedure_stats** plan query using DECOMPRESS after compression migration +- **database_size_stats** InvalidCastException on compatibility_level +- **Deadlock filter** using wrong column reference in `GetFilteredDeadlockCountAsync` +- **RESTORING database** filter added to waiting_tasks collector ([#430]) +- Custom TrayToolTip crash — replaced with plain ToolTipText ([#422]) +- **Lite tab switch freeze** — added `_isRefreshing` guard to prevent tab switch handler from competing with timer ticks for DuckDB connection, eliminating "not responding" hangs ([#510]) +- DuckDB read lock acquisition resilience +- Formatted duration columns sorting alphabetically instead of numerically +- Settings window staying open on validation errors +- Deserialization clamping and validation abort issues +- **sp_IndexCleanup** summary grid column mapping off-by-one, expanded both grids to show all columns from both result sets ([#503]) +- **Rule 22 table variable** false positive on modification operators — INSERT/UPDATE/DELETE on table variables is expected ([#513]) +- **ComboBox focus steal** in plan viewer stealing keyboard focus from other controls ([#508]) +- **DOP 2 skew** false positive — parallel skew rule no longer fires at DOP 2 ([#508]) +- **ReadOnlyIntent connections** sharing server_id in DuckDB when the same server was added with and without ReadOnlyIntent ([#521]) + +[2.2.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v2.1.0...v2.2.0 + +## [2.1.0] - 2026-03-04 + +### Important + +- **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. + +### Added + +- **Light theme and "Cool Breeze" theme** — full light mode support for both Dashboard and Lite with live preview in settings ([#347]) +- **Standalone Plan Viewer** — open, paste (Ctrl+V), or drag & drop `.sqlplan` files independent of any server connection, with tabbed multi-plan support ([#359]) +- **Time display mode toggle** — show timestamps in Server Time, Local Time, or UTC with timezone labels across all grids and tooltips ([#17]) +- **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]) +- **Wait stats banner** in plan viewer showing top waits for the query ([#373]) +- **UDF runtime details** — CPU and elapsed time shown in Runtime Summary pane when UDFs are present ([#382]) +- **Sortable statement grid** and canvas panning in plan viewer ([#331]) +- **Comma-separated column filters** — enter multiple values separated by commas in text filters ([#348]) +- **Optional query text and plan collection** — per-collector flags in `config.collection_schedule` to disable query text or plan capture ([#337]) +- **`--preserve-jobs` installer flag** — keep existing SQL Agent job schedules during upgrade ([#326]) +- **Copy Query Text** context menu on Dashboard statements grid ([#367]) +- **Server list sorting** by display name in both Dashboard and Lite ([#30]) +- **Warning status icon** in server health indicators ([#355]) +- Reserved threads and 10 missing ShowPlan XML attributes in plan viewer ([#378]) +- Nightly build workflow for CI ([#332]) + +### Changed + +- PlanAnalyzer warning messages rewritten to be actionable with expert-guided per-rule advice ([#370], [#371]) +- 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]) +- First-run collector lookback reduced from 3-7 days to 1 hour for faster initial data ([#335]) +- Plan canvas aligns top-left and resets scroll on statement switch ([#366]) +- Plan viewer polish: index suggestions, property panel improvements, muted brush audit ([#365]) +- Add Server dialog visual parity between Dashboard and Lite with theme-driven PasswordBox styling ([#289]) + +### Fixed + +- **OverflowException** on wait stats page with large decimal values — SQL Server `decimal(38,24)` exceeding .NET precision ([#395]) +- **SQL dumps** on mirroring passive servers with RESTORING databases ([#384]) +- **UI hang** when adding first server to Dashboard ([#387]) +- **UTC/local timezone mismatch** in blocked process XML processor ([#383]) +- **AG secondary filter** skipping all inaccessible databases in cross-database collectors ([#325]) +- DuckDB column aliases in long-running queries ([#391]) +- sp_server_diagnostics and WAITFOR excluded from long-running query alerts ([#362]) +- UDF timing units corrected: microseconds to milliseconds ([#338]) +- DuckDB migration ordering after archive-and-reset ([#314]) +- Int16 cast error in long-running query alerts ([#313]) +- Missing dark mode on 19 SystemEventsContent charts ([#321]) +- Missing tooltips on charts after theme changes ([#319]) +- Operator time per-thread calculation synced across all plan viewers ([#392]) +- Theme StaticResource/DynamicResource binding fix for runtime theme switching +- Memory grant MB display, missing index quality scoring, wildcard LIKE detection ([#393]) +- **Installer validation** reporting historical collection errors as current failures — now filters to current run only ([#400]) +- **query_snapshots schema mismatch** after sp_WhoIsActive upgrade — collector auto-recreates daily table when column order changes ([#401]) +- **Missing upgrade script** for `default_trace_events` columns (`duration_us`, `end_time`) on 2.0.0→2.1.0 upgrade path ([#400]) + +## [2.0.0] - 2026-02-25 + +### Important + +- **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. + +### Added + +- **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]) +- **Actual execution plan support** — execute queries with SET STATISTICS XML ON to capture actual plans, with loading indicator and confirmation dialog ([#233]) +- **PlanAnalyzer** — automated plan analysis with rules for missing indexes, eager spools, key lookups, implicit conversions, memory grants, and more +- **Current Active Queries live snapshot** — real-time view of running queries with estimated/live plan download ([#149]) +- **Memory clerks tab** in Lite with picker-driven chart ([#145]) +- **Current Waits charts** in Blocking tab for both Dashboard and Lite ([#280]) +- **File I/O throughput charts** — read/write throughput trends, file-level latency breakdown, queued I/O overlay ([#281]) +- **Memory grant stats charts** — standardized collection with delta framework integration and trend visualization ([#281]) +- **CPU scheduler pressure status** — real-time scheduler, worker, runnable task counts with color-coded pressure level below CPU chart +- **Collection log drill-down** and daily summary in Lite ([#138]) +- **Collector duration trends chart** in Dashboard Collection Health ([#138]) +- **Themed perfmon counter packs** — 14 new counters with organized themed groups ([#255]) +- **User-configurable connection timeout** setting ([#236]) +- **Per-collector retention** — uses per-collector retention from `config.collection_schedule` in data retention ([#237]) +- **Query identifiers** in drill-down windows — query hash, plan hash, SQL handle visible for identification ([#268]) +- **Trace pattern drill-down** with missing columns and query text tooltips ([#273]) +- **Query Store Regressions drill-down** with TVF rewrite for performance ([#274]) +- **CLI `--help` flag** for installer ([#111]) +- Sort arrows, right-aligned numerics, and initial sort indicators across all grids ([#110]) +- Copyable plan viewer properties ([#269]) +- Standardized chart save/export filenames between Dashboard and Lite ([#284]) +- Full Dashboard column parity for query_stats, procedure_stats, and query_store_stats +- Min/max extremes surfaced in both apps — physical reads, rows, grant KB, spills, CLR time, log bytes ([#281]) + +### Changed + +- Query Store detection uses `sys.database_query_store_options` instead of `sys.databases.is_query_store_on` for Azure SQL DB compatibility ([#287]) +- Config tab consolidation, DB drop on server remove, DuckDB-first plan lookups, procedure stats parity +- Collector health status now detects consecutive recent failures — 5+ consecutive errors = FAILING, 3+ = WARNING +- Plan buttons now show a MessageBox when no plan is available instead of silently doing nothing +- CSV export uses locale-appropriate separators for non-US locales ([#240]) +- Query Store Regressions and Query Trace Patterns migrated to popup grid filtering ([#260]) +- NuGet packages updated; xUnit v3 migration + +### Fixed + +- **DuckDB file corruption** during maintenance — ReaderWriterLockSlim coordination, archive-all-and-reset at 512MB replaces compaction ([#218]) +- Archive view column mismatch, wait_stats thread-safety, and percent_complete type cast ([#234]) +- Collector health status bar text color ([#234]) +- View Plan for Query Store and Query Store Regressions tabs ([#261]) +- Query Store drill-down time filter alignment with main view ([#263]) +- Execution count mismatches between main views and drill-downs +- Drill-down chart UX — sparse data markers, hover tooltips, window sizing ([#271]) +- Truncated status text in Add Server dialog ([#257]) +- Scrollbar visibility, self-filtering artifacts, missing columns, and context menus ([#245], [#246], [#247], [#248]) +- query_stats and procedure_stats collectors ignoring recent queries +- Blank tooltips on warning and parallel badge icons +- Missing chart context menu on File I/O Throughput charts in Lite + +### Removed + +- `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]) + +## [1.3.0] - 2026-02-20 + +### Important + +- **Schema upgrade**: The `collect.memory_stats` table gains two new columns (`total_physical_memory_mb`, `committed_target_memory_mb`). The upgrade script runs automatically via the CLI/GUI installer and uses `IF NOT EXISTS` checks, so it is safe to re-run. On servers with very large `memory_stats` tables this ALTER may take a moment. + +### Added + +- Physical Memory, SQL Server Memory, and Target Memory columns in Memory Overview ([#140]) +- Current Configuration view (Server Config, Database Config, Trace Flags) in Dashboard Overview ([#143]) +- Popup column filters and right-click context menus in all drill-down history windows ([#206]) +- Consistent popup column filters across all Dashboard grids — replaced remaining TextBox-in-header filters and added filters to Trace Flags ([#200]) +- 7-day time filter option in drill-down queries ([#165]) +- Alert badge/count on sidebar Alerts button ([#109]) +- Missing poison wait defaults in wait stats picker ([#188]) + +### Changed + +- Default Trace tabs moved from Resource Metrics to Overview section ([#169]) +- Trends tab shown first in Locking section ([#171]) +- Wait stats cap raised from 20 to 30 (Dashboard) / 50 (Lite) so poison waits are never dropped ([#139]) +- Settings time range dropdown now matches dashboard button options ([#210]) +- "Total Executions" label in drill-down summaries renamed to clarify meaning ([#194]) +- WAITFOR sessions excluded from long-running query alerts ([#151]) + +### Fixed + +- Deadlock XML processor timezone mismatch — sp_BlitzLock returning 0 results because UTC dates were passed instead of local time +- Sidebar alert badge not updating when alerts dismissed from server sub-tabs ([#214]) +- Sidebar alert badge not clearing on acknowledge ([#186]) +- NOC deadlock/blocking showing "just now" for stale events instead of actual timestamp ([#187]) +- NOC deadlock severity using extended events timestamp ([#170]) +- Newly added servers not appearing on Overview until app restart ([#199]) +- Double-click on column header incorrectly triggering row drill-down ([#195]) +- Squished drill-down charts — now use proportional sizing ([#166]) +- Unreliable chart tooltips — now use X-axis proximity matching ([#167]) +- Query Trace Patterns showing empty despite data existing ([#168]) +- Drill-down windows: removed inline plan XML, added time range filtering, aggregated by collection_time ([#189]) +- Row clipping in Default Trace and Current Configuration grids ([#183], [#184]) +- Numeric filter negative range parsing ([#113]) +- MCP shutdown deadlock risk ([#112]) +- Lite DBNull cast error in database_config collector on SQL 2016 Express ([#192]) +- DuckDB concurrent file access IO errors ([#164]) + +## [1.2.0] - 2026-02-15 + +### Added + +- Alert types, alerts history view, column filtering, and dismiss/hide for alerts ([#52], [#56]) +- Average ms per wait chart toggle in both apps ([#22]) +- Collection Health tab in Lite UI ([#39]) +- Collector performance diagnostics in Lite UI ([#40]) +- Hover tooltips on all Dashboard charts ([#70]) +- Minimize-to-tray setting added to Lite ([#53]) +- Persist dismissed alerts across app restarts ([#44]) +- Locale-aware date/time formatting throughout UI ([#41]) +- 24-hour format in time range picker ([#41]) +- CI pipelines for build validation, SQL install testing, and DuckDB schema tests +- Expanded Lite database config collector to 28 sys.databases columns ([#142]) +- Parquet archive visibility and scheduled DuckDB database compaction ([#160], [#161]) +- DuckDB checkpoint optimization and collection timing accuracy +- Installer `--reset-schedule` flag to reset collection schedule on re-install + +### Fixed + +- Deadlock charts not populating data ([#73]) +- Chart X-axis double-converting custom range to server time ([#49]) +- query_cost overflow in memory grant collector ([#47]) +- XE ring buffer query timeouts on large buffers ([#37]) +- Dashboard sub-tab badge state and DuckDB migration for dismissed column +- Lite duplicate blocking/deadlock events from missing WHERE clause ([#61]) +- Procedure_stats_collector truncation on DDL triggers ([#69]) +- DataGrid row height increased from 25 to 28 to fix text clipping +- Skip offline servers during Lite collection and reduce connection timeout ([#90]) +- Mutex crash on Lite app exit ([#89]) +- Permission denied errors handled gracefully in collector health ([#150]) + +## [1.1.0] - 2026-02-13 + +### Added + +- Hover tooltips on all multi-series charts — Wait Stats, Sessions, Latch Stats, Spinlock Stats, File I/O, Perfmon, TempDB ([#21]) +- Microsoft Entra MFA authentication for Azure SQL DB connections in Lite ([#20]) +- Column-level filtering on all 11 Lite DataGrids ([#18]) +- Chart visual parity — Material Design 300 color palette, data point markers, consistent grid styling ([#16]) +- Smart Select All for wait types + expand from 12 to 20 wait types ([#12]) +- Trend chart legends always visible in Dashboard ([#11]) +- Per-server collector health in Lite status bar ([#5]) +- Server Online/Offline status in Lite overview ([#2]) +- Check for updates feature in both apps ([#1]) +- High DPI support for both Dashboard and Lite + +### Fixed + +- Query text off-by-one truncation ([#25]) +- Blocking/deadlock XML processors truncating parsed data every run ([#23]) +- WAITFOR queries appearing in top queries views ([#4]) +- Wait type Clear All not refreshing search filter in Dashboard + +## [1.0.0] - 2026-02-11 + +### Added + +- Full Edition: Dashboard + CLI/GUI Installer with 30+ automated SQL Agent collectors +- Lite Edition: Agentless monitoring with local DuckDB storage +- Support for SQL Server 2016-2025, Azure SQL DB, Azure SQL MI, AWS RDS +- Real-time charts and trend analysis for wait stats, CPU, memory, query performance, index usage, file I/O, blocking, deadlocks +- Email alerts for blocking, deadlocks, and high CPU +- MCP server integration for AI-assisted analysis +- System tray operation with background collection and alert notifications +- Data retention with configurable automatic cleanup +- Delta normalization for per-second rate calculations +- Dark theme UI + +[2.1.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v2.0.0...v2.1.0 +[2.0.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.3.0...v2.0.0 +[1.3.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/erikdarlingdata/PerformanceMonitor/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/erikdarlingdata/PerformanceMonitor/releases/tag/v1.0.0 +[#1]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/1 +[#2]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/2 +[#4]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/4 +[#5]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/5 +[#11]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/11 +[#12]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/12 +[#16]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/16 +[#18]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/18 +[#20]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/20 +[#21]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/21 +[#22]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/22 +[#23]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/23 +[#25]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/25 +[#37]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/37 +[#39]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/39 +[#40]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/40 +[#41]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/41 +[#44]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/44 +[#47]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/47 +[#49]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/49 +[#52]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/52 +[#53]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/53 +[#56]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/56 +[#61]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/61 +[#69]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/69 +[#70]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/70 +[#73]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/73 +[#85]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/85 +[#86]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/86 +[#89]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/89 +[#90]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/90 +[#109]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/109 +[#112]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/112 +[#113]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/113 +[#139]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/139 +[#140]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/140 +[#142]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/142 +[#143]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/143 +[#150]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/150 +[#151]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/151 +[#160]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/160 +[#161]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/161 +[#164]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/164 +[#165]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/165 +[#166]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/166 +[#167]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/167 +[#168]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/168 +[#169]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/169 +[#170]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/170 +[#171]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/171 +[#183]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/183 +[#184]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/184 +[#186]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/186 +[#187]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/187 +[#188]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/188 +[#189]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/189 +[#192]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/192 +[#194]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/194 +[#195]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/195 +[#199]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/199 +[#200]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/200 +[#206]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/206 +[#210]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/210 +[#214]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/214 +[#218]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/218 +[#220]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/220 +[#233]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/233 +[#234]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/234 +[#236]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/236 +[#237]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/237 +[#240]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/240 +[#245]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/245 +[#246]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/246 +[#247]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/247 +[#248]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/248 +[#255]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/255 +[#257]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/257 +[#260]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/260 +[#261]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/261 +[#263]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/263 +[#268]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/268 +[#269]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/269 +[#271]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/271 +[#273]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/273 +[#274]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/274 +[#280]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/280 +[#281]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/281 +[#284]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/284 +[#287]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/287 +[#313]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/313 +[#314]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/314 +[#17]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/17 +[#30]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/30 +[#319]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/319 +[#321]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/321 +[#325]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/325 +[#326]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/326 +[#327]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/327 +[#331]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/331 +[#332]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/332 +[#335]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/335 +[#337]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/337 +[#338]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/338 +[#341]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/341 +[#342]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/342 +[#343]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/343 +[#347]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/347 +[#348]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/348 +[#349]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/349 +[#355]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/355 +[#356]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/356 +[#358]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/358 +[#359]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/359 +[#362]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/362 +[#365]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/365 +[#366]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/366 +[#367]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/367 +[#370]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/370 +[#371]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/371 +[#373]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/373 +[#378]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/378 +[#379]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/379 +[#382]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/382 +[#383]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/383 +[#384]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/384 +[#387]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/387 +[#391]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/391 +[#392]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/392 +[#393]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/393 +[#289]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/289 +[#395]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/395 +[#400]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/400 +[#401]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/401 +[#410]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/410 +[#412]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/412 +[#414]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/414 +[#415]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/415 +[#416]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/416 +[#419]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/419 +[#420]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/420 +[#421]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/421 +[#422]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/422 +[#429]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/429 +[#430]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/430 +[#431]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/431 +[#445]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/445 +[#448]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/448 +[#453]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/453 +[#454]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/454 +[#459]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/459 +[#474]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/474 +[#475]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/475 +[#477]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/477 +[#478]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/478 +[#480]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/480 +[#481]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/481 +[#482]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/482 +[#488]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/488 +[#489]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/489 +[#503]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/503 +[#504]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/504 +[#508]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/508 +[#510]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/510 +[#512]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/512 +[#511]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/511 +[#513]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/513 +[#515]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/515 +[#516]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/516 +[#517]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/517 +[#518]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/518 +[#521]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/521 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index fdee23e3..0be3d4f7 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,44 +1,44 @@ -# Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment: - -* Being respectful of differing opinions and experiences -* Giving and gracefully accepting constructive feedback -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior: - -* Trolling, insulting or derogatory comments, and personal attacks -* Public or private harassment -* Publishing others' private information without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the project maintainer at **erik@erikdarling.com**. - -All complaints will be reviewed and investigated and will result in a response -that is deemed necessary and appropriate to the circumstances. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), -version 2.1, available at -https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. +# Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +* Being respectful of differing opinions and experiences +* Giving and gracefully accepting constructive feedback +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior: + +* Trolling, insulting or derogatory comments, and personal attacks +* Public or private harassment +* Publishing others' private information without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the project maintainer at **erik@erikdarling.com**. + +All complaints will be reviewed and investigated and will result in a response +that is deemed necessary and appropriate to the circumstances. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), +version 2.1, available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. diff --git a/Dashboard/AboutWindow.xaml b/Dashboard/AboutWindow.xaml index 6d3d0134..3d729894 100644 --- a/Dashboard/AboutWindow.xaml +++ b/Dashboard/AboutWindow.xaml @@ -1,60 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - GitHub Repository - - - - - Report an Issue - - - -