Skip to content

Commit fbd47b0

Browse files
Merge pull request #153 from rferraton/feature/query-store-wait-stats-v1.1
Feature/query store wait stats v1.1
2 parents 27e03be + 3301e02 commit fbd47b0

5 files changed

Lines changed: 253 additions & 93 deletions

File tree

src/PlanViewer.App/Controls/WaitProfileBarControl.axaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private void Redraw()
113113
Canvas.SetTop(rect, 0);
114114
BarCanvas.Children.Add(rect);
115115

116-
ToolTip.SetTip(rect, $"{seg.Category}: {seg.WaitRatio:P2} ({seg.Ratio:P1} of total)");
116+
ToolTip.SetTip(rect, $"{seg.Category}: {WaitRatioFormatter.Format(seg.WaitRatio)} ({seg.Ratio:P1} of total)");
117117

118118
var capturedCategory = seg.Category;
119119
rect.PointerPressed += (_, e) =>

src/PlanViewer.App/Controls/WaitStatsProfileControl.axaml

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,33 @@
33
xmlns:local="using:PlanViewer.App.Controls"
44
x:Class="PlanViewer.App.Controls.WaitStatsProfileControl">
55
<Grid RowDefinitions="Auto,*">
6-
<!-- Header: toggle chart type -->
7-
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="6" Margin="4,2">
8-
<Button x:Name="ToggleChartButton" Content=""
6+
<!-- Header row: left controls + right-aligned legend button -->
7+
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,*,Auto" Margin="4,2">
8+
<Button x:Name="ToggleChartButton" Grid.Column="0" Content="&#x25A4;"
99
Width="22" Height="22" Padding="0"
1010
FontSize="12" Background="Transparent" BorderThickness="0"
1111
Foreground="{DynamicResource ForegroundBrush}"
1212
ToolTip.Tip="Toggle bar / ribbon chart"
1313
Click="ToggleChart_Click"/>
14-
<TextBlock x:Name="TitleText" Text="Wait Stats"
14+
<TextBlock x:Name="TitleText" Grid.Column="1" Text="Wait Stats"
1515
FontSize="11" FontWeight="SemiBold"
1616
Foreground="{DynamicResource SlicerToggleBrush}"
17-
VerticalAlignment="Center"/>
18-
<Button x:Name="TableViewButton" Content=""
19-
Width="22" Height="22" Padding="0"
20-
FontSize="12" Background="Transparent" BorderThickness="0"
17+
VerticalAlignment="Center" Margin="6,0,0,0"/>
18+
<!-- spacer -->
19+
<Button x:Name="LegendButton" Grid.Column="3" Content="Legend"
20+
Height="20" Padding="8,0"
21+
FontSize="10"
22+
Background="Transparent" BorderThickness="1"
23+
BorderBrush="{DynamicResource ForegroundMutedBrush}"
2124
Foreground="{DynamicResource ForegroundBrush}"
22-
ToolTip.Tip="Show wait stats as table"
23-
Click="TableView_Click"/>
24-
</StackPanel>
25+
ToolTip.Tip="Show color legend"
26+
VerticalAlignment="Center"
27+
Click="Legend_Click"/>
28+
</Grid>
2529
<!-- Content area -->
2630
<Grid Grid.Row="1" x:Name="ContentArea" MinHeight="24">
27-
<local:WaitProfileBarControl x:Name="GlobalBar" PercentMode="True"/>
28-
<local:WaitStatsRibbonControl x:Name="GlobalRibbon" IsVisible="False"/>
29-
<DataGrid x:Name="TableGrid" IsVisible="False"
30-
AutoGenerateColumns="False"
31-
CanUserSortColumns="True"
32-
CanUserResizeColumns="True"
33-
IsReadOnly="True"
34-
SelectionMode="Single"
35-
GridLinesVisibility="Horizontal"
36-
HeadersVisibility="Column"
37-
FontSize="11"
38-
Background="{DynamicResource BackgroundDarkBrush}"
39-
BorderThickness="0">
40-
<DataGrid.Columns>
41-
<DataGridTextColumn Header="Category" Binding="{ReflectionBinding Category}" Width="*"/>
42-
<DataGridTextColumn Header="Wait Ratio" Binding="{ReflectionBinding WaitRatioText}" Width="Auto"/>
43-
<DataGridTextColumn Header="% of Total" Binding="{ReflectionBinding RatioText}" Width="Auto"/>
44-
</DataGrid.Columns>
45-
</DataGrid>
31+
<local:WaitProfileBarControl x:Name="GlobalBar" PercentMode="True" IsVisible="False"/>
32+
<local:WaitStatsRibbonControl x:Name="GlobalRibbon"/>
4633
<!-- Loading overlay -->
4734
<Border x:Name="WaitLoadingOverlay" IsVisible="False"
4835
Background="#80000000" CornerRadius="0"
Lines changed: 100 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,45 @@
11
using System;
2+
using System;
23
using System.Collections.Generic;
3-
using System.Collections.ObjectModel;
4-
using System.Diagnostics;
54
using System.Linq;
5+
using Avalonia;
66
using Avalonia.Controls;
7+
using Avalonia.Controls.Primitives;
78
using Avalonia.Interactivity;
9+
using Avalonia.Layout;
10+
using Avalonia.Media;
811
using PlanViewer.Core.Models;
912

