mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-10-24 13:57:40 -05:00
7 files changed
+112
-20
No files matched your search
@@ -178,6 +178,7 @@ src/network.large.json
|
||||
/src/topology_effective_state.json
|
||||
/src/topology_overrides.json
|
||||
/src/topology_parent_candidates.json
|
||||
/src/topology_canonical_state.json
|
||||
|
||||
# Migrated integration compatibility artifacts
|
||||
/src/integrationUISPbandwidths.csv.migrated-*
|
||||
|
||||
@@ -24,6 +24,7 @@ from deepdiff import DeepDiff
|
||||
from virtual_tree_nodes import (
|
||||
build_logical_to_physical_node_map,
|
||||
build_physical_network,
|
||||
collect_physical_parent_node_aliases,
|
||||
is_virtual_node,
|
||||
)
|
||||
from shaping_skip_report import (
|
||||
@@ -1210,6 +1211,23 @@ def refreshShapers():
|
||||
return newDict
|
||||
network = flattenA(network, 1)
|
||||
|
||||
# After flattening, some attachment-style names may only survive as
|
||||
# metadata on the physical node that now owns that branch.
|
||||
parent_node_aliases = collect_physical_parent_node_aliases(network)
|
||||
for circuit in subscriberCircuits:
|
||||
parent_node = str(circuit.get('ParentNode', '') or '').strip()
|
||||
if not parent_node or parent_node == 'none':
|
||||
continue
|
||||
resolved_parent = parent_node_aliases.get(parent_node)
|
||||
if resolved_parent and resolved_parent != parent_node:
|
||||
logging.info(
|
||||
"Resolved circuit parent alias '%s' to physical queue node '%s' for circuit '%s'",
|
||||
parent_node,
|
||||
resolved_parent,
|
||||
circuit.get('circuitID', ''),
|
||||
)
|
||||
circuit['ParentNode'] = resolved_parent
|
||||
|
||||
# Group circuits by parent node. Reduces runtime for section below this one.
|
||||
circuits_by_parent_node = {}
|
||||
circuit_min_down_combined_by_parent_node = {}
|
||||
|
||||
@@ -4,19 +4,10 @@ import {lerpGreenToRedViaOrange} from "../helpers/scaling";
|
||||
export class CpuHistogram extends DashboardGraph {
|
||||
constructor(id) {
|
||||
super(id);
|
||||
let d = [];
|
||||
let axis = [];
|
||||
for (let i=0; i<20; i++) {
|
||||
d.push({
|
||||
value: i,
|
||||
itemStyle: {color: lerpGreenToRedViaOrange(20-i, 20)},
|
||||
});
|
||||
axis.push(i.toString());
|
||||
}
|
||||
this.option = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: axis,
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
@@ -24,7 +15,7 @@ export class CpuHistogram extends DashboardGraph {
|
||||
max: 100,
|
||||
},
|
||||
series: {
|
||||
data: d,
|
||||
data: [],
|
||||
type: 'bar',
|
||||
}
|
||||
};
|
||||
@@ -33,14 +24,12 @@ export class CpuHistogram extends DashboardGraph {
|
||||
|
||||
update(cpu) {
|
||||
this.chart.hideLoading();
|
||||
this.option.series.data = [];
|
||||
for (let i=0; i<cpu.length; i++) {
|
||||
this.option.series.data.push({
|
||||
value: cpu[i],
|
||||
itemStyle: {color: lerpGreenToRedViaOrange(100-cpu[i], 100)},
|
||||
});
|
||||
}
|
||||
const usage = Array.isArray(cpu) ? cpu : [];
|
||||
this.option.xAxis.data = usage.map((_, index) => index.toString());
|
||||
this.option.series.data = usage.map((value) => ({
|
||||
value,
|
||||
itemStyle: {color: lerpGreenToRedViaOrange(100 - value, 100)},
|
||||
}));
|
||||
this.chart.setOption(this.option);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::node_manager::local_api::ethernet_caps::ethernet_advisory_for_circuit;
|
||||
use crate::shaped_devices_tracker::resolve_parent_node_alias;
|
||||
use crate::shaped_devices_tracker::SHAPED_DEVICES;
|
||||
use lqos_config::{CircuitEthernetMetadata, ShapedDevice};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -31,6 +32,12 @@ pub fn circuit_by_id_data(id: &str) -> Option<CircuitByIdData> {
|
||||
.iter()
|
||||
.filter(|d| d.circuit_id.to_lowercase().trim() == safe_id)
|
||||
.cloned()
|
||||
.map(|mut device| {
|
||||
if let Some(resolved_parent) = resolve_parent_node_alias(&device.parent_node) {
|
||||
device.parent_node = resolved_parent;
|
||||
}
|
||||
device
|
||||
})
|
||||
.collect();
|
||||
|
||||
if devices.is_empty() {
|
||||
|
||||
@@ -491,6 +491,27 @@ pub fn map_node_names(nodes: &[usize]) -> BusResponse {
|
||||
BusResponse::NodeNames(result)
|
||||
}
|
||||
|
||||
pub fn resolve_parent_node_alias(parent_node: &str) -> Option<String> {
|
||||
let trimmed = parent_node.trim();
|
||||
if trimmed.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let reader = NETWORK_JSON.read();
|
||||
let nodes = reader.get_nodes_when_ready();
|
||||
|
||||
if nodes.iter().any(|node| node.name == trimmed) {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
|
||||
nodes.iter().find_map(|node| {
|
||||
node.active_attachment_name
|
||||
.as_deref()
|
||||
.filter(|alias| alias.trim() == trimmed)
|
||||
.map(|_| node.name.clone())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_funnel(circuit_id: &str) -> BusResponse {
|
||||
let reader = NETWORK_JSON.read();
|
||||
if let Some(index) = reader.get_index_for_name(circuit_id) {
|
||||
@@ -553,7 +574,10 @@ pub fn get_all_circuits() -> BusResponse {
|
||||
circuit_name = Some(device.circuit_name.clone());
|
||||
device_id = Some(device.device_id.clone());
|
||||
device_name = Some(device.device_name.clone());
|
||||
parent_node = Some(device.parent_node.clone());
|
||||
parent_node = Some(
|
||||
resolve_parent_node_alias(&device.parent_node)
|
||||
.unwrap_or_else(|| device.parent_node.clone()),
|
||||
);
|
||||
plan.down = device.download_max_mbps.round();
|
||||
plan.up = device.upload_max_mbps.round();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import unittest
|
||||
from virtual_tree_nodes import (
|
||||
build_logical_to_physical_node_map,
|
||||
build_physical_network,
|
||||
collect_physical_parent_node_aliases,
|
||||
is_virtual_node,
|
||||
)
|
||||
|
||||
@@ -75,6 +76,24 @@ class TestVirtualTreeNodes(unittest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
build_physical_network(logical)
|
||||
|
||||
def test_collect_physical_parent_node_aliases_includes_active_attachment_name(self):
|
||||
physical = {
|
||||
"Hoodoo Hill": {
|
||||
"children": {
|
||||
"Thunderhill Core": {
|
||||
"name": "Thunderhill Core",
|
||||
"active_attachment_name": "Thunderhill-Hoodoo",
|
||||
"children": {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aliases = collect_physical_parent_node_aliases(physical)
|
||||
|
||||
self.assertEqual(aliases["Thunderhill Core"], "Thunderhill Core")
|
||||
self.assertEqual(aliases["Thunderhill-Hoodoo"], "Thunderhill Core")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -91,3 +91,37 @@ def build_physical_network(logical_network):
|
||||
|
||||
return physical
|
||||
|
||||
|
||||
def collect_physical_parent_node_aliases(network):
|
||||
"""
|
||||
Returns a mapping of acceptable parent-node aliases to the physical node key
|
||||
that exists in the shaping tree.
|
||||
|
||||
This lets shaping-time code resolve attachment-style names that may survive
|
||||
in `ShapedDevices.csv` even after the physical tree is flattened or promoted.
|
||||
"""
|
||||
aliases = {}
|
||||
|
||||
def recurse(level):
|
||||
if not isinstance(level, dict):
|
||||
return
|
||||
|
||||
for key, node in level.items():
|
||||
if not isinstance(key, str) or not isinstance(node, dict):
|
||||
continue
|
||||
|
||||
candidates = [key, node.get("name"), node.get("active_attachment_name")]
|
||||
for candidate in candidates:
|
||||
if candidate is None:
|
||||
continue
|
||||
alias = str(candidate).strip()
|
||||
if not alias:
|
||||
continue
|
||||
aliases.setdefault(alias, key)
|
||||
|
||||
children = node.get("children")
|
||||
if isinstance(children, dict):
|
||||
recurse(children)
|
||||
|
||||
recurse(network)
|
||||
return aliases
|
||||
Reference in new issue
Block a user