plecto_control/control_observability.rs
1//! `Control`'s observability surface (ADR 000009 Stage A): the host-aggregated filter-execution
2//! metrics snapshot and the operator-configured admin/access-log settings the fast path reads.
3
4use std::sync::Arc;
5
6use plecto_host::MetricsSnapshot;
7use plecto_host::otlp::OtlpBuffer;
8
9use crate::Control;
10
11/// Point-in-time residency of the host's trusted pooling allocator (wasmtime
12/// `PoolingAllocatorMetrics`), lowered to plain counters so the fast path renders them on
13/// `/metrics` without naming wasmtime types.
14#[derive(Debug, Clone, Copy)]
15pub struct PoolResidency {
16 /// Live pooled component instances.
17 pub component_instances: u64,
18 /// Live pooled linear memories.
19 pub memories: usize,
20 /// Bytes kept resident for unused-but-warm pool slots (`linear_memory_keep_resident`;
21 /// left at its default 0 by the host, so this is expected to read ~0).
22 pub unused_memory_bytes_resident: usize,
23}
24
25impl Control {
26 /// A snapshot of the host-aggregated filter-execution metrics (ADR 000009): the tally the
27 /// `MetricsSink` wired at construction has accumulated. The fast path's admin `/metrics`
28 /// endpoint renders this alongside its native RED metrics.
29 pub fn filter_metrics(&self) -> MetricsSnapshot {
30 self.filter_metrics.snapshot()
31 }
32
33 /// Residency of the trusted (pooling) engine, for the admin `/metrics` endpoint. `None` when
34 /// the trusted engine is not pooling (it always is today, so callers can expect `Some`).
35 pub fn pool_residency(&self) -> Option<PoolResidency> {
36 self.host
37 .pooling_allocator_metrics()
38 .map(|m| PoolResidency {
39 component_instances: m.component_instances(),
40 memories: m.memories(),
41 unused_memory_bytes_resident: m.unused_memory_bytes_resident(),
42 })
43 }
44
45 /// The admin endpoint bind address (`[observability] admin_addr`), or `None` when no admin
46 /// listener is configured (the default). The fast path binds a separate listener there for
47 /// `/metrics` + liveness/readiness (ADR 000009 Stage A).
48 pub fn admin_addr(&self) -> Option<&str> {
49 self.observability.admin_addr.as_deref()
50 }
51
52 /// Whether the structured access log is enabled (`[observability] access_log`, ADR 000009).
53 pub fn access_log_enabled(&self) -> bool {
54 self.observability.access_log
55 }
56
57 /// The OTLP/HTTP collector base URL (`[observability] otlp_endpoint`, ADR 000040), or `None`
58 /// when trace export is off (the default). The exporter appends `/v1/traces`.
59 pub fn otlp_endpoint(&self) -> Option<&str> {
60 self.observability.otlp_endpoint.as_deref()
61 }
62
63 /// The OTLP span buffer (ADR 000040): filter spans fan into it from the host sink, the fast
64 /// path pushes its request span, and the export pump drains it. Present iff
65 /// [`otlp_endpoint`](Self::otlp_endpoint) is set.
66 pub fn otlp_buffer(&self) -> Option<Arc<OtlpBuffer>> {
67 self.otlp.clone()
68 }
69
70 /// The manifest's data-plane bind address (`[listen] addr`), or `None` for the binary default.
71 /// Captured at construction, like `admin_addr` — a reload does not re-bind; the CLI's explicit
72 /// positional arg overrides it.
73 pub fn listen_addr(&self) -> Option<&str> {
74 self.listen.addr.as_deref()
75 }
76
77 /// The `Alt-Svc` h3 advertisement port override (`[listen] advertised_port`), or `None` to
78 /// advertise the bound port. For container port mappings where the published port differs
79 /// from the bound one (moka-1 field report §3.4).
80 pub fn advertised_port(&self) -> Option<u16> {
81 self.listen.advertised_port
82 }
83
84 /// The parsed `[listen.proxy_protocol]` trusted networks (ADR 000057), or `None` when PROXY
85 /// v2 reception is off (the default). Captured at construction like `listen_addr` — the TCP
86 /// listener consults it once at startup; a reload does not change it. The h3/UDP listener is
87 /// out of scope (ADR 000057 decision 5).
88 pub fn proxy_protocol_trust(&self) -> Option<crate::manifest::ProxyProtocolTrust> {
89 self.proxy_protocol.clone()
90 }
91
92 /// The readiness grace (`[listen.drain] readiness_grace_ms`, ADR 000059): how long `/readyz`
93 /// reports not-ready — while connections are still accepted — before the drain starts, so a
94 /// front load balancer can take the replica out of rotation first. Zero (the default) starts
95 /// the drain immediately. Captured at construction like the rest of `[listen]`.
96 pub fn readiness_grace(&self) -> std::time::Duration {
97 let ms = self
98 .listen
99 .drain
100 .as_ref()
101 .and_then(|d| d.readiness_grace_ms)
102 .unwrap_or(0);
103 std::time::Duration::from_millis(ms)
104 }
105
106 /// The drain window (`[listen.drain] window_ms`, ADR 000059): how long in-flight work may
107 /// finish at shutdown before remaining connections are cut. `None` = the server's default
108 /// (30 s). One setting shared by every drain path (TCP requests, h3 GOAWAY, tunnels).
109 pub fn drain_window(&self) -> Option<std::time::Duration> {
110 self.listen
111 .drain
112 .as_ref()
113 .and_then(|d| d.window_ms)
114 .map(std::time::Duration::from_millis)
115 }
116}