From 4da17a5e40e34aea27a34241b4f692ae0c827f57 Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 5 Nov 2026 00:56:39 +0100 Subject: neigbours by transport --- docs/measurements.md | 1 + docs/openapi.yaml | 7 ++++++- src/api/stats.rs | 3 +++ src/api/stats/prometheus.rs | 12 ++++++++++++ src/swarm/topology/mod.rs | 22 +++++++++++++++++++++- 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/docs/measurements.md b/docs/measurements.md index 81ca0fc..cff7add 100644 --- a/docs/measurements.md +++ b/docs/measurements.md @@ -98,6 +98,7 @@ or caught up over many small messages. | `topology.view.stable` | `stable_members` | Members whose registry entry says `stable`. | | `topology.view.neighbours` | `neighbours` | Current neighbours: peers this node keeps alive and syncs with on its own. At least `k` (links chosen by either end count); well above `k` on a stable member that many others chose, bounded at about 8 × k. | | `topology.view.stableNeighbours` | `stable_neighbours` | Of those, stable members. | +| `topology.view.neighboursByTransport` | `neighbours_on{transport}` | Of those, by this node's transports (address schemes): the ones the member list gives an address on it. One on two transports counts on both; one it gives none for (not listed yet, or only connecting out), on none. About `k` on each ([`peers.md`](peers.md)); few on one means that transport reaches the swarm through few members. | | `topology.view.ownTree.members` | `own_tree_members` | Other members in this node's own writer's tree: what its writes go along ([`peers.md`](peers.md)), as this node computes it (what members beyond its neighbours reach is taken from the member list's graph). | | `topology.view.ownTree.depth`, `.meanDepth` | `own_tree_depth`, `own_tree_mean_depth` | The most hops along it from this node to one of them, and the mean. | | `topology.view.ownTree.offTree` | `own_tree_off_members` | Members it doesn't reach: no member they reach comes before them, they are below one such, or the member list's graph doesn't connect them to this node. They get its writes by announcement. | diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 3d74746..db73ede 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -2069,7 +2069,7 @@ components: oneOf: - type: 'null' - type: object - required: [members, reachable, stable, neighbours, stableNeighbours, ownTree] + required: [members, reachable, stable, neighbours, stableNeighbours, neighboursByTransport, ownTree] properties: members: type: integer @@ -2083,6 +2083,11 @@ components: type: integer stableNeighbours: type: integer + neighboursByTransport: + type: object + additionalProperties: + type: integer + description: Neighbours the member list gives an address on each of this node's transports (address schemes); one on two counts on both. ownTree: description: This node's own writer's tree; null while it isn't in the member list's graph. oneOf: diff --git a/src/api/stats.rs b/src/api/stats.rs index 3851ef7..306b6a2 100644 --- a/src/api/stats.rs +++ b/src/api/stats.rs @@ -187,6 +187,8 @@ struct TopologyView { stable: usize, neighbours: usize, stable_neighbours: usize, + /// Neighbours by our transports (`topology::Summary`). + neighbours_by_transport: std::collections::BTreeMap, /// Our own writer's tree (`swarm::tree::own_shape`); absent while we aren't in the /// member list's graph. own_tree: Option, @@ -382,6 +384,7 @@ pub(crate) async fn collect(state: &AppState, list: &PeerListResponse) -> StatsR stable: s.stable, neighbours: s.neighbours, stable_neighbours: s.stable_neighbours, + neighbours_by_transport: s.neighbours_by_transport, own_tree, }), }, diff --git a/src/api/stats/prometheus.rs b/src/api/stats/prometheus.rs index db3cef3..ffe6593 100644 --- a/src/api/stats/prometheus.rs +++ b/src/api/stats/prometheus.rs @@ -713,6 +713,18 @@ pub(super) fn render(stats: &StatsResponse, list: &PeerListResponse) -> String { "Current neighbours with the stable tier.", view.stable_neighbours as f64, ); + out.family( + "neighbours_on", + "gauge", + "Current neighbours with an address on one of this node's transports, by transport.", + ); + for (transport, neighbours) in &view.neighbours_by_transport { + out.sample( + "neighbours_on", + &[("transport", transport)], + *neighbours as f64, + ); + } if let Some(tree) = &view.own_tree { out.single( "own_tree_members", diff --git a/src/swarm/topology/mod.rs b/src/swarm/topology/mod.rs index afa13ee..37b2f71 100644 --- a/src/swarm/topology/mod.rs +++ b/src/swarm/topology/mod.rs @@ -117,7 +117,7 @@ pub(crate) fn is_full_mesh(state: &AppState) -> bool { } /// The current view in numbers, once known. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub(crate) struct Summary { /// Registered members, us included. pub(crate) members: usize, @@ -126,6 +126,10 @@ pub(crate) struct Summary { pub(crate) stable: usize, pub(crate) neighbours: usize, pub(crate) stable_neighbours: usize, + /// For each of our transports (address scheme), the neighbours the member list + /// gives an address on it: one on two transports counts on both, one it gives none + /// for (not listed yet, or only connecting out) on none. + pub(crate) neighbours_by_transport: BTreeMap, } pub(crate) fn summary(state: &AppState) -> Option { @@ -148,6 +152,22 @@ pub(crate) fn summary(state: &AppState) -> Option { .iter() .filter(|m| view.stable.contains(*m)) .count(), + neighbours_by_transport: view + .local_transports + .iter() + .map(|transport| { + let on_it = view + .neighbours + .iter() + .filter(|m| { + view.transports + .get(*m) + .is_some_and(|theirs| theirs.contains(transport)) + }) + .count(); + (transport.clone(), on_it) + }) + .collect(), }) } -- cgit v1.3.1