Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/PlanViewer.App/PlanViewer.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>EDD.ico</ApplicationIcon>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<Version>1.6.0</Version>
<Version>1.7.0</Version>
<Authors>Erik Darling</Authors>
<Company>Darling Data LLC</Company>
<Product>Performance Studio</Product>
Expand Down
63 changes: 63 additions & 0 deletions src/PlanViewer.Core/Services/BenefitScorer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,73 @@ public static void Score(ParsedPlan plan)

if (stmt.WaitStats.Count > 0 && stmt.QueryTimeStats != null)
ScoreWaitStats(stmt);

if (stmt.WaitStats.Count > 0)
EmitWaitStatWarnings(stmt);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EmitWaitStatWarnings runs even when QueryTimeStats == null (line 39's ScoreWaitStats is skipped in that case). With no WaitBenefits populated, benefitByType is empty and every wait gets severity = Info regardless of how dominant the wait is. That may be intentional (estimated/incomplete plans don't have elapsed to score against) but worth confirming — if it is, a one-line comment here saying "no QueryTimeStats → all severities pinned to Info" would save the next reader a trip through the lookup chain.

Also: the two adjacent if (stmt.WaitStats.Count > 0) blocks can collapse into a single guarded block, slightly cheaper and reads more clearly:

if (stmt.WaitStats.Count > 0)
{
    if (stmt.QueryTimeStats != null) ScoreWaitStats(stmt);
    EmitWaitStatWarnings(stmt);
}

Generated by Claude Code

}
}
}

/// <summary>
/// Emits a PlanWarning per wait stat entry, merging the per-wait benefit %
/// from ScoreWaitStats with the descriptive content from WaitStatsKnowledge.
/// The existing wait-stats chart/card stays as a complementary view.
/// </summary>
private static void EmitWaitStatWarnings(PlanStatement stmt)
{
// Lookup benefit % by wait type (populated by ScoreWaitStats)
var benefitByType = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
foreach (var wb in stmt.WaitBenefits)
benefitByType[wb.WaitType] = wb.MaxBenefitPercent;

foreach (var wait in stmt.WaitStats)
{
if (wait.WaitTimeMs <= 0) continue;
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only filter on emitting a warning is WaitTimeMs <= 0. Every nonzero wait — including 1ms SOS_SCHEDULER_YIELD chatter on a 30s query — becomes a first-class PlanWarning in the expander. On busy real-world plans (10–30 distinct wait types) this can flood the Plan Warnings UI with low-signal Info rows and make the genuinely actionable ones harder to find.

Consider a minimum-relevance threshold, e.g. skip emission if wait.WaitTimeMs / elapsedMs < 0.005 and benefitPct < 1, or drop pure Info waits below some absolute floor.


Generated by Claude Code


var entry = WaitStatsKnowledge.Lookup(wait.WaitType);
double? benefitPct = benefitByType.TryGetValue(wait.WaitType, out var b) ? b : null;

var msg = new System.Text.StringBuilder();
msg.Append(wait.WaitType).Append(": ").Append(entry.Description);
msg.Append(" Observed ").Append(wait.WaitTimeMs.ToString("N0")).Append(" ms");
if (wait.WaitCount > 0)
msg.Append(" across ").Append(wait.WaitCount.ToString("N0")).Append(" wait").Append(wait.WaitCount == 1 ? "" : "s");
msg.Append('.');

if (entry.ShowEffectiveLatency && wait.WaitCount > 0)
{
var effLatency = (double)wait.WaitTimeMs / wait.WaitCount;
msg.Append(" Effective latency: ")
.Append(FormatLatency(effLatency))
.Append(" per wait.");
}

var severity = benefitPct switch
{
>= 50 => PlanWarningSeverity.Critical,
>= 10 => PlanWarningSeverity.Warning,
_ => PlanWarningSeverity.Info,
};

stmt.PlanWarnings.Add(new PlanWarning
{
WarningType = "Wait: " + wait.WaitType,
Message = msg.ToString(),
Severity = severity,
MaxBenefitPercent = benefitPct,
ActionableFix = entry.HowToFix
});
}
}

private static string FormatLatency(double ms)
{
if (ms >= 1000) return $"{ms / 1000:N2} s";
if (ms >= 10) return $"{ms:N0} ms";
if (ms >= 1) return $"{ms:N1} ms";
return $"{ms * 1000:N0} µs";
}

private static void ScoreStatementWarnings(PlanStatement stmt)
{
var elapsedMs = stmt.QueryTimeStats?.ElapsedTimeMs ?? 0;
Expand Down
Loading
Loading