From 7c6716358503abee5fab960c45e5fc2de5f61203 Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:10 +0100 Subject: dashboard: the writers' trees as this node knows them The tree drawn is computed as every node computes its own part of it: from the member list, and whom it and its neighbours reach, as they told it; no longer from the links members report over LINKS. Auth plan M3 (preparing LINKS' removal). --- docs/dashboard.md | 8 ++++---- src/api/dashboard/mod.rs | 44 +++++--------------------------------------- src/swarm/tree.rs | 18 +++++++++++------- 3 files changed, 20 insertions(+), 50 deletions(-) diff --git a/docs/dashboard.md b/docs/dashboard.md index c063c78..80e9c19 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -168,10 +168,10 @@ static, the nodes staying where they are. The choice is kept in the browser. `tree: off`, next, switched to `tree: on`, shows a writer's tree ([`peers.md`](peers.md)): of the node in focus, else of this node. A chevron on the line from each member's parent to it, at its middle and in the colour of the line in use, points the way the writer's changes go; a member without one gets them by -announcement. The node computes the tree as every node does, from the member list and -whom each member reaches and its round trips, which it takes from the links members -report (a member that reports none counts as reaching its neighbours by the member list): asked for when the -writer in focus changes and every 10 s (`GET /admin/dashboard/tree`). The choice is +announcement. The node computes the tree as every node does its own part of it: from +the member list, and whom it and its neighbours reach and their round trips, as they +told it; other members count as reaching their neighbours by the member list. Asked for +when the writer in focus changes and every 10 s (`GET /admin/dashboard/tree`). The choice is kept in the browser. Hovering over a node or a link highlights it and shows a tooltip: for a link, each of diff --git a/src/api/dashboard/mod.rs b/src/api/dashboard/mod.rs index 7d940c9..e86475f 100644 --- a/src/api/dashboard/mod.rs +++ b/src/api/dashboard/mod.rs @@ -715,41 +715,16 @@ struct TreeQuery { writer: String, } -/// `GET /admin/dashboard/tree?writer=…`: the writer's tree (`swarm::tree`), as this node -/// computes it from the member list and the links members report (whom each reaches, -/// and how far: its round trip, as a bucket, `rtt::bucket`; one that reports none counts -/// as reaching its neighbours by the member list): every member's parent, `null` for -/// one without. `404` if the writer isn't a member. +/// `GET /admin/dashboard/tree?writer=…`: the writer's tree (`swarm::tree::whole`), as +/// this node computes it: from the member list, and whom it and its neighbours reach +/// and how far, as they said (others as reaching their neighbours by the member list): +/// every member's parent, `null` for one without. `404` if the writer isn't a member. async fn writer_tree( Extension(hub): Extension>, Query(query): Query, ) -> Response { let state = &hub.state; - let mut reports: BTreeMap> = state - .links - .replies() - .into_iter() - .map(|(member, _, links)| (member, links)) - .collect(); - reports.insert(state.local_id().to_owned(), links::own_links(state).await); - let reaches: BTreeMap<&str, BTreeSet> = reports - .iter() - .map(|(member, links)| (member.as_str(), reached_by(links))) - .collect(); - let reached = |by: &str, member: &str| { - reaches - .get(by) - .is_none_or(|reached| reached.contains(member)) - }; - let round_trip = |by: &str, member: &str| { - let rtt = reports - .get(by) - .and_then(|links| links.iter().find(|link| link.peer == member)) - .and_then(|link| link.rtt_us) - .map(|us| std::time::Duration::from_micros(u64::from(us))); - crate::swarm::rtt::bucket(rtt, None) - }; - match crate::swarm::tree::whole(state, &query.writer, &reached, &round_trip) { + match crate::swarm::tree::whole(state, &query.writer).await { Some(parents) => { axum::Json(json!({ "writer": query.writer, "parents": parents })).into_response() } @@ -757,15 +732,6 @@ async fn writer_tree( } } -/// Whom a member reaches, by its links: those neither dormant nor down. -fn reached_by(links: &[Link]) -> BTreeSet { - links - .iter() - .filter(|link| !link.dormant && !link.down) - .map(|link| link.peer.clone()) - .collect() -} - /// `GET /admin/dashboard/state`: the latest snapshot, for a client that wants it whole. async fn current_state(Extension(hub): Extension>) -> Response { hub.want(); diff --git a/src/swarm/tree.rs b/src/swarm/tree.rs index db3e920..4d5afd8 100644 --- a/src/swarm/tree.rs +++ b/src/swarm/tree.rs @@ -175,18 +175,22 @@ fn parent<'g>( }) } -/// `writer`'s whole tree, as far as `reached` knows whom each member reaches and -/// `round_trip` how far (the dashboard's map, from the links members report): every -/// member's parent, None for those without one (they get the writer's changes by -/// announcement). None while the member list isn't known, or the writer isn't in it. +/// `writer`'s whole tree, as this node knows it (the dashboard's map): whom it and its +/// neighbours reach and how far, as they said (`known_reaches`), the others by the +/// member list's graph, as every member computes its own part (`plan`): every member's +/// parent, None for those without one (they get the writer's changes by announcement). +/// None while the member list isn't known, or the writer isn't in it. #[cfg_attr(not(feature = "dashboard"), allow(dead_code))] -pub(crate) fn whole( +pub(crate) async fn whole( state: &AppState, writer: &str, - reached: &dyn Fn(&str, &str) -> bool, - round_trip: &dyn Fn(&str, &str) -> u8, ) -> Option>> { let (graph, distance) = distances(state, topology::pair_key(writer))?; + let known = known_reaches(state, &graph).await; + let reached = |by: &str, member: &str| known.reached(by, member); + let round_trip = |by: &str, member: &str| known.round_trip(by, member); + let (reached, round_trip): (&dyn Fn(&str, &str) -> bool, &dyn Fn(&str, &str) -> u8) = + (&reached, &round_trip); Some( graph .keys() -- cgit v1.3.1