mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Small refactor to break up the large network_json module, and adopt the newer module filename/directory system.
This commit is contained in:
parent
0af3ab3b72
commit
78b5cfeea7
295
src/rust/lqos_config/src/network_json.rs
Normal file
295
src/rust/lqos_config/src/network_json.rs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
mod network_json_node;
|
||||||
|
mod network_json_transport;
|
||||||
|
|
||||||
|
use dashmap::DashSet;
|
||||||
|
use log::{error, info, warn};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf}, sync::atomic::AtomicU64,
|
||||||
|
};
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
|
use thiserror::Error;
|
||||||
|
use lqos_utils::units::{AtomicDownUp, DownUpOrder};
|
||||||
|
pub use network_json_node::NetworkJsonNode;
|
||||||
|
pub use network_json_transport::NetworkJsonTransport;
|
||||||
|
|
||||||
|
/// Holder for the network.json representation.
|
||||||
|
/// This is condensed into a single level vector with index-based referencing
|
||||||
|
/// for easy use in funnel calculations.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NetworkJson {
|
||||||
|
/// Nodes that make up the tree, flattened and referenced by index number.
|
||||||
|
/// TODO: We should add a primary key to nodes in network.json.
|
||||||
|
///
|
||||||
|
/// Note that `nodes` is *private* now. This is intentional - direct
|
||||||
|
/// modification via this module is permitted, but external access was
|
||||||
|
/// running into timing issues and reading data mid-update. The locking
|
||||||
|
/// setup makes it hard to performantly lock the whole structure - so we
|
||||||
|
/// have a messy "busy" compromise.
|
||||||
|
nodes: Vec<NetworkJsonNode>,
|
||||||
|
busy: AtomicU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NetworkJson {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkJson {
|
||||||
|
/// Generates an empty network.json
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { nodes: Vec::new(), busy: AtomicU32::new(0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The path to the current `network.json` file, determined
|
||||||
|
/// by acquiring the prefix from the `/etc/lqos.conf` configuration
|
||||||
|
/// file.
|
||||||
|
pub fn path() -> Result<PathBuf, NetworkJsonError> {
|
||||||
|
let cfg =
|
||||||
|
crate::load_config().map_err(|_| NetworkJsonError::ConfigLoadError)?;
|
||||||
|
let base_path = Path::new(&cfg.lqos_directory);
|
||||||
|
Ok(base_path.join("network.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does network.json exist?
|
||||||
|
pub fn exists() -> bool {
|
||||||
|
if let Ok(path) = Self::path() {
|
||||||
|
path.exists()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to load network.json from disk
|
||||||
|
pub fn load() -> Result<Self, NetworkJsonError> {
|
||||||
|
let mut nodes = vec![NetworkJsonNode {
|
||||||
|
name: "Root".to_string(),
|
||||||
|
max_throughput: (0, 0),
|
||||||
|
current_throughput: AtomicDownUp::zeroed(),
|
||||||
|
current_tcp_retransmits: AtomicDownUp::zeroed(),
|
||||||
|
current_drops: AtomicDownUp::zeroed(),
|
||||||
|
current_marks: AtomicDownUp::zeroed(),
|
||||||
|
parents: Vec::new(),
|
||||||
|
immediate_parent: None,
|
||||||
|
rtts: DashSet::new(),
|
||||||
|
node_type: None,
|
||||||
|
}];
|
||||||
|
if !Self::exists() {
|
||||||
|
return Err(NetworkJsonError::FileNotFound);
|
||||||
|
}
|
||||||
|
let path = Self::path()?;
|
||||||
|
let raw = fs::read_to_string(path)
|
||||||
|
.map_err(|_| NetworkJsonError::ConfigLoadError)?;
|
||||||
|
let json: Value = serde_json::from_str(&raw)
|
||||||
|
.map_err(|_| NetworkJsonError::ConfigLoadError)?;
|
||||||
|
|
||||||
|
// Start reading from the top. We are at the root node.
|
||||||
|
let parents = vec![0];
|
||||||
|
if let Value::Object(map) = &json {
|
||||||
|
for (key, value) in map.iter() {
|
||||||
|
if let Value::Object(inner_map) = value {
|
||||||
|
recurse_node(&mut nodes, key, inner_map, &parents, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { nodes, busy: AtomicU32::new(0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the index of a circuit_id
|
||||||
|
pub fn get_index_for_name(&self, name: &str) -> Option<usize> {
|
||||||
|
self.nodes.iter().position(|n| n.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a cloned copy of a NetworkJsonNode entry, or None if there isn't
|
||||||
|
/// an entry at that index.
|
||||||
|
pub fn get_cloned_entry_by_index(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
) -> Option<NetworkJsonTransport> {
|
||||||
|
self.nodes.get(index).map(|n| n.clone_to_transit())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a cloned copy of all children with a parent containing a specific
|
||||||
|
/// node index.
|
||||||
|
pub fn get_cloned_children(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
) -> Vec<(usize, NetworkJsonTransport)> {
|
||||||
|
self
|
||||||
|
.nodes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_i, n)| n.immediate_parent == Some(index))
|
||||||
|
.map(|(i, n)| (i, n.clone_to_transit()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a circuit_id, and if it exists return its list of parent nodes
|
||||||
|
/// as indices within the network_json layout.
|
||||||
|
pub fn get_parents_for_circuit_id(
|
||||||
|
&self,
|
||||||
|
circuit_id: &str,
|
||||||
|
) -> Option<Vec<usize>> {
|
||||||
|
//println!("Looking for parents of {circuit_id}");
|
||||||
|
self
|
||||||
|
.nodes
|
||||||
|
.iter()
|
||||||
|
.find(|n| n.name == circuit_id)
|
||||||
|
.map(|node| node.parents.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtains a reference to nodes once we're sure that
|
||||||
|
/// doing so will provide valid data.
|
||||||
|
pub fn get_nodes_when_ready(&self) -> &Vec<NetworkJsonNode> {
|
||||||
|
//log::warn!("Awaiting the network tree");
|
||||||
|
//atomic_wait::wait(&self.busy, 1);
|
||||||
|
//log::warn!("Acquired");
|
||||||
|
&self.nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets all current throughput values to zero
|
||||||
|
/// Note that due to interior mutability, this does not require mutable
|
||||||
|
/// access.
|
||||||
|
pub fn zero_throughput_and_rtt(&self) {
|
||||||
|
//log::warn!("Locking network tree for throughput cycle");
|
||||||
|
self.busy.store(1, SeqCst);
|
||||||
|
self.nodes.iter().for_each(|n| {
|
||||||
|
n.current_throughput.set_to_zero();
|
||||||
|
n.current_tcp_retransmits.set_to_zero();
|
||||||
|
n.rtts.clear();
|
||||||
|
n.current_drops.set_to_zero();
|
||||||
|
n.current_marks.set_to_zero();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_complete(&self) {
|
||||||
|
//log::warn!("Unlocking network tree");
|
||||||
|
self.busy.store(0, SeqCst);
|
||||||
|
atomic_wait::wake_all(&self.busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add throughput numbers to node entries. Note that this does *not* require
|
||||||
|
/// mutable access due to atomics and interior mutability - so it is safe to use
|
||||||
|
/// a read lock.
|
||||||
|
pub fn add_throughput_cycle(
|
||||||
|
&self,
|
||||||
|
targets: &[usize],
|
||||||
|
bytes: (u64, u64),
|
||||||
|
) {
|
||||||
|
for idx in targets {
|
||||||
|
// Safety first: use "get" to ensure that the node exists
|
||||||
|
if let Some(node) = self.nodes.get(*idx) {
|
||||||
|
node.current_throughput.checked_add_tuple(bytes);
|
||||||
|
} else {
|
||||||
|
warn!("No network tree entry for index {idx}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record RTT time in the tree. Note that due to interior mutability,
|
||||||
|
/// this does not require mutable access.
|
||||||
|
pub fn add_rtt_cycle(&self, targets: &[usize], rtt: f32) {
|
||||||
|
for idx in targets {
|
||||||
|
// Safety first: use "get" to ensure that the node exists
|
||||||
|
if let Some(node) = self.nodes.get(*idx) {
|
||||||
|
node.rtts.insert((rtt * 100.0) as u16);
|
||||||
|
} else {
|
||||||
|
warn!("No network tree entry for index {idx}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_retransmit_cycle(&self, targets: &[usize], tcp_retransmits: DownUpOrder<u64>) {
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_queue_cycle(&self, targets: &[usize], marks: &DownUpOrder<u64>, drops: &DownUpOrder<u64>) {
|
||||||
|
for idx in targets {
|
||||||
|
// Safety first; use "get" to ensure that the node exists
|
||||||
|
if let Some(node) = self.nodes.get(*idx) {
|
||||||
|
node.current_marks.checked_add(*marks);
|
||||||
|
node.current_drops.checked_add(*drops);
|
||||||
|
} else {
|
||||||
|
warn!("No network tree entry for index {idx}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_to_u32(val: Option<&Value>) -> u32 {
|
||||||
|
if let Some(val) = val {
|
||||||
|
if let Some(n) = val.as_u64() {
|
||||||
|
n as u32
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recurse_node(
|
||||||
|
nodes: &mut Vec<NetworkJsonNode>,
|
||||||
|
name: &str,
|
||||||
|
json: &Map<String, Value>,
|
||||||
|
parents: &[usize],
|
||||||
|
immediate_parent: usize,
|
||||||
|
) {
|
||||||
|
info!("Mapping {name} from network.json");
|
||||||
|
let mut parents = parents.to_vec();
|
||||||
|
let my_id = if name != "children" {
|
||||||
|
parents.push(nodes.len());
|
||||||
|
nodes.len()
|
||||||
|
} else {
|
||||||
|
nodes.len() - 1
|
||||||
|
};
|
||||||
|
let node = NetworkJsonNode {
|
||||||
|
parents: parents.to_vec(),
|
||||||
|
max_throughput: (
|
||||||
|
json_to_u32(json.get("downloadBandwidthMbps")),
|
||||||
|
json_to_u32(json.get("uploadBandwidthMbps")),
|
||||||
|
),
|
||||||
|
current_throughput: AtomicDownUp::zeroed(),
|
||||||
|
current_tcp_retransmits: AtomicDownUp::zeroed(),
|
||||||
|
current_drops: AtomicDownUp::zeroed(),
|
||||||
|
current_marks: AtomicDownUp::zeroed(),
|
||||||
|
name: name.to_string(),
|
||||||
|
immediate_parent: Some(immediate_parent),
|
||||||
|
rtts: DashSet::new(),
|
||||||
|
node_type: json.get("type").map(|v| v.as_str().unwrap().to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if node.name != "children" {
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse children
|
||||||
|
for (key, value) in json.iter() {
|
||||||
|
let key_str = key.as_str();
|
||||||
|
if key_str != "uploadBandwidthMbps" && key_str != "downloadBandwidthMbps" {
|
||||||
|
if let Value::Object(value) = value {
|
||||||
|
recurse_node(nodes, key, value, &parents, my_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum NetworkJsonError {
|
||||||
|
#[error("Unable to find or load network.json")]
|
||||||
|
ConfigLoadError,
|
||||||
|
#[error("network.json not found or does not exist")]
|
||||||
|
FileNotFound,
|
||||||
|
}
|
@ -1,386 +0,0 @@
|
|||||||
use dashmap::DashSet;
|
|
||||||
use log::{error, info, warn};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::{Map, Value};
|
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf}, sync::atomic::AtomicU64,
|
|
||||||
};
|
|
||||||
use std::sync::atomic::AtomicU32;
|
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
|
||||||
use thiserror::Error;
|
|
||||||
use lqos_utils::units::{AtomicDownUp, DownUpOrder};
|
|
||||||
|
|
||||||
/// Describes a node in the network map tree.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NetworkJsonNode {
|
|
||||||
/// The node name, as it appears in `network.json`
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// The maximum throughput allowed per `network.json` for this node
|
|
||||||
pub max_throughput: (u32, u32), // In mbps
|
|
||||||
|
|
||||||
/// Current throughput (in bytes/second) at this node
|
|
||||||
pub current_throughput: AtomicDownUp, // In bytes
|
|
||||||
|
|
||||||
/// Current TCP Retransmits
|
|
||||||
pub current_tcp_retransmits: AtomicDownUp, // In retries
|
|
||||||
|
|
||||||
/// Current Cake Marks
|
|
||||||
pub current_marks: AtomicDownUp,
|
|
||||||
|
|
||||||
/// Current Cake Drops
|
|
||||||
pub current_drops: AtomicDownUp,
|
|
||||||
|
|
||||||
/// Approximate RTTs reported for this level of the tree.
|
|
||||||
/// It's never going to be as statistically accurate as the actual
|
|
||||||
/// numbers, being based on medians.
|
|
||||||
pub rtts: DashSet<u16>,
|
|
||||||
|
|
||||||
/// A list of indices in the `NetworkJson` vector of nodes
|
|
||||||
/// linking to parent nodes
|
|
||||||
pub parents: Vec<usize>,
|
|
||||||
|
|
||||||
/// The immediate parent node
|
|
||||||
pub immediate_parent: Option<usize>,
|
|
||||||
|
|
||||||
/// The node type
|
|
||||||
pub node_type: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkJsonNode {
|
|
||||||
/// Make a deep copy of a `NetworkJsonNode`, converting atomics
|
|
||||||
/// into concrete values.
|
|
||||||
pub fn clone_to_transit(&self) -> NetworkJsonTransport {
|
|
||||||
NetworkJsonTransport {
|
|
||||||
name: self.name.clone(),
|
|
||||||
max_throughput: self.max_throughput,
|
|
||||||
current_throughput: (
|
|
||||||
self.current_throughput.get_down(),
|
|
||||||
self.current_throughput.get_up(),
|
|
||||||
),
|
|
||||||
current_retransmits: (
|
|
||||||
self.current_tcp_retransmits.get_down(),
|
|
||||||
self.current_tcp_retransmits.get_up(),
|
|
||||||
),
|
|
||||||
current_marks: (
|
|
||||||
self.current_marks.get_down(),
|
|
||||||
self.current_marks.get_up(),
|
|
||||||
),
|
|
||||||
current_drops: (
|
|
||||||
self.current_drops.get_down(),
|
|
||||||
self.current_drops.get_up(),
|
|
||||||
),
|
|
||||||
rtts: self.rtts.iter().map(|n| *n as f32 / 100.0).collect(),
|
|
||||||
parents: self.parents.clone(),
|
|
||||||
immediate_parent: self.immediate_parent,
|
|
||||||
node_type: self.node_type.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A "transport-friendly" version of `NetworkJsonNode`. Designed
|
|
||||||
/// to be quickly cloned from original nodes and efficiently
|
|
||||||
/// transmitted/received.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct NetworkJsonTransport {
|
|
||||||
/// Display name
|
|
||||||
pub name: String,
|
|
||||||
/// Max throughput for node (not clamped)
|
|
||||||
pub max_throughput: (u32, u32),
|
|
||||||
/// Current node throughput
|
|
||||||
pub current_throughput: (u64, u64),
|
|
||||||
/// Current count of TCP retransmits
|
|
||||||
pub current_retransmits: (u64, u64),
|
|
||||||
/// Cake marks
|
|
||||||
pub current_marks: (u64, u64),
|
|
||||||
/// Cake drops
|
|
||||||
pub current_drops: (u64, u64),
|
|
||||||
/// Set of RTT data
|
|
||||||
pub rtts: Vec<f32>,
|
|
||||||
/// Node indices of parents
|
|
||||||
pub parents: Vec<usize>,
|
|
||||||
/// The immediate parent node in the tree
|
|
||||||
pub immediate_parent: Option<usize>,
|
|
||||||
/// The type of node (site, ap, etc.)
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub node_type: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Holder for the network.json representation.
|
|
||||||
/// This is condensed into a single level vector with index-based referencing
|
|
||||||
/// for easy use in funnel calculations.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NetworkJson {
|
|
||||||
/// Nodes that make up the tree, flattened and referenced by index number.
|
|
||||||
/// TODO: We should add a primary key to nodes in network.json.
|
|
||||||
///
|
|
||||||
/// Note that `nodes` is *private* now. This is intentional - direct
|
|
||||||
/// modification via this module is permitted, but external access was
|
|
||||||
/// running into timing issues and reading data mid-update. The locking
|
|
||||||
/// setup makes it hard to performantly lock the whole structure - so we
|
|
||||||
/// have a messy "busy" compromise.
|
|
||||||
nodes: Vec<NetworkJsonNode>,
|
|
||||||
busy: AtomicU32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for NetworkJson {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkJson {
|
|
||||||
/// Generates an empty network.json
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { nodes: Vec::new(), busy: AtomicU32::new(0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The path to the current `network.json` file, determined
|
|
||||||
/// by acquiring the prefix from the `/etc/lqos.conf` configuration
|
|
||||||
/// file.
|
|
||||||
pub fn path() -> Result<PathBuf, NetworkJsonError> {
|
|
||||||
let cfg =
|
|
||||||
crate::load_config().map_err(|_| NetworkJsonError::ConfigLoadError)?;
|
|
||||||
let base_path = Path::new(&cfg.lqos_directory);
|
|
||||||
Ok(base_path.join("network.json"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Does network.json exist?
|
|
||||||
pub fn exists() -> bool {
|
|
||||||
if let Ok(path) = Self::path() {
|
|
||||||
path.exists()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to load network.json from disk
|
|
||||||
pub fn load() -> Result<Self, NetworkJsonError> {
|
|
||||||
let mut nodes = vec![NetworkJsonNode {
|
|
||||||
name: "Root".to_string(),
|
|
||||||
max_throughput: (0, 0),
|
|
||||||
current_throughput: AtomicDownUp::zeroed(),
|
|
||||||
current_tcp_retransmits: AtomicDownUp::zeroed(),
|
|
||||||
current_drops: AtomicDownUp::zeroed(),
|
|
||||||
current_marks: AtomicDownUp::zeroed(),
|
|
||||||
parents: Vec::new(),
|
|
||||||
immediate_parent: None,
|
|
||||||
rtts: DashSet::new(),
|
|
||||||
node_type: None,
|
|
||||||
}];
|
|
||||||
if !Self::exists() {
|
|
||||||
return Err(NetworkJsonError::FileNotFound);
|
|
||||||
}
|
|
||||||
let path = Self::path()?;
|
|
||||||
let raw = fs::read_to_string(path)
|
|
||||||
.map_err(|_| NetworkJsonError::ConfigLoadError)?;
|
|
||||||
let json: Value = serde_json::from_str(&raw)
|
|
||||||
.map_err(|_| NetworkJsonError::ConfigLoadError)?;
|
|
||||||
|
|
||||||
// Start reading from the top. We are at the root node.
|
|
||||||
let parents = vec![0];
|
|
||||||
if let Value::Object(map) = &json {
|
|
||||||
for (key, value) in map.iter() {
|
|
||||||
if let Value::Object(inner_map) = value {
|
|
||||||
recurse_node(&mut nodes, key, inner_map, &parents, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { nodes, busy: AtomicU32::new(0) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the index of a circuit_id
|
|
||||||
pub fn get_index_for_name(&self, name: &str) -> Option<usize> {
|
|
||||||
self.nodes.iter().position(|n| n.name == name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a cloned copy of a NetworkJsonNode entry, or None if there isn't
|
|
||||||
/// an entry at that index.
|
|
||||||
pub fn get_cloned_entry_by_index(
|
|
||||||
&self,
|
|
||||||
index: usize,
|
|
||||||
) -> Option<NetworkJsonTransport> {
|
|
||||||
self.nodes.get(index).map(|n| n.clone_to_transit())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a cloned copy of all children with a parent containing a specific
|
|
||||||
/// node index.
|
|
||||||
pub fn get_cloned_children(
|
|
||||||
&self,
|
|
||||||
index: usize,
|
|
||||||
) -> Vec<(usize, NetworkJsonTransport)> {
|
|
||||||
self
|
|
||||||
.nodes
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_i, n)| n.immediate_parent == Some(index))
|
|
||||||
.map(|(i, n)| (i, n.clone_to_transit()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find a circuit_id, and if it exists return its list of parent nodes
|
|
||||||
/// as indices within the network_json layout.
|
|
||||||
pub fn get_parents_for_circuit_id(
|
|
||||||
&self,
|
|
||||||
circuit_id: &str,
|
|
||||||
) -> Option<Vec<usize>> {
|
|
||||||
//println!("Looking for parents of {circuit_id}");
|
|
||||||
self
|
|
||||||
.nodes
|
|
||||||
.iter()
|
|
||||||
.find(|n| n.name == circuit_id)
|
|
||||||
.map(|node| node.parents.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtains a reference to nodes once we're sure that
|
|
||||||
/// doing so will provide valid data.
|
|
||||||
pub fn get_nodes_when_ready(&self) -> &Vec<NetworkJsonNode> {
|
|
||||||
//log::warn!("Awaiting the network tree");
|
|
||||||
//atomic_wait::wait(&self.busy, 1);
|
|
||||||
//log::warn!("Acquired");
|
|
||||||
&self.nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets all current throughput values to zero
|
|
||||||
/// Note that due to interior mutability, this does not require mutable
|
|
||||||
/// access.
|
|
||||||
pub fn zero_throughput_and_rtt(&self) {
|
|
||||||
//log::warn!("Locking network tree for throughput cycle");
|
|
||||||
self.busy.store(1, SeqCst);
|
|
||||||
self.nodes.iter().for_each(|n| {
|
|
||||||
n.current_throughput.set_to_zero();
|
|
||||||
n.current_tcp_retransmits.set_to_zero();
|
|
||||||
n.rtts.clear();
|
|
||||||
n.current_drops.set_to_zero();
|
|
||||||
n.current_marks.set_to_zero();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle_complete(&self) {
|
|
||||||
//log::warn!("Unlocking network tree");
|
|
||||||
self.busy.store(0, SeqCst);
|
|
||||||
atomic_wait::wake_all(&self.busy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add throughput numbers to node entries. Note that this does *not* require
|
|
||||||
/// mutable access due to atomics and interior mutability - so it is safe to use
|
|
||||||
/// a read lock.
|
|
||||||
pub fn add_throughput_cycle(
|
|
||||||
&self,
|
|
||||||
targets: &[usize],
|
|
||||||
bytes: (u64, u64),
|
|
||||||
) {
|
|
||||||
for idx in targets {
|
|
||||||
// Safety first: use "get" to ensure that the node exists
|
|
||||||
if let Some(node) = self.nodes.get(*idx) {
|
|
||||||
node.current_throughput.checked_add_tuple(bytes);
|
|
||||||
} else {
|
|
||||||
warn!("No network tree entry for index {idx}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Record RTT time in the tree. Note that due to interior mutability,
|
|
||||||
/// this does not require mutable access.
|
|
||||||
pub fn add_rtt_cycle(&self, targets: &[usize], rtt: f32) {
|
|
||||||
for idx in targets {
|
|
||||||
// Safety first: use "get" to ensure that the node exists
|
|
||||||
if let Some(node) = self.nodes.get(*idx) {
|
|
||||||
node.rtts.insert((rtt * 100.0) as u16);
|
|
||||||
} else {
|
|
||||||
warn!("No network tree entry for index {idx}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_retransmit_cycle(&self, targets: &[usize], tcp_retransmits: DownUpOrder<u64>) {
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_queue_cycle(&self, targets: &[usize], marks: &DownUpOrder<u64>, drops: &DownUpOrder<u64>) {
|
|
||||||
for idx in targets {
|
|
||||||
// Safety first; use "get" to ensure that the node exists
|
|
||||||
if let Some(node) = self.nodes.get(*idx) {
|
|
||||||
node.current_marks.checked_add(*marks);
|
|
||||||
node.current_drops.checked_add(*drops);
|
|
||||||
} else {
|
|
||||||
warn!("No network tree entry for index {idx}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_to_u32(val: Option<&Value>) -> u32 {
|
|
||||||
if let Some(val) = val {
|
|
||||||
if let Some(n) = val.as_u64() {
|
|
||||||
n as u32
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recurse_node(
|
|
||||||
nodes: &mut Vec<NetworkJsonNode>,
|
|
||||||
name: &str,
|
|
||||||
json: &Map<String, Value>,
|
|
||||||
parents: &[usize],
|
|
||||||
immediate_parent: usize,
|
|
||||||
) {
|
|
||||||
info!("Mapping {name} from network.json");
|
|
||||||
let mut parents = parents.to_vec();
|
|
||||||
let my_id = if name != "children" {
|
|
||||||
parents.push(nodes.len());
|
|
||||||
nodes.len()
|
|
||||||
} else {
|
|
||||||
nodes.len() - 1
|
|
||||||
};
|
|
||||||
let node = NetworkJsonNode {
|
|
||||||
parents: parents.to_vec(),
|
|
||||||
max_throughput: (
|
|
||||||
json_to_u32(json.get("downloadBandwidthMbps")),
|
|
||||||
json_to_u32(json.get("uploadBandwidthMbps")),
|
|
||||||
),
|
|
||||||
current_throughput: AtomicDownUp::zeroed(),
|
|
||||||
current_tcp_retransmits: AtomicDownUp::zeroed(),
|
|
||||||
current_drops: AtomicDownUp::zeroed(),
|
|
||||||
current_marks: AtomicDownUp::zeroed(),
|
|
||||||
name: name.to_string(),
|
|
||||||
immediate_parent: Some(immediate_parent),
|
|
||||||
rtts: DashSet::new(),
|
|
||||||
node_type: json.get("type").map(|v| v.as_str().unwrap().to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if node.name != "children" {
|
|
||||||
nodes.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse children
|
|
||||||
for (key, value) in json.iter() {
|
|
||||||
let key_str = key.as_str();
|
|
||||||
if key_str != "uploadBandwidthMbps" && key_str != "downloadBandwidthMbps" {
|
|
||||||
if let Value::Object(value) = value {
|
|
||||||
recurse_node(nodes, key, value, &parents, my_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum NetworkJsonError {
|
|
||||||
#[error("Unable to find or load network.json")]
|
|
||||||
ConfigLoadError,
|
|
||||||
#[error("network.json not found or does not exist")]
|
|
||||||
FileNotFound,
|
|
||||||
}
|
|
71
src/rust/lqos_config/src/network_json/network_json_node.rs
Normal file
71
src/rust/lqos_config/src/network_json/network_json_node.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use dashmap::DashSet;
|
||||||
|
use lqos_utils::units::AtomicDownUp;
|
||||||
|
use crate::NetworkJsonTransport;
|
||||||
|
|
||||||
|
/// Describes a node in the network map tree.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NetworkJsonNode {
|
||||||
|
/// The node name, as it appears in `network.json`
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The maximum throughput allowed per `network.json` for this node
|
||||||
|
pub max_throughput: (u32, u32), // In mbps
|
||||||
|
|
||||||
|
/// Current throughput (in bytes/second) at this node
|
||||||
|
pub current_throughput: AtomicDownUp, // In bytes
|
||||||
|
|
||||||
|
/// Current TCP Retransmits
|
||||||
|
pub current_tcp_retransmits: AtomicDownUp, // In retries
|
||||||
|
|
||||||
|
/// Current Cake Marks
|
||||||
|
pub current_marks: AtomicDownUp,
|
||||||
|
|
||||||
|
/// Current Cake Drops
|
||||||
|
pub current_drops: AtomicDownUp,
|
||||||
|
|
||||||
|
/// Approximate RTTs reported for this level of the tree.
|
||||||
|
/// It's never going to be as statistically accurate as the actual
|
||||||
|
/// numbers, being based on medians.
|
||||||
|
pub rtts: DashSet<u16>,
|
||||||
|
|
||||||
|
/// A list of indices in the `NetworkJson` vector of nodes
|
||||||
|
/// linking to parent nodes
|
||||||
|
pub parents: Vec<usize>,
|
||||||
|
|
||||||
|
/// The immediate parent node
|
||||||
|
pub immediate_parent: Option<usize>,
|
||||||
|
|
||||||
|
/// The node type
|
||||||
|
pub node_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkJsonNode {
|
||||||
|
/// Make a deep copy of a `NetworkJsonNode`, converting atomics
|
||||||
|
/// into concrete values.
|
||||||
|
pub fn clone_to_transit(&self) -> NetworkJsonTransport {
|
||||||
|
NetworkJsonTransport {
|
||||||
|
name: self.name.clone(),
|
||||||
|
max_throughput: self.max_throughput,
|
||||||
|
current_throughput: (
|
||||||
|
self.current_throughput.get_down(),
|
||||||
|
self.current_throughput.get_up(),
|
||||||
|
),
|
||||||
|
current_retransmits: (
|
||||||
|
self.current_tcp_retransmits.get_down(),
|
||||||
|
self.current_tcp_retransmits.get_up(),
|
||||||
|
),
|
||||||
|
current_marks: (
|
||||||
|
self.current_marks.get_down(),
|
||||||
|
self.current_marks.get_up(),
|
||||||
|
),
|
||||||
|
current_drops: (
|
||||||
|
self.current_drops.get_down(),
|
||||||
|
self.current_drops.get_up(),
|
||||||
|
),
|
||||||
|
rtts: self.rtts.iter().map(|n| *n as f32 / 100.0).collect(),
|
||||||
|
parents: self.parents.clone(),
|
||||||
|
immediate_parent: self.immediate_parent,
|
||||||
|
node_type: self.node_type.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A "transport-friendly" version of `NetworkJsonNode`. Designed
|
||||||
|
/// to be quickly cloned from original nodes and efficiently
|
||||||
|
/// transmitted/received.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct NetworkJsonTransport {
|
||||||
|
/// Display name
|
||||||
|
pub name: String,
|
||||||
|
/// Max throughput for node (not clamped)
|
||||||
|
pub max_throughput: (u32, u32),
|
||||||
|
/// Current node throughput
|
||||||
|
pub current_throughput: (u64, u64),
|
||||||
|
/// Current count of TCP retransmits
|
||||||
|
pub current_retransmits: (u64, u64),
|
||||||
|
/// Cake marks
|
||||||
|
pub current_marks: (u64, u64),
|
||||||
|
/// Cake drops
|
||||||
|
pub current_drops: (u64, u64),
|
||||||
|
/// Set of RTT data
|
||||||
|
pub rtts: Vec<f32>,
|
||||||
|
/// Node indices of parents
|
||||||
|
pub parents: Vec<usize>,
|
||||||
|
/// The immediate parent node in the tree
|
||||||
|
pub immediate_parent: Option<usize>,
|
||||||
|
/// The type of node (site, ap, etc.)
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub node_type: Option<String>,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user