Skip to content

Commit fa8458f

Browse files
Merge pull request #224 from erikdarlingdata/feature/wait-stats-benefit
Add wait stats benefit scoring (Stage 2)
2 parents c003007 + b6b0eb7 commit fa8458f

7 files changed

Lines changed: 296 additions & 7 deletions

File tree

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ private void RenderStatement(PlanStatement statement)
377377
// Update banners
378378
ShowMissingIndexes(statement.MissingIndexes);
379379
ShowParameters(statement);
380-
ShowWaitStats(statement.WaitStats, statement.QueryTimeStats != null);
380+
ShowWaitStats(statement.WaitStats, statement.WaitBenefits, statement.QueryTimeStats != null);
381381
ShowRuntimeSummary(statement);
382382
UpdateInsightsHeader();
383383

@@ -2635,7 +2635,7 @@ private static long GetChildElapsedMsSum(PlanNode node)
26352635
return sum;
26362636
}
26372637

2638-
private void ShowWaitStats(List<WaitStatInfo> waits, bool isActualPlan)
2638+
private void ShowWaitStats(List<WaitStatInfo> waits, List<WaitBenefit> benefits, bool isActualPlan)
26392639
{
26402640
WaitStatsContent.Children.Clear();
26412641

@@ -2651,6 +2651,11 @@ private void ShowWaitStats(List<WaitStatInfo> waits, bool isActualPlan)
26512651

26522652
WaitStatsEmpty.IsVisible = false;
26532653

2654+
// Build benefit lookup
2655+
var benefitLookup = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
2656+
foreach (var wb in benefits)
2657+
benefitLookup[wb.WaitType] = wb.MaxBenefitPercent;
2658+
26542659
var sorted = waits.OrderByDescending(w => w.WaitTimeMs).ToList();
26552660
var maxWait = sorted[0].WaitTimeMs;
26562661
var totalWait = sorted.Sum(w => w.WaitTimeMs);
@@ -2659,10 +2664,10 @@ private void ShowWaitStats(List<WaitStatInfo> waits, bool isActualPlan)
26592664
WaitStatsHeader.Text = $" Wait Stats \u2014 {totalWait:N0}ms total";
26602665

26612666
// Build a single Grid for all rows so columns align
2662-
// Name and duration auto-size; bar fills remaining space
2667+
// Name, bar, duration, and benefit columns
26632668
var grid = new Grid
26642669
{
2665-
ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto")
2670+
ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto,Auto")
26662671
};
26672672
for (int i = 0; i < sorted.Count; i++)
26682673
grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
@@ -2709,11 +2714,27 @@ private void ShowWaitStats(List<WaitStatInfo> waits, bool isActualPlan)
27092714
FontSize = 12,
27102715
Foreground = new SolidColorBrush(Color.Parse("#E4E6EB")),
27112716
VerticalAlignment = VerticalAlignment.Center,
2712-
Margin = new Thickness(0, 2, 0, 2)
2717+
Margin = new Thickness(0, 2, 8, 2)
27132718
};
27142719
Grid.SetRow(durationText, i);
27152720
Grid.SetColumn(durationText, 2);
27162721
grid.Children.Add(durationText);
2722+
2723+
// Benefit % (if available)
2724+
if (benefitLookup.TryGetValue(w.WaitType, out var benefitPct) && benefitPct > 0)
2725+
{
2726+
var benefitText = new TextBlock
2727+
{
2728+
Text = $"up to {benefitPct:N0}%",
2729+
FontSize = 11,
2730+
Foreground = new SolidColorBrush(Color.Parse("#8b949e")),
2731+
VerticalAlignment = VerticalAlignment.Center,
2732+
Margin = new Thickness(0, 2, 0, 2)
2733+
};
2734+
Grid.SetRow(benefitText, i);
2735+
Grid.SetColumn(benefitText, 3);
2736+
grid.Children.Add(benefitText);
2737+
}
27172738
}
27182739

27192740
WaitStatsContent.Children.Add(grid);

src/PlanViewer.Core/Models/PlanModels.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class PlanStatement
6262
public SetOptionsInfo? SetOptions { get; set; }
6363
public List<PlanParameter> Parameters { get; set; } = new();
6464
public List<WaitStatInfo> WaitStats { get; set; } = new();
65+
public List<WaitBenefit> WaitBenefits { get; set; } = new();
6566
public QueryTimeInfo? QueryTimeStats { get; set; }
6667

6768
// MaxQueryMemory + QueryPlan-level warnings
@@ -447,6 +448,13 @@ public class PlanParameter
447448
public string? RuntimeValue { get; set; }
448449
}
449450

