From 1a28dabd75832923df0f400e7a8fd35f8eace731 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:41:56 -0400 Subject: [PATCH] Dashboard: visible sub-tab only refresh on auto-refresh ticks Auto-refresh timer was firing all sub-tab queries (~32 SQL queries) every 30 seconds regardless of which sub-tab was visible. Now only the active sub-tab refreshes on timer ticks (1-2 queries). Full refresh preserved for initial load, manual refresh, and Apply-to-All time range changes. Controls updated: ResourceMetricsContent (8 sub-tabs), QueryPerformanceContent (8 sub-tabs), SystemEventsContent (9 sub-tabs), MemoryContent (5 sub-tabs). Co-Authored-By: Claude Opus 4.6 --- Dashboard/Controls/MemoryContent.xaml | 2 +- Dashboard/Controls/MemoryContent.xaml.cs | 35 +++++-- .../Controls/QueryPerformanceContent.xaml | 2 +- .../Controls/QueryPerformanceContent.xaml.cs | 99 ++++++++++++++++--- .../Controls/ResourceMetricsContent.xaml | 2 +- .../Controls/ResourceMetricsContent.xaml.cs | 46 ++++++--- Dashboard/Controls/SystemEventsContent.xaml | 2 +- .../Controls/SystemEventsContent.xaml.cs | 46 ++++++--- Dashboard/ServerTab.xaml.cs | 24 ++--- 9 files changed, 189 insertions(+), 69 deletions(-) diff --git a/Dashboard/Controls/MemoryContent.xaml b/Dashboard/Controls/MemoryContent.xaml index 25055835..36db005e 100644 --- a/Dashboard/Controls/MemoryContent.xaml +++ b/Dashboard/Controls/MemoryContent.xaml @@ -32,7 +32,7 @@ - + diff --git a/Dashboard/Controls/MemoryContent.xaml.cs b/Dashboard/Controls/MemoryContent.xaml.cs index df94f674..91459a08 100644 --- a/Dashboard/Controls/MemoryContent.xaml.cs +++ b/Dashboard/Controls/MemoryContent.xaml.cs @@ -172,22 +172,37 @@ public void SetTimeRange(int hoursBack, DateTime? fromDate = null, DateTime? toD } /// - /// Refreshes all memory data. Can be called from parent control. + /// Refreshes memory data. When fullRefresh is false, only the visible sub-tab is refreshed. /// - public async Task RefreshAllDataAsync() + public async Task RefreshAllDataAsync(bool fullRefresh = true) { try { using var _ = Helpers.MethodProfiler.StartTiming("Memory"); - // Run all independent refreshes in parallel for better performance - await Task.WhenAll( - RefreshMemoryStatsAsync(), - RefreshMemoryGrantsAsync(), - RefreshMemoryClerksAsync(), - RefreshPlanCacheAsync(), - RefreshMemoryPressureEventsAsync() - ); + if (fullRefresh) + { + // Run all independent refreshes in parallel for initial load / manual refresh + await Task.WhenAll( + RefreshMemoryStatsAsync(), + RefreshMemoryGrantsAsync(), + RefreshMemoryClerksAsync(), + RefreshPlanCacheAsync(), + RefreshMemoryPressureEventsAsync() + ); + } + else + { + // Only refresh the visible sub-tab + switch (SubTabControl.SelectedIndex) + { + case 0: await RefreshMemoryStatsAsync(); break; + case 1: await RefreshMemoryGrantsAsync(); break; + case 2: await RefreshMemoryClerksAsync(); break; + case 3: await RefreshPlanCacheAsync(); break; + case 4: await RefreshMemoryPressureEventsAsync(); break; + } + } } catch (Exception ex) { diff --git a/Dashboard/Controls/QueryPerformanceContent.xaml b/Dashboard/Controls/QueryPerformanceContent.xaml index c7bb6c67..5236029c 100644 --- a/Dashboard/Controls/QueryPerformanceContent.xaml +++ b/Dashboard/Controls/QueryPerformanceContent.xaml @@ -46,7 +46,7 @@ - + diff --git a/Dashboard/Controls/QueryPerformanceContent.xaml.cs b/Dashboard/Controls/QueryPerformanceContent.xaml.cs index 041a71fd..48fe2a57 100644 --- a/Dashboard/Controls/QueryPerformanceContent.xaml.cs +++ b/Dashboard/Controls/QueryPerformanceContent.xaml.cs @@ -262,9 +262,9 @@ public void SetTimeRange(int hoursBack, DateTime? fromDate = null, DateTime? toD } /// - /// Refreshes all data for all sub-tabs. + /// Refreshes query performance data. When fullRefresh is false, only the visible sub-tab is refreshed. /// - public async Task RefreshAllDataAsync() + public async Task RefreshAllDataAsync(bool fullRefresh = true) { try { @@ -272,6 +272,25 @@ public async Task RefreshAllDataAsync() if (_databaseService == null) return; + if (!fullRefresh) + { + // Only refresh the visible sub-tab + switch (SubTabControl.SelectedIndex) + { + case 0: await RefreshPerformanceTrendsAsync(); break; + case 1: await RefreshActiveQueriesAsync(); break; + case 2: break; // Current Active Queries — manual refresh only + case 3: await RefreshQueryStatsGridAsync(); break; + case 4: await RefreshProcStatsGridAsync(); break; + case 5: await RefreshQueryStoreGridAsync(); break; + case 6: await RefreshQueryStoreRegressionsAsync(); break; + case 7: await RefreshLongRunningPatternsAsync(); break; + } + return; + } + + // Full refresh — all sub-tabs in parallel + // Only show loading overlay on initial load (no existing data) if (QueryStatsDataGrid.ItemsSource == null) { @@ -303,20 +322,9 @@ await Task.WhenAll( ); // Populate grids from summary data - var queryStats = await queryStatsTask; - QueryStatsDataGrid.ItemsSource = queryStats; - QueryStatsNoDataMessage.Visibility = queryStats.Count == 0 ? Visibility.Visible : Visibility.Collapsed; - SetInitialSort(QueryStatsDataGrid, "AvgCpuTimeMs", ListSortDirection.Descending); - - var procStats = await procStatsTask; - ProcStatsDataGrid.ItemsSource = procStats; - ProcStatsNoDataMessage.Visibility = procStats.Count == 0 ? Visibility.Visible : Visibility.Collapsed; - SetInitialSort(ProcStatsDataGrid, "AvgCpuTimeMs", ListSortDirection.Descending); - - var queryStore = await queryStoreTask; - QueryStoreDataGrid.ItemsSource = queryStore; - QueryStoreNoDataMessage.Visibility = queryStore.Count == 0 ? Visibility.Visible : Visibility.Collapsed; - SetInitialSort(QueryStoreDataGrid, "AvgCpuTimeMs", ListSortDirection.Descending); + PopulateQueryStatsGrid(await queryStatsTask); + PopulateProcStatsGrid(await procStatsTask); + PopulateQueryStoreGrid(await queryStoreTask); // Populate charts from time-series data LoadDurationChart(QueryPerfTrendsQueryChart, await queryDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[0], _queryDurationHover); @@ -334,6 +342,65 @@ await Task.WhenAll( } } + private async Task RefreshPerformanceTrendsAsync() + { + if (_databaseService == null) return; + + var queryDurationTrendsTask = _databaseService.GetQueryDurationTrendsAsync(_perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate); + var procDurationTrendsTask = _databaseService.GetProcedureDurationTrendsAsync(_perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate); + var qsDurationTrendsTask = _databaseService.GetQueryStoreDurationTrendsAsync(_perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate); + var execTrendsTask = _databaseService.GetExecutionTrendsAsync(_perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate); + + await Task.WhenAll(queryDurationTrendsTask, procDurationTrendsTask, qsDurationTrendsTask, execTrendsTask); + + LoadDurationChart(QueryPerfTrendsQueryChart, await queryDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[0], _queryDurationHover); + LoadDurationChart(QueryPerfTrendsProcChart, await procDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[1], _procDurationHover); + LoadDurationChart(QueryPerfTrendsQsChart, await qsDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[4], _qsDurationHover); + LoadExecChart(await execTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate); + } + + private async Task RefreshQueryStatsGridAsync() + { + if (_databaseService == null) return; + var data = await _databaseService.GetQueryStatsAsync(_queryStatsHoursBack, _queryStatsFromDate, _queryStatsToDate); + PopulateQueryStatsGrid(data); + } + + private async Task RefreshProcStatsGridAsync() + { + if (_databaseService == null) return; + var data = await _databaseService.GetProcedureStatsAsync(_procStatsHoursBack, _procStatsFromDate, _procStatsToDate); + PopulateProcStatsGrid(data); + } + + private async Task RefreshQueryStoreGridAsync() + { + if (_databaseService == null) return; + var data = await _databaseService.GetQueryStoreDataAsync(_queryStoreHoursBack, _queryStoreFromDate, _queryStoreToDate); + PopulateQueryStoreGrid(data); + } + + private void PopulateQueryStatsGrid(List data) + { + QueryStatsDataGrid.ItemsSource = data; + QueryStatsNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed; + SetInitialSort(QueryStatsDataGrid, "AvgCpuTimeMs", ListSortDirection.Descending); + } + + private void PopulateProcStatsGrid(List data) + { + ProcStatsDataGrid.ItemsSource = data; + ProcStatsNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed; + SetInitialSort(ProcStatsDataGrid, "AvgCpuTimeMs", ListSortDirection.Descending); + } + + private void PopulateQueryStoreGrid(List data) + { + QueryStoreDataGrid.ItemsSource = data; + QueryStoreNoDataMessage.Visibility = data.Count == 0 ? Visibility.Visible : Visibility.Collapsed; + SetInitialSort(QueryStoreDataGrid, "AvgCpuTimeMs", ListSortDirection.Descending); + } + private void SetStatus(string message) { _statusCallback?.Invoke(message); diff --git a/Dashboard/Controls/ResourceMetricsContent.xaml b/Dashboard/Controls/ResourceMetricsContent.xaml index 2829cb19..03142ca0 100644 --- a/Dashboard/Controls/ResourceMetricsContent.xaml +++ b/Dashboard/Controls/ResourceMetricsContent.xaml @@ -24,7 +24,7 @@ - + diff --git a/Dashboard/Controls/ResourceMetricsContent.xaml.cs b/Dashboard/Controls/ResourceMetricsContent.xaml.cs index 02d75981..03232760 100644 --- a/Dashboard/Controls/ResourceMetricsContent.xaml.cs +++ b/Dashboard/Controls/ResourceMetricsContent.xaml.cs @@ -251,27 +251,45 @@ public void SetTimeRange(int hoursBack, DateTime? fromDate = null, DateTime? toD } /// - /// Refreshes all resource metrics data. Can be called from parent control. + /// Refreshes resource metrics data. When fullRefresh is false, only the visible sub-tab is refreshed. /// - public async Task RefreshAllDataAsync() + public async Task RefreshAllDataAsync(bool fullRefresh = true) { using var _ = Helpers.MethodProfiler.StartTiming("ResourceMetrics"); if (_databaseService == null) return; try { - // Run all independent refreshes in parallel for better performance - await Task.WhenAll( - RefreshLatchStatsAsync(), - RefreshSpinlockStatsAsync(), - RefreshTempdbStatsAsync(), - RefreshSessionStatsAsync(), - LoadFileIoLatencyChartsAsync(), - LoadFileIoThroughputChartsAsync(), - RefreshServerTrendsAsync(), - RefreshPerfmonCountersTabAsync(), - RefreshWaitStatsDetailTabAsync() - ); + if (fullRefresh) + { + // Run all independent refreshes in parallel for initial load / manual refresh + await Task.WhenAll( + RefreshLatchStatsAsync(), + RefreshSpinlockStatsAsync(), + RefreshTempdbStatsAsync(), + RefreshSessionStatsAsync(), + LoadFileIoLatencyChartsAsync(), + LoadFileIoThroughputChartsAsync(), + RefreshServerTrendsAsync(), + RefreshPerfmonCountersTabAsync(), + RefreshWaitStatsDetailTabAsync() + ); + } + else + { + // Only refresh the visible sub-tab + switch (SubTabControl.SelectedIndex) + { + case 0: await RefreshServerTrendsAsync(); break; + case 1: await RefreshWaitStatsDetailTabAsync(); break; + case 2: await RefreshTempdbStatsAsync(); break; + case 3: await Task.WhenAll(LoadFileIoLatencyChartsAsync(), LoadFileIoThroughputChartsAsync()); break; + case 4: await RefreshPerfmonCountersTabAsync(); break; + case 5: await RefreshSessionStatsAsync(); break; + case 6: await RefreshLatchStatsAsync(); break; + case 7: await RefreshSpinlockStatsAsync(); break; + } + } } catch (Exception ex) { diff --git a/Dashboard/Controls/SystemEventsContent.xaml b/Dashboard/Controls/SystemEventsContent.xaml index edf288f8..3434838c 100644 --- a/Dashboard/Controls/SystemEventsContent.xaml +++ b/Dashboard/Controls/SystemEventsContent.xaml @@ -31,7 +31,7 @@ - + diff --git a/Dashboard/Controls/SystemEventsContent.xaml.cs b/Dashboard/Controls/SystemEventsContent.xaml.cs index b46c6d45..aa3e1c20 100644 --- a/Dashboard/Controls/SystemEventsContent.xaml.cs +++ b/Dashboard/Controls/SystemEventsContent.xaml.cs @@ -312,26 +312,46 @@ public void SetTimeRange(int hoursBack, DateTime? fromDate = null, DateTime? toD } /// - /// Refreshes all system events data. Can be called from parent control. + /// Refreshes system events data. When fullRefresh is false, only the visible sub-tab is refreshed. /// - public async Task RefreshAllDataAsync() + public async Task RefreshAllDataAsync(bool fullRefresh = true) { using var _ = Helpers.MethodProfiler.StartTiming("SystemEvents"); if (_databaseService == null) return; try { - // Run all independent refreshes in parallel for better performance - await Task.WhenAll( - RefreshSystemHealthAsync(), - RefreshSevereErrorsAsync(), - RefreshIOIssuesAsync(), - RefreshSchedulerIssuesAsync(), - RefreshMemoryConditionsAsync(), - RefreshCPUTasksAsync(), - RefreshMemoryBrokerAsync(), - RefreshMemoryNodeOOMAsync() - ); + if (fullRefresh) + { + // Run all independent refreshes in parallel for initial load / manual refresh + await Task.WhenAll( + RefreshSystemHealthAsync(), + RefreshSevereErrorsAsync(), + RefreshIOIssuesAsync(), + RefreshSchedulerIssuesAsync(), + RefreshMemoryConditionsAsync(), + RefreshCPUTasksAsync(), + RefreshMemoryBrokerAsync(), + RefreshMemoryNodeOOMAsync() + ); + } + else + { + // Only refresh the visible sub-tab + switch (SubTabControl.SelectedIndex) + { + case 0: // Corruption Events + case 1: // Contention Events — same data source + await RefreshSystemHealthAsync(); break; + case 2: await RefreshSevereErrorsAsync(); break; + case 3: await RefreshIOIssuesAsync(); break; + case 4: await RefreshSchedulerIssuesAsync(); break; + case 5: await RefreshMemoryConditionsAsync(); break; + case 6: await RefreshCPUTasksAsync(); break; + case 7: await RefreshMemoryBrokerAsync(); break; + case 8: await RefreshMemoryNodeOOMAsync(); break; + } + } } catch (Exception ex) { diff --git a/Dashboard/ServerTab.xaml.cs b/Dashboard/ServerTab.xaml.cs index 27eb14c8..f18d87cf 100644 --- a/Dashboard/ServerTab.xaml.cs +++ b/Dashboard/ServerTab.xaml.cs @@ -1174,19 +1174,19 @@ private async Task RefreshVisibleTabAsync() await RefreshOverviewTabAsync(); break; case "Queries": - await RefreshQueriesTabAsync(); + await RefreshQueriesTabAsync(fullRefresh: false); break; case "Resource Metrics": - await RefreshResourceMetricsTabAsync(); + await RefreshResourceMetricsTabAsync(fullRefresh: false); break; case "Memory": - await RefreshMemoryTabAsync(); + await RefreshMemoryTabAsync(fullRefresh: false); break; case "Locking": await RefreshLockingTabAsync(); break; case "System Events": - await RefreshSystemEventsTabAsync(); + await RefreshSystemEventsTabAsync(fullRefresh: false); break; // Plan Viewer has no data to refresh } @@ -1230,11 +1230,11 @@ await Task.WhenAll(healthTask, durationLogsTask, resourceOverviewTask, runningJo /// /// Refreshes the Queries tab (delegated to QueryPerformanceContent UserControl). /// - private async Task RefreshQueriesTabAsync() + private async Task RefreshQueriesTabAsync(bool fullRefresh = true) { try { - await PerformanceTab.RefreshAllDataAsync(); + await PerformanceTab.RefreshAllDataAsync(fullRefresh); } catch (Exception ex) { @@ -1245,11 +1245,11 @@ private async Task RefreshQueriesTabAsync() /// /// Refreshes the Resource Metrics tab (delegated to ResourceMetricsContent UserControl). /// - private async Task RefreshResourceMetricsTabAsync() + private async Task RefreshResourceMetricsTabAsync(bool fullRefresh = true) { try { - await ResourceMetricsContent.RefreshAllDataAsync(); + await ResourceMetricsContent.RefreshAllDataAsync(fullRefresh); } catch (Exception ex) { @@ -1260,11 +1260,11 @@ private async Task RefreshResourceMetricsTabAsync() /// /// Refreshes the Memory tab (delegated to MemoryContent UserControl). /// - private async Task RefreshMemoryTabAsync() + private async Task RefreshMemoryTabAsync(bool fullRefresh = true) { try { - await MemoryTab.RefreshAllDataAsync(); + await MemoryTab.RefreshAllDataAsync(fullRefresh); } catch (Exception ex) { @@ -1338,11 +1338,11 @@ private async Task RefreshLockingTabAsync() /// /// Refreshes the System Events tab (delegated to SystemEventsContent UserControl). /// - private async Task RefreshSystemEventsTabAsync() + private async Task RefreshSystemEventsTabAsync(bool fullRefresh = true) { try { - await SystemEventsContent.RefreshAllDataAsync(); + await SystemEventsContent.RefreshAllDataAsync(fullRefresh); } catch (Exception ex) {