|
38 | 38 |
|
39 | 39 | from messagefoundry import __version__ |
40 | 40 | from messagefoundry.store.pool_metrics import PoolStatus |
41 | | -from messagefoundry.store.store import DestinationMetrics, InboundMetrics, LatencyHistogram |
| 41 | +from messagefoundry.store.store import ( |
| 42 | + ClaimProcStatus, |
| 43 | + DestinationMetrics, |
| 44 | + InboundMetrics, |
| 45 | + LatencyHistogram, |
| 46 | +) |
42 | 47 |
|
43 | 48 | if TYPE_CHECKING: # avoid pulling the heavy engine import into the default path |
44 | 49 | from messagefoundry.pipeline import Engine |
@@ -192,6 +197,10 @@ class _Snapshot: |
192 | 197 | pool: PoolStatus | None = None |
193 | 198 | committed_txns: int = 0 |
194 | 199 | body_copies: int = 0 |
| 200 | + # ADR 0114 AC-7's degraded gauge. None when the backend has no fifo_claim_proc lever or the flag |
| 201 | + # is off — the gauges are then ABSENT rather than 0, so a scrape can tell "not requested" from |
| 202 | + # "requested and degraded" (a constant 0 on every SQLite fleet would be pure alert noise). |
| 203 | + claim_proc: ClaimProcStatus | None = None |
195 | 204 |
|
196 | 205 |
|
197 | 206 | async def gather_snapshot(engine: Engine) -> _Snapshot: |
@@ -230,6 +239,7 @@ async def gather_snapshot(engine: Engine) -> _Snapshot: |
230 | 239 | pool=pool, |
231 | 240 | committed_txns=committed_txns, |
232 | 241 | body_copies=body_copies, |
| 242 | + claim_proc=engine.store.claim_proc_status(), |
233 | 243 | ) |
234 | 244 |
|
235 | 245 |
|
@@ -369,6 +379,33 @@ def collect(self) -> Iterable[Any]: |
369 | 379 | body_copies.add_metric([], float(s.body_copies)) |
370 | 380 | yield body_copies |
371 | 381 |
|
| 382 | + # ADR 0114 AC-7 degraded gauge. Emitted ONLY when [store].fifo_claim_proc is on: a constant |
| 383 | + # 0 on every fleet that never asked for the lever is noise a scraper cannot alert on, and |
| 384 | + # absence is the honest encoding of "not applicable here". Numeric and LABEL-LESS by |
| 385 | + # design — the human-readable degrade reason is free text (it embeds a proc name and, on the |
| 386 | + # probe-failure arm, an exception string), so carrying it as a label would both blow the |
| 387 | + # cardinality budget and break this module's strict {connection,destination,status,version,le} |
| 388 | + # allowlist. The reason string lives on /status and the console store panel instead. |
| 389 | + cp = s.claim_proc |
| 390 | + if cp is not None: |
| 391 | + effective = GaugeMetricFamily( |
| 392 | + "messagefoundry_store_claim_proc_effective", |
| 393 | + "1 when the ADR 0114 stored-procedure claim path passed its startup gate and is" |
| 394 | + " active, 0 when it degraded to the shipped ad-hoc batch (claims still flow).", |
| 395 | + ) |
| 396 | + effective.add_metric([], 1.0 if cp.effective else 0.0) |
| 397 | + yield effective |
| 398 | + # Which stored head form the deployed modules matched. "verbatim" means this server did |
| 399 | + # NOT rewrite the CREATE OR ALTER head — no engine measured to date does, so a fleet |
| 400 | + # reporting 1 here is a live counterexample worth knowing about, not a fault. |
| 401 | + verbatim = GaugeMetricFamily( |
| 402 | + "messagefoundry_store_claim_proc_head_verbatim", |
| 403 | + "1 when at least one deployed claim procedure's stored definition kept the CREATE" |
| 404 | + " OR ALTER head verbatim (this server does not rewrite it), else 0.", |
| 405 | + ) |
| 406 | + verbatim.add_metric([], 1.0 if "verbatim" in cp.head_forms.values() else 0.0) |
| 407 | + yield verbatim |
| 408 | + |
372 | 409 | # Connection-pool saturation + acquire-wait (server backends only; absent on SQLite, which has |
373 | 410 | # no pool). [store].pool_size previously emitted NO saturation metric — these close that gap. |
374 | 411 | pool = s.pool |
|
0 commit comments