Initial tree and circuit capacity dashlet displays. Quite Ugly.

This commit is contained in:
Herbert Wolverson 2024-07-16 15:58:07 -05:00
parent afda5d3bdf
commit 9248ae469b
4 changed files with 164 additions and 4 deletions

View File

@ -0,0 +1,75 @@
import {BaseDashlet} from "./base_dashlet";
import {clearDashDiv, simpleRow, simpleRowHtml, theading} from "../helpers/builders";
import {scaleNumber, scaleNanos, formatRtt} from "../helpers/scaling";
import {redactCell} from "../helpers/redact";
export class CircuitCapacityDash extends BaseDashlet {
constructor(slot) {
super(slot);
}
title() {
return "Circuits At Capacity";
}
tooltip() {
return "<h5>Circuits at Capacity</h5><p>Customer circuits using close to their maximum capacities, and possibly in need of an upsell.</p>";
}
subscribeTo() {
return [ "CircuitCapacity" ];
}
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
return base;
}
setup() {
super.setup();
}
onMessage(msg) {
if (msg.event === "CircuitCapacity") {
let target = document.getElementById(this.id);
let table = document.createElement("table");
table.classList.add("table", "table-striped", "small");
let thead = document.createElement("thead");
thead.classList.add("small");
thead.appendChild(theading("Circuit"));
thead.appendChild(theading("% Utilization (DL)"));
thead.appendChild(theading("% Utilization (UL)"));
thead.appendChild(theading("RTT"));
table.appendChild(thead);
let tbody = document.createElement("tbody");
msg.data.forEach((c) => {
if (c.capacity[0] < 0.9 && c.capacity[1] < 0.9) {
return;
}
let row = document.createElement("tr");
row.classList.add("small");
let linkCol = document.createElement("td");
let link = document.createElement("a");
link.href = "circuit.html?id=" + encodeURI(c.circuit_id);
link.innerText = c.circuit_name;
redactCell(link);
linkCol.appendChild(link);
row.appendChild(linkCol);
row.appendChild(simpleRow((c.capacity[0]*100).toFixed(0)));
row.appendChild(simpleRow((c.capacity[1]*100).toFixed(0)));
row.appendChild(simpleRowHtml(formatRtt(c.rtt)));
tbody.appendChild(row);
})
table.appendChild(tbody);
// Display it
clearDashDiv(this.id, target);
target.appendChild(table);
}
}
}

View File

