diff --git a/Dashboard/Controls/ResourceMetricsContent.xaml b/Dashboard/Controls/ResourceMetricsContent.xaml
index 52b06e2c..9ea43c3e 100644
--- a/Dashboard/Controls/ResourceMetricsContent.xaml
+++ b/Dashboard/Controls/ResourceMetricsContent.xaml
@@ -110,8 +110,9 @@
-
+
+
x.IsSelected);
+ WaitTypeCountText.Text = $"{count} / 20 selected";
+ WaitTypeCountText.Foreground = count >= 20
+ ? new System.Windows.Media.SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#E57373")!)
+ : (System.Windows.Media.Brush)FindResource("ForegroundMutedBrush");
}
private void WaitTypeSearch_TextChanged(object sender, TextChangedEventArgs e)
@@ -1897,19 +1908,10 @@ private async void WaitTypes_SelectAll_Click(object sender, RoutedEventArgs e)
{
if (_waitTypeItems == null) return;
_isUpdatingWaitTypeSelection = true;
- // Only select first 12 (color limit)
- int count = 0;
+ var topWaits = TabHelpers.GetDefaultWaitTypes(_waitTypeItems.Select(x => x.WaitType).ToList());
foreach (var item in _waitTypeItems)
{
- if (count < 12)
- {
- item.IsSelected = true;
- count++;
- }
- else
- {
- item.IsSelected = false;
- }
+ item.IsSelected = topWaits.Contains(item.WaitType);
}
_isUpdatingWaitTypeSelection = false;
RefreshWaitTypeListOrder();
@@ -1925,6 +1927,7 @@ private async void WaitTypes_ClearAll_Click(object sender, RoutedEventArgs e)
item.IsSelected = false;
}
_isUpdatingWaitTypeSelection = false;
+ UpdateWaitTypeCount();
await UpdateWaitStatsDetailChartAsync();
}
@@ -1961,22 +1964,14 @@ private async Task RefreshWaitStatsDetailTabAsync()
})
.ToList();
- // If nothing was previously selected, default select some common wait types
+ // If nothing was previously selected, apply poison waits + usual suspects + top 10
if (!waitTypes.Any(w => w.IsSelected))
{
- var defaultWaitTypes = new[] { "CXPACKET", "SOS_SCHEDULER_YIELD", "PAGEIOLATCH_SH", "LCK_M_X", "ASYNC_NETWORK_IO", "WRITELOG" };
- foreach (var item in waitTypes.Where(w => defaultWaitTypes.Contains(w.WaitType)))
+ var topWaits = TabHelpers.GetDefaultWaitTypes(waitTypes.Select(w => w.WaitType).ToList());
+ foreach (var item in waitTypes.Where(w => topWaits.Contains(w.WaitType)))
{
item.IsSelected = true;
}
- // If none of the defaults exist, select the top 5
- if (!waitTypes.Any(w => w.IsSelected) && waitTypes.Count > 0)
- {
- foreach (var item in waitTypes.Take(5))
- {
- item.IsSelected = true;
- }
- }
}
_waitTypeItems = waitTypes;
@@ -2047,12 +2042,14 @@ private void LoadWaitStatsDetailChart(List? data, int hoursB
var colors = new[] {
ScottPlot.Colors.Blue, ScottPlot.Colors.Green, ScottPlot.Colors.Orange, ScottPlot.Colors.Red,
ScottPlot.Colors.Purple, ScottPlot.Colors.Cyan, ScottPlot.Colors.Magenta, ScottPlot.Colors.DarkGreen,
- ScottPlot.Colors.Navy, ScottPlot.Colors.Brown, ScottPlot.Colors.Teal, ScottPlot.Colors.Olive
+ ScottPlot.Colors.Navy, ScottPlot.Colors.Brown, ScottPlot.Colors.Teal, ScottPlot.Colors.Olive,
+ ScottPlot.Colors.Coral, ScottPlot.Colors.SkyBlue, ScottPlot.Colors.Gold, ScottPlot.Colors.MediumPurple,
+ ScottPlot.Colors.Salmon, ScottPlot.Colors.LimeGreen, ScottPlot.Colors.SandyBrown, ScottPlot.Colors.SlateGray
};
// Get all time points across all wait types for gap filling
int colorIndex = 0;
- foreach (var waitType in selectedWaitTypes.Take(12)) // Limit to 12 wait types
+ foreach (var waitType in selectedWaitTypes.Take(20)) // Limit to 20 wait types
{
// Get data for this wait type
var waitTypeData = data
diff --git a/Dashboard/Helpers/TabHelpers.cs b/Dashboard/Helpers/TabHelpers.cs
index e73a438b..577e832d 100644
--- a/Dashboard/Helpers/TabHelpers.cs
+++ b/Dashboard/Helpers/TabHelpers.cs
@@ -28,6 +28,72 @@ namespace PerformanceMonitorDashboard.Helpers
///
public static class TabHelpers
{
+ ///
+ /// Poison waits — always selected by default. These indicate critical resource exhaustion.
+ ///
+ public static readonly string[] PoisonWaits = new[]
+ {
+ "THREADPOOL",
+ "RESOURCE_SEMAPHORE",
+ "RESOURCE_SEMAPHORE_QUERY_COMPILE"
+ };
+
+ ///
+ /// Usual suspect waits — always selected by default. Common performance-relevant wait types.
+ ///
+ public static readonly string[] UsualSuspectWaits = new[]
+ {
+ "SOS_SCHEDULER_YIELD",
+ "CXPACKET",
+ "CXCONSUMER",
+ "PAGEIOLATCH_SH",
+ "PAGEIOLATCH_EX",
+ "WRITELOG"
+ };
+
+ ///
+ /// Prefix patterns for usual suspect waits (e.g. PAGELATCH_EX, PAGELATCH_SH, etc.)
+ ///
+ public static readonly string[] UsualSuspectPrefixes = new[] { "PAGELATCH_" };
+
+ ///
+ /// Returns the set of wait types that should be selected by default:
+ /// poison waits + usual suspects + top 10 by total wait time (deduped), capped at 12.
+ /// The availableWaitTypes list must be sorted by total wait time descending.
+ ///
+ public static HashSet GetDefaultWaitTypes(IList availableWaitTypes)
+ {
+ var defaults = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ // 1. Poison waits that exist in data
+ foreach (var w in PoisonWaits)
+ if (availableWaitTypes.Contains(w)) defaults.Add(w);
+
+ // 2. Usual suspects — exact matches
+ foreach (var w in UsualSuspectWaits)
+ if (availableWaitTypes.Contains(w)) defaults.Add(w);
+
+ // 3. Usual suspects — prefix matches
+ foreach (var prefix in UsualSuspectPrefixes)
+ foreach (var w in availableWaitTypes)
+ if (w.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ defaults.Add(w);
+
+ // 4. Top 10 by total wait time (items not already in the set), hard cap at 20 total
+ int added = 0;
+ foreach (var w in availableWaitTypes)
+ {
+ if (defaults.Count >= 20) break;
+ if (added >= 10) break;
+ if (defaults.Add(w))
+ {
+ added++;
+ }
+ }
+
+ return defaults;
+ }
+
///
/// Applies the Darling Data dark theme to a ScottPlot chart.
///
diff --git a/Lite/Controls/ServerTab.xaml b/Lite/Controls/ServerTab.xaml
index f5cf4ea7..8b97c15e 100644
--- a/Lite/Controls/ServerTab.xaml
+++ b/Lite/Controls/ServerTab.xaml
@@ -125,8 +125,9 @@
-
+
+
diff --git a/Lite/Controls/ServerTab.xaml.cs b/Lite/Controls/ServerTab.xaml.cs
index c7001e88..6dfdd344 100644
--- a/Lite/Controls/ServerTab.xaml.cs
+++ b/Lite/Controls/ServerTab.xaml.cs
@@ -50,7 +50,8 @@ public partial class ServerTab : UserControl
{
"#2eaef1", "#F44336", "#4CAF50", "#FFC107", "#9C27B0",
"#FF9800", "#00BCD4", "#E91E63", "#8BC34A", "#3F51B5",
- "#CDDC39", "#795548"
+ "#CDDC39", "#795548", "#FF7F50", "#87CEEB", "#FFD700",
+ "#9370DB", "#FA8072", "#32CD32", "#F4A460", "#708090"
};
public int UtcOffsetMinutes { get; }
@@ -988,15 +989,41 @@ private void UpdateExecutionCountTrendChart(List data)
/* ========== Wait Stats Picker ========== */
+ private static readonly string[] PoisonWaits = { "THREADPOOL", "RESOURCE_SEMAPHORE", "RESOURCE_SEMAPHORE_QUERY_COMPILE" };
+ private static readonly string[] UsualSuspectWaits = { "SOS_SCHEDULER_YIELD", "CXPACKET", "CXCONSUMER", "PAGEIOLATCH_SH", "PAGEIOLATCH_EX", "WRITELOG" };
+ private static readonly string[] UsualSuspectPrefixes = { "PAGELATCH_" };
+
+ private static HashSet GetDefaultWaitTypes(List availableWaitTypes)
+ {
+ var defaults = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var w in PoisonWaits)
+ if (availableWaitTypes.Contains(w)) defaults.Add(w);
+ foreach (var w in UsualSuspectWaits)
+ if (availableWaitTypes.Contains(w)) defaults.Add(w);
+ foreach (var prefix in UsualSuspectPrefixes)
+ foreach (var w in availableWaitTypes)
+ if (w.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ defaults.Add(w);
+ int added = 0;
+ foreach (var w in availableWaitTypes)
+ {
+ if (defaults.Count >= 20) break;
+ if (added >= 10) break;
+ if (!defaults.Contains(w)) { defaults.Add(w); added++; }
+ }
+ return defaults;
+ }
+
private bool _isUpdatingWaitTypeSelection;
private void PopulateWaitTypePicker(List waitTypes)
{
var previouslySelected = new HashSet(_waitTypeItems.Where(i => i.IsSelected).Select(i => i.DisplayName));
+ var topWaits = previouslySelected.Count == 0 ? GetDefaultWaitTypes(waitTypes) : null;
_waitTypeItems = waitTypes.Select(w => new SelectableItem
{
DisplayName = w,
- IsSelected = previouslySelected.Contains(w) || (previouslySelected.Count == 0 && waitTypes.IndexOf(w) < 10)
+ IsSelected = previouslySelected.Contains(w) || (topWaits != null && topWaits.Contains(w))
}).ToList();
/* Sort checked items to top, then preserve original order (by total wait time desc) */
RefreshWaitTypeListOrder();
@@ -1007,9 +1034,20 @@ private void RefreshWaitTypeListOrder()
if (_waitTypeItems == null) return;
_waitTypeItems = _waitTypeItems
.OrderByDescending(x => x.IsSelected)
- .ThenBy(x => _waitTypeItems.IndexOf(x))
+ .ThenBy(x => x.DisplayName)
.ToList();
ApplyWaitTypeFilter();
+ UpdateWaitTypeCount();
+ }
+
+ private void UpdateWaitTypeCount()
+ {
+ if (_waitTypeItems == null || WaitTypeCountText == null) return;
+ int count = _waitTypeItems.Count(x => x.IsSelected);
+ WaitTypeCountText.Text = $"{count} / 20 selected";
+ WaitTypeCountText.Foreground = count >= 20
+ ? new SolidColorBrush((System.Windows.Media.Color)ColorConverter.ConvertFromString("#E57373")!)
+ : (System.Windows.Media.Brush)FindResource("ForegroundMutedBrush");
}
private void ApplyWaitTypeFilter()
@@ -1027,15 +1065,10 @@ private void ApplyWaitTypeFilter()
private void WaitTypeSelectAll_Click(object sender, RoutedEventArgs e)
{
_isUpdatingWaitTypeSelection = true;
- var visible = (WaitTypesList.ItemsSource as IEnumerable)?.ToList() ?? _waitTypeItems;
- int count = visible.Count(i => i.IsSelected);
- foreach (var item in visible)
+ var topWaits = GetDefaultWaitTypes(_waitTypeItems.Select(x => x.DisplayName).ToList());
+ foreach (var item in _waitTypeItems)
{
- if (!item.IsSelected && count < 12)
- {
- item.IsSelected = true;
- count++;
- }
+ item.IsSelected = topWaits.Contains(item.DisplayName);
}
_isUpdatingWaitTypeSelection = false;
RefreshWaitTypeListOrder();
@@ -1063,7 +1096,7 @@ private async System.Threading.Tasks.Task UpdateWaitStatsChartFromPickerAsync()
{
try
{
- var selected = _waitTypeItems.Where(i => i.IsSelected).Take(12).ToList();
+ var selected = _waitTypeItems.Where(i => i.IsSelected).Take(20).ToList();
ClearChart(WaitStatsChart);
ApplyDarkTheme(WaitStatsChart);