Add performance tests (#113)

Adds the performance_tests/ directory.

In the future we could use our own fake client software instead of
rust-mc-bot. This would make it easier to run the tests.
This commit is contained in:
Ryan Johnson 2022-10-14 21:18:03 -07:00 committed by GitHub
parent a53738355f
commit f58e6662dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 246 additions and 4 deletions

4
.gitignore vendored
View file

@ -8,3 +8,7 @@ Cargo.lock
/extractor/out
/extractor/classes
/extractor/run
/performance_tests/rust-mc-bot
flamegraph.svg
perf.data
perf.data.old

View file

@ -68,7 +68,12 @@ rayon = "1.5.3"
num = "0.4.0"
[workspace]
members = ["valence_nbt", "packet_inspector"]
members = [
"valence_nbt",
"packet_inspector",
"performance_tests/players"
]
exclude = ["performance_tests/rust-mc-bot"]
[profile.dev.package."*"]
opt-level = 3

View file

@ -0,0 +1,34 @@
# Performance Tests
Run the server
```shell
cargo r -r -p players
```
In a separate terminal, start [rust-mc-bot](https://github.com/Eoghanmc22/rust-mc-bot).
This command should connect 1000 clients to the server.
```shell
cargo r -r -- 127.0.0.1:25565 1000
# If rust-mc-bot was cloned in the performance_tests directory, do
cargo r -r -p rust-mc-bot -- 127.0.0.1:25565 1000
```
If the delta time is consistently >50ms, the server is running behind schedule.
# Flamegraph
To start capturing a [flamegraph](https://github.com/flamegraph-rs/flamegraph),
run the server like this:
```shell
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -p players
```
Run rust-mc-bot as above, and then stop the server after a few seconds. Flamegraph will take its own sweet time to
generate a flamegraph.svg in the current directory. You can then open that file in your internet browser of choice.
NOTE: The indiscriminate use of `rayon` in Valence appears to have made the flamegraph basically unreadable. This
situation should change soon.

View file

@ -0,0 +1,9 @@
[package]
name = "players"
version = "0.1.0"
edition = "2021"
[dependencies]
env_logger = "0.9.1"
log = "0.4.17"
valence = { path = "../.." }

View file

@ -0,0 +1,190 @@
use std::net::SocketAddr;
use std::time::Instant;
use log::LevelFilter;
use valence::async_trait;
use valence::block::BlockState;
use valence::chunk::{Chunk, UnloadedChunk};
use valence::client::{handle_event_default, ClientEvent};
use valence::config::{Config, ServerListPing};
use valence::dimension::{Dimension, DimensionId};
use valence::entity::{EntityId, EntityKind};
use valence::player_list::PlayerListId;
use valence::server::{Server, SharedServer, ShutdownResult};
pub fn main() -> ShutdownResult {
env_logger::Builder::new()
.filter_module("valence", LevelFilter::Trace)
.parse_default_env()
.init();
valence::start_server(
Game,
ServerState {
player_list: None,
time: None,
millis_sum: 0.0,
},
)
}
const WITH_PLAYER_ENTITIES: bool = true;
const MAX_PLAYERS: usize = 10_000;
struct Game;
struct ServerState {
player_list: Option<PlayerListId>,
time: Option<Instant>,
millis_sum: f64,
}
#[async_trait]
impl Config for Game {
type ServerState = ServerState;
type ClientState = EntityId;
type EntityState = ();
type WorldState = ();
type ChunkState = ();
type PlayerListState = ();
fn max_connections(&self) -> usize {
MAX_PLAYERS + 64
}
fn online_mode(&self) -> bool {
false
}
fn outgoing_packet_capacity(&self) -> usize {
usize::MAX
}
fn dimensions(&self) -> Vec<Dimension> {
vec![Dimension {
natural: false,
ambient_light: 1.0,
fixed_time: None,
effects: Default::default(),
min_y: 0,
height: 256,
}]
}
async fn server_list_ping(
&self,
_shared: &SharedServer<Self>,
_remote_addr: SocketAddr,
_protocol_version: i32,
) -> ServerListPing {
ServerListPing::Respond {
online_players: -1,
max_players: -1,
player_sample: Default::default(),
description: "Test server!".into(),
favicon_png: None,
}
}
fn init(&self, server: &mut Server<Self>) {
server.state.player_list = Some(server.player_lists.insert(()).0);
let (_, world) = server.worlds.insert(DimensionId::default(), ());
let size = 5;
for chunk_z in -size..size {
for chunk_x in -size..size {
let mut chunk = UnloadedChunk::new(16);
for z in 0..16 {
for x in 0..16 {
chunk.set_block_state(x, 0, z, BlockState::GRASS_BLOCK);
}
}
world.chunks.insert([chunk_x, chunk_z], chunk, ());
}
}
}
fn update(&self, server: &mut Server<Self>) {
if let Some(time) = &mut server.state.time {
let millis = time.elapsed().as_secs_f64() * 1000.0;
let tick = server.shared.current_tick();
let players = server.clients.len();
let delay = 20;
server.state.millis_sum += millis;
if tick % delay == 0 {
let avg = server.state.millis_sum / delay as f64;
println!("Avg delta: {avg:.3}ms tick={tick} players={players}");
server.state.millis_sum = 0.0;
}
}
server.state.time = Some(Instant::now());
let (world_id, _) = server.worlds.iter_mut().next().unwrap();
server.clients.retain(|_, client| {
if client.created_this_tick() {
log::info!("Client \"{}\" joined", client.username());
client.spawn(world_id);
client.set_flat(true);
client.teleport([0.0, 1.0, 0.0], 0.0, 0.0);
if WITH_PLAYER_ENTITIES {
client.set_player_list(server.state.player_list.clone());
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).insert(
client.uuid(),
client.username(),
client.textures().cloned(),
client.game_mode(),
0,
None,
);
}
match server
.entities
.insert_with_uuid(EntityKind::Player, client.uuid(), ())
{
Some((id, _)) => client.state = id,
None => {
client.disconnect("Conflicting UUID");
return false;
}
}
}
}
if client.is_disconnected() {
log::info!("Client \"{}\" disconnected", client.username());
if WITH_PLAYER_ENTITIES {
if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state);
}
return false;
}
if WITH_PLAYER_ENTITIES {
if let Some(entity) = server.entities.get_mut(client.state) {
while handle_event_default(client, entity).is_some() {}
}
} else {
while let Some(event) = client.pop_event() {
if let ClientEvent::SettingsChanged { view_distance, .. } = event {
client.set_view_distance(view_distance);
}
}
}
true
});
}
}

View file

@ -870,7 +870,7 @@ impl<C: Config> Client<C> {
C2sPlayPacket::ClickContainer(p) => {
if p.slot_idx == -999 {
// client is trying to drop the currently held stack
let held = std::mem::replace(&mut self.cursor_held_item, None);
let held = mem::replace(&mut self.cursor_held_item, None);
match held {
None => {}
Some(stack) => self.events.push_back(ClientEvent::DropItemStack { stack }),

View file

@ -72,13 +72,13 @@ impl<C: Config> Entities<C> {
&mut self,
kind: EntityKind,
uuid: Uuid,
data: C::EntityState,
state: C::EntityState,
) -> Option<(EntityId, &mut Entity<C>)> {
match self.uuid_to_entity.entry(uuid) {
Entry::Occupied(_) => None,
Entry::Vacant(ve) => {
let (k, e) = self.slab.insert(Entity {
state: data,
state,
variants: TrackedData::new(kind),
events: Vec::new(),
bits: EntityBits::new(),