451+
public class WaitBenefit
452+
{
453+
public string WaitType { get; set; } = "";
454+
public double MaxBenefitPercent { get; set; }
455+
public string Category { get; set; } = "";
456+
}
457+
450458
public class WaitStatInfo
451459
{
452460
public string WaitType { get; set; } = "";

src/PlanViewer.Core/Output/AnalysisResult.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ public class StatementResult
139139
[JsonPropertyName("wait_stats")]
140140
public List<WaitStatResult> WaitStats { get; set; } = new();
141141

142+
// Wait stats benefit analysis
143+
[JsonPropertyName("wait_benefits")]
144+
public List<WaitBenefitResult> WaitBenefits { get; set; } = new();
145+
142146
// Cursor metadata
143147
[JsonPropertyName("cursor")]
144148
public CursorResult? Cursor { get; set; }
@@ -353,6 +357,18 @@ public class WaitStatResult
353357
public long WaitCount { get; set; }
354358
}
355359

360+
public class WaitBenefitResult
361+
{
362+
[JsonPropertyName("wait_type")]
363+
public string WaitType { get; set; } = "";
364+
365+
[JsonPropertyName("max_benefit_percent")]
366+
public double MaxBenefitPercent { get; set; }
367+
368+
[JsonPropertyName("category")]
369+
public string Category { get; set; } = "";
370+
}
371+
356372
public class CursorResult
357373
{
358374
[JsonPropertyName("name")]

src/PlanViewer.Core/Output/HtmlExporter.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,14 +391,22 @@ private static void WriteWaitStatsCard(StringBuilder sb, StatementResult stmt, b
391391
sb.AppendLine("<div class=\"card-body\">");
392392
if (stmt.WaitStats.Count > 0)
393393
{
394+
// Build benefit lookup
395+
var benefitLookup = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
396+
foreach (var wb in stmt.WaitBenefits)
397+
benefitLookup[wb.WaitType] = wb.MaxBenefitPercent;
398+
394399
var maxWait = stmt.WaitStats.Max(w => w.WaitTimeMs);
395400
foreach (var w in stmt.WaitStats.OrderByDescending(w => w.WaitTimeMs))
396401
{
397402
var barPct = maxWait > 0 ? (double)w.WaitTimeMs / maxWait * 100 : 0;
403+
var benefitTag = benefitLookup.TryGetValue(w.WaitType, out var pct)
404+
? $" <span class=\"warn-benefit\">up to {pct:N0}%</span>"
405+
: "";
398406
sb.AppendLine("<div class=\"wait-row\">");
399407
sb.AppendLine($"<span class=\"wait-type\">{Encode(w.WaitType)}</span>");
400408
sb.AppendLine($"<div class=\"wait-bar-container\"><div class=\"wait-bar\" style=\"width:{barPct:F0}%\"></div></div>");
401-
sb.AppendLine($"<span class=\"wait-ms\">{w.WaitTimeMs:N0} ms</span>");
409+
sb.AppendLine($"<span class=\"wait-ms\">{w.WaitTimeMs:N0} ms{benefitTag}</span>");
402410
sb.AppendLine("</div>");
403411
}
404412
}

src/PlanViewer.Core/Output/ResultMapper.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,17 @@ private static StatementResult MapStatement(PlanStatement stmt)
129129
});
130130
}
131131

132+
// Wait stat benefits
133+
foreach (var wb in stmt.WaitBenefits)
134+
{
135+
result.WaitBenefits.Add(new WaitBenefitResult
136+
{
137+
WaitType = wb.WaitType,
138+
MaxBenefitPercent = wb.MaxBenefitPercent,
139+
Category = wb.Category
140+
});
141+
}
142+
132143
// Parameters — flag potential sniffing issues
133144
foreach (var p in stmt.Parameters)
134145
{

src/PlanViewer.Core/Output/TextFormatter.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,18 @@ public static void WriteText(AnalysisResult result, TextWriter writer)
131131
if (stmt.WaitStats.Count > 0)
132132
{
133133
writer.WriteLine("Wait stats:");
134+
// Build a lookup from wait type to benefit %
135+
var benefitLookup = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
136+
foreach (var wb in stmt.WaitBenefits)
137+
benefitLookup[wb.WaitType] = wb.MaxBenefitPercent;
138+
134139
foreach (var w in stmt.WaitStats.OrderByDescending(w => w.WaitTimeMs))
135-
writer.WriteLine($" {w.WaitType}: {w.WaitTimeMs:N0}ms");
140+
{
141+
var benefitTag = benefitLookup.TryGetValue(w.WaitType, out var pct)
142+
? $" (up to {pct:N0}% benefit)"
143+
: "";
144+
writer.WriteLine($" {w.WaitType}: {w.WaitTimeMs:N0}ms{benefitTag}");
145+
}
136146
}
137147

138148
if (stmt.Parameters.Count > 0)

0 commit comments

Comments
 (0)