Skip to main content

switchyard_llm_client/
metrics.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Metric labelling inherited from Python
5
6use opentelemetry::{KeyValue, global};
7
8pub(crate) const fn is_retryable_http_status(status: u16) -> bool {
9    status == 408 || status == 429 || (status >= 500 && status <= 599)
10}
11
12pub const fn http_outcome_label(status: Option<u16>) -> &'static str {
13    match status {
14        Some(200..=299) => "success",
15        Some(status) if is_retryable_http_status(status) => "retryable_error",
16        None => "retryable_error",
17        Some(_) => "other_error",
18    }
19}
20
21/// Limit the cardinality of the HTTP status code.
22/// This matches Python, but likely we should log the status code directly. Most of these never
23/// appear.
24pub const fn http_status_code_label(status: Option<u16>) -> &'static str {
25    match status {
26        None => "none",
27        Some(200) => "200",
28        Some(400) => "400",
29        Some(401) => "401",
30        Some(403) => "403",
31        Some(404) => "404",
32        Some(408) => "408",
33        Some(409) => "409",
34        Some(422) => "422",
35        Some(429) => "429",
36        Some(500) => "500",
37        Some(502) => "502",
38        Some(503) => "503",
39        Some(504) => "504",
40        Some(100..=199) => "1xx",
41        Some(200..=299) => "2xx",
42        Some(300..=399) => "3xx",
43        Some(400..=499) => "4xx",
44        Some(500..=599) => "5xx",
45        Some(_) => "other",
46    }
47}
48
49pub(crate) fn record_upstream_attempt(status: Option<u16>) {
50    global::meter("switchyard")
51        .u64_counter("switchyard.upstream_attempts")
52        .build()
53        .add(
54            1,
55            &[
56                KeyValue::new("outcome", http_outcome_label(status)),
57                KeyValue::new("code", http_status_code_label(status)),
58            ],
59        );
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn outcome_labels_match_the_retry_policy() {
68        for status in [408, 429, 500, 502, 599] {
69            assert!(is_retryable_http_status(status));
70            assert_eq!(http_outcome_label(Some(status)), "retryable_error");
71        }
72        for status in [400, 409, 499, 600] {
73            assert!(!is_retryable_http_status(status));
74            assert_eq!(http_outcome_label(Some(status)), "other_error");
75        }
76        assert_eq!(http_outcome_label(None), "retryable_error");
77    }
78}