Skip to content

Commit 1e92eaa

Browse files
committed
wait profiles added for GroupBy displays
1 parent cd7153a commit 1e92eaa

1 file changed

Lines changed: 182 additions & 94 deletions

File tree

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

Lines changed: 182 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
using System;
2-
using System;
3-
using System.Collections.Generic;
4-
using System.Collections.ObjectModel;
5-
using System.ComponentModel;
6-
using System.Linq;
7-
using System.Runtime.CompilerServices;
8-
using System.Threading;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.ComponentModel;
5+
using System.Linq;
6+
using System.Runtime.CompilerServices;
7+
using System.Threading;
98
using Avalonia;
109
using Avalonia.Controls;
1110
using Avalonia.Controls.Primitives;
@@ -302,6 +301,89 @@ private async System.Threading.Tasks.Task FetchGroupedPlansAsync(
302301
SelectToggleButton.Content = "Select All";
303302
UpdateStatusText();
304303
UpdateBarRatios();
304+
305+
// Fetch per-plan wait stats for leaf rows, then consolidate upward
306+
if (_waitStatsSupported && _waitStatsEnabled && _slicerStartUtc.HasValue && _slicerEndUtc.HasValue)
307+
_ = FetchGroupedWaitStatsAsync(_slicerStartUtc.Value, _slicerEndUtc.Value, ct);
308+
}
309+
310+
/// <summary>
311+
/// Fetches per-plan wait stats for all real plan IDs found in the grouped hierarchy,
312+
/// assigns them to leaf rows, then consolidates upward to intermediate and root rows.
313+
/// </summary>
314+
private async System.Threading.Tasks.Task FetchGroupedWaitStatsAsync(
315+
DateTime startUtc, DateTime endUtc, CancellationToken ct)
316+
{
317+
try
318+
{
319+
// Collect all real plan IDs from rows that have a real PlanId
320+
var allPlanIds = _rows
321+
.Where(r => r.PlanId > 0)
322+
.Select(r => r.PlanId)
323+
.Distinct()
324+
.ToList();
325+
326+
if (allPlanIds.Count == 0) return;
327+
328+
var planWaits = await QueryStoreService.FetchPlanWaitStatsAsync(
329+
_connectionString, startUtc, endUtc, allPlanIds, ct);
330+
if (ct.IsCancellationRequested) return;
331+
332+
// Build lookup: plan_id → list of WaitCategoryTotal
333+
var byPlan = planWaits
334+
.GroupBy(x => x.PlanId)
335+
.ToDictionary(g => g.Key, g => g.Select(x => x.Wait).ToList());
336+
337+
// 1. Assign raw waits + profiles to rows with a real PlanId
338+
foreach (var row in _rows)
339+
{
340+
if (row.PlanId > 0 && byPlan.TryGetValue(row.PlanId, out var waits))
341+
{
342+
row.RawWaitCategories = waits;
343+
row.WaitProfile = QueryStoreService.BuildWaitProfile(waits);
344+
}
345+
}
346+
347+
// 2. Consolidate upward through the hierarchy
348+
foreach (var root in _groupedRootRows)
349+
ConsolidateWaitProfileUpward(root);
350+
351+
UpdateWaitBarMode();
352+
}
353+
catch (OperationCanceledException) { }
354+
catch (Exception) { }
355+
}
356+
357+
/// <summary>
358+
/// Recursively consolidates wait profiles from children into their parent.
359+
/// For each parent: merges all children's RawWaitCategories by summing WaitRatio
360+
/// per category, then builds a new WaitProfile from the merged totals.
361+
/// </summary>
362+
private static void ConsolidateWaitProfileUpward(QueryStoreRow parent)
363+
{
364+
if (parent.Children.Count == 0) return;
365+
366+
// Recurse first so children are consolidated before we merge them
367+
foreach (var child in parent.Children)
368+
ConsolidateWaitProfileUpward(child);
369+
370+
// Merge all children's raw wait categories by summing WaitRatio per category
371+
var merged = parent.Children
372+
.SelectMany(c => c.RawWaitCategories)
373+
.GroupBy(w => new { w.WaitCategory, w.WaitCategoryDesc })
374+
.Select(g => new WaitCategoryTotal
375+
{
376+
WaitCategory = g.Key.WaitCategory,
377+
WaitCategoryDesc = g.Key.WaitCategoryDesc,
378+
WaitRatio = g.Sum(w => w.WaitRatio),
379+
})
380+
.ToList();
381+
382+
if (merged.Count > 0)
383+
{
384+
parent.RawWaitCategories = merged;
385+
parent.WaitProfile = QueryStoreService.BuildWaitProfile(merged);
386+
}
305387
}
306388

