Skip to content

Recs Stage B (severity): cap tuning-class CXPACKET, escalate meltdowns through THREADPOOL - #1494

Merged
erikdarlingdata merged 2 commits into
devfrom
feature/recs-remainder-stage-b-severity
Jul 12, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
feature/recs-remainder-stage-b-severity

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Stage B change 4 — the severity half of the recs-engine remainder (the dedup half shipped in #1493; Stage A in #1491). All on the shared FactScorer, so Lite, Dashboard, and Darling change identically. Blocking/deadlock detection is deliberately left on its current raw-count behavior.

What changed

A. Tuning-class severity cap (FactScorer.ScoreAll, new Layer 3). CXPACKET saturates its base severity to 1.0 at 25%-of-period, then its corroborating amplifiers (default CTFP, MAXDOP 0, high-DOP queries) multiplied it past the shared >= 1.5 CRITICAL band on amplifier COUNT alone — so a pure parallelism-tuning situation e-mailed as CRITICAL. A final pass now caps the tuning-class keys (CXPACKET / CXCONSUMER / QUERY_HIGH_DOP + every ANOMALY_*) at the 1.49 WARNING ceiling, with an escape hatch: released the moment an impact-bearing peer co-fires (THREADPOOL, SOS_SCHEDULER_YIELD, or RESOURCE_SEMAPHORE), because then the parallelism genuinely IS driving an outage. Caps the numeric Severity only (the band is derived downstream) — no SeverityBand fork, parity preserved. Reality-check per the audit: CXPACKET is the only key with teeth today (ANOMALY_* + QUERY_HIGH_DOP already max at 1.0); the rest is forward-safety.

D. Impact escalation. So a genuine parallelism → worker-exhaustion meltdown still reaches CRITICAL, THREADPOOL (an impact key, never capped) gets a heavier CXPACKET amplifier (+0.2 → +0.5) plus a new runnable-queue amplifier (+0.5). The runnable-queue signal is a RUNNABLE_TASKS context fact read from the EXISTING cpu_scheduler_stats snapshot's runnable_tasks_warning flag (the collector's own SUM(runnable_tasks_count) >= cpu_count heuristic) — no new collector, table, or migration. The fact is surfaced by the Lite + Darling fact collectors (Source cpu, carried into the amplifier lookup but never scored/rooted). The deprecated Dashboard's fact collection is left untouched — the amplifier simply no-ops there, and the cap + raised CXPACKET amplifier still escalate a Dashboard meltdown to CRITICAL through THREADPOOL. (cpu_scheduler_stats isn't collected on Azure SQL DB either, so the fact is absent and the amplifier no-ops there too.)

B. Confidence-hardening. The Stage-A low-quality anomaly fallback now floors at 0.5 after the confidence multiply (Math.Max(0.5, (…) * confidence)) instead of before — behaviorally identical today (confidence is a hardcoded 1.0 everywhere) but robust to a future sub-1.0 confidence that would otherwise silently drop a fired thin-baseline finding below InferenceEngine's 0.5 root entry-point. Trustworthy z-path untouched.

Runnable-tasks fact-flow

Both DuckDbFactCollector (Lite) and PgFactCollector (Darling) read the latest cpu_scheduler_stats row (total_runnable_tasks_count, runnable_tasks_warning) with byte-identical SQL and emit one RUNNABLE_TASKS fact carrying runnable_tasks_warning in metadata; the THREADPOOL runnable-queue amplifier predicates on that flag.

Tests

New FactScorerTests in both Lite.Tests and Dashboard.Tests: CXPACKET alone caps to WARNING; CXPACKET + THREADPOOL releases the cap into CRITICAL; THREADPOOL reaches CRITICAL via the raised amplifier + the runnable-queue amplifier; the runnable-queue amplifier does not fire when the warning is clear; the confidence floor holds at 0.5 under a hypothetical sub-1.0 confidence.

Full suites green: Lite.Tests 1087 passed, Dashboard.Tests 728 passed, Darling.Tests 2066 passed / 133 skipped (live-PG), 0 failures.

🤖 Generated with Claude Code

erikdarlingdata and others added 2 commits July 12, 2026 08:35
…tdowns through THREADPOOL

Stage B change 4 -- the severity half -- on the shared FactScorer, applying
identically to Lite, Dashboard, and Darling.

- Cap the FINAL severity of tuning-class keys (CXPACKET/CXCONSUMER/QUERY_HIGH_DOP
  + all ANOMALY_*) at the 1.49 WARNING ceiling, released when an impact-bearing
  peer co-fires (THREADPOOL / SOS_SCHEDULER_YIELD / RESOURCE_SEMAPHORE). Caps the
  numeric Severity only; the band is derived downstream.
- Raise THREADPOOL's CXPACKET amplifier +0.2 -> +0.5 and add a runnable-queue
  amplifier (+0.5) so a genuine parallelism -> thread-exhaustion meltdown reaches
  CRITICAL through THREADPOOL (an impact key, never capped).
- Feed the runnable-queue amplifier from the EXISTING cpu_scheduler_stats
  snapshot's runnable_tasks_warning (SUM(runnable_tasks_count) >= cpu_count) via a
  RUNNABLE_TASKS context fact in the Lite + Darling fact collectors -- no new
  collector/table/migration. Dashboard fact collection untouched (amplifier no-ops).
- Harden the Stage-A low-quality anomaly fallback to floor at 0.5 AFTER the
  confidence multiply (robust to a future sub-1.0 confidence).

