diff --git a/Cargo.lock b/Cargo.lock index 5cff08740c..8133a96275 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1643,6 +1643,15 @@ dependencies = [ "miniz_oxide 0.4.3", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -5598,6 +5607,7 @@ dependencies = [ "directories-next", "dot_vox", "enum-iterator", + "float-cmp", "hashbrown 0.9.1", "image", "indexmap", @@ -6681,4 +6691,4 @@ dependencies = [ name = "xml-rs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" +checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" \ No newline at end of file diff --git a/common/Cargo.toml b/common/Cargo.toml index 7c5cb4c482..2d614c40a3 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -68,6 +68,7 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "9fab7b3 [dev-dependencies] #bench criterion = "0.3" +float-cmp = "0.8.0" [[bench]] name = "chonk_benchmark" diff --git a/common/src/lib.rs b/common/src/lib.rs index 472cd38b64..7bbad0249a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -61,6 +61,7 @@ pub mod spiral; #[cfg(not(target_arch = "wasm32"))] pub mod states; #[cfg(not(target_arch = "wasm32"))] pub mod store; +pub mod system; #[cfg(not(target_arch = "wasm32"))] pub mod terrain; #[cfg(not(target_arch = "wasm32"))] pub mod time; diff --git a/common/src/metrics.rs b/common/src/metrics.rs index cc7435bd41..c919786e1f 100644 --- a/common/src/metrics.rs +++ b/common/src/metrics.rs @@ -1,15 +1,9 @@ -use std::sync::atomic::AtomicU64; +use crate::system::CpuTimeline; +use std::{collections::HashMap, sync::Mutex}; #[derive(Default)] pub struct SysMetrics { - pub agent_ns: AtomicU64, - pub mount_ns: AtomicU64, - pub controller_ns: AtomicU64, - pub character_behavior_ns: AtomicU64, - pub stats_ns: AtomicU64, - pub phys_ns: AtomicU64, - pub projectile_ns: AtomicU64, - pub melee_ns: AtomicU64, + pub stats: Mutex>, } #[derive(Default)] diff --git a/common/src/system.rs b/common/src/system.rs new file mode 100644 index 0000000000..36eb5697da --- /dev/null +++ b/common/src/system.rs @@ -0,0 +1,458 @@ +use crate::metrics::SysMetrics; +use specs::{ReadExpect, RunNow}; +use std::{collections::HashMap, time::Instant}; + +/// measuring the level of threads a unit of code ran on. Use Rayon when it ran +/// on their threadpool. Use Exact when you know on how many threads your code +/// ran on exactly. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ParMode { + None, /* Job is not running at all */ + Single, + Rayon, + Exact(u16), +} + +//TODO: make use of the phase of a system for advanced scheduling and logging +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum Phase { + Create, + Review, + Apply, +} + +//TODO: make use of the origin of the system for better logging +#[derive(Clone, PartialEq, Debug)] +pub enum Origin { + Common, + Client, + Server, + Frontend(&'static str), +} + +#[derive(Default, Debug, Clone)] +pub struct CpuTimeline { + /// measurements for a System + /// - The first entry will always be ParMode::Single, as when the + /// System::run is executed, we run + /// single threaded until we start a Rayon::ParIter or similar + /// - The last entry will contain the end time of the System. To mark the + /// End it will always contain + /// ParMode::None, which means from that point on 0 CPU threads work in this + /// system + measures: Vec<(Instant, ParMode)>, +} + +#[derive(Default)] +pub struct CpuTimeStats { + /// the first entry will always be 0, the last entry will always be `dt` + /// `usage` starting from `ns` + measures: Vec<(/* ns */ u64, /* usage */ f32)>, +} + +/// Parallel Mode tells us how much you are scaling. `None` means your code +/// isn't running. `Single` means you are running single threaded. +/// `Rayon` means you are running on the rayon threadpool. +impl ParMode { + fn threads(&self, rayon_threads: u16) -> u16 { + match self { + ParMode::None => 0, + ParMode::Single => 1, + ParMode::Rayon => rayon_threads, + ParMode::Exact(u) => *u, + } + } +} + +impl CpuTimeline { + fn reset(&mut self) { + self.measures.clear(); + self.measures.push((Instant::now(), ParMode::Single)); + } + + /// Start a new measurement. par will be covering the parallelisation AFTER + /// this statement, till the next / end of the System. + pub fn measure(&mut self, par: ParMode) { self.measures.push((Instant::now(), par)); } + + fn end(&mut self) { self.measures.push((Instant::now(), ParMode::None)); } + + fn get(&self, time: Instant) -> ParMode { + match self.measures.binary_search_by_key(&time, |&(a, _)| a) { + Ok(id) => self.measures[id].1, + Err(0) => ParMode::None, /* not yet started */ + Err(id) => self.measures[id - 1].1, + } + } +} + +impl CpuTimeStats { + pub fn length_ns(&self) -> u64 { self.end_ns() - self.start_ns() } + + pub fn start_ns(&self) -> u64 { + self.measures + .iter() + .find(|e| e.1 > 0.001) + .unwrap_or(&(0, 0.0)) + .0 + } + + pub fn end_ns(&self) -> u64 { self.measures.last().unwrap_or(&(0, 0.0)).0 } + + pub fn avg_threads(&self) -> f32 { + let mut sum = 0.0; + for w in self.measures.windows(2) { + let len = w[1].0 - w[0].0; + let h = w[0].1; + sum += len as f32 * h; + } + sum / (self.length_ns() as f32) + } +} + +/// The Idea is to transform individual timelines per system to a map of all +/// cores and what they (prob) are working on. +/// +/// # Example +/// +/// - Input: 3 services, 0 and 1 are 100% parallel and 2 is single threaded. `-` +/// means no work for *0.5s*. `#` means full work for *0.5s*. We see the first +/// service starts after 1s and runs for 3s The second one starts a sec later +/// and runs for 4s. The last service runs 2.5s after the tick start and runs +/// for 1s. Read left to right. +/// ```ignore +/// [--######------] +/// [----########--] +/// [-----##-------] +/// ``` +/// +/// - Output: a Map that calculates where our 6 cores are spending their time. +/// Here each number means 50% of a core is working on it. A '-' represents an +/// idling core. We start with all 6 cores idling. Then all cores start to +/// work on task 0. 2s in, task1 starts and we have to split cores. 2.5s in +/// task2 starts. We have 6 physical threads but work to fill 13. Later task 2 +/// and task 0 will finish their work and give more threads for task 1 to work +/// on. Read top to bottom +/// ```ignore +/// 0-1s [------------] +/// 1-2s [000000000000] +/// 2-2.5s [000000111111] +/// 2.5-3.5s [000001111122] +/// 3.5-4s [000000111111] +/// 4-6s [111111111111] +/// 6s.. [------------] +/// ``` +pub fn gen_stats( + timelines: &HashMap, + tick_work_start: Instant, + rayon_threads: u16, + physical_threads: u16, +) -> HashMap { + let mut result = HashMap::new(); + let mut all = timelines + .iter() + .map(|(s, t)| { + let mut stat = CpuTimeStats::default(); + stat.measures.push((0, 0.0)); + result.insert(s.clone(), stat); + t.measures.iter().map(|e| &e.0) + }) + .flatten() + .collect::>(); + + all.sort(); + all.dedup(); + for time in all { + let relative_time = time.duration_since(tick_work_start).as_nanos() as u64; + // get all parallelisation at this particular time + let individual_cores_wanted = timelines + .iter() + .map(|(k, t)| (k, t.get(*time).threads(rayon_threads))) + .collect::>(); + let total = individual_cores_wanted + .iter() + .map(|(_, a)| a) + .sum::() + .max(1) as f32; + let total_or_max = total.max(physical_threads as f32); + // update ALL states + for individual in individual_cores_wanted.iter() { + let actual = (individual.1 as f32 / total_or_max) * physical_threads as f32; + let p = result.get_mut(individual.0).unwrap(); + if (p.measures.last().unwrap().1 - actual).abs() > 0.0001 { + p.measures.push((relative_time, actual)); + } + } + } + result +} + +/// This trait wraps around specs::System and does additional veloren tasks like +/// metrics collection +/// +/// ``` +/// use specs::Read; +/// pub use veloren_common::system::{Job, Origin, ParMode, Phase, System}; +/// # use std::time::Duration; +/// pub struct Sys; +/// impl<'a> System<'a> for Sys { +/// type SystemData = (Read<'a, ()>, Read<'a, ()>); +/// +/// const NAME: &'static str = "example"; +/// const ORIGIN: Origin = Origin::Frontend("voxygen"); +/// const PHASE: Phase = Phase::Create; +/// +/// fn run(job: &mut Job, (_read, _read2): Self::SystemData) { +/// std::thread::sleep(Duration::from_millis(100)); +/// job.cpu_stats.measure(ParMode::Rayon); +/// std::thread::sleep(Duration::from_millis(500)); +/// job.cpu_stats.measure(ParMode::Single); +/// std::thread::sleep(Duration::from_millis(40)); +/// } +/// } +/// ``` +pub trait System<'a> { + const NAME: &'static str; + const PHASE: Phase; + const ORIGIN: Origin; + + type SystemData: specs::SystemData<'a>; + fn run(job: &mut Job, data: Self::SystemData); + fn sys_name() -> String { format!("{}_sys", Self::NAME) } +} + +pub fn dispatch<'a, 'b, T>(builder: &mut specs::DispatcherBuilder<'a, 'b>, dep: &[&str]) +where + T: for<'c> System<'c> + Send + 'a + Default, +{ + builder.add(Job::::default(), &T::sys_name(), dep); +} + +pub fn run_now<'a, 'b, T>(world: &'a specs::World) +where + T: for<'c> System<'c> + Send + 'a + Default, +{ + Job::::default().run_now(world); +} + +/// This Struct will wrap the System in order to avoid the can only impl trait +/// for local defined structs error It also contains the cpu measurements +pub struct Job +where + T: ?Sized, +{ + pub own: Box, + pub cpu_stats: CpuTimeline, +} + +impl<'a, T> specs::System<'a> for Job +where + T: System<'a>, +{ + type SystemData = (T::SystemData, ReadExpect<'a, SysMetrics>); + + fn run(&mut self, data: Self::SystemData) { + crate::span!(_guard, "run", &format!("{}::Sys::run", T::NAME)); + self.cpu_stats.reset(); + T::run(self, data.0); + self.cpu_stats.end(); + data.1 + .stats + .lock() + .unwrap() + .insert(T::NAME.to_string(), self.cpu_stats.clone()); + } +} + +impl<'a, T> Default for Job +where + T: System<'a> + Default, +{ + fn default() -> Self { + Self { + own: Box::new(T::default()), + cpu_stats: CpuTimeline::default(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use float_cmp::approx_eq; + use std::time::Duration; + + fn mock_timelines( + tick_start: Instant, + durations: Vec<(u64, u64, ParMode)>, + ) -> HashMap { + let job = durations + .iter() + .enumerate() + .map(|(i, (s, e, p))| { + ( + i, + tick_start + Duration::from_millis(*s), + tick_start + Duration::from_millis(*e), + *p, + ) + }) + .collect::>(); + + job.iter() + .map(|(i, f, s, p)| { + (i.to_string(), CpuTimeline { + measures: vec![(*f, *p), (*s, ParMode::None)], + }) + }) + .collect() + } + + #[test] + fn single() { + const RAYON_THREADS: u16 = 4; + const PHYSICAL_THREADS: u16 = RAYON_THREADS; + let tick_start = Instant::now(); + let job_d = vec![(500, 1500, ParMode::Rayon)]; + let timelines = mock_timelines(tick_start, job_d); + + let stats = gen_stats(&timelines, tick_start, RAYON_THREADS, PHYSICAL_THREADS); + + const THREADS: f32 = PHYSICAL_THREADS as f32; + + let s = &stats["0"]; + let measures = &s.measures; + assert_eq!(measures.len(), 3); + assert_eq!(measures[0].0, 0); + assert!(approx_eq!(f32, measures[0].1, 0.0)); + assert_eq!(measures[1].0, 500000000); + assert!(approx_eq!(f32, measures[1].1, THREADS)); + assert_eq!(measures[2].0, 1500000000); + assert!(approx_eq!(f32, measures[2].1, 0.0)); + assert_eq!(s.start_ns(), 500000000); + assert_eq!(s.end_ns(), 1500000000); + assert_eq!(s.length_ns(), 1000000000); + assert!(approx_eq!(f32, s.avg_threads(), THREADS)); + } + + #[test] + fn two_jobs() { + const RAYON_THREADS: u16 = 8; + const PHYSICAL_THREADS: u16 = RAYON_THREADS; + let tick_start = Instant::now(); + let job_d = vec![(2000, 3000, ParMode::Single), (5000, 6500, ParMode::Single)]; + let timelines = mock_timelines(tick_start, job_d); + + let stats = gen_stats(&timelines, tick_start, RAYON_THREADS, PHYSICAL_THREADS); + + let s = &stats["0"]; + let measures = &s.measures; + assert_eq!(measures.len(), 3); + assert_eq!(measures[0].0, 0); + assert!(approx_eq!(f32, measures[0].1, 0.0)); + assert_eq!(measures[1].0, 2000000000); + assert!(approx_eq!(f32, measures[1].1, 1.0)); + assert_eq!(measures[2].0, 3000000000); + assert!(approx_eq!(f32, measures[2].1, 0.0)); + assert_eq!(s.start_ns(), 2000000000); + assert_eq!(s.end_ns(), 3000000000); + assert_eq!(s.length_ns(), 1000000000); + assert!(approx_eq!(f32, s.avg_threads(), 1.0)); + + let s = &stats["1"]; + let measures = &s.measures; + assert_eq!(measures.len(), 3); + assert_eq!(measures[0].0, 0); + assert!(approx_eq!(f32, measures[0].1, 0.0)); + assert_eq!(measures[1].0, 5000000000); + assert!(approx_eq!(f32, measures[1].1, 1.0)); + assert_eq!(measures[2].0, 6500000000); + assert!(approx_eq!(f32, measures[2].1, 0.0)); + assert_eq!(s.start_ns(), 5000000000); + assert_eq!(s.end_ns(), 6500000000); + assert_eq!(s.length_ns(), 1500000000); + assert!(approx_eq!(f32, s.avg_threads(), 1.0)); + } + + #[test] + fn generate_stats() { + const RAYON_THREADS: u16 = 6; + const PHYSICAL_THREADS: u16 = RAYON_THREADS; + let tick_start = Instant::now(); + let job_d = vec![ + (2000, 5000, ParMode::Rayon), + (3000, 7000, ParMode::Rayon), + (3500, 4500, ParMode::Single), + ]; + let timelines = mock_timelines(tick_start, job_d); + + let stats = gen_stats(&timelines, tick_start, RAYON_THREADS, PHYSICAL_THREADS); + + const THREADS: f32 = PHYSICAL_THREADS as f32; + + let s = &stats["0"]; + let measures = &s.measures; + assert_eq!(measures.len(), 6); + assert_eq!(measures[0].0, 0); + assert!(approx_eq!(f32, measures[0].1, 0.0)); + assert_eq!(measures[1].0, 2000000000); + assert!(approx_eq!(f32, measures[1].1, THREADS)); + assert_eq!(measures[2].0, 3000000000); + assert!(approx_eq!(f32, measures[2].1, THREADS / 2.0)); + assert_eq!(measures[3].0, 3500000000); + assert!(approx_eq!( + f32, + measures[3].1, + THREADS * THREADS / (THREADS * 2.0 + 1.0) + )); + assert_eq!(measures[4].0, 4500000000); + assert!(approx_eq!(f32, measures[4].1, THREADS / 2.0)); + assert_eq!(measures[5].0, 5000000000); + assert!(approx_eq!(f32, measures[5].1, 0.0)); + assert_eq!(s.start_ns(), 2000000000); + assert_eq!(s.end_ns(), 5000000000); + assert_eq!(s.length_ns(), 3000000000); + assert!(approx_eq!(f32, s.avg_threads(), 3.923077)); + + let s = &stats["1"]; + let measures = &s.measures; + assert_eq!(measures.len(), 6); + assert_eq!(measures[0].0, 0); + assert!(approx_eq!(f32, measures[0].1, 0.0)); + assert_eq!(measures[1].0, 3000000000); + assert!(approx_eq!(f32, measures[1].1, THREADS / 2.0)); + assert_eq!(measures[2].0, 3500000000); + assert!(approx_eq!( + f32, + measures[2].1, + THREADS * THREADS / (THREADS * 2.0 + 1.0) + )); + assert_eq!(measures[3].0, 4500000000); + assert!(approx_eq!(f32, measures[3].1, THREADS / 2.0)); + assert_eq!(measures[4].0, 5000000000); + assert!(approx_eq!(f32, measures[4].1, THREADS)); + assert_eq!(measures[5].0, 7000000000); + assert!(approx_eq!(f32, measures[5].1, 0.0)); + assert_eq!(s.start_ns(), 3000000000); + assert_eq!(s.end_ns(), 7000000000); + assert_eq!(s.length_ns(), 4000000000); + assert!(approx_eq!(f32, s.avg_threads(), 4.4423075)); + + let s = &stats["2"]; + let measures = &s.measures; + assert_eq!(measures.len(), 3); + assert_eq!(measures[0].0, 0); + assert!(approx_eq!(f32, measures[0].1, 0.0)); + assert_eq!(measures[1].0, 3500000000); + assert!(approx_eq!( + f32, + measures[1].1, + THREADS / (THREADS * 2.0 + 1.0) + )); + assert_eq!(measures[2].0, 4500000000); + assert!(approx_eq!(f32, measures[2].1, 0.0)); + assert_eq!(s.start_ns(), 3500000000); + assert_eq!(s.end_ns(), 4500000000); + assert_eq!(s.length_ns(), 1000000000); + assert!(approx_eq!(f32, s.avg_threads(), 0.4615385)); + } +} diff --git a/common/sys/src/aura.rs b/common/sys/src/aura.rs index 3a7dda3f6b..90829a39e0 100644 --- a/common/sys/src/aura.rs +++ b/common/sys/src/aura.rs @@ -7,11 +7,12 @@ use common::{ }, event::{EventBus, ServerEvent}, resources::DeltaTime, + system::{Job, Origin, Phase, System}, uid::UidAllocator, }; use specs::{ - saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, System, - SystemData, World, WriteStorage, + saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, + World, WriteStorage, }; use std::time::Duration; @@ -27,6 +28,7 @@ pub struct ReadData<'a> { groups: ReadStorage<'a, Group>, } +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( @@ -35,7 +37,11 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Buffs>, ); - fn run(&mut self, (read_data, mut auras, mut buffs): Self::SystemData) { + const NAME: &'static str = "aura"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + fn run(_job: &mut Job, (read_data, mut auras, mut buffs): Self::SystemData) { let mut server_emitter = read_data.server_bus.emitter(); let dt = read_data.dt.0; diff --git a/common/sys/src/beam.rs b/common/sys/src/beam.rs index 0f4cdf130d..60ebd77d07 100644 --- a/common/sys/src/beam.rs +++ b/common/sys/src/beam.rs @@ -6,12 +6,13 @@ use common::{ }, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, + system::{Job, Origin, Phase, System}, uid::{Uid, UidAllocator}, GroupTarget, }; use specs::{ - saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, System, - SystemData, World, WriteStorage, + saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, + World, WriteStorage, }; use std::time::Duration; use vek::*; @@ -37,6 +38,7 @@ pub struct ReadData<'a> { } /// This system is responsible for handling beams that heal or do damage +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( @@ -45,7 +47,11 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Beam>, ); - fn run(&mut self, (read_data, mut beam_segments, mut beams): Self::SystemData) { + const NAME: &'static str = "beam"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + fn run(_job: &mut Job, (read_data, mut beam_segments, mut beams): Self::SystemData) { let mut server_emitter = read_data.server_bus.emitter(); let time = read_data.time.0; diff --git a/common/sys/src/buff.rs b/common/sys/src/buff.rs index 2a2aaa8c05..9453e64b4b 100644 --- a/common/sys/src/buff.rs +++ b/common/sys/src/buff.rs @@ -5,10 +5,11 @@ use common::{ }, event::{EventBus, ServerEvent}, resources::DeltaTime, + system::{Job, Origin, Phase, System}, Damage, DamageSource, }; use specs::{ - shred::ResourceId, Entities, Join, Read, ReadStorage, System, SystemData, World, WriteStorage, + shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage, }; use std::time::Duration; @@ -20,6 +21,7 @@ pub struct ReadData<'a> { inventories: ReadStorage<'a, Inventory>, } +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( @@ -30,8 +32,12 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Stats>, ); + const NAME: &'static str = "buff"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, (read_data, mut healths, mut energies, mut buffs, mut stats): Self::SystemData, ) { let mut server_emitter = read_data.server_bus.emitter(); diff --git a/common/sys/src/character_behavior.rs b/common/sys/src/character_behavior.rs index fc83ab21ec..8783f29955 100644 --- a/common/sys/src/character_behavior.rs +++ b/common/sys/src/character_behavior.rs @@ -1,6 +1,6 @@ use specs::{ - shred::ResourceId, Entities, Join, LazyUpdate, Read, ReadExpect, ReadStorage, System, - SystemData, World, WriteStorage, + shred::ResourceId, Entities, Join, LazyUpdate, Read, ReadStorage, SystemData, World, + WriteStorage, }; use common::{ @@ -13,13 +13,12 @@ use common::{ Ori, PhysicsState, Poise, PoiseState, Pos, StateUpdate, Stats, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, - metrics::SysMetrics, resources::DeltaTime, - span, states::{ self, behavior::{CharacterBehavior, JoinData, JoinStruct}, }, + system::{Job, Origin, Phase, System}, uid::Uid, }; use std::time::Duration; @@ -56,7 +55,6 @@ pub struct ReadData<'a> { local_bus: Read<'a, EventBus>, dt: Read<'a, DeltaTime>, lazy_update: Read<'a, LazyUpdate>, - metrics: ReadExpect<'a, SysMetrics>, healths: ReadStorage<'a, Health>, bodies: ReadStorage<'a, Body>, physics_states: ReadStorage<'a, PhysicsState>, @@ -72,6 +70,7 @@ pub struct ReadData<'a> { /// ## Character Behavior System /// Passes `JoinData` to `CharacterState`'s `behavior` handler fn's. Receives a /// `StateUpdate` in return and performs updates to ECS Components from that. +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { @@ -88,9 +87,13 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Poise>, ); + const NAME: &'static str = "character_behavior"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + #[allow(clippy::while_let_on_iterator)] // TODO: Pending review in #587 fn run( - &mut self, + _job: &mut Job, ( read_data, mut character_states, @@ -103,8 +106,6 @@ impl<'a> System<'a> for Sys { mut poises, ): Self::SystemData, ) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "character_behavior::Sys::run"); let mut server_emitter = read_data.server_bus.emitter(); let mut local_emitter = read_data.local_bus.emitter(); @@ -347,9 +348,5 @@ impl<'a> System<'a> for Sys { server_emitter.append(&mut state_update.server_events); incorporate_update(&mut join_struct, state_update); } - read_data.metrics.character_behavior_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); } } diff --git a/common/sys/src/controller.rs b/common/sys/src/controller.rs index 8618c55e97..40c0a07ce9 100644 --- a/common/sys/src/controller.rs +++ b/common/sys/src/controller.rs @@ -1,14 +1,13 @@ use common::{ comp::{BuffChange, ControlEvent, Controller}, event::{EventBus, ServerEvent}, - metrics::SysMetrics, - span, + system::{Job, Origin, Phase, System}, uid::UidAllocator, }; use specs::{ saveload::{Marker, MarkerAllocator}, shred::ResourceId, - Entities, Join, Read, ReadExpect, System, SystemData, World, WriteStorage, + Entities, Join, Read, SystemData, World, WriteStorage, }; use vek::*; @@ -17,17 +16,19 @@ pub struct ReadData<'a> { entities: Entities<'a>, uid_allocator: Read<'a, UidAllocator>, server_bus: Read<'a, EventBus>, - metrics: ReadExpect<'a, SysMetrics>, } +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = (ReadData<'a>, WriteStorage<'a, Controller>); - fn run(&mut self, (read_data, mut controllers): Self::SystemData) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "controller::Sys::run"); + const NAME: &'static str = "controller"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + fn run(_job: &mut Job, (read_data, mut controllers): Self::SystemData) { let mut server_emitter = read_data.server_bus.emitter(); for (entity, controller) in (&read_data.entities, &mut controllers).join() { @@ -103,9 +104,5 @@ impl<'a> System<'a> for Sys { } } } - read_data.metrics.controller_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); } } diff --git a/common/sys/src/lib.rs b/common/sys/src/lib.rs index 560e56af65..c0dc0e99bf 100644 --- a/common/sys/src/lib.rs +++ b/common/sys/src/lib.rs @@ -15,33 +15,23 @@ pub mod state; mod stats; // External +use common::system::{dispatch, System}; use specs::DispatcherBuilder; -// System names -pub const CHARACTER_BEHAVIOR_SYS: &str = "character_behavior_sys"; -pub const MELEE_SYS: &str = "melee_sys"; -pub const BEAM_SYS: &str = "beam_sys"; -pub const CONTROLLER_SYS: &str = "controller_sys"; -pub const MOUNT_SYS: &str = "mount_sys"; -pub const PHYS_SYS: &str = "phys_sys"; -pub const PROJECTILE_SYS: &str = "projectile_sys"; -pub const SHOCKWAVE_SYS: &str = "shockwave_sys"; -pub const STATS_SYS: &str = "stats_sys"; -pub const BUFFS_SYS: &str = "buffs_sys"; -pub const AURAS_SYS: &str = "auras_sys"; - pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { - dispatch_builder.add(mount::Sys, MOUNT_SYS, &[]); - dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[MOUNT_SYS]); - dispatch_builder.add(character_behavior::Sys, CHARACTER_BEHAVIOR_SYS, &[ - CONTROLLER_SYS, + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[&mount::Sys::sys_name()]); + dispatch::(dispatch_builder, &[&controller::Sys::sys_name()]); + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[ + &controller::Sys::sys_name(), + &mount::Sys::sys_name(), + &stats::Sys::sys_name(), ]); - dispatch_builder.add(stats::Sys, STATS_SYS, &[]); - dispatch_builder.add(buff::Sys, BUFFS_SYS, &[]); - dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS, MOUNT_SYS, STATS_SYS]); - dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]); - dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]); - dispatch_builder.add(beam::Sys, BEAM_SYS, &[PHYS_SYS]); - dispatch_builder.add(melee::Sys, MELEE_SYS, &[PROJECTILE_SYS]); - dispatch_builder.add(aura::Sys, AURAS_SYS, &[]); + dispatch::(dispatch_builder, &[&phys::Sys::sys_name()]); + dispatch::(dispatch_builder, &[&phys::Sys::sys_name()]); + dispatch::(dispatch_builder, &[&phys::Sys::sys_name()]); + dispatch::(dispatch_builder, &[&projectile::Sys::sys_name()]); + dispatch::(dispatch_builder, &[]); } diff --git a/common/sys/src/melee.rs b/common/sys/src/melee.rs index 548b76f143..7bf88d813a 100644 --- a/common/sys/src/melee.rs +++ b/common/sys/src/melee.rs @@ -2,15 +2,13 @@ use common::{ combat::{AttackerInfo, TargetInfo}, comp::{Body, CharacterState, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale, Stats}, event::{EventBus, ServerEvent}, - metrics::SysMetrics, - span, + system::{Job, Origin, Phase, System}, uid::Uid, util::Dir, GroupTarget, }; use specs::{ - shred::ResourceId, Entities, Join, Read, ReadExpect, ReadStorage, System, SystemData, World, - WriteStorage, + shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage, }; use vek::*; @@ -28,20 +26,22 @@ pub struct ReadData<'a> { groups: ReadStorage<'a, Group>, char_states: ReadStorage<'a, CharacterState>, server_bus: Read<'a, EventBus>, - metrics: ReadExpect<'a, SysMetrics>, stats: ReadStorage<'a, Stats>, } /// This system is responsible for handling accepted inputs like moving or /// attacking +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = (ReadData<'a>, WriteStorage<'a, Melee>); - fn run(&mut self, (read_data, mut melee_attacks): Self::SystemData) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "melee::Sys::run"); + const NAME: &'static str = "melee"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + fn run(_job: &mut Job, (read_data, mut melee_attacks): Self::SystemData) { let mut server_emitter = read_data.server_bus.emitter(); // Attacks for (attacker, uid, pos, ori, melee_attack, body) in ( @@ -135,9 +135,5 @@ impl<'a> System<'a> for Sys { } } } - read_data.metrics.melee_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); } } diff --git a/common/sys/src/mount.rs b/common/sys/src/mount.rs index 80b71cfed5..68f1bedb9c 100644 --- a/common/sys/src/mount.rs +++ b/common/sys/src/mount.rs @@ -1,22 +1,21 @@ use common::{ comp::{Controller, MountState, Mounting, Ori, Pos, Vel}, - metrics::SysMetrics, - span, + system::{Job, Origin, Phase, System}, uid::UidAllocator, }; use specs::{ saveload::{Marker, MarkerAllocator}, - Entities, Join, Read, ReadExpect, System, WriteStorage, + Entities, Join, Read, WriteStorage, }; use vek::*; /// This system is responsible for controlling mounts +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = ( Read<'a, UidAllocator>, - ReadExpect<'a, SysMetrics>, Entities<'a>, WriteStorage<'a, Controller>, WriteStorage<'a, MountState>, @@ -26,11 +25,14 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Ori>, ); + const NAME: &'static str = "mount"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( uid_allocator, - sys_metrics, entities, mut controllers, mut mount_state, @@ -40,8 +42,6 @@ impl<'a> System<'a> for Sys { mut orientations, ): Self::SystemData, ) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "mount::Sys::run"); // Mounted entities. for (entity, mut mount_states) in (&entities, &mut mount_state.restrict_mut()).join() { match mount_states.get_unchecked() { @@ -92,9 +92,5 @@ impl<'a> System<'a> for Sys { for entity in to_unmount { mountings.remove(entity); } - sys_metrics.mount_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); } } diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index f576447082..c8277ec0db 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -5,17 +5,16 @@ use common::{ }, consts::{FRIC_GROUND, GRAVITY}, event::{EventBus, ServerEvent}, - metrics::{PhysicsMetrics, SysMetrics}, + metrics::PhysicsMetrics, resources::DeltaTime, span, + system::{Job, Origin, ParMode, Phase, System}, terrain::{Block, TerrainGrid}, uid::Uid, vol::ReadVol, }; use rayon::iter::ParallelIterator; -use specs::{ - Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, System, WriteExpect, WriteStorage, -}; +use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use std::ops::Range; use vek::*; @@ -61,6 +60,7 @@ fn calc_z_limit( } /// This system applies forces and calculates new positions and velocities. +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { @@ -70,7 +70,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Uid>, ReadExpect<'a, TerrainGrid>, Read<'a, DeltaTime>, - ReadExpect<'a, SysMetrics>, WriteExpect<'a, PhysicsMetrics>, Read<'a, EventBus>, ReadStorage<'a, Scale>, @@ -90,16 +89,19 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, CharacterState>, ); + const NAME: &'static str = "phys"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 fn run( - &mut self, + job: &mut Job, ( entities, uids, terrain, dt, - sys_metrics, mut physics_metrics, event_bus, scales, @@ -119,8 +121,6 @@ impl<'a> System<'a> for Sys { char_states, ): Self::SystemData, ) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "phys::Sys::run"); let mut event_emitter = event_bus.emitter(); // Add/reset physics state components @@ -212,6 +212,7 @@ impl<'a> System<'a> for Sys { drop(guard); span!(guard, "Apply pushback"); + job.cpu_stats.measure(ParMode::Rayon); let metrics = ( &entities, &positions, @@ -776,13 +777,10 @@ impl<'a> System<'a> for Sys { land_on_grounds_a }); drop(guard); + job.cpu_stats.measure(ParMode::Single); land_on_grounds.into_iter().for_each(|(entity, vel)| { event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 }); }); - sys_metrics.phys_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); } } diff --git a/common/sys/src/projectile.rs b/common/sys/src/projectile.rs index 45701c3cb3..b09c28d10d 100644 --- a/common/sys/src/projectile.rs +++ b/common/sys/src/projectile.rs @@ -5,16 +5,15 @@ use common::{ Stats, Vel, }, event::{EventBus, ServerEvent}, - metrics::SysMetrics, resources::DeltaTime, - span, + system::{Job, Origin, Phase, System}, uid::UidAllocator, util::Dir, GroupTarget, }; use specs::{ - saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadExpect, ReadStorage, - System, SystemData, World, WriteStorage, + saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, + World, WriteStorage, }; use std::time::Duration; @@ -24,7 +23,6 @@ pub struct ReadData<'a> { dt: Read<'a, DeltaTime>, uid_allocator: Read<'a, UidAllocator>, server_bus: Read<'a, EventBus>, - metrics: ReadExpect<'a, SysMetrics>, positions: ReadStorage<'a, Pos>, physics_states: ReadStorage<'a, PhysicsState>, velocities: ReadStorage<'a, Vel>, @@ -35,6 +33,7 @@ pub struct ReadData<'a> { } /// This system is responsible for handling projectile effect triggers +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( @@ -43,9 +42,11 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Projectile>, ); - fn run(&mut self, (read_data, mut orientations, mut projectiles): Self::SystemData) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "projectile::Sys::run"); + const NAME: &'static str = "projectile"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + fn run(_job: &mut Job, (read_data, mut orientations, mut projectiles): Self::SystemData) { let mut server_emitter = read_data.server_bus.emitter(); // Attacks @@ -204,9 +205,5 @@ impl<'a> System<'a> for Sys { .checked_sub(Duration::from_secs_f32(read_data.dt.0)) .unwrap_or_default(); } - read_data.metrics.projectile_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); } } diff --git a/common/sys/src/shockwave.rs b/common/sys/src/shockwave.rs index 80b5a12efa..20b084e3df 100644 --- a/common/sys/src/shockwave.rs +++ b/common/sys/src/shockwave.rs @@ -6,13 +6,14 @@ use common::{ }, event::{EventBus, ServerEvent}, resources::{DeltaTime, Time}, + system::{Job, Origin, Phase, System}, uid::{Uid, UidAllocator}, util::Dir, GroupTarget, }; use specs::{ - saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, System, - SystemData, World, WriteStorage, + saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, + World, WriteStorage, }; use vek::*; @@ -39,6 +40,7 @@ pub struct ReadData<'a> { /// This system is responsible for handling accepted inputs like moving or /// attacking +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( @@ -47,7 +49,14 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, ShockwaveHitEntities>, ); - fn run(&mut self, (read_data, mut shockwaves, mut shockwave_hit_lists): Self::SystemData) { + const NAME: &'static str = "shockwave"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + fn run( + _job: &mut Job, + (read_data, mut shockwaves, mut shockwave_hit_lists): Self::SystemData, + ) { let mut server_emitter = read_data.server_bus.emitter(); let time = read_data.time.0; diff --git a/common/sys/src/stats.rs b/common/sys/src/stats.rs index fa9fdb8e21..caa403d9a9 100644 --- a/common/sys/src/stats.rs +++ b/common/sys/src/stats.rs @@ -5,16 +5,14 @@ use common::{ PoiseChange, PoiseSource, Pos, Stats, }, event::{EventBus, ServerEvent}, - metrics::SysMetrics, outcome::Outcome, resources::{DeltaTime, Time}, - span, + system::{Job, Origin, Phase, System}, uid::Uid, }; use hashbrown::HashSet; use specs::{ - shred::ResourceId, Entities, Join, Read, ReadExpect, ReadStorage, System, SystemData, World, - Write, WriteStorage, + shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, Write, WriteStorage, }; use vek::Vec3; @@ -28,7 +26,6 @@ pub struct ReadData<'a> { dt: Read<'a, DeltaTime>, time: Read<'a, Time>, server_bus: Read<'a, EventBus>, - metrics: ReadExpect<'a, SysMetrics>, positions: ReadStorage<'a, Pos>, uids: ReadStorage<'a, Uid>, bodies: ReadStorage<'a, Body>, @@ -36,6 +33,7 @@ pub struct ReadData<'a> { } /// This system kills players, levels them up, and regenerates energy. +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -49,8 +47,12 @@ impl<'a> System<'a> for Sys { Write<'a, Vec>, ); + const NAME: &'static str = "stats"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( read_data, mut stats, @@ -61,8 +63,6 @@ impl<'a> System<'a> for Sys { mut outcomes, ): Self::SystemData, ) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "stats::Sys::run"); let mut server_event_emitter = read_data.server_bus.emitter(); let dt = read_data.dt.0; @@ -266,10 +266,5 @@ impl<'a> System<'a> for Sys { combo.reset(); } } - - read_data.metrics.stats_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 8215c01e48..4729a738ba 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -64,6 +64,7 @@ use common::{ recipe::default_recipe_book, resources::TimeOfDay, rtsim::RtSimEntity, + system::run_now, terrain::TerrainChunkSize, vol::{ReadVol, RectVolSize}, }; @@ -78,7 +79,7 @@ use common_net::{ use common_sys::plugin::PluginMgr; use common_sys::{plugin::memory_manager::EcsWorld, state::State}; use hashbrown::HashMap; -use metrics::{PhysicsMetrics, PlayerMetrics, StateTickMetrics, TickMetrics}; +use metrics::{EcsSystemMetrics, PhysicsMetrics, PlayerMetrics, TickMetrics}; use network::{Network, Pid, ProtocolAddr}; use persistence::{ character_loader::{CharacterLoader, CharacterLoaderResponseKind}, @@ -87,11 +88,11 @@ use persistence::{ use plugin_api::Uid; use prometheus::Registry; use prometheus_hyper::Server as PrometheusServer; -use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt}; +use specs::{join::Join, Builder, Entity as EcsEntity, SystemData, WorldExt}; use std::{ i32, ops::{Deref, DerefMut}, - sync::{atomic::Ordering, Arc}, + sync::Arc, time::{Duration, Instant}, }; #[cfg(not(feature = "worldgen"))] @@ -117,6 +118,10 @@ struct SpawnPoint(Vec3); #[derive(Copy, Clone, Default)] pub struct Tick(u64); +// Start of Tick, used for metrics +#[derive(Copy, Clone)] +pub struct TickStart(Instant); + pub struct Server { state: State, world: Arc, @@ -128,9 +133,6 @@ pub struct Server { runtime: Arc, metrics_shutdown: Arc, - tick_metrics: TickMetrics, - state_tick_metrics: StateTickMetrics, - physics_metrics: PhysicsMetrics, } impl Server { @@ -158,10 +160,13 @@ impl Server { panic!("Migration error: {:?}", e); } - let (chunk_gen_metrics, registry_chunk) = metrics::ChunkGenMetrics::new().unwrap(); - let (network_request_metrics, registry_network) = - metrics::NetworkRequestMetrics::new().unwrap(); - let (player_metrics, registry_player) = metrics::PlayerMetrics::new().unwrap(); + let registry = Arc::new(Registry::new()); + let chunk_gen_metrics = metrics::ChunkGenMetrics::new(®istry).unwrap(); + let network_request_metrics = metrics::NetworkRequestMetrics::new(®istry).unwrap(); + let player_metrics = metrics::PlayerMetrics::new(®istry).unwrap(); + let ecs_system_metrics = EcsSystemMetrics::new(®istry).unwrap(); + let tick_metrics = TickMetrics::new(®istry).unwrap(); + let physics_metrics = PhysicsMetrics::new(®istry).unwrap(); let mut state = State::server(); state.ecs_mut().insert(settings.clone()); @@ -174,8 +179,12 @@ impl Server { .ecs_mut() .insert(LoginProvider::new(settings.auth_server_address.clone())); state.ecs_mut().insert(Tick(0)); + state.ecs_mut().insert(TickStart(Instant::now())); state.ecs_mut().insert(network_request_metrics); state.ecs_mut().insert(player_metrics); + state.ecs_mut().insert(ecs_system_metrics); + state.ecs_mut().insert(tick_metrics); + state.ecs_mut().insert(physics_metrics); state .ecs_mut() .insert(ChunkGenerator::new(chunk_gen_metrics)); @@ -195,23 +204,6 @@ impl Server { .ecs_mut() .insert(CharacterLoader::new(&persistence_db_dir)?); - // System timers for performance monitoring - state.ecs_mut().insert(sys::EntitySyncTimer::default()); - state.ecs_mut().insert(sys::GeneralMsgTimer::default()); - state.ecs_mut().insert(sys::PingMsgTimer::default()); - state - .ecs_mut() - .insert(sys::CharacterScreenMsgTimer::default()); - state.ecs_mut().insert(sys::InGameMsgTimer::default()); - state.ecs_mut().insert(sys::SentinelTimer::default()); - state.ecs_mut().insert(sys::SubscriptionTimer::default()); - state.ecs_mut().insert(sys::TerrainSyncTimer::default()); - state.ecs_mut().insert(sys::TerrainTimer::default()); - state.ecs_mut().insert(sys::WaypointTimer::default()); - state.ecs_mut().insert(sys::InviteTimeoutTimer::default()); - state.ecs_mut().insert(sys::PersistenceTimer::default()); - state.ecs_mut().insert(sys::AgentTimer::default()); - // System schedulers to control execution of systems state .ecs_mut() @@ -356,20 +348,6 @@ impl Server { state.ecs_mut().insert(DeletedEntities::default()); - // register all metrics submodules here - let (tick_metrics, registry_tick) = - TickMetrics::new().expect("Failed to initialize server tick metrics submodule."); - let (state_tick_metrics, registry_state) = StateTickMetrics::new().unwrap(); - let (physics_metrics, registry_physics) = PhysicsMetrics::new().unwrap(); - - let registry = Arc::new(Registry::new()); - registry_chunk(®istry).expect("failed to register chunk gen metrics"); - registry_network(®istry).expect("failed to register network request metrics"); - registry_player(®istry).expect("failed to register player metrics"); - registry_tick(®istry).expect("failed to register tick metrics"); - registry_state(®istry).expect("failed to register state metrics"); - registry_physics(®istry).expect("failed to register state metrics"); - let network = Network::new_with_registry(Pid::new(), &runtime, ®istry); let metrics_shutdown = Arc::new(Notify::new()); let metrics_shutdown_clone = Arc::clone(&metrics_shutdown); @@ -402,9 +380,6 @@ impl Server { runtime, metrics_shutdown, - tick_metrics, - state_tick_metrics, - physics_metrics, }; debug!(?settings, "created veloren server with"); @@ -472,6 +447,8 @@ impl Server { /// the given duration. pub fn tick(&mut self, _input: Input, dt: Duration) -> Result, Error> { self.state.ecs().write_resource::().0 += 1; + self.state.ecs().write_resource::().0 = Instant::now(); + // This tick function is the centre of the Veloren universe. Most server-side // things are managed from here, and as such it's important that it // stays organised. Please consult the core developers before making @@ -510,12 +487,12 @@ impl Server { // Run message receiving sys before the systems in common for decreased latency // (e.g. run before controller system) //TODO: run in parallel - sys::msg::general::Sys.run_now(&self.state.ecs()); + run_now::(&self.state.ecs()); self.register_run(); - sys::msg::character_screen::Sys.run_now(&self.state.ecs()); - sys::msg::in_game::Sys.run_now(&self.state.ecs()); - sys::msg::ping::Sys.run_now(&self.state.ecs()); - sys::agent::Sys.run_now(&self.state.ecs()); + run_now::(&self.state.ecs()); + run_now::(&self.state.ecs()); + run_now::(&self.state.ecs()); + run_now::(&self.state.ecs()); let before_state_tick = Instant::now(); @@ -704,211 +681,29 @@ impl Server { let end_of_server_tick = Instant::now(); // 8) Update Metrics - // Get system timing info - let agent_nanos = self.state.ecs().read_resource::().nanos as i64; - let entity_sync_nanos = self - .state - .ecs() - .read_resource::() - .nanos as i64; - let message_nanos = { - let state = self.state.ecs(); - (state.read_resource::().nanos - + state.read_resource::().nanos - + state.read_resource::().nanos - + state.read_resource::().nanos) as i64 - }; - let sentinel_nanos = self.state.ecs().read_resource::().nanos as i64; - let subscription_nanos = self - .state - .ecs() - .read_resource::() - .nanos as i64; - let terrain_sync_nanos = self - .state - .ecs() - .read_resource::() - .nanos as i64; - let terrain_nanos = self.state.ecs().read_resource::().nanos as i64; - let waypoint_nanos = self.state.ecs().read_resource::().nanos as i64; - let invite_timeout_nanos = self - .state - .ecs() - .read_resource::() - .nanos as i64; - let stats_persistence_nanos = self - .state - .ecs() - .read_resource::() - .nanos as i64; - let total_sys_ran_in_dispatcher_nanos = - terrain_nanos + waypoint_nanos + invite_timeout_nanos + stats_persistence_nanos; + run_now::(&self.state.ecs()); - // Report timing info - self.tick_metrics - .tick_time - .with_label_values(&["new connections"]) - .set((before_message_system - before_new_connections).as_nanos() as i64); - self.tick_metrics - .tick_time - .with_label_values(&["state tick"]) - .set( - (before_handle_events - before_state_tick).as_nanos() as i64 - - total_sys_ran_in_dispatcher_nanos, - ); - self.tick_metrics - .tick_time - .with_label_values(&["handle server events"]) - .set((before_update_terrain_and_regions - before_handle_events).as_nanos() as i64); - self.tick_metrics - .tick_time - .with_label_values(&["update terrain and region map"]) - .set((before_sync - before_update_terrain_and_regions).as_nanos() as i64); - self.tick_metrics - .tick_time - .with_label_values(&["world tick"]) - .set((before_entity_cleanup - before_world_tick).as_nanos() as i64); - self.tick_metrics - .tick_time - .with_label_values(&["entity cleanup"]) - .set((before_persistence_updates - before_entity_cleanup).as_nanos() as i64); - self.tick_metrics - .tick_time - .with_label_values(&["persistence_updates"]) - .set((end_of_server_tick - before_persistence_updates).as_nanos() as i64); - self.tick_metrics - .tick_time - .with_label_values(&["entity sync"]) - .set(entity_sync_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["message"]) - .set(message_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["sentinel"]) - .set(sentinel_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["subscription"]) - .set(subscription_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["terrain sync"]) - .set(terrain_sync_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["terrain"]) - .set(terrain_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["waypoint"]) - .set(waypoint_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["invite timeout"]) - .set(invite_timeout_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["persistence:stats"]) - .set(stats_persistence_nanos); - self.tick_metrics - .tick_time - .with_label_values(&["agent"]) - .set(agent_nanos); - - //detailed state metrics { - let res = self - .state - .ecs() - .read_resource::(); - let c = &self.state_tick_metrics.state_tick_time_count; - let agent_ns = res.agent_ns.load(Ordering::Relaxed); - let mount_ns = res.mount_ns.load(Ordering::Relaxed); - let controller_ns = res.controller_ns.load(Ordering::Relaxed); - let character_behavior_ns = res.character_behavior_ns.load(Ordering::Relaxed); - let stats_ns = res.stats_ns.load(Ordering::Relaxed); - let phys_ns = res.phys_ns.load(Ordering::Relaxed); - let projectile_ns = res.projectile_ns.load(Ordering::Relaxed); - let melee_ns = res.melee_ns.load(Ordering::Relaxed); + // Report timing info + let tick_metrics = self.state.ecs().read_resource::(); - c.with_label_values(&[sys::AGENT_SYS]).inc_by(agent_ns); - c.with_label_values(&[common_sys::MOUNT_SYS]) - .inc_by(mount_ns); - c.with_label_values(&[common_sys::CONTROLLER_SYS]) - .inc_by(controller_ns); - c.with_label_values(&[common_sys::CHARACTER_BEHAVIOR_SYS]) - .inc_by(character_behavior_ns); - c.with_label_values(&[common_sys::STATS_SYS]) - .inc_by(stats_ns); - c.with_label_values(&[common_sys::PHYS_SYS]).inc_by(phys_ns); - c.with_label_values(&[common_sys::PROJECTILE_SYS]) - .inc_by(projectile_ns); - c.with_label_values(&[common_sys::MELEE_SYS]) - .inc_by(melee_ns); - - const NANOSEC_PER_SEC: f64 = Duration::from_secs(1).as_nanos() as f64; - let h = &self.state_tick_metrics.state_tick_time_hist; - h.with_label_values(&[sys::AGENT_SYS]) - .observe(agent_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common_sys::MOUNT_SYS]) - .observe(mount_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common_sys::CONTROLLER_SYS]) - .observe(controller_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common_sys::CHARACTER_BEHAVIOR_SYS]) - .observe(character_behavior_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common_sys::STATS_SYS]) - .observe(stats_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common_sys::PHYS_SYS]) - .observe(phys_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common_sys::PROJECTILE_SYS]) - .observe(projectile_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common_sys::MELEE_SYS]) - .observe(melee_ns as f64 / NANOSEC_PER_SEC); + let tt = &tick_metrics.tick_time; + tt.with_label_values(&["new connections"]) + .set((before_message_system - before_new_connections).as_nanos() as i64); + tt.with_label_values(&["handle server events"]) + .set((before_update_terrain_and_regions - before_handle_events).as_nanos() as i64); + tt.with_label_values(&["update terrain and region map"]) + .set((before_sync - before_update_terrain_and_regions).as_nanos() as i64); + tt.with_label_values(&["state"]) + .set((before_handle_events - before_state_tick).as_nanos() as i64); + tt.with_label_values(&["world tick"]) + .set((before_entity_cleanup - before_world_tick).as_nanos() as i64); + tt.with_label_values(&["entity cleanup"]) + .set((before_persistence_updates - before_entity_cleanup).as_nanos() as i64); + tt.with_label_values(&["persistence_updates"]) + .set((end_of_server_tick - before_persistence_updates).as_nanos() as i64); } - //detailed physics metrics - { - let res = self - .state - .ecs() - .read_resource::(); - - self.physics_metrics - .entity_entity_collision_checks_count - .inc_by(res.entity_entity_collision_checks); - self.physics_metrics - .entity_entity_collisions_count - .inc_by(res.entity_entity_collisions); - } - - // Report other info - self.tick_metrics - .time_of_day - .set(self.state.ecs().read_resource::().0); - if self.tick_metrics.is_100th_tick() { - let mut chonk_cnt = 0; - let mut group_cnt = 0; - let chunk_cnt = self.state.terrain().iter().fold(0, |a, (_, c)| { - chonk_cnt += 1; - group_cnt += c.sub_chunk_groups(); - a + c.sub_chunks_len() - }); - self.tick_metrics.chonks_count.set(chonk_cnt as i64); - self.tick_metrics.chunks_count.set(chunk_cnt as i64); - self.tick_metrics.chunk_groups_count.set(group_cnt as i64); - - let entity_count = self.state.ecs().entities().join().count(); - self.tick_metrics.entity_count.set(entity_count as i64); - } - //self.metrics.entity_count.set(self.state.); - self.tick_metrics - .tick_time - .with_label_values(&["metrics"]) - .set(end_of_server_tick.elapsed().as_nanos() as i64); - self.tick_metrics.tick(); - // 9) Finish the tick, pass control back to the frontend. Ok(frontend_events) diff --git a/server/src/metrics.rs b/server/src/metrics.rs index ac57121437..2af96a5e46 100644 --- a/server/src/metrics.rs +++ b/server/src/metrics.rs @@ -1,29 +1,27 @@ use prometheus::{ - Gauge, HistogramOpts, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, Opts, - Registry, + Gauge, GaugeVec, HistogramOpts, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, + Opts, Registry, }; use std::{ convert::TryInto, error::Error, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, time::{Duration, SystemTime, UNIX_EPOCH}, }; -type RegistryFn = Box Result<(), prometheus::Error>>; - pub struct PhysicsMetrics { pub entity_entity_collision_checks_count: IntCounter, pub entity_entity_collisions_count: IntCounter, } -pub struct StateTickMetrics { - // Counter will only give us granularity on pool speed (2s?) for actuall spike detection we +pub struct EcsSystemMetrics { + // Gauges give us detailed information for random ticks + pub system_start_time: IntGaugeVec, + pub system_length_time: IntGaugeVec, + pub system_thread_avg: GaugeVec, + // Counter will only give us granularity on pool speed (2s?) for actual spike detection we // need the Historgram - pub state_tick_time_hist: HistogramVec, - pub state_tick_time_count: IntCounterVec, + pub system_length_hist: HistogramVec, + pub system_length_count: IntCounterVec, } pub struct PlayerMetrics { @@ -54,11 +52,10 @@ pub struct TickMetrics { pub start_time: IntGauge, pub time_of_day: Gauge, pub light_count: IntGauge, - tick: Arc, } impl PhysicsMetrics { - pub fn new() -> Result<(Self, RegistryFn), prometheus::Error> { + pub fn new(registry: &Registry) -> Result { let entity_entity_collision_checks_count = IntCounter::with_opts(Opts::new( "entity_entity_collision_checks_count", "shows the number of collision checks", @@ -68,28 +65,18 @@ impl PhysicsMetrics { "shows the number of actual collisions detected", ))?; - let entity_entity_collision_checks_count_clone = - entity_entity_collision_checks_count.clone(); - let entity_entity_collisions_count_clone = entity_entity_collisions_count.clone(); + registry.register(Box::new(entity_entity_collision_checks_count.clone()))?; + registry.register(Box::new(entity_entity_collisions_count.clone()))?; - let f = |registry: &Registry| { - registry.register(Box::new(entity_entity_collision_checks_count_clone))?; - registry.register(Box::new(entity_entity_collisions_count_clone))?; - Ok(()) - }; - - Ok(( - Self { - entity_entity_collision_checks_count, - entity_entity_collisions_count, - }, - Box::new(f), - )) + Ok(Self { + entity_entity_collision_checks_count, + entity_entity_collisions_count, + }) } } -impl StateTickMetrics { - pub fn new() -> Result<(Self, RegistryFn), prometheus::Error> { +impl EcsSystemMetrics { + pub fn new(registry: &Registry) -> Result { let bucket = vec![ Duration::from_micros(1).as_secs_f64(), Duration::from_micros(10).as_secs_f64(), @@ -104,43 +91,58 @@ impl StateTickMetrics { Duration::from_millis(50).as_secs_f64(), Duration::from_millis(100).as_secs_f64(), ]; - let state_tick_time_hist = HistogramVec::new( + let system_length_hist = HistogramVec::new( HistogramOpts::new( - "state_tick_time_hist", - "shows the number of clients joined to the server", + "system_length_hist", + "shows the detailed time in ns inside each ECS system as histogram", ) .buckets(bucket), &["system"], )?; - let state_tick_time_count = IntCounterVec::new( + let system_length_count = IntCounterVec::new( Opts::new( - "state_tick_time_count", - "shows the detailed time inside the `state_tick` for each system", + "system_length_count", + "shows the detailed time in ns inside each ECS system", + ), + &["system"], + )?; + let system_start_time = IntGaugeVec::new( + Opts::new( + "system_start_time", + "start relative to tick start in ns required per ECS system", + ), + &["system"], + )?; + let system_length_time = IntGaugeVec::new( + Opts::new("system_length_time", "time in ns required per ECS system"), + &["system"], + )?; + let system_thread_avg = GaugeVec::new( + Opts::new( + "system_thread_avg", + "average threads used by the ECS system", ), &["system"], )?; - let state_tick_time_hist_clone = state_tick_time_hist.clone(); - let state_tick_time_count_clone = state_tick_time_count.clone(); + registry.register(Box::new(system_length_hist.clone()))?; + registry.register(Box::new(system_length_count.clone()))?; + registry.register(Box::new(system_start_time.clone()))?; + registry.register(Box::new(system_length_time.clone()))?; + registry.register(Box::new(system_thread_avg.clone()))?; - let f = |registry: &Registry| { - registry.register(Box::new(state_tick_time_hist_clone))?; - registry.register(Box::new(state_tick_time_count_clone))?; - Ok(()) - }; - - Ok(( - Self { - state_tick_time_hist, - state_tick_time_count, - }, - Box::new(f), - )) + Ok(Self { + system_length_hist, + system_length_count, + system_start_time, + system_length_time, + system_thread_avg, + }) } } impl PlayerMetrics { - pub fn new() -> Result<(Self, RegistryFn), prometheus::Error> { + pub fn new(registry: &Registry) -> Result { let clients_connected = IntCounter::with_opts(Opts::new( "clients_connected", "shows the number of clients joined to the server", @@ -158,30 +160,20 @@ impl PlayerMetrics { &["reason"], )?; - let clients_connected_clone = clients_connected.clone(); - let players_connected_clone = players_connected.clone(); - let clients_disconnected_clone = clients_disconnected.clone(); + registry.register(Box::new(clients_connected.clone()))?; + registry.register(Box::new(players_connected.clone()))?; + registry.register(Box::new(clients_disconnected.clone()))?; - let f = |registry: &Registry| { - registry.register(Box::new(clients_connected_clone))?; - registry.register(Box::new(players_connected_clone))?; - registry.register(Box::new(clients_disconnected_clone))?; - Ok(()) - }; - - Ok(( - Self { - clients_connected, - players_connected, - clients_disconnected, - }, - Box::new(f), - )) + Ok(Self { + clients_connected, + players_connected, + clients_disconnected, + }) } } impl NetworkRequestMetrics { - pub fn new() -> Result<(Self, RegistryFn), prometheus::Error> { + pub fn new(registry: &Registry) -> Result { let chunks_request_dropped = IntCounter::with_opts(Opts::new( "chunks_request_dropped", "number of all chunk request dropped, e.g because the player was to far away", @@ -195,30 +187,20 @@ impl NetworkRequestMetrics { "number of all chunks that were requested and needs to be generated", ))?; - let chunks_request_dropped_clone = chunks_request_dropped.clone(); - let chunks_served_from_memory_clone = chunks_served_from_memory.clone(); - let chunks_generation_triggered_clone = chunks_generation_triggered.clone(); + registry.register(Box::new(chunks_request_dropped.clone()))?; + registry.register(Box::new(chunks_served_from_memory.clone()))?; + registry.register(Box::new(chunks_generation_triggered.clone()))?; - let f = |registry: &Registry| { - registry.register(Box::new(chunks_request_dropped_clone))?; - registry.register(Box::new(chunks_served_from_memory_clone))?; - registry.register(Box::new(chunks_generation_triggered_clone))?; - Ok(()) - }; - - Ok(( - Self { - chunks_request_dropped, - chunks_served_from_memory, - chunks_generation_triggered, - }, - Box::new(f), - )) + Ok(Self { + chunks_request_dropped, + chunks_served_from_memory, + chunks_generation_triggered, + }) } } impl ChunkGenMetrics { - pub fn new() -> Result<(Self, RegistryFn), prometheus::Error> { + pub fn new(registry: &Registry) -> Result { let chunks_requested = IntCounter::with_opts(Opts::new( "chunks_requested", "number of all chunks requested on the server", @@ -232,30 +214,20 @@ impl ChunkGenMetrics { "number of all canceled chunks on the server", ))?; - let chunks_requested_clone = chunks_requested.clone(); - let chunks_served_clone = chunks_served.clone(); - let chunks_canceled_clone = chunks_canceled.clone(); + registry.register(Box::new(chunks_requested.clone()))?; + registry.register(Box::new(chunks_served.clone()))?; + registry.register(Box::new(chunks_canceled.clone()))?; - let f = |registry: &Registry| { - registry.register(Box::new(chunks_requested_clone))?; - registry.register(Box::new(chunks_served_clone))?; - registry.register(Box::new(chunks_canceled_clone))?; - Ok(()) - }; - - Ok(( - Self { - chunks_requested, - chunks_served, - chunks_canceled, - }, - Box::new(f), - )) + Ok(Self { + chunks_requested, + chunks_served, + chunks_canceled, + }) } } impl TickMetrics { - pub fn new() -> Result<(Self, RegistryFn), Box> { + pub fn new(registry: &Registry) -> Result> { let chonks_count = IntGauge::with_opts(Opts::new( "chonks_count", "number of all chonks currently active on the server", @@ -296,48 +268,26 @@ impl TickMetrics { .expect("Time went backwards"); start_time.set(since_the_epoch.as_secs().try_into()?); - let chonks_count_clone = chonks_count.clone(); - let chunks_count_clone = chunks_count.clone(); - let chunk_groups_count_clone = chunk_groups_count.clone(); - let entity_count_clone = entity_count.clone(); - let build_info_clone = build_info.clone(); - let start_time_clone = start_time.clone(); - let time_of_day_clone = time_of_day.clone(); - let light_count_clone = light_count.clone(); - let tick_time_clone = tick_time.clone(); - let tick = Arc::new(AtomicU64::new(0)); + registry.register(Box::new(chonks_count.clone()))?; + registry.register(Box::new(chunks_count.clone()))?; + registry.register(Box::new(chunk_groups_count.clone()))?; + registry.register(Box::new(entity_count.clone()))?; + registry.register(Box::new(build_info.clone()))?; + registry.register(Box::new(start_time.clone()))?; + registry.register(Box::new(time_of_day.clone()))?; + registry.register(Box::new(light_count.clone()))?; + registry.register(Box::new(tick_time.clone()))?; - let f = |registry: &Registry| { - registry.register(Box::new(chonks_count_clone))?; - registry.register(Box::new(chunks_count_clone))?; - registry.register(Box::new(chunk_groups_count_clone))?; - registry.register(Box::new(entity_count_clone))?; - registry.register(Box::new(build_info_clone))?; - registry.register(Box::new(start_time_clone))?; - registry.register(Box::new(time_of_day_clone))?; - registry.register(Box::new(light_count_clone))?; - registry.register(Box::new(tick_time_clone))?; - Ok(()) - }; - - Ok(( - Self { - chonks_count, - chunks_count, - chunk_groups_count, - entity_count, - tick_time, - build_info, - start_time, - time_of_day, - light_count, - tick, - }, - Box::new(f), - )) + Ok(Self { + chonks_count, + chunks_count, + chunk_groups_count, + entity_count, + tick_time, + build_info, + start_time, + time_of_day, + light_count, + }) } - - pub fn tick(&self) { self.tick.fetch_add(1, Ordering::Relaxed); } - - pub fn is_100th_tick(&self) -> bool { self.tick.load(Ordering::Relaxed).rem_euclid(100) == 0 } } diff --git a/server/src/rtsim/load_chunks.rs b/server/src/rtsim/load_chunks.rs index 756b4c3f79..37ecb29dae 100644 --- a/server/src/rtsim/load_chunks.rs +++ b/server/src/rtsim/load_chunks.rs @@ -1,13 +1,21 @@ use super::*; -use common::event::{EventBus, ServerEvent}; -use specs::{Read, System, WriteExpect}; +use common::{ + event::{EventBus, ServerEvent}, + system::{Job, Origin, Phase, System}, +}; +use specs::{Read, WriteExpect}; +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = (Read<'a, EventBus>, WriteExpect<'a, RtSim>); - fn run(&mut self, (_server_event_bus, mut rtsim): Self::SystemData) { + const NAME: &'static str = "rtsim::load_chunks"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + + fn run(_job: &mut Job, (_server_event_bus, mut rtsim): Self::SystemData) { for _chunk in std::mem::take(&mut rtsim.chunks.chunks_to_load) { // TODO } diff --git a/server/src/rtsim/mod.rs b/server/src/rtsim/mod.rs index f2352c9168..dca14151c3 100644 --- a/server/src/rtsim/mod.rs +++ b/server/src/rtsim/mod.rs @@ -10,6 +10,7 @@ use self::{chunks::Chunks, entity::Entity}; use common::{ comp, rtsim::{RtSimController, RtSimEntity, RtSimId}, + system::{dispatch, System}, terrain::TerrainChunk, vol::RectRasterableVol, }; @@ -72,14 +73,13 @@ impl RtSim { } } -const LOAD_CHUNK_SYS: &str = "rtsim_load_chunk_sys"; -const UNLOAD_CHUNK_SYS: &str = "rtsim_unload_chunk_sys"; -const TICK_SYS: &str = "rtsim_tick_sys"; - pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { - dispatch_builder.add(unload_chunks::Sys, UNLOAD_CHUNK_SYS, &[]); - dispatch_builder.add(load_chunks::Sys, LOAD_CHUNK_SYS, &[UNLOAD_CHUNK_SYS]); - dispatch_builder.add(tick::Sys, TICK_SYS, &[LOAD_CHUNK_SYS, UNLOAD_CHUNK_SYS]); + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[&unload_chunks::Sys::sys_name()]); + dispatch::(dispatch_builder, &[ + &load_chunks::Sys::sys_name(), + &unload_chunks::Sys::sys_name(), + ]); } pub fn init(state: &mut State, #[cfg(feature = "worldgen")] world: &world::World) { diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 002c414f23..312c403661 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -6,13 +6,15 @@ use common::{ comp::inventory::loadout_builder::LoadoutBuilder, event::{EventBus, ServerEvent}, resources::DeltaTime, + system::{Job, Origin, Phase, System}, terrain::TerrainGrid, }; -use specs::{Join, Read, ReadExpect, ReadStorage, System, WriteExpect, WriteStorage}; +use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use std::sync::Arc; const ENTITY_TICK_PERIOD: u64 = 30; +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -28,8 +30,12 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, comp::Agent>, ); + const NAME: &'static str = "rtsim::tick"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( dt, server_event_bus, diff --git a/server/src/rtsim/unload_chunks.rs b/server/src/rtsim/unload_chunks.rs index 1de4eb144b..eb9c85f79f 100644 --- a/server/src/rtsim/unload_chunks.rs +++ b/server/src/rtsim/unload_chunks.rs @@ -2,10 +2,12 @@ use super::*; use common::{ comp::Pos, event::{EventBus, ServerEvent}, + system::{Job, Origin, Phase, System}, terrain::TerrainGrid, }; -use specs::{Entities, Read, ReadExpect, ReadStorage, System, WriteExpect}; +use specs::{Entities, Read, ReadExpect, ReadStorage, WriteExpect}; +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -18,8 +20,12 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Pos>, ); + const NAME: &'static str = "rtsim::unload_chunks"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( _server_event_bus, mut rtsim, diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index ebdac75da4..d3dcfb6364 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -1,4 +1,3 @@ -use super::SysTimer; use common::{ comp::{ self, @@ -16,10 +15,9 @@ use common::{ UnresolvedChatMsg, Vel, }, event::{Emitter, EventBus, ServerEvent}, - metrics::SysMetrics, path::TraversalConfig, resources::{DeltaTime, TimeOfDay}, - span, + system::{Job, Origin, Phase, System}, terrain::{Block, TerrainGrid}, time::DayPeriod, uid::{Uid, UidAllocator}, @@ -31,8 +29,8 @@ use rayon::iter::ParallelIterator; use specs::{ saveload::{Marker, MarkerAllocator}, shred::ResourceId, - Entities, Entity as EcsEntity, Join, ParJoin, Read, ReadExpect, ReadStorage, System, - SystemData, World, Write, WriteStorage, + Entities, Entity as EcsEntity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World, + Write, WriteStorage, }; use std::f32::consts::PI; use vek::*; @@ -64,7 +62,6 @@ pub struct ReadData<'a> { uid_allocator: Read<'a, UidAllocator>, dt: Read<'a, DeltaTime>, group_manager: Read<'a, group::GroupManager>, - sys_metrics: ReadExpect<'a, SysMetrics>, energies: ReadStorage<'a, Energy>, positions: ReadStorage<'a, Pos>, velocities: ReadStorage<'a, Vel>, @@ -99,26 +96,26 @@ const SNEAK_COEFFICIENT: f32 = 0.25; const AVG_FOLLOW_DIST: f32 = 6.0; /// This system will allow NPCs to modify their controller +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = ( ReadData<'a>, - Write<'a, SysTimer>, Write<'a, EventBus>, WriteStorage<'a, Agent>, WriteStorage<'a, Controller>, ); + const NAME: &'static str = "agent"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn run( - &mut self, - (read_data, mut sys_timer, event_bus, mut agents, mut controllers): Self::SystemData, + _job: &mut Job, + (read_data, event_bus, mut agents, mut controllers): Self::SystemData, ) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "agent::Sys::run"); - sys_timer.start(); - ( &read_data.entities, (&read_data.energies, &read_data.healths), @@ -423,13 +420,6 @@ impl<'a> System<'a> for Sys { debug_assert!(controller.inputs.look_dir.map(|e| !e.is_nan()).reduce_and()); }, ); - - read_data.sys_metrics.agent_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); - - sys_timer.end(); } } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 46315841d4..1684770436 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -1,7 +1,4 @@ -use super::{ - sentinel::{DeletedEntities, ReadTrackers, TrackedComps}, - SysTimer, -}; +use super::sentinel::{DeletedEntities, ReadTrackers, TrackedComps}; use crate::{ client::Client, presence::{Presence, RegionSubscription}, @@ -12,27 +9,27 @@ use common::{ outcome::Outcome, region::{Event as RegionEvent, RegionMap}, resources::TimeOfDay, - span, + system::{Job, Origin, Phase, System}, terrain::TerrainChunkSize, uid::Uid, vol::RectVolSize, }; use common_net::{msg::ServerGeneral, sync::CompSyncPackage}; use specs::{ - Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage, + Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage, }; use vek::*; /// This system will send physics updates to the client +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, Read<'a, Tick>, ReadExpect<'a, TimeOfDay>, ReadExpect<'a, RegionMap>, - Write<'a, SysTimer>, ReadStorage<'a, Uid>, ReadStorage<'a, Pos>, ReadStorage<'a, Vel>, @@ -52,14 +49,17 @@ impl<'a> System<'a> for Sys { ReadTrackers<'a>, ); + const NAME: &'static str = "entity_sync"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( entities, tick, time_of_day, region_map, - mut timer, uids, positions, velocities, @@ -79,9 +79,6 @@ impl<'a> System<'a> for Sys { trackers, ): Self::SystemData, ) { - span!(_guard, "run", "entity_sync::Sys::run"); - timer.start(); - let tick = tick.0; // To send entity updates // 1. Iterate through regions @@ -381,7 +378,5 @@ impl<'a> System<'a> for Sys { } tof_lazymsg.as_ref().map(|msg| client.send_prepared(&msg)); } - - timer.end(); } } diff --git a/server/src/sys/invite_timeout.rs b/server/src/sys/invite_timeout.rs index 1bc1979cb1..d08f9e848e 100644 --- a/server/src/sys/invite_timeout.rs +++ b/server/src/sys/invite_timeout.rs @@ -1,35 +1,34 @@ -use super::SysTimer; use crate::client::Client; use common::{ comp::invite::{Invite, PendingInvites}, - span, + system::{Job, Origin, Phase, System}, uid::Uid, }; use common_net::msg::{InviteAnswer, ServerGeneral}; -use specs::{Entities, Join, ReadStorage, System, Write, WriteStorage}; +use specs::{Entities, Join, ReadStorage, WriteStorage}; /// This system removes timed out invites +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, WriteStorage<'a, Invite>, WriteStorage<'a, PendingInvites>, ReadStorage<'a, Client>, ReadStorage<'a, Uid>, - Write<'a, SysTimer>, ); + const NAME: &'static str = "invite_timeout"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, - (entities, mut invites, mut pending_invites, clients, uids, mut timer): Self::SystemData, + _job: &mut Job, + (entities, mut invites, mut pending_invites, clients, uids): Self::SystemData, ) { - span!(_guard, "run", "invite_timeout::Sys::run"); - timer.start(); - let now = std::time::Instant::now(); - let timed_out_invites = (&entities, &invites) .join() .filter_map(|(invitee, Invite { inviter, kind })| { @@ -68,7 +67,5 @@ impl<'a> System<'a> for Sys { for entity in timed_out_invites { invites.remove(entity); } - - timer.end(); } } diff --git a/server/src/sys/metrics.rs b/server/src/sys/metrics.rs new file mode 100644 index 0000000000..b991622538 --- /dev/null +++ b/server/src/sys/metrics.rs @@ -0,0 +1,128 @@ +use crate::{ + metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics}, + Tick, TickStart, +}; +use common::{ + metrics::SysMetrics, + resources::TimeOfDay, + system::{Job, Origin, Phase, System}, + terrain::TerrainGrid, +}; +use specs::{Entities, Join, Read, ReadExpect}; +use std::time::Instant; + +/// This system exports metrics +#[derive(Default)] +pub struct Sys; +impl<'a> System<'a> for Sys { + #[allow(clippy::type_complexity)] + type SystemData = ( + Option>, + ReadExpect<'a, Tick>, + ReadExpect<'a, TimeOfDay>, + ReadExpect<'a, TickStart>, + Option>, + Read<'a, SysMetrics>, + Read<'a, common::metrics::PhysicsMetrics>, + ReadExpect<'a, EcsSystemMetrics>, + ReadExpect<'a, TickMetrics>, + ReadExpect<'a, PhysicsMetrics>, + ); + + const NAME: &'static str = "metrics"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Apply; + + fn run( + _job: &mut Job, + ( + entities, + tick, + time_of_day, + tick_start, + terrain, + sys_metrics, + phys_metrics, + export_ecs, + export_tick, + export_physics, + ): Self::SystemData, + ) { + const NANOSEC_PER_SEC: f64 = std::time::Duration::from_secs(1).as_nanos() as f64; + + let start = Instant::now(); + + let mut state = sys_metrics.stats.lock().unwrap(); + //this system hasn't run yet + state.remove(Self::NAME); + + for (name, stat) in common::system::gen_stats(&state, tick_start.0, 8, 8) { + export_ecs + .system_start_time + .with_label_values(&[&name]) + .set(stat.start_ns() as i64); + export_ecs + .system_thread_avg + .with_label_values(&[&name]) + .set(stat.avg_threads() as f64); + let len = stat.length_ns() as i64; + export_ecs + .system_length_time + .with_label_values(&[&name]) + .set(len); + export_ecs + .system_length_hist + .with_label_values(&[&name]) + .observe(len as f64 / NANOSEC_PER_SEC); + } + + // 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 { + let mut chonk_cnt = 0; + let mut group_cnt = 0; + let chunk_cnt = terrain.iter().fold(0, |a, (_, c)| { + chonk_cnt += 1; + group_cnt += c.sub_chunk_groups(); + a + c.sub_chunks_len() + }); + export_tick.chonks_count.set(chonk_cnt as i64); + export_tick.chunks_count.set(chunk_cnt as i64); + 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); + } + } + + //detailed physics metrics + export_physics + .entity_entity_collision_checks_count + .inc_by(phys_metrics.entity_entity_collision_checks); + export_physics + .entity_entity_collisions_count + .inc_by(phys_metrics.entity_entity_collisions); + + // export self time as best as possible + export_ecs + .system_start_time + .with_label_values(&["metrics"]) + .set(start.duration_since(tick_start.0).as_nanos() as i64); + export_ecs + .system_thread_avg + .with_label_values(&["metrics"]) + .set(1.0); + let len = start.elapsed().as_nanos() as i64; + export_ecs + .system_length_time + .with_label_values(&["metrics"]) + .set(len); + export_ecs + .system_length_hist + .with_label_values(&["metrics"]) + .observe(len as f64 / NANOSEC_PER_SEC); + } +} diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs index 649c52114b..0995c82859 100644 --- a/server/src/sys/mod.rs +++ b/server/src/sys/mod.rs @@ -1,6 +1,7 @@ pub mod agent; pub mod entity_sync; pub mod invite_timeout; +pub mod metrics; pub mod msg; pub mod object; pub mod persistence; @@ -10,59 +11,32 @@ pub mod terrain; pub mod terrain_sync; pub mod waypoint; +use common::system::{dispatch, run_now}; use specs::DispatcherBuilder; use std::{ marker::PhantomData, time::{Duration, Instant}, }; -pub type EntitySyncTimer = SysTimer; -pub type GeneralMsgTimer = SysTimer; -pub type PingMsgTimer = SysTimer; -pub type CharacterScreenMsgTimer = SysTimer; -pub type InGameMsgTimer = SysTimer; -pub type SentinelTimer = SysTimer; -pub type SubscriptionTimer = SysTimer; -pub type TerrainTimer = SysTimer; -pub type TerrainSyncTimer = SysTimer; -pub type WaypointTimer = SysTimer; -pub type InviteTimeoutTimer = SysTimer; -pub type PersistenceTimer = SysTimer; pub type PersistenceScheduler = SysScheduler; -pub type AgentTimer = SysTimer; - -// System names -// Note: commented names may be useful in the future -//const ENTITY_SYNC_SYS: &str = "server_entity_sync_sys"; -//const SENTINEL_SYS: &str = "sentinel_sys"; -//const SUBSCRIPTION_SYS: &str = "server_subscription_sys"; -//const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys"; -const TERRAIN_SYS: &str = "server_terrain_sys"; -const WAYPOINT_SYS: &str = "server_waypoint_sys"; -const INVITE_TIMEOUT_SYS: &str = "server_invite_timeout_sys"; -const PERSISTENCE_SYS: &str = "server_persistence_sys"; -const OBJECT_SYS: &str = "server_object_sys"; -pub const AGENT_SYS: &str = "agent_sys"; pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { - dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]); - dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]); - dispatch_builder.add(invite_timeout::Sys, INVITE_TIMEOUT_SYS, &[]); - dispatch_builder.add(persistence::Sys, PERSISTENCE_SYS, &[]); - dispatch_builder.add(object::Sys, OBJECT_SYS, &[]); + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[]); } pub fn run_sync_systems(ecs: &mut specs::World) { - use specs::RunNow; - // Setup for entity sync // If I'm not mistaken, these two could be ran in parallel - sentinel::Sys.run_now(ecs); - subscription::Sys.run_now(ecs); + run_now::(ecs); + run_now::(ecs); // Sync - terrain_sync::Sys.run_now(ecs); - entity_sync::Sys.run_now(ecs); + run_now::(ecs); + run_now::(ecs); } /// Used to schedule systems to run at an interval @@ -101,38 +75,3 @@ impl Default for SysScheduler { } } } - -/// Used to keep track of how much time each system takes -pub struct SysTimer { - pub nanos: u64, - start: Option, - _phantom: PhantomData, -} - -impl SysTimer { - pub fn start(&mut self) { - if self.start.is_some() { - panic!("Timer already started"); - } - self.start = Some(Instant::now()); - } - - pub fn end(&mut self) { - self.nanos = self - .start - .take() - .expect("Timer ended without starting it") - .elapsed() - .as_nanos() as u64; - } -} - -impl Default for SysTimer { - fn default() -> Self { - Self { - nanos: 0, - start: None, - _phantom: PhantomData, - } - } -} diff --git a/server/src/sys/msg/character_screen.rs b/server/src/sys/msg/character_screen.rs index b3dd3ca57a..6bd591ec74 100644 --- a/server/src/sys/msg/character_screen.rs +++ b/server/src/sys/msg/character_screen.rs @@ -1,4 +1,3 @@ -use super::super::SysTimer; use crate::{ alias_validator::AliasValidator, character_creator, client::Client, persistence::character_loader::CharacterLoader, presence::Presence, EditableSettings, @@ -6,11 +5,11 @@ use crate::{ use common::{ comp::{ChatType, Player, UnresolvedChatMsg}, event::{EventBus, ServerEvent}, - span, + system::{Job, Origin, Phase, System}, uid::Uid, }; use common_net::msg::{ClientGeneral, ServerGeneral}; -use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write}; +use specs::{Entities, Join, Read, ReadExpect, ReadStorage}; use std::sync::atomic::Ordering; use tracing::{debug, warn}; @@ -121,6 +120,7 @@ impl Sys { } /// This system will handle new messages from clients +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -128,7 +128,6 @@ impl<'a> System<'a> for Sys { Entities<'a>, Read<'a, EventBus>, ReadExpect<'a, CharacterLoader>, - Write<'a, SysTimer>, ReadStorage<'a, Uid>, ReadStorage<'a, Client>, ReadStorage<'a, Player>, @@ -137,13 +136,16 @@ impl<'a> System<'a> for Sys { ReadExpect<'a, AliasValidator>, ); + const NAME: &'static str = "msg::character_screen"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( entities, server_event_bus, character_loader, - mut timer, uids, clients, players, @@ -152,9 +154,6 @@ impl<'a> System<'a> for Sys { alias_validator, ): Self::SystemData, ) { - span!(_guard, "run", "msg::character_screen::Sys::run"); - timer.start(); - let mut server_emitter = server_event_bus.emitter(); let mut new_chat_msgs = Vec::new(); @@ -189,7 +188,5 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Chat(msg)); } } - - timer.end() } } diff --git a/server/src/sys/msg/general.rs b/server/src/sys/msg/general.rs index 1200a74d0e..7928218efd 100644 --- a/server/src/sys/msg/general.rs +++ b/server/src/sys/msg/general.rs @@ -1,16 +1,15 @@ -use super::super::SysTimer; use crate::{client::Client, metrics::PlayerMetrics}; use common::{ comp::{ChatMode, Player, UnresolvedChatMsg}, event::{EventBus, ServerEvent}, resources::Time, - span, + system::{Job, Origin, Phase, System}, uid::Uid, }; use common_net::msg::{ validate_chat_msg, ChatMsgValidationError, ClientGeneral, MAX_BYTES_CHAT_MSG, }; -use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write}; +use specs::{Entities, Join, Read, ReadExpect, ReadStorage}; use std::sync::atomic::Ordering; use tracing::{debug, error, warn}; @@ -65,6 +64,7 @@ impl Sys { } /// This system will handle new messages from clients +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -73,30 +73,29 @@ impl<'a> System<'a> for Sys { Read<'a, EventBus>, Read<'a, Time>, ReadExpect<'a, PlayerMetrics>, - Write<'a, SysTimer>, ReadStorage<'a, Uid>, ReadStorage<'a, ChatMode>, ReadStorage<'a, Player>, ReadStorage<'a, Client>, ); + const NAME: &'static str = "msg::general"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( entities, server_event_bus, time, player_metrics, - mut timer, uids, chat_modes, players, clients, ): Self::SystemData, ) { - span!(_guard, "run", "msg::general::Sys::run"); - timer.start(); - let mut server_emitter = server_event_bus.emitter(); let mut new_chat_msgs = Vec::new(); @@ -134,7 +133,5 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Chat(msg)); } } - - timer.end() } } diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index c8076c9bc6..af11dc1b20 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -1,15 +1,14 @@ -use super::super::SysTimer; use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence, Settings}; use common::{ comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Pos, Stats, Vel}, event::{EventBus, ServerEvent}, - span, + system::{Job, Origin, Phase, System}, terrain::{TerrainChunkSize, TerrainGrid}, vol::{ReadVol, RectVolSize}, }; use common_net::msg::{ClientGeneral, PresenceKind, ServerGeneral}; use common_sys::state::BlockChange; -use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage}; +use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage}; use tracing::{debug, trace}; impl Sys { @@ -165,6 +164,7 @@ impl Sys { } /// This system will handle new messages from clients +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -173,7 +173,6 @@ impl<'a> System<'a> for Sys { Read<'a, EventBus>, ReadExpect<'a, TerrainGrid>, ReadExpect<'a, NetworkRequestMetrics>, - Write<'a, SysTimer>, ReadStorage<'a, CanBuild>, ReadStorage<'a, ForceUpdate>, WriteStorage<'a, Stats>, @@ -188,14 +187,17 @@ impl<'a> System<'a> for Sys { Read<'a, Settings>, ); + const NAME: &'static str = "msg::in_game"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( entities, server_event_bus, terrain, network_metrics, - mut timer, can_build, force_updates, mut stats, @@ -210,9 +212,6 @@ impl<'a> System<'a> for Sys { settings, ): Self::SystemData, ) { - span!(_guard, "run", "msg::in_game::Sys::run"); - timer.start(); - let mut server_emitter = server_event_bus.emitter(); for (entity, client, mut maybe_presence) in @@ -240,7 +239,5 @@ impl<'a> System<'a> for Sys { ) }); } - - timer.end() } } diff --git a/server/src/sys/msg/ping.rs b/server/src/sys/msg/ping.rs index ea12514b1b..2ec8b4e907 100644 --- a/server/src/sys/msg/ping.rs +++ b/server/src/sys/msg/ping.rs @@ -1,12 +1,11 @@ -use super::super::SysTimer; use crate::{client::Client, metrics::PlayerMetrics, Settings}; use common::{ event::{EventBus, ServerEvent}, resources::Time, - span, + system::{Job, Origin, Phase, System}, }; use common_net::msg::PingMsg; -use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write}; +use specs::{Entities, Join, Read, ReadExpect, ReadStorage}; use std::sync::atomic::Ordering; use tracing::{debug, info}; @@ -21,6 +20,7 @@ impl Sys { } /// This system will handle new messages from clients +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -29,26 +29,18 @@ impl<'a> System<'a> for Sys { Read<'a, EventBus>, Read<'a, Time>, ReadExpect<'a, PlayerMetrics>, - Write<'a, SysTimer>, ReadStorage<'a, Client>, Read<'a, Settings>, ); - fn run( - &mut self, - ( - entities, - server_event_bus, - time, - player_metrics, - mut timer, - clients, - settings, - ): Self::SystemData, - ) { - span!(_guard, "run", "msg::ping::Sys::run"); - timer.start(); + const NAME: &'static str = "msg::ping"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( + _job: &mut Job, + (entities, server_event_bus, time, player_metrics, clients, settings): Self::SystemData, + ) { let mut server_emitter = server_event_bus.emitter(); for (entity, client) in (&entities, &clients).join() { @@ -89,7 +81,5 @@ impl<'a> System<'a> for Sys { }, } } - - timer.end() } } diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index 0810017d93..b24cc0224b 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -3,11 +3,13 @@ use common::{ effect::Effect, event::{EventBus, ServerEvent}, resources::DeltaTime, - span, Damage, DamageSource, Explosion, RadiusEffect, + system::{Job, Origin, Phase, System}, + Damage, DamageSource, Explosion, RadiusEffect, }; -use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadStorage, WriteStorage}; /// This system is responsible for handling misc object behaviours +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -21,11 +23,14 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Object>, ); + const NAME: &'static str = "object"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, (entities, _dt, server_bus, positions, velocities, physics_states, mut objects): Self::SystemData, ) { - span!(_guard, "run", "object::Sys::run"); let mut server_emitter = server_bus.emitter(); // Objects diff --git a/server/src/sys/persistence.rs b/server/src/sys/persistence.rs index 2aecd80f75..12b2c07695 100644 --- a/server/src/sys/persistence.rs +++ b/server/src/sys/persistence.rs @@ -1,19 +1,16 @@ -use crate::{ - persistence::character_updater, - presence::Presence, - sys::{SysScheduler, SysTimer}, -}; +use crate::{persistence::character_updater, presence::Presence, sys::SysScheduler}; use common::{ comp::{Inventory, Stats, Waypoint}, - span, + system::{Job, Origin, Phase, System}, }; use common_net::msg::PresenceKind; -use specs::{Join, ReadExpect, ReadStorage, System, Write}; +use specs::{Join, ReadExpect, ReadStorage, Write}; +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( ReadStorage<'a, Presence>, ReadStorage<'a, Stats>, @@ -21,11 +18,14 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Waypoint>, ReadExpect<'a, character_updater::CharacterUpdater>, Write<'a, SysScheduler>, - Write<'a, SysTimer>, ); + const NAME: &'static str = "persistence"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( presences, player_stats, @@ -33,12 +33,9 @@ impl<'a> System<'a> for Sys { player_waypoint, updater, mut scheduler, - mut timer, ): Self::SystemData, ) { - span!(_guard, "run", "persistence::Sys::run"); if scheduler.should_run() { - timer.start(); updater.batch_update( ( &presences, @@ -54,7 +51,6 @@ impl<'a> System<'a> for Sys { }, ), ); - timer.end(); } } } diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 517743f9e7..394f966fbb 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,11 +1,10 @@ -use super::SysTimer; use common::{ comp::{ Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Combo, Energy, Gravity, Group, Health, Inventory, Item, LightEmitter, Mass, MountState, Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, Stats, Sticky, Vel, }, - span, + system::{Job, Origin, Phase, System}, uid::Uid, }; use common_net::{ @@ -14,29 +13,25 @@ use common_net::{ }; use hashbrown::HashMap; use specs::{ - shred::ResourceId, Entity as EcsEntity, Join, ReadExpect, ReadStorage, System, SystemData, - World, Write, WriteExpect, + shred::ResourceId, Entity as EcsEntity, Join, ReadExpect, ReadStorage, SystemData, World, + WriteExpect, }; use vek::*; /// Always watching /// This system will monitor specific components for insertion, removal, and /// modification +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - type SystemData = ( - Write<'a, SysTimer>, - TrackedComps<'a>, - WriteTrackers<'a>, - ); + type SystemData = (TrackedComps<'a>, WriteTrackers<'a>); - fn run(&mut self, (mut timer, comps, mut trackers): Self::SystemData) { - span!(_guard, "run", "sentinel::Sys::run"); - timer.start(); + const NAME: &'static str = "sentinel"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run(_job: &mut Job, (comps, mut trackers): Self::SystemData) { record_changes(&comps, &mut trackers); - - timer.end(); } } diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index 66ac09ff48..51a242e1d8 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -1,7 +1,4 @@ -use super::{ - sentinel::{DeletedEntities, TrackedComps}, - SysTimer, -}; +use super::sentinel::{DeletedEntities, TrackedComps}; use crate::{ client::Client, presence::{self, Presence, RegionSubscription}, @@ -9,27 +6,26 @@ use crate::{ use common::{ comp::{Ori, Pos, Vel}, region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap}, - span, + system::{Job, Origin, Phase, System}, terrain::TerrainChunkSize, uid::Uid, vol::RectVolSize, }; use common_net::msg::ServerGeneral; use specs::{ - Entities, Join, ReadExpect, ReadStorage, System, SystemData, World, WorldExt, Write, - WriteStorage, + Entities, Join, ReadExpect, ReadStorage, SystemData, World, WorldExt, Write, WriteStorage, }; use tracing::{debug, error}; use vek::*; /// This system will update region subscriptions based on client positions +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, ReadExpect<'a, RegionMap>, - Write<'a, SysTimer>, ReadStorage<'a, Uid>, ReadStorage<'a, Pos>, ReadStorage<'a, Vel>, @@ -41,13 +37,16 @@ impl<'a> System<'a> for Sys { TrackedComps<'a>, ); + const NAME: &'static str = "subscription"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 fn run( - &mut self, + _job: &mut Job, ( entities, region_map, - mut timer, uids, positions, velocities, @@ -59,9 +58,6 @@ impl<'a> System<'a> for Sys { tracked_comps, ): Self::SystemData, ) { - span!(_guard, "run", "subscription::Sys::run"); - timer.start(); - // To update subscriptions // 1. Iterate through clients // 2. Calculate current chunk position @@ -209,8 +205,6 @@ impl<'a> System<'a> for Sys { } } } - - timer.end(); } } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 6c493baf7b..8f21a42324 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -1,4 +1,3 @@ -use super::SysTimer; use crate::{ chunk_generator::ChunkGenerator, client::Client, presence::Presence, rtsim::RtSim, Tick, }; @@ -7,13 +6,13 @@ use common::{ event::{EventBus, ServerEvent}, generation::get_npc_name, npc::NPC_NAMES, - span, + system::{Job, Origin, Phase, System}, terrain::TerrainGrid, LoadoutBuilder, SkillSetBuilder, }; use common_net::msg::ServerGeneral; use common_sys::state::TerrainChanges; -use specs::{Join, Read, ReadStorage, System, Write, WriteExpect}; +use specs::{Join, Read, ReadStorage, Write, WriteExpect}; use std::sync::Arc; use vek::*; @@ -23,13 +22,13 @@ use vek::*; /// 2. Sends new chunks to nearby clients /// 3. Handles the chunk's supplement (e.g. npcs) /// 4. Removes chunks outside the range of players +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] // TODO: Pending review in #587 type SystemData = ( Read<'a, EventBus>, Read<'a, Tick>, - Write<'a, SysTimer>, WriteExpect<'a, ChunkGenerator>, WriteExpect<'a, TerrainGrid>, Write<'a, TerrainChanges>, @@ -39,12 +38,15 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Client>, ); + const NAME: &'static str = "terrain"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( server_event_bus, tick, - mut timer, mut chunk_generator, mut terrain, mut terrain_changes, @@ -54,9 +56,6 @@ impl<'a> System<'a> for Sys { clients, ): Self::SystemData, ) { - span!(_guard, "run", "terrain::Sys::run"); - timer.start(); - let mut server_emitter = server_event_bus.emitter(); // Fetch any generated `TerrainChunk`s and insert them into the terrain. @@ -236,8 +235,6 @@ impl<'a> System<'a> for Sys { chunk_generator.cancel_if_pending(key); } - - timer.end() } } diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index ffbfcfab60..070bfa8880 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -1,31 +1,35 @@ -use super::SysTimer; use crate::{client::Client, presence::Presence}; -use common::{comp::Pos, span, terrain::TerrainGrid}; +use common::{ + comp::Pos, + system::{Job, Origin, Phase, System}, + terrain::TerrainGrid, +}; use common_net::msg::ServerGeneral; use common_sys::state::TerrainChanges; -use specs::{Join, Read, ReadExpect, ReadStorage, System, Write}; +use specs::{Join, Read, ReadExpect, ReadStorage}; /// This systems sends new chunks to clients as well as changes to existing /// chunks +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( ReadExpect<'a, TerrainGrid>, Read<'a, TerrainChanges>, - Write<'a, SysTimer>, ReadStorage<'a, Pos>, ReadStorage<'a, Presence>, ReadStorage<'a, Client>, ); - fn run( - &mut self, - (terrain, terrain_changes, mut timer, positions, presences, clients): Self::SystemData, - ) { - span!(_guard, "run", "terrain_sync::Sys::run"); - timer.start(); + const NAME: &'static str = "terrain_sync"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( + _job: &mut Job, + (terrain, terrain_changes, positions, presences, clients): Self::SystemData, + ) { // Sync changed chunks 'chunk: for chunk_key in &terrain_changes.modified_chunks { let mut lazy_msg = None; @@ -58,7 +62,5 @@ impl<'a> System<'a> for Sys { } lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg)); } - - timer.end(); } } diff --git a/server/src/sys/waypoint.rs b/server/src/sys/waypoint.rs index de81c557bf..7107fc79c5 100644 --- a/server/src/sys/waypoint.rs +++ b/server/src/sys/waypoint.rs @@ -1,21 +1,21 @@ -use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos, Waypoint, WaypointArea}, resources::Time, - span, + system::{Job, Origin, Phase, System}, }; use common_net::msg::{Notification, ServerGeneral}; -use specs::{Entities, Join, Read, ReadStorage, System, Write, WriteStorage}; +use specs::{Entities, Join, Read, ReadStorage, WriteStorage}; /// Cooldown time (in seconds) for "Waypoint Saved" notifications const NOTIFY_TIME: f64 = 10.0; /// This system updates player waypoints /// TODO: Make this faster by only considering local waypoints +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, ReadStorage<'a, Pos>, @@ -24,11 +24,14 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Waypoint>, ReadStorage<'a, Client>, Read<'a, Time>, - Write<'a, SysTimer>, ); + const NAME: &'static str = "waypoint"; + const ORIGIN: Origin = Origin::Server; + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, ( entities, positions, @@ -37,12 +40,8 @@ impl<'a> System<'a> for Sys { mut waypoints, clients, time, - mut timer, ): Self::SystemData, ) { - span!(_guard, "run", "waypoint::Sys::run"); - timer.start(); - for (entity, player_pos, _, client) in (&entities, &positions, &players, &clients).join() { for (waypoint_pos, waypoint_area) in (&positions, &waypoint_areas).join() { if player_pos.0.distance_squared(waypoint_pos.0) < waypoint_area.radius().powi(2) { @@ -57,7 +56,5 @@ impl<'a> System<'a> for Sys { } } } - - timer.end(); } } diff --git a/voxygen/src/ecs/sys.rs b/voxygen/src/ecs/sys.rs index a3b184effb..a9810a35b0 100644 --- a/voxygen/src/ecs/sys.rs +++ b/voxygen/src/ecs/sys.rs @@ -1,15 +1,10 @@ pub mod floater; mod interpolation; +use common::system::{dispatch, System}; use specs::DispatcherBuilder; -// System names -const FLOATER_SYS: &str = "floater_voxygen_sys"; -const INTERPOLATION_SYS: &str = "interpolation_voxygen_sys"; - pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { - dispatch_builder.add(interpolation::Sys, INTERPOLATION_SYS, &[ - common_sys::PHYS_SYS, - ]); - dispatch_builder.add(floater::Sys, FLOATER_SYS, &[INTERPOLATION_SYS]); + dispatch::(dispatch_builder, &[&common_sys::phys::Sys::sys_name()]); + dispatch::(dispatch_builder, &[&interpolation::Sys::sys_name()]); } diff --git a/voxygen/src/ecs/sys/floater.rs b/voxygen/src/ecs/sys/floater.rs index b19acf22e8..164ca568c4 100644 --- a/voxygen/src/ecs/sys/floater.rs +++ b/voxygen/src/ecs/sys/floater.rs @@ -5,18 +5,20 @@ use crate::ecs::{ use common::{ comp::{Health, HealthSource, Pos}, resources::DeltaTime, + system::{Job, Origin, Phase, System}, uid::Uid, }; -use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage}; // How long floaters last (in seconds) pub const HP_SHOWTIME: f32 = 3.0; pub const MY_HP_SHOWTIME: f32 = 2.5; pub const HP_ACCUMULATETIME: f32 = 1.0; +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, ReadExpect<'a, MyEntity>, @@ -27,9 +29,13 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, HpFloaterList>, ); + const NAME: &'static str = "floater"; + const ORIGIN: Origin = Origin::Frontend("voxygen"); + const PHASE: Phase = Phase::Create; + #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 fn run( - &mut self, + _job: &mut Job, (entities, my_entity, dt, uids, pos, healths, mut hp_floater_lists): Self::SystemData, ) { // Add hp floater lists to all entities with health and a position diff --git a/voxygen/src/ecs/sys/interpolation.rs b/voxygen/src/ecs/sys/interpolation.rs index 9a09f852ee..2b96957150 100644 --- a/voxygen/src/ecs/sys/interpolation.rs +++ b/voxygen/src/ecs/sys/interpolation.rs @@ -2,15 +2,17 @@ use crate::ecs::comp::Interpolated; use common::{ comp::{object, Body, Ori, Pos, Vel}, resources::DeltaTime, + system::{Job, Origin, Phase, System}, }; -use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadStorage, WriteStorage}; use tracing::warn; use vek::*; /// This system will allow NPCs to modify their controller +#[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, Read<'a, DeltaTime>, @@ -21,8 +23,12 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Interpolated>, ); + const NAME: &'static str = "interpolation"; + const ORIGIN: Origin = Origin::Frontend("voxygen"); + const PHASE: Phase = Phase::Create; + fn run( - &mut self, + _job: &mut Job, (entities, dt, positions, orientations, velocities, bodies, mut interpolated): Self::SystemData, ) { // Update interpolated positions and orientations