307389
/// <summary>Maps an orderBy metric string to a Func that extracts the sort value from a QueryStoreRow.</summary>
@@ -810,7 +892,10 @@ private async System.Threading.Tasks.Task FetchWaitStatsAsync(
810892
DateTime startUtc, DateTime endUtc, CancellationToken ct)
811893
{
812894
await FetchGlobalWaitStatsOnlyAsync(startUtc, endUtc, ct);
813-
await FetchPerPlanWaitStatsAsync(startUtc, endUtc, ct);
895+
if (_groupByMode != QueryStoreGroupBy.None)
896+
await FetchGroupedWaitStatsAsync(startUtc, endUtc, ct);
897+
else
898+
await FetchPerPlanWaitStatsAsync(startUtc, endUtc, ct);
814899
}
815900

816901
private void OnWaitCategoryClicked(object? sender, string category)
@@ -1460,88 +1545,91 @@ private static IComparable GetSortKey(string columnTag, QueryStoreRow r) =>
14601545
};
14611546
}
14621547

1463-
public class QueryStoreRow : INotifyPropertyChanged
1464-
{
1465-
private bool _isSelected = false;
1466-
1467-
// Bar ratios [0..1] per column
1468-
private double _execsRatio;
1469-
private double _totalCpuRatio;
1470-
private double _avgCpuRatio;
1471-
private double _totalDurRatio;
1472-
private double _avgDurRatio;
1473-
private double _totalReadsRatio;
1474-
private double _avgReadsRatio;
1475-
private double _totalWritesRatio;
1476-
private double _avgWritesRatio;
1477-
private double _totalPhysReadsRatio;
1478-
private double _avgPhysReadsRatio;
1479-
private double _totalMemRatio;
1480-
private double _avgMemRatio;
1481-
1482-
// IsSortedColumn flags
1483-
private bool _isSorted_Executions;
1484-
private bool _isSorted_TotalCpu;
1485-
private bool _isSorted_AvgCpu;
1486-
private bool _isSorted_TotalDuration;
1487-
private bool _isSorted_AvgDuration;
1488-
private bool _isSorted_TotalReads;
1489-
private bool _isSorted_AvgReads;
1490-
private bool _isSorted_TotalWrites;
1491-
private bool _isSorted_AvgWrites;
1492-
private bool _isSorted_TotalPhysReads;
1493-
private bool _isSorted_AvgPhysReads;
1494-
private bool _isSorted_TotalMemory;
1495-
private bool _isSorted_AvgMemory;
1496-
1497-
// Wait stats
1498-
private WaitProfile? _waitProfile;
1499-
private string? _waitHighlightCategory;
1500-
1501-
// Hierarchy support
1502-
private bool _isExpanded;
1503-
private int _indentLevel;
1504-
1505-
/// <summary>Standard constructor for flat (ungrouped) rows.</summary>
1506-
public QueryStoreRow(QueryStorePlan plan)
1507-
{
1508-
Plan = plan;
1509-
}
1510-
1511-
/// <summary>Constructor for grouped parent/intermediate rows (aggregated, no single plan).</summary>
1512-
public QueryStoreRow(QueryStorePlan syntheticPlan, int indentLevel, string groupLabel, List<QueryStoreRow> children)
1513-
{
1514-
Plan = syntheticPlan;
1515-
_indentLevel = indentLevel;
1516-
GroupLabel = groupLabel;
1517-
Children = children;
1518-
}
1519-
1520-
public QueryStorePlan Plan { get; }
1521-
1522-
// ── Hierarchy properties ───────────────────────────────────────────────
1523-
1524-
/// <summary>Indentation level: 0 = top group, 1 = intermediate, 2 = leaf.</summary>
1525-
public int IndentLevel
1526-
{
1527-
get => _indentLevel;
1528-
set { _indentLevel = value; OnPropertyChanged(); }
1529-
}
1530-
1531-
/// <summary>Label shown for grouped rows (e.g. "0x1A2B3C" or "dbo.MyProc").</summary>
1532-
public string GroupLabel { get; set; } = "";
1533-
1534-
/// <summary>Direct children of this group row.</summary>
1535-
public List<QueryStoreRow> Children { get; set; } = new();
1536-
1537-
public bool HasChildren => Children.Count > 0;
1538-
1539-
public bool IsExpanded
1540-
{
1541-
get => _isExpanded;
1542-
set { _isExpanded = value; OnPropertyChanged(); OnPropertyChanged(nameof(ExpandChevron)); }
1543-
}
1544-
1548+
public class QueryStoreRow : INotifyPropertyChanged
1549+
{
1550+
private bool _isSelected = false;
1551+
1552+
// Bar ratios [0..1] per column
1553+
private double _execsRatio;
1554+
private double _totalCpuRatio;
1555+
private double _avgCpuRatio;
1556+
private double _totalDurRatio;
1557+
private double _avgDurRatio;
1558+
private double _totalReadsRatio;
1559+
private double _avgReadsRatio;
1560+
private double _totalWritesRatio;
1561+
private double _avgWritesRatio;
1562+
private double _totalPhysReadsRatio;
1563+
private double _avgPhysReadsRatio;
1564+
private double _totalMemRatio;
1565+
private double _avgMemRatio;
1566+
1567+
// IsSortedColumn flags
1568+
private bool _isSorted_Executions;
1569+
private bool _isSorted_TotalCpu;
1570+
private bool _isSorted_AvgCpu;
1571+
private bool _isSorted_TotalDuration;
1572+
private bool _isSorted_AvgDuration;
1573+
private bool _isSorted_TotalReads;
1574+
private bool _isSorted_AvgReads;
1575+
private bool _isSorted_TotalWrites;
1576+
private bool _isSorted_AvgWrites;
1577+
private bool _isSorted_TotalPhysReads;
1578+
private bool _isSorted_AvgPhysReads;
1579+
private bool _isSorted_TotalMemory;
1580+
private bool _isSorted_AvgMemory;
1581+
1582+
// Wait stats
1583+
private WaitProfile? _waitProfile;
1584+
private string? _waitHighlightCategory;
1585+
1586+
/// <summary>Raw wait category totals for this row. Used for upward consolidation in grouped mode.</summary>
1587+
public List<WaitCategoryTotal> RawWaitCategories { get; set; } = new();
1588+
1589+
// Hierarchy support
1590+
private bool _isExpanded;
1591+
private int _indentLevel;
1592+
1593+
/// <summary>Standard constructor for flat (ungrouped) rows.</summary>
1594+
public QueryStoreRow(QueryStorePlan plan)
1595+
{
1596+
Plan = plan;
1597+
}
1598+
1599+
/// <summary>Constructor for grouped parent/intermediate rows (aggregated, no single plan).</summary>
1600+
public QueryStoreRow(QueryStorePlan syntheticPlan, int indentLevel, string groupLabel, List<QueryStoreRow> children)
1601+
{
1602+
Plan = syntheticPlan;
1603+
_indentLevel = indentLevel;
1604+
GroupLabel = groupLabel;
1605+
Children = children;
1606+
}
1607+
1608+
public QueryStorePlan Plan { get; }
1609+
1610+
// ── Hierarchy properties ───────────────────────────────────────────────
1611+
1612+
/// <summary>Indentation level: 0 = top group, 1 = intermediate, 2 = leaf.</summary>
1613+
public int IndentLevel
1614+
{
1615+
get => _indentLevel;
1616+
set { _indentLevel = value; OnPropertyChanged(); }
1617+
}
1618+
1619+
/// <summary>Label shown for grouped rows (e.g. "0x1A2B3C" or "dbo.MyProc").</summary>
1620+
public string GroupLabel { get; set; } = "";
1621+
1622+
/// <summary>Direct children of this group row.</summary>
1623+
public List<QueryStoreRow> Children { get; set; } = new();
1624+
1625+
public bool HasChildren => Children.Count > 0;
1626+
1627+
public bool IsExpanded
1628+
{
1629+
get => _isExpanded;
1630+
set { _isExpanded = value; OnPropertyChanged(); OnPropertyChanged(nameof(ExpandChevron)); }
1631+
}
1632+
15451633
public string ExpandChevron => HasChildren ? (IsExpanded ? "▾" : "▸") : "";
15461634

15471635
/// <summary>Left margin that increases with indent level to visually show hierarchy.</summary>
@@ -1553,10 +1641,10 @@ public bool IsExpanded
15531641
/// <summary>Bold for top-level groups, normal for children.</summary>
15541642
public Avalonia.Media.FontWeight GroupFontWeight => IndentLevel == 0 ? Avalonia.Media.FontWeight.Bold : Avalonia.Media.FontWeight.Normal;
15551643

1556-
public bool IsSelected
1557-
{
1558-
get => _isSelected;
1559-
set { _isSelected = value; OnPropertyChanged(); }
1644+
public bool IsSelected
1645+
{
1646+
get => _isSelected;
1647+
set { _isSelected = value; OnPropertyChanged(); }
15601648
}
15611649

15621650
// ── Bar ratio properties ───────────────────────────────────────────────

0 commit comments

Comments
 (0)