Repository navigation
Plan analysis: PerformanceStudio's per-rule configuration, rule numbers, rule 38 and the legacy marker (#4535, #4566) - #4585
Merged
Conversation
Ports PerformanceStudio's AnalyzerConfig/RulesConfig (disabled rules, severity overrides) and the top-level ServerMetadata fields into PerformanceMonitor.PlanAnalysis, adds PlanWarning.RuleNumber, and adds Analyze/Run overloads that take (config, serverMetadata, cancellationToken) and thread both down to the statement and node walks. The existing overloads still forward to AnalyzerConfig.Default and a null metadata, and no rule reads either new parameter yet, so analyzer output is unchanged.
…ng framework as legacy (#4566)
…ing (#4535) Ports erikdarlingdata/PerformanceStudio's severity-override pass: overrides now find a warning's rule via PlanWarning.RuleNumber instead of matching WarningType against a rule-to-name table, and the engine's own warnings (Source == SqlServer) are always skipped. Also ports PlanStatements.EnumerateAllWithContainer, with EnumerateAll delegating to it so the plain and container-carrying walks stay one walk.
…5-analyzer-config
…5-analyzer-config
#4535, #4566) The plumbing step's RuleNumber_DefaultsToNull_UntilLaterStepsStampIt pinned the state partway through the per-rule configuration rollout (only rule 3 stamped). Every step that stamps a rule has landed on this branch, so that pin is wrong by design now. Replaces it with PlanSync4535RuleNumberCensusTests: a code census over every new PlanWarning construction in PlanAnalyzer.cs (each must set RuleNumber, except a SqlServer-sourced initializer), a runtime census over the existing plan fixtures (every analyzer finding carries a non-null RuleNumber), and a cross-step check that disabling every stamped rule number leaves zero analyzer findings.
erikdarlingdata
marked this pull request as ready for review
September 28, 2026 15:31
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.
Refs #4535 (the override mechanism is here; the configuration source that lets a user set overrides comes in the follow-up, so the issue stays open until then)
Fixes #4566
Refs #4530 (the entry points that pass the server metadata come in a follow-up)
Part of #4511
Why
PerformanceMonitor's and PerformanceStudio's plan analyzers are kept equivalent. This change ports PerformanceStudio's per-rule configuration plumbing, the rule numbers it tags each finding with, its Standard Edition DOP-2 rule, and its legacy-finding marker.
What changes
AnalyzerConfig(withRulesConfig, disabled rules and severity overrides) andServerMetadataare new types the analyzer accepts.PlanAnalyzer.AnalyzeandPlanAnalysisPipeline.Rungain overloads that take them; the existing no-argument overloads are unchanged and behave exactly as before (both pinned byte-for-byte identical).PlanWarningthe analyzer constructs now carries aRuleNumber. A rule number census over the analyzer's source confirms this for every construction site, with two documented exceptions: rules 7 (spill severity) and 29 (implicit-conversion severity escalation) mutate an existing finding's severity/message rather than constructing a new one, so they carry a disable guard with no stamp.!cfg.IsRuleDisabled(N)guard is wired at its own construction point, so a disabled rule produces no finding. A cross-step test disables every stamped rule number and confirms the fixtures then produce zero analyzer findings (SQL Server's own findings, which aren't numbered, are unaffected).ApplySeverityOverrides) applies configured severity overrides to any stamped, analyzer-sourced finding, walking into stored-procedure and UDF bodies the same way the analyzer's own statement walk does. SQL Server-sourced findings are never overridden.dev: it looks at a statement that ran at DOP 2 with at least one batch-mode operator, unless the statement carries aMAXDOP 2hint (masked for comments and literals, like rule 39's hint check), which makes the cap intentional. With the edition known to be Standard and the server's MAXDOP above 2 it is a Warning; with the edition unknown it is reported as a possible limitation (Info). Until the entry points pass server metadata (the follow-up), the edition is always unknown, so every rule-38 finding is Info.IsLegacy = trueon the finding, except when the finding came from SQL Server itself rather than the analyzer. This reaches the MCP plan tools' JSON (is_legacyper warning) and the plan viewer's warning header (an appended" [legacy]"tag, after the source tag).Not yet
Loading a per-rule configuration from a file is the next PR. Until then, the default configuration applies everywhere: no rule is disabled, and no severity is overridden.
Test plan
Each step's own pins and REDs (recorded in the pre-merge step reports) plus, at the combine:
PlanSync4535ConfigPlumbingTests.RuleNumber_DefaultsToNull_UntilLaterStepsStampItpinned the state partway through the rollout (only rule 3 stamped) and is wrong now that every rule-stamping step has landed.PlanSync4535RuleNumberCensusTests(replaces it): (a) a code census walking everynew PlanWarningconstruction inPlanAnalyzer.cs, asserting each setsRuleNumberunless it's SQL-Server-sourced; (b) a runtime census over plan fixtures, asserting every analyzer-sourced finding (excluding the benefit scorer'sWait:findings, which aren't a numbered rule) has a non-nullRuleNumber; (c) disabling every rule number the code census found, plus rules 7 and 29 (guard-only, no construction site), and confirming the fixtures then produce zero analyzer findings.b20e89003): all 4 pins failed there (2 runtime failures from unstamped/undisabled rules, 1 empty-collection failure from having no rules to disable yet, and a missing-substring failure since the rule 7/29 guards didn't exist at that point).RuleNumber = 20stamp from the Local Variables construction site, rebuilt, confirmed the code census caught it (EveryAnalyzerConstructedWarning_StampsRuleNumber_UnlessSqlServerSourced [FAIL]), then reverted and rebuilt clean.Darling.Tests,PerformanceMonitor.Ui,Lite/PerformanceMonitorLite.csproj,Lite.Tests,Darling/PerformanceMonitor.Darling.Serviceanddeprecated/Dashboard.Testsall build Release withEnableWindowsTargeting=true: 0 warnings, 0 errors on every one of them.Microsoft.WindowsDesktop.Appstripped from the runtime config), everyPlanSync*class plusBenefitScorerWiringTests,McpToolsListBudgetTests,McpPayloadContractCensusTestsand the required non-PlanSyncclasses (ShowPlanParserCondAndMultiplePlanTests, theActualPlan*classes,QueryModificationDetectorTests,ReproScriptBuilderHardeningTests,DarlingAnalysisPipelineTests,DarlingMcpPlanToolsSurfaceAndSqlTests,DarlingMcpPlanToolsLivePostgresTests,McpPlanAnalysisEnvelopeTests,SerialLoopStoreSizeSourceTests,TsqlConventionGuardTests,DocCommentHygieneTests):Total: 563, Errors: 0, Failed: 0, Skipped: 2, Not Run: 0. The 2 skips are the two live-Postgres tests that need
DARLING_TEST_PG, unaffected by this change.Lite.Testswas built (0 warnings/0 errors) but not run — it targetsnet10.0-windowsand can't run on macOS; CI decides it.CHANGELOG
SECTION: Added
ENTRY: - Plan analysis flags a possible Standard Edition batch-mode DOP limit, and marks legacy findings ([#4585]) - A plan that ran at DOP 2 with batch-mode operators gets a Standard Edition DOP Limitation finding (Info until the server's edition is known). Findings from rules that predate the benefit-scoring framework carry
is_legacyin the MCP plan output and a [legacy] tag in the plan viewer.REF: [#4585]: #4585