11using 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 ;
98using Avalonia ;
109using Avalonia . Controls ;
1110using 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