Repository navigation
Plan analysis: flag an expensive operator no other rule caught (#4527) - #4550
Merged
Merged
Conversation
erikdarlingdata
marked this pull request as ready for review
September 28, 2026 03:00
This was referenced Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4527
Part of #4511
Why
Some operators take a large share of a statement's time without tripping any of the plan analyzer's specific rules — a scan that's just reading a lot of data, with no bad estimate, no spill, no missing index to name. Without a catch-all, that time silently disappears from the findings even though it dominated the run.
What changes
OperatorTimeRuleslist entry to add for rule 35 either — the source project doesn't add "Expensive Operator" to that list, so this port doesn't either.Test plan
New file
Darling/Darling.Tests/PlanSync4527Tests.cs, five cases against a synthetic single-leaf-operator plan (dbo.T,[db]), all throughShowPlanParser.Parse+PlanAnalyzer.Analyze:60% self-time on a 2,000ms statement → Critical, benefit 60.0
30% self-time → Warning, benefit 30.0
10% self-time → no finding
the same 60% share on a 500ms statement → no finding (the 1,000ms floor)
a 60% operator that already carries a UDF-timing warning → no "Expensive Operator" finding (no stacking)
Runtime RED on dev: the two firing facts fail there (
Assert.Single() Failure: The collection did not contain any matching items); the three "does not fire" facts pass there, as they should.Mutation, reverted: the 1,000 ms floor changed to
> 0failsRule35_60PercentSelfTimeOn500msStatement_DoesNotFire_BelowFloor.Run: the new class plus the plan-analysis classes:
Total: 236, Failed: 0, Skipped: 2(the live-Postgres plan-tool class runs in CI).Lite.Testsbuilds.Ordering: rule 35 runs last among the node rules, as in PerformanceStudio, because it fires only on a node that no other rule warned about. The pins use leaf operators, so they do not depend on the self-time fixes in Plan analysis: operator self-time excludes the coordinator thread and looks through Compute Scalar and batch mode subtrees (#4513, #4515) #4547.
The finding's benefit percentage takes effect once the scorer runs in the product (PerformanceMonitor never runs the plan BenefitScorer, so findings get no benefit score and the scorer's wait findings never appear #4546).
CHANGELOG
SECTION: Added
ENTRY: - Plan analysis now flags an operator that takes a large share of a statement's run time even when no other rule has advice for it ([#4550]) - A new "Expensive Operator" finding fires when a single operator's own time is at least 20% of a statement that ran 1 second or longer, so a big chunk of runtime no longer disappears from the findings just because nothing specific was wrong with it.
REF: [#4550]: #4550