1013
namespace PlanViewer.App.Controls;
1114

1215
public partial class WaitStatsProfileControl : UserControl
1316
{
14-
private enum ViewMode { Bar, Ribbon, Table }
17+
private enum ViewMode { Bar, Ribbon }
1518

16-
private ViewMode _viewMode = ViewMode.Bar;
19+
private ViewMode _viewMode = ViewMode.Ribbon;
1720
private bool _isCollapsed;
1821
private WaitProfile? _currentProfile;
19-
private readonly ObservableCollection<WaitTableRow> _tableRows = new();
22+
private Popup? _legendPopup;
2023

2124
public event EventHandler<string>? CategoryClicked;
2225
public event EventHandler<string>? CategoryDoubleClicked;
2326
public event EventHandler<bool>? CollapsedChanged;
2427

2528
public bool IsCollapsed => _isCollapsed;
2629

30+
// All known wait categories in the order they appear in the theme
31+
private static readonly string[] AllWaitCategories =
32+
[
33+
"Unknown", "CPU", "Worker Thread", "Lock", "Latch", "Buffer Latch",
34+
"Buffer IO", "Compilation", "SQL CLR", "Mirroring", "Transaction",
35+
"Preemptive", "Service Broker", "Tran Log IO", "Network IO",
36+
"Parallelism", "Memory", "Tracing", "Full Text Search",
37+
"Other Disk IO", "Replication", "Log Rate Governor", "Others"
38+
];
39+
2740
public WaitStatsProfileControl()
2841
{
2942
InitializeComponent();
30-
TableGrid.ItemsSource = _tableRows;
3143
GlobalBar.CategoryClicked += (_, cat) => CategoryClicked?.Invoke(this, cat);
3244
GlobalBar.CategoryDoubleClicked += (_, cat) => CategoryDoubleClicked?.Invoke(this, cat);
3345
GlobalRibbon.CategoryClicked += (_, cat) => CategoryClicked?.Invoke(this, cat);
@@ -38,7 +50,6 @@ public void SetBarProfile(WaitProfile? profile)
3850
{
3951
_currentProfile = profile;
4052
GlobalBar.SetProfile(profile);
41-
RefreshTableRows();
4253
}
4354

4455
public void SetRibbonData(List<WaitCategoryTimeSlice> data)
@@ -59,7 +70,7 @@ public void Expand()
5970
ContentArea.IsVisible = true;
6071
TitleText.IsVisible = true;
6172
ToggleChartButton.IsVisible = true;
62-
TableViewButton.IsVisible = true;
73+
LegendButton.IsVisible = true;
6374
CollapsedChanged?.Invoke(this, false);
6475
}
6576

@@ -70,7 +81,7 @@ public void Collapse()
7081
ContentArea.IsVisible = false;
7182
TitleText.IsVisible = false;
7283
ToggleChartButton.IsVisible = false;
73-
TableViewButton.IsVisible = false;
84+
LegendButton.IsVisible = false;
7485
CollapsedChanged?.Invoke(this, true);
7586
}
7687

@@ -81,57 +92,99 @@ public void SetLoading(bool isLoading)
8192

8293
private void ToggleChart_Click(object? sender, RoutedEventArgs e)
8394
{
84-
// Cycle: Bar -> Ribbon -> Bar (skip table; table has its own button)
85-
if (_viewMode == ViewMode.Table)
86-
{
87-
// If in table mode, toggle goes back to bar
88-
_viewMode = ViewMode.Bar;
89-
}
90-
else
91-
{
92-
_viewMode = _viewMode == ViewMode.Bar ? ViewMode.Ribbon : ViewMode.Bar;
93-
}
94-
ApplyViewMode();
95-
}
96-
97-
private void TableView_Click(object? sender, RoutedEventArgs e)
98-
{
99-
_viewMode = _viewMode == ViewMode.Table ? ViewMode.Bar : ViewMode.Table;
95+
_viewMode = _viewMode == ViewMode.Bar ? ViewMode.Ribbon : ViewMode.Bar;
10096
ApplyViewMode();
10197
}
10298

