aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-06 04:03:24 +0100
committertv <tv@krebsco.de>2026-11-06 04:03:24 +0100
commit186688be07c082fc8df1e16e0aefbc0b9807c3ff (patch)
treeb3991da422fcd2d6768fd22892facec2af450879 /src
parent927e630bf5dd17ae1435804cb0144db4df3c948b (diff)
tree views measured
Diffstat (limited to 'src')
-rw-r--r--src/simtest/bench.rs95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/simtest/bench.rs b/src/simtest/bench.rs
index 30a43c7..6729c7d 100644
--- a/src/simtest/bench.rs
+++ b/src/simtest/bench.rs
@@ -260,6 +260,16 @@ const SCENARIOS: &[Scenario] = &[
joining: 0,
reply_timeout: Some(Duration::from_secs(5)),
},
+ Scenario {
+ name: "writer behind NAT (one link), partial mesh, 50 nodes",
+ nodes: 50,
+ on: sim_only,
+ net: writer_behind_nat,
+ writer: 1,
+ away: None,
+ joining: 0,
+ reply_timeout: None,
+ },
];
#[tokio::test(flavor = "multi_thread", worker_threads = 16)]
@@ -618,6 +628,7 @@ async fn trees(out: &mut String, nodes: &[AppState], net: &crate::transport::sim
_ => f64::NAN,
};
let mut latencies = Vec::new();
+ let mut views = Views::default();
for node in nodes {
// Each member's parent in this node's tree, as the member chose it.
let mut parents = std::collections::HashMap::new();
@@ -627,8 +638,10 @@ async fn trees(out: &mut String, nodes: &[AppState], net: &crate::transport::sim
parents.insert(member.local_id().to_owned(), chosen);
}
}
+ views.add(nodes, node, &parents).await;
latencies.extend(crate::swarm::tree::own_latencies(node, &parents, &latency).await);
}
+ views.report(out);
let members: usize = latencies.iter().map(|l| l.members).sum();
if members == 0 {
return;
@@ -649,6 +662,88 @@ async fn trees(out: &mut String, nodes: &[AppState], net: &crate::transport::sim
);
}
+/// Whether the nodes' views of the writers' trees agree: each member's parent as the
+/// member chose it (`swarm::tree::own_parent`) against what that parent and the
+/// others plan (`swarm::tree::plan`: their children, the neighbours they count as
+/// covered). Summed over every node's tree.
+#[derive(Default)]
+struct Views {
+ /// Child links planned (a node sends to its children).
+ planned: usize,
+ /// Of those, the child's own choice of parent.
+ confirmed: usize,
+ /// Members whose chosen parent doesn't plan them as a child, so that nothing comes
+ /// along the tree: the parent counts them as covered (as having the changes from
+ /// another), or neither (it doesn't reach them, or ranks them before itself).
+ orphans_covered: usize,
+ orphans_neither: usize,
+ /// Members two nodes or more plan as a child (all but one send in vain).
+ doubled: usize,
+ /// Members (not writers) that chose no parent: announcements bring them all.
+ parentless: usize,
+}
+
+impl Views {
+ async fn add(
+ &mut self,
+ nodes: &[AppState],
+ writer: &AppState,
+ parents: &HashMap<String, Option<String>>,
+ ) {
+ let key = crate::swarm::topology::pair_key(writer.local_id());
+ let mut plans = HashMap::new();
+ for node in nodes {
+ if let Some(plan) = crate::swarm::tree::plan(node, key).await {
+ plans.insert(node.local_id().to_owned(), plan);
+ }
+ }
+ let mut planned_by: HashMap<&str, usize> = HashMap::new();
+ for (node, plan) in &plans {
+ for child in &plan.children {
+ self.planned += 1;
+ *planned_by.entry(child.as_str()).or_default() += 1;
+ if parents
+ .get(child)
+ .is_some_and(|parent| parent.as_ref() == Some(node))
+ {
+ self.confirmed += 1;
+ }
+ }
+ }
+ self.doubled += planned_by.values().filter(|count| **count > 1).count();
+ for (member, parent) in parents {
+ let Some(parent) = parent else {
+ self.parentless += 1;
+ continue;
+ };
+ let Some(plan) = plans.get(parent) else {
+ continue;
+ };
+ if plan.children.contains(member) {
+ continue;
+ }
+ if plan.covered.contains(member) {
+ self.orphans_covered += 1;
+ } else {
+ self.orphans_neither += 1;
+ }
+ }
+ }
+
+ fn report(&self, out: &mut String) {
+ let _ = writeln!(
+ out,
+ " views of the trees: {} of {} child links the child's own choice; orphans (the parent it chose doesn't send): {} counted covered, {} neither; {} planned as a child twice or more; {} members without a parent",
+ self.confirmed,
+ self.planned,
+ self.orphans_covered,
+ self.orphans_neither,
+ self.doubled,
+ self.parentless,
+ );
+ }
+}
+
/// The members `writer`'s changes take longest to reach along its tree, by the links'
/// latencies alone (the parents as each member chose them, `swarm::tree::own_place`),
/// those later than `LATE_MS`: how far each is from the writer, its parent and the