Skip to content

Commit 1aa95e2

Browse files
committed
Reduce Performance tab to optional diagnostics
1 parent cbe2402 commit 1aa95e2

10 files changed

Lines changed: 286 additions & 80 deletions

MainWindow.Behaviors.partial.cs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -340,15 +340,7 @@ private async Task LoadViewModelsAsync()
340340
await powerPlanTask; // Ensure we get any exceptions
341341
this.LogDebug("PowerPlanViewModel loaded successfully");
342342

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");
343+
this.LogDebug("Skipping optional diagnostics initialization until the diagnostics page is opened.");
352344

353345
this.LogDebug("About to load SystemTweaksViewModel...");
354346
var systemTweaksTask = this.systemTweaksViewModel.LoadCommand.ExecuteAsync(null);
@@ -1365,8 +1357,13 @@ private void ResumeForegroundRefreshes()
13651357

13661358
private AppActivityState GetForegroundActivityState()
13671359
{
1368-
return this.ProcessManagementTab.Visibility == Visibility.Visible
1369-
? AppActivityState.ForegroundProcessView
1360+
if (this.ProcessManagementTab.Visibility == Visibility.Visible)
1361+
{
1362+
return AppActivityState.ForegroundProcessView;
1363+
}
1364+
1365+
return this.PerformanceViewControl.Visibility == Visibility.Visible
1366+
? AppActivityState.ForegroundDiagnosticsView
13701367
: AppActivityState.ForegroundOtherTab;
13711368
}
13721369

@@ -1408,7 +1405,7 @@ private void ApplyAppRefreshPolicy(AppActivityState state)
14081405

14091406
if (decision.PerformanceUiMonitoringEnabled)
14101407
{
1411-
_ = this.performanceViewModel.ResumeBackgroundMonitoringAsync();
1408+
_ = this.performanceViewModel.ActivateDiagnosticsAsync();
14121409
}
14131410
else
14141411
{
@@ -1791,9 +1788,14 @@ private void ApplySectionVisibility(string tag)
17911788
return;
17921789
}
17931790

1794-
this.ApplyAppRefreshPolicy(string.Equals(tag, "Process", StringComparison.Ordinal)
1795-
? AppActivityState.ForegroundProcessView
1796-
: AppActivityState.ForegroundOtherTab);
1791+
var activityState = tag switch
1792+
{
1793+
"Process" => AppActivityState.ForegroundProcessView,
1794+
"Performance" => AppActivityState.ForegroundDiagnosticsView,
1795+
_ => AppActivityState.ForegroundOtherTab,
1796+
};
1797+
1798+
this.ApplyAppRefreshPolicy(activityState);
17971799
}
17981800

17991801
private void NavMenuItem_Click(object sender, RoutedEventArgs e)

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public MainWindow(
109109

110110
this.InitializeComponent();
111111
System.Diagnostics.Debug.WriteLine("InitializeComponent completed");
112+
this.ConfigureDiagnosticsNavigation();
112113

113114
// Initialize loading overlay
114115
this.InitializeLoadingOverlay();
@@ -159,5 +160,12 @@ public MainWindow(
159160
throw;
160161
}
161162
}
163+
164+
private void ConfigureDiagnosticsNavigation()
165+
{
166+
this.NavPerf.Visibility = AppNavigationOptions.ShowAdvancedDiagnostics
167+
? Visibility.Visible
168+
: Visibility.Collapsed;
169+
}
162170
}
163171
}

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+
}

Services/AppRefreshPolicy.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ namespace ThreadPilot.Services
2222
public enum AppActivityState
2323
{
2424
ForegroundProcessView,
25+
ForegroundDiagnosticsView,
2526
ForegroundOtherTab,
2627
Minimized,
2728
TrayHidden,
@@ -56,14 +57,21 @@ public static AppRefreshDecision Evaluate(AppActivityState state)
5657
ProcessUiRefreshEnabled: true,
5758
ImmediateProcessRefresh: true,
5859
VirtualizedPreloadEnabled: true,
60+
PerformanceUiMonitoringEnabled: false,
61+
PowerPlanUiRefreshEnabled: true,
62+
BackgroundAutomationEnabled: true),
63+
AppActivityState.ForegroundDiagnosticsView => new AppRefreshDecision(
64+
ProcessUiRefreshEnabled: false,
65+
ImmediateProcessRefresh: false,
66+
VirtualizedPreloadEnabled: false,
5967
PerformanceUiMonitoringEnabled: true,
6068
PowerPlanUiRefreshEnabled: true,
6169
BackgroundAutomationEnabled: true),
6270
AppActivityState.ForegroundOtherTab => new AppRefreshDecision(
6371
ProcessUiRefreshEnabled: false,
6472
ImmediateProcessRefresh: false,
6573
VirtualizedPreloadEnabled: false,
66-
PerformanceUiMonitoringEnabled: true,
74+
PerformanceUiMonitoringEnabled: false,
6775
PowerPlanUiRefreshEnabled: true,
6876
BackgroundAutomationEnabled: true),
6977
AppActivityState.Minimized or AppActivityState.TrayHidden => new AppRefreshDecision(

Services/SystemTrayService.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,15 @@ private void CreateContextMenu()
127127
openDashboardMenuItem.Click += this.OnDashboardClick;
128128
this.contextMenu.Items.Add(openDashboardMenuItem);
129129

130-
this.performanceMenuItem = new ToolStripMenuItem("Open Performance")
130+
if (AppNavigationOptions.ShowAdvancedDiagnostics)
131131
{
132-
Font = new Font(this.menuFont, FontStyle.Regular),
133-
};
134-
this.performanceMenuItem.Click += this.OnPerformanceDashboardClick;
135-
this.contextMenu.Items.Add(this.performanceMenuItem);
132+
this.performanceMenuItem = new ToolStripMenuItem("Open Diagnostics")
133+
{
134+
Font = new Font(this.menuFont, FontStyle.Regular),
135+
};
136+
this.performanceMenuItem.Click += this.OnPerformanceDashboardClick;
137+
this.contextMenu.Items.Add(this.performanceMenuItem);
138+
}
136139

