use prometheus::{
Encoder, IntCounterVec, IntGauge, Registry, TextEncoder,
register_int_counter_vec_with_registry, register_int_gauge_with_registry,
};
use std::sync::OnceLock;
pub struct Metrics {
pub registry: Registry,
pub render_route_decision_total: IntCounterVec,
pub circuit_breaker_open_total: IntCounterVec,
pub host_preferences_promotions_total: IntCounterVec,
pub admin_preferences_reset_total: IntCounterVec,
pub user_pin_total: IntCounterVec,
pub host_preferences_size: IntGauge,
pub chrome_budget_truncated_total: IntCounterVec,
pub chrome_blocked_requests_total: IntCounterVec,
pub breaker_ignored_total: IntCounterVec,
pub cdp_pending_requests: IntGauge,
pub cdp_live_connections: IntGauge,
pub target_lifecycle_total: IntCounterVec,
pub renderer_recycle_total: IntCounterVec,
}
static METRICS: OnceLock<Metrics> = OnceLock::new();
pub fn metrics() -> &'static Metrics {
METRICS.get_or_init(Metrics::new)
}
pub fn init() {
let _ = metrics();
}
impl Metrics {
fn new() -> Self {
let registry = Registry::new();
let render_route_decision_total = register_int_counter_vec_with_registry!(
"crw_render_route_decision_total",
"Routing decisions by chosen renderer and decision kind",
&["renderer", "decision"],
registry
)
.unwrap();
let circuit_breaker_open_total = register_int_counter_vec_with_registry!(
"crw_circuit_breaker_open_total",
"Circuit breaker transitions to Open, labeled by renderer and scope",
&["renderer", "scope"],
registry
)
.unwrap();
let host_preferences_promotions_total = register_int_counter_vec_with_registry!(
"crw_host_preferences_promotions_total",
"Host preference promotions to a heavier renderer",
&["from", "to"],
registry
)
.unwrap();
let admin_preferences_reset_total = register_int_counter_vec_with_registry!(
"crw_admin_preferences_reset_total",
"Admin resets of host preference state",
&["scope"],
registry
)
.unwrap();
let user_pin_total = register_int_counter_vec_with_registry!(
"crw_user_pin_total",
"User-pinned renderer requests",
&["renderer"],
registry
)
.unwrap();
let host_preferences_size = register_int_gauge_with_registry!(
"crw_host_preferences_size",
"Current size of the host preferences cache",
registry
)
.unwrap();
let chrome_budget_truncated_total = register_int_counter_vec_with_registry!(
"crw_chrome_budget_truncated_total",
"Chrome nav-budget truncations by snapshot outcome",
&["outcome"],
registry
)
.unwrap();
let chrome_blocked_requests_total = register_int_counter_vec_with_registry!(
"crw_chrome_blocked_requests_total",
"Chrome requests blocked by interception, labeled by reason",
&["reason"],
registry
)
.unwrap();
let breaker_ignored_total = register_int_counter_vec_with_registry!(
"crw_breaker_ignored_total",
"Renderer outcomes ignored by the circuit breaker (deadline-clamped, truncated, etc.)",
&["renderer", "reason"],
registry
)
.unwrap();
let cdp_pending_requests = register_int_gauge_with_registry!(
"crw_cdp_pending_requests",
"CDP pending request map size summed across all live connections (sampler tick)",
registry
)
.unwrap();
let cdp_live_connections = register_int_gauge_with_registry!(
"crw_cdp_live_connections",
"Number of CDP connections currently registered as live",
registry
)
.unwrap();
let target_lifecycle_total = register_int_counter_vec_with_registry!(
"crw_target_lifecycle_total",
"CDP target lifecycle events by renderer and phase (created/closed/leaked)",
&["renderer", "phase"],
registry
)
.unwrap();
let renderer_recycle_total = register_int_counter_vec_with_registry!(
"crw_renderer_recycle_total",
"Renderer recycle events by renderer and reason",
&["renderer", "reason"],
registry
)
.unwrap();
Self {
registry,
render_route_decision_total,
circuit_breaker_open_total,
host_preferences_promotions_total,
admin_preferences_reset_total,
user_pin_total,
host_preferences_size,
chrome_budget_truncated_total,
chrome_blocked_requests_total,
breaker_ignored_total,
cdp_pending_requests,
cdp_live_connections,
target_lifecycle_total,
renderer_recycle_total,
}
}
}
pub fn gather_text() -> String {
let metric_families = metrics().registry.gather();
let encoder = TextEncoder::new();
let mut buf = Vec::new();
encoder.encode(&metric_families, &mut buf).ok();
String::from_utf8(buf).unwrap_or_default()
}