From 27c870ed85deb5b5a1c60e0d1ed831d0b1108d6b Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 4 Nov 2026 21:49:15 +0100 Subject: benchmark members joining --- src/simtest/bench.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) (limited to 'src/simtest') diff --git a/src/simtest/bench.rs b/src/simtest/bench.rs index 09cd82c..9b18b2c 100644 --- a/src/simtest/bench.rs +++ b/src/simtest/bench.rs @@ -57,6 +57,12 @@ struct Scenario { /// 10 s, not long enough to be held dormant (four keepalives, 20 s), so what a node /// that is gone costs before the others notice. away: Option, + /// Nodes that join during each workload, one at a time, evenly spread (of + /// `nodes`: they start with `nodes` less twice as many), and so what members joining + /// cost while writes go on: member lists differ between nodes until the newcomer's + /// registration reached them, and their trees with them. Their catching up counts + /// in the messages, but not in the delays (`swarm::stats`: not until caught up). + joining: usize, } fn sim_only(_: usize) -> &'static [&'static str] { @@ -107,6 +113,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: None, + joining: 0, }, Scenario { name: "writer behind NAT (one link), 13 nodes", @@ -115,6 +122,7 @@ const SCENARIOS: &[Scenario] = &[ net: writer_behind_nat, writer: 1, away: None, + joining: 0, }, Scenario { name: "two transports, 3 bridges, 30 nodes", @@ -123,6 +131,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 5, away: None, + joining: 0, }, Scenario { name: "partial mesh, 50 nodes", @@ -131,6 +140,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: None, + joining: 0, }, Scenario { name: "partial mesh, lossy and slow links, 50 nodes", @@ -139,6 +149,7 @@ const SCENARIOS: &[Scenario] = &[ net: lossy, writer: 1, away: None, + joining: 0, }, Scenario { name: "partial mesh, 100 nodes", @@ -147,6 +158,7 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: None, + joining: 0, }, Scenario { name: "partial mesh, a member away for 10 s, 50 nodes", @@ -155,6 +167,16 @@ const SCENARIOS: &[Scenario] = &[ net: clean, writer: 1, away: Some(3), + joining: 0, + }, + Scenario { + name: "partial mesh, 5 members joining during each workload, 50 nodes", + nodes: 50, + on: sim_only, + net: clean, + writer: 1, + away: None, + joining: 5, }, ]; @@ -327,7 +349,9 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { config.neighbours_k = 12; }; let mut nodes = vec![sim.start_on_with(&ids[0], (scenario.on)(0), &[], Start::Genesis, timing)]; - for (i, id) in ids.iter().enumerate().skip(1) { + // Those there from the start; the others join during the workloads. + let initial = scenario.nodes - 2 * scenario.joining; + for (i, id) in ids.iter().enumerate().take(initial).skip(1) { // The writer behind NAT reaches node 2 alone. let seed = if i == 1 { ids[2].as_str() @@ -339,7 +363,7 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { let all: Vec<&AppState> = nodes.iter().collect(); let all = all.as_slice(); sim.wait_ready(all, SETTLE).await; - let count = scenario.nodes; + let count = initial; sim.wait_until( "every node knows every member", SETTLE, @@ -378,6 +402,7 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { WRITES, 1000 / WRITE_EVERY.as_millis() ); + let mut joined = initial; for (w, (workload, writer)) in [ ("one writer", Some(scenario.writer)), ("writers in turn", None), @@ -386,7 +411,22 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { .enumerate() { let start = Totals::of(&nodes).await; + // Before which writes the members joining in this workload start. + let joins: Vec = (1..=scenario.joining) + .map(|k| WRITES * k / (scenario.joining + 1)) + .collect(); for i in 0..WRITES { + if joins.contains(&i) { + let id = &ids[joined]; + nodes.push(sim.start_on_with( + id, + (scenario.on)(joined), + &[ids[0].as_str()], + Start::Join, + timing, + )); + joined += 1; + } // The middle half, the node that goes away is gone (writing nothing). if let Some(away) = scenario.away { if i == WRITES / 4 { @@ -396,20 +436,23 @@ async fn run(seed: u64, scenario: &'static Scenario) -> String { } } let gone = scenario.away.is_some_and(|away| { - (WRITES / 4..WRITES * 3 / 4).contains(&i) && away == i % nodes.len() + (WRITES / 4..WRITES * 3 / 4).contains(&i) && away == i % initial }); if writer.is_none() && gone { tokio::time::sleep(WRITE_EVERY).await; continue; } - let node = &nodes[writer.unwrap_or(i % nodes.len())]; + // In turn, those there from the start: one that just joined may not be + // ready yet. + let node = &nodes[writer.unwrap_or(i % initial)]; // A path of each workload's own: writing what is there already changes // nothing, and nothing would pass. write(node, &format!("bench/{w}/{i}"), json!(i)).await; tokio::time::sleep(WRITE_EVERY).await; } let written = Instant::now(); - sim.wait_converged(all, SETTLE).await; + let all: Vec<&AppState> = nodes.iter().collect(); + sim.wait_converged(&all, SETTLE).await; let converged = written.elapsed(); report( &mut out, -- cgit v1.3.1