aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/openapi.yaml2
-rw-r--r--src/api/dashboard/clocks.rs1
-rw-r--r--src/api/dashboard/dashboard.html27
-rw-r--r--src/api/dashboard/mod.rs1
-rw-r--r--src/swarm/links.rs10
-rw-r--r--src/transport/mod.rs3
-rw-r--r--src/transport/wire.rs3
7 files changed, 35 insertions, 12 deletions
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index 2067aac..1f3a71a 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -547,7 +547,7 @@ paths:
`/admin/stats`), `peers` (as `/peers`) and `graph`: `members` (by node ID:
`alias`, `self`), `intended` (by node ID: the neighbours the member list gives
it, as this node computes them) and `measured` (by node ID: `secsAgo` and
- `links`, each with `peer`, `transport`, `relayed`, `rttMs`, `dormant` and
+ `links`, each with `peer`, `transport`, `relayed`, `down`, `rttMs`, `dormant` and
`others` (the peer's other transports: `transport`, `rttMs`, `relayed`), as the member
answered a LINKS; ours always, the others' while fresh). One producer
per node makes it every 2 s while anyone looks (and for a minute after this
diff --git a/src/api/dashboard/clocks.rs b/src/api/dashboard/clocks.rs
index 313b78e..9f069be 100644
--- a/src/api/dashboard/clocks.rs
+++ b/src/api/dashboard/clocks.rs
@@ -96,6 +96,7 @@ mod tests {
peer: peer.into(),
transport: None,
relayed: false,
+ down: false,
rtt_us: None,
dormant: false,
others: Vec::new(),
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index f45ded3..d2935f6 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -404,7 +404,7 @@ function renderLegend(measured) {
el("span", { class: "other" }, "other transport"),
el("span", { class: "direct" }, "direct"),
el("span", { class: "relayed" }, "relayed"),
- el("span", { class: "dormant" }, "dormant"),
+ el("span", { class: "dormant" }, "dormant or down"),
el("span", { class: "silent" }, "an end reports no links"));
document.getElementById("legend").replaceChildren(...items);
}
@@ -427,15 +427,19 @@ function edges() {
if (!known || (timed && !knownTimed) ||
(timed === knownTimed && report.secsAgo < known.secsAgo)) {
measured.set(key, { a, b: link.peer, link, secsAgo: report.secsAgo,
- dormant: link.dormant || (known && known.dormant) });
- } else if (link.dormant) {
- known.dormant = true;
+ dormant: link.dormant || (known && known.dormant),
+ down: link.down || (known && known.down) });
+ } else {
+ if (link.dormant) known.dormant = true;
+ if (link.down) known.down = true;
}
}
}
- // Dormant if either end holds the other so, whichever report is drawn.
+ // Dormant, or down, if either end says so, whichever report is drawn.
for (const edge of measured.values()) {
- if (edge.dormant && !edge.link.dormant) edge.link = { ...edge.link, dormant: true };
+ if ((edge.dormant && !edge.link.dormant) || (edge.down && !edge.link.down)) {
+ edge.link = { ...edge.link, dormant: !!edge.dormant, down: !!edge.down };
+ }
}
return { intended, measured };
}
@@ -615,8 +619,10 @@ function renderGraph() {
const nx = -(q.y - p.y) / length, ny = (q.x - p.x) / length;
// An end that reports no links (any more): what's drawn may be out of date.
const silent = !(a in state.graph.measured) || !(b in state.graph.measured);
- // Dormant: a red underlay as wide as the lines, as a dormant node's red outline.
- if (link.dormant) {
+ // Dormant, or down (no path answers, whatever its connection was): a red underlay
+ // as wide as the lines, as a dormant node's red outline.
+ const broken = link.dormant || link.down;
+ if (broken) {
children.push(drawing("underlay " + key, "line", { x1: p.x, y1: p.y, x2: q.x, y2: q.y,
stroke: "var(--bad)", "stroke-opacity": .5, "stroke-width": width + 4,
"stroke-linecap": "round" }));
@@ -627,8 +633,8 @@ function renderGraph() {
const ox = nx * side, oy = ny * side;
const attrs = { x1: p.x + ox, y1: p.y + oy, x2: q.x + ox, y2: q.y + oy,
stroke: colourOf(line.transport),
- // Narrow where dormant (over its underlay).
- "stroke-width": link.dormant ? (line.used ? 1.5 : 0.8) : line.used ? 3 : 1.2 };
+ // Narrow where dormant or down (over its underlay).
+ "stroke-width": broken ? (line.used ? 1.5 : 0.8) : line.used ? 3 : 1.2 };
// Dotted where an end reports nothing, dashed where relayed, solid where direct.
if (silent) {
attrs["stroke-dasharray"] = line.used ? "1 5" : "1 3";
@@ -640,6 +646,7 @@ function renderGraph() {
const description = name(a) + " – " + name(b) +
(line.transport ? " via " + line.transport : "") + (line.used ? " (in use)" : "") +
((line.used ? link.relayed : line.relayed) ? ", relayed" : "") + (link.dormant ? ", dormant" : "") +
+ (line.used && link.down ? ", down" : "") +
(silent ? ", one end reports no links" : "") +
(typeof line.rttMs === "number" ? ", " + line.rttMs.toFixed(1) + " ms" : "") +
", measured " + age(secsAgo + elapsed()) + " ago";
diff --git a/src/api/dashboard/mod.rs b/src/api/dashboard/mod.rs
index 310846a..0e7b442 100644
--- a/src/api/dashboard/mod.rs
+++ b/src/api/dashboard/mod.rs
@@ -331,6 +331,7 @@ fn links_json(links: &[Link]) -> JsonValue {
"peer": link.peer,
"transport": link.transport,
"relayed": link.relayed,
+ "down": link.down,
"rttMs": link.rtt_us.map(|us| f64::from(us) / 1000.0),
"dormant": link.dormant,
"clock": link.clock,
diff --git a/src/swarm/links.rs b/src/swarm/links.rs
index d5238f3..8502b39 100644
--- a/src/swarm/links.rs
+++ b/src/swarm/links.rs
@@ -122,6 +122,11 @@ pub(crate) async fn own_links(state: &AppState) -> Vec<Link> {
relayed: destination
.as_ref()
.is_some_and(|destination| destination.connection == Some(Connection::Relayed)),
+ // Ranked up before down: the first is down only if all are.
+ down: destination
+ .as_ref()
+ .and_then(|destination| destination.paths.first())
+ .is_some_and(|path| path.down),
// The path in use's (`Transport::rtt`, the first of `destination.paths`).
rtt_us: state
.transport
@@ -377,6 +382,7 @@ fn changed(old: &[Link], new: &[Link]) -> bool {
a.peer != b.peer
|| a.transport != b.transport
|| a.relayed != b.relayed
+ || a.down != b.down
|| a.dormant != b.dormant
|| moved(a.rtt_us, b.rtt_us)
|| clock_moved(a.clock, b.clock)
@@ -398,6 +404,7 @@ mod tests {
peer: peer.into(),
transport: Some("libp2p".into()),
relayed: false,
+ down: false,
rtt_us,
dormant: false,
others: Vec::new(),
@@ -416,6 +423,9 @@ mod tests {
let mut dormant = link("a", Some(10_000));
dormant.dormant = true;
assert!(changed(&old, &[dormant]));
+ let mut down = link("a", Some(10_000));
+ down.down = true;
+ assert!(changed(&old, &[down]));
// Small round trips: a millisecond's wobble isn't news.
assert!(!changed(&[link("a", Some(400))], &[link("a", Some(1_300))]));
// A clock: news when measured, and when it moved beyond both bounds.
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
index 2093e3d..705e473 100644
--- a/src/transport/mod.rs
+++ b/src/transport/mod.rs
@@ -435,6 +435,9 @@ pub(crate) struct Link {
pub(crate) transport: Option<String>,
/// Only through a relay (libp2p).
pub(crate) relayed: bool,
+ /// The path in use is down: none to the peer answers (`paths`), whatever its
+ /// connection was.
+ pub(crate) down: bool,
/// The round trip on that path, in microseconds, if measured.
pub(crate) rtt_us: Option<u32>,
/// Silent for long (`peers.md`, dormancy).
diff --git a/src/transport/wire.rs b/src/transport/wire.rs
index 26794a1..0ce8c33 100644
--- a/src/transport/wire.rs
+++ b/src/transport/wire.rs
@@ -14,7 +14,7 @@ use super::{EncodedMessage, MessageKind, ParsedMessage};
/// The wire format's version, the first byte of every payload. Bump it with every change
/// to a message type or a codec (`golden_bytes` fails then).
-pub(crate) const VERSION: u8 = 13;
+pub(crate) const VERSION: u8 = 14;
/// What the second byte of a payload says.
fn tag(kind: MessageKind) -> u8 {
@@ -437,6 +437,7 @@ pub(crate) fn samples() -> Vec<ParsedMessage> {
peer: "p".into(),
transport: Some("libp2p".into()),
relayed: true,
+ down: false,
rtt_us: Some(1500),
dormant: false,
others: vec![crate::transport::OtherPath {