Skip to content

Commit 266834c

Browse files
Reduce Performance tab to optional diagnostics (#17)
Reduce Performance tab to optional diagnostics Hide the Performance/Diagnostics surface from the primary UX by default and remove hidden startup/tray runtime cost. - Hide Diagnostics from primary navigation and tray by default - Lazy-create PerformanceViewModel only when Diagnostics is opened - Stop initializing diagnostics snapshots during app startup - Gate tray CPU/RAM metrics behind the advanced diagnostics flag - Avoid starting the tray lightweight metrics timer when diagnostics are hidden - Lazily allocate PerformanceMonitoringService counters on first metrics use - Stop live diagnostics when inactive, minimized or tray-hidden - Preserve diagnostics internals for future selected-process summary work - Add tests for lazy diagnostics creation, tray metrics gating and explicit diagnostics activation No Process tab redesign, CPU affinity changes, memory priority changes, persistent rules changes, version bump or tag changes.
1 parent cbe2402 commit 266834c

17 files changed

Lines changed: 751 additions & 164 deletions

MainWindow.Behaviors.partial.cs

Lines changed: 45 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ private void SetDataContexts()
4949
// Set DataContext for the association view
5050
this.AssociationView.DataContext = this.associationViewModel;
5151

52-
// Set DataContext for the performance view
53-
this.PerformanceViewControl.DataContext = this.performanceViewModel;
54-
5552
// Set DataContext for the log viewer view
5653
this.LogViewerViewControl.DataContext = this.logViewerViewModel;
5754

@@ -340,15 +337,7 @@ private async Task LoadViewModelsAsync()
340337
await powerPlanTask; // Ensure we get any exceptions
341338
this.LogDebug("PowerPlanViewModel loaded successfully");
342339

343-
this.LogDebug("About to initialize PerformanceViewModel...");
344-
var performanceTask = this.performanceViewModel.InitializeAsync();
345-
var performanceResult = await Task.WhenAny(performanceTask, Task.Delay(5000));
346-
if (performanceResult != performanceTask)
347-
{
348-
throw new TimeoutException("PerformanceViewModel.InitializeAsync() timed out after 5 seconds");
349-
}
350-
await performanceTask; // Ensure we get any exceptions
351-
this.LogDebug("PerformanceViewModel initialized successfully");
340+
this.LogDebug("Skipping optional diagnostics initialization until the diagnostics page is opened.");
352341

353342
this.LogDebug("About to load SystemTweaksViewModel...");
354343
var systemTweaksTask = this.systemTweaksViewModel.LoadCommand.ExecuteAsync(null);
@@ -1009,57 +998,7 @@ private async Task UpdateSystemTrayContextMenuAsync()
1009998
{
1010999
try
10111000
{
1012-
// Update power plans in system tray
1013-
var powerPlanService = this.serviceProvider.GetRequiredService<IPowerPlanService>();
1014-
var powerPlans = await powerPlanService.GetPowerPlansAsync();
1015-
var activePowerPlan = await powerPlanService.GetActivePowerPlan();
1016-
this.systemTrayService.UpdatePowerPlans(powerPlans, activePowerPlan);
1017-
1018-
// Update profiles in system tray
1019-
var profilesDirectory = StoragePaths.ProfilesDirectory;
1020-
var profileNames = new List<string>();
1021-
1022-
if (Directory.Exists(profilesDirectory))
1023-
{
1024-
profileNames = Directory.GetFiles(profilesDirectory, "*.json")
1025-
.Select(Path.GetFileNameWithoutExtension)
1026-
.Where(name => !string.IsNullOrWhiteSpace(name))
1027-
.ToList()!;
1028-
}
1029-
1030-
this.systemTrayService.UpdateProfiles(profileNames);
1031-
1032-
// Update system status (with timeout to prevent hanging)
1033-
try
1034-
{
1035-
var performanceService = this.serviceProvider.GetRequiredService<IPerformanceMonitoringService>();
1036-
var metricsTask = performanceService.GetSystemMetricsAsync(lightweight: true);
1037-
var metricsResult = await Task.WhenAny(metricsTask, Task.Delay(2000)); // 2 second timeout
1038-
1039-
if (metricsResult == metricsTask)
1040-
{
1041-
var currentMetrics = await metricsTask;
1042-
this.systemTrayService.UpdateSystemStatus(
1043-
activePowerPlan?.Name ?? "Unknown",
1044-
currentMetrics?.TotalCpuUsage ?? 0.0,
1045-
currentMetrics?.MemoryUsagePercentage ?? 0.0);
1046-
}
1047-
else
1048-
{
1049-
// Timeout - use default values
1050-
this.systemTrayService.UpdateSystemStatus(
1051-
activePowerPlan?.Name ?? "Unknown",
1052-
0.0, 0.0);
1053-
}
1054-
}
1055-
catch (Exception metricsEx)
1056-
{
1057-
System.Diagnostics.Debug.WriteLine($"Failed to get performance metrics for tray: {metricsEx.Message}");
1058-
// Use default values
1059-
this.systemTrayService.UpdateSystemStatus(
1060-
activePowerPlan?.Name ?? "Unknown",
1061-
0.0, 0.0);
1062-
}
1001+
await this.systemTrayStatusUpdater.UpdateContextMenuAsync(this.systemTrayService);
10631002
}
10641003
catch (Exception ex)
10651004
{
@@ -1073,6 +1012,12 @@ private void StartSystemTrayUpdateTimer()
10731012
{
10741013
this.systemTrayUpdateTimer?.Stop();
10751014
this.systemTrayUpdateTimer?.Dispose();
1015+
this.systemTrayUpdateTimer = null;
1016+
1017+
if (!this.systemTrayStatusUpdater.ShouldRunPerformanceStatusUpdates)
1018+
{
1019+
return;
1020+
}
10761021

10771022
this.systemTrayUpdateFailureStreak = 0;
10781023
this.systemTrayUpdateTimer = new System.Timers.Timer(SystemTrayUpdateBaseIntervalMs);
@@ -1150,21 +1095,9 @@ private async Task<bool> UpdateSystemTrayStatusAsync()
11501095
{
11511096
try
11521097
{
1153-
var powerPlanService = this.serviceProvider.GetRequiredService<IPowerPlanService>();
1154-
var performanceService = this.serviceProvider.GetRequiredService<IPerformanceMonitoringService>();
1155-
1156-
var activePowerPlan = await powerPlanService.GetActivePowerPlan();
1157-
var currentMetrics = await performanceService.GetSystemMetricsAsync(lightweight: true);
1158-
1159-
await this.Dispatcher.InvokeAsync(() =>
1160-
{
1161-
this.systemTrayService.UpdateSystemStatus(
1162-
activePowerPlan?.Name ?? "Unknown",
1163-
currentMetrics?.TotalCpuUsage ?? 0.0,
1164-
currentMetrics?.MemoryUsagePercentage ?? 0.0);
1165-
});
1166-
1167-
return true;
1098+
return await this.systemTrayStatusUpdater.UpdateStatusAsync(
1099+
this.systemTrayService,
1100+
action => this.Dispatcher.InvokeAsync(action).Task);
11681101
}
11691102
catch (Exception ex)
11701103
{
@@ -1343,6 +1276,12 @@ private void ResumeForegroundRefreshes()
13431276
this.isSystemTrayUpdatesSuspended = false;
13441277
this.systemTrayUpdateFailureStreak = 0;
13451278
this.systemTrayUpdateTimer?.Stop();
1279+
1280+
if (!this.systemTrayStatusUpdater.ShouldRunPerformanceStatusUpdates)
1281+
{
1282+
return;
1283+
}
1284+
13461285
if (this.systemTrayUpdateTimer != null)
13471286
{
13481287
this.systemTrayUpdateTimer.Interval = SystemTrayUpdateBaseIntervalMs;
@@ -1365,8 +1304,13 @@ private void ResumeForegroundRefreshes()
13651304

13661305
private AppActivityState GetForegroundActivityState()
13671306
{
1368-
return this.ProcessManagementTab.Visibility == Visibility.Visible
1369-
? AppActivityState.ForegroundProcessView
1307+
if (this.ProcessManagementTab.Visibility == Visibility.Visible)
1308+
{
1309+
return AppActivityState.ForegroundProcessView;
1310+
}
1311+
1312+
return this.PerformanceViewControl.Visibility == Visibility.Visible
1313+
? AppActivityState.ForegroundDiagnosticsView
13701314
: AppActivityState.ForegroundOtherTab;
13711315
}
13721316

@@ -1408,9 +1352,9 @@ private void ApplyAppRefreshPolicy(AppActivityState state)
14081352

14091353
if (decision.PerformanceUiMonitoringEnabled)
14101354
{
1411-
_ = this.performanceViewModel.ResumeBackgroundMonitoringAsync();
1355+
_ = this.GetPerformanceViewModel().ActivateDiagnosticsAsync();
14121356
}
1413-
else
1357+
else if (this.performanceViewModel != null)
14141358
{
14151359
_ = this.performanceViewModel.SuspendBackgroundMonitoringAsync();
14161360
}
@@ -1541,6 +1485,11 @@ private void SelectMainTab(string tag)
15411485
return;
15421486
}
15431487

1488+
if (string.Equals(tag, "Performance", StringComparison.Ordinal))
1489+
{
1490+
this.GetPerformanceViewModel();
1491+
}
1492+
15441493
this.ApplySectionVisibility(tag);
15451494

15461495
if (string.Equals(tag, "Performance", StringComparison.Ordinal))
@@ -1791,9 +1740,14 @@ private void ApplySectionVisibility(string tag)
17911740
return;
17921741
}
17931742

1794-
this.ApplyAppRefreshPolicy(string.Equals(tag, "Process", StringComparison.Ordinal)
1795-
? AppActivityState.ForegroundProcessView
1796-
: AppActivityState.ForegroundOtherTab);
1743+
var activityState = tag switch
1744+
{
1745+
"Process" => AppActivityState.ForegroundProcessView,
1746+
"Performance" => AppActivityState.ForegroundDiagnosticsView,
1747+
_ => AppActivityState.ForegroundOtherTab,
1748+
};
1749+
1750+
this.ApplyAppRefreshPolicy(activityState);
17971751
}
17981752

17991753
private void NavMenuItem_Click(object sender, RoutedEventArgs e)
@@ -1840,6 +1794,11 @@ private async Task NavMenuItem_ClickAsync(object sender, RoutedEventArgs e)
18401794
return;
18411795
}
18421796

1797+
if (string.Equals(tag, "Performance", StringComparison.Ordinal))
1798+
{
1799+
this.GetPerformanceViewModel();
1800+
}
1801+
18431802
this.ApplySectionVisibility(tag);
18441803

18451804
if (string.Equals(tag, "Performance", StringComparison.Ordinal))
@@ -1895,6 +1854,7 @@ protected override void OnClosed(EventArgs e)
18951854

18961855
this.initializationTimeoutTimer?.Stop();
18971856
this.initializationTimeoutTimer?.Dispose();
1857+
this.performanceViewModel?.Dispose();
18981858

18991859
this.selfResourceManagementService.RestoreForegroundMode();
19001860
this.navigationBehavior.Dispose();

MainWindow.xaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
<ui:SymbolIcon Symbol="TasksApp24"/>
139139
</ui:NavigationViewItem.Icon>
140140
</ui:NavigationViewItem>
141-
<ui:NavigationViewItem x:Name="NavPerf" Content="Performance" Tag="Performance" TargetPageTag="Performance" Click="NavMenuItem_Click">
141+
<ui:NavigationViewItem x:Name="NavPerf" Content="Diagnostics" Tag="Performance" TargetPageTag="Performance" Click="NavMenuItem_Click">
142142
<ui:NavigationViewItem.Icon>
143143
<ui:SymbolIcon Symbol="DataHistogram24"/>
144144
</ui:NavigationViewItem.Icon>
@@ -325,14 +325,14 @@
325325
</Border.Effect>
326326
<StackPanel>
327327
<TextBlock x:Name="PerformanceIntroTitle"
328-
Text="Performance Dashboard"
328+
Text="Optional Diagnostics"
329329
FontSize="24"
330330
FontWeight="SemiBold"
331331
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
332332
Margin="0,0,0,10"/>
333333

334334
<TextBlock x:Name="PerformanceIntroDescription"
335-
Text="This page gives you live CPU, memory and hotspot visibility so you can build precise optimization rules."
335+
Text="Diagnostics are optional and intended for troubleshooting. For in-game overlays and detailed performance graphs, use dedicated tools."
336336
TextWrapping="Wrap"
337337
Margin="0,0,0,10"
338338
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
@@ -344,21 +344,21 @@
344344
Margin="0,0,0,6"/>
345345

346346
<TextBlock x:Name="PerformanceIntroTip1"
347-
Text="1. Start live metrics to collect a current system snapshot."
347+
Text="1. Open diagnostics only when you need a focused troubleshooting snapshot."
348348
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
349349
Margin="0,0,0,4"/>
350350
<TextBlock x:Name="PerformanceIntroTip2"
351-
Text="2. Review top processes and pick recurring workloads."
351+
Text="2. Review hotspots only as a hint before creating automation rules."
352352
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
353353
Margin="0,0,0,4"/>
354354
<TextBlock x:Name="PerformanceIntroTip3"
355-
Text="3. Create rules from hotspots to automate power plan and affinity behavior."
355+
Text="3. Stop live metrics when you are done troubleshooting."
356356
TextWrapping="Wrap"
357357
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
358358
Margin="0,0,0,16"/>
359359

360360
<Button x:Name="PerformanceIntroContinueButton"
361-
Content="Continue to Performance"
361+
Content="Continue to Diagnostics"
362362
Click="PerformanceIntroContinue_Click"
363363
HorizontalAlignment="Right"
364364
Background="{DynamicResource AccentFillColorDefaultBrush}"

MainWindow.xaml.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
4242

4343
private readonly ProcessViewModel processViewModel;
4444
private readonly PowerPlanViewModel powerPlanViewModel;
45-
private readonly PerformanceViewModel performanceViewModel;
45+
private readonly IDiagnosticsViewModelProvider diagnosticsViewModelProvider;
4646
private readonly ProcessPowerPlanAssociationViewModel associationViewModel;
4747
private readonly LogViewerViewModel logViewerViewModel;
4848
private readonly ISystemTrayService systemTrayService;
49+
private readonly ISystemTrayStatusUpdater systemTrayStatusUpdater;
4950
private readonly IApplicationSettingsService settingsService;
5051
private readonly INotificationService notificationService;
5152
private readonly IProcessMonitorService processMonitorService;
@@ -59,6 +60,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
5960
private readonly IServiceProvider serviceProvider;
6061
private readonly IThemeService themeService;
6162
private System.Timers.Timer? systemTrayUpdateTimer;
63+
private PerformanceViewModel? performanceViewModel;
6264
private bool isSystemTrayUpdatesSuspended;
6365
private int isSystemTrayUpdateInProgress;
6466
private int systemTrayUpdateFailureStreak;
@@ -84,10 +86,11 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
8486
public MainWindow(
8587
ProcessViewModel processViewModel,
8688
PowerPlanViewModel powerPlanViewModel,
87-
PerformanceViewModel performanceViewModel,
89+
IDiagnosticsViewModelProvider diagnosticsViewModelProvider,
8890
ProcessPowerPlanAssociationViewModel associationViewModel,
8991
LogViewerViewModel logViewerViewModel,
9092
ISystemTrayService systemTrayService,
93+
ISystemTrayStatusUpdater systemTrayStatusUpdater,
9194
IApplicationSettingsService settingsService,
9295
INotificationService notificationService,
9396
IProcessMonitorService processMonitorService,
@@ -109,6 +112,7 @@ public MainWindow(
109112

110113
this.InitializeComponent();
111114
System.Diagnostics.Debug.WriteLine("InitializeComponent completed");
115+
this.ConfigureDiagnosticsNavigation();
112116

113117
// Initialize loading overlay
114118
this.InitializeLoadingOverlay();
@@ -117,10 +121,11 @@ public MainWindow(
117121

118122
this.processViewModel = processViewModel;
119123
this.powerPlanViewModel = powerPlanViewModel;
120-
this.performanceViewModel = performanceViewModel;
124+
this.diagnosticsViewModelProvider = diagnosticsViewModelProvider;
121125
this.associationViewModel = associationViewModel;
122126
this.logViewerViewModel = logViewerViewModel;
123127
this.systemTrayService = systemTrayService;
128+
this.systemTrayStatusUpdater = systemTrayStatusUpdater;
124129
this.settingsService = settingsService;
125130
this.notificationService = notificationService;
126131
this.processMonitorService = processMonitorService;
@@ -159,5 +164,24 @@ public MainWindow(
159164
throw;
160165
}
161166
}
167+
168+
private void ConfigureDiagnosticsNavigation()
169+
{
170+
this.NavPerf.Visibility = AppNavigationOptions.ShowAdvancedDiagnostics
171+
? Visibility.Visible
172+
: Visibility.Collapsed;
173+
}
174+
175+
private PerformanceViewModel GetPerformanceViewModel()
176+
{
177+
if (this.performanceViewModel != null)
178+
{
179+
return this.performanceViewModel;
180+
}
181+
182+
this.performanceViewModel = this.diagnosticsViewModelProvider.GetOrCreate();
183+
this.PerformanceViewControl.DataContext = this.performanceViewModel;
184+
return this.performanceViewModel;
185+
}
162186
}
163187
}

Services/AppNavigationOptions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* ThreadPilot - Advanced Windows Process and Power Plan Manager
3+
* Copyright (C) 2025 Prime Build
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, version 3 only.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
namespace ThreadPilot.Services
18+
{
19+
/// <summary>
20+
/// Compile-time navigation switches for optional surfaces.
21+
/// </summary>
22+
public static class AppNavigationOptions
23+
{
24+
public static bool ShowAdvancedDiagnostics => false;
25+
}
26+
}

0 commit comments

Comments
 (0)