Skip to main content

sozu_lib/metrics/
names.rs

1//! Canonical metric-name constants.
2//!
3//! Every metric string emitted by Sōzu and consumed by the StatsD/Prometheus/
4//! TUI surface should reference a constant declared here rather than being
5//! repeated as a literal. The constants intentionally preserve the dotted
6//! string values byte-for-byte so dashboards and scrapers stay valid.
7//!
8//! Layout: one submodule per metric family (`http`, `h2`, `tcp`, `tls`, …),
9//! constants inside are `UPPER_SNAKE_CASE` derived from the dotted suffix.
10//! E.g. `h2.frames.tx.data` lives at [`h2::FRAMES_TX_DATA`].
11//!
12//! When adding a new metric:
13//! 1. Add the constant in the matching submodule (create one if needed).
14//! 2. Reference the constant from the emission site
15//!    (`incr!(names::h2::FRAMES_TX_DATA)`) instead of repeating the literal.
16//! 3. If the TUI or any scraper reads this metric, reference the same
17//!    constant on the read side too.
18
19/// Accept-queue counters and gauges, fed by the worker accept loop in
20/// `lib/src/server.rs`.
21pub mod accept_queue {
22    pub const BACKPRESSURE: &str = "accept_queue.backpressure";
23    pub const CONNECTIONS: &str = "accept_queue.connections";
24    pub const SATURATED_SECONDS: &str = "accept_queue.saturated_seconds";
25    pub const TIMEOUT: &str = "accept_queue.timeout";
26    pub const WAIT_TIME: &str = "accept_queue.wait_time";
27}
28
29/// Access-log infrastructure counters.
30pub mod access_logs {
31    pub const COUNT: &str = "access_logs.count";
32    pub const UNSENT: &str = "unsent-access-logs";
33}
34
35/// Per-backend bandwidth + connection counters emitted by both H1 and the
36/// H2 mux. Front-bytes and back-bytes are accounted separately so dashboards
37/// can compare upstream vs downstream traffic.
38pub mod backend {
39    pub const BYTES_IN: &str = "bytes_in";
40    pub const BYTES_OUT: &str = "bytes_out";
41    pub const BACK_BYTES_IN: &str = "back_bytes_in";
42    pub const BACK_BYTES_OUT: &str = "back_bytes_out";
43    pub const AVAILABLE: &str = "backend.available";
44    pub const CONNECTIONS: &str = "backend.connections";
45    pub const FLOW_CONTROL_PAUSED: &str = "backend.flow_control.paused";
46    pub const POOL_HIT: &str = "backend.pool.hit";
47    pub const POOL_MISS: &str = "backend.pool.miss";
48    pub const POOL_SIZE: &str = "backend.pool.size";
49    pub const CONNECTIONS_PER_BACKEND: &str = "connections_per_backend";
50    pub const CONNECTION_TIME: &str = "backend_connection_time";
51    /// Backend time-to-first-header-byte: established connection -> response
52    /// headers parsed. nginx's `$upstream_header_time` (sozu-proxy/sozu#426).
53    /// L7 only — raw TCP has no headers and emits nothing under this key.
54    pub const HEADER_TIME: &str = "backend_header_time";
55    pub const RESPONSE_TIME: &str = "backend_response_time";
56    pub const REQUESTS: &str = "requests";
57    pub const FAIL_OPEN: &str = "backends.fail_open";
58    /// Shuffle sharding (sozu-proxy/sozu#524), per cluster: a selection whose
59    /// client shard had no backend able to take a connection, and which the
60    /// cluster's `FALLBACK` mode sent to a backend outside the shard.
61    pub const SHARD_SPILLOVER: &str = "backend.shard.spillover";
62    /// Shuffle sharding, per cluster: a selection whose client shard had no
63    /// backend able to take a connection, refused by the cluster's `STRICT`
64    /// mode (HTTP answers 503, TCP closes).
65    pub const SHARD_EXHAUSTED: &str = "backend.shard.exhausted";
66
67    // Connection-lifecycle transition counters, emitted per
68    // `(cluster_id, backend_id)` from both proxies: the mux (H1 and H2) and
69    // raw TCP.
70    pub const UP: &str = "backend.up";
71    pub const DOWN: &str = "backend.down";
72    pub const CONNECTIONS_ERROR: &str = "backend.connections.error";
73    pub const CONNECT_RETRIES_EXHAUSTED: &str = "backend.connect.retries_exhausted";
74
75    /// An idempotent request written onto a pooled keep-alive upstream that
76    /// then closed without answering was replayed on a fresh backend instead
77    /// of being answered `502 Bad Gateway` (sozu-proxy/sozu#1442). Labelled
78    /// with the cluster and the STALE backend — the one that did not answer.
79    ///
80    /// One request can increment this more than once: the replay goes back
81    /// through `Router::plan_connect`, so the cluster's
82    /// `max_connection_attempts` bounds the total, not this counter.
83    ///
84    /// Read it as "an upstream went away before answering", NOT as "the pool
85    /// held a closed socket". sozu cannot tell those apart: a backend that
86    /// half-closes after processing a request, and one that crashes
87    /// mid-request, both land here. Raising the upstream keep-alive idle
88    /// timeout above sozu's is the right fix only for the first reading, so
89    /// check the backends' own error rate and restart/OOM history before
90    /// reaching for it — if they are dying mid-request, this counter is
91    /// reporting that, and the idle timeout will not move it.
92    pub const RETRY_STALE_UPSTREAM: &str = "backend.retry.stale_upstream";
93
94    /// Gauge: request captures currently armed for a stale-upstream replay,
95    /// summed over every live stream of this worker (sozu-proxy/sozu#1450).
96    ///
97    /// A capture is armed when an IDEMPOTENT request is written onto a POOLED
98    /// keep-alive upstream, and released the moment the attempt stops being
99    /// replayable — the upstream answered, the request overflowed one front
100    /// buffer, the replay was queued, or the stream was torn down. Emitted as
101    /// a signed `gauge_add!` delta from `ReplayCapture`'s constructor and its
102    /// `impl Drop`, so teardown is symmetric whichever of those paths ran.
103    ///
104    /// To read the capture heap from it, the bound is `2 * gauge *
105    /// buffer_size`, NOT `gauge * buffer_size`. Each capture's CONTENT is
106    /// bounded by one front kawa's `storage.capacity()`, but the write path
107    /// grows it with `Vec::reserve`, which allocates
108    /// `max(2 * old_capacity, required)`: a capture written in one pass has
109    /// `capacity == len`, and one assembled over several partial writes can
110    /// have twice it. The single-write case is the common one, so the true
111    /// figure sits between the two.
112    pub const RETRY_CAPTURES_ARMED: &str = "backend.retry.captures_armed";
113
114    /// An IDEMPOTENT request written onto a pooled keep-alive upstream was NOT
115    /// captured, because `MAX_ARMED_REPLAY_CAPTURES` captures were already
116    /// armed on this worker (sozu-proxy/sozu#1450).
117    ///
118    /// Nothing is refused and no frontend parks: the request proceeds
119    /// un-replayable, and if its upstream turns out to be stale it answers the
120    /// same `502 Bad Gateway` it answered before the replay existed.
121    ///
122    /// It is an UPPER BOUND on lost retries, not a count of them. It counts
123    /// requests that were left un-replayable; the overwhelming majority of
124    /// them get a normal answer from an upstream that was not stale at all,
125    /// and would never have used the capture. Only the fraction that then hit
126    /// the stale-pool race becomes a `502` that the ceiling turned into a lost
127    /// retry. Compare it against `backend.retry.stale_upstream` to see how
128    /// often that race actually fires on this worker before reading anything
129    /// into this counter's rate.
130    ///
131    /// Non-idempotent requests are not counted here. They are never captured
132    /// and never charged (`Stream::arm_upstream_replay`), so a POST-heavy
133    /// workload does not inflate this counter.
134    ///
135    /// Read it against [`RETRY_CAPTURES_ARMED`]: this counter only moves while
136    /// that gauge sits at the ceiling.
137    pub const RETRY_CAPTURES_DECLINED: &str = "backend.retry.captures_declined";
138}
139
140/// Buffer-pool gauges and counters.
141pub mod buffer {
142    pub const CAPACITY: &str = "buffer.capacity";
143    pub const IN_USE: &str = "buffer.in_use";
144    pub const USAGE_PERCENT: &str = "buffer.usage_percent";
145}
146
147/// Client-side connection gauges, populated by the worker accept loop.
148pub mod client {
149    pub const CONNECTIONS: &str = "client.connections";
150    pub const CONNECTIONS_MAX: &str = "client.connections_max";
151    pub const CONNECTIONS_PERCENT: &str = "client.connections_percent";
152}
153
154/// Per-cluster aggregate gauges.
155pub mod cluster {
156    pub const AVAILABLE_BACKENDS: &str = "cluster.available_backends";
157    pub const AVAILABLE_RECOVERED: &str = "cluster.available_recovered";
158    pub const NO_AVAILABLE_BACKENDS: &str = "cluster.no_available_backends";
159    pub const TOTAL_BACKENDS: &str = "cluster.total_backends";
160}
161
162/// Configuration-state inventory gauges, refreshed when the master
163/// fans out a state change.
164pub mod configuration {
165    pub const BACKENDS: &str = "configuration.backends";
166    pub const CLUSTERS: &str = "configuration.clusters";
167    pub const FRONTENDS: &str = "configuration.frontends";
168}
169
170/// Per-source connection-admission counters. Distinct from `client`, which
171/// gauges the worker-wide connection population.
172pub mod connections {
173    pub const REJECTED_PER_CLUSTER_IP: &str = "connections.rejected_per_cluster_ip";
174}
175
176/// Event-loop timing counters.
177pub mod event_loop {
178    pub const EPOLL_TIME: &str = "epoll_time";
179    pub const EVENT_LOOP_TIME: &str = "event_loop_time";
180    pub const FRONTEND_MATCHING_TIME: &str = "frontend_matching_time";
181    pub const REGEX_MATCHING_TIME: &str = "regex_matching_time";
182    pub const REQUEST_TIME: &str = "request_time";
183    pub const SERVICE_TIME: &str = "service_time";
184}
185
186/// Health-check transition counters.
187pub mod health_check {
188    pub const UP: &str = "health_check.up";
189    pub const DOWN: &str = "health_check.down";
190    pub const SUCCESS: &str = "health_check.success";
191    pub const FAILURE: &str = "health_check.failure";
192    pub const HEALTHY_BACKENDS: &str = "health_check.healthy_backends";
193}
194
195/// H1 protocol counters.
196pub mod h1 {
197    pub const BACKEND_EOF_BEFORE_MESSAGE_COMPLETE: &str = "h1.backend_eof_before_message_complete";
198}
199
200/// H2 multiplexer counters — frame TX, flood mitigations, header policy
201/// rejections, signal-writable rearm sites, and other H2-specific buckets.
202pub mod h2 {
203    pub const CLOSE_WITH_ACTIVE_STREAMS: &str = "h2.close_with_active_streams";
204    pub const COALESCING_ACCEPTED: &str = "h2.coalescing.accepted";
205    pub const CONNECTION_ACTIVE_STREAMS: &str = "h2.connection.active_streams";
206    pub const CONNECTION_PENDING_WINDOW_UPDATES: &str = "h2.connection.pending_window_updates";
207    pub const CONNECTION_WINDOW_BYTES: &str = "h2.connection.window_bytes";
208    pub const FLOW_CONTROL_STALL: &str = "h2.flow_control_stall";
209
210    // Per-stream reap counters — `cancel_timed_out_streams` breaks reaps down by
211    // which guard tripped (M1), plus a stall-budget subset for the
212    // `WINDOW_UPDATE`-drip vector the cumulative-stall budget closes (M2).
213    // `reaped.stall_budget` is a subset of `reaped.window_stall`.
214    pub const STREAMS_REAPED_IDLE_TIMEOUT: &str = "h2.streams.reaped.idle_timeout";
215    pub const STREAMS_REAPED_WINDOW_STALL: &str = "h2.streams.reaped.window_stall";
216    pub const STREAMS_REAPED_STALL_BUDGET: &str = "h2.streams.reaped.stall_budget";
217
218    /// Streams that were ready to emit AND marked incremental (RFC 9218 §4),
219    /// summed over the urgency buckets of one write pass and aggregated across
220    /// every live H2 connection. Emitted as a signed delta that
221    /// `ConnectionH2::gauge_connection_state` returns as a `MetricEvent`, with
222    /// the matching teardown in `impl Drop for H2Shell`, exactly like the
223    /// `CONNECTION_*` trio below it.
224    pub const STREAMS_READY_INCREMENTAL_BY_URGENCY: &str =
225        "h2.streams.ready_incremental.by_urgency";
226
227    // Frame-TX counters (frame type fanout).
228    pub const FRAMES_TX_CONTINUATION: &str = "h2.frames.tx.continuation";
229    pub const FRAMES_TX_DATA: &str = "h2.frames.tx.data";
230    pub const FRAMES_TX_GOAWAY: &str = "h2.frames.tx.goaway";
231    pub const FRAMES_TX_HEADERS: &str = "h2.frames.tx.headers";
232    pub const FRAMES_TX_PING_ACK: &str = "h2.frames.tx.ping_ack";
233    pub const FRAMES_TX_RST_STREAM: &str = "h2.frames.tx.rst_stream";
234    pub const FRAMES_TX_SETTINGS: &str = "h2.frames.tx.settings";
235    pub const FRAMES_TX_WINDOW_UPDATE: &str = "h2.frames.tx.window_update";
236
237    // Header-policy rejections (the `h2.headers.rejected.*` family).
238    pub const HEADERS_NO_STREAM_ERROR: &str = "h2.headers_no_stream.error";
239    pub const HEADERS_REJECTED_BUDGET_OVERRUN: &str = "h2.headers.rejected.budget_overrun";
240    pub const HEADERS_REJECTED_TOTAL: &str = "h2.headers.rejected.total";
241
242    pub const RST_STREAM_RECEIVED_PRE_RESPONSE_START: &str =
243        "h2.rst_stream.received.pre_response_start";
244
245    /// A proxy-emitted RST_STREAM that was never queued because
246    /// `pending_rst_streams` was already at its bound (`pending_rst_bound`,
247    /// at least `MIN_PENDING_RST_STREAMS`).
248    /// Sibling of `WINDOW_UPDATE_DROPPED` below, same "control queue full"
249    /// semantics; a non-zero value means the connection is on its way to
250    /// `GOAWAY(ENHANCE_YOUR_CALM)`.
251    pub const RST_STREAM_DROPPED: &str = "h2.rst_stream_dropped";
252
253    // Writable-rearm signal counters — one per rearm reason.
254    pub const SIGNAL_WRITABLE_REARMED_CONTROL_QUEUE: &str =
255        "h2.signal.writable.rearmed.control_queue";
256    pub const SIGNAL_WRITABLE_REARMED_DEFAULT_ANSWER: &str =
257        "h2.signal.writable.rearmed.default_answer";
258    pub const SIGNAL_WRITABLE_REARMED_FORCEFULLY_TERMINATE_ANSWER: &str =
259        "h2.signal.writable.rearmed.forcefully_terminate_answer";
260    pub const SIGNAL_WRITABLE_REARMED_PEER_DATA: &str = "h2.signal.writable.rearmed.peer_data";
261    pub const SIGNAL_WRITABLE_REARMED_PEER_HEADERS: &str =
262        "h2.signal.writable.rearmed.peer_headers";
263    pub const SIGNAL_WRITABLE_REARMED_PRIORITY_UPDATE: &str =
264        "h2.signal.writable.rearmed.priority_update";
265
266    pub const TRAILERS_DROPPED_CONTENT_LENGTH: &str = "h2.trailers_dropped_content_length";
267    /// H2 trailer blocks of a response to HEAD, a 204 or a 304 dropped by
268    /// `ConnectionH1::writable` towards an H1 client, which reads such a
269    /// response as ending with its header section (RFC 9112 §6.3).
270    pub const TRAILERS_DROPPED_NO_BODY: &str = "h2.trailers_dropped_no_body";
271    /// Backend streams reset by `ConnectionH2::handle_data_frame` because a
272    /// 204 or a 304 response carried a DATA payload (RFC 9110 §15.3.5,
273    /// §15.4.5; RFC 9113 §8.1.1).
274    pub const BODILESS_RESPONSE_DATA_RESET: &str = "h2.bodiless_response_data_reset";
275    /// Request trailer fields elided by `pkawa::handle_trailer` because their
276    /// name is one of `editor::TRAILER_FORBIDDEN_FIELDS` (sozu-proxy/sozu#1714).
277    /// One increment per field; the request is still forwarded. The H1
278    /// frontend counts the same drop in `http::TRAILER_FORBIDDEN_FIELD_ELIDED`.
279    pub const TRAILER_FORBIDDEN_FIELD_ELIDED: &str = "h2.trailer.forbidden_field_elided";
280    pub const TRAILER_SPOOF_VECTOR_ELIDED: &str = "h2.trailer.spoof_vector_elided";
281    pub const WINDOW_UPDATE_DROPPED: &str = "h2.window_update_dropped";
282
283    // Flood-mitigation violation counters — one per flood class the H2
284    // mux's `H2FloodDetector` recognises. Surfaced in the TUI's H2 pane
285    // so operators can spot a flood-pattern before it pages.
286    pub const FLOOD_VIOLATION_GLITCH_WINDOW: &str = "h2.flood.violation.glitch_window";
287    pub const FLOOD_VIOLATION_RST_STREAM_WINDOW: &str = "h2.flood.violation.rst_stream_window";
288    pub const FLOOD_VIOLATION_RST_STREAM_LIFETIME: &str = "h2.flood.violation.rst_stream_lifetime";
289    pub const FLOOD_VIOLATION_RST_STREAM_PRE_RESPONSE_LIFETIME: &str =
290        "h2.flood.violation.rst_stream_pre_response_lifetime";
291    pub const FLOOD_VIOLATION_RST_STREAM_EMITTED_LIFETIME: &str =
292        "h2.flood.violation.rst_stream_emitted_lifetime";
293    pub const FLOOD_VIOLATION_PING_WINDOW: &str = "h2.flood.violation.ping_window";
294    pub const FLOOD_VIOLATION_PING_LIFETIME: &str = "h2.flood.violation.ping_lifetime";
295    pub const FLOOD_VIOLATION_SETTINGS_WINDOW: &str = "h2.flood.violation.settings_window";
296    pub const FLOOD_VIOLATION_SETTINGS_LIFETIME: &str = "h2.flood.violation.settings_lifetime";
297    pub const FLOOD_VIOLATION_EMPTY_DATA_WINDOW: &str = "h2.flood.violation.empty_data_window";
298    pub const FLOOD_VIOLATION_CONTINUATION_PER_BLOCK: &str =
299        "h2.flood.violation.continuation_per_block";
300    pub const FLOOD_VIOLATION_WINDOW_UPDATE_STREAM0_WINDOW: &str =
301        "h2.flood.violation.window_update_stream0_window";
302    pub const FLOOD_VIOLATION_HEADER_SIZE_PER_BLOCK: &str =
303        "h2.flood.violation.header_size_per_block";
304    /// Every `metric_key` an `H2FloodViolation` carries, one per flood class
305    /// (`lib/src/protocol/mux/h2_flood_detector.rs`). The detector's
306    /// `test_flood_violation_metric_keys_are_unique_and_namespaced` pins this
307    /// list to what it emits, and `sozu top` reads its H2 pane rows from it.
308    pub const FLOOD_VIOLATION_KEYS: [&str; 13] = [
309        FLOOD_VIOLATION_RST_STREAM_PRE_RESPONSE_LIFETIME,
310        FLOOD_VIOLATION_RST_STREAM_LIFETIME,
311        FLOOD_VIOLATION_RST_STREAM_EMITTED_LIFETIME,
312        FLOOD_VIOLATION_RST_STREAM_WINDOW,
313        FLOOD_VIOLATION_CONTINUATION_PER_BLOCK,
314        FLOOD_VIOLATION_HEADER_SIZE_PER_BLOCK,
315        FLOOD_VIOLATION_PING_WINDOW,
316        FLOOD_VIOLATION_PING_LIFETIME,
317        FLOOD_VIOLATION_SETTINGS_WINDOW,
318        FLOOD_VIOLATION_SETTINGS_LIFETIME,
319        FLOOD_VIOLATION_EMPTY_DATA_WINDOW,
320        FLOOD_VIOLATION_WINDOW_UPDATE_STREAM0_WINDOW,
321        FLOOD_VIOLATION_GLITCH_WINDOW,
322    ];
323    /// A new client stream refused with `REFUSED_STREAM` in the soft state
324    /// below the pre-response RST_STREAM cap (`h2_stream_refusal_percent`).
325    /// The connection stays open; a rising rate is the early warning before
326    /// `h2.flood.violation.rst_stream_pre_response_lifetime`.
327    pub const FLOOD_STREAM_REFUSED: &str = "h2.flood.stream_refused";
328}
329
330/// HTTP counters (H1 + H2 share these); see `https` for the HTTPS-specific
331/// variants and `h2` for H2-frame-level counters.
332pub mod http {
333    pub const ERR_400: &str = "http.400.errors";
334    pub const ERR_401: &str = "http.401.errors";
335    pub const ERR_404: &str = "http.404.errors";
336    pub const ERR_408: &str = "http.408.errors";
337    pub const ERR_413: &str = "http.413.errors";
338    pub const ERR_421: &str = "http.421.errors";
339    pub const ERR_502: &str = "http.502.errors";
340    pub const ERR_503: &str = "http.503.errors";
341    pub const ERR_504: &str = "http.504.errors";
342    pub const ERR_507: &str = "http.507.errors";
343    /// Catch-all for the default answers that have no dedicated `ERR_*`
344    /// bucket; incremented alongside the per-code counter where one exists.
345    pub const ERRORS: &str = "http.errors";
346    pub const REDIRECTION_301: &str = "http.301.redirection";
347    pub const REDIRECTION_302: &str = "http.302.redirection";
348    pub const REDIRECTION_308: &str = "http.308.redirection";
349    pub const ACTIVE_REQUESTS: &str = "http.active_requests";
350    pub const ALPN_H2: &str = "http.alpn.h2";
351    pub const ALPN_HTTP11: &str = "http.alpn.http11";
352    pub const BACKEND_PARSE_ERRORS: &str = "http.backend_parse_errors";
353    pub const E2E_H2: &str = "http.e2e.h2";
354    pub const E2E_HTTP11: &str = "http.e2e.http11";
355    pub const EARLY_RESPONSE_CLOSE: &str = "http.early_response_close";
356    /// H1 client connections closed because a response completed before its
357    /// request was received whole (a backend answering an upload early): the
358    /// rest of the body is never read as a request (sozu-proxy/sozu#1721).
359    pub const CLOSE_REQUEST_INCOMPLETE: &str = "http.close.request_incomplete";
360    pub const FAILED_BACKEND_MATCHING: &str = "http.failed_backend_matching";
361    /// Client `Forwarded` lines removed because their value is not a
362    /// well-formed RFC 7239 §4 list, counted once per removed line. Only the
363    /// `both` and `rfc7239` `forwarded_headers` modes, which extend the chain,
364    /// remove such a line; the request itself is still forwarded.
365    pub const FORWARDED_MALFORMED_ELIDED: &str = "http.forwarded_malformed_elided";
366    /// Requests rejected because a Transfer-Encoding header survived parsing without
367    /// kawa adopting chunked framing (CL.TE request-smuggling defense, CWE-444).
368    pub const FRONTEND_TE_SMUGGLING: &str = "http.frontend.transfer_encoding_smuggling";
369    /// Requests rejected (400) because a forwarded Content-Length value was not
370    /// `1*DIGIT`, e.g. `+5` (RFC 9110 §8.6; request-smuggling defense, CWE-444).
371    /// Defense in depth: kawa >= 0.7.2 refuses such a value first, counted in
372    /// `FRONTEND_PARSE_ERRORS`, so this stays at zero unless kawa regresses.
373    pub const FRONTEND_CONTENT_LENGTH_INVALID: &str = "http.frontend.content_length_invalid";
374    /// Backend responses rejected (502) because their Content-Length value was
375    /// not `1*DIGIT` (RFC 9110 §8.6, RFC 9112 §6.3). Defense in depth:
376    /// kawa >= 0.7.2 refuses such a response first, counted in
377    /// `BACKEND_PARSE_ERRORS`, so this stays at zero unless kawa regresses.
378    pub const BACKEND_CONTENT_LENGTH_INVALID: &str = "http.backend.content_length_invalid";
379    pub const FRONTEND_PARSE_ERRORS: &str = "http.frontend_parse_errors";
380    pub const HSTS_FRONTEND_ADDED: &str = "http.hsts.frontend_added";
381    pub const HSTS_FRONTEND_REFRESHED: &str = "http.hsts.frontend_refreshed";
382    pub const HSTS_LISTENER_DEFAULT_PATCHED: &str = "http.hsts.listener_default_patched";
383    pub const HSTS_SUPPRESSED_PLAINTEXT: &str = "http.hsts.suppressed_plaintext";
384    pub const HSTS_UNRENDERED: &str = "http.hsts.unrendered";
385    pub const INFINITE_LOOP_ERROR: &str = "http.infinite_loop.error";
386    pub const REDIRECT_TEMPLATE_COMPILE_ERROR: &str = "http.redirect_template.compile_error";
387    pub const REQUESTS: &str = "http.requests";
388    pub const SNI_AUTHORITY_MISMATCH: &str = "http.sni_authority_mismatch";
389    pub const STATUS_1XX: &str = "http.status.1xx";
390    pub const STATUS_2XX: &str = "http.status.2xx";
391    pub const STATUS_3XX: &str = "http.status.3xx";
392    pub const STATUS_4XX: &str = "http.status.4xx";
393    pub const STATUS_5XX: &str = "http.status.5xx";
394    pub const STATUS_OTHER: &str = "http.status.other";
395    /// H1 chunked requests rejected because their trailer section carried
396    /// more fields than the listener's `h2_max_header_fields`, the bound the
397    /// H2 frontend applies to a trailer block (sozu-proxy/sozu#1701). One
398    /// increment per request; the request is answered 400.
399    pub const TRAILER_FIELD_LIMIT_EXCEEDED: &str = "http.trailer.field_limit_exceeded";
400    /// Trailer fields elided from an H1 chunked request because their name is
401    /// one of `editor::TRAILER_FORBIDDEN_FIELDS` (framing, routing, request
402    /// modifiers, authentication, content processing or connection-specific
403    /// fields a trailer must not carry, RFC 9110 §6.5.1;
404    /// sozu-proxy/sozu#1701). One increment per field. The H2 frontend
405    /// counts the same drop in `h2::TRAILER_FORBIDDEN_FIELD_ELIDED`.
406    pub const TRAILER_FORBIDDEN_FIELD_ELIDED: &str = "http.trailer.forbidden_field_elided";
407    /// Trailer fields elided from an H1 chunked request because their name is
408    /// one of `editor::TRAILER_SPOOF_VECTOR_HEADERS` (client attribution a
409    /// trailer must not carry, RFC 9110 §6.5.1; sozu-proxy/sozu#1689). One
410    /// increment per field. The H2 frontend counts the same drop in
411    /// `h2::TRAILER_SPOOF_VECTOR_ELIDED`.
412    pub const TRAILER_SPOOF_VECTOR_ELIDED: &str = "http.trailer.spoof_vector_elided";
413    pub const TRUSTING_X_PORT: &str = "http.trusting.x_port";
414    pub const TRUSTING_X_PORT_DIFF: &str = "http.trusting.x_port.diff";
415    pub const TRUSTING_X_PROTO: &str = "http.trusting.x_proto";
416    pub const TRUSTING_X_PROTO_DIFF: &str = "http.trusting.x_proto.diff";
417    pub const UPGRADE_EXPECT_FAILED: &str = "http.upgrade.expect.failed";
418    pub const UPGRADE_MUX_FAILED: &str = "http.upgrade.mux.failed";
419    pub const UPGRADE_WS_FAILED: &str = "http.upgrade.ws.failed";
420    pub const X_REQUEST_ID_GENERATED: &str = "http.x_request_id.generated";
421    pub const X_REQUEST_ID_PROPAGATED: &str = "http.x_request_id.propagated";
422}
423
424/// Per-status-code HTTP counters. Only the codes Sōzu either generates as
425/// a default answer or that operators routinely chart get a dedicated bucket;
426/// the rest fold into the `http::STATUS_*XX` parent buckets.
427pub mod http_status {
428    pub const S200: &str = "http.status.200";
429    pub const S201: &str = "http.status.201";
430    pub const S204: &str = "http.status.204";
431    pub const S301: &str = "http.status.301";
432    pub const S302: &str = "http.status.302";
433    pub const S304: &str = "http.status.304";
434    pub const S400: &str = "http.status.400";
435    pub const S401: &str = "http.status.401";
436    pub const S403: &str = "http.status.403";
437    pub const S404: &str = "http.status.404";
438    pub const S408: &str = "http.status.408";
439    pub const S413: &str = "http.status.413";
440    pub const S429: &str = "http.status.429";
441    pub const S500: &str = "http.status.500";
442    pub const S502: &str = "http.status.502";
443    pub const S503: &str = "http.status.503";
444    pub const S504: &str = "http.status.504";
445    pub const S507: &str = "http.status.507";
446}
447
448/// HTTPS-specific counters; see `http` for the HTTP family these complement.
449pub mod https {
450    pub const ALPN_REJECTED_HTTP11_DISABLED: &str = "https.alpn.rejected.http11_disabled";
451    pub const ALPN_REJECTED_UNSUPPORTED: &str = "https.alpn.rejected.unsupported";
452    pub const UPGRADE_EXPECT_FAILED: &str = "https.upgrade.expect.failed";
453    pub const UPGRADE_HANDSHAKE_FAILED: &str = "https.upgrade.handshake.failed";
454    pub const UPGRADE_MUX_FAILED: &str = "https.upgrade.mux.failed";
455    pub const UPGRADE_WSS_FAILED: &str = "https.upgrade.wss.failed";
456}
457
458/// Per-listener counters.
459pub mod listener {
460    pub const ACCEPTED_TOTAL: &str = "listener.accepted.total";
461    pub const CONNECTION_CAPPED: &str = "listener.connection_capped";
462}
463
464/// Pipe-protocol counters.
465pub mod pipe {
466    pub const ERRORS: &str = "pipe.errors";
467}
468
469/// Worker-process runtime gauges.
470pub mod process {
471    pub const UPTIME_SECONDS: &str = "process.uptime_seconds";
472}
473
474/// Protocol-type counters that increment once per session and track which
475/// protocol carried it end-to-end.
476pub mod protocol {
477    pub const HTTP: &str = "protocol.http";
478    pub const HTTPS: &str = "protocol.https";
479    pub const PROXY_EXPECT: &str = "protocol.proxy.expect";
480    pub const PROXY_RELAY: &str = "protocol.proxy.relay";
481    pub const PROXY_SEND: &str = "protocol.proxy.send";
482    pub const TCP: &str = "protocol.tcp";
483    pub const TLS_HANDSHAKE: &str = "protocol.tls.handshake";
484    pub const WS: &str = "protocol.ws";
485    pub const WSS: &str = "protocol.wss";
486}
487
488/// PROXY-protocol counters.
489pub mod proxy_protocol {
490    pub const ERRORS: &str = "proxy_protocol.errors";
491}
492
493/// `rustls`-specific counters for read/write infinite-loop guards.
494pub mod rustls {
495    pub const READ_ERROR: &str = "rustls.read.error";
496    pub const READ_INFINITE_LOOP_ERROR: &str = "rustls.read.infinite_loop.error";
497    /// A TLS frontend's `recv(2)` answered `ECONNRESET`/`ECONNABORTED`: the
498    /// peer reset the connection (sozu-proxy/sozu#434). Counted once per
499    /// reset, during the handshake or after it.
500    pub const READ_RESET: &str = "rustls.read.reset";
501    pub const WRITE_ERROR: &str = "rustls.write.error";
502    /// A TLS frontend's `writev(2)` answered `ECONNRESET`/`ECONNABORTED`.
503    /// Still counted under [`WRITE_ERROR`] as well, as it was before this
504    /// counter existed.
505    pub const WRITE_RESET: &str = "rustls.write.reset";
506    pub const WRITE_INFINITE_LOOP_ERROR: &str = "rustls.write.infinite_loop.error";
507}
508
509/// Worker liveness gauge, emitted by the server tick.
510pub mod server {
511    pub const LIVE: &str = "server.live";
512}
513
514/// Generic session-level counters.
515pub mod sessions {
516    pub const EVICTED: &str = "sessions.evicted";
517}
518
519/// Slab-allocator gauges.
520pub mod slab {
521    pub const ACCEPT_THRESHOLD_PERCENT: &str = "slab.accept_threshold_percent";
522    pub const CAPACITY: &str = "slab.capacity";
523    pub const ENTRIES: &str = "slab.entries";
524    pub const USAGE_PERCENT: &str = "slab.usage_percent";
525}
526
527/// Raw-socket counters.
528pub mod socket {
529    pub const WRITE_INFINITE_LOOP_ERROR: &str = "socket.write.infinite_loop.error";
530}
531
532/// TCP protocol counters.
533pub mod tcp {
534    pub const INFINITE_LOOP_ERROR: &str = "tcp.infinite_loop.error";
535    pub const READ_ERROR: &str = "tcp.read.error";
536    /// A plain TCP socket's `recv(2)` or `splice(2)` answered
537    /// `ECONNRESET`/`ECONNABORTED`: the peer reset the connection
538    /// (sozu-proxy/sozu#434). Frontend and backend sockets alike.
539    pub const READ_RESET: &str = "tcp.read.reset";
540    pub const REQUESTS: &str = "tcp.requests";
541    pub const UPGRADE_EXPECT_FAILED: &str = "tcp.upgrade.expect.failed";
542    pub const UPGRADE_PIPE_FAILED: &str = "tcp.upgrade.pipe.failed";
543    pub const UPGRADE_RELAY_FAILED: &str = "tcp.upgrade.relay.failed";
544    pub const UPGRADE_SEND_FAILED: &str = "tcp.upgrade.send.failed";
545    pub const UPGRADE_SNI_PREREAD_FAILED: &str = "tcp.upgrade.sni_preread.failed";
546    pub const WRITE_ERROR: &str = "tcp.write.error";
547    /// A plain TCP socket's `send(2)`/`writev(2)` or `splice(2)` answered
548    /// `ECONNRESET`/`ECONNABORTED`. The `send` paths still count it under
549    /// [`WRITE_ERROR`] as well, as they did before this counter existed.
550    pub const WRITE_RESET: &str = "tcp.write.reset";
551
552    /// SNI-preread counters (TCP passthrough routing, sozu-proxy/sozu#1279),
553    /// fed by the shell in `lib/src/protocol/tcp_preread/shell.rs` (decision
554    /// metrics) and `lib/src/tcp.rs` (the `ACTIVE` gauge + `DURATION`
555    /// timing, both owned centrally by the session lifecycle so each is
556    /// touched exactly once per session regardless of which of the three
557    /// exits -- reject / upgrade / teardown -- it takes).
558    pub mod sni_preread {
559        use crate::protocol::tcp_preread::RejectReason;
560
561        /// A cluster was chosen from the ClientHello's SNI (+ optional ALPN).
562        pub const ROUTED: &str = "tcp.sni_preread.routed";
563        /// Gauge: sessions currently prereading. Incremented once at
564        /// `SniPreread` construction, decremented exactly once on every
565        /// exit (reject / upgrade / teardown); underflow is a bug.
566        pub const ACTIVE: &str = "tcp.sni_preread.active";
567        /// `time!` of the wall-clock spent prereading, recorded on every
568        /// exit alongside the `ACTIVE` decrement.
569        pub const DURATION: &str = "tcp.sni_preread.duration";
570
571        pub const REJECTED_NOT_TLS: &str = "tcp.sni_preread.rejected.not_tls";
572        pub const REJECTED_MALFORMED_RECORD: &str = "tcp.sni_preread.rejected.malformed_record";
573        pub const REJECTED_MALFORMED_HANDSHAKE: &str =
574            "tcp.sni_preread.rejected.malformed_handshake";
575        pub const REJECTED_FRAGMENTED: &str = "tcp.sni_preread.rejected.fragmented";
576        pub const REJECTED_TOO_LARGE: &str = "tcp.sni_preread.rejected.too_large";
577        pub const REJECTED_NO_SNI: &str = "tcp.sni_preread.rejected.no_sni";
578        pub const REJECTED_ECH_OUTER_ABSENT: &str = "tcp.sni_preread.rejected.ech_outer_absent";
579        pub const REJECTED_SNI_UNMATCHED: &str = "tcp.sni_preread.rejected.sni_unmatched";
580        pub const REJECTED_ALPN_UNMATCHED: &str = "tcp.sni_preread.rejected.alpn_unmatched";
581        pub const REJECTED_PROXY_HEADER_INVALID: &str =
582            "tcp.sni_preread.rejected.proxy_header_invalid";
583        pub const REJECTED_FRONT_CLOSED: &str = "tcp.sni_preread.rejected.front_closed";
584
585        /// Total function over every `RejectReason` variant: a new variant
586        /// added to the core without a matching arm here fails to compile,
587        /// so the metric surface can never silently drop a rejection reason.
588        pub fn rejected_name(reason: RejectReason) -> &'static str {
589            match reason {
590                RejectReason::NotTls => REJECTED_NOT_TLS,
591                RejectReason::MalformedRecord => REJECTED_MALFORMED_RECORD,
592                RejectReason::MalformedHandshake => REJECTED_MALFORMED_HANDSHAKE,
593                RejectReason::Fragmented => REJECTED_FRAGMENTED,
594                RejectReason::TooLarge => REJECTED_TOO_LARGE,
595                RejectReason::NoSni => REJECTED_NO_SNI,
596                RejectReason::EchOuterAbsent => REJECTED_ECH_OUTER_ABSENT,
597                RejectReason::SniUnmatched => REJECTED_SNI_UNMATCHED,
598                RejectReason::AlpnUnmatched => REJECTED_ALPN_UNMATCHED,
599                RejectReason::ProxyHeaderInvalid => REJECTED_PROXY_HEADER_INVALID,
600                RejectReason::FrontClosed => REJECTED_FRONT_CLOSED,
601            }
602        }
603    }
604}
605
606/// UDP datapath counters/gauges, fed by the UDP I/O shell in `lib/src/udp.rs`.
607///
608/// `IN`/`OUT` follow the client perspective: `IN` = client→backend
609/// (request), `OUT` = backend→client (reply). `ACTIVE_FLOWS` is a gauge that
610/// must never underflow — it is incremented exactly once per admitted flow
611/// (`FLOWS_CREATED`) and decremented exactly once per close.
612pub mod udp {
613    /// Client→backend datagrams forwarded.
614    pub const DATAGRAMS_IN: &str = "udp.datagrams.in";
615    /// Backend→client datagrams returned.
616    pub const DATAGRAMS_OUT: &str = "udp.datagrams.out";
617    /// Payload bytes forwarded client→backend (excludes PPv2 prefix).
618    pub const BYTES_IN: &str = "udp.bytes.in";
619    /// Payload bytes returned backend→client.
620    pub const BYTES_OUT: &str = "udp.bytes.out";
621    /// Gauge: currently admitted flows. Never underflows.
622    pub const ACTIVE_FLOWS: &str = "udp.active_flows";
623    /// Flows admitted since boot.
624    pub const FLOWS_CREATED: &str = "udp.flows.created";
625    /// Flows torn down (idle / teardown / drain).
626    pub const FLOWS_EVICTED: &str = "udp.flows.evicted";
627    /// New flows shed at the `max_flows` cap, at a per-source limit, or
628    /// under fd pressure.
629    pub const FLOWS_SHED: &str = "udp.flows.shed";
630    /// New flows shed because their source IP or subnet held the cluster's
631    /// `max_connections_per_ip` / `max_connections_per_subnet` flows. Also
632    /// counted in [`FLOWS_SHED`].
633    pub const FLOWS_SHED_SOURCE_LIMIT: &str = "udp.flows.shed.source_limit";
634    /// Datagrams dropped before allocation (aggregate). Reason-specific
635    /// counters below carry the dotted `.<reason>` suffix; both are emitted
636    /// so dashboards can chart the total or break it down. The `incr!` macro
637    /// needs `&'static str`, so the by-reason names are fixed constants
638    /// rather than a runtime-formatted suffix.
639    pub const DATAGRAMS_DROPPED: &str = "udp.datagrams.dropped";
640    /// Dropped: failed validation (empty / extractor rejected).
641    pub const DROPPED_INVALID: &str = "udp.datagrams.dropped.invalid";
642    /// Dropped: exceeded `max_rx_datagram_size` (truncation surrogate).
643    pub const DROPPED_TRUNCATED: &str = "udp.datagrams.dropped.truncated";
644    /// Dropped: no cluster/backend configured for the listener.
645    pub const DROPPED_NO_BACKEND: &str = "udp.datagrams.dropped.no_backend";
646    /// Dropped: flow-table cap reached (shed).
647    pub const DROPPED_SHED: &str = "udp.datagrams.dropped.shed";
648    /// Dropped: referenced an unknown / already-closed flow.
649    pub const DROPPED_UNKNOWN_FLOW: &str = "udp.datagrams.dropped.unknown_flow";
650    /// Dropped: the bounded per-flow / client-return write queue was at
651    /// capacity (genuine egress-queue overflow under sustained `WouldBlock`).
652    pub const DROPPED_WQ_FULL: &str = "udp.datagrams.dropped.wq_full";
653    /// Dropped: a hard `send`/`send_to` error (e.g. ECONNREFUSED) on the egress
654    /// path, distinct from a queue-full overflow. The egress "no resolved flow /
655    /// socket gone" case routes to [`DROPPED_UNKNOWN_FLOW`] instead, so
656    /// `wq_full` only counts genuine bounded-queue overflow.
657    pub const DROPPED_SEND_ERROR: &str = "udp.datagrams.dropped.send_error";
658    /// Backend health transitions observed by the UDP health prober.
659    pub const BACKEND_HEALTH: &str = "udp.backend.health";
660    /// `time!` of a flow's lifetime, recorded on close.
661    pub const FLOW_DURATION: &str = "udp.flow.duration";
662}
663
664/// TLS counters (certificate inventory, handshake timing).
665pub mod tls {
666    pub const CERT_MIN_EXPIRES_AT_SECONDS: &str = "tls.cert.min_expires_at_seconds";
667    pub const DEFAULT_CERT_USED: &str = "tls.default_cert_used";
668    pub const HANDSHAKE_MS: &str = "tls.handshake_ms";
669}
670
671/// WebSocket counters.
672pub mod websocket {
673    pub const ACTIVE_REQUESTS: &str = "websocket.active_requests";
674}
675
676/// Miscellaneous counters that don't fit a richer family.
677pub mod misc {
678    pub const PANIC: &str = "panic";
679    pub const ZOMBIES: &str = "zombies";
680}