mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Merge pull request #279 from LibreQoE/the-funnel
Merge the funnel branch
This commit is contained in:
commit
4f9bc7e867
40
src/rust/Cargo.lock
generated
40
src/rust/Cargo.lock
generated
@ -587,9 +587,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "csv"
|
name = "csv"
|
||||||
version = "1.2.0"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af91f40b7355f82b0a891f50e70399475945bb0b0da4f1700ce60761c9d3e359"
|
checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv-core",
|
"csv-core",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -615,6 +615,19 @@ dependencies = [
|
|||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dashmap"
|
||||||
|
version = "5.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"hashbrown",
|
||||||
|
"lock_api",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "default-net"
|
name = "default-net"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@ -1116,9 +1129,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-lifetimes"
|
name = "io-lifetimes"
|
||||||
version = "1.0.5"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
@ -1330,6 +1343,7 @@ name = "lqos_config"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
|
"dashmap",
|
||||||
"ip_network",
|
"ip_network",
|
||||||
"ip_network_table",
|
"ip_network_table",
|
||||||
"log",
|
"log",
|
||||||
@ -1348,13 +1362,11 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"default-net",
|
"default-net",
|
||||||
"jemallocator",
|
"jemallocator",
|
||||||
"lazy_static",
|
|
||||||
"lqos_bus",
|
"lqos_bus",
|
||||||
"lqos_config",
|
"lqos_config",
|
||||||
"lqos_utils",
|
"lqos_utils",
|
||||||
"nix",
|
"nix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_async_compression",
|
"rocket_async_compression",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
@ -1378,15 +1390,14 @@ name = "lqos_queue_tracker"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"lazy_static",
|
"dashmap",
|
||||||
"log",
|
"log",
|
||||||
"log-once",
|
"log-once",
|
||||||
"lqos_bus",
|
"lqos_bus",
|
||||||
"lqos_config",
|
"lqos_config",
|
||||||
"lqos_sys",
|
"lqos_sys",
|
||||||
"lqos_utils",
|
"lqos_utils",
|
||||||
"parking_lot",
|
"once_cell",
|
||||||
"rayon",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@ -1435,6 +1446,7 @@ name = "lqosd"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"dashmap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"jemallocator",
|
"jemallocator",
|
||||||
"log",
|
"log",
|
||||||
@ -1445,8 +1457,6 @@ dependencies = [
|
|||||||
"lqos_utils",
|
"lqos_utils",
|
||||||
"nix",
|
"nix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
|
||||||
"rayon",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
@ -2200,18 +2210,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.152"
|
version = "1.0.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.152"
|
version = "1.0.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -112,11 +112,27 @@ pub enum BusRequest {
|
|||||||
ValidateShapedDevicesCsv,
|
ValidateShapedDevicesCsv,
|
||||||
|
|
||||||
/// Request details of part of the network tree
|
/// Request details of part of the network tree
|
||||||
GetNetworkMap{
|
GetNetworkMap {
|
||||||
/// The parent of the map to retrieve
|
/// The parent of the map to retrieve
|
||||||
parent: usize
|
parent: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Retrieves the top N queues from the root level, and summarizes
|
||||||
|
/// the others as "other"
|
||||||
|
TopMapQueues(usize),
|
||||||
|
|
||||||
|
/// Retrieve node names from network.json
|
||||||
|
GetNodeNamesFromIds(Vec<usize>),
|
||||||
|
|
||||||
|
/// Retrieve stats for all queues above a named circuit id
|
||||||
|
GetFunnel {
|
||||||
|
/// Circuit being analyzed, as the named circuit id
|
||||||
|
target: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Obtain the lqosd statistics
|
||||||
|
GetLqosStats,
|
||||||
|
|
||||||
/// If running on Equinix (the `equinix_test` feature is enabled),
|
/// If running on Equinix (the `equinix_test` feature is enabled),
|
||||||
/// display a "run bandwidht test" link.
|
/// display a "run bandwidht test" link.
|
||||||
#[cfg(feature = "equinix_tests")]
|
#[cfg(feature = "equinix_tests")]
|
||||||
|
@ -70,5 +70,16 @@ pub enum BusResponse {
|
|||||||
RawQueueData(String),
|
RawQueueData(String),
|
||||||
|
|
||||||
/// Results from network map queries
|
/// Results from network map queries
|
||||||
NetworkMap(Vec<(usize, lqos_config::NetworkJsonNode)>),
|
NetworkMap(Vec<(usize, lqos_config::NetworkJsonTransport)>),
|
||||||
|
|
||||||
|
/// Named nodes from network.json
|
||||||
|
NodeNames(Vec<(usize, String)>),
|
||||||
|
|
||||||
|
/// Statistics from lqosd
|
||||||
|
LqosdStats{
|
||||||
|
/// Number of bus requests handled
|
||||||
|
bus_requests: u64,
|
||||||
|
/// Us to poll hosts
|
||||||
|
time_to_poll_hosts: u64,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use thiserror::Error;
|
|||||||
|
|
||||||
/// Provides consistent handling of TC handle types.
|
/// Provides consistent handling of TC handle types.
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy, Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq,
|
Copy, Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq, Hash
|
||||||
)]
|
)]
|
||||||
pub struct TcHandle(u32);
|
pub struct TcHandle(u32);
|
||||||
|
|
||||||
|
@ -14,3 +14,4 @@ ip_network = "0"
|
|||||||
sha2 = "0"
|
sha2 = "0"
|
||||||
uuid = { version = "1", features = ["v4", "fast-rng" ] }
|
uuid = { version = "1", features = ["v4", "fast-rng" ] }
|
||||||
log = "0"
|
log = "0"
|
||||||
|
dashmap = "5"
|
||||||
|
@ -9,16 +9,16 @@
|
|||||||
mod authentication;
|
mod authentication;
|
||||||
mod etc;
|
mod etc;
|
||||||
mod libre_qos_config;
|
mod libre_qos_config;
|
||||||
|
mod network_json;
|
||||||
mod program_control;
|
mod program_control;
|
||||||
mod shaped_devices;
|
mod shaped_devices;
|
||||||
mod network_json;
|
|
||||||
|
|
||||||
pub use authentication::{UserRole, WebUsers};
|
pub use authentication::{UserRole, WebUsers};
|
||||||
pub use etc::{BridgeConfig, BridgeInterface, BridgeVlan, EtcLqos, Tunables};
|
pub use etc::{BridgeConfig, BridgeInterface, BridgeVlan, EtcLqos, Tunables};
|
||||||
pub use libre_qos_config::LibreQoSConfig;
|
pub use libre_qos_config::LibreQoSConfig;
|
||||||
|
pub use network_json::{NetworkJson, NetworkJsonNode, NetworkJsonTransport};
|
||||||
pub use program_control::load_libreqos;
|
pub use program_control::load_libreqos;
|
||||||
pub use shaped_devices::{ConfigShapedDevices, ShapedDevice};
|
pub use shaped_devices::{ConfigShapedDevices, ShapedDevice};
|
||||||
pub use network_json::{NetworkJson, NetworkJsonNode};
|
|
||||||
|
|
||||||
/// Used as a constant in determining buffer preallocation
|
/// Used as a constant in determining buffer preallocation
|
||||||
pub const SUPPORTED_CUSTOMERS: usize = 16_000_000;
|
pub const SUPPORTED_CUSTOMERS: usize = 16_000_000;
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
use crate::etc;
|
use crate::etc;
|
||||||
|
use dashmap::DashSet;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf}, sync::atomic::AtomicU64,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Describes a node in the network map tree.
|
/// Describes a node in the network map tree.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub struct NetworkJsonNode {
|
pub struct NetworkJsonNode {
|
||||||
/// The node name, as it appears in `network.json`
|
/// The node name, as it appears in `network.json`
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -18,12 +19,12 @@ pub struct NetworkJsonNode {
|
|||||||
pub max_throughput: (u32, u32), // In mbps
|
pub max_throughput: (u32, u32), // In mbps
|
||||||
|
|
||||||
/// Current throughput (in bytes/second) at this node
|
/// Current throughput (in bytes/second) at this node
|
||||||
pub current_throughput: (u64, u64), // In bytes
|
pub current_throughput: (AtomicU64, AtomicU64), // In bytes
|
||||||
|
|
||||||
/// Approximate RTTs reported for this level of the tree.
|
/// Approximate RTTs reported for this level of the tree.
|
||||||
/// It's never going to be as statistically accurate as the actual
|
/// It's never going to be as statistically accurate as the actual
|
||||||
/// numbers, being based on medians.
|
/// numbers, being based on medians.
|
||||||
pub rtts: Vec<f32>,
|
pub rtts: DashSet<u16>,
|
||||||
|
|
||||||
/// A list of indices in the `NetworkJson` vector of nodes
|
/// A list of indices in the `NetworkJson` vector of nodes
|
||||||
/// linking to parent nodes
|
/// linking to parent nodes
|
||||||
@ -33,12 +34,51 @@ pub struct NetworkJsonNode {
|
|||||||
pub immediate_parent: Option<usize>,
|
pub immediate_parent: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.0.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
self.current_throughput.1.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
),
|
||||||
|
rtts: self.rtts.iter().map(|n| *n as f32 / 100.0).collect(),
|
||||||
|
parents: self.parents.clone(),
|
||||||
|
immediate_parent: self.immediate_parent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
/// 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>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Holder for the network.json representation.
|
/// Holder for the network.json representation.
|
||||||
/// This is condensed into a single level vector with index-based referencing
|
/// This is condensed into a single level vector with index-based referencing
|
||||||
/// for easy use in funnel calculations.
|
/// for easy use in funnel calculations.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub struct NetworkJson {
|
pub struct NetworkJson {
|
||||||
nodes: Vec<NetworkJsonNode>,
|
/// Nodes that make up the tree, flattened and referenced by index number.
|
||||||
|
/// TODO: We should add a primary key to nodes in network.json.
|
||||||
|
pub nodes: Vec<NetworkJsonNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NetworkJson {
|
impl Default for NetworkJson {
|
||||||
@ -77,10 +117,10 @@ impl NetworkJson {
|
|||||||
let mut nodes = vec![NetworkJsonNode {
|
let mut nodes = vec![NetworkJsonNode {
|
||||||
name: "Root".to_string(),
|
name: "Root".to_string(),
|
||||||
max_throughput: (0, 0),
|
max_throughput: (0, 0),
|
||||||
current_throughput: (0, 0),
|
current_throughput: (AtomicU64::new(0), AtomicU64::new(0)),
|
||||||
parents: Vec::new(),
|
parents: Vec::new(),
|
||||||
immediate_parent: None,
|
immediate_parent: None,
|
||||||
rtts: Vec::new(),
|
rtts: DashSet::new(),
|
||||||
}];
|
}];
|
||||||
if !Self::exists() {
|
if !Self::exists() {
|
||||||
return Err(NetworkJsonError::FileNotFound);
|
return Err(NetworkJsonError::FileNotFound);
|
||||||
@ -114,19 +154,22 @@ impl NetworkJson {
|
|||||||
pub fn get_cloned_entry_by_index(
|
pub fn get_cloned_entry_by_index(
|
||||||
&self,
|
&self,
|
||||||
index: usize,
|
index: usize,
|
||||||
) -> Option<NetworkJsonNode> {
|
) -> Option<NetworkJsonTransport> {
|
||||||
self.nodes.get(index).cloned()
|
self.nodes.get(index).map(|n| n.clone_to_transit())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a cloned copy of all children with a parent containing a specific
|
/// Retrieve a cloned copy of all children with a parent containing a specific
|
||||||
/// node index.
|
/// node index.
|
||||||
pub fn get_cloned_children(&self, index: usize) -> Vec<(usize, NetworkJsonNode)> {
|
pub fn get_cloned_children(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
) -> Vec<(usize, NetworkJsonTransport)> {
|
||||||
self
|
self
|
||||||
.nodes
|
.nodes
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_i,n)| n.immediate_parent == Some(index))
|
.filter(|(_i, n)| n.immediate_parent == Some(index))
|
||||||
.map(|(i, n)| (i, n.clone()))
|
.map(|(i, n)| (i, n.clone_to_transit()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,28 +187,42 @@ impl NetworkJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets all current throughput values to zero
|
/// Sets all current throughput values to zero
|
||||||
pub fn zero_throughput_and_rtt(&mut self) {
|
/// Note that due to interior mutability, this does not require mutable
|
||||||
self.nodes.iter_mut().for_each(|n| {
|
/// access.
|
||||||
n.current_throughput = (0, 0);
|
pub fn zero_throughput_and_rtt(&self) {
|
||||||
|
self.nodes.iter().for_each(|n| {
|
||||||
|
n.current_throughput.0.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
n.current_throughput.1.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||||
n.rtts.clear();
|
n.rtts.clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add throughput numbers to node entries
|
/// 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(
|
pub fn add_throughput_cycle(
|
||||||
&mut self,
|
&self,
|
||||||
targets: &[usize],
|
targets: &[usize],
|
||||||
bytes: (u64, u64),
|
bytes: (u64, u64),
|
||||||
median_rtt: f32,
|
|
||||||
) {
|
) {
|
||||||
for idx in targets {
|
for idx in targets {
|
||||||
// Safety first: use "get" to ensure that the node exists
|
// Safety first: use "get" to ensure that the node exists
|
||||||
if let Some(node) = self.nodes.get_mut(*idx) {
|
if let Some(node) = self.nodes.get(*idx) {
|
||||||
node.current_throughput.0 += bytes.0;
|
node.current_throughput.0.fetch_add(bytes.0, std::sync::atomic::Ordering::Relaxed);
|
||||||
node.current_throughput.1 += bytes.1;
|
node.current_throughput.1.fetch_add(bytes.1, std::sync::atomic::Ordering::Relaxed);
|
||||||
if median_rtt > 0.0 {
|
} else {
|
||||||
node.rtts.push(median_rtt);
|
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 {
|
} else {
|
||||||
warn!("No network tree entry for index {idx}");
|
warn!("No network tree entry for index {idx}");
|
||||||
}
|
}
|
||||||
@ -193,22 +250,28 @@ fn recurse_node(
|
|||||||
immediate_parent: usize,
|
immediate_parent: usize,
|
||||||
) {
|
) {
|
||||||
info!("Mapping {name} from network.json");
|
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 {
|
let node = NetworkJsonNode {
|
||||||
parents: parents.to_vec(),
|
parents: parents.to_vec(),
|
||||||
max_throughput: (
|
max_throughput: (
|
||||||
json_to_u32(json.get("downloadBandwidthMbps")),
|
json_to_u32(json.get("downloadBandwidthMbps")),
|
||||||
json_to_u32(json.get("uploadBandwidthMbps")),
|
json_to_u32(json.get("uploadBandwidthMbps")),
|
||||||
),
|
),
|
||||||
current_throughput: (0, 0),
|
current_throughput: (AtomicU64::new(0), AtomicU64::new(0)),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
immediate_parent: Some(immediate_parent),
|
immediate_parent: Some(immediate_parent),
|
||||||
rtts: Vec::new(),
|
rtts: DashSet::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let my_id = nodes.len();
|
if node.name != "children" {
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
let mut parents = parents.to_vec();
|
}
|
||||||
parents.push(my_id);
|
|
||||||
|
|
||||||
// Recurse children
|
// Recurse children
|
||||||
for (key, value) in json.iter() {
|
for (key, value) in json.iter() {
|
||||||
|
@ -10,8 +10,6 @@ equinix_tests = []
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { version = "0.5.0-rc.2", features = [ "json", "msgpack", "uuid" ] }
|
rocket = { version = "0.5.0-rc.2", features = [ "json", "msgpack", "uuid" ] }
|
||||||
rocket_async_compression = "0.2.0"
|
rocket_async_compression = "0.2.0"
|
||||||
lazy_static = "1.4"
|
|
||||||
parking_lot = "0.12"
|
|
||||||
lqos_bus = { path = "../lqos_bus" }
|
lqos_bus = { path = "../lqos_bus" }
|
||||||
lqos_config = { path = "../lqos_config" }
|
lqos_config = { path = "../lqos_config" }
|
||||||
lqos_utils = { path = "../lqos_utils" }
|
lqos_utils = { path = "../lqos_utils" }
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use lazy_static::*;
|
|
||||||
use lqos_config::{UserRole, WebUsers};
|
use lqos_config::{UserRole, WebUsers};
|
||||||
use parking_lot::Mutex;
|
use once_cell::sync::Lazy;
|
||||||
use rocket::serde::{json::Json, Deserialize, Serialize};
|
use rocket::serde::{json::Json, Deserialize, Serialize};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::{Cookie, CookieJar, Status},
|
http::{Cookie, CookieJar, Status},
|
||||||
@ -9,9 +10,8 @@ use rocket::{
|
|||||||
Request,
|
Request,
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
static WEB_USERS: Lazy<Mutex<Option<WebUsers>>> =
|
||||||
static ref WEB_USERS: Mutex<Option<WebUsers>> = Mutex::new(None);
|
Lazy::new(|| Mutex::new(None));
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AuthGuard {
|
pub enum AuthGuard {
|
||||||
@ -27,7 +27,7 @@ impl<'r> FromRequest<'r> for AuthGuard {
|
|||||||
async fn from_request(
|
async fn from_request(
|
||||||
request: &'r Request<'_>,
|
request: &'r Request<'_>,
|
||||||
) -> Outcome<Self, Self::Error> {
|
) -> Outcome<Self, Self::Error> {
|
||||||
let mut lock = WEB_USERS.lock();
|
let mut lock = WEB_USERS.lock().unwrap();
|
||||||
if lock.is_none() {
|
if lock.is_none() {
|
||||||
if WebUsers::does_users_file_exist().unwrap() {
|
if WebUsers::does_users_file_exist().unwrap() {
|
||||||
*lock = Some(WebUsers::load_or_create().unwrap());
|
*lock = Some(WebUsers::load_or_create().unwrap());
|
||||||
@ -82,7 +82,7 @@ pub fn create_first_user(
|
|||||||
if WebUsers::does_users_file_exist().unwrap() {
|
if WebUsers::does_users_file_exist().unwrap() {
|
||||||
return Json("ERROR".to_string());
|
return Json("ERROR".to_string());
|
||||||
}
|
}
|
||||||
let mut lock = WEB_USERS.lock();
|
let mut lock = WEB_USERS.lock().unwrap();
|
||||||
let mut users = WebUsers::load_or_create().unwrap();
|
let mut users = WebUsers::load_or_create().unwrap();
|
||||||
users.allow_anonymous(info.allow_anonymous).unwrap();
|
users.allow_anonymous(info.allow_anonymous).unwrap();
|
||||||
let token = users
|
let token = users
|
||||||
@ -102,7 +102,7 @@ pub struct LoginAttempt {
|
|||||||
|
|
||||||
#[post("/api/login", data = "<info>")]
|
#[post("/api/login", data = "<info>")]
|
||||||
pub fn login(cookies: &CookieJar, info: Json<LoginAttempt>) -> Json<String> {
|
pub fn login(cookies: &CookieJar, info: Json<LoginAttempt>) -> Json<String> {
|
||||||
let mut lock = WEB_USERS.lock();
|
let mut lock = WEB_USERS.lock().unwrap();
|
||||||
if lock.is_none() && WebUsers::does_users_file_exist().unwrap() {
|
if lock.is_none() && WebUsers::does_users_file_exist().unwrap() {
|
||||||
*lock = Some(WebUsers::load_or_create().unwrap());
|
*lock = Some(WebUsers::load_or_create().unwrap());
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ pub fn admin_check(auth: AuthGuard) -> Json<bool> {
|
|||||||
#[get("/api/username")]
|
#[get("/api/username")]
|
||||||
pub fn username(_auth: AuthGuard, cookies: &CookieJar) -> Json<String> {
|
pub fn username(_auth: AuthGuard, cookies: &CookieJar) -> Json<String> {
|
||||||
if let Some(token) = cookies.get("User-Token") {
|
if let Some(token) = cookies.get("User-Token") {
|
||||||
let lock = WEB_USERS.lock();
|
let lock = WEB_USERS.lock().unwrap();
|
||||||
if let Some(users) = &*lock {
|
if let Some(users) = &*lock {
|
||||||
return Json(users.get_username(token.value()));
|
return Json(users.get_username(token.value()));
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{auth_guard::AuthGuard, cache_control::NoCache};
|
use crate::{auth_guard::AuthGuard, cache_control::NoCache};
|
||||||
use default_net::get_interfaces;
|
use default_net::get_interfaces;
|
||||||
use lqos_bus::{bus_request, BusRequest};
|
use lqos_bus::{bus_request, BusRequest, BusResponse};
|
||||||
use lqos_config::{EtcLqos, LibreQoSConfig, Tunables};
|
use lqos_config::{EtcLqos, LibreQoSConfig, Tunables};
|
||||||
use rocket::{fs::NamedFile, serde::json::Json};
|
use rocket::{fs::NamedFile, serde::{json::Json, Serialize}};
|
||||||
|
|
||||||
// Note that NoCache can be replaced with a cache option
|
// Note that NoCache can be replaced with a cache option
|
||||||
// once the design work is complete.
|
// once the design work is complete.
|
||||||
@ -76,3 +76,23 @@ pub async fn update_lqos_tuning(
|
|||||||
|
|
||||||
Json("OK".to_string())
|
Json("OK".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, Default)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct LqosStats {
|
||||||
|
pub bus_requests_since_start: u64,
|
||||||
|
pub time_to_poll_hosts_us: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/api/stats")]
|
||||||
|
pub async fn stats() -> NoCache<Json<LqosStats>> {
|
||||||
|
for msg in bus_request(vec![BusRequest::GetLqosStats]).await.unwrap() {
|
||||||
|
if let BusResponse::LqosdStats { bus_requests, time_to_poll_hosts } = msg {
|
||||||
|
return NoCache::new(Json(LqosStats {
|
||||||
|
bus_requests_since_start: bus_requests,
|
||||||
|
time_to_poll_hosts_us: time_to_poll_hosts
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoCache::new(Json(LqosStats::default()))
|
||||||
|
}
|
@ -9,8 +9,8 @@ mod unknown_devices;
|
|||||||
use rocket_async_compression::Compression;
|
use rocket_async_compression::Compression;
|
||||||
mod auth_guard;
|
mod auth_guard;
|
||||||
mod config_control;
|
mod config_control;
|
||||||
mod queue_info;
|
|
||||||
mod network_tree;
|
mod network_tree;
|
||||||
|
mod queue_info;
|
||||||
|
|
||||||
// Use JemAllocator only on supported platforms
|
// Use JemAllocator only on supported platforms
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
@ -46,14 +46,12 @@ fn rocket() -> _ {
|
|||||||
static_pages::klingon,
|
static_pages::klingon,
|
||||||
// API calls
|
// API calls
|
||||||
tracker::current_throughput,
|
tracker::current_throughput,
|
||||||
tracker::throughput_ring,
|
|
||||||
tracker::cpu_usage,
|
tracker::cpu_usage,
|
||||||
tracker::ram_usage,
|
tracker::ram_usage,
|
||||||
tracker::top_10_downloaders,
|
tracker::top_10_downloaders,
|
||||||
tracker::worst_10_rtt,
|
tracker::worst_10_rtt,
|
||||||
tracker::rtt_histogram,
|
tracker::rtt_histogram,
|
||||||
tracker::host_counts,
|
tracker::host_counts,
|
||||||
tracker::busy_quantile,
|
|
||||||
shaped_devices::all_shaped_devices,
|
shaped_devices::all_shaped_devices,
|
||||||
shaped_devices::shaped_devices_count,
|
shaped_devices::shaped_devices_count,
|
||||||
shaped_devices::shaped_devices_range,
|
shaped_devices::shaped_devices_range,
|
||||||
@ -80,10 +78,16 @@ fn rocket() -> _ {
|
|||||||
static_pages::login_page,
|
static_pages::login_page,
|
||||||
auth_guard::username,
|
auth_guard::username,
|
||||||
network_tree::tree_entry,
|
network_tree::tree_entry,
|
||||||
|
network_tree::tree_clients,
|
||||||
|
network_tree::network_tree_summary,
|
||||||
|
network_tree::node_names,
|
||||||
|
network_tree::funnel_for_queue,
|
||||||
|
config_control::stats,
|
||||||
// Supporting files
|
// Supporting files
|
||||||
static_pages::bootsrap_css,
|
static_pages::bootsrap_css,
|
||||||
static_pages::plotly_js,
|
static_pages::plotly_js,
|
||||||
static_pages::jquery_js,
|
static_pages::jquery_js,
|
||||||
|
static_pages::msgpack_js,
|
||||||
static_pages::bootsrap_js,
|
static_pages::bootsrap_js,
|
||||||
static_pages::tinylogo,
|
static_pages::tinylogo,
|
||||||
static_pages::favicon,
|
static_pages::favicon,
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
use lqos_bus::{bus_request, BusRequest, BusResponse};
|
use std::net::IpAddr;
|
||||||
use lqos_config::NetworkJsonNode;
|
|
||||||
use rocket::{fs::NamedFile, serde::json::Json};
|
|
||||||
|
|
||||||
use crate::cache_control::NoCache;
|
use lqos_bus::{bus_request, BusRequest, BusResponse};
|
||||||
|
use lqos_config::NetworkJsonTransport;
|
||||||
|
use rocket::{
|
||||||
|
fs::NamedFile,
|
||||||
|
serde::{json::Json, Serialize, msgpack::MsgPack},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{cache_control::NoCache, tracker::SHAPED_DEVICES};
|
||||||
|
|
||||||
// Note that NoCache can be replaced with a cache option
|
// Note that NoCache can be replaced with a cache option
|
||||||
// once the design work is complete.
|
// once the design work is complete.
|
||||||
@ -14,7 +19,7 @@ pub async fn tree_page<'a>() -> NoCache<Option<NamedFile>> {
|
|||||||
#[get("/api/network_tree/<parent>")]
|
#[get("/api/network_tree/<parent>")]
|
||||||
pub async fn tree_entry(
|
pub async fn tree_entry(
|
||||||
parent: usize,
|
parent: usize,
|
||||||
) -> NoCache<Json<Vec<(usize, NetworkJsonNode)>>> {
|
) -> NoCache<MsgPack<Vec<(usize, NetworkJsonTransport)>>> {
|
||||||
let responses =
|
let responses =
|
||||||
bus_request(vec![BusRequest::GetNetworkMap { parent }]).await.unwrap();
|
bus_request(vec![BusRequest::GetNetworkMap { parent }]).await.unwrap();
|
||||||
let result = match &responses[0] {
|
let result = match &responses[0] {
|
||||||
@ -22,5 +27,105 @@ pub async fn tree_entry(
|
|||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
NoCache::new(MsgPack(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/api/network_tree_summary")]
|
||||||
|
pub async fn network_tree_summary(
|
||||||
|
) -> NoCache<MsgPack<Vec<(usize, NetworkJsonTransport)>>> {
|
||||||
|
let responses =
|
||||||
|
bus_request(vec![BusRequest::TopMapQueues(4)]).await.unwrap();
|
||||||
|
let result = match &responses[0] {
|
||||||
|
BusResponse::NetworkMap(nodes) => nodes.to_owned(),
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
NoCache::new(MsgPack(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct CircuitThroughput {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub traffic: (u64, u64),
|
||||||
|
pub limit: (u64, u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/api/tree_clients/<parent>")]
|
||||||
|
pub async fn tree_clients(
|
||||||
|
parent: String,
|
||||||
|
) -> NoCache<MsgPack<Vec<CircuitThroughput>>> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for msg in
|
||||||
|
bus_request(vec![BusRequest::GetHostCounter]).await.unwrap().iter()
|
||||||
|
{
|
||||||
|
let devices = SHAPED_DEVICES.read().unwrap();
|
||||||
|
if let BusResponse::HostCounters(hosts) = msg {
|
||||||
|
for (ip, down, up) in hosts.iter() {
|
||||||
|
let lookup = match ip {
|
||||||
|
IpAddr::V4(ip) => ip.to_ipv6_mapped(),
|
||||||
|
IpAddr::V6(ip) => *ip,
|
||||||
|
};
|
||||||
|
if let Some(c) = devices.trie.longest_match(lookup) {
|
||||||
|
if devices.devices[*c.1].parent_node == parent {
|
||||||
|
result.push(CircuitThroughput {
|
||||||
|
id: devices.devices[*c.1].circuit_id.clone(),
|
||||||
|
name: devices.devices[*c.1].circuit_name.clone(),
|
||||||
|
traffic: (*down, *up),
|
||||||
|
limit: (
|
||||||
|
devices.devices[*c.1].download_max_mbps as u64,
|
||||||
|
devices.devices[*c.1].upload_max_mbps as u64,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoCache::new(MsgPack(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/api/node_names", data = "<nodes>")]
|
||||||
|
pub async fn node_names(
|
||||||
|
nodes: Json<Vec<usize>>,
|
||||||
|
) -> NoCache<Json<Vec<(usize, String)>>> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for msg in bus_request(vec![BusRequest::GetNodeNamesFromIds(nodes.0)])
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
if let BusResponse::NodeNames(map) = msg {
|
||||||
|
result.extend_from_slice(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoCache::new(Json(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/api/funnel_for_queue/<circuit_id>")]
|
||||||
|
pub async fn funnel_for_queue(
|
||||||
|
circuit_id: String,
|
||||||
|
) -> NoCache<Json<Vec<(usize, NetworkJsonTransport)>>> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
let target = SHAPED_DEVICES
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.devices
|
||||||
|
.iter()
|
||||||
|
.find(|d| d.circuit_id == circuit_id)
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.parent_node
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
for msg in
|
||||||
|
bus_request(vec![BusRequest::GetFunnel { target }]).await.unwrap().iter()
|
||||||
|
{
|
||||||
|
if let BusResponse::NetworkMap(map) = msg {
|
||||||
|
result.extend_from_slice(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
NoCache::new(Json(result))
|
NoCache::new(Json(result))
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,12 @@ pub async fn circuit_info(
|
|||||||
circuit_id: String,
|
circuit_id: String,
|
||||||
_auth: AuthGuard,
|
_auth: AuthGuard,
|
||||||
) -> NoCache<Json<CircuitInfo>> {
|
) -> NoCache<Json<CircuitInfo>> {
|
||||||
if let Some(device) =
|
if let Some(device) = SHAPED_DEVICES
|
||||||
SHAPED_DEVICES.read().devices.iter().find(|d| d.circuit_id == circuit_id)
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.devices
|
||||||
|
.iter()
|
||||||
|
.find(|d| d.circuit_id == circuit_id)
|
||||||
{
|
{
|
||||||
let result = CircuitInfo {
|
let result = CircuitInfo {
|
||||||
name: device.circuit_name.clone(),
|
name: device.circuit_name.clone(),
|
||||||
@ -63,7 +67,7 @@ pub async fn current_circuit_throughput(
|
|||||||
bus_request(vec![BusRequest::GetHostCounter]).await.unwrap().iter()
|
bus_request(vec![BusRequest::GetHostCounter]).await.unwrap().iter()
|
||||||
{
|
{
|
||||||
if let BusResponse::HostCounters(hosts) = msg {
|
if let BusResponse::HostCounters(hosts) = msg {
|
||||||
let devices = SHAPED_DEVICES.read();
|
let devices = SHAPED_DEVICES.read().unwrap();
|
||||||
for (ip, down, up) in hosts.iter() {
|
for (ip, down, up) in hosts.iter() {
|
||||||
let lookup = match ip {
|
let lookup = match ip {
|
||||||
IpAddr::V4(ip) => ip.to_ipv6_mapped(),
|
IpAddr::V4(ip) => ip.to_ipv6_mapped(),
|
||||||
|
@ -13,12 +13,12 @@ static RELOAD_REQUIRED: AtomicBool = AtomicBool::new(false);
|
|||||||
pub fn all_shaped_devices(
|
pub fn all_shaped_devices(
|
||||||
_auth: AuthGuard,
|
_auth: AuthGuard,
|
||||||
) -> NoCache<Json<Vec<ShapedDevice>>> {
|
) -> NoCache<Json<Vec<ShapedDevice>>> {
|
||||||
NoCache::new(Json(SHAPED_DEVICES.read().devices.clone()))
|
NoCache::new(Json(SHAPED_DEVICES.read().unwrap().devices.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/shaped_devices_count")]
|
#[get("/api/shaped_devices_count")]
|
||||||
pub fn shaped_devices_count(_auth: AuthGuard) -> NoCache<Json<usize>> {
|
pub fn shaped_devices_count(_auth: AuthGuard) -> NoCache<Json<usize>> {
|
||||||
NoCache::new(Json(SHAPED_DEVICES.read().devices.len()))
|
NoCache::new(Json(SHAPED_DEVICES.read().unwrap().devices.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/shaped_devices_range/<start>/<end>")]
|
#[get("/api/shaped_devices_range/<start>/<end>")]
|
||||||
@ -27,7 +27,7 @@ pub fn shaped_devices_range(
|
|||||||
end: usize,
|
end: usize,
|
||||||
_auth: AuthGuard,
|
_auth: AuthGuard,
|
||||||
) -> NoCache<Json<Vec<ShapedDevice>>> {
|
) -> NoCache<Json<Vec<ShapedDevice>>> {
|
||||||
let reader = SHAPED_DEVICES.read();
|
let reader = SHAPED_DEVICES.read().unwrap();
|
||||||
let result: Vec<ShapedDevice> =
|
let result: Vec<ShapedDevice> =
|
||||||
reader.devices.iter().skip(start).take(end).cloned().collect();
|
reader.devices.iter().skip(start).take(end).cloned().collect();
|
||||||
NoCache::new(Json(result))
|
NoCache::new(Json(result))
|
||||||
@ -39,7 +39,7 @@ pub fn shaped_devices_search(
|
|||||||
_auth: AuthGuard,
|
_auth: AuthGuard,
|
||||||
) -> NoCache<Json<Vec<ShapedDevice>>> {
|
) -> NoCache<Json<Vec<ShapedDevice>>> {
|
||||||
let term = term.trim().to_lowercase();
|
let term = term.trim().to_lowercase();
|
||||||
let reader = SHAPED_DEVICES.read();
|
let reader = SHAPED_DEVICES.read().unwrap();
|
||||||
let result: Vec<ShapedDevice> = reader
|
let result: Vec<ShapedDevice> = reader
|
||||||
.devices
|
.devices
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -104,6 +104,11 @@ pub async fn jquery_js<'a>() -> LongCache<Option<NamedFile>> {
|
|||||||
LongCache::new(NamedFile::open("static/vendor/jquery.min.js").await.ok())
|
LongCache::new(NamedFile::open("static/vendor/jquery.min.js").await.ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/vendor/msgpack.min.js")]
|
||||||
|
pub async fn msgpack_js<'a>() -> LongCache<Option<NamedFile>> {
|
||||||
|
LongCache::new(NamedFile::open("static/vendor/msgpack.min.js").await.ok())
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/vendor/bootstrap.bundle.min.js")]
|
#[get("/vendor/bootstrap.bundle.min.js")]
|
||||||
pub async fn bootsrap_js<'a>() -> LongCache<Option<NamedFile>> {
|
pub async fn bootsrap_js<'a>() -> LongCache<Option<NamedFile>> {
|
||||||
LongCache::new(
|
LongCache::new(
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
use lazy_static::*;
|
|
||||||
use lqos_bus::IpStats;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref TOP_10_DOWNLOADERS: RwLock<Vec<IpStats>> =
|
|
||||||
RwLock::new(Vec::with_capacity(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref WORST_10_RTT: RwLock<Vec<IpStats>> =
|
|
||||||
RwLock::new(Vec::with_capacity(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref RTT_HISTOGRAM: RwLock<Vec<u32>> =
|
|
||||||
RwLock::new(Vec::with_capacity(100));
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref HOST_COUNTS: RwLock<(u32, u32)> = RwLock::new((0, 0));
|
|
||||||
}
|
|
@ -3,11 +3,7 @@
|
|||||||
//! of the system.
|
//! of the system.
|
||||||
|
|
||||||
mod cpu_ram;
|
mod cpu_ram;
|
||||||
mod lqosd_stats;
|
|
||||||
mod shaped_devices;
|
mod shaped_devices;
|
||||||
mod throughput;
|
|
||||||
|
|
||||||
pub use cpu_ram::*;
|
pub use cpu_ram::*;
|
||||||
pub use lqosd_stats::*;
|
|
||||||
pub use shaped_devices::*;
|
pub use shaped_devices::*;
|
||||||
pub use throughput::*;
|
|
||||||
|
@ -1,18 +1,9 @@
|
|||||||
use lazy_static::*;
|
|
||||||
use lqos_bus::IpStats;
|
|
||||||
use lqos_config::ConfigShapedDevices;
|
use lqos_config::ConfigShapedDevices;
|
||||||
use parking_lot::RwLock;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
lazy_static! {
|
/// Global storage of the shaped devices csv data.
|
||||||
/// Global storage of the shaped devices csv data.
|
/// Updated by the file system watcher whenever
|
||||||
/// Updated by the file system watcher whenever
|
/// the underlying file changes.
|
||||||
/// the underlying file changes.
|
pub static SHAPED_DEVICES: Lazy<RwLock<ConfigShapedDevices>> =
|
||||||
pub static ref SHAPED_DEVICES : RwLock<ConfigShapedDevices> = RwLock::new(ConfigShapedDevices::default());
|
Lazy::new(|| RwLock::new(ConfigShapedDevices::default()));
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
/// Global storage of the shaped devices csv data.
|
|
||||||
/// Updated by the file system watcher whenever
|
|
||||||
/// the underlying file changes.
|
|
||||||
pub static ref UNKNOWN_DEVICES : RwLock<Vec<IpStats>> = RwLock::new(Vec::new());
|
|
||||||
}
|
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
use lazy_static::*;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use rocket::serde::Serialize;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
/// Global storage of the current throughput counter.
|
|
||||||
pub static ref CURRENT_THROUGHPUT : RwLock<ThroughputPerSecond> = RwLock::new(ThroughputPerSecond::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
/// Global storage of the last N seconds throughput buffer.
|
|
||||||
pub static ref THROUGHPUT_BUFFER : RwLock<ThroughputRingbuffer> = RwLock::new(ThroughputRingbuffer::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores total system throughput per second.
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Default)]
|
|
||||||
#[serde(crate = "rocket::serde")]
|
|
||||||
pub struct ThroughputPerSecond {
|
|
||||||
pub bits_per_second: (u64, u64),
|
|
||||||
pub packets_per_second: (u64, u64),
|
|
||||||
pub shaped_bits_per_second: (u64, u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How many entries (at one per second) should we keep in the
|
|
||||||
/// throughput ringbuffer?
|
|
||||||
const RINGBUFFER_SAMPLES: usize = 300;
|
|
||||||
|
|
||||||
/// Stores Throughput samples in a ringbuffer, continually
|
|
||||||
/// updating. There are always RINGBUFFER_SAMPLES available,
|
|
||||||
/// allowing for non-allocating/non-growing storage of
|
|
||||||
/// throughput for the dashboard summaries.
|
|
||||||
pub struct ThroughputRingbuffer {
|
|
||||||
readings: Vec<ThroughputPerSecond>,
|
|
||||||
next: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThroughputRingbuffer {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
readings: vec![ThroughputPerSecond::default(); RINGBUFFER_SAMPLES],
|
|
||||||
next: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn store(&mut self, reading: ThroughputPerSecond) {
|
|
||||||
self.readings[self.next] = reading;
|
|
||||||
self.next += 1;
|
|
||||||
self.next %= RINGBUFFER_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_result(&self) -> Vec<ThroughputPerSecond> {
|
|
||||||
let mut result = Vec::with_capacity(RINGBUFFER_SAMPLES);
|
|
||||||
|
|
||||||
for i in self.next..RINGBUFFER_SAMPLES {
|
|
||||||
result.push(self.readings[i]);
|
|
||||||
}
|
|
||||||
for i in 0..self.next {
|
|
||||||
result.push(self.readings[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,6 @@
|
|||||||
//! when there are multiple clients.
|
//! when there are multiple clients.
|
||||||
use super::cache::*;
|
use super::cache::*;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use lqos_bus::{bus_request, BusRequest, BusResponse, IpStats};
|
|
||||||
use lqos_config::ConfigShapedDevices;
|
use lqos_config::ConfigShapedDevices;
|
||||||
use lqos_utils::file_watcher::FileWatcher;
|
use lqos_utils::file_watcher::FileWatcher;
|
||||||
use nix::sys::{
|
use nix::sys::{
|
||||||
@ -11,7 +10,7 @@ use nix::sys::{
|
|||||||
timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags},
|
timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags},
|
||||||
};
|
};
|
||||||
use rocket::tokio::task::spawn_blocking;
|
use rocket::tokio::task::spawn_blocking;
|
||||||
use std::{net::IpAddr, sync::atomic::AtomicBool};
|
use std::{sync::atomic::AtomicBool};
|
||||||
|
|
||||||
/// Once per second, update CPU and RAM usage and ask
|
/// Once per second, update CPU and RAM usage and ask
|
||||||
/// `lqosd` for updated system statistics.
|
/// `lqosd` for updated system statistics.
|
||||||
@ -69,10 +68,6 @@ pub async fn update_tracking() {
|
|||||||
.store(sys.used_memory(), std::sync::atomic::Ordering::Relaxed);
|
.store(sys.used_memory(), std::sync::atomic::Ordering::Relaxed);
|
||||||
TOTAL_RAM
|
TOTAL_RAM
|
||||||
.store(sys.total_memory(), std::sync::atomic::Ordering::Relaxed);
|
.store(sys.total_memory(), std::sync::atomic::Ordering::Relaxed);
|
||||||
let error = get_data_from_server().await; // Ignoring errors to keep running
|
|
||||||
if let Err(error) = error {
|
|
||||||
error!("Error in usage update loop: {:?}", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_busy.store(false, std::sync::atomic::Ordering::Relaxed);
|
monitor_busy.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
@ -95,10 +90,10 @@ fn load_shaped_devices() {
|
|||||||
let shaped_devices = ConfigShapedDevices::load();
|
let shaped_devices = ConfigShapedDevices::load();
|
||||||
if let Ok(new_file) = shaped_devices {
|
if let Ok(new_file) = shaped_devices {
|
||||||
info!("ShapedDevices.csv loaded");
|
info!("ShapedDevices.csv loaded");
|
||||||
*SHAPED_DEVICES.write() = new_file;
|
*SHAPED_DEVICES.write().unwrap() = new_file;
|
||||||
} else {
|
} else {
|
||||||
warn!("ShapedDevices.csv failed to load, see previous error messages. Reverting to empty set.");
|
warn!("ShapedDevices.csv failed to load, see previous error messages. Reverting to empty set.");
|
||||||
*SHAPED_DEVICES.write() = ConfigShapedDevices::default();
|
*SHAPED_DEVICES.write().unwrap() = ConfigShapedDevices::default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,77 +118,3 @@ fn watch_for_shaped_devices_changing() -> Result<()> {
|
|||||||
info!("ShapedDevices watcher returned: {result:?}");
|
info!("ShapedDevices watcher returned: {result:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests data from `lqosd` and stores it in local
|
|
||||||
/// caches.
|
|
||||||
async fn get_data_from_server() -> Result<()> {
|
|
||||||
// Send request to lqosd
|
|
||||||
let requests = vec![
|
|
||||||
BusRequest::GetCurrentThroughput,
|
|
||||||
BusRequest::GetTopNDownloaders { start: 0, end: 10 },
|
|
||||||
BusRequest::GetWorstRtt { start: 0, end: 10 },
|
|
||||||
BusRequest::RttHistogram,
|
|
||||||
BusRequest::AllUnknownIps,
|
|
||||||
];
|
|
||||||
|
|
||||||
for r in bus_request(requests).await?.iter() {
|
|
||||||
match r {
|
|
||||||
BusResponse::CurrentThroughput {
|
|
||||||
bits_per_second,
|
|
||||||
packets_per_second,
|
|
||||||
shaped_bits_per_second,
|
|
||||||
} => {
|
|
||||||
{
|
|
||||||
let mut lock = CURRENT_THROUGHPUT.write();
|
|
||||||
lock.bits_per_second = *bits_per_second;
|
|
||||||
lock.packets_per_second = *packets_per_second;
|
|
||||||
} // Lock scope
|
|
||||||
{
|
|
||||||
let mut lock = THROUGHPUT_BUFFER.write();
|
|
||||||
lock.store(ThroughputPerSecond {
|
|
||||||
packets_per_second: *packets_per_second,
|
|
||||||
bits_per_second: *bits_per_second,
|
|
||||||
shaped_bits_per_second: *shaped_bits_per_second,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BusResponse::TopDownloaders(stats) => {
|
|
||||||
*TOP_10_DOWNLOADERS.write() = stats.clone();
|
|
||||||
}
|
|
||||||
BusResponse::WorstRtt(stats) => {
|
|
||||||
*WORST_10_RTT.write() = stats.clone();
|
|
||||||
}
|
|
||||||
BusResponse::RttHistogram(stats) => {
|
|
||||||
*RTT_HISTOGRAM.write() = stats.clone();
|
|
||||||
}
|
|
||||||
BusResponse::AllUnknownIps(unknowns) => {
|
|
||||||
*HOST_COUNTS.write() = (unknowns.len() as u32, 0);
|
|
||||||
let cfg = SHAPED_DEVICES.read();
|
|
||||||
let really_unknown: Vec<IpStats> = unknowns
|
|
||||||
.iter()
|
|
||||||
.filter(|ip| {
|
|
||||||
if let Ok(ip) = ip.ip_address.parse::<IpAddr>() {
|
|
||||||
let lookup = match ip {
|
|
||||||
IpAddr::V4(ip) => ip.to_ipv6_mapped(),
|
|
||||||
IpAddr::V6(ip) => ip,
|
|
||||||
};
|
|
||||||
cfg.trie.longest_match(lookup).is_none()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
*HOST_COUNTS.write() = (really_unknown.len() as u32, 0);
|
|
||||||
*UNKNOWN_DEVICES.write() = really_unknown;
|
|
||||||
}
|
|
||||||
BusResponse::NotReadyYet => {
|
|
||||||
warn!("Host system isn't ready to answer all queries yet.");
|
|
||||||
}
|
|
||||||
// Default
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
mod cache;
|
mod cache;
|
||||||
mod cache_manager;
|
mod cache_manager;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use self::cache::{
|
use self::cache::{
|
||||||
CPU_USAGE, CURRENT_THROUGHPUT, HOST_COUNTS, NUM_CPUS, RAM_USED,
|
CPU_USAGE, NUM_CPUS, RAM_USED, TOTAL_RAM,
|
||||||
RTT_HISTOGRAM, THROUGHPUT_BUFFER, TOP_10_DOWNLOADERS, TOTAL_RAM,
|
|
||||||
WORST_10_RTT,
|
|
||||||
};
|
};
|
||||||
use crate::{auth_guard::AuthGuard, tracker::cache::ThroughputPerSecond};
|
use crate::{auth_guard::AuthGuard, cache_control::NoCache};
|
||||||
pub use cache::{SHAPED_DEVICES, UNKNOWN_DEVICES};
|
pub use cache::SHAPED_DEVICES;
|
||||||
pub use cache_manager::update_tracking;
|
pub use cache_manager::update_tracking;
|
||||||
use lazy_static::lazy_static;
|
use lqos_bus::{bus_request, BusRequest, BusResponse, IpStats, TcHandle};
|
||||||
use lqos_bus::{IpStats, TcHandle};
|
use rocket::serde::{Deserialize, Serialize, msgpack::MsgPack};
|
||||||
use lqos_config::LibreQoSConfig;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use rocket::serde::{json::Json, Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
@ -41,6 +38,7 @@ impl From<&IpStats> for IpStatsWithPlan {
|
|||||||
if !result.circuit_id.is_empty() {
|
if !result.circuit_id.is_empty() {
|
||||||
if let Some(circuit) = SHAPED_DEVICES
|
if let Some(circuit) = SHAPED_DEVICES
|
||||||
.read()
|
.read()
|
||||||
|
.unwrap()
|
||||||
.devices
|
.devices
|
||||||
.iter()
|
.iter()
|
||||||
.find(|sd| sd.circuit_id == result.circuit_id)
|
.find(|sd| sd.circuit_id == result.circuit_id)
|
||||||
@ -50,8 +48,7 @@ impl From<&IpStats> for IpStatsWithPlan {
|
|||||||
} else {
|
} else {
|
||||||
&circuit.circuit_name
|
&circuit.circuit_name
|
||||||
};
|
};
|
||||||
result.ip_address =
|
result.ip_address = format!("{} ({})", name, result.ip_address);
|
||||||
format!("{} ({})", name, result.ip_address);
|
|
||||||
result.plan = (circuit.download_max_mbps, circuit.download_min_mbps);
|
result.plan = (circuit.download_max_mbps, circuit.download_min_mbps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,91 +57,130 @@ impl From<&IpStats> for IpStatsWithPlan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/current_throughput")]
|
/// Stores total system throughput per second.
|
||||||
pub fn current_throughput(_auth: AuthGuard) -> Json<ThroughputPerSecond> {
|
#[derive(Debug, Clone, Copy, Serialize, Default)]
|
||||||
let result = *CURRENT_THROUGHPUT.read();
|
#[serde(crate = "rocket::serde")]
|
||||||
Json(result)
|
pub struct ThroughputPerSecond {
|
||||||
|
pub bits_per_second: (u64, u64),
|
||||||
|
pub packets_per_second: (u64, u64),
|
||||||
|
pub shaped_bits_per_second: (u64, u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/throughput_ring")]
|
#[get("/api/current_throughput")]
|
||||||
pub fn throughput_ring(_auth: AuthGuard) -> Json<Vec<ThroughputPerSecond>> {
|
pub async fn current_throughput(
|
||||||
let result = THROUGHPUT_BUFFER.read().get_result();
|
_auth: AuthGuard,
|
||||||
Json(result)
|
) -> NoCache<MsgPack<ThroughputPerSecond>> {
|
||||||
|
let mut result = ThroughputPerSecond::default();
|
||||||
|
if let Ok(messages) =
|
||||||
|
bus_request(vec![BusRequest::GetCurrentThroughput]).await
|
||||||
|
{
|
||||||
|
for msg in messages {
|
||||||
|
if let BusResponse::CurrentThroughput {
|
||||||
|
bits_per_second,
|
||||||
|
packets_per_second,
|
||||||
|
shaped_bits_per_second,
|
||||||
|
} = msg
|
||||||
|
{
|
||||||
|
result.bits_per_second = bits_per_second;
|
||||||
|
result.packets_per_second = packets_per_second;
|
||||||
|
result.shaped_bits_per_second = shaped_bits_per_second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoCache::new(MsgPack(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/cpu")]
|
#[get("/api/cpu")]
|
||||||
pub fn cpu_usage(_auth: AuthGuard) -> Json<Vec<u32>> {
|
pub fn cpu_usage(_auth: AuthGuard) -> NoCache<MsgPack<Vec<u32>>> {
|
||||||
let usage: Vec<u32> = CPU_USAGE
|
let usage: Vec<u32> = CPU_USAGE
|
||||||
.iter()
|
.iter()
|
||||||
.take(NUM_CPUS.load(std::sync::atomic::Ordering::Relaxed))
|
.take(NUM_CPUS.load(std::sync::atomic::Ordering::Relaxed))
|
||||||
.map(|cpu| cpu.load(std::sync::atomic::Ordering::Relaxed))
|
.map(|cpu| cpu.load(std::sync::atomic::Ordering::Relaxed))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Json(usage)
|
NoCache::new(MsgPack(usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/ram")]
|
#[get("/api/ram")]
|
||||||
pub fn ram_usage(_auth: AuthGuard) -> Json<Vec<u64>> {
|
pub fn ram_usage(_auth: AuthGuard) -> NoCache<MsgPack<Vec<u64>>> {
|
||||||
let ram_usage = RAM_USED.load(std::sync::atomic::Ordering::Relaxed);
|
let ram_usage = RAM_USED.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
let total_ram = TOTAL_RAM.load(std::sync::atomic::Ordering::Relaxed);
|
let total_ram = TOTAL_RAM.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
Json(vec![ram_usage, total_ram])
|
NoCache::new(MsgPack(vec![ram_usage, total_ram]))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/top_10_downloaders")]
|
#[get("/api/top_10_downloaders")]
|
||||||
pub fn top_10_downloaders(_auth: AuthGuard) -> Json<Vec<IpStatsWithPlan>> {
|
pub async fn top_10_downloaders(_auth: AuthGuard) -> NoCache<MsgPack<Vec<IpStatsWithPlan>>> {
|
||||||
let tt: Vec<IpStatsWithPlan> =
|
if let Ok(messages) = bus_request(vec![BusRequest::GetTopNDownloaders { start: 0, end: 10 }]).await
|
||||||
TOP_10_DOWNLOADERS.read().iter().map(|tt| tt.into()).collect();
|
{
|
||||||
Json(tt)
|
for msg in messages {
|
||||||
|
if let BusResponse::TopDownloaders(stats) = msg {
|
||||||
|
let result = stats.iter().map(|tt| tt.into()).collect();
|
||||||
|
return NoCache::new(MsgPack(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoCache::new(MsgPack(Vec::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/worst_10_rtt")]
|
#[get("/api/worst_10_rtt")]
|
||||||
pub fn worst_10_rtt(_auth: AuthGuard) -> Json<Vec<IpStatsWithPlan>> {
|
pub async fn worst_10_rtt(_auth: AuthGuard) -> NoCache<MsgPack<Vec<IpStatsWithPlan>>> {
|
||||||
let tt: Vec<IpStatsWithPlan> =
|
if let Ok(messages) = bus_request(vec![BusRequest::GetWorstRtt { start: 0, end: 10 }]).await
|
||||||
WORST_10_RTT.read().iter().map(|tt| tt.into()).collect();
|
{
|
||||||
Json(tt)
|
for msg in messages {
|
||||||
|
if let BusResponse::WorstRtt(stats) = msg {
|
||||||
|
let result = stats.iter().map(|tt| tt.into()).collect();
|
||||||
|
return NoCache::new(MsgPack(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoCache::new(MsgPack(Vec::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/rtt_histogram")]
|
#[get("/api/rtt_histogram")]
|
||||||
pub fn rtt_histogram(_auth: AuthGuard) -> Json<Vec<u32>> {
|
pub async fn rtt_histogram(_auth: AuthGuard) -> NoCache<MsgPack<Vec<u32>>> {
|
||||||
Json(RTT_HISTOGRAM.read().clone())
|
if let Ok(messages) = bus_request(vec![BusRequest::RttHistogram]).await
|
||||||
|
{
|
||||||
|
for msg in messages {
|
||||||
|
if let BusResponse::RttHistogram(stats) = msg {
|
||||||
|
let result = stats;
|
||||||
|
return NoCache::new(MsgPack(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoCache::new(MsgPack(Vec::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/host_counts")]
|
#[get("/api/host_counts")]
|
||||||
pub fn host_counts(_auth: AuthGuard) -> Json<(u32, u32)> {
|
pub async fn host_counts(_auth: AuthGuard) -> NoCache<MsgPack<(u32, u32)>> {
|
||||||
let shaped_reader = SHAPED_DEVICES.read();
|
let mut host_counts = (0, 0);
|
||||||
let n_devices = shaped_reader.devices.len();
|
if let Ok(messages) = bus_request(vec![BusRequest::AllUnknownIps]).await {
|
||||||
let host_counts = HOST_COUNTS.read();
|
for msg in messages {
|
||||||
|
if let BusResponse::AllUnknownIps(unknowns) = msg {
|
||||||
|
let really_unknown: Vec<IpStats> = unknowns
|
||||||
|
.iter()
|
||||||
|
.filter(|ip| {
|
||||||
|
if let Ok(ip) = ip.ip_address.parse::<IpAddr>() {
|
||||||
|
let lookup = match ip {
|
||||||
|
IpAddr::V4(ip) => ip.to_ipv6_mapped(),
|
||||||
|
IpAddr::V6(ip) => ip,
|
||||||
|
};
|
||||||
|
SHAPED_DEVICES.read().unwrap().trie.longest_match(lookup).is_none()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
host_counts = (really_unknown.len() as u32, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let n_devices = SHAPED_DEVICES.read().unwrap().devices.len();
|
||||||
let unknown = host_counts.0 - host_counts.1;
|
let unknown = host_counts.0 - host_counts.1;
|
||||||
Json((n_devices as u32, unknown))
|
NoCache::new(MsgPack((n_devices as u32, unknown)))
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref CONFIG: Mutex<LibreQoSConfig> =
|
|
||||||
Mutex::new(lqos_config::LibreQoSConfig::load().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/api/busy_quantile")]
|
|
||||||
pub fn busy_quantile(_auth: AuthGuard) -> Json<Vec<(u32, u32)>> {
|
|
||||||
let (down_capacity, up_capacity) = {
|
|
||||||
let lock = CONFIG.lock();
|
|
||||||
(
|
|
||||||
lock.total_download_mbps as f64 * 1_000_000.0,
|
|
||||||
lock.total_upload_mbps as f64 * 1_000_000.0,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let throughput = THROUGHPUT_BUFFER.read().get_result();
|
|
||||||
let mut result = vec![(0, 0); 10];
|
|
||||||
throughput.iter().for_each(|tp| {
|
|
||||||
let (down, up) = tp.bits_per_second;
|
|
||||||
let (down, up) = (down * 8, up * 8);
|
|
||||||
//println!("{down_capacity}, {up_capacity}, {down}, {up}");
|
|
||||||
let (down, up) = (
|
|
||||||
if down_capacity > 0.0 { down as f64 / down_capacity } else { 0.0 },
|
|
||||||
if up_capacity > 0.0 { up as f64 / up_capacity } else { 0.0 },
|
|
||||||
);
|
|
||||||
let (down, up) = ((down * 10.0) as usize, (up * 10.0) as usize);
|
|
||||||
result[usize::min(9, down)].0 += 1;
|
|
||||||
result[usize::min(0, up)].1 += 1;
|
|
||||||
});
|
|
||||||
Json(result)
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,65 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth_guard::AuthGuard, cache_control::NoCache, tracker::UNKNOWN_DEVICES,
|
auth_guard::AuthGuard, cache_control::NoCache, tracker::SHAPED_DEVICES
|
||||||
};
|
};
|
||||||
use lqos_bus::IpStats;
|
use lqos_bus::{IpStats, bus_request, BusRequest, BusResponse};
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
async fn unknown_devices() -> Vec<IpStats> {
|
||||||
|
if let Ok(messages) = bus_request(vec![BusRequest::AllUnknownIps]).await {
|
||||||
|
for msg in messages {
|
||||||
|
if let BusResponse::AllUnknownIps(unknowns) = msg {
|
||||||
|
let cfg = SHAPED_DEVICES.read().unwrap();
|
||||||
|
let really_unknown: Vec<IpStats> = unknowns
|
||||||
|
.iter()
|
||||||
|
.filter(|ip| {
|
||||||
|
if let Ok(ip) = ip.ip_address.parse::<IpAddr>() {
|
||||||
|
let lookup = match ip {
|
||||||
|
IpAddr::V4(ip) => ip.to_ipv6_mapped(),
|
||||||
|
IpAddr::V6(ip) => ip,
|
||||||
|
};
|
||||||
|
cfg.trie.longest_match(lookup).is_none()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
return really_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/api/all_unknown_devices")]
|
#[get("/api/all_unknown_devices")]
|
||||||
pub fn all_unknown_devices(_auth: AuthGuard) -> NoCache<Json<Vec<IpStats>>> {
|
pub async fn all_unknown_devices(_auth: AuthGuard) -> NoCache<Json<Vec<IpStats>>> {
|
||||||
NoCache::new(Json(UNKNOWN_DEVICES.read().clone()))
|
NoCache::new(Json(unknown_devices().await))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/unknown_devices_count")]
|
#[get("/api/unknown_devices_count")]
|
||||||
pub fn unknown_devices_count(_auth: AuthGuard) -> NoCache<Json<usize>> {
|
pub async fn unknown_devices_count(_auth: AuthGuard) -> NoCache<Json<usize>> {
|
||||||
NoCache::new(Json(UNKNOWN_DEVICES.read().len()))
|
NoCache::new(Json(unknown_devices().await.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/unknown_devices_range/<start>/<end>")]
|
#[get("/api/unknown_devices_range/<start>/<end>")]
|
||||||
pub fn unknown_devices_range(
|
pub async fn unknown_devices_range(
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
_auth: AuthGuard,
|
_auth: AuthGuard,
|
||||||
) -> NoCache<Json<Vec<IpStats>>> {
|
) -> NoCache<Json<Vec<IpStats>>> {
|
||||||
let reader = UNKNOWN_DEVICES.read();
|
let reader = unknown_devices().await;
|
||||||
let result: Vec<IpStats> =
|
let result: Vec<IpStats> =
|
||||||
reader.iter().skip(start).take(end).cloned().collect();
|
reader.iter().skip(start).take(end).cloned().collect();
|
||||||
NoCache::new(Json(result))
|
NoCache::new(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/unknown_devices_csv")]
|
#[get("/api/unknown_devices_csv")]
|
||||||
pub fn unknown_devices_csv(_auth: AuthGuard) -> NoCache<String> {
|
pub async fn unknown_devices_csv(_auth: AuthGuard) -> NoCache<String> {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let reader = UNKNOWN_DEVICES.read();
|
let reader = unknown_devices().await;
|
||||||
|
|
||||||
for unknown in reader.iter() {
|
for unknown in reader.iter() {
|
||||||
result += &format!("{}\n", unknown.ip_address);
|
result += &format!("{}\n", unknown.ip_address);
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
@ -25,10 +25,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" id="currentLogin"></li>
|
<li class="nav-item">
|
||||||
<!--<li class="nav-item">
|
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Tree</a>
|
||||||
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</a>
|
</li>
|
||||||
</li>-->
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
<a class="nav-link active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
@ -39,6 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item" id="currentLogin"></li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
|
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
|
||||||
</li>
|
</li>
|
||||||
@ -68,6 +68,9 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="pills-tins-tab" data-bs-toggle="pill" data-bs-target="#pills-tins" type="button" role="tab" aria-controls="pills-profile" aria-selected="false">All Tins</button>
|
<button class="nav-link" id="pills-tins-tab" data-bs-toggle="pill" data-bs-target="#pills-tins" type="button" role="tab" aria-controls="pills-profile" aria-selected="false">All Tins</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="pills-funnel-tab" data-bs-toggle="pill" data-bs-target="#pills-funnel" type="button" role="tab" aria-controls="pills-funnel" aria-selected="false">Queue Funnel</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
@ -139,7 +142,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="pills-tins" role="tabpanel" aria-labelledby="pills-tins-tab" tabindex="0">
|
<div class="tab-pane fade" id="pills-tins" role="tabpanel" aria-labelledby="pills-tins-tab" tabindex="1">
|
||||||
<div class="row" class="mtop4">
|
<div class="row" class="mtop4">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="card bg-light">
|
<div class="card bg-light">
|
||||||
@ -181,6 +184,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="pills-funnel" role="tabpanel" aria-labelledby="pills-funnel-tab" tabindex="2">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -450,6 +456,63 @@
|
|||||||
setTimeout(getThroughput, 1000);
|
setTimeout(getThroughput, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let funnels = new MultiRingBuffer(300);
|
||||||
|
let rtts = {};
|
||||||
|
let circuitId = "";
|
||||||
|
|
||||||
|
function getFunnel(c) {
|
||||||
|
circuitId = encodeURI(c);
|
||||||
|
$.get("/api/funnel_for_queue/" + circuitId, (data) => {
|
||||||
|
let html = "";
|
||||||
|
for (let i=0; i<data.length; ++i) {
|
||||||
|
funnels.push(data[i][0], data[i][1].current_throughput[0]*8, data[i][1].current_throughput[1]*8);
|
||||||
|
rtts[data[i][0]] = new RttHistogram();
|
||||||
|
|
||||||
|
let row = "<div class='row row220'>";
|
||||||
|
|
||||||
|
row += "<div class='col-sm-6'>";
|
||||||
|
row += "<div class='card bg-light'>";
|
||||||
|
row += "<h5 class='card-title'><i class='fa fa-hourglass'></i> <a class='redact' href='/tree?parent=" + data[i][0] + "'>" + redactText(data[i][1].name) + " Throughput</a></h5>";
|
||||||
|
row += "<div id='tp" + data[i][0] + "' class='graph98 graph150'></div>";
|
||||||
|
row += "</div>";
|
||||||
|
row += "</div>";
|
||||||
|
|
||||||
|
row += "<div class='col-sm-6'>";
|
||||||
|
row += "<div class='card bg-light'>";
|
||||||
|
row += "<h5 class='card-title redact'><i class='fa fa-bar-chart'></i> " + redactText(data[i][1].name) + " TCP RTT</h5>";
|
||||||
|
row += "<div id='rtt" + data[i][0] + "' class='graph98 graph150'></div>";
|
||||||
|
row += "</div>";
|
||||||
|
row += "</div>";
|
||||||
|
|
||||||
|
row += "</div>";
|
||||||
|
html += row;
|
||||||
|
}
|
||||||
|
$("#pills-funnel").html(html);
|
||||||
|
setTimeout(plotFunnels, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function plotFunnels() {
|
||||||
|
$.get("/api/funnel_for_queue/" + encodeURI(circuitId), (data) => {
|
||||||
|
for (let i=0; i<data.length; ++i) {
|
||||||
|
funnels.push(data[i][0], data[i][1].current_throughput[0]*8, data[i][1].current_throughput[1]*8);
|
||||||
|
for (const [k, v] of Object.entries(funnels.data)) {
|
||||||
|
let target_div = "tp" + k;
|
||||||
|
let graphData = v.toScatterGraphData();
|
||||||
|
let graph = document.getElementById(target_div);
|
||||||
|
Plotly.newPlot(graph, graphData, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true, title: "Time since now (seconds)"} }, { responsive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
rtts[data[i][0]].clear();
|
||||||
|
for (let j=0; j<data[i][1].rtts.length; j++) {
|
||||||
|
rtts[data[i][0]].push(data[i][1].rtts[j]);
|
||||||
|
}
|
||||||
|
rtts[data[i][0]].plot("rtt" + data[i][0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(plotFunnels, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
colorReloadButton();
|
colorReloadButton();
|
||||||
updateHostCounts();
|
updateHostCounts();
|
||||||
@ -459,6 +522,7 @@
|
|||||||
$.get("/api/watch_circuit/" + params.id, () => {
|
$.get("/api/watch_circuit/" + params.id, () => {
|
||||||
pollQueue();
|
pollQueue();
|
||||||
getThroughput();
|
getThroughput();
|
||||||
|
getFunnel(params.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
@ -25,10 +25,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" id="currentLogin"></li>
|
<li class="nav-item">
|
||||||
<!--<li class="nav-item">
|
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Tree</a>
|
||||||
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</a>
|
</li>
|
||||||
</li>-->
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
<a class="nav-link" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
@ -39,6 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item" id="currentLogin"></li>
|
||||||
<li class="nav-item ms-auto">
|
<li class="nav-item ms-auto">
|
||||||
<a class="nav-link active" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
<a class="nav-link active" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
|
@ -1,11 +1,49 @@
|
|||||||
|
function msgPackGet(url, success) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("GET", url, true);
|
||||||
|
xhr.responseType = "arraybuffer";
|
||||||
|
xhr.onload = () => {
|
||||||
|
var data = xhr.response;
|
||||||
|
let decoded = msgpack.decode(new Uint8Array(data));
|
||||||
|
success(decoded);
|
||||||
|
};
|
||||||
|
xhr.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const NetTrans = {
|
||||||
|
"name": 0,
|
||||||
|
"max_throughput": 1,
|
||||||
|
"current_throughput": 2,
|
||||||
|
"rtts": 3,
|
||||||
|
"parents": 4,
|
||||||
|
"immediate_parent": 5
|
||||||
|
}
|
||||||
|
|
||||||
|
const Circuit = {
|
||||||
|
"id" : 0,
|
||||||
|
"name" : 1,
|
||||||
|
"traffic": 2,
|
||||||
|
"limit": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
const IpStats = {
|
||||||
|
"ip_address": 0,
|
||||||
|
"bits_per_second": 1,
|
||||||
|
"packets_per_second": 2,
|
||||||
|
"median_tcp_rtt": 3,
|
||||||
|
"tc_handle": 4,
|
||||||
|
"circuit_id": 5,
|
||||||
|
"plan": 6,
|
||||||
|
}
|
||||||
|
|
||||||
function metaverse_color_ramp(n) {
|
function metaverse_color_ramp(n) {
|
||||||
if (n <= 9) {
|
if (n <= 9) {
|
||||||
return "#32b08c";
|
return "#32b08c";
|
||||||
} else if (n <= 20) {
|
} else if (n <= 20) {
|
||||||
return "#ffb94a";
|
return "#ffb94a";
|
||||||
} else if (n <=50) {
|
} else if (n <= 50) {
|
||||||
return "#f95f53";
|
return "#f95f53";
|
||||||
} else if (n <=70) {
|
} else if (n <= 70) {
|
||||||
return "#bf3d5e";
|
return "#bf3d5e";
|
||||||
} else {
|
} else {
|
||||||
return "#dc4e58";
|
return "#dc4e58";
|
||||||
@ -49,9 +87,9 @@ function deleteAllCookies() {
|
|||||||
|
|
||||||
function cssrules() {
|
function cssrules() {
|
||||||
var rules = {};
|
var rules = {};
|
||||||
for (var i=0; i<document.styleSheets.length; ++i) {
|
for (var i = 0; i < document.styleSheets.length; ++i) {
|
||||||
var cssRules = document.styleSheets[i].cssRules;
|
var cssRules = document.styleSheets[i].cssRules;
|
||||||
for (var j=0; j<cssRules.length; ++j)
|
for (var j = 0; j < cssRules.length; ++j)
|
||||||
rules[cssRules[j].selectorText] = cssRules[j];
|
rules[cssRules[j].selectorText] = cssRules[j];
|
||||||
}
|
}
|
||||||
return rules;
|
return rules;
|
||||||
@ -65,7 +103,7 @@ function css_getclass(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateHostCounts() {
|
function updateHostCounts() {
|
||||||
$.get("/api/host_counts", (hc) => {
|
msgPackGet("/api/host_counts", (hc) => {
|
||||||
$("#shapedCount").text(hc[0]);
|
$("#shapedCount").text(hc[0]);
|
||||||
$("#unshapedCount").text(hc[1]);
|
$("#unshapedCount").text(hc[1]);
|
||||||
setTimeout(updateHostCounts, 5000);
|
setTimeout(updateHostCounts, 5000);
|
||||||
@ -80,7 +118,7 @@ function updateHostCounts() {
|
|||||||
$("#currentLogin").html(html);
|
$("#currentLogin").html(html);
|
||||||
});
|
});
|
||||||
$("#startTest").on('click', () => {
|
$("#startTest").on('click', () => {
|
||||||
$.get("/api/run_btest", () => {});
|
$.get("/api/run_btest", () => { });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +126,7 @@ function colorReloadButton() {
|
|||||||
$("body").append(reloadModal);
|
$("body").append(reloadModal);
|
||||||
$("#btnReload").on('click', () => {
|
$("#btnReload").on('click', () => {
|
||||||
$.get("/api/reload_libreqos", (result) => {
|
$.get("/api/reload_libreqos", (result) => {
|
||||||
const myModal = new bootstrap.Modal(document.getElementById('reloadModal'), {focus: true});
|
const myModal = new bootstrap.Modal(document.getElementById('reloadModal'), { focus: true });
|
||||||
$("#reloadLibreResult").text(result);
|
$("#reloadLibreResult").text(result);
|
||||||
myModal.show();
|
myModal.show();
|
||||||
});
|
});
|
||||||
@ -150,7 +188,7 @@ function redactText(text) {
|
|||||||
if (!isRedacted()) return text;
|
if (!isRedacted()) return text;
|
||||||
let redacted = "";
|
let redacted = "";
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for(let i = 0; i < text.length; i++){
|
for (let i = 0; i < text.length; i++) {
|
||||||
let code = text.charCodeAt(i);
|
let code = text.charCodeAt(i);
|
||||||
sum += code;
|
sum += code;
|
||||||
}
|
}
|
||||||
@ -160,13 +198,13 @@ function redactText(text) {
|
|||||||
|
|
||||||
function scaleNumber(n) {
|
function scaleNumber(n) {
|
||||||
if (n > 1000000000000) {
|
if (n > 1000000000000) {
|
||||||
return (n/1000000000000).toFixed(2) + "T";
|
return (n / 1000000000000).toFixed(2) + "T";
|
||||||
} else if (n > 1000000000) {
|
} else if (n > 1000000000) {
|
||||||
return (n/1000000000).toFixed(2) + "G";
|
return (n / 1000000000).toFixed(2) + "G";
|
||||||
} else if (n > 1000000) {
|
} else if (n > 1000000) {
|
||||||
return (n/1000000).toFixed(2) + "M";
|
return (n / 1000000).toFixed(2) + "M";
|
||||||
} else if (n > 1000) {
|
} else if (n > 1000) {
|
||||||
return (n/1000).toFixed(2) + "K";
|
return (n / 1000).toFixed(2) + "K";
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@ -189,3 +227,145 @@ const reloadModal = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
// MultiRingBuffer provides an interface for storing multiple ring-buffers
|
||||||
|
// of performance data, with a view to them ending up on the same graph.
|
||||||
|
class MultiRingBuffer {
|
||||||
|
constructor(capacity) {
|
||||||
|
this.capacity = capacity;
|
||||||
|
this.data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
push(id, download, upload) {
|
||||||
|
if (!this.data.hasOwnProperty(id)) {
|
||||||
|
this.data[id] = new RingBuffer(this.capacity);
|
||||||
|
}
|
||||||
|
this.data[id].push(download, upload);
|
||||||
|
}
|
||||||
|
|
||||||
|
plotStackedBars(target_div, rootName) {
|
||||||
|
let graphData = [];
|
||||||
|
for (const [k, v] of Object.entries(this.data)) {
|
||||||
|
if (k != rootName) {
|
||||||
|
let y = v.sortedY;
|
||||||
|
let dn = { x: v.x_axis, y: y.down, name: k + "_DL", type: 'scatter', stackgroup: 'dn' };
|
||||||
|
let up = { x: v.x_axis, y: y.up, name: k + "_UL", type: 'scatter', stackgroup: 'up' };
|
||||||
|
graphData.push(dn);
|
||||||
|
graphData.push(up);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let graph = document.getElementById(target_div);
|
||||||
|
Plotly.newPlot(
|
||||||
|
graph,
|
||||||
|
graphData,
|
||||||
|
{
|
||||||
|
margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 },
|
||||||
|
yaxis: { automargin: true },
|
||||||
|
xaxis: { automargin: true, title: "Time since now (seconds)" },
|
||||||
|
showlegend: false,
|
||||||
|
},
|
||||||
|
{ responsive: true, displayModeBar: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
plotTotalThroughput(target_div) {
|
||||||
|
let graph = document.getElementById(target_div);
|
||||||
|
|
||||||
|
let total = this.data['total'].sortedY();
|
||||||
|
let shaped = this.data['shaped'].sortedY();
|
||||||
|
|
||||||
|
let x = this.data['total'].x_axis;
|
||||||
|
|
||||||
|
let data = [
|
||||||
|
{x: x, y:total.down, name: 'Download', type: 'scatter', marker: {color: 'rgb(255,160,122)'}},
|
||||||
|
{x: x, y:total.up, name: 'Upload', type: 'scatter', marker: {color: 'rgb(255,160,122)'}},
|
||||||
|
{x: x, y:shaped.down, name: 'Shaped Download', type: 'scatter', fill: 'tozeroy', marker: {color: 'rgb(124,252,0)'}},
|
||||||
|
{x: x, y:shaped.up, name: 'Shaped Upload', type: 'scatter', fill: 'tozeroy', marker: {color: 'rgb(124,252,0)'}},
|
||||||
|
];
|
||||||
|
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true, title: "Time since now (seconds)"} }, { responsive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RingBuffer {
|
||||||
|
constructor(capacity) {
|
||||||
|
this.capacity = capacity;
|
||||||
|
this.head = capacity - 1;
|
||||||
|
this.download = [];
|
||||||
|
this.upload = [];
|
||||||
|
this.x_axis = [];
|
||||||
|
for (var i = 0; i < capacity; ++i) {
|
||||||
|
this.download.push(0.0);
|
||||||
|
this.upload.push(0.0);
|
||||||
|
this.x_axis.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push(download, upload) {
|
||||||
|
this.download[this.head] = download;
|
||||||
|
this.upload[this.head] = 0.0 - upload;
|
||||||
|
this.head += 1;
|
||||||
|
this.head %= this.capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedY() {
|
||||||
|
let result = {
|
||||||
|
down: [],
|
||||||
|
up: [],
|
||||||
|
};
|
||||||
|
for (let i=this.head; i<this.capacity; i++) {
|
||||||
|
result.down.push(this.download[i]);
|
||||||
|
result.up.push(this.upload[i]);
|
||||||
|
}
|
||||||
|
for (let i=0; i < this.head; i++) {
|
||||||
|
result.down.push(this.download[i]);
|
||||||
|
result.up.push(this.upload[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
toScatterGraphData() {
|
||||||
|
let y = this.sortedY();
|
||||||
|
let GraphData = [
|
||||||
|
{ x: this.x_axis, y: y.down, name: 'Download', type: 'scatter' },
|
||||||
|
{ x: this.x_axis, y: y.up, name: 'Upload', type: 'scatter' },
|
||||||
|
];
|
||||||
|
return GraphData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RttHistogram {
|
||||||
|
constructor() {
|
||||||
|
this.entries = []
|
||||||
|
this.x = [];
|
||||||
|
for (let i = 0; i < 20; ++i) {
|
||||||
|
this.entries.push(i);
|
||||||
|
this.x.push(i * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
for (let i = 0; i < 20; ++i) {
|
||||||
|
this.entries[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push(rtt) {
|
||||||
|
let band = Math.floor(rtt / 10.0);
|
||||||
|
if (band > 19) {
|
||||||
|
band = 19;
|
||||||
|
}
|
||||||
|
this.entries[band] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushBand(band, n) {
|
||||||
|
this.entries[band] += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
plot(target_div) {
|
||||||
|
let gData = [
|
||||||
|
{ x: this.x, y: this.entries, type: 'bar', marker: { color: this.x, colorscale: 'RdBu' } }
|
||||||
|
]
|
||||||
|
let graph = document.getElementById(target_div);
|
||||||
|
Plotly.newPlot(graph, gData, { margin: { l: 0, r: 0, b: 35, t: 0 }, xaxis: { title: 'TCP Round-Trip Time (ms)' } }, { responsive: true });
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@ -9,15 +10,19 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/"><img src="/vendor/tinylogo.svg" alt="LibreQoS SVG Logo" width="25" height="25" /> LibreQoS</a>
|
<a class="navbar-brand" href="/"><img src="/vendor/tinylogo.svg" alt="LibreQoS SVG Logo" width="25"
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
height="25" /> LibreQoS</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
@ -25,28 +30,32 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" aria-current="page" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
<a class="nav-link active" aria-current="page" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" id="currentLogin"></li>
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Network Layout</a>
|
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Tree</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span
|
||||||
|
id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/unknown"><i class="fa fa-address-card"></i> Unknown IPs <span id="unshapedCount" class="badge badge-warning orange-badge">?</span></a>
|
<a class="nav-link" href="/unknown"><i class="fa fa-address-card"></i> Unknown IPs <span
|
||||||
|
id="unshapedCount" class="badge badge-warning orange-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item" id="currentLogin"></li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
|
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth
|
||||||
|
Test</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item ms-auto">
|
<li class="nav-item ms-auto">
|
||||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload LibreQoS</a>
|
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload
|
||||||
|
LibreQoS</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -54,227 +63,200 @@
|
|||||||
|
|
||||||
<div id="container" class="pad4">
|
<div id="container" class="pad4">
|
||||||
|
|
||||||
<!-- Dashboard Row 1 -->
|
<!-- Dashboard Row 1 -->
|
||||||
<div class="row mbot8">
|
<div class="row mbot8">
|
||||||
<!-- THROUGHPUT -->
|
<!-- THROUGHPUT -->
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="card bg-light">
|
<div class="card bg-light">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title"><i class="fa fa-bolt"></i> Current Throughput</h5>
|
<h5 class="card-title"><i class="fa fa-bolt"></i> Current Throughput</h5>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="bold">Packets/Second</td>
|
<td class="bold">Packets/Second</td>
|
||||||
<td id="ppsDown"></td>
|
<td id="ppsDown"></td>
|
||||||
<td id="ppsUp"></td>
|
<td id="ppsUp"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="bold">Bits/Second</td>
|
<td class="bold">Bits/Second</td>
|
||||||
<td id="bpsDown"></td>
|
<td id="bpsDown"></td>
|
||||||
<td id="bpsUp"></td>
|
<td id="bpsUp"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RAM INFO -->
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<div class="card bg-light d-none d-lg-block">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-database"></i> Memory Status</h5>
|
||||||
|
<div id="ram" class="graph98"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CPU INFO -->
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-microchip"></i> CPU Status</h5>
|
||||||
|
<div id="cpu" class="graph98"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RAM INFO -->
|
<!-- Dashboard Row 2 -->
|
||||||
<div class="col-sm-2">
|
<div class="row mbot8 row220">
|
||||||
<div class="card bg-light d-none d-lg-block">
|
<!-- 5 minutes of throughput -->
|
||||||
<div class="card-body">
|
<div class="col-sm-4">
|
||||||
<h5 class="card-title"><i class="fa fa-database"></i> Memory Status</h5>
|
<div class="card bg-light">
|
||||||
<div id="ram" class="graph98"></div>
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-hourglass"></i> Last 5 Minutes</h5>
|
||||||
|
<div id="tpGraph" class="graph98 graph150"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RTT Histogram -->
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-bar-chart"></i> TCP Round-Trip Time Histogram</h5>
|
||||||
|
<div id="rttHistogram" class="graph98 graph150"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Site Funnel -->
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-globe"></i> Site Funnel</h5>
|
||||||
|
<div id="siteFunnel" class="graph98 graph150"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CPU INFO -->
|
<!-- Dashboard Row 3 -->
|
||||||
<div class="col-sm-6">
|
<div class="row">
|
||||||
<div class="card bg-light">
|
<!-- Top 10 downloaders -->
|
||||||
<div class="card-body">
|
<div class="col-sm-6">
|
||||||
<h5 class="card-title"><i class="fa fa-microchip"></i> CPU Status</h5>
|
<div class="card bg-light">
|
||||||
<div id="cpu" class="graph98"></div>
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class='fa fa-arrow-down'></i> Top 10 Downloaders</h5>
|
||||||
|
<div id="top10dl"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dashboard Row 2 -->
|
<!-- Worst 10 RTT -->
|
||||||
<div class="row mbot8 row220">
|
<div class="col-sm-6">
|
||||||
<!-- 5 minutes of throughput -->
|
<div class="card bg-light">
|
||||||
<div class="col-sm-4">
|
<div class="card-body">
|
||||||
<div class="card bg-light">
|
<h5 class="card-title"><i class='fa fa-exclamation'></i> Worst 10 RTT</h5>
|
||||||
<div class="card-body">
|
<div id="worstRtt"></div>
|
||||||
<h5 class="card-title"><i class="fa fa-hourglass"></i> Last 5 Minutes</h5>
|
</div>
|
||||||
<div id="tpGraph" class="graph98 graph150"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RTT Histogram -->
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="card bg-light">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title"><i class="fa fa-bar-chart"></i> TCP Round-Trip Time Histogram</h5>
|
|
||||||
<div id="rttHistogram" class="graph98 graph150"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Idle/Activity Quantiles -->
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="card bg-light">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title"><i class="fa fa-bar-chart"></i> Utilization Quantiles</h5>
|
|
||||||
<div id="capacityHistogram" class="graph98 graph150"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dashboard Row 3 -->
|
|
||||||
<div class="row">
|
|
||||||
<!-- Top 10 downloaders -->
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="card bg-light">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title"><i class='fa fa-arrow-down'></i> Top 10 Downloaders</h5>
|
|
||||||
<div id="top10dl"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Worst 10 RTT -->
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="card bg-light">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title"><i class='fa fa-exclamation'></i> Worst 10 RTT</h5>
|
|
||||||
<div id="worstRtt"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>© 2022-2023, LibreQoE LLC</footer>
|
<footer>© 2022-2023, LibreQoE LLC</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
var throughput = new MultiRingBuffer(300);
|
||||||
|
|
||||||
function updateCurrentThroughput() {
|
function updateCurrentThroughput() {
|
||||||
$.get("/api/current_throughput", (tp) => {
|
msgPackGet("/api/current_throughput", (tp) => {
|
||||||
$("#ppsDown").text(scaleNumber(tp.packets_per_second[0]));
|
const bits = 0;
|
||||||
$("#ppsUp").text(scaleNumber(tp.packets_per_second[1]));
|
const packets = 1;
|
||||||
$("#bpsDown").text(scaleNumber(tp.bits_per_second[0]));
|
const shaped = 2;
|
||||||
$("#bpsUp").text(scaleNumber(tp.bits_per_second[1]));
|
$("#ppsDown").text(scaleNumber(tp[packets][0]));
|
||||||
setTimeout(updateCurrentThroughput, 1000);
|
$("#ppsUp").text(scaleNumber(tp[packets][1]));
|
||||||
|
$("#bpsDown").text(scaleNumber(tp[bits][0]));
|
||||||
|
$("#bpsUp").text(scaleNumber(tp[bits][1]));
|
||||||
|
|
||||||
|
throughput.push("pps", tp[1][0], tp[packets][1]);
|
||||||
|
throughput.push("total", tp[bits][0], tp[bits][1]);
|
||||||
|
throughput.push("shaped", tp[shaped][0], tp[shaped][1]);
|
||||||
|
throughput.plotTotalThroughput("tpGraph");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateThroughputGraph() {
|
var funnelData = new MultiRingBuffer(300);
|
||||||
$.get("/api/throughput_ring", (tp) => {
|
|
||||||
let graph = document.getElementById("tpGraph");
|
|
||||||
let x = [];
|
|
||||||
let y = []; // Down
|
|
||||||
let y2 = []; // Up
|
|
||||||
let y3 = []; // Shaped Down
|
|
||||||
let y4 = []; // Shaped Up
|
|
||||||
for (i=0; i<300; i++) {
|
|
||||||
x.push(i);
|
|
||||||
y.push(tp[i].bits_per_second[0]);
|
|
||||||
y2.push(0.0 - tp[i].bits_per_second[1]);
|
|
||||||
y3.push(tp[i].shaped_bits_per_second[0]);
|
|
||||||
y4.push(0.0 - tp[i].shaped_bits_per_second[1]);
|
|
||||||
}
|
|
||||||
let data = [
|
|
||||||
{x: x, y:y, name: 'Download', type: 'scatter'},
|
|
||||||
{x: x, y:y2, name: 'Upload', type: 'scatter'},
|
|
||||||
{x: x, y:y3, name: 'Shaped Download', type: 'scatter', fill: 'tozeroy'},
|
|
||||||
{x: x, y:y4, name: 'Shaped Upload', type: 'scatter', fill: 'tozeroy'},
|
|
||||||
];
|
|
||||||
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true, title: "Time since now (seconds)"} }, { responsive: true });
|
|
||||||
//console.log(tp);
|
|
||||||
setTimeout(updateThroughputGraph, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateThroughputQuantile() {
|
function updateSiteFunnel() {
|
||||||
$.get("/api/busy_quantile", (tp) => {
|
msgPackGet("/api/network_tree_summary/", (data) => {
|
||||||
//console.log(tp);
|
let table = "<table class='table' style='font-size: 8pt;'>";
|
||||||
let graph = document.getElementById("capacityHistogram");
|
for (let i = 0; i < data.length; ++i) {
|
||||||
let x1 = [];
|
let name = data[i][1][NetTrans.name];
|
||||||
let x2 = [];
|
if (name.length > 20) {
|
||||||
let y1 = [];
|
name = name.substring(0, 20) + "...";
|
||||||
let y2 = [];
|
|
||||||
for (let i=0; i<10; i++) {
|
|
||||||
x1.push(i*10);
|
|
||||||
x2.push(i*10);
|
|
||||||
if (i > 0) {
|
|
||||||
y1.push(tp[i][0]);
|
|
||||||
y2.push(tp[i][1]);
|
|
||||||
} else {
|
|
||||||
y1.push(0);
|
|
||||||
y2.push(0);
|
|
||||||
}
|
}
|
||||||
|
table += "<tr>";
|
||||||
|
table += "<td class='redact'>" + redactText(name) + "</td>";
|
||||||
|
table += "<td>" + scaleNumber(data[i][1][NetTrans.current_throughput][0] * 8) + "</td>";
|
||||||
|
table += "<td>" + scaleNumber(data[i][1][NetTrans.current_throughput][1] * 8) + "</td>";
|
||||||
|
table += "</tr>";
|
||||||
}
|
}
|
||||||
let data = [
|
table += "</table>";
|
||||||
{x: x1, y:y1, type: 'bar', name: 'Download'},
|
$("#siteFunnel").html(table);
|
||||||
{x: x1, y:y2, type: 'bar', name: 'Upload'},
|
|
||||||
];
|
|
||||||
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: '# Samples' }, xaxis: {automargin: true, title: "% utilization"} }, { responsive: true });
|
|
||||||
setTimeout(updateThroughputQuantile, 1000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCpu() {
|
function updateCpu() {
|
||||||
$.get("/api/cpu", (cpu) => {
|
msgPackGet("/api/cpu", (cpu) => {
|
||||||
let graph = document.getElementById("cpu");
|
let graph = document.getElementById("cpu");
|
||||||
let x = [];
|
let x = [];
|
||||||
let y = [];
|
let y = [];
|
||||||
let colors = [];
|
let colors = [];
|
||||||
for (i=0; i<cpu.length; i++) {
|
for (i = 0; i < cpu.length; i++) {
|
||||||
x.push(i);
|
x.push(i);
|
||||||
y.push(cpu[i]);
|
y.push(cpu[i]);
|
||||||
colors.push(cpu[i]);
|
colors.push(cpu[i]);
|
||||||
}
|
}
|
||||||
colors.push(100); // 1 extra colors entry to force color scaling
|
colors.push(100); // 1 extra colors entry to force color scaling
|
||||||
let data = [ {x: x, y:y, type: 'bar', marker: { color:colors, colorscale: 'Jet' } } ];
|
let data = [{ x: x, y: y, type: 'bar', marker: { color: colors, colorscale: 'Jet' } }];
|
||||||
Plotly.newPlot(graph, data, {
|
Plotly.newPlot(graph, data, {
|
||||||
margin: { l:0,r:0,b:15,t:0 },
|
margin: { l: 0, r: 0, b: 15, t: 0 },
|
||||||
yaxis: { automargin: true, autorange: false, range: [0.0, 100.0 ]},
|
yaxis: { automargin: true, autorange: false, range: [0.0, 100.0] },
|
||||||
},
|
},
|
||||||
{ responsive: true });
|
{ responsive: true });
|
||||||
setTimeout(updateCpu, 2000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRam() {
|
function updateRam() {
|
||||||
$.get("/api/ram", (ram) => {
|
msgPackGet("/api/ram", (ram) => {
|
||||||
let graph = document.getElementById("ram");
|
let graph = document.getElementById("ram");
|
||||||
let data = [ {
|
let data = [{
|
||||||
values: [ram[0], ram[1]-ram[0]],
|
values: [ram[0], ram[1] - ram[0]],
|
||||||
labels: ['Used', 'Available'],
|
labels: ['Used', 'Available'],
|
||||||
type: 'pie'
|
type: 'pie'
|
||||||
} ];
|
}];
|
||||||
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:12 }, showlegend: false }, { responsive: true });
|
Plotly.newPlot(graph, data, { margin: { l: 0, r: 0, b: 0, t: 12 }, showlegend: false }, { responsive: true });
|
||||||
setTimeout(updateRam, 30000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNTable(target, tt) {
|
function updateNTable(target, tt) {
|
||||||
let html = "<table class='table'>";
|
let html = "<table class='table'>";
|
||||||
html += "<thead><th>IP Address</th><th>DL ⬇️</th><th>UL ⬆️</th><th>RTT (ms)</th><th>Shaped</th></thead>";
|
html += "<thead><th>IP Address</th><th>DL ⬇️</th><th>UL ⬆️</th><th>RTT (ms)</th><th>Shaped</th></thead>";
|
||||||
for (let i=0; i<tt.length; i++) {
|
for (let i = 0; i < tt.length; i++) {
|
||||||
let color = color_ramp(tt[i].median_tcp_rtt);
|
let color = color_ramp(tt[i][IpStats.median_tcp_rtt]);
|
||||||
html += "<tr style='background-color: " + color + "'>";
|
html += "<tr style='background-color: " + color + "'>";
|
||||||
if (tt[i].circuit_id != "") {
|
if (tt[i][IpStats.circuit_id] != "") {
|
||||||
html += "<td><a class='redact' href='/circuit_queue?id=" + encodeURI(tt[i].circuit_id) + "'>" + redactText(tt[i].ip_address) + "</td>";
|
html += "<td><a class='redact' href='/circuit_queue?id=" + encodeURI(tt[i][IpStats.circuit_id]) + "'>" + redactText(tt[i][IpStats.ip_address]) + "</td>";
|
||||||
} else {
|
} else {
|
||||||
html += "<td><span class='redact'>" + redactText(tt[i].ip_address) + "</span></td>";
|
html += "<td><span class='redact'>" + redactText(tt[i][IpStats.ip_address]) + "</span></td>";
|
||||||
}
|
}
|
||||||
html += "<td>" + scaleNumber(tt[i].bits_per_second[0]) + "</td>";
|
html += "<td>" + scaleNumber(tt[i][IpStats.bits_per_second][0]) + "</td>";
|
||||||
html += "<td>" + scaleNumber(tt[i].bits_per_second[1]) + "</td>";
|
html += "<td>" + scaleNumber(tt[i][IpStats.bits_per_second][1]) + "</td>";
|
||||||
html += "<td>" + tt[i].median_tcp_rtt.toFixed(2) + "</td>";
|
html += "<td>" + tt[i][IpStats.median_tcp_rtt].toFixed(2) + "</td>";
|
||||||
if (tt[i].tc_handle !=0) {
|
if (tt[i].tc_handle != 0) {
|
||||||
html += "<td><i class='fa fa-check-circle'></i> (" + tt[i].plan[0] + "/" + tt[i].plan[1] + ")</td>";
|
html += "<td><i class='fa fa-check-circle'></i> (" + tt[i][IpStats.plan][0] + "/" + tt[i][IpStats.plan][1] + ")</td>";
|
||||||
} else {
|
} else {
|
||||||
//html += "<td><a class='btn btn-small btn-success' href='/shaped-add?ip=" + tt[i].ip_address + "'>Add Shaper</a></td>";
|
//html += "<td><a class='btn btn-small btn-success' href='/shaped-add?ip=" + tt[i].ip_address + "'>Add Shaper</a></td>";
|
||||||
html += "<td>Not Shaped</td>"
|
html += "<td>Not Shaped</td>"
|
||||||
@ -286,57 +268,72 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateTop10() {
|
function updateTop10() {
|
||||||
$.get("/api/top_10_downloaders", (tt) => {
|
msgPackGet("/api/top_10_downloaders", (tt) => {
|
||||||
updateNTable('#top10dl', tt);
|
updateNTable('#top10dl', tt);
|
||||||
setTimeout(updateTop10, 5000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWorst10() {
|
function updateWorst10() {
|
||||||
$.get("/api/worst_10_rtt", (tt) => {
|
msgPackGet("/api/worst_10_rtt", (tt) => {
|
||||||
updateNTable('#worstRtt', tt);
|
updateNTable('#worstRtt', tt);
|
||||||
setTimeout(updateWorst10, 5000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rttGraph = new RttHistogram();
|
||||||
|
|
||||||
function updateHistogram() {
|
function updateHistogram() {
|
||||||
$.get("/api/rtt_histogram", (rtt) => {
|
msgPackGet("/api/rtt_histogram", (rtt) => {
|
||||||
let graph = document.getElementById("rttHistogram");
|
rttGraph.clear();
|
||||||
let x = [];
|
for (let i = 0; i < rtt.length; i++) {
|
||||||
let y = [];
|
rttGraph.pushBand(i, rtt[i]);
|
||||||
for (let i=0; i<rtt.length; i++) {
|
|
||||||
x.push(i*10.0);
|
|
||||||
y.push(rtt[i]);
|
|
||||||
}
|
}
|
||||||
let data = [
|
rttGraph.plot("rttHistogram");
|
||||||
{x:x, y:y, type: 'bar', marker: { color:x, colorscale: 'RdBu' } }
|
|
||||||
]
|
|
||||||
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:35,t:0 }, xaxis: { title: 'TCP Round-Trip Time (ms)' } }, { responsive: true });
|
|
||||||
setTimeout(updateHistogram, 5000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tickCount = 0;
|
||||||
|
|
||||||
|
function OneSecondCadence() {
|
||||||
|
updateCurrentThroughput();
|
||||||
|
updateSiteFunnel();
|
||||||
|
|
||||||
|
if (tickCount % 5 == 0) {
|
||||||
|
updateHistogram();
|
||||||
|
updateWorst10();
|
||||||
|
updateTop10();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tickCount % 10 == 0) {
|
||||||
|
updateCpu();
|
||||||
|
updateRam();
|
||||||
|
}
|
||||||
|
|
||||||
|
tickCount++;
|
||||||
|
setTimeout(OneSecondCadence, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
if (isRedacted()) {
|
if (isRedacted()) {
|
||||||
console.log("Redacting");
|
//console.log("Redacting");
|
||||||
//css_getclass(".redact").style.filter = "blur(4px)";
|
//css_getclass(".redact").style.filter = "blur(4px)";
|
||||||
css_getclass(".redact").style.fontFamily = "klingon";
|
css_getclass(".redact").style.fontFamily = "klingon";
|
||||||
}
|
}
|
||||||
|
|
||||||
colorReloadButton();
|
colorReloadButton();
|
||||||
updateCurrentThroughput();
|
updateCurrentThroughput();
|
||||||
updateThroughputGraph();
|
|
||||||
updateCpu();
|
updateCpu();
|
||||||
updateRam();
|
updateRam();
|
||||||
updateTop10();
|
updateTop10();
|
||||||
updateWorst10();
|
updateWorst10();
|
||||||
updateHistogram();
|
updateHistogram();
|
||||||
updateHostCounts();
|
updateHostCounts();
|
||||||
updateThroughputQuantile();
|
updateSiteFunnel();
|
||||||
|
OneSecondCadence();
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(start);
|
$(document).ready(start);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -9,7 +9,7 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
@ -25,10 +25,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" id="currentLogin"></li>
|
<li class="nav-item">
|
||||||
<!--<li class="nav-item">
|
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Tree</a>
|
||||||
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</a>
|
</li>
|
||||||
</li>-->
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
<a class="nav-link active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
@ -39,6 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item" id="currentLogin"></li>
|
||||||
<li class="nav-item ms-auto">
|
<li class="nav-item ms-auto">
|
||||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
@ -25,10 +25,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" id="currentLogin"></li>
|
<li class="nav-item">
|
||||||
<!--<li class="nav-item">
|
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Tree</a>
|
||||||
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</a>
|
</li>
|
||||||
</li>-->
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
<a class="nav-link active" aria-current="page" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
@ -39,6 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item" id="currentLogin"></li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
|
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@ -9,44 +10,52 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/"><img src="/vendor/tinylogo.svg" alt="LibreQoS SVG Logo" width="25" height="25" /> LibreQoS</a>
|
<a class="navbar-brand" href="/"><img src="/vendor/tinylogo.svg" alt="LibreQoS SVG Logo" width="25"
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
height="25" /> LibreQoS</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" aria-current="page" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
<a class="nav-link" aria-current="page" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
||||||
</li>
|
|
||||||
<li class="nav-item" id="currentLogin"></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Network Layout</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
<a class="nav-link active" href="/tree?parent=0"><i class="fa fa-globe"></i> Tree</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/unknown"><i class="fa fa-address-card"></i> Unknown IPs <span id="unshapedCount" class="badge badge-warning orange-badge">?</span></a>
|
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span
|
||||||
|
id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/unknown"><i class="fa fa-address-card"></i> Unknown IPs <span
|
||||||
|
id="unshapedCount" class="badge badge-warning orange-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item" id="currentLogin"></li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth Test</a>
|
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth
|
||||||
|
Test</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item ms-auto">
|
<li class="nav-item ms-auto">
|
||||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload LibreQoS</a>
|
<a class="nav-link btn btn-small" href="#" id="btnReload"><i class="fa fa-refresh"></i> Reload
|
||||||
|
LibreQoS</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -54,76 +63,232 @@
|
|||||||
|
|
||||||
<div id="container" class="pad4">
|
<div id="container" class="pad4">
|
||||||
|
|
||||||
<div class="row top-shunt">
|
<div class="row mbot8 row220">
|
||||||
<div class="col-sm-12 bg-light center-txt">
|
<!-- 5 minutes of throughput -->
|
||||||
THIS NODE
|
<!--
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-hourglass"></i> Last 5 Minutes</h5>
|
||||||
|
<div id="tpGraph" class="graph98 graph150"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- RTT Histogram -->
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-bar-chart"></i> TCP Round-Trip Time Histogram</h5>
|
||||||
|
<div id="rttHistogram" class="graph98 graph150"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info -->
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-globe"></i> <span id="nodeName"
|
||||||
|
style="font-weight: bold;" class='redact'></span></h5>
|
||||||
|
<strong>DL Limit</strong>: <span id="nodeDL"></span><br />
|
||||||
|
<strong>UL Limit</strong>: <span id="nodeUL"></span><br />
|
||||||
|
<div id="breadcrumbs"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="margin-top: 4px;">
|
||||||
|
<!-- List of network circuits -->
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-globe"></i> Child Nodes</h5>
|
||||||
|
<div id="treeList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List of client circuits -->
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="fa fa-users"></i> Attached Clients</h5>
|
||||||
|
<div id="clientList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 bg-light center-txt">
|
|
||||||
<div id="treeList"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<footer>© 2022-2023, LibreQoE LLC</footer>
|
||||||
|
|
||||||
<footer>© 2022-2023, LibreQoE LLC</footer>
|
<script>
|
||||||
|
let node = 0;
|
||||||
|
let buffers = new MultiRingBuffer(300);
|
||||||
|
let rtt_histo = new RttHistogram();
|
||||||
|
|
||||||
<script>
|
function bgColor(traffic, limit) {
|
||||||
let node = 0;
|
if (limit == 0) {
|
||||||
|
return "#ddffdd";
|
||||||
function getTree() {
|
|
||||||
$.get("/api/network_tree/" + node, (data) => {
|
|
||||||
//console.log(data);
|
|
||||||
|
|
||||||
let tbl = "<table class='table'>";
|
|
||||||
tbl += "<thead><th>Circuit</th><th>Limit</th><th>Download</th><th>Upload</th><th>RTT Latency</th></thead>";
|
|
||||||
for (let i=1; i<data.length; ++i) {
|
|
||||||
tbl += "<tr>";
|
|
||||||
tbl += "<td style='width: 20%'><a href='/tree?parent=" + encodeURI(data[i][0]) + "'>" + data[i][1].name + "</a></td>";
|
|
||||||
if (data[i][1].max_throughput[0]==0 && data[i][1].max_throughput[1] == 0) {
|
|
||||||
tbl += "<td>No Limit</td>";
|
|
||||||
} else {
|
|
||||||
let down = scaleNumber(data[i][1].max_throughput[0] * 1000000);
|
|
||||||
let up = scaleNumber(data[i][1].max_throughput[1] * 1000000);
|
|
||||||
tbl += "<td>" + down + " / " + up + "</td>";
|
|
||||||
}
|
|
||||||
let down = scaleNumber(data[i][1].current_throughput[0] * 8);
|
|
||||||
let up = scaleNumber(data[i][1].current_throughput[1] * 8);
|
|
||||||
tbl += "<td>" + down + "</td>";
|
|
||||||
tbl += "<td>" + up + "</td>";
|
|
||||||
let rtt = "-";
|
|
||||||
if (data[i][1].rtts.length > 0) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let j=0; j<data[i][1].rtts.length; ++j) {
|
|
||||||
sum += data[i][1].rtts[j];
|
|
||||||
}
|
|
||||||
sum /= data[i][1].rtts.length;
|
|
||||||
rtt = sum.toFixed(2) + " ms";
|
|
||||||
}
|
|
||||||
tbl += "<td>" + rtt + "</td>";
|
|
||||||
tbl += "</tr>";
|
|
||||||
}
|
}
|
||||||
tbl += "</table>";
|
let usage = (traffic * 8) / (limit * 1000000);
|
||||||
$("#treeList").html(tbl);
|
if (usage < 0.25) { return "#ddffdd" }
|
||||||
|
else if (usage < 0.5) { return "#aaffaa" }
|
||||||
|
else if (usage < 0.75) { return "#ffa500" }
|
||||||
|
else { return "#ffdddd" }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClients(rootName) {
|
||||||
|
msgPackGet("/api/tree_clients/" + encodeURI(rootName), (data) => {
|
||||||
|
let tbl = "<table class='table table-striped'>";
|
||||||
|
tbl += "<thead><th>Circuit</th><th>Limit</th><th>⬇️ DL</th><th>⬆️ UL</th></thead>";
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; ++i) {
|
||||||
|
let nodeDL = scaleNumber(data[i][Circuit.limit][0] * 1000000);
|
||||||
|
let nodeUL = scaleNumber(data[i][Circuit.limit[1]] * 1000000);
|
||||||
|
if (nodeDL == "0") nodeDL = "Unlimited";
|
||||||
|
if (nodeUL == "0") nodeUL = "Unlimited";
|
||||||
|
tbl += "<tr>";
|
||||||
|
let displayName = data[i][Circuit.name];
|
||||||
|
if (displayName.length > 30) displayName = displayName.substring(0, 30) + "...";
|
||||||
|
tbl += "<td class='redact'><a href='/circuit_queue?id=" + encodeURI(data[i][Circuit.id]) + "'>" + redactText(displayName) + "</a></td>";
|
||||||
|
tbl += "<td>" + nodeDL + " / " + nodeUL + "</td>";
|
||||||
|
let upbg = bgColor(data[i][Circuit.traffic][1], data[i][Circuit.limit][1]);
|
||||||
|
let dnbg = bgColor(data[i][Circuit.traffic][0], data[0][Circuit.limit][1]);
|
||||||
|
tbl += "<td style='background-color: " + dnbg + "'>" + scaleNumber(data[i][Circuit.traffic][0] * 8) + "</td>";
|
||||||
|
tbl += "<td style='background-color: " + upbg + "'>" + scaleNumber(data[i][Circuit.traffic][1] * 8) + "</td>";
|
||||||
|
|
||||||
|
buffers.push(nodeName, data[i][Circuit.traffic][0] * 8, data[i][Circuit.traffic][1] * 8);
|
||||||
|
}
|
||||||
|
tbl += "</table>";
|
||||||
|
$("#clientList").html(tbl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let filled_root = false;
|
||||||
|
|
||||||
|
function getTree() {
|
||||||
|
msgPackGet("/api/network_tree/" + node, (data) => {
|
||||||
|
rtt_histo.clear();
|
||||||
|
//console.log(data);
|
||||||
|
// Setup "this node"
|
||||||
|
let rootName = data[0][1][NetTrans.name];
|
||||||
|
if (!filled_root) {
|
||||||
|
$("#nodeName").text(redactText(rootName));
|
||||||
|
let nodeDL = scaleNumber(data[0][1][NetTrans.max_throughput][0] * 1000000);
|
||||||
|
let nodeUL = scaleNumber(data[0][1][NetTrans.max_throughput][1] * 1000000);
|
||||||
|
if (nodeDL == "0") nodeDL = "Unlimited";
|
||||||
|
if (nodeUL == "0") nodeUL = "Unlimited";
|
||||||
|
$("#nodeDL").text(nodeDL);
|
||||||
|
$("#nodeUL").text(nodeUL);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "/api/node_names",
|
||||||
|
data: JSON.stringify(data[0][1][NetTrans.parents]),
|
||||||
|
success: (nodeNames) => {
|
||||||
|
let breadcrumbs = "<nav aria-label='breadcrumb'>";
|
||||||
|
breadcrumbs += "<ol class='breadcrumb'>";
|
||||||
|
for (let i=0; i<data[0][1][NetTrans.parents].length; ++i) {
|
||||||
|
let bcid = data[0][1][NetTrans.parents][i];
|
||||||
|
if (bcid != node) {
|
||||||
|
let n = nodeNames.find(e => e[0] == data[0][1][NetTrans.parents][i])[1];
|
||||||
|
breadcrumbs += "<li class='breadcrumb-item redact'>";
|
||||||
|
breadcrumbs += "<a href='/tree?parent=" + data[0][1][NetTrans.parents][i] + "'>";
|
||||||
|
breadcrumbs += redactText(n);
|
||||||
|
breadcrumbs += "</a></li>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
breadcrumbs += "<li class='breadcrumb-item active redact' aria-current='page'>";
|
||||||
|
breadcrumbs += redactText(rootName);
|
||||||
|
breadcrumbs += "</li>";
|
||||||
|
breadcrumbs += "</ol>";
|
||||||
|
breadcrumbs += "</nav>";
|
||||||
|
$("#breadcrumbs").html(breadcrumbs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
filled_root = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getClients(rootName);
|
||||||
|
|
||||||
|
// Throughput graph
|
||||||
|
buffers.push(rootName, data[0][1][NetTrans.current_throughput][0] * 8, data[0][1][NetTrans.current_throughput][1] * 8);
|
||||||
|
|
||||||
|
// Build the table & update node buffers
|
||||||
|
let tbl = "<table class='table table-striped'>";
|
||||||
|
tbl += "<thead><th>Site</th><th>Limit</th><th>⬇️ DL</th><th>⬆️ UL</th><th>RTT Latency</th></thead>";
|
||||||
|
for (let i = 1; i < data.length; ++i) {
|
||||||
|
let nodeName = data[i][1][NetTrans.name];
|
||||||
|
|
||||||
|
buffers.push(nodeName, data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8);
|
||||||
|
|
||||||
|
tbl += "<tr>";
|
||||||
|
tbl += "<td class='redact'><a href='/tree?parent=" + encodeURI(data[i][0]) + "'>" + redactText(nodeName) + "</a></td>";
|
||||||
|
if (data[i][1][NetTrans.max_throughput][0] == 0 && data[i][1][NetTrans.max_throughput][1] == 0) {
|
||||||
|
tbl += "<td>No Limit</td>";
|
||||||
|
} else {
|
||||||
|
let down = scaleNumber(data[i][1][NetTrans.max_throughput][0] * 1000000);
|
||||||
|
let up = scaleNumber(data[i][1][NetTrans.max_throughput][1] * 1000000);
|
||||||
|
tbl += "<td>" + down + " / " + up + "</td>";
|
||||||
|
}
|
||||||
|
let down = scaleNumber(data[i][1][NetTrans.current_throughput][0] * 8);
|
||||||
|
let up = scaleNumber(data[i][1][NetTrans.current_throughput][1] * 8);
|
||||||
|
let dbg = bgColor(data[i][1][NetTrans.current_throughput][0], data[i][1][NetTrans.max_throughput][0]);
|
||||||
|
let ubg = bgColor(data[i][1][NetTrans.current_throughput][0], data[i][1][NetTrans.max_throughput][0]);
|
||||||
|
tbl += "<td style='background-color: " + dbg + "'>" + down + "</td>";
|
||||||
|
tbl += "<td style='background-color: " + ubg + "'>" + up + "</td>";
|
||||||
|
let rtt = "-";
|
||||||
|
if (data[i][1][NetTrans.rtts].length > 0) {
|
||||||
|
let sum = 0;
|
||||||
|
for (let j = 0; j < data[i][1][NetTrans.rtts].length; ++j) {
|
||||||
|
sum += data[i][1][NetTrans.rtts][j];
|
||||||
|
}
|
||||||
|
sum /= data[i][1][NetTrans.rtts].length;
|
||||||
|
rtt = sum.toFixed(2) + " ms";
|
||||||
|
rtt_histo.push(sum);
|
||||||
|
}
|
||||||
|
tbl += "<td>" + rtt + "</td>";
|
||||||
|
tbl += "</tr>";
|
||||||
|
}
|
||||||
|
tbl += "</table>";
|
||||||
|
$("#treeList").html(tbl);
|
||||||
|
|
||||||
|
// Build the stacked chart
|
||||||
|
//buffers.plotStackedBars("tpGraph", rootName);
|
||||||
|
|
||||||
|
// Build the RTT histo
|
||||||
|
rtt_histo.plot("rttHistogram");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isRedacted()) {
|
||||||
|
//console.log("Redacting");
|
||||||
|
//css_getclass(".redact").style.filter = "blur(4px)";
|
||||||
|
css_getclass(".redact").style.fontFamily = "klingon";
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(getTree, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
for (let i = 0; i < 20; ++i) rtt_histo.push(0);
|
||||||
|
colorReloadButton();
|
||||||
|
updateHostCounts();
|
||||||
|
getTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new Proxy(new URLSearchParams(window.location.search), {
|
||||||
|
get: (searchParams, prop) => searchParams.get(prop),
|
||||||
});
|
});
|
||||||
setTimeout(getTree, 1000);
|
node = params.parent;
|
||||||
}
|
|
||||||
|
|
||||||
function start() {
|
$(document).ready(start);
|
||||||
colorReloadButton();
|
</script>
|
||||||
updateHostCounts();
|
|
||||||
getTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new Proxy(new URLSearchParams(window.location.search), {
|
|
||||||
get: (searchParams, prop) => searchParams.get(prop),
|
|
||||||
});
|
|
||||||
node = params.parent;
|
|
||||||
|
|
||||||
$(document).ready(start);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -9,7 +9,7 @@
|
|||||||
<title>LibreQoS - Local Node Manager</title>
|
<title>LibreQoS - Local Node Manager</title>
|
||||||
<script src="/lqos.js"></script>
|
<script src="/lqos.js"></script>
|
||||||
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
<script src="/vendor/plotly-2.16.1.min.js"></script>
|
||||||
<script src="/vendor/jquery.min.js"></script>
|
<script src="/vendor/jquery.min.js"></script><script src="/vendor/msgpack.min.js"></script>
|
||||||
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
<script defer src="/vendor/bootstrap.bundle.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-secondary">
|
<body class="bg-secondary">
|
||||||
@ -25,10 +25,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" id="currentLogin"></li>
|
<li class="nav-item">
|
||||||
<!--<li class="nav-item">
|
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-globe"></i> Tree</a>
|
||||||
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</a>
|
</li>
|
||||||
</li>-->
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
<a class="nav-link" href="/shaped"><i class="fa fa-users"></i> Shaped Devices <span id="shapedCount" class="badge badge-pill badge-success green-badge">?</span></a>
|
||||||
</li>
|
</li>
|
||||||
@ -39,6 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item" id="currentLogin"></li>
|
||||||
<li class="nav-item ms-auto">
|
<li class="nav-item ms-auto">
|
||||||
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
|
||||||
</li>
|
</li>
|
||||||
|
2
src/rust/lqos_node_manager/static/vendor/msgpack.min.js
vendored
Normal file
2
src/rust/lqos_node_manager/static/vendor/msgpack.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -2,8 +2,8 @@ use lqos_bus::{BusRequest, BusResponse, TcHandle};
|
|||||||
use lqos_utils::hex_string::read_hex_string;
|
use lqos_utils::hex_string::read_hex_string;
|
||||||
use nix::libc::getpid;
|
use nix::libc::getpid;
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
exceptions::PyOSError, pyclass, pyfunction, pymodule, types::PyModule,
|
exceptions::PyOSError, pyclass, pyfunction, pymethods, pymodule,
|
||||||
wrap_pyfunction, PyResult, Python, pymethods,
|
types::PyModule, wrap_pyfunction, PyResult, Python,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{remove_file, File},
|
fs::{remove_file, File},
|
||||||
@ -158,7 +158,13 @@ impl BatchedCommands {
|
|||||||
Ok(Self { batch: Vec::new() })
|
Ok(Self { batch: Vec::new() })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_ip_mapping(&mut self, ip: String, classid: String, cpu: String, upload: bool) -> PyResult<()> {
|
pub fn add_ip_mapping(
|
||||||
|
&mut self,
|
||||||
|
ip: String,
|
||||||
|
classid: String,
|
||||||
|
cpu: String,
|
||||||
|
upload: bool,
|
||||||
|
) -> PyResult<()> {
|
||||||
let request = parse_add_ip(&ip, &classid, &cpu, upload);
|
let request = parse_add_ip(&ip, &classid, &cpu, upload);
|
||||||
if let Ok(request) = request {
|
if let Ok(request) = request {
|
||||||
self.batch.push(request);
|
self.batch.push(request);
|
||||||
|
@ -13,10 +13,9 @@ lqos_sys = { path = "../lqos_sys" }
|
|||||||
lqos_utils = { path = "../lqos_utils" }
|
lqos_utils = { path = "../lqos_utils" }
|
||||||
log = "0"
|
log = "0"
|
||||||
log-once = "0.4.0"
|
log-once = "0.4.0"
|
||||||
lazy_static = "1.4"
|
|
||||||
parking_lot = "0"
|
|
||||||
tokio = { version = "1", features = [ "full", "parking_lot" ] }
|
tokio = { version = "1", features = [ "full", "parking_lot" ] }
|
||||||
rayon = "1"
|
once_cell = "1"
|
||||||
|
dashmap = "5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0", features = [ "html_reports"] }
|
criterion = { version = "0", features = [ "html_reports"] }
|
||||||
|
@ -3,9 +3,8 @@ use lqos_bus::BusResponse;
|
|||||||
|
|
||||||
pub fn get_raw_circuit_data(circuit_id: &str) -> BusResponse {
|
pub fn get_raw_circuit_data(circuit_id: &str) -> BusResponse {
|
||||||
still_watching(circuit_id);
|
still_watching(circuit_id);
|
||||||
let reader = CIRCUIT_TO_QUEUE.read();
|
if let Some(circuit) = CIRCUIT_TO_QUEUE.get(circuit_id) {
|
||||||
if let Some(circuit) = reader.get(circuit_id) {
|
if let Ok(json) = serde_json::to_string(circuit.value()) {
|
||||||
if let Ok(json) = serde_json::to_string(circuit) {
|
|
||||||
BusResponse::RawQueueData(json)
|
BusResponse::RawQueueData(json)
|
||||||
} else {
|
} else {
|
||||||
BusResponse::RawQueueData(String::new())
|
BusResponse::RawQueueData(String::new())
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
|
use dashmap::DashMap;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use crate::queue_store::QueueStore;
|
use crate::queue_store::QueueStore;
|
||||||
use lazy_static::*;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
lazy_static! {
|
pub(crate) static CIRCUIT_TO_QUEUE: Lazy<DashMap<String, QueueStore>> =
|
||||||
pub(crate) static ref CIRCUIT_TO_QUEUE: RwLock<HashMap<String, QueueStore>> =
|
Lazy::new(DashMap::new);
|
||||||
RwLock::new(HashMap::new());
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
use lazy_static::*;
|
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
|
|
||||||
lazy_static! {
|
pub(crate) static QUEUE_MONITOR_INTERVAL: AtomicU64 = AtomicU64::new(1000);
|
||||||
pub(crate) static ref QUEUE_MONITOR_INTERVAL: AtomicU64 =
|
|
||||||
AtomicU64::new(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_queue_refresh_interval(interval_ms: u64) {
|
pub fn set_queue_refresh_interval(interval_ms: u64) {
|
||||||
QUEUE_MONITOR_INTERVAL
|
QUEUE_MONITOR_INTERVAL
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use crate::queue_structure::{
|
use crate::queue_structure::{
|
||||||
queue_network::QueueNetwork, queue_node::QueueNode, read_queueing_structure,
|
queue_network::QueueNetwork, queue_node::QueueNode, read_queueing_structure,
|
||||||
};
|
};
|
||||||
use lazy_static::*;
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use lqos_utils::file_watcher::FileWatcher;
|
use lqos_utils::file_watcher::FileWatcher;
|
||||||
use parking_lot::RwLock;
|
use once_cell::sync::Lazy;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
lazy_static! {
|
pub(crate) static QUEUE_STRUCTURE: Lazy<RwLock<QueueStructure>> =
|
||||||
/// Global storage of the shaped devices csv data.
|
Lazy::new(|| RwLock::new(QueueStructure::new()));
|
||||||
/// Updated by the file system watcher whenever
|
|
||||||
/// the underlying file changes.
|
|
||||||
pub(crate) static ref QUEUE_STRUCTURE : RwLock<QueueStructure> = RwLock::new(QueueStructure::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct QueueStructure {
|
pub(crate) struct QueueStructure {
|
||||||
@ -48,7 +45,7 @@ pub async fn spawn_queue_structure_monitor() {
|
|||||||
|
|
||||||
fn update_queue_structure() {
|
fn update_queue_structure() {
|
||||||
info!("queueingStructure.json reloaded");
|
info!("queueingStructure.json reloaded");
|
||||||
QUEUE_STRUCTURE.write().update();
|
QUEUE_STRUCTURE.write().unwrap().update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires up a Linux file system watcher than notifies
|
/// Fires up a Linux file system watcher than notifies
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use lqos_utils::hex_string::read_hex_string;
|
|
||||||
use super::QueueStructureError;
|
use super::QueueStructureError;
|
||||||
use log::error;
|
use log::error;
|
||||||
use lqos_bus::TcHandle;
|
use lqos_bus::TcHandle;
|
||||||
|
use lqos_utils::hex_string::read_hex_string;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
|
@ -5,7 +5,6 @@ use crate::{
|
|||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use lqos_config::LibreQoSConfig;
|
use lqos_config::LibreQoSConfig;
|
||||||
use lqos_utils::fdtimer::periodic;
|
use lqos_utils::fdtimer::periodic;
|
||||||
use rayon::prelude::{IntoParallelRefMutIterator, ParallelIterator};
|
|
||||||
mod reader;
|
mod reader;
|
||||||
mod watched_queues;
|
mod watched_queues;
|
||||||
use self::watched_queues::expire_watched_queues;
|
use self::watched_queues::expire_watched_queues;
|
||||||
@ -13,8 +12,7 @@ use watched_queues::WATCHED_QUEUES;
|
|||||||
pub use watched_queues::{add_watched_queue, still_watching};
|
pub use watched_queues::{add_watched_queue, still_watching};
|
||||||
|
|
||||||
fn track_queues() {
|
fn track_queues() {
|
||||||
let mut watching = WATCHED_QUEUES.write();
|
if WATCHED_QUEUES.is_empty() {
|
||||||
if watching.is_empty() {
|
|
||||||
//info!("No queues marked for read.");
|
//info!("No queues marked for read.");
|
||||||
return; // There's nothing to do - bail out fast
|
return; // There's nothing to do - bail out fast
|
||||||
}
|
}
|
||||||
@ -24,7 +22,7 @@ fn track_queues() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let config = config.unwrap();
|
let config = config.unwrap();
|
||||||
watching.par_iter_mut().for_each(|q| {
|
WATCHED_QUEUES.iter_mut().for_each(|q| {
|
||||||
let (circuit_id, download_class, upload_class) = q.get();
|
let (circuit_id, download_class, upload_class) = q.get();
|
||||||
|
|
||||||
let (download, upload) = if config.on_a_stick_mode {
|
let (download, upload) = if config.on_a_stick_mode {
|
||||||
@ -50,13 +48,12 @@ fn track_queues() {
|
|||||||
|
|
||||||
if let Ok(download) = download {
|
if let Ok(download) = download {
|
||||||
if let Ok(upload) = upload {
|
if let Ok(upload) = upload {
|
||||||
let mut mapping = CIRCUIT_TO_QUEUE.write();
|
if let Some(mut circuit) = CIRCUIT_TO_QUEUE.get_mut(circuit_id) {
|
||||||
if let Some(circuit) = mapping.get_mut(circuit_id) {
|
|
||||||
circuit.update(&download[0], &upload[0]);
|
circuit.update(&download[0], &upload[0]);
|
||||||
} else {
|
} else {
|
||||||
// It's new: insert it
|
// It's new: insert it
|
||||||
if !download.is_empty() && !upload.is_empty() {
|
if !download.is_empty() && !upload.is_empty() {
|
||||||
mapping.insert(
|
CIRCUIT_TO_QUEUE.insert(
|
||||||
circuit_id.to_string(),
|
circuit_id.to_string(),
|
||||||
QueueStore::new(download[0].clone(), upload[0].clone()),
|
QueueStore::new(download[0].clone(), upload[0].clone()),
|
||||||
);
|
);
|
||||||
@ -74,7 +71,6 @@ fn track_queues() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
std::mem::drop(watching); // Release the lock
|
|
||||||
expire_watched_queues();
|
expire_watched_queues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
use crate::queue_structure::QUEUE_STRUCTURE;
|
use crate::queue_structure::QUEUE_STRUCTURE;
|
||||||
use lazy_static::*;
|
use dashmap::DashMap;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use lqos_bus::TcHandle;
|
use lqos_bus::TcHandle;
|
||||||
use lqos_utils::unix_time::unix_now;
|
use lqos_utils::unix_time::unix_now;
|
||||||
use parking_lot::RwLock;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
lazy_static! {
|
pub(crate) static WATCHED_QUEUES: Lazy<DashMap<String, WatchedQueue>> =
|
||||||
pub(crate) static ref WATCHED_QUEUES: RwLock<Vec<WatchedQueue>> =
|
Lazy::new(DashMap::new);
|
||||||
RwLock::new(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
pub(crate) struct WatchedQueue {
|
pub(crate) struct WatchedQueue {
|
||||||
circuit_id: String,
|
circuit_id: String,
|
||||||
expires_unix_time: u64,
|
expires_unix_time: u64,
|
||||||
@ -35,13 +34,12 @@ pub fn add_watched_queue(circuit_id: &str) {
|
|||||||
//info!("Watching queue {circuit_id}");
|
//info!("Watching queue {circuit_id}");
|
||||||
let max = unsafe { lqos_sys::libbpf_num_possible_cpus() } * 2;
|
let max = unsafe { lqos_sys::libbpf_num_possible_cpus() } * 2;
|
||||||
{
|
{
|
||||||
let read_lock = WATCHED_QUEUES.read();
|
if WATCHED_QUEUES.contains_key(circuit_id) {
|
||||||
if read_lock.iter().any(|q| q.circuit_id == circuit_id) {
|
|
||||||
warn!("Queue {circuit_id} is already being watched. Duplicate ignored.");
|
warn!("Queue {circuit_id} is already being watched. Duplicate ignored.");
|
||||||
return; // No duplicates, please
|
return; // No duplicates, please
|
||||||
}
|
}
|
||||||
|
|
||||||
if read_lock.len() > max as usize {
|
if WATCHED_QUEUES.len() > max as usize {
|
||||||
warn!(
|
warn!(
|
||||||
"Watching too many queues - didn't add {circuit_id} to watch list."
|
"Watching too many queues - didn't add {circuit_id} to watch list."
|
||||||
);
|
);
|
||||||
@ -49,7 +47,7 @@ pub fn add_watched_queue(circuit_id: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(queues) = &QUEUE_STRUCTURE.read().maybe_queues {
|
if let Some(queues) = &QUEUE_STRUCTURE.read().unwrap().maybe_queues {
|
||||||
if let Some(circuit) = queues.iter().find(|c| {
|
if let Some(circuit) = queues.iter().find(|c| {
|
||||||
c.circuit_id.is_some() && c.circuit_id.as_ref().unwrap() == circuit_id
|
c.circuit_id.is_some() && c.circuit_id.as_ref().unwrap() == circuit_id
|
||||||
}) {
|
}) {
|
||||||
@ -60,7 +58,7 @@ pub fn add_watched_queue(circuit_id: &str) {
|
|||||||
upload_class: circuit.up_class_id,
|
upload_class: circuit.up_class_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
WATCHED_QUEUES.write().push(new_watch);
|
WATCHED_QUEUES.insert(circuit.circuit_id.as_ref().unwrap().clone(), new_watch);
|
||||||
//info!("Added {circuit_id} to watched queues. Now watching {} queues.", WATCHED_QUEUES.read().len());
|
//info!("Added {circuit_id} to watched queues. Now watching {} queues.", WATCHED_QUEUES.read().len());
|
||||||
} else {
|
} else {
|
||||||
warn!("No circuit ID of {circuit_id}");
|
warn!("No circuit ID of {circuit_id}");
|
||||||
@ -71,19 +69,16 @@ pub fn add_watched_queue(circuit_id: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expire_watched_queues() {
|
pub(crate) fn expire_watched_queues() {
|
||||||
let mut lock = WATCHED_QUEUES.write();
|
|
||||||
let now = unix_now().unwrap_or(0);
|
let now = unix_now().unwrap_or(0);
|
||||||
lock.retain(|w| w.expires_unix_time > now);
|
WATCHED_QUEUES.retain(|_,w| w.expires_unix_time > now);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn still_watching(circuit_id: &str) {
|
pub fn still_watching(circuit_id: &str) {
|
||||||
let mut lock = WATCHED_QUEUES.write();
|
if let Some(mut q) = WATCHED_QUEUES.get_mut(circuit_id) {
|
||||||
if let Some(q) = lock.iter_mut().find(|q| q.circuit_id == circuit_id) {
|
|
||||||
//info!("Still watching circuit: {circuit_id}");
|
//info!("Still watching circuit: {circuit_id}");
|
||||||
q.refresh_timer();
|
q.refresh_timer();
|
||||||
} else {
|
} else {
|
||||||
info!("Still watching circuit, but it had expired: {circuit_id}");
|
info!("Still watching circuit, but it had expired: {circuit_id}");
|
||||||
std::mem::drop(lock);
|
|
||||||
add_watched_queue(circuit_id);
|
add_watched_queue(circuit_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,9 +59,9 @@ impl XdpIpAddress {
|
|||||||
/// Convers an `XdpIpAddress` type to a Rust `IpAddr` type, using
|
/// Convers an `XdpIpAddress` type to a Rust `IpAddr` type, using
|
||||||
/// the in-build mapped function for squishing IPv4 into IPv6
|
/// the in-build mapped function for squishing IPv4 into IPv6
|
||||||
pub fn as_ipv6(&self) -> Ipv6Addr {
|
pub fn as_ipv6(&self) -> Ipv6Addr {
|
||||||
if self.is_v4()
|
if self.is_v4() {
|
||||||
{
|
Ipv4Addr::new(self.0[12], self.0[13], self.0[14], self.0[15])
|
||||||
Ipv4Addr::new(self.0[12], self.0[13], self.0[14], self.0[15]).to_ipv6_mapped()
|
.to_ipv6_mapped()
|
||||||
} else {
|
} else {
|
||||||
Ipv6Addr::new(
|
Ipv6Addr::new(
|
||||||
BigEndian::read_u16(&self.0[0..2]),
|
BigEndian::read_u16(&self.0[0..2]),
|
||||||
@ -78,8 +78,7 @@ impl XdpIpAddress {
|
|||||||
|
|
||||||
/// Converts an `XdpIpAddress` type to a Rust `IpAddr` type
|
/// Converts an `XdpIpAddress` type to a Rust `IpAddr` type
|
||||||
pub fn as_ip(&self) -> IpAddr {
|
pub fn as_ip(&self) -> IpAddr {
|
||||||
if self.is_v4()
|
if self.is_v4() {
|
||||||
{
|
|
||||||
// It's an IPv4 Address
|
// It's an IPv4 Address
|
||||||
IpAddr::V4(Ipv4Addr::new(self.0[12], self.0[13], self.0[14], self.0[15]))
|
IpAddr::V4(Ipv4Addr::new(self.0[12], self.0[13], self.0[14], self.0[15]))
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,20 +19,20 @@ use thiserror::Error;
|
|||||||
/// assert_eq!(read_hex_string("0x12AD").unwrap(), 4781);
|
/// assert_eq!(read_hex_string("0x12AD").unwrap(), 4781);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn read_hex_string(s: &str) -> Result<u32, HexParseError> {
|
pub fn read_hex_string(s: &str) -> Result<u32, HexParseError> {
|
||||||
let result = u32::from_str_radix(&s.replace("0x", ""), 16);
|
let result = u32::from_str_radix(&s.replace("0x", ""), 16);
|
||||||
match result {
|
match result {
|
||||||
Ok(data) => Ok(data),
|
Ok(data) => Ok(data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to convert {s} to a u32");
|
error!("Unable to convert {s} to a u32");
|
||||||
error!("{:?}", e);
|
error!("{:?}", e);
|
||||||
Err(HexParseError::ParseError)
|
Err(HexParseError::ParseError)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `HexParseError` is an error type defining what can go wrong
|
/// `HexParseError` is an error type defining what can go wrong
|
||||||
/// parsing a string into a `u32` hex number.
|
/// parsing a string into a `u32` hex number.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum HexParseError {
|
pub enum HexParseError {
|
||||||
#[error("Unable to decode string into valid hex")]
|
#[error("Unable to decode string into valid hex")]
|
||||||
ParseError,
|
ParseError,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
mod commands;
|
mod commands;
|
||||||
pub mod fdtimer;
|
pub mod fdtimer;
|
||||||
pub mod file_watcher;
|
pub mod file_watcher;
|
||||||
|
pub mod hex_string;
|
||||||
pub mod packet_scale;
|
pub mod packet_scale;
|
||||||
mod string_table_enum;
|
mod string_table_enum;
|
||||||
pub mod unix_time;
|
pub mod unix_time;
|
||||||
pub mod hex_string;
|
|
||||||
|
@ -15,7 +15,6 @@ lqos_queue_tracker = { path = "../lqos_queue_tracker" }
|
|||||||
lqos_utils = { path = "../lqos_utils" }
|
lqos_utils = { path = "../lqos_utils" }
|
||||||
tokio = { version = "1", features = [ "full", "parking_lot" ] }
|
tokio = { version = "1", features = [ "full", "parking_lot" ] }
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
parking_lot = "0.12"
|
|
||||||
lqos_bus = { path = "../lqos_bus" }
|
lqos_bus = { path = "../lqos_bus" }
|
||||||
signal-hook = "0.3"
|
signal-hook = "0.3"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
@ -23,8 +22,8 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
env_logger = "0"
|
env_logger = "0"
|
||||||
log = "0"
|
log = "0"
|
||||||
nix = "0"
|
nix = "0"
|
||||||
rayon = "1"
|
|
||||||
sysinfo = "0"
|
sysinfo = "0"
|
||||||
|
dashmap = "5"
|
||||||
|
|
||||||
# Support JemAlloc on supported platforms
|
# Support JemAlloc on supported platforms
|
||||||
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
|
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
|
||||||
|
@ -11,7 +11,7 @@ pub fn lqos_daht_test() -> BusResponse {
|
|||||||
true,
|
true,
|
||||||
std::sync::atomic::Ordering::Relaxed,
|
std::sync::atomic::Ordering::Relaxed,
|
||||||
std::sync::atomic::Ordering::Relaxed,
|
std::sync::atomic::Ordering::Relaxed,
|
||||||
) == Ok(true)
|
) == Ok(false)
|
||||||
{
|
{
|
||||||
let result = Command::new("/bin/ssh")
|
let result = Command::new("/bin/ssh")
|
||||||
.args(["-t", "lqtest@lqos.taht.net", "\"/home/lqtest/bin/v6vsv4.sh\""])
|
.args(["-t", "lqtest@lqos.taht.net", "\"/home/lqtest/bin/v6vsv4.sh\""])
|
||||||
|
@ -24,7 +24,9 @@ use signal_hook::{
|
|||||||
consts::{SIGHUP, SIGINT, SIGTERM},
|
consts::{SIGHUP, SIGINT, SIGTERM},
|
||||||
iterator::Signals,
|
iterator::Signals,
|
||||||
};
|
};
|
||||||
|
use stats::{BUS_REQUESTS, TIME_TO_POLL_HOSTS};
|
||||||
use tokio::join;
|
use tokio::join;
|
||||||
|
mod stats;
|
||||||
|
|
||||||
// Use JemAllocator only on supported platforms
|
// Use JemAllocator only on supported platforms
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
@ -120,6 +122,7 @@ fn handle_bus_requests(
|
|||||||
) {
|
) {
|
||||||
for req in requests.iter() {
|
for req in requests.iter() {
|
||||||
//println!("Request: {:?}", req);
|
//println!("Request: {:?}", req);
|
||||||
|
BUS_REQUESTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
responses.push(match req {
|
responses.push(match req {
|
||||||
BusRequest::Ping => lqos_bus::BusResponse::Ack,
|
BusRequest::Ping => lqos_bus::BusResponse::Ack,
|
||||||
BusRequest::GetCurrentThroughput => {
|
BusRequest::GetCurrentThroughput => {
|
||||||
@ -160,10 +163,25 @@ fn handle_bus_requests(
|
|||||||
BusRequest::RequestLqosEquinixTest => lqos_daht_test::lqos_daht_test(),
|
BusRequest::RequestLqosEquinixTest => lqos_daht_test::lqos_daht_test(),
|
||||||
BusRequest::ValidateShapedDevicesCsv => {
|
BusRequest::ValidateShapedDevicesCsv => {
|
||||||
validation::validate_shaped_devices_csv()
|
validation::validate_shaped_devices_csv()
|
||||||
},
|
}
|
||||||
BusRequest::GetNetworkMap { parent } => {
|
BusRequest::GetNetworkMap { parent } => {
|
||||||
shaped_devices_tracker::get_one_network_map_layer(*parent)
|
shaped_devices_tracker::get_one_network_map_layer(*parent)
|
||||||
},
|
}
|
||||||
|
BusRequest::TopMapQueues(n_queues) => {
|
||||||
|
shaped_devices_tracker::get_top_n_root_queues(*n_queues)
|
||||||
|
}
|
||||||
|
BusRequest::GetNodeNamesFromIds(nodes) => {
|
||||||
|
shaped_devices_tracker::map_node_names(nodes)
|
||||||
|
}
|
||||||
|
BusRequest::GetFunnel { target: parent } => {
|
||||||
|
shaped_devices_tracker::get_funnel(parent)
|
||||||
|
}
|
||||||
|
BusRequest::GetLqosStats => {
|
||||||
|
BusResponse::LqosdStats {
|
||||||
|
bus_requests: BUS_REQUESTS.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
time_to_poll_hosts: TIME_TO_POLL_HOSTS.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use lqos_bus::BusResponse;
|
use lqos_bus::BusResponse;
|
||||||
use lqos_config::ConfigShapedDevices;
|
use lqos_config::{ConfigShapedDevices, NetworkJsonTransport};
|
||||||
use lqos_utils::file_watcher::FileWatcher;
|
use lqos_utils::file_watcher::FileWatcher;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::RwLock;
|
use std::sync::RwLock;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
mod netjson;
|
mod netjson;
|
||||||
pub use netjson::*;
|
pub use netjson::*;
|
||||||
@ -17,11 +17,12 @@ fn load_shaped_devices() {
|
|||||||
let shaped_devices = ConfigShapedDevices::load();
|
let shaped_devices = ConfigShapedDevices::load();
|
||||||
if let Ok(new_file) = shaped_devices {
|
if let Ok(new_file) = shaped_devices {
|
||||||
info!("ShapedDevices.csv loaded");
|
info!("ShapedDevices.csv loaded");
|
||||||
*SHAPED_DEVICES.write() = new_file;
|
*SHAPED_DEVICES.write().unwrap() = new_file;
|
||||||
crate::throughput_tracker::THROUGHPUT_TRACKER.write().refresh_circuit_ids();
|
crate::throughput_tracker::THROUGHPUT_TRACKER
|
||||||
|
.refresh_circuit_ids();
|
||||||
} else {
|
} else {
|
||||||
warn!("ShapedDevices.csv failed to load, see previous error messages. Reverting to empty set.");
|
warn!("ShapedDevices.csv failed to load, see previous error messages. Reverting to empty set.");
|
||||||
*SHAPED_DEVICES.write() = ConfigShapedDevices::default();
|
*SHAPED_DEVICES.write().unwrap() = ConfigShapedDevices::default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ fn watch_for_shaped_devices_changing() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_one_network_map_layer(parent_idx: usize) -> BusResponse {
|
pub fn get_one_network_map_layer(parent_idx: usize) -> BusResponse {
|
||||||
let net_json = NETWORK_JSON.read();
|
let net_json = NETWORK_JSON.read().unwrap();
|
||||||
if let Some(parent) = net_json.get_cloned_entry_by_index(parent_idx) {
|
if let Some(parent) = net_json.get_cloned_entry_by_index(parent_idx) {
|
||||||
let mut nodes = vec![(parent_idx, parent)];
|
let mut nodes = vec![(parent_idx, parent)];
|
||||||
nodes.extend_from_slice(&net_json.get_cloned_children(parent_idx));
|
nodes.extend_from_slice(&net_json.get_cloned_children(parent_idx));
|
||||||
@ -64,3 +65,67 @@ pub fn get_one_network_map_layer(parent_idx: usize) -> BusResponse {
|
|||||||
BusResponse::Fail("No such node".to_string())
|
BusResponse::Fail("No such node".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_top_n_root_queues(n_queues: usize) -> BusResponse {
|
||||||
|
let net_json = NETWORK_JSON.read().unwrap();
|
||||||
|
if let Some(parent) = net_json.get_cloned_entry_by_index(0) {
|
||||||
|
let mut nodes = vec![(0, parent)];
|
||||||
|
nodes.extend_from_slice(&net_json.get_cloned_children(0));
|
||||||
|
// Remove the top-level entry for root
|
||||||
|
nodes.remove(0);
|
||||||
|
// Sort by total bandwidth (up + down) descending
|
||||||
|
nodes.sort_by(|a, b| {
|
||||||
|
let total_a = a.1.current_throughput.0 + a.1.current_throughput.1;
|
||||||
|
let total_b = b.1.current_throughput.0 + b.1.current_throughput.1;
|
||||||
|
total_b.cmp(&total_a)
|
||||||
|
});
|
||||||
|
// Summarize everything after n_queues
|
||||||
|
if nodes.len() > n_queues {
|
||||||
|
let mut other_bw = (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;
|
||||||
|
});
|
||||||
|
|
||||||
|
nodes.push((
|
||||||
|
0,
|
||||||
|
NetworkJsonTransport {
|
||||||
|
name: "Others".into(),
|
||||||
|
max_throughput: (0, 0),
|
||||||
|
current_throughput: other_bw,
|
||||||
|
rtts: Vec::new(),
|
||||||
|
parents: Vec::new(),
|
||||||
|
immediate_parent: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
BusResponse::NetworkMap(nodes)
|
||||||
|
} else {
|
||||||
|
BusResponse::Fail("No such node".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_node_names(nodes: &[usize]) -> BusResponse {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let reader = NETWORK_JSON.read().unwrap();
|
||||||
|
nodes.iter().for_each(|id| {
|
||||||
|
if let Some(node) = reader.nodes.get(*id) {
|
||||||
|
result.push((*id, node.name.clone()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
BusResponse::NodeNames(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_funnel(circuit_id: &str) -> BusResponse {
|
||||||
|
let reader = NETWORK_JSON.read().unwrap();
|
||||||
|
if let Some(index) = reader.get_index_for_name(circuit_id) {
|
||||||
|
// Reverse the scanning order and skip the last entry (the parent)
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for idx in reader.nodes[index].parents.iter().rev().skip(1) {
|
||||||
|
result.push((*idx, reader.nodes[*idx].clone_to_transit()));
|
||||||
|
}
|
||||||
|
return BusResponse::NetworkMap(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
BusResponse::Fail("Unknown Node".into())
|
||||||
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use log::{info, error, warn};
|
use anyhow::Result;
|
||||||
|
use log::{error, info, warn};
|
||||||
use lqos_config::NetworkJson;
|
use lqos_config::NetworkJson;
|
||||||
use lqos_utils::file_watcher::FileWatcher;
|
use lqos_utils::file_watcher::FileWatcher;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::RwLock;
|
use std::sync::RwLock;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub static NETWORK_JSON: Lazy<RwLock<NetworkJson>> = Lazy::new(|| RwLock::new(NetworkJson::default()));
|
pub static NETWORK_JSON: Lazy<RwLock<NetworkJson>> =
|
||||||
|
Lazy::new(|| RwLock::new(NetworkJson::default()));
|
||||||
|
|
||||||
pub async fn network_json_watcher() {
|
pub async fn network_json_watcher() {
|
||||||
spawn_blocking(|| {
|
spawn_blocking(|| {
|
||||||
@ -18,30 +19,29 @@ pub async fn network_json_watcher() {
|
|||||||
/// Fires up a Linux file system watcher than notifies
|
/// Fires up a Linux file system watcher than notifies
|
||||||
/// when `network.json` changes, and triggers a reload.
|
/// when `network.json` changes, and triggers a reload.
|
||||||
fn watch_for_network_json_changing() -> Result<()> {
|
fn watch_for_network_json_changing() -> Result<()> {
|
||||||
let watch_path = NetworkJson::path();
|
let watch_path = NetworkJson::path();
|
||||||
if watch_path.is_err() {
|
if watch_path.is_err() {
|
||||||
error!("Unable to generate path for network.json");
|
error!("Unable to generate path for network.json");
|
||||||
return Err(anyhow::Error::msg(
|
return Err(anyhow::Error::msg("Unable to create path for network.json"));
|
||||||
"Unable to create path for network.json",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let watch_path = watch_path.unwrap();
|
|
||||||
|
|
||||||
let mut watcher = FileWatcher::new("network.json", watch_path);
|
|
||||||
watcher.set_file_exists_callback(load_network_json);
|
|
||||||
watcher.set_file_created_callback(load_network_json);
|
|
||||||
watcher.set_file_changed_callback(load_network_json);
|
|
||||||
loop {
|
|
||||||
let result = watcher.watch();
|
|
||||||
info!("network.json watcher returned: {result:?}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let watch_path = watch_path.unwrap();
|
||||||
|
|
||||||
fn load_network_json() {
|
let mut watcher = FileWatcher::new("network.json", watch_path);
|
||||||
let njs = NetworkJson::load();
|
watcher.set_file_exists_callback(load_network_json);
|
||||||
if let Ok(njs) = njs {
|
watcher.set_file_created_callback(load_network_json);
|
||||||
*NETWORK_JSON.write() = njs;
|
watcher.set_file_changed_callback(load_network_json);
|
||||||
} else {
|
loop {
|
||||||
warn!("Unable to load network.json");
|
let result = watcher.watch();
|
||||||
}
|
info!("network.json watcher returned: {result:?}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_network_json() {
|
||||||
|
let njs = NetworkJson::load();
|
||||||
|
if let Ok(njs) = njs {
|
||||||
|
let mut write_lock = NETWORK_JSON.write().unwrap();
|
||||||
|
*write_lock = njs;
|
||||||
|
} else {
|
||||||
|
warn!("Unable to load network.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4
src/rust/lqosd/src/stats.rs
Normal file
4
src/rust/lqosd/src/stats.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
use std::sync::atomic::AtomicU64;
|
||||||
|
|
||||||
|
pub static BUS_REQUESTS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
pub static TIME_TO_POLL_HOSTS: AtomicU64 = AtomicU64::new(0);
|
@ -1,17 +1,20 @@
|
|||||||
mod throughput_entry;
|
mod throughput_entry;
|
||||||
mod tracking_data;
|
mod tracking_data;
|
||||||
use crate::{throughput_tracker::tracking_data::ThroughputTracker, shaped_devices_tracker::NETWORK_JSON};
|
use crate::{
|
||||||
|
shaped_devices_tracker::NETWORK_JSON,
|
||||||
|
throughput_tracker::tracking_data::ThroughputTracker, stats::TIME_TO_POLL_HOSTS,
|
||||||
|
};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use lqos_bus::{BusResponse, IpStats, TcHandle, XdpPpingResult};
|
use lqos_bus::{BusResponse, IpStats, TcHandle, XdpPpingResult};
|
||||||
use lqos_sys::XdpIpAddress;
|
use lqos_sys::XdpIpAddress;
|
||||||
use lqos_utils::{fdtimer::periodic, unix_time::time_since_boot};
|
use lqos_utils::{fdtimer::periodic, unix_time::time_since_boot};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::RwLock;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
const RETIRE_AFTER_SECONDS: u64 = 30;
|
const RETIRE_AFTER_SECONDS: u64 = 30;
|
||||||
|
|
||||||
pub static THROUGHPUT_TRACKER: Lazy<RwLock<ThroughputTracker>> = Lazy::new(|| RwLock::new(ThroughputTracker::new()));
|
pub static THROUGHPUT_TRACKER: Lazy<ThroughputTracker> =
|
||||||
|
Lazy::new(ThroughputTracker::new);
|
||||||
|
|
||||||
pub fn spawn_throughput_monitor() {
|
pub fn spawn_throughput_monitor() {
|
||||||
info!("Starting the bandwidth monitor thread.");
|
info!("Starting the bandwidth monitor thread.");
|
||||||
@ -20,24 +23,28 @@ pub fn spawn_throughput_monitor() {
|
|||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
periodic(interval_ms, "Throughput Monitor", &mut || {
|
periodic(interval_ms, "Throughput Monitor", &mut || {
|
||||||
let mut throughput = THROUGHPUT_TRACKER.write();
|
let start = std::time::Instant::now();
|
||||||
let mut net_json = NETWORK_JSON.write();
|
{
|
||||||
throughput.copy_previous_and_reset_rtt(&mut net_json);
|
let net_json = NETWORK_JSON.read().unwrap();
|
||||||
throughput.apply_new_throughput_counters();
|
net_json.zero_throughput_and_rtt();
|
||||||
throughput.apply_rtt_data();
|
} // Scope to end the lock
|
||||||
throughput.update_totals(&mut net_json);
|
THROUGHPUT_TRACKER.copy_previous_and_reset_rtt();
|
||||||
throughput.next_cycle();
|
THROUGHPUT_TRACKER.apply_new_throughput_counters();
|
||||||
|
THROUGHPUT_TRACKER.apply_rtt_data();
|
||||||
|
THROUGHPUT_TRACKER.update_totals();
|
||||||
|
THROUGHPUT_TRACKER.next_cycle();
|
||||||
|
let duration_ms = start.elapsed().as_micros();
|
||||||
|
TIME_TO_POLL_HOSTS.store(duration_ms as u64, std::sync::atomic::Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_throughput() -> BusResponse {
|
pub fn current_throughput() -> BusResponse {
|
||||||
let (bits_per_second, packets_per_second, shaped_bits_per_second) = {
|
let (bits_per_second, packets_per_second, shaped_bits_per_second) = {
|
||||||
let tp = THROUGHPUT_TRACKER.read();
|
|
||||||
(
|
(
|
||||||
tp.bits_per_second(),
|
THROUGHPUT_TRACKER.bits_per_second(),
|
||||||
tp.packets_per_second(),
|
THROUGHPUT_TRACKER.packets_per_second(),
|
||||||
tp.shaped_bits_per_second(),
|
THROUGHPUT_TRACKER.shaped_bits_per_second(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
BusResponse::CurrentThroughput {
|
BusResponse::CurrentThroughput {
|
||||||
@ -49,9 +56,8 @@ pub fn current_throughput() -> BusResponse {
|
|||||||
|
|
||||||
pub fn host_counters() -> BusResponse {
|
pub fn host_counters() -> BusResponse {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
let tp = THROUGHPUT_TRACKER.read();
|
THROUGHPUT_TRACKER.raw_data.iter().for_each(|v| {
|
||||||
tp.raw_data.iter().for_each(|(k, v)| {
|
let ip = v.key().as_ip();
|
||||||
let ip = k.as_ip();
|
|
||||||
let (down, up) = v.bytes_per_second;
|
let (down, up) = v.bytes_per_second;
|
||||||
result.push((ip, down, up));
|
result.push((ip, down, up));
|
||||||
});
|
});
|
||||||
@ -67,14 +73,14 @@ type TopList = (XdpIpAddress, (u64, u64), (u64, u64), f32, TcHandle, String);
|
|||||||
|
|
||||||
pub fn top_n(start: u32, end: u32) -> BusResponse {
|
pub fn top_n(start: u32, end: u32) -> BusResponse {
|
||||||
let mut full_list: Vec<TopList> = {
|
let mut full_list: Vec<TopList> = {
|
||||||
let tp = THROUGHPUT_TRACKER.read();
|
let tp_cycle = THROUGHPUT_TRACKER.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
tp.raw_data
|
THROUGHPUT_TRACKER.raw_data
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(ip, _)| !ip.as_ip().is_loopback())
|
.filter(|v| !v.key().as_ip().is_loopback())
|
||||||
.filter(|(_, d)| retire_check(tp.cycle, d.most_recent_cycle))
|
.filter(|d| retire_check(tp_cycle, d.most_recent_cycle))
|
||||||
.map(|(ip, te)| {
|
.map(|te| {
|
||||||
(
|
(
|
||||||
*ip,
|
*te.key(),
|
||||||
te.bytes_per_second,
|
te.bytes_per_second,
|
||||||
te.packets_per_second,
|
te.packets_per_second,
|
||||||
te.median_latency(),
|
te.median_latency(),
|
||||||
@ -112,15 +118,15 @@ pub fn top_n(start: u32, end: u32) -> BusResponse {
|
|||||||
|
|
||||||
pub fn worst_n(start: u32, end: u32) -> BusResponse {
|
pub fn worst_n(start: u32, end: u32) -> BusResponse {
|
||||||
let mut full_list: Vec<TopList> = {
|
let mut full_list: Vec<TopList> = {
|
||||||
let tp = THROUGHPUT_TRACKER.read();
|
let tp_cycle = THROUGHPUT_TRACKER.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
tp.raw_data
|
THROUGHPUT_TRACKER.raw_data
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(ip, _)| !ip.as_ip().is_loopback())
|
.filter(|v| !v.key().as_ip().is_loopback())
|
||||||
.filter(|(_, d)| retire_check(tp.cycle, d.most_recent_cycle))
|
.filter(|d| retire_check(tp_cycle, d.most_recent_cycle))
|
||||||
.filter(|(_, te)| te.median_latency() > 0.0)
|
.filter(|te| te.median_latency() > 0.0)
|
||||||
.map(|(ip, te)| {
|
.map(|te| {
|
||||||
(
|
(
|
||||||
*ip,
|
*te.key(),
|
||||||
te.bytes_per_second,
|
te.bytes_per_second,
|
||||||
te.packets_per_second,
|
te.packets_per_second,
|
||||||
te.median_latency(),
|
te.median_latency(),
|
||||||
@ -157,15 +163,15 @@ pub fn worst_n(start: u32, end: u32) -> BusResponse {
|
|||||||
}
|
}
|
||||||
pub fn best_n(start: u32, end: u32) -> BusResponse {
|
pub fn best_n(start: u32, end: u32) -> BusResponse {
|
||||||
let mut full_list: Vec<TopList> = {
|
let mut full_list: Vec<TopList> = {
|
||||||
let tp = THROUGHPUT_TRACKER.read();
|
let tp_cycle = THROUGHPUT_TRACKER.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
tp.raw_data
|
THROUGHPUT_TRACKER.raw_data
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(ip, _)| !ip.as_ip().is_loopback())
|
.filter(|v| !v.key().as_ip().is_loopback())
|
||||||
.filter(|(_, d)| retire_check(tp.cycle, d.most_recent_cycle))
|
.filter(|d| retire_check(tp_cycle, d.most_recent_cycle))
|
||||||
.filter(|(_, te)| te.median_latency() > 0.0)
|
.filter(|te| te.median_latency() > 0.0)
|
||||||
.map(|(ip, te)| {
|
.map(|te| {
|
||||||
(
|
(
|
||||||
*ip,
|
*te.key(),
|
||||||
te.bytes_per_second,
|
te.bytes_per_second,
|
||||||
te.packets_per_second,
|
te.packets_per_second,
|
||||||
te.median_latency(),
|
te.median_latency(),
|
||||||
@ -203,12 +209,12 @@ pub fn best_n(start: u32, end: u32) -> BusResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn xdp_pping_compat() -> BusResponse {
|
pub fn xdp_pping_compat() -> BusResponse {
|
||||||
let raw = THROUGHPUT_TRACKER.read();
|
let raw_cycle = THROUGHPUT_TRACKER.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
let result = raw
|
let result = THROUGHPUT_TRACKER
|
||||||
.raw_data
|
.raw_data
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, d)| retire_check(raw.cycle, d.most_recent_cycle))
|
.filter(|d| retire_check(raw_cycle, d.most_recent_cycle))
|
||||||
.filter_map(|(_ip, data)| {
|
.filter_map(|data| {
|
||||||
if data.tc_handle.as_u32() > 0 {
|
if data.tc_handle.as_u32() > 0 {
|
||||||
let mut valid_samples: Vec<u32> =
|
let mut valid_samples: Vec<u32> =
|
||||||
data.recent_rtt_data.iter().filter(|d| **d > 0).copied().collect();
|
data.recent_rtt_data.iter().filter(|d| **d > 0).copied().collect();
|
||||||
@ -242,11 +248,11 @@ pub fn xdp_pping_compat() -> BusResponse {
|
|||||||
|
|
||||||
pub fn rtt_histogram() -> BusResponse {
|
pub fn rtt_histogram() -> BusResponse {
|
||||||
let mut result = vec![0; 20];
|
let mut result = vec![0; 20];
|
||||||
let reader = THROUGHPUT_TRACKER.read();
|
let reader_cycle = THROUGHPUT_TRACKER.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
for (_, data) in reader
|
for data in THROUGHPUT_TRACKER
|
||||||
.raw_data
|
.raw_data
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, d)| retire_check(reader.cycle, d.most_recent_cycle))
|
.filter(|d| retire_check(reader_cycle, d.most_recent_cycle))
|
||||||
{
|
{
|
||||||
let valid_samples: Vec<u32> =
|
let valid_samples: Vec<u32> =
|
||||||
data.recent_rtt_data.iter().filter(|d| **d > 0).copied().collect();
|
data.recent_rtt_data.iter().filter(|d| **d > 0).copied().collect();
|
||||||
@ -265,11 +271,11 @@ pub fn rtt_histogram() -> BusResponse {
|
|||||||
pub fn host_counts() -> BusResponse {
|
pub fn host_counts() -> BusResponse {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
let mut shaped = 0;
|
let mut shaped = 0;
|
||||||
let tp = THROUGHPUT_TRACKER.read();
|
let tp_cycle = THROUGHPUT_TRACKER.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
tp.raw_data
|
THROUGHPUT_TRACKER.raw_data
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, d)| retire_check(tp.cycle, d.most_recent_cycle))
|
.filter(|d| retire_check(tp_cycle, d.most_recent_cycle))
|
||||||
.for_each(|(_, d)| {
|
.for_each(|d| {
|
||||||
total += 1;
|
total += 1;
|
||||||
if d.tc_handle.as_u32() != 0 {
|
if d.tc_handle.as_u32() != 0 {
|
||||||
shaped += 1;
|
shaped += 1;
|
||||||
@ -294,15 +300,14 @@ pub fn all_unknown_ips() -> BusResponse {
|
|||||||
let five_minutes_ago_nanoseconds = five_minutes_ago.as_nanos();
|
let five_minutes_ago_nanoseconds = five_minutes_ago.as_nanos();
|
||||||
|
|
||||||
let mut full_list: Vec<FullList> = {
|
let mut full_list: Vec<FullList> = {
|
||||||
let tp = THROUGHPUT_TRACKER.read();
|
THROUGHPUT_TRACKER.raw_data
|
||||||
tp.raw_data
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(ip, _)| !ip.as_ip().is_loopback())
|
.filter(|v| !v.key().as_ip().is_loopback())
|
||||||
.filter(|(_, d)| d.tc_handle.as_u32() == 0)
|
.filter(|d| d.tc_handle.as_u32() == 0)
|
||||||
.filter(|(_, d)| d.last_seen as u128 > five_minutes_ago_nanoseconds)
|
.filter(|d| d.last_seen as u128 > five_minutes_ago_nanoseconds)
|
||||||
.map(|(ip, te)| {
|
.map(|te| {
|
||||||
(
|
(
|
||||||
*ip,
|
*te.key(),
|
||||||
te.bytes,
|
te.bytes,
|
||||||
te.packets,
|
te.packets,
|
||||||
te.median_latency(),
|
te.median_latency(),
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
use crate::shaped_devices_tracker::SHAPED_DEVICES;
|
use std::sync::atomic::AtomicU64;
|
||||||
|
use crate::shaped_devices_tracker::{SHAPED_DEVICES, NETWORK_JSON};
|
||||||
use super::{throughput_entry::ThroughputEntry, RETIRE_AFTER_SECONDS};
|
use super::{throughput_entry::ThroughputEntry, RETIRE_AFTER_SECONDS};
|
||||||
|
use dashmap::DashMap;
|
||||||
use lqos_bus::TcHandle;
|
use lqos_bus::TcHandle;
|
||||||
use lqos_config::NetworkJson;
|
|
||||||
use lqos_sys::{rtt_for_each, throughput_for_each, XdpIpAddress};
|
use lqos_sys::{rtt_for_each, throughput_for_each, XdpIpAddress};
|
||||||
use rayon::prelude::{IntoParallelRefMutIterator, ParallelIterator};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub struct ThroughputTracker {
|
pub struct ThroughputTracker {
|
||||||
pub(crate) cycle: u64,
|
pub(crate) cycle: AtomicU64,
|
||||||
pub(crate) raw_data: HashMap<XdpIpAddress, ThroughputEntry>,
|
pub(crate) raw_data: DashMap<XdpIpAddress, ThroughputEntry>,
|
||||||
pub(crate) bytes_per_second: (u64, u64),
|
pub(crate) bytes_per_second: (AtomicU64, AtomicU64),
|
||||||
pub(crate) packets_per_second: (u64, u64),
|
pub(crate) packets_per_second: (AtomicU64, AtomicU64),
|
||||||
pub(crate) shaped_bytes_per_second: (u64, u64),
|
pub(crate) shaped_bytes_per_second: (AtomicU64, AtomicU64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThroughputTracker {
|
impl ThroughputTracker {
|
||||||
@ -21,26 +19,21 @@ impl ThroughputTracker {
|
|||||||
// maximums.h (MAX_TRACKED_IPS), so we grab it
|
// maximums.h (MAX_TRACKED_IPS), so we grab it
|
||||||
// from there via the C API.
|
// from there via the C API.
|
||||||
Self {
|
Self {
|
||||||
cycle: RETIRE_AFTER_SECONDS,
|
cycle: AtomicU64::new(RETIRE_AFTER_SECONDS),
|
||||||
raw_data: HashMap::with_capacity(lqos_sys::max_tracked_ips()),
|
raw_data: DashMap::with_capacity(lqos_sys::max_tracked_ips()),
|
||||||
bytes_per_second: (0, 0),
|
bytes_per_second: (AtomicU64::new(0), AtomicU64::new(0)),
|
||||||
packets_per_second: (0, 0),
|
packets_per_second: (AtomicU64::new(0), AtomicU64::new(0)),
|
||||||
shaped_bytes_per_second: (0, 0),
|
shaped_bytes_per_second: (AtomicU64::new(0), AtomicU64::new(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn copy_previous_and_reset_rtt(
|
pub(crate) fn copy_previous_and_reset_rtt(&self) {
|
||||||
&mut self,
|
|
||||||
netjson: &mut NetworkJson,
|
|
||||||
) {
|
|
||||||
// Zero the previous funnel hierarchy current numbers
|
|
||||||
netjson.zero_throughput_and_rtt();
|
|
||||||
|
|
||||||
// Copy previous byte/packet numbers and reset RTT data
|
// Copy previous byte/packet numbers and reset RTT data
|
||||||
// We're using Rayon's "par_iter_mut" to spread the operation across
|
// We're using Rayon's "par_iter_mut" to spread the operation across
|
||||||
// all CPU cores.
|
// all CPU cores.
|
||||||
self.raw_data.par_iter_mut().for_each(|(_k, v)| {
|
let self_cycle = self.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
if v.first_cycle < self.cycle {
|
self.raw_data.iter_mut().for_each(|mut v| {
|
||||||
|
if v.first_cycle < self_cycle {
|
||||||
v.bytes_per_second.0 =
|
v.bytes_per_second.0 =
|
||||||
u64::checked_sub(v.bytes.0, v.prev_bytes.0).unwrap_or(0);
|
u64::checked_sub(v.bytes.0, v.prev_bytes.0).unwrap_or(0);
|
||||||
v.bytes_per_second.1 =
|
v.bytes_per_second.1 =
|
||||||
@ -53,8 +46,8 @@ impl ThroughputTracker {
|
|||||||
v.prev_packets = v.packets;
|
v.prev_packets = v.packets;
|
||||||
}
|
}
|
||||||
// Roll out stale RTT data
|
// Roll out stale RTT data
|
||||||
if self.cycle > RETIRE_AFTER_SECONDS
|
if self_cycle > RETIRE_AFTER_SECONDS
|
||||||
&& v.last_fresh_rtt_data_cycle < self.cycle - RETIRE_AFTER_SECONDS
|
&& v.last_fresh_rtt_data_cycle < self_cycle - RETIRE_AFTER_SECONDS
|
||||||
{
|
{
|
||||||
v.recent_rtt_data = [0; 60];
|
v.recent_rtt_data = [0; 60];
|
||||||
}
|
}
|
||||||
@ -64,7 +57,7 @@ impl ThroughputTracker {
|
|||||||
fn lookup_circuit_id(xdp_ip: &XdpIpAddress) -> Option<String> {
|
fn lookup_circuit_id(xdp_ip: &XdpIpAddress) -> Option<String> {
|
||||||
let mut circuit_id = None;
|
let mut circuit_id = None;
|
||||||
let lookup = xdp_ip.as_ipv6();
|
let lookup = xdp_ip.as_ipv6();
|
||||||
let cfg = SHAPED_DEVICES.read();
|
let cfg = SHAPED_DEVICES.read().unwrap();
|
||||||
if let Some((_, id)) = cfg.trie.longest_match(lookup) {
|
if let Some((_, id)) = cfg.trie.longest_match(lookup) {
|
||||||
circuit_id = Some(cfg.devices[*id].circuit_id.clone());
|
circuit_id = Some(cfg.devices[*id].circuit_id.clone());
|
||||||
}
|
}
|
||||||
@ -76,12 +69,14 @@ impl ThroughputTracker {
|
|||||||
circuit_id: Option<String>,
|
circuit_id: Option<String>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
if let Some(circuit_id) = circuit_id {
|
if let Some(circuit_id) = circuit_id {
|
||||||
let shaped = SHAPED_DEVICES.read();
|
let shaped = SHAPED_DEVICES.read().unwrap();
|
||||||
shaped
|
let parent_name = shaped
|
||||||
.devices
|
.devices
|
||||||
.iter()
|
.iter()
|
||||||
.find(|d| d.circuit_id == circuit_id)
|
.find(|d| d.circuit_id == circuit_id)
|
||||||
.map(|device| device.parent_node.clone())
|
.map(|device| device.parent_node.clone());
|
||||||
|
//println!("{parent_name:?}");
|
||||||
|
parent_name
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -91,26 +86,28 @@ impl ThroughputTracker {
|
|||||||
circuit_id: Option<String>,
|
circuit_id: Option<String>,
|
||||||
) -> Option<Vec<usize>> {
|
) -> Option<Vec<usize>> {
|
||||||
if let Some(parent) = Self::get_node_name_for_circuit_id(circuit_id) {
|
if let Some(parent) = Self::get_node_name_for_circuit_id(circuit_id) {
|
||||||
let lock = crate::shaped_devices_tracker::NETWORK_JSON.read();
|
let lock = crate::shaped_devices_tracker::NETWORK_JSON.read().unwrap();
|
||||||
lock.get_parents_for_circuit_id(&parent)
|
lock.get_parents_for_circuit_id(&parent)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn refresh_circuit_ids(&mut self) {
|
pub(crate) fn refresh_circuit_ids(&self) {
|
||||||
self.raw_data.par_iter_mut().for_each(|(ip, data)| {
|
self.raw_data.iter_mut().for_each(|mut data| {
|
||||||
data.circuit_id = Self::lookup_circuit_id(ip);
|
data.circuit_id = Self::lookup_circuit_id(data.key());
|
||||||
data.network_json_parents =
|
data.network_json_parents =
|
||||||
Self::lookup_network_parents(data.circuit_id.clone());
|
Self::lookup_network_parents(data.circuit_id.clone());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_new_throughput_counters(&mut self) {
|
pub(crate) fn apply_new_throughput_counters(
|
||||||
let cycle = self.cycle;
|
&self,
|
||||||
let raw_data = &mut self.raw_data;
|
) {
|
||||||
|
let raw_data = &self.raw_data;
|
||||||
|
let self_cycle = self.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
throughput_for_each(&mut |xdp_ip, counts| {
|
throughput_for_each(&mut |xdp_ip, counts| {
|
||||||
if let Some(entry) = raw_data.get_mut(xdp_ip) {
|
if let Some(mut entry) = raw_data.get_mut(xdp_ip) {
|
||||||
entry.bytes = (0, 0);
|
entry.bytes = (0, 0);
|
||||||
entry.packets = (0, 0);
|
entry.packets = (0, 0);
|
||||||
for c in counts {
|
for c in counts {
|
||||||
@ -126,14 +123,25 @@ impl ThroughputTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if entry.packets != entry.prev_packets {
|
if entry.packets != entry.prev_packets {
|
||||||
entry.most_recent_cycle = cycle;
|
entry.most_recent_cycle = self_cycle;
|
||||||
|
|
||||||
|
if let Some(parents) = &entry.network_json_parents {
|
||||||
|
let net_json = NETWORK_JSON.read().unwrap();
|
||||||
|
net_json.add_throughput_cycle(
|
||||||
|
parents,
|
||||||
|
(
|
||||||
|
entry.bytes.0 - entry.prev_bytes.0,
|
||||||
|
entry.bytes.1 - entry.prev_bytes.1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let circuit_id = Self::lookup_circuit_id(xdp_ip);
|
let circuit_id = Self::lookup_circuit_id(xdp_ip);
|
||||||
let mut entry = ThroughputEntry {
|
let mut entry = ThroughputEntry {
|
||||||
circuit_id: circuit_id.clone(),
|
circuit_id: circuit_id.clone(),
|
||||||
network_json_parents: Self::lookup_network_parents(circuit_id),
|
network_json_parents: Self::lookup_network_parents(circuit_id),
|
||||||
first_cycle: self.cycle,
|
first_cycle: self_cycle,
|
||||||
most_recent_cycle: 0,
|
most_recent_cycle: 0,
|
||||||
bytes: (0, 0),
|
bytes: (0, 0),
|
||||||
packets: (0, 0),
|
packets: (0, 0),
|
||||||
@ -160,25 +168,42 @@ impl ThroughputTracker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_rtt_data(&mut self) {
|
pub(crate) fn apply_rtt_data(&self) {
|
||||||
|
let self_cycle = self.cycle.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
rtt_for_each(&mut |raw_ip, rtt| {
|
rtt_for_each(&mut |raw_ip, rtt| {
|
||||||
if rtt.has_fresh_data != 0 {
|
if rtt.has_fresh_data != 0 {
|
||||||
let ip = XdpIpAddress(*raw_ip);
|
let ip = XdpIpAddress(*raw_ip);
|
||||||
if let Some(tracker) = self.raw_data.get_mut(&ip) {
|
if let Some(mut tracker) = self.raw_data.get_mut(&ip) {
|
||||||
tracker.recent_rtt_data = rtt.rtt;
|
tracker.recent_rtt_data = rtt.rtt;
|
||||||
tracker.last_fresh_rtt_data_cycle = self.cycle;
|
tracker.last_fresh_rtt_data_cycle = self_cycle;
|
||||||
|
if let Some(parents) = &tracker.network_json_parents {
|
||||||
|
let net_json = NETWORK_JSON.write().unwrap();
|
||||||
|
net_json.add_rtt_cycle(parents, tracker.median_latency());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_totals(&mut self, net_json: &mut NetworkJson) {
|
#[inline(always)]
|
||||||
self.bytes_per_second = (0, 0);
|
fn set_atomic_tuple_to_zero(tuple: &(AtomicU64, AtomicU64)) {
|
||||||
self.packets_per_second = (0, 0);
|
tuple.0.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||||
self.shaped_bytes_per_second = (0, 0);
|
tuple.1.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn add_atomic_tuple(tuple: &(AtomicU64, AtomicU64), n: (u64, u64)) {
|
||||||
|
tuple.0.fetch_add(n.0, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
tuple.1.fetch_add(n.1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_totals(&self) {
|
||||||
|
Self::set_atomic_tuple_to_zero(&self.bytes_per_second);
|
||||||
|
Self::set_atomic_tuple_to_zero(&self.packets_per_second);
|
||||||
|
Self::set_atomic_tuple_to_zero(&self.shaped_bytes_per_second);
|
||||||
self
|
self
|
||||||
.raw_data
|
.raw_data
|
||||||
.values()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
(
|
(
|
||||||
v.bytes.0.saturating_sub(v.prev_bytes.0),
|
v.bytes.0.saturating_sub(v.prev_bytes.0),
|
||||||
@ -186,181 +211,40 @@ impl ThroughputTracker {
|
|||||||
v.packets.0.saturating_sub(v.prev_packets.0),
|
v.packets.0.saturating_sub(v.prev_packets.0),
|
||||||
v.packets.1.saturating_sub(v.prev_packets.1),
|
v.packets.1.saturating_sub(v.prev_packets.1),
|
||||||
v.tc_handle.as_u32() > 0,
|
v.tc_handle.as_u32() > 0,
|
||||||
&v.network_json_parents,
|
|
||||||
v.median_latency(),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.for_each(
|
.for_each(|(bytes_down, bytes_up, packets_down, packets_up, shaped)| {
|
||||||
|(bytes_down, bytes_up, packets_down, packets_up, shaped, parents, median_rtt)| {
|
Self::add_atomic_tuple(&self.bytes_per_second, (bytes_down, bytes_up));
|
||||||
self.bytes_per_second.0 =
|
Self::add_atomic_tuple(&self.packets_per_second, (packets_down, packets_up));
|
||||||
self.bytes_per_second.0.checked_add(bytes_down).unwrap_or(0);
|
if shaped {
|
||||||
self.bytes_per_second.1 =
|
Self::add_atomic_tuple(&self.shaped_bytes_per_second, (bytes_down, bytes_up));
|
||||||
self.bytes_per_second.1.checked_add(bytes_up).unwrap_or(0);
|
}
|
||||||
self.packets_per_second.0 =
|
});
|
||||||
self.packets_per_second.0.checked_add(packets_down).unwrap_or(0);
|
|
||||||
self.packets_per_second.1 =
|
|
||||||
self.packets_per_second.1.checked_add(packets_up).unwrap_or(0);
|
|
||||||
if shaped {
|
|
||||||
self.shaped_bytes_per_second.0 = self
|
|
||||||
.shaped_bytes_per_second
|
|
||||||
.0
|
|
||||||
.checked_add(bytes_down)
|
|
||||||
.unwrap_or(0);
|
|
||||||
self.shaped_bytes_per_second.1 = self
|
|
||||||
.shaped_bytes_per_second
|
|
||||||
.1
|
|
||||||
.checked_add(bytes_up)
|
|
||||||
.unwrap_or(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have parent node data, we apply it now
|
|
||||||
if let Some(parents) = parents {
|
|
||||||
net_json.add_throughput_cycle(
|
|
||||||
parents,
|
|
||||||
(self.bytes_per_second.0, self.bytes_per_second.1),
|
|
||||||
median_rtt,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_cycle(&mut self) {
|
pub(crate) fn next_cycle(&self) {
|
||||||
self.cycle += 1;
|
self.cycle.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub(crate) fn tick(
|
|
||||||
// &mut self,
|
|
||||||
// value_dump: &[(XdpIpAddress, Vec<HostCounter>)],
|
|
||||||
// rtt: Result<Vec<([u8; 16], RttTrackingEntry)>>,
|
|
||||||
// ) -> Result<()> {
|
|
||||||
// // Copy previous byte/packet numbers and reset RTT data
|
|
||||||
// self.raw_data.iter_mut().for_each(|(_k, v)| {
|
|
||||||
// if v.first_cycle < self.cycle {
|
|
||||||
// v.bytes_per_second.0 = u64::checked_sub(v.bytes.0, v.prev_bytes.0).unwrap_or(0);
|
|
||||||
// v.bytes_per_second.1 = u64::checked_sub(v.bytes.1, v.prev_bytes.1).unwrap_or(0);
|
|
||||||
// v.packets_per_second.0 =
|
|
||||||
// u64::checked_sub(v.packets.0, v.prev_packets.0).unwrap_or(0);
|
|
||||||
// v.packets_per_second.1 =
|
|
||||||
// u64::checked_sub(v.packets.1, v.prev_packets.1).unwrap_or(0);
|
|
||||||
// v.prev_bytes = v.bytes;
|
|
||||||
// v.prev_packets = v.packets;
|
|
||||||
// }
|
|
||||||
// // Roll out stale RTT data
|
|
||||||
// if self.cycle > RETIRE_AFTER_SECONDS
|
|
||||||
// && v.last_fresh_rtt_data_cycle < self.cycle - RETIRE_AFTER_SECONDS
|
|
||||||
// {
|
|
||||||
// v.recent_rtt_data = [0; 60];
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// value_dump.iter().for_each(|(xdp_ip, counts)| {
|
|
||||||
// if let Some(entry) = self.raw_data.get_mut(xdp_ip) {
|
|
||||||
// entry.bytes = (0, 0);
|
|
||||||
// entry.packets = (0, 0);
|
|
||||||
// for c in counts {
|
|
||||||
// entry.bytes.0 += c.download_bytes;
|
|
||||||
// entry.bytes.1 += c.upload_bytes;
|
|
||||||
// entry.packets.0 += c.download_packets;
|
|
||||||
// entry.packets.1 += c.upload_packets;
|
|
||||||
// if c.tc_handle != 0 {
|
|
||||||
// entry.tc_handle = TcHandle::from_u32(c.tc_handle);
|
|
||||||
// }
|
|
||||||
// if c.last_seen != 0 {
|
|
||||||
// entry.last_seen = c.last_seen;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if entry.packets != entry.prev_packets {
|
|
||||||
// entry.most_recent_cycle = self.cycle;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// let mut entry = ThroughputEntry {
|
|
||||||
// first_cycle: self.cycle,
|
|
||||||
// most_recent_cycle: 0,
|
|
||||||
// bytes: (0, 0),
|
|
||||||
// packets: (0, 0),
|
|
||||||
// prev_bytes: (0, 0),
|
|
||||||
// prev_packets: (0, 0),
|
|
||||||
// bytes_per_second: (0, 0),
|
|
||||||
// packets_per_second: (0, 0),
|
|
||||||
// tc_handle: TcHandle::zero(),
|
|
||||||
// recent_rtt_data: [0; 60],
|
|
||||||
// last_fresh_rtt_data_cycle: 0,
|
|
||||||
// last_seen: 0,
|
|
||||||
// };
|
|
||||||
// for c in counts {
|
|
||||||
// entry.bytes.0 += c.download_bytes;
|
|
||||||
// entry.bytes.1 += c.upload_bytes;
|
|
||||||
// entry.packets.0 += c.download_packets;
|
|
||||||
// entry.packets.1 += c.upload_packets;
|
|
||||||
// if c.tc_handle != 0 {
|
|
||||||
// entry.tc_handle = TcHandle::from_u32(c.tc_handle);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// self.raw_data.insert(*xdp_ip, entry);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Apply RTT data
|
|
||||||
// if let Ok(rtt_dump) = rtt {
|
|
||||||
// for (raw_ip, rtt) in rtt_dump {
|
|
||||||
// if rtt.has_fresh_data != 0 {
|
|
||||||
// let ip = XdpIpAddress(raw_ip);
|
|
||||||
// if let Some(tracker) = self.raw_data.get_mut(&ip) {
|
|
||||||
// tracker.recent_rtt_data = rtt.rtt;
|
|
||||||
// tracker.last_fresh_rtt_data_cycle = self.cycle;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Update totals
|
|
||||||
// self.bytes_per_second = (0, 0);
|
|
||||||
// self.packets_per_second = (0, 0);
|
|
||||||
// self.shaped_bytes_per_second = (0, 0);
|
|
||||||
// self.raw_data
|
|
||||||
// .iter()
|
|
||||||
// .map(|(_k, v)| {
|
|
||||||
// (
|
|
||||||
// v.bytes.0 - v.prev_bytes.0,
|
|
||||||
// v.bytes.1 - v.prev_bytes.1,
|
|
||||||
// v.packets.0 - v.prev_packets.0,
|
|
||||||
// v.packets.1 - v.prev_packets.1,
|
|
||||||
// v.tc_handle.as_u32() > 0,
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// .for_each(|(bytes_down, bytes_up, packets_down, packets_up, shaped)| {
|
|
||||||
// self.bytes_per_second.0 += bytes_down;
|
|
||||||
// self.bytes_per_second.1 += bytes_up;
|
|
||||||
// self.packets_per_second.0 += packets_down;
|
|
||||||
// self.packets_per_second.1 += packets_up;
|
|
||||||
// if shaped {
|
|
||||||
// self.shaped_bytes_per_second.0 += bytes_down;
|
|
||||||
// self.shaped_bytes_per_second.1 += bytes_up;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Onto the next cycle
|
|
||||||
// self.cycle += 1;
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub(crate) fn bits_per_second(&self) -> (u64, u64) {
|
pub(crate) fn bits_per_second(&self) -> (u64, u64) {
|
||||||
(self.bytes_per_second.0 * 8, self.bytes_per_second.1 * 8)
|
(self.bytes_per_second.0.load(std::sync::atomic::Ordering::Relaxed) * 8, self.bytes_per_second.1.load(std::sync::atomic::Ordering::Relaxed) * 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn shaped_bits_per_second(&self) -> (u64, u64) {
|
pub(crate) fn shaped_bits_per_second(&self) -> (u64, u64) {
|
||||||
(self.shaped_bytes_per_second.0 * 8, self.shaped_bytes_per_second.1 * 8)
|
(self.shaped_bytes_per_second.0.load(std::sync::atomic::Ordering::Relaxed) * 8, self.shaped_bytes_per_second.1.load(std::sync::atomic::Ordering::Relaxed) * 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn packets_per_second(&self) -> (u64, u64) {
|
pub(crate) fn packets_per_second(&self) -> (u64, u64) {
|
||||||
self.packets_per_second
|
(
|
||||||
|
self.packets_per_second.0.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
self.packets_per_second.1.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn dump(&self) {
|
pub(crate) fn dump(&self) {
|
||||||
for (k, v) in self.raw_data.iter() {
|
for v in self.raw_data.iter() {
|
||||||
let ip = k.as_ip();
|
let ip = v.key().as_ip();
|
||||||
log::info!("{:<34}{:?}", ip, v.tc_handle);
|
log::info!("{:<34}{:?}", ip, v.tc_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user