Decision records
ADR-0015: Deterministic diagnostics
- Status: Accepted (2026-10-05)
Context
Handoff §10/M7: diagnostics with root-cause correlation views; eBPF is optional
enrichment for process, network and block-I/O correlation and basic diagnosis must never
depend on it. Diagnostic enrichment happens off the hot path. scalwsctl diagnose is part of
the CLI surface (handoff §9).
Decision
scalws-diagis pure, likescalws-policy: it maps one evidence snapshot per application to ordered findings. No I/O, no clock, no AI.- Evidence per window (collected by the server’s analysis task, which also runs the
optimizer):
- request telemetry: requests, 5xx, latency to response headers (p50/p95/p99),
upstream failures by reason (
connect,timeout,unavailable, other), tenant quota rejections, cache hit ratio; - runtime: lifecycle state, ready/target workers, restarts in the window, utilisation;
- cgroup v2 (when enforced): memory usage and limit, OOM kills in the window, pressure
stall information
some avg10for memory, CPU and I/O (*.pressure), CPU throttling.
- request telemetry: requests, 5xx, latency to response headers (p50/p95/p99),
upstream failures by reason (
- Phases: scalws does not queue requests (quotas reject, they do not wait), so the phases are time to response headers (routing + handler + upstream until headers) and transfer (body until finished). Upstream connect time inside pooled clients is not measured separately. TLS handshake duration is a metric per listener.
- Findings carry a stable
code,severity(critical,warning,info), a summary, evidence lines and a suggestion. Resource causes (crash_loop,runtime_down,oom_kill,memory_pressure,cpu_pressure,io_pressure) are linked to symptom findings in the same window (upstream_timeouts,upstream_unreachable,app_errors,saturated,slow) asrelatedcauses — correlation, stated as such, not proof. Thresholds are constants inscalws-diag. - Recent requests: a bounded ring buffer per application keeps the last
server.diagnostics.recent_requests(default 50) requests that failed with 5xx or took longer thanserver.diagnostics.slow_request(default 1 s): time, method, path without query, status, handler, failure reason, cache result, time to headers, total time, bytes, completion. - Exposure:
GET /diagnose(all applications) andGET /diagnose?app=tenant/appon the loopback admin listener;scalwsctl diagnose [tenant/app]; findings counted inscalws_diagnostic_findings_total{tenant,app,code}when they first appear. - eBPF and OpenTelemetry trace export are not part of this stage. Both stay optional later additions; every finding above is produced from cgroup files, process state and in-process counters.
Consequences
- Findings lag by up to one window (
server.optimizer.interval). - The ring buffer holds request paths (no query strings, no headers), like the access log.