use std::{cell::RefCell, collections::HashMap};
thread_local! {
static HTTP_METRICS: RefCell<HashMap<HttpMetricKey, u64>> = RefCell::new(HashMap::new());
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct HttpMetricKey {
pub method: HttpMethod,
pub label: String,
}
#[derive(Clone)]
pub struct HttpMetricsSnapshot {
pub entries: Vec<(HttpMetricKey, u64)>,
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum HttpMethod {
Get,
Post,
Head,
}
impl HttpMethod {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Get => "GET",
Self::Post => "POST",
Self::Head => "HEAD",
}
}
}
pub struct HttpMetrics;
impl HttpMetrics {
pub fn record_http_request(method: HttpMethod, url: &str, label: Option<&str>) {
let label = label.map_or_else(|| normalize_http_label(url, label), str::to_string);
Self::increment(method, &label);
}
fn increment(method: HttpMethod, label: &str) {
HTTP_METRICS.with_borrow_mut(|counts| {
let key = HttpMetricKey {
method,
label: label.to_string(),
};
let entry = counts.entry(key).or_insert(0);
*entry = entry.saturating_add(1);
});
}
#[must_use]
pub fn snapshot() -> HttpMetricsSnapshot {
let entries = HTTP_METRICS
.with_borrow(std::clone::Clone::clone)
.into_iter()
.collect();
HttpMetricsSnapshot { entries }
}
}
#[must_use]
pub fn normalize_http_label(url: &str, label: Option<&str>) -> String {
if let Some(label) = label {
return label.to_string();
}
let without_fragment = url.split('#').next().unwrap_or(url);
let without_query = without_fragment
.split('?')
.next()
.unwrap_or(without_fragment);
let trimmed = without_query.trim();
if trimmed.is_empty() {
url.to_string()
} else {
trimmed.to_string()
}
}