Skip to content

Commit ef7690c

Browse files
Merge pull request #245 from erikdarlingdata/dev
Release v1.7.0 — wait stats as warnings
2 parents 2958568 + 1391589 commit ef7690c

4 files changed

Lines changed: 372 additions & 1 deletion

File tree

src/PlanViewer.App/PlanViewer.App.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<ApplicationManifest>app.manifest</ApplicationManifest>
77
<ApplicationIcon>EDD.ico</ApplicationIcon>
88
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
9-
<Version>1.6.0</Version>
9+
<Version>1.7.0</Version>
1010
<Authors>Erik Darling</Authors>
1111
<Company>Darling Data LLC</Company>
1212
<Product>Performance Studio</Product>

src/PlanViewer.Core/Services/BenefitScorer.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,73 @@ public static void Score(ParsedPlan plan)
3838

3939
if (stmt.WaitStats.Count > 0 && stmt.QueryTimeStats != null)
4040
ScoreWaitStats(stmt);
41+
42+
if (stmt.WaitStats.Count > 0)
43+
EmitWaitStatWarnings(stmt);
44+
}
45+
}
46+
}
47+
48+
/// <summary>
49+
/// Emits a PlanWarning per wait stat entry, merging the per-wait benefit %
50+
/// from ScoreWaitStats with the descriptive content from WaitStatsKnowledge.
51+
/// The existing wait-stats chart/card stays as a complementary view.
52+
/// </summary>
53+
private static void EmitWaitStatWarnings(PlanStatement stmt)
54+
{
55+
// Lookup benefit % by wait type (populated by ScoreWaitStats)
56+
var benefitByType = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
57+
foreach (var wb in stmt.WaitBenefits)
58+
benefitByType[wb.WaitType] = wb.MaxBenefitPercent;
59+
60+
foreach (var wait in stmt.WaitStats)
61+
{
62+
if (wait.WaitTimeMs <= 0) continue;
63+
64+
var entry = WaitStatsKnowledge.Lookup(wait.WaitType);
65+
double? benefitPct = benefitByType.TryGetValue(wait.WaitType, out var b) ? b : null;
66+
67+
var msg = new System.Text.StringBuilder();
68+
msg.Append(wait.WaitType).Append(": ").Append(entry.Description);
69+
msg.Append(" Observed ").Append(wait.WaitTimeMs.ToString("N0")).Append(" ms");
70+
if (wait.WaitCount > 0)
71+
msg.Append(" across ").Append(wait.WaitCount.ToString("N0")).Append(" wait").Append(wait.WaitCount == 1 ? "" : "s");
72+
msg.Append('.');
73+
74+
if (entry.ShowEffectiveLatency && wait.WaitCount > 0)
75+
{
76+
var effLatency = (double)wait.WaitTimeMs / wait.WaitCount;
77+
msg.Append(" Effective latency: ")
78+
.Append(FormatLatency(effLatency))
79+
.Append(" per wait.");
4180
}
81+
82+
var severity = benefitPct switch
83+
{
84+
>= 50 => PlanWarningSeverity.Critical,
85+
>= 10 => PlanWarningSeverity.Warning,
86+
_ => PlanWarningSeverity.Info,
87+
};
88+
89+
stmt.PlanWarnings.Add(new PlanWarning
90+
{
91+
WarningType = "Wait: " + wait.WaitType,
92+
Message = msg.ToString(),
93+
Severity = severity,
94+
MaxBenefitPercent = benefitPct,
95+
ActionableFix = entry.HowToFix
96+
});
4297
}
4398
}
4499

100+
private static string FormatLatency(double ms)
101+
{
102+
if (ms >= 1000) return $"{ms / 1000:N2} s";
103+
if (ms >= 10) return $"{ms:N0} ms";
104+
if (ms >= 1) return $"{ms:N1} ms";
105+
return $"{ms * 1000:N0} µs";
106+
}
107+
45108
private static void ScoreStatementWarnings(PlanStatement stmt)
46109
{
47110
var elapsedMs = stmt.QueryTimeStats?.ElapsedTimeMs ?? 0;

0 commit comments

Comments
 (0)