Tests: new FactScorerTests in Lite.Tests + Dashboard.Tests. Full suites green
(Lite 1087, Dashboard 728, Darling 2066 + 133 live-PG skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CRITICAL: the CXPACKET tuning-class severity cap's escape hatch released
on sosPeer.BaseSeverity > 0 / rsPeer.BaseSeverity > 0 -- a near-zero bar.
SOS_SCHEDULER_YIELD (0.75, null) and RESOURCE_SEMAPHORE (0.01, null) have
no minimum-total guard in ScoreWaitFact, so BaseSeverity > 0 means only
"the wait is present at all." SOS physically co-occurs with high CXPACKET
(parallel workers yield -> SOS) and is emitted for any delta_wait_time_ms
> 0, so a trivial SOS (e.g. 500ms/hr) released the cap on exactly the busy
servers the cap targets, re-admitting the CXPACKET=CRITICAL noise the cap
exists to kill. Gate SOS/RS on significance via the same HasSignificantWait
helper the amplifiers use (SOS 0.25 = the CXPACKET SOS amplifier bar; RS
0.10 -- no amplifier bar exists, 10% of period is meaningful grant
starvation). THREADPOOL keeps its presence check: its base is self-gating
(>= 1hr total AND >= 1s avg), so BaseSeverity > 0 already means exhaustion.
Corrected the now-false "belt-and-suspenders" comment.

Regression tests added to BOTH Lite.Tests and Dashboard.Tests
FactScorerTests: a trivial SOS or RESOURCE_SEMAPHORE below the bar leaves
CXPACKET capped at 1.49 WARNING; a significant one releases it into
CRITICAL. Verified failing against the old presence-only predicate.

MINOR: the RUNNABLE_TASKS context-fact SQL (RunnableTaskStatsSql in Darling
PgFactCollector.Resources.cs and the identical inline SQL in Lite
DuckDbFactCollector.Resources.cs) filtered only collection_time <= end with
ORDER BY collection_time DESC LIMIT 1. Add the lower bound (>= start,
matching sibling CpuUtilizationSql) so a lapsed collection can't read an
arbitrarily old snapshot from outside the analysis window. Kept the
point-in-time LATEST snapshot rather than a MAX window-aggregate because
runnable_tasks_warning is BOOLEAN and Postgres has no max(boolean)
aggregate, so a byte-identical MAX() across the DuckDB and Postgres
providers is not possible without a cast. SQL kept byte-identical between
both providers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@erikdarlingdata
erikdarlingdata merged commit d0dab99 into dev Jul 12, 2026
2 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/recs-remainder-stage-b-severity branch July 12, 2026 13:32
pull Bot pushed a commit to ehtick/PerformanceMonitor that referenced this pull request Jul 22, 2026
…schedule presets

The viewer's Aggressive/Balanced/Low-Impact preset tables
(CollectorSchedulePresets.Presets) were missing these three collectors in all
three presets, so applying a preset in the Darling schedule editor left them at
their code-default cadence instead of the preset's -- a Lite<->Darling drift
flagged in the erikdarlingdata#1494 review. Each preset now carries the three at Lite's exact
interval (Aggressive 2, Balanced 5, Low-Impact 15 min); retention is unchanged
(it lives in the shared CollectorScheduleDefaults, which already carried all
three, so only the preset-apply path had drifted).

The class docstring promised a Darling.Tests pin against Lite's intervals, but
no membership pin existed -- Presets_OnlyReferenceKnownCollectors only checks the
reverse direction (no unknown collectors), which is why the gap went uncaught.
Add Presets_CoverTheSamePinnedCollectorSet, mirroring Lite's own
SchedulePresets_CoverTheSamePinnedCollectorSet, pinning the full 27-collector
membership of every preset so this drift class fails a test next time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pull Bot pushed a commit to ehtick/PerformanceMonitor that referenced this pull request Jul 22, 2026
Restore three classifications the deprecated Dashboard's report.* /
config.critical_issues SQL made but the ported shared analysis engine had
dropped. All three are pure FactScorer additions over facts the collectors
already emit (no collector/schema/install change), so they light up in Lite,
the Dashboard, and Darling recommendations at once; each cites its source
SQL line in a code comment.

- Query Store disabled: INFO advisory folded into DB_CONFIG, detected as
  query_store_on_count < database_count (install/50:83).
- Security-cache (TokenAndPermUserStore) growth: flat WARNING when the
  USERSTORE_TOKENPERM clerk >= 1 GB, off the context-only MEMORY_CLERKS fact
  (install/50:562,583).
- Runnable-queue / worker pressure: standalone ScoreCpuFact arm on
  total_runnable_tasks_count tiered > 50/20/10 plus the small-box
  runnable_tasks_warning fallback (install/47:1839-1844); additive to and
  distinct from the erikdarlingdata#1494 THREADPOOL amplifier, which is unchanged.

Advice wired for the new/expanded root keys (MEMORY_CLERKS + RUNNABLE_TASKS
blocks; Query Store folded into ComposeDbConfig) so the cards are not
contentless, sourced from the Dashboard's own message/recommendation text.

FactScorer tests added in both Lite.Tests and Dashboard.Tests (tiers fire at
the right band, stay silent below the bar, existing arms unchanged). The Lite
clean-server seeder now defaults Query Store ON (the healthy value) so the
QS-off arm stays dormant in fixtures not exercising it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant