From 8cf76e55acd5df104dffa61fa0dc40351e812624 Mon Sep 17 00:00:00 2001 From: Denis Drakhnia Date: Tue, 14 May 2024 11:38:32 +0300 Subject: [PATCH] master: add stats feature flag --- master/Cargo.toml | 1 + master/config/default.toml | 2 + master/src/stats.rs | 159 ++----------------------------------- master/src/stats/imp.rs | 155 ++++++++++++++++++++++++++++++++++++ master/src/stats/stub.rs | 17 ++++ 5 files changed, 181 insertions(+), 153 deletions(-) create mode 100644 master/src/stats/imp.rs create mode 100644 master/src/stats/stub.rs diff --git a/master/Cargo.toml b/master/Cargo.toml index 578292a..9d832e0 100644 --- a/master/Cargo.toml +++ b/master/Cargo.toml @@ -14,6 +14,7 @@ repository = "https://git.mentality.rip/numas13/xash3d-master" [features] default = ["logtime"] logtime = ["chrono"] +stats = [] [dependencies] thiserror = "1.0.49" diff --git a/master/config/default.toml b/master/config/default.toml index 8cc84e2..0024a8e 100644 --- a/master/config/default.toml +++ b/master/config/default.toml @@ -18,6 +18,8 @@ ## Time in seconds before next admin request is allowed after wrong password #admin = 10 +# NOTE: require build with `stats` feature flag +# #[stat] # Interval in seconds, set zero to disable #interval = 0 diff --git a/master/src/stats.rs b/master/src/stats.rs index ff4a9a6..af7d593 100644 --- a/master/src/stats.rs +++ b/master/src/stats.rs @@ -1,155 +1,8 @@ -use std::fmt::{self, Write}; -use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering::Relaxed}; -use std::sync::mpsc; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, Instant}; +#[cfg(feature = "stats")] +mod imp; -use log::info; +#[cfg(not(feature = "stats"))] +#[path = "stats/stub.rs"] +mod imp; -use crate::config::StatConfig; - -#[derive(Default)] -struct Counters { - servers: AtomicUsize, - server_add: AtomicU32, - server_del: AtomicU32, - query_servers: AtomicU32, - errors: AtomicU32, -} - -impl Counters { - fn print(&self, mut format: &str, buf: &mut String, time: Duration) -> fmt::Result { - let time = time.as_secs_f64(); - let servers = self.servers.load(Relaxed); - let server_add = self.server_add.swap(0, Relaxed); - let server_del = self.server_del.swap(0, Relaxed); - let query_servers = self.query_servers.swap(0, Relaxed); - let errors = self.errors.swap(0, Relaxed); - - loop { - // TODO: precompile format string - match format.find('%').map(|i| format.split_at(i)) { - Some((head, tail)) => { - format = &tail[1..]; - write!(buf, "{}", head)?; - let mut chars = format.char_indices(); - match chars.next().map(|(_, c)| c) { - Some('s') => write!(buf, "{}", servers)?, - Some('A') => write!(buf, "{}", server_add)?, - Some('D') => write!(buf, "{}", server_del)?, - Some('Q') => write!(buf, "{}", query_servers)?, - Some('E') => write!(buf, "{}", errors)?, - Some('a') => write!(buf, "{:.1}", server_add as f64 / time)?, - Some('d') => write!(buf, "{:.1}", server_del as f64 / time)?, - Some('q') => write!(buf, "{:.1}", query_servers as f64 / time)?, - Some('e') => write!(buf, "{:.1}", errors as f64 / time)?, - Some(c) => write!(buf, "%{}", c)?, - None => write!(buf, "%")?, - } - match chars.next() { - Some((i, _)) => format = &format[i..], - None => break, - } - } - None => { - write!(buf, "{}", format)?; - break; - } - } - } - - Ok(()) - } - - fn clear(&self) { - self.servers.store(0, Relaxed); - self.server_add.store(0, Relaxed); - self.server_del.store(0, Relaxed); - self.query_servers.store(0, Relaxed); - self.errors.store(0, Relaxed); - } -} - -pub struct Stats { - enabled: bool, - tx: mpsc::Sender, - counters: Arc, -} - -impl Stats { - pub fn new(mut config: StatConfig) -> Self { - let counters_ = Arc::new(Counters::default()); - let (tx, rx) = mpsc::channel(); - - let enabled = config.interval != 0; - let counters = counters_.clone(); - thread::spawn(move || -> fmt::Result { - let buf = &mut String::new(); - - loop { - if config.interval == 0 { - config = rx.recv().unwrap(); - counters.clear(); - continue; - } - - let duration = Duration::from_secs(config.interval as u64); - let start = Instant::now(); - if let Ok(new_config) = rx.recv_timeout(duration) { - config = new_config; - continue; - } - - buf.clear(); - counters.print(&config.format, buf, start.elapsed())?; - info!("{}", buf); - } - }); - - Self { - enabled, - tx, - counters: counters_, - } - } - - pub fn update_config(&mut self, config: StatConfig) { - self.enabled = config.interval != 0; - self.tx.send(config).unwrap(); - } - - pub fn clear(&self) { - self.counters.clear(); - } - - pub fn servers_count(&self, n: usize) { - if self.enabled { - self.counters.servers.store(n, Relaxed); - } - } - - pub fn on_server_add(&self) { - if self.enabled { - self.counters.server_add.fetch_add(1, Relaxed); - } - } - - pub fn on_server_del(&self) { - if self.enabled { - self.counters.server_del.fetch_add(1, Relaxed); - } - } - - pub fn on_query_servers(&self) { - if self.enabled { - self.counters.query_servers.fetch_add(1, Relaxed); - } - } - - pub fn on_error(&self) { - if self.enabled { - self.counters.errors.fetch_add(1, Relaxed); - } - } -} +pub use self::imp::*; diff --git a/master/src/stats/imp.rs b/master/src/stats/imp.rs new file mode 100644 index 0000000..ff4a9a6 --- /dev/null +++ b/master/src/stats/imp.rs @@ -0,0 +1,155 @@ +use std::fmt::{self, Write}; +use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering::Relaxed}; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; +use std::time::{Duration, Instant}; + +use log::info; + +use crate::config::StatConfig; + +#[derive(Default)] +struct Counters { + servers: AtomicUsize, + server_add: AtomicU32, + server_del: AtomicU32, + query_servers: AtomicU32, + errors: AtomicU32, +} + +impl Counters { + fn print(&self, mut format: &str, buf: &mut String, time: Duration) -> fmt::Result { + let time = time.as_secs_f64(); + let servers = self.servers.load(Relaxed); + let server_add = self.server_add.swap(0, Relaxed); + let server_del = self.server_del.swap(0, Relaxed); + let query_servers = self.query_servers.swap(0, Relaxed); + let errors = self.errors.swap(0, Relaxed); + + loop { + // TODO: precompile format string + match format.find('%').map(|i| format.split_at(i)) { + Some((head, tail)) => { + format = &tail[1..]; + write!(buf, "{}", head)?; + let mut chars = format.char_indices(); + match chars.next().map(|(_, c)| c) { + Some('s') => write!(buf, "{}", servers)?, + Some('A') => write!(buf, "{}", server_add)?, + Some('D') => write!(buf, "{}", server_del)?, + Some('Q') => write!(buf, "{}", query_servers)?, + Some('E') => write!(buf, "{}", errors)?, + Some('a') => write!(buf, "{:.1}", server_add as f64 / time)?, + Some('d') => write!(buf, "{:.1}", server_del as f64 / time)?, + Some('q') => write!(buf, "{:.1}", query_servers as f64 / time)?, + Some('e') => write!(buf, "{:.1}", errors as f64 / time)?, + Some(c) => write!(buf, "%{}", c)?, + None => write!(buf, "%")?, + } + match chars.next() { + Some((i, _)) => format = &format[i..], + None => break, + } + } + None => { + write!(buf, "{}", format)?; + break; + } + } + } + + Ok(()) + } + + fn clear(&self) { + self.servers.store(0, Relaxed); + self.server_add.store(0, Relaxed); + self.server_del.store(0, Relaxed); + self.query_servers.store(0, Relaxed); + self.errors.store(0, Relaxed); + } +} + +pub struct Stats { + enabled: bool, + tx: mpsc::Sender, + counters: Arc, +} + +impl Stats { + pub fn new(mut config: StatConfig) -> Self { + let counters_ = Arc::new(Counters::default()); + let (tx, rx) = mpsc::channel(); + + let enabled = config.interval != 0; + let counters = counters_.clone(); + thread::spawn(move || -> fmt::Result { + let buf = &mut String::new(); + + loop { + if config.interval == 0 { + config = rx.recv().unwrap(); + counters.clear(); + continue; + } + + let duration = Duration::from_secs(config.interval as u64); + let start = Instant::now(); + if let Ok(new_config) = rx.recv_timeout(duration) { + config = new_config; + continue; + } + + buf.clear(); + counters.print(&config.format, buf, start.elapsed())?; + info!("{}", buf); + } + }); + + Self { + enabled, + tx, + counters: counters_, + } + } + + pub fn update_config(&mut self, config: StatConfig) { + self.enabled = config.interval != 0; + self.tx.send(config).unwrap(); + } + + pub fn clear(&self) { + self.counters.clear(); + } + + pub fn servers_count(&self, n: usize) { + if self.enabled { + self.counters.servers.store(n, Relaxed); + } + } + + pub fn on_server_add(&self) { + if self.enabled { + self.counters.server_add.fetch_add(1, Relaxed); + } + } + + pub fn on_server_del(&self) { + if self.enabled { + self.counters.server_del.fetch_add(1, Relaxed); + } + } + + pub fn on_query_servers(&self) { + if self.enabled { + self.counters.query_servers.fetch_add(1, Relaxed); + } + } + + pub fn on_error(&self) { + if self.enabled { + self.counters.errors.fetch_add(1, Relaxed); + } + } +} diff --git a/master/src/stats/stub.rs b/master/src/stats/stub.rs new file mode 100644 index 0000000..4df92a0 --- /dev/null +++ b/master/src/stats/stub.rs @@ -0,0 +1,17 @@ +use crate::config::StatConfig; + +#[derive(Default)] +struct Counters; + +pub struct Stats; + +impl Stats { + pub fn new(_: StatConfig) -> Self { Self } + pub fn update_config(&mut self, _: StatConfig) {} + pub fn clear(&self) {} + pub fn servers_count(&self, _: usize) {} + pub fn on_server_add(&self) {} + pub fn on_server_del(&self) {} + pub fn on_query_servers(&self) {} + pub fn on_error(&self) {} +}