137140
this.monitoringToggleMenuItem = new ToolStripMenuItem("Pause Automation Monitoring");
138141
this.monitoringToggleMenuItem.Click += this.OnMonitoringToggleClick;
@@ -743,4 +746,3 @@ private static Color ResolveColorFromResource(string resourceKey, Color fallback
743746
}
744747
}
745748
}
746-

Tests/ThreadPilot.Core.Tests/AppRefreshPolicyTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ namespace ThreadPilot.Core.Tests
55
public sealed class AppRefreshPolicyTests
66
{
77
[Theory]
8-
[InlineData(AppActivityState.ForegroundProcessView, true, true, true, true, true, true)]
9-
[InlineData(AppActivityState.ForegroundOtherTab, false, false, false, true, true, true)]
8+
[InlineData(AppActivityState.ForegroundProcessView, true, true, true, false, true, true)]
9+
[InlineData(AppActivityState.ForegroundDiagnosticsView, false, false, false, true, true, true)]
10+
[InlineData(AppActivityState.ForegroundOtherTab, false, false, false, false, true, true)]
1011
[InlineData(AppActivityState.Minimized, false, false, false, false, false, true)]
1112
[InlineData(AppActivityState.TrayHidden, false, false, false, false, false, true)]
1213
public void Evaluate_ReturnsExpectedRefreshDecision(
@@ -44,11 +45,13 @@ public void Evaluate_WhenStateIsUnknown_KeepsBackgroundAutomationOnly()
4445
[Theory]
4546
[InlineData(null, AppActivityState.ForegroundProcessView, true)]
4647
[InlineData(AppActivityState.ForegroundProcessView, AppActivityState.ForegroundProcessView, false)]
48+
[InlineData(AppActivityState.ForegroundDiagnosticsView, AppActivityState.ForegroundDiagnosticsView, false)]
4749
[InlineData(AppActivityState.ForegroundOtherTab, AppActivityState.ForegroundOtherTab, false)]
4850
[InlineData(AppActivityState.Minimized, AppActivityState.Minimized, false)]
4951
[InlineData(AppActivityState.TrayHidden, AppActivityState.TrayHidden, false)]
5052
[InlineData(AppActivityState.TrayHidden, AppActivityState.ForegroundProcessView, true)]
5153
[InlineData(AppActivityState.ForegroundProcessView, AppActivityState.ForegroundOtherTab, true)]
54+
[InlineData(AppActivityState.ForegroundOtherTab, AppActivityState.ForegroundDiagnosticsView, true)]
5255
public void ShouldApplyTransition_SkipsRedundantStateTransitions(
5356
AppActivityState? previousState,
5457
AppActivityState nextState,
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
namespace ThreadPilot.Core.Tests
2+
{
3+
using Microsoft.Extensions.Logging.Abstractions;
4+
using Moq;
5+
using ThreadPilot.Models;
6+
using ThreadPilot.Services;
7+
using ThreadPilot.ViewModels;
8+
9+
public sealed class PerformanceViewModelDiagnosticsTests
10+
{
11+
[Fact]
12+
public async Task InitializeAsync_DoesNotStartLiveMonitoringOrScanProcesses()
13+
{
14+
var harness = new Harness();
15+
var viewModel = harness.CreateViewModel();
16+
17+
await viewModel.InitializeAsync();
18+
19+
harness.Performance.Verify(x => x.StartMonitoringAsync(), Times.Never);
20+
harness.Performance.Verify(x => x.GetSystemMetricsAsync(It.IsAny<bool>()), Times.Never);
21+
harness.Performance.Verify(x => x.GetTopCpuProcessesAsync(It.IsAny<int>()), Times.Never);
22+
harness.Performance.Verify(x => x.GetTopMemoryProcessesAsync(It.IsAny<int>()), Times.Never);
23+
harness.PowerPlan.Verify(x => x.GetActivePowerPlan(), Times.Never);
24+
}
25+
26+
[Fact]
27+
public async Task ActivateDiagnosticsAsync_LoadsSnapshotWithoutStartingLiveMonitoring()
28+
{
29+
var harness = new Harness();
30+
var viewModel = harness.CreateViewModel();
31+
32+
await viewModel.ActivateDiagnosticsAsync();
33+
34+
harness.Performance.Verify(x => x.GetSystemMetricsAsync(false), Times.Once);
35+
harness.Performance.Verify(x => x.GetHistoricalDataAsync(TimeSpan.FromHours(1)), Times.Once);
36+
harness.Performance.Verify(x => x.GetTopCpuProcessesAsync(25), Times.Once);
37+
harness.Performance.Verify(x => x.GetTopMemoryProcessesAsync(25), Times.Once);
38+
harness.PowerPlan.Verify(x => x.GetActivePowerPlan(), Times.Once);
39+
harness.Performance.Verify(x => x.StartMonitoringAsync(), Times.Never);
40+
Assert.False(viewModel.IsMonitoring);
41+
}
42+
43+
[Fact]
44+
public async Task SuspendBackgroundMonitoringAsync_StopsLiveMonitoringAndDoesNotAutoResume()
45+
{
46+
var harness = new Harness();
47+
var viewModel = harness.CreateViewModel();
48+
49+
await viewModel.StartMonitoringCommand.ExecuteAsync(null);
50+
await viewModel.SuspendBackgroundMonitoringAsync();
51+
await viewModel.ResumeBackgroundMonitoringAsync();
52+
53+
harness.Performance.Verify(x => x.StartMonitoringAsync(), Times.Once);
54+
harness.Performance.Verify(x => x.StopMonitoringAsync(), Times.Once);
55+
Assert.False(viewModel.IsMonitoring);
56+
Assert.Equal("Stopped", viewModel.MonitoringStateText);
57+
}
58+
59+
[Fact]
60+
public void ShowAdvancedDiagnostics_DefaultsToHidden()
61+
{
62+
Assert.False(AppNavigationOptions.ShowAdvancedDiagnostics);
63+
}
64+
65+
private sealed class Harness
66+
{
67+
public Mock<IPerformanceMonitoringService> Performance { get; } = new(MockBehavior.Strict);
68+
69+
public Mock<IProcessService> Process { get; } = new(MockBehavior.Strict);
70+
71+
public Mock<IProcessPowerPlanAssociationService> Associations { get; } = new(MockBehavior.Strict);
72+
73+
public Mock<IPowerPlanService> PowerPlan { get; } = new(MockBehavior.Strict);
74+
75+
public Mock<IProcessMonitorManagerService> ProcessMonitorManager { get; } = new(MockBehavior.Strict);
76+
77+
public Mock<ISystemTweaksService> SystemTweaks { get; } = new(MockBehavior.Strict);
78+
79+
public Harness()
80+
{
81+
this.Performance
82+
.Setup(x => x.GetSystemMetricsAsync(It.IsAny<bool>()))
83+
.ReturnsAsync(new SystemPerformanceMetrics());
84+
this.Performance
85+
.Setup(x => x.GetHistoricalDataAsync(It.IsAny<TimeSpan>()))
86+
.ReturnsAsync(new List<SystemPerformanceMetrics>());
87+
this.Performance
88+
.Setup(x => x.GetTopCpuProcessesAsync(It.IsAny<int>()))
89+
.ReturnsAsync(new List<ProcessPerformanceInfo>());
90+
this.Performance
91+
.Setup(x => x.GetTopMemoryProcessesAsync(It.IsAny<int>()))
92+
.ReturnsAsync(new List<ProcessPerformanceInfo>());
93+
this.Performance
94+
.Setup(x => x.StartMonitoringAsync())
95+
.Returns(Task.CompletedTask);
96+
this.Performance
97+
.Setup(x => x.StopMonitoringAsync())
98+
.Returns(Task.CompletedTask);
99+
100+
this.Associations
101+
.Setup(x => x.GetAssociationsAsync())
102+
.ReturnsAsync(Array.Empty<ProcessPowerPlanAssociation>());
103+
104+
this.PowerPlan
105+
.Setup(x => x.GetActivePowerPlan())
106+
.ReturnsAsync(new PowerPlanModel { Guid = "balanced", Name = "Balanced" });
107+
}
108+
109+
public PerformanceViewModel CreateViewModel() =>
110+
new(
111+
this.Performance.Object,
112+
this.Process.Object,
113+
this.Associations.Object,
114+
this.PowerPlan.Object,
115+
this.ProcessMonitorManager.Object,
116+
this.SystemTweaks.Object,
117+
NullLogger<PerformanceViewModel>.Instance);
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)