10399
private void ApplyViewMode()
104100
{
105101
GlobalBar.IsVisible = _viewMode == ViewMode.Bar;
106102
GlobalRibbon.IsVisible = _viewMode == ViewMode.Ribbon;
107-
TableGrid.IsVisible = _viewMode == ViewMode.Table;
108-
ToggleChartButton.Content = _viewMode == ViewMode.Ribbon ? "▤" : "☰";
109-
110-
// The ContentArea lives inside an Auto-sized parent row, so a *-row
111-
// DataGrid would collapse to zero height. Give an explicit height
112-
// when in table mode; reset to NaN (auto) for chart modes.
113-
ContentArea.Height = _viewMode == ViewMode.Table ? 120 : double.NaN;
103+
ToggleChartButton.Content = _viewMode == ViewMode.Ribbon ? "☰" : "▤";
114104
}
115105

116-
private void RefreshTableRows()
106+
private void Legend_Click(object? sender, RoutedEventArgs e)
117107
{
118-
_tableRows.Clear();
119-
if (_currentProfile == null) { return; }
120-
foreach (var seg in _currentProfile.Segments.OrderByDescending(s => s.Ratio))
108+
if (_legendPopup != null)
121109
{
122-
_tableRows.Add(new WaitTableRow
110+
_legendPopup.IsOpen = !_legendPopup.IsOpen;
111+
return;
112+
}
113+
114+
var grid = new Grid { ColumnDefinitions = new ColumnDefinitions("Auto,Auto") };
115+
for (int i = 0; i < AllWaitCategories.Length; i++)
116+
grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
117+
118+
for (int i = 0; i < AllWaitCategories.Length; i++)
119+
{
120+
var cat = AllWaitCategories[i];
121+
var brush = TryFindBrush($"WaitCategory.{cat}", new SolidColorBrush(Color.Parse("#555D66")));
122+
123+
var swatch = new Border
123124
{
124-
Category = seg.Category,
125-
WaitRatioText = seg.WaitRatio.ToString("P2"),
126-
RatioText = seg.Ratio.ToString("P1")
127-
});
125+
Width = 14,
126+
Height = 14,
127+
Background = brush,
128+
CornerRadius = new CornerRadius(2),
129+
Margin = new Thickness(4, 2),
130+
VerticalAlignment = VerticalAlignment.Center,
131+
};
132+
Grid.SetRow(swatch, i);
133+
Grid.SetColumn(swatch, 0);
134+
grid.Children.Add(swatch);
135+
136+
var label = new TextBlock
137+
{
138+
Text = cat,
139+
FontSize = 11,
140+
VerticalAlignment = VerticalAlignment.Center,
141+
Margin = new Thickness(4, 2, 8, 2),
142+
};
143+
if (this.TryFindResource("ForegroundBrush", this.ActualThemeVariant, out var fg) && fg is IBrush fgBrush)
144+
label.Foreground = fgBrush;
145+
Grid.SetRow(label, i);
146+
Grid.SetColumn(label, 1);
147+
grid.Children.Add(label);
128148
}
149+
150+
var scroll = new ScrollViewer
151+
{
152+
Content = grid,
153+
MaxHeight = 300,
154+
VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto,
155+
};
156+
157+
var bgBrush = TryFindBrush("BackgroundLightBrush", new SolidColorBrush(Color.Parse("#22252D")));
158+
var borderBrush = TryFindBrush("BorderBrush", new SolidColorBrush(Color.Parse("#3A3D45")));
159+
var container = new Border
160+
{
161+
Background = bgBrush,
162+
BorderBrush = borderBrush,
163+
BorderThickness = new Thickness(1),
164+
CornerRadius = new CornerRadius(6),
165+
Padding = new Thickness(4),
166+
Child = scroll,
167+
};
168+
169+
_legendPopup = new Popup
170+
{
171+
Child = container,
172+
IsLightDismissEnabled = true,
173+
Placement = PlacementMode.Bottom,
174+
PlacementTarget = LegendButton,
175+
};
176+
177+
// Add to visual tree so DynamicResources resolve
178+
if (this.Content is Grid rootGrid)
179+
rootGrid.Children.Add(_legendPopup);
180+
181+
_legendPopup.IsOpen = true;
129182
}
130-
}
131183

132-
public class WaitTableRow
133-
{
134-
public string Category { get; set; } = "";
135-
public string WaitRatioText { get; set; } = "";
136-
public string RatioText { get; set; } = "";
184+
private IBrush TryFindBrush(string key, IBrush fallback)
185+
{
186+
if (this.TryFindResource(key, this.ActualThemeVariant, out var resource) && resource is IBrush brush)
187+
return brush;
188+
return fallback;
189+
}
137190
}

0 commit comments

Comments
 (0)