@ -18,6 +18,8 @@ import {TopTreeSummary} from "./top_tree_summary";
import {CombinedTopDashlet} from "./combined_top_dash";
import {RttHisto3dDash} from "./rtt_histo3d_dash";
import {QueueStatsTotalDash} from "./queue_stats_total";
import {TreeCapacityDash} from "./tree_capacity_dash";
import {CircuitCapacityDash} from "./circuit_capacity_dash";
export const DashletMenu = [
{ name: "Throughput Bits/Second", tag: "throughputBps", size: 3 },
@ -40,6 +42,8 @@ export const DashletMenu = [
{ name: "Combined Top 10 Box", tag: "combinedTop10", size: 3 },
{ name: "Total Cake Stats", tag: "totalCakeStats", size: 3 },
{ name: "Round-Trip Time Histogram 3D", tag: "rttHistogram3D", size: 12 },
{ name: "Circuits At Capacity", tag: "circuitCapacity", size: 6 },
{ name: "Tree Nodes At Capacity", tag: "treeCapacity", size: 6 },
];
export function widgetFactory(widgetName, count) {
@ -65,6 +69,8 @@ export function widgetFactory(widgetName, count) {
case "treeSummary" : widget = new TopTreeSummary(count); break;
case "combinedTop10" : widget = new CombinedTopDashlet(count); break;
case "totalCakeStats" : widget = new QueueStatsTotalDash(count); break;
case "circuitCapacity" : widget = new CircuitCapacityDash(count); break;
case "treeCapacity" : widget = new TreeCapacityDash(count); break;
default: {
console.log("I don't know how to construct a widget of type [" + widgetName + "]");
return null;

View File

@ -0,0 +1,79 @@
import {BaseDashlet} from "./base_dashlet";
import {clearDashDiv, simpleRow, simpleRowHtml, theading} from "../helpers/builders";
import {scaleNumber, scaleNanos, formatRtt} from "../helpers/scaling";
export class TreeCapacityDash extends BaseDashlet {
constructor(slot) {
super(slot);
}
title() {
return "Tree Nodes At Capacity";
}
tooltip() {
return "<h5>Tree Nodes at Capacity</h5><p>Distribution Nodes approaching their maximum capacity, possibly in need of an upgrade or a better shaping policy.</p>";
}
subscribeTo() {
return [ "TreeCapacity" ];
}
buildContainer() {
let base = super.buildContainer();
base.style.height = "250px";
base.style.overflow = "auto";
return base;
}
setup() {
super.setup();
}
onMessage(msg) {
if (msg.event === "TreeCapacity") {
//console.log(msg.data);
let target = document.getElementById(this.id);
let table = document.createElement("table");
table.classList.add("table", "table-striped", "small");
let thead = document.createElement("thead");
thead.classList.add("small");
thead.appendChild(theading("Node"));
thead.appendChild(theading("% Utilization (DL)"));
thead.appendChild(theading("% Utilization (UL)"));
thead.appendChild(theading("RTT"));
table.appendChild(thead);
let tbody = document.createElement("tbody");
msg.data.forEach((node) => {
if (node.max_down === 0 || node.max_up === 0) {
// No divisions by zero
return;
}
let down = node.down / node.max_down;
let up = node.up / node.max_up;
if (down < 0.75 && up < 0.75) {
// Not at capacity
return;
}
let row = document.createElement("tr");
row.classList.add("small");
row.appendChild(simpleRow(node.name));
row.appendChild(simpleRow((down*100).toFixed(0)));
row.appendChild(simpleRow((up*100).toFixed(0)));
row.appendChild(simpleRowHtml(formatRtt(node.rtt)));
tbody.appendChild(row);
});
table.appendChild(tbody);
// Display it
clearDashDiv(this.id, target);
target.appendChild(table);
}
}
}

View File

@ -33,13 +33,13 @@ pub async fn circuit_capacity(channels: Arc<PubSub>) {
THROUGHPUT_TRACKER.raw_data.iter().for_each(|c| {
if let Some(circuit_id) = &c.circuit_id {
if let Some(accumulator) = circuits.get_mut(circuit_id) {
accumulator.bytes += c.bytes;
accumulator.bytes += c.bytes_per_second;
if let Some(latency) = c.median_latency() {
accumulator.median_rtt = latency;
}
} else {
circuits.insert(circuit_id.clone(), CircuitAccumulator {
bytes: c.bytes,
bytes: c.bytes_per_second,
median_rtt: c.median_latency().unwrap_or(0.0),
});
}
@ -51,9 +51,9 @@ pub async fn circuit_capacity(channels: Arc<PubSub>) {
let shaped_devices = SHAPED_DEVICES.read().unwrap();
circuits.iter().filter_map(|(circuit_id, accumulator)| {
if let Some(device) = shaped_devices.devices.iter().find(|sd| sd.circuit_id == *circuit_id) {
let down_mbps = accumulator.bytes.down as f64 * 8.0 / 1_000_000.0;
let down_mbps = (accumulator.bytes.down as f64 * 8.0) / 1_000_000.0;
let down = down_mbps / device.download_max_mbps as f64;
let up_mbps = accumulator.bytes.up as f64 * 8.0 / 1_000_000.0;
let up_mbps = (accumulator.bytes.up as f64 * 8.0) / 1_000_000.0;
let up = up_mbps / device.upload_max_mbps as f64;
Some(Capacity {