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    /// Probes not sent because the configuration names headers whose values
194    /// this worker does not hold. Neither a success nor a failure.
195    pub const MISSING_SECRET: &str = "health_check.missing_secret";
196}
197
198/// H1 protocol counters.
199pub mod h1 {
200    pub const BACKEND_EOF_BEFORE_MESSAGE_COMPLETE: &str = "h1.backend_eof_before_message_complete";
201}
202
203/// H2 multiplexer counters — frame TX, flood mitigations, header policy
204/// rejections, signal-writable rearm sites, and other H2-specific buckets.
205pub mod h2 {
206    pub const CLOSE_WITH_ACTIVE_STREAMS: &str = "h2.close_with_active_streams";
207    pub const COALESCING_ACCEPTED: &str = "h2.coalescing.accepted";
208    pub const CONNECTION_ACTIVE_STREAMS: &str = "h2.connection.active_streams";
209    pub const CONNECTION_PENDING_WINDOW_UPDATES: &str = "h2.connection.pending_window_updates";
210    pub const CONNECTION_WINDOW_BYTES: &str = "h2.connection.window_bytes";
211    pub const FLOW_CONTROL_STALL: &str = "h2.flow_control_stall";
212
213    // Per-stream reap counters — `cancel_timed_out_streams` breaks reaps down by
214    // which guard tripped (M1), plus a stall-budget subset for the
215    // `WINDOW_UPDATE`-drip vector the cumulative-stall budget closes (M2).
216    // `reaped.stall_budget` is a subset of `reaped.window_stall`.
217    pub const STREAMS_REAPED_IDLE_TIMEOUT: &str = "h2.streams.reaped.idle_timeout";
218    pub const STREAMS_REAPED_WINDOW_STALL: &str = "h2.streams.reaped.window_stall";
219    pub const STREAMS_REAPED_STALL_BUDGET: &str = "h2.streams.reaped.stall_budget";
220
221    /// Streams that were ready to emit AND marked incremental (RFC 9218 §4),
222    /// summed over the urgency buckets of one write pass and aggregated across
223    /// every live H2 connection. Emitted as a signed delta that
224    /// `ConnectionH2::gauge_connection_state` returns as a `MetricEvent`, with
225    /// the matching teardown in `impl Drop for H2Shell`, exactly like the
226    /// `CONNECTION_*` trio below it.
227    pub const STREAMS_READY_INCREMENTAL_BY_URGENCY: &str =
228        "h2.streams.ready_incremental.by_urgency";
229
230    // Frame-TX counters (frame type fanout).
231    pub const FRAMES_TX_CONTINUATION: &str = "h2.frames.tx.continuation";
232    pub const FRAMES_TX_DATA: &str = "h2.frames.tx.data";
233    pub const FRAMES_TX_GOAWAY: &str = "h2.frames.tx.goaway";
234    pub const FRAMES_TX_HEADERS: &str = "h2.frames.tx.headers";
235    pub const FRAMES_TX_PING_ACK: &str = "h2.frames.tx.ping_ack";
236    pub const FRAMES_TX_RST_STREAM: &str = "h2.frames.tx.rst_stream";
237    pub const FRAMES_TX_SETTINGS: &str = "h2.frames.tx.settings";
238    pub const FRAMES_TX_WINDOW_UPDATE: &str = "h2.frames.tx.window_update";
239
240    // Header-policy rejections (the `h2.headers.rejected.*` family).
241    pub const HEADERS_NO_STREAM_ERROR: &str = "h2.headers_no_stream.error";
242    pub const HEADERS_REJECTED_BUDGET_OVERRUN: &str = "h2.headers.rejected.budget_overrun";
243    pub const HEADERS_REJECTED_TOTAL: &str = "h2.headers.rejected.total";
244
245    pub const RST_STREAM_RECEIVED_PRE_RESPONSE_START: &str =
246        "h2.rst_stream.received.pre_response_start";
247
248    /// A proxy-emitted RST_STREAM that was never queued because
249    /// `pending_rst_streams` was already at its bound (`pending_rst_bound`,
250    /// at least `MIN_PENDING_RST_STREAMS`).
251    /// Sibling of `WINDOW_UPDATE_DROPPED` below, same "control queue full"
252    /// semantics; a non-zero value means the connection is on its way to
253    /// `GOAWAY(ENHANCE_YOUR_CALM)`.
254    pub const RST_STREAM_DROPPED: &str = "h2.rst_stream_dropped";
255
256    // Writable-rearm signal counters — one per rearm reason.
257    pub const SIGNAL_WRITABLE_REARMED_CONTROL_QUEUE: &str =
258        "h2.signal.writable.rearmed.control_queue";
259    pub const SIGNAL_WRITABLE_REARMED_DEFAULT_ANSWER: &str =
260        "h2.signal.writable.rearmed.default_answer";
261    pub const SIGNAL_WRITABLE_REARMED_FORCEFULLY_TERMINATE_ANSWER: &str =
262        "h2.signal.writable.rearmed.forcefully_terminate_answer";
263    pub const SIGNAL_WRITABLE_REARMED_PEER_DATA: &str = "h2.signal.writable.rearmed.peer_data";
264    pub const SIGNAL_WRITABLE_REARMED_PEER_HEADERS: &str =
265        "h2.signal.writable.rearmed.peer_headers";
266    pub const SIGNAL_WRITABLE_REARMED_PRIORITY_UPDATE: &str =
267        "h2.signal.writable.rearmed.priority_update";
268
269    pub const TRAILERS_DROPPED_CONTENT_LENGTH: &str = "h2.trailers_dropped_content_length";
270    /// H2 trailer blocks of a response to HEAD, a 204 or a 304 dropped by
271    /// `ConnectionH1::writable` towards an H1 client, which reads such a
272    /// response as ending with its header section (RFC 9112 §6.3).
273    pub const TRAILERS_DROPPED_NO_BODY: &str = "h2.trailers_dropped_no_body";
274    /// Backend streams reset by `ConnectionH2::handle_data_frame` because a
275    /// 204 or a 304 response carried a DATA payload (RFC 9110 §15.3.5,
276    /// §15.4.5; RFC 9113 §8.1.1).
277    pub const BODILESS_RESPONSE_DATA_RESET: &str = "h2.bodiless_response_data_reset";
278    /// Request trailer fields elided by `pkawa::handle_trailer` because their
279    /// name is one of `editor::TRAILER_FORBIDDEN_FIELDS` (sozu-proxy/sozu#1714).
280    /// One increment per field; the request is still forwarded. The H1
281    /// frontend counts the same drop in `http::TRAILER_FORBIDDEN_FIELD_ELIDED`.
282    pub const TRAILER_FORBIDDEN_FIELD_ELIDED: &str = "h2.trailer.forbidden_field_elided";
283    pub const TRAILER_SPOOF_VECTOR_ELIDED: &str = "h2.trailer.spoof_vector_elided";
284    pub const WINDOW_UPDATE_DROPPED: &str = "h2.window_update_dropped";
285
286    // Flood-mitigation violation counters — one per flood class the H2
287    // mux's `H2FloodDetector` recognises. Surfaced in the TUI's H2 pane
288    // so operators can spot a flood-pattern before it pages.
289    pub const FLOOD_VIOLATION_GLITCH_WINDOW: &str = "h2.flood.violation.glitch_window";
290    pub const FLOOD_VIOLATION_RST_STREAM_WINDOW: &str = "h2.flood.violation.rst_stream_window";
291    pub const FLOOD_VIOLATION_RST_STREAM_LIFETIME: &str = "h2.flood.violation.rst_stream_lifetime";
292    pub const FLOOD_VIOLATION_RST_STREAM_PRE_RESPONSE_LIFETIME: &str =
293        "h2.flood.violation.rst_stream_pre_response_lifetime";
294    pub const FLOOD_VIOLATION_RST_STREAM_EMITTED_LIFETIME: &str =
295        "h2.flood.violation.rst_stream_emitted_lifetime";
296    pub const FLOOD_VIOLATION_PING_WINDOW: &str = "h2.flood.violation.ping_window";
297    pub const FLOOD_VIOLATION_PING_LIFETIME: &str = "h2.flood.violation.ping_lifetime";
298    pub const FLOOD_VIOLATION_SETTINGS_WINDOW: &str = "h2.flood.violation.settings_window";
299    pub const FLOOD_VIOLATION_SETTINGS_LIFETIME: &str = "h2.flood.violation.settings_lifetime";
300    pub const FLOOD_VIOLATION_EMPTY_DATA_WINDOW: &str = "h2.flood.violation.empty_data_window";
301    pub const FLOOD_VIOLATION_CONTINUATION_PER_BLOCK: &str =
302        "h2.flood.violation.continuation_per_block";
303    pub const FLOOD_VIOLATION_WINDOW_UPDATE_STREAM0_WINDOW: &str =
304        "h2.flood.violation.window_update_stream0_window";
305    pub const FLOOD_VIOLATION_HEADER_SIZE_PER_BLOCK: &str =
306        "h2.flood.violation.header_size_per_block";
307    /// Every `metric_key` an `H2FloodViolation` carries, one per flood class
308    /// (`lib/src/protocol/mux/h2_flood_detector.rs`). The detector's
309    /// `test_flood_violation_metric_keys_are_unique_and_namespaced` pins this
310    /// list to what it emits, and `sozu top` reads its H2 pane rows from it.
311    pub const FLOOD_VIOLATION_KEYS: [&str; 13] = [
312        FLOOD_VIOLATION_RST_STREAM_PRE_RESPONSE_LIFETIME,
313        FLOOD_VIOLATION_RST_STREAM_LIFETIME,
314        FLOOD_VIOLATION_RST_STREAM_EMITTED_LIFETIME,
315        FLOOD_VIOLATION_RST_STREAM_WINDOW,
316        FLOOD_VIOLATION_CONTINUATION_PER_BLOCK,
317        FLOOD_VIOLATION_HEADER_SIZE_PER_BLOCK,
318        FLOOD_VIOLATION_PING_WINDOW,
319        FLOOD_VIOLATION_PING_LIFETIME,
320        FLOOD_VIOLATION_SETTINGS_WINDOW,
321        FLOOD_VIOLATION_SETTINGS_LIFETIME,
322        FLOOD_VIOLATION_EMPTY_DATA_WINDOW,
323        FLOOD_VIOLATION_WINDOW_UPDATE_STREAM0_WINDOW,
324        FLOOD_VIOLATION_GLITCH_WINDOW,
325    ];
326    /// A new client stream refused with `REFUSED_STREAM` in the soft state
327    /// below the pre-response RST_STREAM cap (`h2_stream_refusal_percent`).
328    /// The connection stays open; a rising rate is the early warning before
329    /// `h2.flood.violation.rst_stream_pre_response_lifetime`.
330    pub const FLOOD_STREAM_REFUSED: &str = "h2.flood.stream_refused";
331}
332
333/// HTTP counters (H1 + H2 share these); see `https` for the HTTPS-specific
334/// variants and `h2` for H2-frame-level counters.
335pub mod http {
336    pub const ERR_400: &str = "http.400.errors";
337    pub const ERR_401: &str = "http.401.errors";
338    pub const ERR_404: &str = "http.404.errors";
339    pub const ERR_408: &str = "http.408.errors";
340    pub const ERR_413: &str = "http.413.errors";
341    pub const ERR_421: &str = "http.421.errors";
342    pub const ERR_502: &str = "http.502.errors";
343    pub const ERR_503: &str = "http.503.errors";
344    pub const ERR_504: &str = "http.504.errors";
345    pub const ERR_507: &str = "http.507.errors";
346    /// Catch-all for the default answers that have no dedicated `ERR_*`
347    /// bucket; incremented alongside the per-code counter where one exists.
348    pub const ERRORS: &str = "http.errors";
349    pub const REDIRECTION_301: &str = "http.301.redirection";
350    pub const REDIRECTION_302: &str = "http.302.redirection";
351    pub const REDIRECTION_308: &str = "http.308.redirection";
352    pub const ACTIVE_REQUESTS: &str = "http.active_requests";
353    pub const ALPN_H2: &str = "http.alpn.h2";
354    pub const ALPN_HTTP11: &str = "http.alpn.http11";
355    pub const BACKEND_PARSE_ERRORS: &str = "http.backend_parse_errors";
356    pub const E2E_H2: &str = "http.e2e.h2";
357    pub const E2E_HTTP11: &str = "http.e2e.http11";
358    pub const EARLY_RESPONSE_CLOSE: &str = "http.early_response_close";
359    /// Cleartext connections an `h2c_prior_knowledge` HTTP listener served as
360    /// HTTP/2 because their first bytes were the connection preface — the
361    /// cleartext counterpart of [`ALPN_H2`].
362    pub const H2C_PRIOR_KNOWLEDGE: &str = "http.h2c.prior_knowledge";
363    /// H1 client connections closed because a response completed before its
364    /// request was received whole (a backend answering an upload early): the
365    /// rest of the body is never read as a request (sozu-proxy/sozu#1721).
366    pub const CLOSE_REQUEST_INCOMPLETE: &str = "http.close.request_incomplete";
367    pub const FAILED_BACKEND_MATCHING: &str = "http.failed_backend_matching";
368    /// Client `Forwarded` lines removed because their value is not a
369    /// well-formed RFC 7239 §4 list, counted once per removed line. Only the
370    /// `both` and `rfc7239` `forwarded_headers` modes, which extend the chain,
371    /// remove such a line; the request itself is still forwarded.
372    pub const FORWARDED_MALFORMED_ELIDED: &str = "http.forwarded_malformed_elided";
373    /// Requests rejected because a Transfer-Encoding header survived parsing without
374    /// kawa adopting chunked framing (CL.TE request-smuggling defense, CWE-444).
375    pub const FRONTEND_TE_SMUGGLING: &str = "http.frontend.transfer_encoding_smuggling";
376    /// Requests rejected (400) because a forwarded Content-Length value was not
377    /// `1*DIGIT`, e.g. `+5` (RFC 9110 §8.6; request-smuggling defense, CWE-444).
378    /// Defense in depth: kawa >= 0.7.2 refuses such a value first, counted in
379    /// `FRONTEND_PARSE_ERRORS`, so this stays at zero unless kawa regresses.
380    pub const FRONTEND_CONTENT_LENGTH_INVALID: &str = "http.frontend.content_length_invalid";
381    /// Backend responses rejected (502) because their Content-Length value was
382    /// not `1*DIGIT` (RFC 9110 §8.6, RFC 9112 §6.3). Defense in depth:
383    /// kawa >= 0.7.2 refuses such a response first, counted in
384    /// `BACKEND_PARSE_ERRORS`, so this stays at zero unless kawa regresses.
385    pub const BACKEND_CONTENT_LENGTH_INVALID: &str = "http.backend.content_length_invalid";
386    pub const FRONTEND_PARSE_ERRORS: &str = "http.frontend_parse_errors";
387    pub const HSTS_FRONTEND_ADDED: &str = "http.hsts.frontend_added";
388    pub const HSTS_FRONTEND_REFRESHED: &str = "http.hsts.frontend_refreshed";
389    pub const HSTS_LISTENER_DEFAULT_PATCHED: &str = "http.hsts.listener_default_patched";
390    pub const HSTS_SUPPRESSED_PLAINTEXT: &str = "http.hsts.suppressed_plaintext";
391    pub const HSTS_UNRENDERED: &str = "http.hsts.unrendered";
392    pub const INFINITE_LOOP_ERROR: &str = "http.infinite_loop.error";
393    /// `Mux::ready_inner` reached `MAX_LOOP_ITERATIONS` while forwarding
394    /// bytes and yielded to the event loop, which resumes it on its next turn
395    /// (sozu-proxy/sozu#1906). Not an error: a transfer whose peers keep up.
396    pub const MUX_LOOP_BUDGET_YIELD: &str = "http.mux.loop_budget_yield";
397    pub const REDIRECT_TEMPLATE_COMPILE_ERROR: &str = "http.redirect_template.compile_error";
398    pub const REQUESTS: &str = "http.requests";
399    pub const SNI_AUTHORITY_MISMATCH: &str = "http.sni_authority_mismatch";
400    pub const STATUS_1XX: &str = "http.status.1xx";
401    pub const STATUS_2XX: &str = "http.status.2xx";
402    pub const STATUS_3XX: &str = "http.status.3xx";
403    pub const STATUS_4XX: &str = "http.status.4xx";
404    pub const STATUS_5XX: &str = "http.status.5xx";
405    pub const STATUS_OTHER: &str = "http.status.other";
406    /// H1 chunked requests rejected because their trailer section carried
407    /// more fields than the listener's `h2_max_header_fields`, the bound the
408    /// H2 frontend applies to a trailer block (sozu-proxy/sozu#1701). One
409    /// increment per request; the request is answered 400.
410    pub const TRAILER_FIELD_LIMIT_EXCEEDED: &str = "http.trailer.field_limit_exceeded";
411    /// Trailer fields elided from an H1 chunked request because their name is
412    /// one of `editor::TRAILER_FORBIDDEN_FIELDS` (framing, routing, request
413    /// modifiers, authentication, content processing or connection-specific
414    /// fields a trailer must not carry, RFC 9110 §6.5.1;
415    /// sozu-proxy/sozu#1701). One increment per field. The H2 frontend
416    /// counts the same drop in `h2::TRAILER_FORBIDDEN_FIELD_ELIDED`.
417    pub const TRAILER_FORBIDDEN_FIELD_ELIDED: &str = "http.trailer.forbidden_field_elided";
418    /// Trailer fields elided from an H1 chunked request because their name is
419    /// one of `editor::TRAILER_SPOOF_VECTOR_HEADERS` (client attribution a
420    /// trailer must not carry, RFC 9110 §6.5.1; sozu-proxy/sozu#1689). One
421    /// increment per field. The H2 frontend counts the same drop in
422    /// `h2::TRAILER_SPOOF_VECTOR_ELIDED`.
423    pub const TRAILER_SPOOF_VECTOR_ELIDED: &str = "http.trailer.spoof_vector_elided";
424    pub const TRUSTING_X_PORT: &str = "http.trusting.x_port";
425    pub const TRUSTING_X_PORT_DIFF: &str = "http.trusting.x_port.diff";
426    pub const TRUSTING_X_PROTO: &str = "http.trusting.x_proto";
427    pub const TRUSTING_X_PROTO_DIFF: &str = "http.trusting.x_proto.diff";
428    pub const UPGRADE_EXPECT_FAILED: &str = "http.upgrade.expect.failed";
429    /// The h2c preface sniff decided, but the mux could not be built (stream
430    /// or H2 buffer allocation failed).
431    pub const UPGRADE_H2C_FAILED: &str = "http.upgrade.h2c.failed";
432    pub const UPGRADE_MUX_FAILED: &str = "http.upgrade.mux.failed";
433    pub const UPGRADE_WS_FAILED: &str = "http.upgrade.ws.failed";
434    pub const X_REQUEST_ID_GENERATED: &str = "http.x_request_id.generated";
435    pub const X_REQUEST_ID_PROPAGATED: &str = "http.x_request_id.propagated";
436}
437
438/// Per-status-code HTTP counters. Only the codes Sōzu either generates as
439/// a default answer or that operators routinely chart get a dedicated bucket;
440/// the rest fold into the `http::STATUS_*XX` parent buckets.
441pub mod http_status {
442    pub const S200: &str = "http.status.200";
443    pub const S201: &str = "http.status.201";
444    pub const S204: &str = "http.status.204";
445    pub const S301: &str = "http.status.301";
446    pub const S302: &str = "http.status.302";
447    pub const S304: &str = "http.status.304";
448    pub const S400: &str = "http.status.400";
449    pub const S401: &str = "http.status.401";
450    pub const S403: &str = "http.status.403";
451    pub const S404: &str = "http.status.404";
452    pub const S408: &str = "http.status.408";
453    pub const S413: &str = "http.status.413";
454    pub const S429: &str = "http.status.429";
455    pub const S500: &str = "http.status.500";
456    pub const S502: &str = "http.status.502";
457    pub const S503: &str = "http.status.503";
458    pub const S504: &str = "http.status.504";
459    pub const S507: &str = "http.status.507";
460}
461
462/// HTTPS-specific counters; see `http` for the HTTP family these complement.
463pub mod https {
464    pub const ALPN_REJECTED_HTTP11_DISABLED: &str = "https.alpn.rejected.http11_disabled";
465    pub const ALPN_REJECTED_UNSUPPORTED: &str = "https.alpn.rejected.unsupported";
466    pub const UPGRADE_EXPECT_FAILED: &str = "https.upgrade.expect.failed";
467    pub const UPGRADE_HANDSHAKE_FAILED: &str = "https.upgrade.handshake.failed";
468    pub const UPGRADE_MUX_FAILED: &str = "https.upgrade.mux.failed";
469    pub const UPGRADE_WSS_FAILED: &str = "https.upgrade.wss.failed";
470}
471
472/// Per-listener counters.
473pub mod listener {
474    pub const ACCEPTED_TOTAL: &str = "listener.accepted.total";
475    pub const CONNECTION_CAPPED: &str = "listener.connection_capped";
476}
477
478/// Pipe-protocol counters.
479pub mod pipe {
480    pub const ERRORS: &str = "pipe.errors";
481}
482
483/// Worker-process runtime gauges.
484pub mod process {
485    pub const UPTIME_SECONDS: &str = "process.uptime_seconds";
486}
487
488/// Protocol-type counters that increment once per session and track which
489/// protocol carried it end-to-end.
490pub mod protocol {
491    pub const HTTP: &str = "protocol.http";
492    pub const HTTPS: &str = "protocol.https";
493    pub const PROXY_EXPECT: &str = "protocol.proxy.expect";
494    pub const PROXY_RELAY: &str = "protocol.proxy.relay";
495    pub const PROXY_SEND: &str = "protocol.proxy.send";
496    pub const TCP: &str = "protocol.tcp";
497    pub const TLS_HANDSHAKE: &str = "protocol.tls.handshake";
498    pub const WS: &str = "protocol.ws";
499    pub const WSS: &str = "protocol.wss";
500}
501
502/// PROXY-protocol counters.
503pub mod proxy_protocol {
504    pub const ERRORS: &str = "proxy_protocol.errors";
505}
506
507/// `rustls`-specific counters for read/write infinite-loop guards.
508pub mod rustls {
509    pub const READ_ERROR: &str = "rustls.read.error";
510    pub const READ_INFINITE_LOOP_ERROR: &str = "rustls.read.infinite_loop.error";
511    /// A TLS frontend's `recv(2)` answered `ECONNRESET`/`ECONNABORTED`: the
512    /// peer reset the connection (sozu-proxy/sozu#434). Counted once per
513    /// reset, during the handshake or after it.
514    pub const READ_RESET: &str = "rustls.read.reset";
515    pub const WRITE_ERROR: &str = "rustls.write.error";
516    /// A TLS frontend's `writev(2)` answered `ECONNRESET`/`ECONNABORTED`.
517    /// Still counted under [`WRITE_ERROR`] as well, as it was before this
518    /// counter existed.
519    pub const WRITE_RESET: &str = "rustls.write.reset";
520    pub const WRITE_INFINITE_LOOP_ERROR: &str = "rustls.write.infinite_loop.error";
521}
522
523/// Worker liveness gauge, emitted by the server tick.
524pub mod server {
525    pub const LIVE: &str = "server.live";
526}
527
528/// Generic session-level counters.
529pub mod sessions {
530    pub const EVICTED: &str = "sessions.evicted";
531}
532
533/// Slab-allocator gauges.
534pub mod slab {
535    pub const ACCEPT_THRESHOLD_PERCENT: &str = "slab.accept_threshold_percent";
536    pub const CAPACITY: &str = "slab.capacity";
537    pub const ENTRIES: &str = "slab.entries";
538    pub const USAGE_PERCENT: &str = "slab.usage_percent";
539}
540
541/// Raw-socket counters.
542pub mod socket {
543    pub const WRITE_INFINITE_LOOP_ERROR: &str = "socket.write.infinite_loop.error";
544}
545
546/// TCP protocol counters.
547pub mod tcp {
548    pub const INFINITE_LOOP_ERROR: &str = "tcp.infinite_loop.error";
549    pub const READ_ERROR: &str = "tcp.read.error";
550    /// A plain TCP socket's `recv(2)` or `splice(2)` answered
551    /// `ECONNRESET`/`ECONNABORTED`: the peer reset the connection
552    /// (sozu-proxy/sozu#434). Frontend and backend sockets alike.
553    pub const READ_RESET: &str = "tcp.read.reset";
554    pub const REQUESTS: &str = "tcp.requests";
555    pub const UPGRADE_EXPECT_FAILED: &str = "tcp.upgrade.expect.failed";
556    pub const UPGRADE_PIPE_FAILED: &str = "tcp.upgrade.pipe.failed";
557    pub const UPGRADE_RELAY_FAILED: &str = "tcp.upgrade.relay.failed";
558    pub const UPGRADE_SEND_FAILED: &str = "tcp.upgrade.send.failed";
559    pub const UPGRADE_SNI_PREREAD_FAILED: &str = "tcp.upgrade.sni_preread.failed";
560    pub const WRITE_ERROR: &str = "tcp.write.error";
561    /// A plain TCP socket's `send(2)`/`writev(2)` or `splice(2)` answered
562    /// `ECONNRESET`/`ECONNABORTED`. The `send` paths still count it under
563    /// [`WRITE_ERROR`] as well, as they did before this counter existed.
564    pub const WRITE_RESET: &str = "tcp.write.reset";
565
566    /// SNI-preread counters (TCP passthrough routing, sozu-proxy/sozu#1279),
567    /// fed by the shell in `lib/src/protocol/tcp_preread/shell.rs` (decision
568    /// metrics) and `lib/src/tcp.rs` (the `ACTIVE` gauge + `DURATION`
569    /// timing, both owned centrally by the session lifecycle so each is
570    /// touched exactly once per session regardless of which of the three
571    /// exits -- reject / upgrade / teardown -- it takes).
572    pub mod sni_preread {
573        use crate::protocol::tcp_preread::RejectReason;
574
575        /// A cluster was chosen from the ClientHello's SNI (+ optional ALPN).
576        pub const ROUTED: &str = "tcp.sni_preread.routed";
577        /// Gauge: sessions currently prereading. Incremented once at
578        /// `SniPreread` construction, decremented exactly once on every
579        /// exit (reject / upgrade / teardown); underflow is a bug.
580        pub const ACTIVE: &str = "tcp.sni_preread.active";
581        /// `time!` of the wall-clock spent prereading, recorded on every
582        /// exit alongside the `ACTIVE` decrement.
583        pub const DURATION: &str = "tcp.sni_preread.duration";
584
585        pub const REJECTED_NOT_TLS: &str = "tcp.sni_preread.rejected.not_tls";
586        pub const REJECTED_MALFORMED_RECORD: &str = "tcp.sni_preread.rejected.malformed_record";
587        pub const REJECTED_MALFORMED_HANDSHAKE: &str =
588            "tcp.sni_preread.rejected.malformed_handshake";
589        pub const REJECTED_FRAGMENTED: &str = "tcp.sni_preread.rejected.fragmented";
590        pub const REJECTED_TOO_LARGE: &str = "tcp.sni_preread.rejected.too_large";
591        pub const REJECTED_NO_SNI: &str = "tcp.sni_preread.rejected.no_sni";
592        pub const REJECTED_ECH_OUTER_ABSENT: &str = "tcp.sni_preread.rejected.ech_outer_absent";
593        pub const REJECTED_SNI_UNMATCHED: &str = "tcp.sni_preread.rejected.sni_unmatched";
594        pub const REJECTED_ALPN_UNMATCHED: &str = "tcp.sni_preread.rejected.alpn_unmatched";
595        pub const REJECTED_PROXY_HEADER_INVALID: &str =
596            "tcp.sni_preread.rejected.proxy_header_invalid";
597        pub const REJECTED_FRONT_CLOSED: &str = "tcp.sni_preread.rejected.front_closed";
598
599        /// Total function over every `RejectReason` variant: a new variant
600        /// added to the core without a matching arm here fails to compile,
601        /// so the metric surface can never silently drop a rejection reason.
602        pub fn rejected_name(reason: RejectReason) -> &'static str {
603            match reason {
604                RejectReason::NotTls => REJECTED_NOT_TLS,
605                RejectReason::MalformedRecord => REJECTED_MALFORMED_RECORD,
606                RejectReason::MalformedHandshake => REJECTED_MALFORMED_HANDSHAKE,
607                RejectReason::Fragmented => REJECTED_FRAGMENTED,
608                RejectReason::TooLarge => REJECTED_TOO_LARGE,
609                RejectReason::NoSni => REJECTED_NO_SNI,
610                RejectReason::EchOuterAbsent => REJECTED_ECH_OUTER_ABSENT,
611                RejectReason::SniUnmatched => REJECTED_SNI_UNMATCHED,
612                RejectReason::AlpnUnmatched => REJECTED_ALPN_UNMATCHED,
613                RejectReason::ProxyHeaderInvalid => REJECTED_PROXY_HEADER_INVALID,
614                RejectReason::FrontClosed => REJECTED_FRONT_CLOSED,
615            }
616        }
617    }
618}
619
620/// UDP datapath counters/gauges, fed by the UDP I/O shell in `lib/src/udp.rs`.
621///
622/// `IN`/`OUT` follow the client perspective: `IN` = client→backend
623/// (request), `OUT` = backend→client (reply). `ACTIVE_FLOWS` is a gauge that
624/// must never underflow — it is incremented exactly once per admitted flow
625/// (`FLOWS_CREATED`) and decremented exactly once per close.
626pub mod udp {
627    /// Client→backend datagrams forwarded.
628    pub const DATAGRAMS_IN: &str = "udp.datagrams.in";
629    /// Backend→client datagrams returned.
630    pub const DATAGRAMS_OUT: &str = "udp.datagrams.out";
631    /// Payload bytes forwarded client→backend (excludes PPv2 prefix).
632    pub const BYTES_IN: &str = "udp.bytes.in";
633    /// Payload bytes returned backend→client.
634    pub const BYTES_OUT: &str = "udp.bytes.out";
635    /// Gauge: currently admitted flows. Never underflows.
636    pub const ACTIVE_FLOWS: &str = "udp.active_flows";
637    /// Flows admitted since boot.
638    pub const FLOWS_CREATED: &str = "udp.flows.created";
639    /// Flows torn down (idle / teardown / drain).
640    pub const FLOWS_EVICTED: &str = "udp.flows.evicted";
641    /// New flows shed at the `max_flows` cap, at a per-source limit, or
642    /// under fd pressure.
643    pub const FLOWS_SHED: &str = "udp.flows.shed";
644    /// New flows shed because their source IP or subnet held the cluster's
645    /// `max_connections_per_ip` / `max_connections_per_subnet` flows. Also
646    /// counted in [`FLOWS_SHED`].
647    pub const FLOWS_SHED_SOURCE_LIMIT: &str = "udp.flows.shed.source_limit";
648    /// Datagrams dropped before allocation (aggregate). Reason-specific
649    /// counters below carry the dotted `.<reason>` suffix; both are emitted
650    /// so dashboards can chart the total or break it down. The `incr!` macro
651    /// needs `&'static str`, so the by-reason names are fixed constants
652    /// rather than a runtime-formatted suffix.
653    pub const DATAGRAMS_DROPPED: &str = "udp.datagrams.dropped";
654    /// Dropped: failed validation (empty / extractor rejected).
655    pub const DROPPED_INVALID: &str = "udp.datagrams.dropped.invalid";
656    /// Dropped: exceeded `max_rx_datagram_size` (truncation surrogate).
657    pub const DROPPED_TRUNCATED: &str = "udp.datagrams.dropped.truncated";
658    /// Dropped: no cluster/backend configured for the listener.
659    pub const DROPPED_NO_BACKEND: &str = "udp.datagrams.dropped.no_backend";
660    /// Dropped: flow-table cap reached (shed).
661    pub const DROPPED_SHED: &str = "udp.datagrams.dropped.shed";
662    /// Dropped: referenced an unknown / already-closed flow.
663    pub const DROPPED_UNKNOWN_FLOW: &str = "udp.datagrams.dropped.unknown_flow";
664    /// Dropped: the bounded per-flow / client-return write queue was at
665    /// capacity (genuine egress-queue overflow under sustained `WouldBlock`).
666    pub const DROPPED_WQ_FULL: &str = "udp.datagrams.dropped.wq_full";
667    /// Dropped: a hard `send`/`send_to` error (e.g. ECONNREFUSED) on the egress
668    /// path, distinct from a queue-full overflow. The egress "no resolved flow /
669    /// socket gone" case routes to [`DROPPED_UNKNOWN_FLOW`] instead, so
670    /// `wq_full` only counts genuine bounded-queue overflow.
671    pub const DROPPED_SEND_ERROR: &str = "udp.datagrams.dropped.send_error";
672    /// Backend health transitions observed by the UDP health prober.
673    pub const BACKEND_HEALTH: &str = "udp.backend.health";
674    /// `time!` of a flow's lifetime, recorded on close.
675    pub const FLOW_DURATION: &str = "udp.flow.duration";
676}
677
678/// TLS counters (certificate inventory, handshake timing).
679pub mod tls {
680    pub const CERT_MIN_EXPIRES_AT_SECONDS: &str = "tls.cert.min_expires_at_seconds";
681    pub const DEFAULT_CERT_USED: &str = "tls.default_cert_used";
682    pub const HANDSHAKE_MS: &str = "tls.handshake_ms";
683}
684
685/// WebSocket counters.
686pub mod websocket {
687    pub const ACTIVE_REQUESTS: &str = "websocket.active_requests";
688}
689
690/// Miscellaneous counters that don't fit a richer family.
691pub mod misc {
692    pub const PANIC: &str = "panic";
693    pub const ZOMBIES: &str = "zombies";
694}