Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Dashboard/Controls/ResourceMetricsContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@
<Button Content="Refresh" Click="WaitStatsDetail_Refresh_Click" Margin="10,0,0,0" Padding="8,2" Style="{StaticResource SuccessButton}" FontSize="11"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5,2">
<Button Content="Select All" Click="WaitTypes_SelectAll_Click" Padding="6,2" FontSize="10" Margin="0,0,5,0"/>
<Button Content="Top Waits" Click="WaitTypes_SelectAll_Click" Padding="6,2" FontSize="10" Margin="0,0,5,0"/>
<Button Content="Clear All" Click="WaitTypes_ClearAll_Click" Padding="6,2" FontSize="10"/>
<TextBlock x:Name="WaitTypeCountText" Text="0 / 20 selected" FontSize="10" Foreground="{DynamicResource ForegroundMutedBrush}" VerticalAlignment="Center" Margin="8,0,0,0"/>
</StackPanel>
<TextBox Grid.Row="2" x:Name="WaitTypeSearchBox" Margin="5,2" Height="24" FontSize="11"
TextChanged="WaitTypeSearch_TextChanged"
Expand Down
45 changes: 21 additions & 24 deletions Dashboard/Controls/ResourceMetricsContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,17 @@ private void RefreshWaitTypeListOrder()
.ToList();
_waitTypeItems = sorted;
ApplyWaitTypeSearchFilter();
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 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)
Expand Down Expand Up @@ -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();
Expand All @@ -1925,6 +1927,7 @@ private async void WaitTypes_ClearAll_Click(object sender, RoutedEventArgs e)
item.IsSelected = false;
}
_isUpdatingWaitTypeSelection = false;
UpdateWaitTypeCount();
await UpdateWaitStatsDetailChartAsync();
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2047,12 +2042,14 @@ private void LoadWaitStatsDetailChart(List<WaitStatsDataPoint>? 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
Expand Down
66 changes: 66 additions & 0 deletions Dashboard/Helpers/TabHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,72 @@ namespace PerformanceMonitorDashboard.Helpers
/// </summary>
public static class TabHelpers
{
/// <summary>
/// Poison waits — always selected by default. These indicate critical resource exhaustion.
/// </summary>
public static readonly string[] PoisonWaits = new[]
{
"THREADPOOL",
"RESOURCE_SEMAPHORE",
"RESOURCE_SEMAPHORE_QUERY_COMPILE"
};

/// <summary>
/// Usual suspect waits — always selected by default. Common performance-relevant wait types.
/// </summary>
public static readonly string[] UsualSuspectWaits = new[]
{
"SOS_SCHEDULER_YIELD",
"CXPACKET",
"CXCONSUMER",
"PAGEIOLATCH_SH",
"PAGEIOLATCH_EX",
"WRITELOG"
};

/// <summary>
/// Prefix patterns for usual suspect waits (e.g. PAGELATCH_EX, PAGELATCH_SH, etc.)
/// </summary>
public static readonly string[] UsualSuspectPrefixes = new[] { "PAGELATCH_" };

/// <summary>
/// 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.
/// </summary>
public static HashSet<string> GetDefaultWaitTypes(IList<string> availableWaitTypes)
{
var defaults = new HashSet<string>(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;
}

/// <summary>
/// Applies the Darling Data dark theme to a ScottPlot chart.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion Lite/Controls/ServerTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Select Wait Types" FontWeight="SemiBold" Margin="0,0,0,4"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,4">
<Button Content="Select All" Click="WaitTypeSelectAll_Click" Padding="6,2" Margin="0,0,4,0" FontSize="11"/>
<Button Content="Top Waits" Click="WaitTypeSelectAll_Click" Padding="6,2" Margin="0,0,4,0" FontSize="11"/>
<Button Content="Clear All" Click="WaitTypeClearAll_Click" Padding="6,2" FontSize="11"/>
<TextBlock x:Name="WaitTypeCountText" Text="0 / 20 selected" FontSize="10" Foreground="{StaticResource ForegroundMutedBrush}" VerticalAlignment="Center" Margin="8,0,0,0"/>
</StackPanel>
<TextBox Grid.Row="2" x:Name="WaitTypeSearchBox" TextChanged="WaitTypeSearch_TextChanged"
Margin="0,0,0,4" Padding="4,2"/>
Expand Down
57 changes: 45 additions & 12 deletions Lite/Controls/ServerTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -988,15 +989,41 @@ private void UpdateExecutionCountTrendChart(List<QueryTrendPoint> 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<string> GetDefaultWaitTypes(List<string> availableWaitTypes)
{
var defaults = new HashSet<string>(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<string> waitTypes)
{
var previouslySelected = new HashSet<string>(_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();
Expand All @@ -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()
Expand All @@ -1027,15 +1065,10 @@ private void ApplyWaitTypeFilter()
private void WaitTypeSelectAll_Click(object sender, RoutedEventArgs e)
{
_isUpdatingWaitTypeSelection = true;
var visible = (WaitTypesList.ItemsSource as IEnumerable<SelectableItem>)?.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();
Expand Down Expand Up @@ -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);
Expand Down