use std::sync::OnceLock;
use liminal::metrics::{
CounterHandle, GaugeHandle, MetricsRegistry, global_registry, install_global_registry,
};
use crate::server::connection::refusal::AdmissionRefusal;
use crate::server::connection::websocket::UpgradeRefusal;
use crate::server::mount::MountKind;
const CONNECTIONS_ACTIVE: &str = "liminal_connections_active";
const PUBLISHES_TOTAL: &str = "liminal_publishes_total";
const DELIVERIES_TOTAL: &str = "liminal_deliveries_total";
const TRANSPORT_DELIVERIES_TOTAL: &str = "liminal_transport_deliveries_total";
const SHEDS_TOTAL: &str = "liminal_subscription_sheds_total";
const TRANSPORT_LABEL: &str = "transport";
const ADMISSION_REFUSALS_TOTAL: &str = "liminal_admission_refusals_total";
const HANDSHAKE_REFUSALS_TOTAL: &str = "liminal_handshake_refusals_total";
const REASON_LABEL: &str = "reason";
static SERVER_METRICS: OnceLock<ServerMetrics> = OnceLock::new();
#[derive(Clone, Debug)]
struct ServerMetrics {
connections_active: GaugeHandle,
publishes_total: CounterHandle,
deliveries_total: CounterHandle,
transport_deliveries: [CounterHandle; MOUNT_KINDS.len()],
sheds_total: CounterHandle,
admission_refusals: [CounterHandle; AdmissionRefusal::LABELS.len()],
handshake_refusals: [CounterHandle; UpgradeRefusal::LABELS.len()],
}
const MOUNT_KINDS: [MountKind; 3] = [MountKind::Tcp, MountKind::WebSocket, MountKind::Loopback];
const fn transport_slot(transport: MountKind) -> usize {
match transport {
MountKind::Tcp => 0,
MountKind::WebSocket => 1,
MountKind::Loopback => 2,
}
}
pub fn init() {
if SERVER_METRICS.get().is_some() {
return;
}
let Some(registry) = global_or_install() else {
return;
};
if let Some(metrics) = ServerMetrics::register(registry) {
let _ = SERVER_METRICS.set(metrics);
}
}
pub fn connection_spawned() {
if let Some(metrics) = SERVER_METRICS.get() {
metrics.connections_active.increment();
}
}
pub fn connection_closed() {
if let Some(metrics) = SERVER_METRICS.get() {
metrics.connections_active.decrement();
}
}
pub fn publish_accepted() {
if let Some(metrics) = SERVER_METRICS.get() {
metrics.publishes_total.increment();
}
}
pub fn deliveries_recorded(count: u64) {
if count == 0 {
return;
}
if let Some(metrics) = SERVER_METRICS.get() {
metrics.deliveries_total.increment_by(count);
}
}
pub fn transport_deliveries_recorded(transport: MountKind, count: u64) {
if count == 0 {
return;
}
if let Some(metrics) = SERVER_METRICS.get() {
metrics.transport_deliveries[transport_slot(transport)].increment_by(count);
}
}
pub(crate) fn admission_refused(refusal: AdmissionRefusal) {
if let Some(metrics) = SERVER_METRICS.get() {
metrics.admission_refusals[refusal.slot()].increment();
}
}
pub(crate) fn handshake_refused(refusal: &UpgradeRefusal) {
if let Some(metrics) = SERVER_METRICS.get() {
metrics.handshake_refusals[refusal.slot()].increment();
}
}
pub fn subscription_shed() {
if let Some(metrics) = SERVER_METRICS.get() {
metrics.sheds_total.increment();
}
}
#[cfg(test)]
pub(crate) fn publishes_total_value() -> Option<u64> {
use liminal::metrics::MetricValue;
let registry = global_registry()?;
registry
.snapshot()
.metrics()
.iter()
.find(|metric| metric.name == PUBLISHES_TOTAL)
.and_then(|metric| match metric.value {
MetricValue::Counter(value) => Some(value),
MetricValue::Gauge(_) | MetricValue::Histogram(_) => None,
})
}
impl ServerMetrics {
fn register(registry: &MetricsRegistry) -> Option<Self> {
let connections_active = registry
.register_gauge(CONNECTIONS_ACTIVE, no_labels())
.ok()?;
let publishes_total = registry
.register_counter(PUBLISHES_TOTAL, no_labels())
.ok()?;
let deliveries_total = registry
.register_counter(DELIVERIES_TOTAL, no_labels())
.ok()?;
let mut transport_deliveries = Vec::with_capacity(MOUNT_KINDS.len());
for transport in MOUNT_KINDS {
transport_deliveries.push(
registry
.register_counter(
TRANSPORT_DELIVERIES_TOTAL,
[(TRANSPORT_LABEL, transport.as_str())],
)
.ok()?,
);
}
let transport_deliveries: [CounterHandle; MOUNT_KINDS.len()] =
transport_deliveries.try_into().ok()?;
let sheds_total = registry.register_counter(SHEDS_TOTAL, no_labels()).ok()?;
let admission_refusals = register_labelled(
registry,
ADMISSION_REFUSALS_TOTAL,
&AdmissionRefusal::LABELS,
)?;
let handshake_refusals =
register_labelled(registry, HANDSHAKE_REFUSALS_TOTAL, &UpgradeRefusal::LABELS)?;
Some(Self {
connections_active,
publishes_total,
deliveries_total,
transport_deliveries,
sheds_total,
admission_refusals,
handshake_refusals,
})
}
}
fn register_labelled<const N: usize>(
registry: &MetricsRegistry,
name: &'static str,
labels: &[&'static str; N],
) -> Option<[CounterHandle; N]> {
let mut handles = Vec::with_capacity(N);
for label in labels {
handles.push(
registry
.register_counter(name, [(REASON_LABEL, *label)])
.ok()?,
);
}
handles.try_into().ok()
}
const fn no_labels() -> std::iter::Empty<(&'static str, &'static str)> {
std::iter::empty()
}
fn global_or_install() -> Option<&'static MetricsRegistry> {
if let Some(registry) = global_registry() {
return Some(registry);
}
let _ = install_global_registry(MetricsRegistry::new());
global_registry()
}
#[cfg(test)]
mod tests {
use super::{
CONNECTIONS_ACTIVE, DELIVERIES_TOTAL, PUBLISHES_TOTAL, connection_spawned,
deliveries_recorded, init, publish_accepted,
};
use liminal::metrics::{global_registry, render};
#[test]
fn init_registers_the_three_server_families_on_the_global_registry()
-> Result<(), Box<dyn std::error::Error>> {
init();
connection_spawned();
publish_accepted();
deliveries_recorded(2);
let registry =
global_registry().ok_or("init must install and enable the global registry")?;
let exposition = render(®istry.snapshot());
assert!(exposition.contains(CONNECTIONS_ACTIVE));
assert!(exposition.contains(PUBLISHES_TOTAL));
assert!(exposition.contains(DELIVERIES_TOTAL));
Ok(())
}
}