Repository navigation
The four PostgreSQL host alerts fire with the tier they earned instead of Severity: null, so their history rows stop wearing the colour their name implies (#3653 A8e, PostgreSQL half) - #3680
Merged
Conversation
…d of Severity: null, so their history rows stop wearing the colour their name implies (#3653 A8e, PostgreSQL half) EvaluatePgCpuAsync grades on ServerHealthClassifier.CpuSeverity — the card's own classifier on the card's own two inputs (raw PI CPU + ACU utilization, Performance Insights arm) — so Critical lands at the band's 95%-of-configured-ceiling bar and the figure graded is the figure the gate thresholded, by construction. EvaluatePgDeadlocksAsync grades on AlertEngine.GradeDeadlockFire over the same one-hour count the fire quotes, against DarlingAlertSettings.DeadlockRateThresholds (the V120 store tiers the PostgreSQL fleet card bands on since #3638), never the shipped Default. EvaluatePgBlockingAsync / EvaluatePgLongRunningQueryAsync fire an explicit Warning and only Warning — the constants' docs say which bars were considered and why none is a measured Critical tier. The tier rides the outcome only, like every graded fire in this file; the host's one deliverer folds it into the persisted context (#2090), which is what #3635's grids and get_alert_history read. Pre-change rows keep the by-name fallback. Tests: the pure graders at every boundary, the store-tier read, a per-arm source pin that no arm still passes Severity: null, and one drive of the real DarlingAlertDeliverer proving a PG-shaped fire persists its tier and AlertHistoryRowSeverity reads it as fired.
Closed
39 tasks done
…nswers: the PostgreSQL High CPU alert is the third CPU-band decider under Darling/ (a decider, not a provenance caller — its source is Performance Insights by construction), and the new per-arm fire-site pin anchors on a line break, so it reads LF-normalised text FleetCardPostgresCpuTests: the decider census now expects DarlingWorker.cs beside the two cards; the caller census (TotalNonIdleCpuPercent + ClassifyCpuSource) stays two, and the comment says why the alert cannot be a caller. Renamed TheOnlyTwoSurfaces... → TheOnlySurfacesThatBandCpu_AllReachTheSharedDecision (cref in PgCpuCapacityHeadroomTests updated). RepoFileAdoptionTests: DarlingPgOperationalAlertTests.cs joins s_lfReaders.
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?
The PostgreSQL half of #3653's A8e row: the four host alert arms in
DarlingWorker— High CPU, Deadlocks Detected, Blocking Detected, Long-Running Query — fired withSeverity: null, so once #3635 taught the history grids,get_alert_historyand every channel to render the tier a row fired at, these four had nothing to render and fell back to the colour their NAME implies, while their SQL Server twins have carried the tier they fired at since #3660. Every PostgreSQL Deadlocks Detected row was red (one deadlock and a storm alike); every PostgreSQL High CPU row was amber (a 100%-of-ceiling fire indistinguishable from an 80% one while the fleet card beside it was red); Blocking and Long-Running Query rows were amber by implication rather than by statement. Partial for #3653 — ticks the "PG host arms still fireSeverity: null" item; references #3653, does not close it.Mechanism
DarlingAlertDeliverer.SendAndRecordAsyncfoldsAlertOutcome.SeverityintoContext.SeverityOverridebefore the row is written (#2090), and since #3635 the serializer carries that as the row'sSeveritymember — so a tier passed on the outcome at the fire site is the tier the grids read. These four arms passednull. Nothing downstream was wrong; the fire sites simply said nothing.What changed, per arm — existing measured bars only, no new constants
High CPU →
GradePgCpuFire(cpuPercent, acuUtilizationPercent), new, pure,internal staticbeside the arm. It isServerHealthClassifier.CpuSeverity— the fleet card's classifier — called on the card's own two inputs (raw Performance Insights CPU + ACU utilization) on thePerformanceInsightsarm, mapped Critical→Critical, everything else→Warning. Not a copy of the ladder: the classifier runsFleetCpuProvenance.CpuBandInputPercentitself to pick the capacity percent (#3281), so the figure graded is the figure the gate thresholded, by construction. Critical lands atServerHealthThresholds.CpuCriticalPercent(95% of the configured ACU ceiling, never percent-of-allocated). The doc says what that bar is and is not, as #3660'sGradeCpuFiredoes for SQL Server: the one ladder every card surface bands on (#3539 A2), stated against a quantity, not a fleet percentile — the claim is that the row and the card must not disagree about the same minute. Unknown (no capacity reading) cannot reach the fire site (breachingrequires it) and maps to Warning rather than throwing.Deadlocks Detected →
AlertEngine.GradeDeadlockFire(count, rateTiers)— #3660's grader, the same count over the same window, against the same store-tunable pair. The PG arm's window isAlertEngine.RollingCountWindowHours(one hour), the constant the grader divides by, so the count IS the rate: Warning through 19/hr, Critical atdeadlock_critical_per_hour(shipped 20/hr, #3368's measured tier).rateTiersisDarlingAlertSettings.DeadlockRateThresholds— the V120 knobs the PostgreSQL fleet card bands the same server on since #3638 — read once beside the count threshold (#3444's idiom), neverDeadlockRateThresholds.Default, so an operator who raised the card's Critical to 31 sees the alert grade Warning at 25.Why the alert's own count and not the card's counter. The brief asked me to reuse the differenced
pg_stat_database.deadlocksrate #3638 computes for the fleet band. I did not, and the code says why: that rate lives in the fleet reader's own SQL (FleetPgDeadlockSql, run on demand forget_fleet_overview) and is not in hand in the alert pass — reusing it would be one more alert-path store read per PostgreSQL server per sweep for a number the row cannot display. The row quotes the dedupedpg_deadlocksreport count; grading it on a different number would be a second card/alert disagreement in place of the one this ends. The tiers are shared; the instrument is the alert's own, exactly as #3660's SQL Server arm grades its own graph count. One stated edge: the read caps at 50 distinct reports, so the grade saturates there — a Critical tier raised past 50/hr is unreachable on this host (shipped 20 is well under; the message quotes the same capped count, so grade and text agree). README states it.Blocking Detected →
PgBlockingFireSeverity = Warning; Long-Running Query →PgLongRunningQueryFireSeverity = Warning— explicit, and ONLY Warning, the #3660 tempdb shape. No measured bar exists for either and the constants' docs name what was considered: the SQL Server blocking band's tiers (#3596) were measured in reports-per-server-hour on engine-recorded evidence, while this alert counts distinct roots seen in a once-a-minutepg_stat_activitysample — the denominator mismatch that keeps the PG blocking band Unknown-with-reason; the sampled-shape distribution an honest tier needs has not been taken (#3653's #3539 residue names it, and #3601'slock_waitevents as the source). LRQ has only the operator's threshold, a fire bar; "N× the threshold" would be a folklore multiplier, and the measured evidence that exists (the SQL Server twin's population, #3653 A5: 191 distinct ≥30-min sessions in 7 days, permanent background) argues a duration tier would grade the background as the emergency. What changes is that the row now says Warning; when a measured bar exists it lands as a second tier with nothing to unpick.The tier rides the outcome only —
Severity: grade— matching every graded fire already in this file (the Tier-0 trio and Poison Wait passfinding.Severitythe same way). The SQL engine sets both context and outcome because it serves two SKUs' deliverers; this host serves one, whose fold is the documented contract. The CPU arm keepsContext: null(the deliverer creates one to hold the tier, as it does for every self-alert).What it does NOT do
CHANGELOG.mdedit (entry handed to the coordinator).DarlingWorker(PostgreSQL-target analysis engine: design and v1 build #3542's lane),PgTarget*,DarlingAnalysisService, the PostgresAlertEvaluator's Tier-0 trio,AlertEngine.cs,AlertSeverity.ForMetric's by-name arms (pre-change PG rows keep the faithful replay: red for deadlocks, amber for the rest), orAlertMetricClassifier.blockingContext?.SeverityOverride/lrqContext?.SeverityOverride, always null) — noted below as an out-of-lane finding for the coordinator, not this PR's file.AlertReadFailureSurfaceTestscensus: no catch block added or removed; no new await inside any try block.FleetCardPostgresCpuTests' CPU-decider census now expectsDarlingWorker.csas the third file underDarling/that callsServerHealthClassifier.CpuSeverity(— a decider that reaches the shared decision is exactly what the census exists to force — while its caller census (TotalNonIdleCpuPercent+ClassifyCpuSource) stays at the two cards, with the comment saying why the alert cannot be one (its source isPerformanceInsightsby construction; there is no second collector to classify). The test is renamedTheOnlySurfacesThatBandCpu_AllReachTheSharedDecision(one cref updated).RepoFileAdoptionTests' LF-reader census gainsDarlingPgOperationalAlertTests.cs, because the fire-site pin anchorsSeverity: null,on the line break that follows it.Blast radius
"Severity":"Warning"|"Critical"incontext_json;get_alert_historyreportsseverity_source: firedfor them. Visible colour changes: PG Deadlocks Detected rows at 1–19/hr go red→amber; PG High CPU rows at ≥95% of ceiling go amber→red. Blocking / LRQ stay amber (sourcemetric_name→fired); the Blocking channel badge readsWARNINGinstead of the by-nameALERT.EvaluatePgCpuAsync/EvaluatePgBlockingAsync"to the nextprivatemember" (PgCpuCapacityHeadroomTests,BuiltinAlertPersistenceRungTests,DarlingPgBlockingReaderTests); the newinternalhelpers are placed BEFORE their arm's doc comment so those slices still cover exactly one method. Verified by running the slicer verbatim (below).Tests
Darling.Tests/DarlingPgOperationalAlertTests(the file that already holds these arms' pure seams), new section:GradePgCpuFire_IsTheCardsClassifierOnTheCapacityPercent_CriticalAtTheBandsBar— theory: (100,80)/(100,94.9)→Warning, (100,95)/(100,100)→Critical, and the raw CPU is NOT the input: (100,30)→Warning, (20,96)→Critical; each row cross-checked againstServerHealthClassifier.CpuSeverity.GradePgCpuFire_WithNoCapacityReading_IsWarning_NeverGradedOnTheRawCpu.PgDeadlockFire_IsTheSharedGrader_OnTheStoresTiers— 1/5/19→Warning, 20/50→Critical on Default;DarlingAlertSettings(config).DeadlockRateThresholdsreads (7,31) → 25 Warning, 31 Critical;RollingCountWindowHours == 1.PgBlockingAndLongRunningQuery_FireAnExplicitWarning_AndNeverCritical.PgHostAlertArms_PassTheirGradeAtTheFireSite_NoneFiresSeverityNull— per-arm source slice: the expectedSeverity: <grade>,present,Severity: null,\nabsent,DeadlockRateThresholds.Defaultabsent from the deadlock arm; positive control that the exact code spelling still exists inAlertEngine.cs; the deliverer'scontext.SeverityOverride ??= outcome.Severity;fold pinned.APgHostFire_PersistsTheTierItFiredAt_SoTheRowRendersItInsteadOfTheName— drives the realDarlingAlertDeliverer(no channels configured,CapturingHistoryStore) with a deadlock-shaped fire (Incidents-only context, Warning) and a CPU-shaped fire (null context, Critical):AlertHistoryRowSeverity.Describereads("warning","fired")/("critical","fired"),IsCriticalflips against the by-name answer, the three incidents survive beside the tier, and a pre-Brains-review campaign: deferred structural residue (from #3538 / #3539 / #3540 / #3541) #3653 JSON of either name still readsmetric_name.Executed on this machine (both censuses included, the CPU one as a verbatim probe because its file references the WPF Viewer — 94 tests, 0 failed after the second commit) via the mactest pattern (net10.0 harness,
AssemblyName=Darling.Tests, project-referencing the built Service + Storage): the whole class (30 tests), plusAlertMasterSwitchSurfaceTests,AlertReadFailureSurfaceTests,DarlingPgBlockingReaderTestsand a verbatim copy of the two Viewer-referencing classes'EvaluatePgCpuBody()pins — 91 tests, 0 failed, 1 skipped (live PG). Mutation control: reverting the blocking arm toSeverity: nullfails the source pin (Assert.Contains() Failure: Sub-string not found).Builds, 0 warnings,
-c Release -p:EnableWindowsTargeting=true:PerformanceMonitor.Darling.Service,Darling.Tests(net10.0-windows). CI is the first execution undernet10.0-windows.Which component(s) does this affect?
How was this tested?
See Tests. Pure grading of values the arms already had; the one integration seam (deliverer → persisted row →
AlertHistoryRowSeverity) is driven end-to-end against the real deliverer. No SQL Server or PostgreSQL store was touched — no read changed.Checklist
dotnet build -c Debug)References #3653.