Repository navigation
compare_analysis sigma-bands the PostgreSQL baselined metrics, so a 6× TPS surge on a PostgreSQL target stops reading "stable" (#3691 W1/W2) - #3713
Merged
Conversation
…x TPS surge stops reading stable (#3691 W1, W2) ComparisonBanding.BaselinedMetricFor mapped only the four SQL Server keys; on a PostgreSQL target PG_TPS (base severity 0 by design) could never move under the absolute rule and a 10 -> 60 tps move banded stable in the window the detector called 25 sigma. PG_TPS / PG_CONNECTION_SATURATION / PG_DEADLOCK_RATE / PG_CPU_PERCENT now map to the pg_tps / pg_session_count / pg_deadlock_rate / pg_cpu buckets PgTargetBaselineProvider stores, through a unit-reconciliation seam (BaselinedValueFor): the saturation FRACTION is banded on its peak_total_sessions count, a CPU fact holding the raw cpu_percent is withheld, every SQL Server reading is its value unchanged. The plan_cache_churn note speaks the engine of the facts (pg_ source prefix). SQL Server output is byte-identical before/after (harness-diffed); pins in ComparisonBandingTests, live wiring proof in CompareAnalysisPgBandingLiveTests.
erikdarlingdata
enabled auto-merge (squash)
September 19, 2026 10:33
Owner
Author
|
Coordinator read: |
This was referenced Sep 19, 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.
What does this PR do?
compare_analysison a PostgreSQL target now bandsPG_TPS,PG_CONNECTION_SATURATION,PG_DEADLOCK_RATEandPG_CPU_PERCENTagainst the server's own hour-of-week baseline, so a 6× throughput surge stops reading "stable". PARTIAL for #3691 — ticks the checklist line:compare_analysisbands a 6× TPS move "stable"The lie it told
The v1 exit check planted 31 days at ~10 tps and four hours at 60 tps on a server stamped
engine_kind='postgres'. The anomaly detector called it 25σ.compare_analysis, same store, same window, said this about thePG_TPSfamily:{ "key": "PG_TPS", "baseline_value": 10, "comparison_value": 60, "value_delta": 50, "relative_move": 0.8333, "ladder_position": 0, "status": "stable", "band_source": "absolute", "delta_sigma": null, "baseline_sigma": null, "baseline_median": null, "baseline_metric": null, "beyond_anomaly_cutoff": null }Two things conspired.
ComparisonBanding.BaselinedMetricFormapped only the four SQL Server keys (CPU_SQL_PERCENT,CPU_SPIKE,IO_READ_LATENCY_MS,SESSION_STATS), so no PostgreSQL row ever took the sigma path — the PostgreSQL bucketsPgTargetBaselineProviderstores (pg_tps,pg_session_count,pg_deadlock_rate,pg_cpu) were never even requested. And the absolute rule needs BOTH a quarter relative move AND a quarter ladder position;PG_TPShas base severity 0 by design (throughput is context, not a grade), so no move of any size could register. A key whose only honest reading is "how far did it move on this server's own scale" was structurally shut out of the one rule that reads that.What it says now
Same facts, planted bucket median 10 / sigma 1 (the unit pin; the live class proves the real bucket):
{ "key": "PG_TPS", "baseline_value": 10, "comparison_value": 60, "value_delta": 50, "relative_move": 0.8333, "ladder_position": 0, "status": "worse", "band_source": "baseline", "delta_sigma": 25, "baseline_sigma": 1, "baseline_median": 10, "baseline_confidence": 1, "baseline_tier": "Full", "baseline_metric": "pg_tps", "beyond_anomaly_cutoff": true }delta_sigmais the detectors' 25σ display cap on a +50σ raw move;beyond_anomaly_cutoff: truesays the move also clears the detector's own 3.5σ bar, which is the same verdict the detector gave.Why this shape
The mapping (W1). Four arms added to
BaselinedMetricFor, constants only (PgTargetFactKeys.*→MetricNames.Pg*), mapped where the fact's reading is in the bucket's unit and stated per key in the doc:PG_TPS= window transactions ÷ observed seconds;pg_tps= per-collection transactions ÷ the collection's own gap. Averaged-key case: per-sample sigma is an upper bound on the average's dispersion, so the band is conservative (the existingStableWithinRobustSigmasdoc already argues this for CPU %).PG_DEADLOCK_RATE= deadlocks ÷ observed hours;pg_deadlock_rate= deadlocks × 3600 ÷ gap — the same unit, unlike SQL Server's events-per-day deadlock baseline which stays unmapped. On a healthy server the bucket's median and MAD sit at 0,EffectiveRobustSigmais 0, and the existingCompareguard routes the key to the absolute rule — the never-blind fallback, withbaseline_metricstill naming the bucket.PG_CPU_PERCENT= peakacu_utilization_percentwhen the window carried a capacity sample;pg_cpu= that column's buckets.PG_CONNECTION_SATURATIONis the one whoseValueis not the bucket's unit: peak ÷ usable connections (a 0–1 fraction) againstMAX(total_sessions)per capture (a count).The unit-reconciliation seam.
SESSION_STATSneeded none (its value IS the connection count). For PostgreSQL a newBaselinedValueFor(Fact)returns the reading in the bucket's unit or null:PG_CONNECTION_SATURATION→ itspeak_total_sessionsmetadata (the very reading the bucket is built from and the onePgTargetAnomalyDetectorjudges against it; null when absent, as on a hand-built fact);PG_CPU_PERCENT→ its value only whencapacity_measured = 1(a fact holding the rawcpu_percentis percent of currently-allocated capacity, #3281 — another unit — and is withheld); every other key →Fact.Value.Comparetakes the sigma path only when the bucket is trustworthy AND both sides offer a reading; otherwise the absolute rule.BandBySigmadecides the band on the reading delta;value_deltastays in the fact's own unit. Rejected: banding the fraction in count-sigma (the brief forbade it and it is wrong by a factor ofusable_connections); converting the bucket into fraction units (same arithmetic, but thenbaseline_median/baseline_sigmawould be in a unit thebaseline_metricname does not describe).Direction. The sigma arm's rule stays "higher is worse". For
PG_TPSthat is the detector's reading — the high side is the flagged one, more load than this hour routinely carries — so the surge counts as a regression in the family rollup instead of vanishing. A throughput FALL therefore readsbetter; that is the same one-sided limit the detector has (it does not fire on a collapse), stated in code, and the two-sided reading is left as a v3 item rather than faked by inverting a key whose fall is either an outage or a quiet hour.Wiring (brief step 2) — confirmed, not re-plumbed.
DarlingAnalysisService.ComparePeriodsAsyncresolves the engine per call fromservers.engine_kindandLookUpDispersionAsyncasksengine.Baselines—PgTargetBaselineProviderfor a PostgreSQL target — for each metricDispersionMetricsFornames, anchored atcomparisonStart, which is exactly wherePgTargetAnomalyDetectoranchors itsGetBaselineAsync. No file outside the boundary needed to change.The churn note (W2).
plan_cache_churn.notetold a PostgreSQL operator thatBAD_ACTOR_<hash>keys — which their payload never contains — are plan-cache identities.PlanCacheChurngains aPostgresTargetflag and two named sentences;Comparesets it when any compared fact carries aPgTargetSources.Prefix(pg_) source — the engine resolution carried on the fact by the collector that ran, the same prefix the shared scorer routes on, so neither SKU's tool body has to plumb an engine parameter. The PostgreSQL sentence says whatPG_BAD_ACTOR_<queryid>keys are (statement identities frompg_stat_statements), that the churn lists are empty by construction (the prefix decision from between waves, pinned inPgTargetBetweenWavesTests), and that a statement present on one side only IS counted innew_issues/resolved_issueswhen it clears the presence bar. Rejected: suppressing the block when no bad actors exist — that would change SQL Server output on a quiet server.Blast radius
BaselinedValueForis its value, the four SQL Server metric arms are unchanged,DispersionMetricsForover a SQL Server set never names apg_metric, and the churn note is the same literal. Proven two ways: the 18 existingComparisonBandingTestspass unchanged, and a throwaway harness serialized the full compare payload (summary, families, rows, churn, band_rules) for a nine-key SQL Server scenario with and without dispersion against the dev DLL and this branch's DLL —cmpidentical for both files.ComparisonBanding; Lite'sBaselineProvideris never asked for apg_metric because Lite has nopg_-sourced facts.LookUpDispersionAsyncissues up to four more bucket reads per compare (each a windowed, parameterised read on the same tables the anomaly pass already reads; not on the alert path). Payload shape unchanged — no new fields.What it does NOT do
PG_WAIT_PROFILE(per-type shares vs an all-typespg_wait_ms_per_secbaseline — the same reason SQL Server wait fractions are unmapped), nor the buffer / write / vacuum / temp / config readings (no bucket exists).band_rules.baselineengine-aware ("delta_sigma is the value delta…"): forPG_CONNECTION_SATURATIONthe sigma is on the session-count reading whilevalue_deltais the fraction. Stated in the code; changing the shared sentence would alter SQL Server output. Noted for the coordinator.PG_TPSa two-sided reading (v3 on PostgreSQL-target analysis engine: v2/v3 slices and the v1 lanes' residue (from #3542) #3691).PgTargetBaselineProvider,MetricNames, or anyPgTarget*source file.Which component(s) does this affect?
PerformanceMonitor.Analysis)How was this tested?
New, executed locally on macOS in a net10.0 harness compiling the real test file against the real
PerformanceMonitor.Analysis(23/23 green: 18 existing + 5 new), first executed by CI on Windows as part ofLite.Tests:ComparisonBandingTests.APostgresTpsSurge_IsBandedByTheServersOwnSigma_NotStableByALadderThatCannotMove— the before picture (no dispersion → absolute → stable,relative_move 0.8333, ladder 0,baseline_metric: pg_tps) and the after (bucket median 10 / MAD 0.6745 = sigma 1.0 → worse, baseline,delta_sigmaat the 25 cap,beyond_anomaly_cutoff); inside one sigma is stable; a fall reads better.…APostgresSaturationFraction_IsBandedOnItsPeakSessionCount_NeverOnTheFractionInCountSigma—BaselinedValueForpicks the count; 20 → 60 sessions of 100 against median 20 / MAD 0 (sigma floors at 0.2) is worse with the fraction invalue_delta; a one-sigma count move is stable; a fact without the metadata takes the absolute rule and still namespg_session_count.…PostgresCpu_IsSigmaBandedOnlyInTheCapacityUnit_AndDeadlocks_OnlyAgainstANonZeroBucket— capacity-measured CPU bands; raw-percent CPU falls back; a zero deadlock bucket has sigma 0 and falls back; a non-zero one bands (5σ);DispersionMetricsFornames exactly the fourpg_metrics for a PostgreSQL set.…TheChurnNote_SpeaksTheEngineOfTheFacts— PostgreSQL set:PostgresTarget, empty churn,PG_BAD_ACTOR_*on the presence path, the PostgreSQL note verbatim; SQL Server set: the SQL Server note verbatim (literal asserted).…ASqlServerFactSet_NeverReachesThePostgresArms— the routing pin: every SQL Server reading is its value, the four SQL Server arms unchanged, nopg_metric requested, churn not PostgreSQL.New, gated (
DARLING_TEST_PG), compile-verified here, first executed in CI'sDarling PostgreSQL tests:CompareAnalysisPgBandingLiveTests.ASixFoldTpsSurge_IsBandedWorseBySigma_AgainstThePgTpsBucket_ThroughTheRealTool— 31 days of one-minutepg_database_statsat ~10 tps, the last 250 minutes at 60, server stampedengine_kind='postgres', the REALDarlingMcpTools.CompareAnalysis(4 h vs 28 h back) →PG_TPSrowworse/baseline/baseline_metric: pg_tps/beyond_anomaly_cutoff/delta_sigma25 /ladder_position0 / no coverage caveat; family row worse; the PostgreSQL churn note.[Collection("live-postgres")], #1902LiveStoreCleanupshape.Census pre-checks executed locally (harness compiling the census classes against this tree):
DocCommentHygieneTests(all but the project-list pin, which saw the harness itself),LiveCleanupConversionRatchetTests,LivePostgresCollectionHygieneTests(the new class is not flagged),RepoFileAdoptionTests,PgTargetBetweenWavesTests— green.Builds (zero new warnings):
dotnet build PerformanceMonitor.Analysis -c Debug0/0;Lite.Tests -p:EnableWindowsTargeting=true1 warning = dev's pre-existingxUnit2000inLogTailOverlapThresholdPinTests.cs:95(verified identical on a stash of this branch's changes);Darling/Darling.Tests0/0;deprecated/Dashboard.Tests0/0.Checklist
dotnet build -c Debug)