use chrono::{Datelike, Local, Timelike}; use serde::Serialize; use std::sync::{LazyLock, Mutex}; use std::time::Instant; static START_TIME: LazyLock = LazyLock::new(Instant::now); static SERVER_STATS: LazyLock = LazyLock::new(ServerStats::default); #[derive(Default)] pub struct ServerStats { inner: Mutex, } #[derive(Default)] struct ServerStatsInner { requests: DurationCounter, connections: DurationCounter, assign_requests: DurationCounter, read_requests: DurationCounter, write_requests: DurationCounter, delete_requests: DurationCounter, bytes_in: DurationCounter, bytes_out: DurationCounter, } #[derive(Clone, Serialize)] #[serde(rename_all = "PascalCase")] pub struct ServerStatsSnapshot { pub requests: DurationCounterSnapshot, pub connections: DurationCounterSnapshot, pub assign_requests: DurationCounterSnapshot, pub read_requests: DurationCounterSnapshot, pub write_requests: DurationCounterSnapshot, pub delete_requests: DurationCounterSnapshot, pub bytes_in: DurationCounterSnapshot, pub bytes_out: DurationCounterSnapshot, } #[derive(Clone, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DurationCounterSnapshot { pub minute_counter: RoundRobinCounterSnapshot, pub hour_counter: RoundRobinCounterSnapshot, pub day_counter: RoundRobinCounterSnapshot, pub week_counter: RoundRobinCounterSnapshot, } #[derive(Clone, Serialize)] #[serde(rename_all = "PascalCase")] pub struct RoundRobinCounterSnapshot { pub last_index: i32, pub values: Vec, pub counts: Vec, } #[derive(Clone)] struct DurationCounter { minute_counter: RoundRobinCounter, hour_counter: RoundRobinCounter, day_counter: RoundRobinCounter, week_counter: RoundRobinCounter, } #[derive(Clone)] struct RoundRobinCounter { last_index: i32, values: Vec, counts: Vec, } impl Default for DurationCounter { fn default() -> Self { Self { minute_counter: RoundRobinCounter::new(60), hour_counter: RoundRobinCounter::new(60), day_counter: RoundRobinCounter::new(24), week_counter: RoundRobinCounter::new(7), } } } impl RoundRobinCounter { fn new(slots: usize) -> Self { Self { last_index: -1, values: vec![0; slots], counts: vec![0; slots], } } fn add(&mut self, index: usize, val: i64) { if index >= self.values.len() { return; } while self.last_index != index as i32 { self.last_index = (self.last_index + 1).rem_euclid(self.values.len() as i32); self.values[self.last_index as usize] = 0; self.counts[self.last_index as usize] = 0; } self.values[index] += val; self.counts[index] += 1; } fn snapshot(&self) -> RoundRobinCounterSnapshot { RoundRobinCounterSnapshot { last_index: self.last_index, values: self.values.clone(), counts: self.counts.clone(), } } } impl DurationCounter { fn add_now(&mut self, val: i64) { let now = Local::now(); self.minute_counter.add(now.second() as usize, val); self.hour_counter.add(now.minute() as usize, val); self.day_counter.add(now.hour() as usize, val); self.week_counter .add(now.weekday().num_days_from_sunday() as usize, val); } fn snapshot(&self) -> DurationCounterSnapshot { DurationCounterSnapshot { minute_counter: self.minute_counter.snapshot(), hour_counter: self.hour_counter.snapshot(), day_counter: self.day_counter.snapshot(), week_counter: self.week_counter.snapshot(), } } } impl ServerStatsInner { fn snapshot(&self) -> ServerStatsSnapshot { ServerStatsSnapshot { requests: self.requests.snapshot(), connections: self.connections.snapshot(), assign_requests: self.assign_requests.snapshot(), read_requests: self.read_requests.snapshot(), write_requests: self.write_requests.snapshot(), delete_requests: self.delete_requests.snapshot(), bytes_in: self.bytes_in.snapshot(), bytes_out: self.bytes_out.snapshot(), } } } impl ServerStats { fn update(&self, update: F) where F: FnOnce(&mut ServerStatsInner), { let mut inner = self.inner.lock().unwrap(); update(&mut inner); } fn snapshot(&self) -> ServerStatsSnapshot { self.inner.lock().unwrap().snapshot() } } impl RoundRobinCounterSnapshot { pub fn to_list(&self) -> Vec { if self.values.is_empty() { return Vec::new(); } let mut ret = Vec::with_capacity(self.values.len()); let mut index = self.last_index; let mut step = self.values.len(); while step > 0 { step -= 1; index += 1; if index >= self.values.len() as i32 { index = 0; } ret.push(self.values[index as usize]); } ret } } pub fn init_process_start() { LazyLock::force(&START_TIME); LazyLock::force(&SERVER_STATS); } pub fn uptime_string() -> String { let secs = START_TIME.elapsed().as_secs(); let hours = secs / 3600; let minutes = (secs % 3600) / 60; let seconds = secs % 60; let mut out = String::new(); if hours > 0 { out.push_str(&format!("{}h", hours)); } if hours > 0 || minutes > 0 { out.push_str(&format!("{}m", minutes)); } out.push_str(&format!("{}s", seconds)); out } pub fn snapshot() -> ServerStatsSnapshot { SERVER_STATS.snapshot() } pub fn record_request_open() { SERVER_STATS.update(|inner| inner.requests.add_now(1)); } pub fn record_request_close() { SERVER_STATS.update(|inner| inner.requests.add_now(-1)); } pub fn record_connection_open() { SERVER_STATS.update(|inner| inner.connections.add_now(1)); } pub fn record_connection_close() { SERVER_STATS.update(|inner| inner.connections.add_now(-1)); } pub fn record_read_request() { SERVER_STATS.update(|inner| inner.read_requests.add_now(1)); } pub fn record_write_request() { SERVER_STATS.update(|inner| inner.write_requests.add_now(1)); } pub fn record_delete_request() { SERVER_STATS.update(|inner| inner.delete_requests.add_now(1)); } pub fn record_bytes_in(bytes: i64) { SERVER_STATS.update(|inner| inner.bytes_in.add_now(bytes)); } pub fn record_bytes_out(bytes: i64) { SERVER_STATS.update(|inner| inner.bytes_out.add_now(bytes)); } #[cfg(test)] pub fn reset_for_tests() { LazyLock::force(&START_TIME); let mut inner = SERVER_STATS.inner.lock().unwrap(); *inner = ServerStatsInner::default(); }