diff --git a/src/rust/lqos_config/src/network_json/mod.rs b/src/rust/lqos_config/src/network_json/mod.rs index 5224e0a7..91490320 100644 --- a/src/rust/lqos_config/src/network_json/mod.rs +++ b/src/rust/lqos_config/src/network_json/mod.rs @@ -7,7 +7,7 @@ use std::{ path::{Path, PathBuf}, sync::atomic::AtomicU64, }; use thiserror::Error; -use lqos_utils::units::AtomicDownUp; +use lqos_utils::units::{AtomicDownUp, DownUpOrder}; /// Describes a node in the network map tree. #[derive(Debug)] @@ -20,6 +20,9 @@ pub struct NetworkJsonNode { /// Current throughput (in bytes/second) at this node pub current_throughput: AtomicDownUp, // In bytes + + /// Current TCP Retransmits + pub current_tcp_retransmits: AtomicDownUp, // In retries /// Approximate RTTs reported for this level of the tree. /// It's never going to be as statistically accurate as the actual @@ -48,6 +51,10 @@ impl NetworkJsonNode { self.current_throughput.get_down(), self.current_throughput.get_up(), ), + current_retransmits: ( + self.current_tcp_retransmits.get_down(), + self.current_tcp_retransmits.get_up(), + ), rtts: self.rtts.iter().map(|n| *n as f32 / 100.0).collect(), parents: self.parents.clone(), immediate_parent: self.immediate_parent, @@ -67,6 +74,8 @@ pub struct NetworkJsonTransport { pub max_throughput: (u32, u32), /// Current node throughput pub current_throughput: (u64, u64), + /// Current count of TCP retransmits + pub current_retransmits: (u64, u64), /// Set of RTT data pub rtts: Vec, /// Node indices of parents @@ -125,6 +134,7 @@ impl NetworkJson { name: "Root".to_string(), max_throughput: (0, 0), current_throughput: AtomicDownUp::zeroed(), + current_tcp_retransmits: AtomicDownUp::zeroed(), parents: Vec::new(), immediate_parent: None, rtts: DashSet::new(), @@ -201,6 +211,7 @@ impl NetworkJson { pub fn zero_throughput_and_rtt(&self) { self.nodes.iter().for_each(|n| { n.current_throughput.set_to_zero(); + n.current_tcp_retransmits.set_to_zero(); n.rtts.clear(); }); } @@ -235,6 +246,17 @@ impl NetworkJson { } } } + + pub fn add_retransmit_cycle(&self, targets: &[usize], tcp_retransmits: DownUpOrder) { + for idx in targets { + // Safety first; use "get" to ensure that the node exists + if let Some(node) = self.nodes.get(*idx) { + node.current_tcp_retransmits.checked_add(tcp_retransmits); + } else { + warn!("No network tree entry for index {idx}"); + } + } + } } fn json_to_u32(val: Option<&Value>) -> u32 { @@ -271,6 +293,7 @@ fn recurse_node( json_to_u32(json.get("uploadBandwidthMbps")), ), current_throughput: AtomicDownUp::zeroed(), + current_tcp_retransmits: AtomicDownUp::zeroed(), name: name.to_string(), immediate_parent: Some(immediate_parent), rtts: DashSet::new(), diff --git a/src/rust/lqos_utils/src/units/atomic_down_up.rs b/src/rust/lqos_utils/src/units/atomic_down_up.rs index 9f67242f..e7a37380 100644 --- a/src/rust/lqos_utils/src/units/atomic_down_up.rs +++ b/src/rust/lqos_utils/src/units/atomic_down_up.rs @@ -32,6 +32,18 @@ impl AtomicDownUp { self.up.store(n, std::sync::atomic::Ordering::Relaxed); } } + + pub fn checked_add(&self, n: DownUpOrder) { + let n0 = self.down.load(std::sync::atomic::Ordering::Relaxed); + if let Some(n) = n0.checked_add(n.down) { + self.down.store(n, std::sync::atomic::Ordering::Relaxed); + } + + let n1 = self.up.load(std::sync::atomic::Ordering::Relaxed); + if let Some(n) = n1.checked_add(n.up) { + self.up.store(n, std::sync::atomic::Ordering::Relaxed); + } + } pub fn get_down(&self) -> u64 { self.down.load(Relaxed) diff --git a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/top_tree_summary.js b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/top_tree_summary.js index 39179b03..922168d3 100644 --- a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/top_tree_summary.js +++ b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/top_tree_summary.js @@ -37,6 +37,7 @@ export class TopTreeSummary extends BaseDashlet { th.appendChild(theading("Branch")); th.appendChild(theading("DL ⬇️")); th.appendChild(theading("UL ⬆️")); + th.appendChild(theading("TCP Re-xmit")); t.appendChild(th); let tbody = document.createElement("tbody"); @@ -45,6 +46,7 @@ export class TopTreeSummary extends BaseDashlet { row.appendChild(simpleRow(r[1].name)); row.appendChild(simpleRow(scaleNumber(r[1].current_throughput[0] * 8))); row.appendChild(simpleRow(scaleNumber(r[1].current_throughput[1] * 8))); + row.appendChild(simpleRow(r[1].current_retransmits[0] + " / " + r[1].current_retransmits[1])) t.appendChild(row); }); diff --git a/src/rust/lqosd/src/shaped_devices_tracker/mod.rs b/src/rust/lqosd/src/shaped_devices_tracker/mod.rs index adb5b109..1927c100 100644 --- a/src/rust/lqosd/src/shaped_devices_tracker/mod.rs +++ b/src/rust/lqosd/src/shaped_devices_tracker/mod.rs @@ -83,9 +83,12 @@ pub fn get_top_n_root_queues(n_queues: usize) -> BusResponse { // Summarize everything after n_queues if nodes.len() > n_queues { let mut other_bw = (0, 0); + let mut other_xmit = (0, 0); nodes.drain(n_queues..).for_each(|n| { other_bw.0 += n.1.current_throughput.0; other_bw.1 += n.1.current_throughput.1; + other_xmit.0 += n.1.current_retransmits.0; + other_xmit.1 += n.1.current_retransmits.1; }); nodes.push(( @@ -94,6 +97,7 @@ pub fn get_top_n_root_queues(n_queues: usize) -> BusResponse { name: "Others".into(), max_throughput: (0, 0), current_throughput: other_bw, + current_retransmits: other_xmit, rtts: Vec::new(), parents: Vec::new(), immediate_parent: None, diff --git a/src/rust/lqosd/src/throughput_tracker/tracking_data.rs b/src/rust/lqosd/src/throughput_tracker/tracking_data.rs index 040c5016..650cbe15 100644 --- a/src/rust/lqosd/src/throughput_tracker/tracking_data.rs +++ b/src/rust/lqosd/src/throughput_tracker/tracking_data.rs @@ -297,6 +297,14 @@ impl ThroughputTracker { tracker.tcp_retransmits.up = retries.up.saturating_sub(tracker.prev_tcp_retransmits.up); tracker.prev_tcp_retransmits.down = retries.down; tracker.prev_tcp_retransmits.up = retries.up; + + // Send it upstream + if let Some(parents) = &tracker.network_json_parents { + let net_json = NETWORK_JSON.write().unwrap(); + // Send it upstream + println!("{:?}", tracker.tcp_retransmits); + net_json.add_retransmit_cycle(parents, tracker.tcp_retransmits); + } } }