diff --git a/src/PlanViewer.App/PlanViewer.App.csproj b/src/PlanViewer.App/PlanViewer.App.csproj
index 6aa36a1..3064690 100644
--- a/src/PlanViewer.App/PlanViewer.App.csproj
+++ b/src/PlanViewer.App/PlanViewer.App.csproj
@@ -6,7 +6,7 @@
app.manifest
EDD.ico
true
- 1.7.7
+ 1.7.8
Erik Darling
Darling Data LLC
Performance Studio
diff --git a/src/PlanViewer.Core/Services/PlanAnalyzer.cs b/src/PlanViewer.Core/Services/PlanAnalyzer.cs
index dd5739f..69ada1b 100644
--- a/src/PlanViewer.Core/Services/PlanAnalyzer.cs
+++ b/src/PlanViewer.Core/Services/PlanAnalyzer.cs
@@ -1731,6 +1731,14 @@ private static long GetEffectiveChildElapsedMs(PlanNode child)
if (child.PhysicalOp == "Parallelism" && child.Children.Count > 0)
return child.Children.Max(GetEffectiveChildElapsedMs);
+ // Batch mode pipelines — each operator's elapsed stands alone rather than
+ // rolling up its descendants the way row-mode does. For a parent computing
+ // self-time above a batch-mode subtree, subtract the whole pipeline's time
+ // (Joe #215 D1: Parallelism gather-streams above three batch operators).
+ var mode = child.ActualExecutionMode ?? child.ExecutionMode;
+ if (mode == "Batch" && child.HasActualStats)
+ return SumBatchSubtreeElapsedMs(child);
+
// Child has its own stats: use them
if (child.ActualElapsedMs > 0)
return child.ActualElapsedMs;
@@ -1745,6 +1753,30 @@ private static long GetEffectiveChildElapsedMs(PlanNode child)
return sum;
}
+ ///
+ /// Sums ActualElapsedMs across a contiguous batch-mode subtree (stops at
+ /// Parallelism exchange zone boundaries). Batch operators pipeline — elapsed
+ /// times are standalone, not cumulative — so summing gives the total work the
+ /// zone did, which is what a row-mode parent above the zone should subtract
+ /// to get its own self-time.
+ ///
+ private static long SumBatchSubtreeElapsedMs(PlanNode node)
+ {
+ long sum = node.ActualElapsedMs;
+ foreach (var child in node.Children)
+ {
+ // Zone boundary — stop summing
+ if (child.PhysicalOp == "Parallelism") continue;
+
+ var childMode = child.ActualExecutionMode ?? child.ExecutionMode;
+ if (childMode == "Batch" && child.HasActualStats)
+ sum += SumBatchSubtreeElapsedMs(child);
+ else
+ sum += GetEffectiveChildElapsedMs(child);
+ }
+ return sum;
+ }
+
///
/// Calculates a Parallelism (exchange) operator's own elapsed time.
/// Exchange times are unreliable — they accumulate wait time caused by