mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add stats chat command
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2583,6 +2583,7 @@ name = "oj_rc_chat_room"
|
|||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"git-version",
|
"git-version",
|
||||||
|
|||||||
@@ -14491,6 +14491,14 @@
|
|||||||
},
|
},
|
||||||
"permission": "Developer"
|
"permission": "Developer"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"regex": "\\?stats?",
|
||||||
|
"op": {
|
||||||
|
"type": "BuiltIn",
|
||||||
|
"built_in": "Stats"
|
||||||
|
},
|
||||||
|
"permission": "Player"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"regex": "\\?broadcast",
|
"regex": "\\?broadcast",
|
||||||
"op": {
|
"op": {
|
||||||
|
|||||||
@@ -21,3 +21,4 @@ serde_json.workspace = true
|
|||||||
regex = "1"
|
regex = "1"
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
git-version.workspace = true
|
git-version.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ use polariton::operation::{OperationResponse, Typed};
|
|||||||
|
|
||||||
pub type UserTy = oj_rc_core::UserState;
|
pub type UserTy = oj_rc_core::UserState;
|
||||||
|
|
||||||
|
pub static START_TIMESTAMP_S: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
|
||||||
|
pub static READY_DURATION_NS: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
|
let start_time = chrono::Utc::now();
|
||||||
|
START_TIMESTAMP_S.store(start_time.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let args = cli::CliArgs::get();
|
let args = cli::CliArgs::get();
|
||||||
log::debug!("Got cli args {:?}", args);
|
log::debug!("Got cli args {:?}", args);
|
||||||
@@ -34,6 +39,9 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
|
let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
|
||||||
|
|
||||||
|
let ready_dur = chrono::Utc::now() - start_time;
|
||||||
|
READY_DURATION_NS.store(ready_dur.num_nanoseconds().unwrap_or(-1), std::sync::atomic::Ordering::Relaxed);
|
||||||
|
log::info!("chat_room ready");
|
||||||
if args.once {
|
if args.once {
|
||||||
log::warn!("Handling first connection and then exiting");
|
log::warn!("Handling first connection and then exiting");
|
||||||
let (socket, address) = listener.accept().await?;
|
let (socket, address) = listener.accept().await?;
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ enum BuiltIn {
|
|||||||
Intercom(Intercom),
|
Intercom(Intercom),
|
||||||
OnlineUsers,
|
OnlineUsers,
|
||||||
TotalUsers,
|
TotalUsers,
|
||||||
|
Stats,
|
||||||
Version,
|
Version,
|
||||||
Help,
|
Help,
|
||||||
}
|
}
|
||||||
@@ -127,6 +128,7 @@ impl BuiltIn {
|
|||||||
oj_rc_core::persist::BuiltInChatOperation::Intercom(com) => Self::Intercom(Intercom::from_persist(com)),
|
oj_rc_core::persist::BuiltInChatOperation::Intercom(com) => Self::Intercom(Intercom::from_persist(com)),
|
||||||
oj_rc_core::persist::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers,
|
oj_rc_core::persist::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers,
|
||||||
oj_rc_core::persist::BuiltInChatOperation::TotalUsers => Self::TotalUsers,
|
oj_rc_core::persist::BuiltInChatOperation::TotalUsers => Self::TotalUsers,
|
||||||
|
oj_rc_core::persist::BuiltInChatOperation::Stats => Self::Stats,
|
||||||
oj_rc_core::persist::BuiltInChatOperation::Version => Self::Version,
|
oj_rc_core::persist::BuiltInChatOperation::Version => Self::Version,
|
||||||
oj_rc_core::persist::BuiltInChatOperation::Help => Self::Help,
|
oj_rc_core::persist::BuiltInChatOperation::Help => Self::Help,
|
||||||
}
|
}
|
||||||
@@ -157,6 +159,53 @@ impl BuiltIn {
|
|||||||
Err(e) => e.error_msg().map(|x| x.to_owned()).unwrap_or_else(|| "Failed to retrieve registered users".to_owned()),
|
Err(e) => e.error_msg().map(|x| x.to_owned()).unwrap_or_else(|| "Failed to retrieve registered users".to_owned()),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Self::Stats => {
|
||||||
|
let mut stats = Vec::new();
|
||||||
|
for variant in text.trim().split(' ').skip(1) {
|
||||||
|
match variant {
|
||||||
|
"db" | "database" => {
|
||||||
|
let db_stats = format!("(chat db) {}", ctx.user.db_metrics().await);
|
||||||
|
stats.push(db_stats);
|
||||||
|
},
|
||||||
|
"perms" | "permission" | "permissions" => {
|
||||||
|
let com_stats = format!("(perms {}) mod:{} admin:{} dev:{} banned:{} royal:{}",
|
||||||
|
ctx.user.public_id(),
|
||||||
|
ctx.user.is_mod(),
|
||||||
|
ctx.user.is_admin(),
|
||||||
|
ctx.user.is_dev(),
|
||||||
|
ctx.user.is_banned(),
|
||||||
|
ctx.user.is_royal(),
|
||||||
|
);
|
||||||
|
stats.push(com_stats);
|
||||||
|
},
|
||||||
|
"up" | "uptime" => {
|
||||||
|
let now = chrono::Utc::now().timestamp();
|
||||||
|
let startup_timestamp = crate::START_TIMESTAMP_S.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let uptime_delta = now - startup_timestamp;
|
||||||
|
let uptime_str = if uptime_delta <= 0 {
|
||||||
|
"0?".to_owned()
|
||||||
|
} else if uptime_delta < 60 {
|
||||||
|
format!("{}s", uptime_delta)
|
||||||
|
} else if uptime_delta < 24 * 60 * 60 {
|
||||||
|
format!("{}:{:02}", uptime_delta / (60 * 60), (uptime_delta % (60 * 60)) / 60)
|
||||||
|
} else {
|
||||||
|
format!("{} days {}:{:02}", uptime_delta / (24 * 60 * 60), (uptime_delta % (24 * 60 * 60)) / (60 * 60), ((uptime_delta % (24 * 60 * 60)) % (60 * 60)) / 60)
|
||||||
|
};
|
||||||
|
let ready_ns = crate::READY_DURATION_NS.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let uptime_stats = format!("(uptime) {}, startup in {}ns", uptime_str, ready_ns);
|
||||||
|
stats.push(uptime_stats);
|
||||||
|
},
|
||||||
|
idk => {
|
||||||
|
let stat = format!("(unknown stat) {}", idk);
|
||||||
|
stats.push(stat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stats.is_empty() {
|
||||||
|
stats.push(format!("TODO: general stats (try db)"));
|
||||||
|
}
|
||||||
|
stats.join("\n")
|
||||||
|
},
|
||||||
Self::Version => {
|
Self::Version => {
|
||||||
let name = env!("CARGO_PKG_NAME");
|
let name = env!("CARGO_PKG_NAME");
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
@@ -189,6 +238,7 @@ impl BuiltIn {
|
|||||||
Self::Intercom(i) => i.do_help(),
|
Self::Intercom(i) => i.do_help(),
|
||||||
Self::OnlineUsers => "Show total users online".to_owned(),
|
Self::OnlineUsers => "Show total users online".to_owned(),
|
||||||
Self::TotalUsers => "Show total users registered".to_owned(),
|
Self::TotalUsers => "Show total users registered".to_owned(),
|
||||||
|
Self::Stats => "Show server metrics (db|perms)".to_owned(),
|
||||||
Self::Version => "Show chat server version information".to_owned(),
|
Self::Version => "Show chat server version information".to_owned(),
|
||||||
Self::Help => "Display this message".to_owned(),
|
Self::Help => "Display this message".to_owned(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ pub enum BuiltInChatOperation {
|
|||||||
Intercom(IntercomChatOperation),
|
Intercom(IntercomChatOperation),
|
||||||
OnlineUsers,
|
OnlineUsers,
|
||||||
TotalUsers,
|
TotalUsers,
|
||||||
|
Stats,
|
||||||
Version,
|
Version,
|
||||||
Help,
|
Help,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,4 +29,8 @@ impl super::CommonUser for UserData {
|
|||||||
async fn resolve_config_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<super::ResolvedVehicle, polariton_server::operations::SimpleOpError> {
|
async fn resolve_config_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<super::ResolvedVehicle, polariton_server::operations::SimpleOpError> {
|
||||||
self.resolve_vehicle(vehicle, factory, weapon_order, cpu_counter).await
|
self.resolve_vehicle(vehicle, factory, weapon_order, cpu_counter).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn db_metrics(&self) -> oj_rc_database::DatabaseMetrics {
|
||||||
|
self.db.metrics().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,4 +376,5 @@ pub trait CommonUser: Send + Sync {
|
|||||||
fn is_dev(&self) -> bool;
|
fn is_dev(&self) -> bool;
|
||||||
fn is_royal(&self) -> bool;
|
fn is_royal(&self) -> bool;
|
||||||
fn is_banned(&self) -> bool;
|
fn is_banned(&self) -> bool;
|
||||||
|
async fn db_metrics(&self) -> oj_rc_database::DatabaseMetrics;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,7 @@ pub mod schema;
|
|||||||
mod wrapper;
|
mod wrapper;
|
||||||
pub use wrapper::Database;
|
pub use wrapper::Database;
|
||||||
|
|
||||||
|
mod metrics;
|
||||||
|
pub use metrics::DatabaseMetrics;
|
||||||
|
|
||||||
pub use sea_orm;
|
pub use sea_orm;
|
||||||
|
|||||||
91
rc_database/src/metrics.rs
Normal file
91
rc_database/src/metrics.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
pub(crate) struct MetricsState {
|
||||||
|
total_calls: u64,
|
||||||
|
total_duration_ns: u128,
|
||||||
|
total_failed: u64,
|
||||||
|
fastest: StatementInfo,
|
||||||
|
slowest: StatementInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DatabaseMetrics {
|
||||||
|
pub avg_query_duration: std::time::Duration,
|
||||||
|
pub fail_queries: u64,
|
||||||
|
pub success_queries: u64,
|
||||||
|
pub slowest: StatementInfo,
|
||||||
|
pub fastest: StatementInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for DatabaseMetrics {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "avg: {}s success: {}, failed: {} worst: {} ({}s), best: {} ({}s)",
|
||||||
|
self.avg_query_duration.as_secs_f32(),
|
||||||
|
self.success_queries,
|
||||||
|
self.fail_queries,
|
||||||
|
self.slowest.statement, self.slowest.elapsed.as_secs_f32(),
|
||||||
|
self.fastest.statement, self.fastest.elapsed.as_secs_f32(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StatementInfo {
|
||||||
|
elapsed: std::time::Duration,
|
||||||
|
statement: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatementInfo {
|
||||||
|
fn fastest() -> Self {
|
||||||
|
Self {
|
||||||
|
elapsed: std::time::Duration::ZERO,
|
||||||
|
statement: "".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slowest() -> Self {
|
||||||
|
Self {
|
||||||
|
elapsed: std::time::Duration::MAX,
|
||||||
|
statement: "".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetricsState {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
total_calls: 0,
|
||||||
|
total_duration_ns: 0,
|
||||||
|
total_failed: 0,
|
||||||
|
slowest: StatementInfo::fastest(),
|
||||||
|
fastest: StatementInfo::slowest(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn snapshot(&self) -> DatabaseMetrics {
|
||||||
|
DatabaseMetrics {
|
||||||
|
avg_query_duration: std::time::Duration::from_nanos((self.total_duration_ns / self.total_calls as u128) as u64),
|
||||||
|
fail_queries: self.total_failed,
|
||||||
|
success_queries: self.total_calls,
|
||||||
|
slowest: self.slowest.clone(),
|
||||||
|
fastest: self.fastest.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn metrics_cb(state: std::sync::Arc<std::sync::Mutex<MetricsState>>) -> impl Fn(&sea_orm::metric::Info) + Send + Sync + 'static {
|
||||||
|
move |info: &sea_orm::metric::Info| {
|
||||||
|
let mut lock = state.lock().unwrap();
|
||||||
|
lock.total_calls += 1;
|
||||||
|
lock.total_duration_ns += info.elapsed.as_nanos();
|
||||||
|
if info.failed {
|
||||||
|
lock.total_failed += 1;
|
||||||
|
}
|
||||||
|
if info.elapsed < lock.fastest.elapsed {
|
||||||
|
lock.fastest.elapsed = info.elapsed;
|
||||||
|
lock.fastest.statement = info.statement.to_string();
|
||||||
|
}
|
||||||
|
if info.elapsed > lock.slowest.elapsed {
|
||||||
|
lock.slowest.elapsed = info.elapsed;
|
||||||
|
lock.slowest.statement = info.statement.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,15 +3,19 @@ use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryF
|
|||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
orm: sea_orm::DatabaseConnection,
|
orm: sea_orm::DatabaseConnection,
|
||||||
|
metrics: std::sync::Arc<std::sync::Mutex<super::metrics::MetricsState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
pub async fn init(uri: &str) -> Result<Self, sea_orm::DbErr>{
|
pub async fn init(uri: &str) -> Result<Self, sea_orm::DbErr>{
|
||||||
let db = sea_orm::Database::connect(uri).await?;
|
let mut db = sea_orm::Database::connect(uri).await?;
|
||||||
|
let metrics_data = std::sync::Arc::new(std::sync::Mutex::new(super::metrics::MetricsState::new()));
|
||||||
|
db.set_metric_callback(super::metrics::metrics_cb(metrics_data.clone()));
|
||||||
//let schema_manager = SchemaManager::new(&db);
|
//let schema_manager = SchemaManager::new(&db);
|
||||||
super::Migrator::up(&db, None).await?;
|
super::Migrator::up(&db, None).await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
orm: db,
|
orm: db,
|
||||||
|
metrics: metrics_data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,4 +427,8 @@ impl Database {
|
|||||||
pub async fn insert_game_event(&self, entity: crate::schema::game_event::ActiveModel) -> Result<crate::schema::game_event::Model, sea_orm::DbErr> {
|
pub async fn insert_game_event(&self, entity: crate::schema::game_event::ActiveModel) -> Result<crate::schema::game_event::Model, sea_orm::DbErr> {
|
||||||
entity.insert(&self.orm).await
|
entity.insert(&self.orm).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn metrics(&self) -> super::DatabaseMetrics {
|
||||||
|
self.metrics.lock().unwrap().snapshot()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user