diff --git a/Darling/Darling.Tests/Viewer4571Tests.cs b/Darling/Darling.Tests/Viewer4571Tests.cs
new file mode 100644
index 0000000000..6a8516d6c2
--- /dev/null
+++ b/Darling/Darling.Tests/Viewer4571Tests.cs
@@ -0,0 +1,23 @@
+using PerformanceMonitor.PlanAnalysis;
+using Xunit;
+
+namespace Darling.Tests;
+
+///
+/// Pins the Wait Stats card header's tooltip (#4571), matching erikdarlingdata/PerformanceStudio's
+/// desktop viewer: null when there are no waits, otherwise the total wait time and wait-type count.
+///
+public class Viewer4571Tests
+{
+ [Fact]
+ public void WaitStatsHeaderTooltip_NoWaits_IsNull()
+ {
+ Assert.Null(PlanDisplayText.WaitStatsHeaderTooltip(0, 0));
+ }
+
+ [Fact]
+ public void WaitStatsHeaderTooltip_WithWaits_DescribesCountAndTotal()
+ {
+ Assert.Equal("1,234 ms of waits across 3 wait types", PlanDisplayText.WaitStatsHeaderTooltip(3, 1234));
+ }
+}
diff --git a/PerformanceMonitor.PlanAnalysis/PlanDisplayText.cs b/PerformanceMonitor.PlanAnalysis/PlanDisplayText.cs
index 1641c18ec8..a8b9eab941 100644
--- a/PerformanceMonitor.PlanAnalysis/PlanDisplayText.cs
+++ b/PerformanceMonitor.PlanAnalysis/PlanDisplayText.cs
@@ -17,6 +17,13 @@ public static class PlanDisplayText
public static string? ParseErrorMessage(ParsedPlan plan) =>
plan.ParseError == null ? null : $"This plan couldn't be parsed: {plan.ParseError}";
+ ///
+ /// The Wait Stats card's header tooltip, matching erikdarlingdata/PerformanceStudio's desktop viewer:
+ /// shown only when there are waits to describe.
+ ///
+ public static string? WaitStatsHeaderTooltip(int waitCount, long totalWaitMs) =>
+ waitCount > 0 ? $"{totalWaitMs:N0} ms of waits across {waitCount} wait types" : null;
+
///
/// The CPU:Elapsed ratio shown in the runtime summary, matching
/// erikdarlingdata/PerformanceStudio@28d4c74's row: CPU time with external-wait time
diff --git a/PerformanceMonitor.Ui/PlanViewerControl.Properties.cs b/PerformanceMonitor.Ui/PlanViewerControl.Properties.cs
index 29ddfa46cd..fdec547814 100644
--- a/PerformanceMonitor.Ui/PlanViewerControl.Properties.cs
+++ b/PerformanceMonitor.Ui/PlanViewerControl.Properties.cs
@@ -1067,6 +1067,7 @@ private void ShowWaitStats(List waits, bool isActualPlan)
if (waits.Count == 0)
{
WaitStatsHeader.Text = "Wait Stats";
+ WaitStatsHeader.ToolTip = PlanDisplayText.WaitStatsHeaderTooltip(0, 0);
WaitStatsEmpty.Text = isActualPlan
? "No wait stats recorded"
: "No wait stats (estimated plan)";
@@ -1081,6 +1082,7 @@ private void ShowWaitStats(List waits, bool isActualPlan)
var totalWait = sorted.Sum(w => w.WaitTimeMs);
WaitStatsHeader.Text = $" Wait Stats \u2014 {totalWait:N0}ms total";
+ WaitStatsHeader.ToolTip = PlanDisplayText.WaitStatsHeaderTooltip(sorted.Count, totalWait);
var longestName = sorted.Max(w => w.WaitType.Length);
var nameColWidth = longestName * 6.5 + 10;