diff --git a/server-cli/src/cli.rs b/server-cli/src/cli.rs index f383cbf9fa..239e40967c 100644 --- a/server-cli/src/cli.rs +++ b/server-cli/src/cli.rs @@ -82,10 +82,23 @@ pub struct TuiApp { command: Message, } +#[derive(Debug, Clone, Copy, Parser)] +pub struct BenchParams { + /// View distance of the loaded area (in chunks) + #[arg(long)] + pub view_distance: u32, + /// Duration to run after loading completes (in seconds). + #[arg(long)] + pub duration: u32, +} + #[derive(Parser)] pub enum ArgvCommand { #[command(flatten)] Shared(SharedCommand), + /// Load an area, run the server for some time, and then exit (useful for + /// profiling). + Bench(BenchParams), } #[derive(Parser)] diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 7074177400..ea5d7346cd 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -29,7 +29,7 @@ use server::{persistence::DatabaseSettings, settings::Protocol, Event, Input, Se use std::{ io, sync::{atomic::AtomicBool, mpsc, Arc}, - time::Duration, + time::{Duration, Instant}, }; use tracing::{info, trace}; @@ -117,15 +117,16 @@ fn main() -> io::Result<()> { sql_log_mode, }; + let mut bench = None; if let Some(command) = app.command { - return match command { + match command { ArgvCommand::Shared(SharedCommand::Admin { command }) => { let login_provider = server::login_provider::LoginProvider::new( server_settings.auth_server_address, runtime, ); - match command { + return match command { Admin::Add { username, role } => { // FIXME: Currently the UUID can get returned even if the file didn't // change, so this can't be relied on as an error @@ -139,6 +140,7 @@ fn main() -> io::Result<()> { &mut editable_settings, &server_data_dir, ); + Ok(()) }, Admin::Remove { username } => { // FIXME: Currently the UUID can get returned even if the file didn't @@ -152,9 +154,15 @@ fn main() -> io::Result<()> { &mut editable_settings, &server_data_dir, ); + Ok(()) }, - } - Ok(()) + }; + }, + ArgvCommand::Bench(params) => { + bench = Some(params); + // If we are trying to benchmark, don't limit the server view distance. + server_settings.max_view_distance = None; + // TODO: add setting to adjust entity spawn density }, }; } @@ -207,12 +215,28 @@ fn main() -> io::Result<()> { // Set up an fps clock let mut clock = Clock::new(Duration::from_secs_f64(1.0 / TPS as f64)); - // Wait for a tick so we don't start with a zero dt + + if let Some(bench) = bench { + #[cfg(feature = "worldgen")] + server.create_centered_persister(bench.view_distance); + } + let mut bench_exit_time = None; let mut tick_no = 0u64; loop { - tick_no += 1; span!(guard, "work"); + if let Some(bench) = bench { + if let Some(t) = bench_exit_time { + if Instant::now() > t { + break; + } + } else if tick_no != 0 && !server.chunks_pending() { + println!("Chunk loading complete"); + bench_exit_time = Some(Instant::now() + Duration::from_secs(bench.duration.into())); + } + }; + + tick_no += 1; // Terminate the server if instructed to do so by the shutdown coordinator if shutdown_coordinator.check(&mut server, &settings) { break; diff --git a/server/src/lib.rs b/server/src/lib.rs index 491d70ed17..5cb4ad5123 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1442,6 +1442,15 @@ impl Server { .build(); } + /// Used by benchmarking code. + pub fn chunks_pending(&mut self) -> bool { + self.state_mut() + .mut_resource::() + .pending_chunks() + .next() + .is_some() + } + /// Sets the SQL log mode at runtime pub fn set_sql_log_mode(&mut self, sql_log_mode: SqlLogMode) { // Unwrap is safe here because we only perform a variable assignment with the diff --git a/server/src/sys/metrics.rs b/server/src/sys/metrics.rs index 07c79dfd22..d8633805cc 100644 --- a/server/src/sys/metrics.rs +++ b/server/src/sys/metrics.rs @@ -1,4 +1,5 @@ use crate::{ + chunk_generator::ChunkGenerator, metrics::{EcsSystemMetrics, JobMetrics, PhysicsMetrics, TickMetrics}, HwStats, Tick, TickStart, }; @@ -12,11 +13,12 @@ use std::time::Instant; pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( - Option>, + Entities<'a>, ReadExpect<'a, HwStats>, ReadExpect<'a, Tick>, ReadExpect<'a, TimeOfDay>, ReadExpect<'a, TickStart>, + ReadExpect<'a, ChunkGenerator>, Option>, Read<'a, SysMetrics>, Read<'a, common_ecs::PhysicsMetrics>, @@ -39,6 +41,7 @@ impl<'a> System<'a> for Sys { tick, time_of_day, tick_start, + chunk_generator, terrain, sys_metrics, phys_metrics, @@ -89,7 +92,7 @@ impl<'a> System<'a> for Sys { // Report other info export_tick.time_of_day.set(time_of_day.0); if tick.0.rem_euclid(100) == 0 { - if let Some(terrain) = terrain { + if let Some(terrain) = terrain.as_ref() { let mut chonk_cnt = 0; let mut group_cnt = 0; let chunk_cnt = terrain.iter().fold(0, |a, (_, c)| { @@ -102,11 +105,16 @@ impl<'a> System<'a> for Sys { export_tick.chunk_groups_count.set(group_cnt as i64); } - if let Some(entities) = entities { - let entity_count = entities.join().count(); - export_tick.entity_count.set(entity_count as i64); - common_base::plot!("entity count", entity_count as f64); - } + let entity_count = entities.join().count(); + export_tick.entity_count.set(entity_count as i64); + } + common_base::plot!("entity count", entities.join().count() as f64); + common_base::plot!( + "pending chunks", + chunk_generator.pending_chunks().count() as f64 + ); + if let Some(terrain) = terrain.as_ref() { + common_base::plot!("chunk count", terrain.iter().count() as f64); } //detailed physics metrics