From 0fecadbc7f0f4615f55fc0f5f74bbca946d1ab2a Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Thu, 27 Jun 2024 11:57:39 -0500 Subject: [PATCH] Add geo endpoints, ip protocol, ether protocol, worst 10 on retransmits. --- .../js_build/src/dashlets/dashboard.js | 1 + .../js_build/src/dashlets/dashlet_index.js | 12 +++ .../src/dashlets/endpoints_by_country.js | 79 ++++++++++++++++ .../js_build/src/dashlets/ether_protocols.js | 76 ++++++++++++++++ .../js_build/src/dashlets/ip_protocols.js | 64 +++++++++++++ .../src/dashlets/worst10_retransmits.js | 90 +++++++++++++++++++ .../js_build/src/helpers/builders.js | 6 ++ .../src/node_manager/ws/published_channels.rs | 4 + src/rust/lqosd/src/node_manager/ws/ticker.rs | 5 ++ .../node_manager/ws/ticker/flow_endpoints.rs | 64 +++++++++++++ .../src/node_manager/ws/ticker/top_10.rs | 22 +++++ 11 files changed, 423 insertions(+) create mode 100644 src/rust/lqosd/src/node_manager/js_build/src/dashlets/endpoints_by_country.js create mode 100644 src/rust/lqosd/src/node_manager/js_build/src/dashlets/ether_protocols.js create mode 100644 src/rust/lqosd/src/node_manager/js_build/src/dashlets/ip_protocols.js create mode 100644 src/rust/lqosd/src/node_manager/js_build/src/dashlets/worst10_retransmits.js create mode 100644 src/rust/lqosd/src/node_manager/ws/ticker/flow_endpoints.rs diff --git a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashboard.js b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashboard.js index d03c051d..0a0250d3 100644 --- a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashboard.js +++ b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashboard.js @@ -439,6 +439,7 @@ export class Dashboard { list.size = DashletMenu.length; list.style.width = "100%"; list.classList.add("listBox"); + list.size = 10; DashletMenu.forEach((d) => { let entry = document.createElement("option"); entry.value = d.tag; diff --git a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashlet_index.js b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashlet_index.js index a3814486..e6fd1686 100644 --- a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashlet_index.js +++ b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/dashlet_index.js @@ -8,6 +8,10 @@ import {Top10Downloaders} from "./top10_downloaders"; import {Worst10Downloaders} from "./worst10_downloaders"; import {Top10FlowsBytes} from "./top10flows_bytes"; import {Top10FlowsRate} from "./top10flows_rate"; +import {Top10EndpointsByCountry} from "./endpoints_by_country"; +import {EtherProtocols} from "./ether_protocols"; +import {IpProtocols} from "./ip_protocols"; +import {Worst10Retransmits} from "./worst10_retransmits"; export const DashletMenu = [ { name: "Throughput Bits/Second", tag: "throughputBps", size: 3 }, @@ -18,8 +22,12 @@ export const DashletMenu = [ { name: "Round-Trip Time Histogram", tag: "rttHistogram", size: 6 }, { name: "Top 10 Downloaders", tag: "top10downloaders", size: 6 }, { name: "Worst 10 Round-Trip Time", tag: "worst10downloaders", size: 6 }, + { name: "Worst 10 Retransmits", tag: "worst10retransmits", size: 6 }, { name: "Top 10 Flows (total bytes)", tag: "top10flowsBytes", size: 6 }, { name: "Top 10 Flows (rate)", tag: "top10flowsRate", size: 6 }, + { name: "Top 10 Endpoints by Country", tag: "top10endpointsCountry", size: 6 }, + { name: "Ether Protocols", tag: "etherProtocols", size: 6 }, + { name: "IP Protocols", tag: "ipProtocols", size: 6 }, ]; export function widgetFactory(widgetName, count) { @@ -33,8 +41,12 @@ export function widgetFactory(widgetName, count) { case "rttHistogram": widget = new RttHistoDash(count); break; case "top10downloaders":widget = new Top10Downloaders(count); break; case "worst10downloaders":widget = new Worst10Downloaders(count); break; + case "worst10retransmits":widget = new Worst10Retransmits(count); break; case "top10flowsBytes" : widget = new Top10FlowsBytes(count); break; case "top10flowsRate" : widget = new Top10FlowsRate(count); break; + case "top10endpointsCountry" : widget = new Top10EndpointsByCountry(count); break; + case "etherProtocols" : widget = new EtherProtocols(count); break; + case "ipProtocols" : widget = new IpProtocols(count); break; default: { console.log("I don't know how to construct a widget of type [" + widgetName + "]"); return null; diff --git a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/endpoints_by_country.js b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/endpoints_by_country.js new file mode 100644 index 00000000..afc556f8 --- /dev/null +++ b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/endpoints_by_country.js @@ -0,0 +1,79 @@ +import {BaseDashlet} from "./base_dashlet"; +import {theading} from "../helpers/builders"; +import {scaleNumber, scaleNanos} from "../helpers/scaling"; + +export class Top10EndpointsByCountry extends BaseDashlet { + constructor(slot) { + super(slot); + } + + title() { + return "Endpoints by Country"; + } + + subscribeTo() { + return [ "EndpointsByCountry" ]; + } + + buildContainer() { + let base = super.buildContainer(); + base.style.height = "250px"; + base.style.overflow = "auto"; + return base; + } + + setup() { + super.setup(); + } + + onMessage(msg) { + if (msg.event === "EndpointsByCountry") { + let target = document.getElementById(this.id); + + let t = document.createElement("table"); + t.classList.add("table", "table-striped", "tiny"); + + let th = document.createElement("thead"); + th.appendChild(theading("Country")); + th.appendChild(theading("DL ⬇️")); + th.appendChild(theading("UL ⬆️")); + th.appendChild(theading("⬇ RTT")); + th.appendChild(theading("️️⬆ RTT")); + t.appendChild(th); + + let tbody = document.createElement("tbody"); + msg.data.forEach((r) => { + let row = document.createElement("tr"); + + let country = document.createElement("td"); + country.innerText = r[0]; + row.appendChild(country); + + let dld = document.createElement("td"); + dld.innerText = scaleNumber(r[1][0]); + row.appendChild(dld); + + let dlu = document.createElement("td"); + dlu.innerText = scaleNumber(r[1][1]); + row.appendChild(dlu); + + let rttd = document.createElement("td"); + rttd.innerText = scaleNanos(r[2][0]); + row.appendChild(rttd); + + let rttu = document.createElement("td"); + rttu.innerText = scaleNanos(r[2][1]); + row.appendChild(rttu); + + t.appendChild(row); + }); + t.appendChild(tbody); + + // Display it + while (target.children.length > 1) { + target.removeChild(target.lastChild); + } + target.appendChild(t); + } + } +} \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/ether_protocols.js b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/ether_protocols.js new file mode 100644 index 00000000..6d895e59 --- /dev/null +++ b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/ether_protocols.js @@ -0,0 +1,76 @@ +import {BaseDashlet} from "./base_dashlet"; +import {simpleRow, theading} from "../helpers/builders"; +import {scaleNumber, scaleNanos} from "../helpers/scaling"; + +export class EtherProtocols extends BaseDashlet { + constructor(slot) { + super(slot); + } + + title() { + return "Ethernet Protocols"; + } + + subscribeTo() { + return [ "EtherProtocols" ]; + } + + buildContainer() { + let base = super.buildContainer(); + base.style.height = "250px"; + base.style.overflow = "auto"; + return base; + } + + setup() { + super.setup(); + } + + onMessage(msg) { + if (msg.event === "EtherProtocols") { + let target = document.getElementById(this.id); + + let t = document.createElement("table"); + t.classList.add("table", "table-striped", "tiny"); + + let th = document.createElement("thead"); + th.appendChild(theading("Protocol")); + th.appendChild(theading("DL ⬇️")); + th.appendChild(theading("UL ⬆️")); + th.appendChild(theading("Packets ⬇️")); + th.appendChild(theading("Packets ⬆️")); + th.appendChild(theading("⬇ RTT")); + th.appendChild(theading("️️⬆ RTT")); + t.appendChild(th); + + let tbody = document.createElement("tbody"); + let row = document.createElement("tr"); + row.appendChild(simpleRow("IPv4")); + row.appendChild(simpleRow(scaleNumber(msg.data.v4_bytes[0]))); + row.appendChild(simpleRow(scaleNumber(msg.data.v4_bytes[1]))); + row.appendChild(simpleRow(scaleNumber(msg.data.v4_packets[0]))); + row.appendChild(simpleRow(scaleNumber(msg.data.v4_packets[1]))); + row.appendChild(simpleRow(scaleNanos(msg.data.v4_rtt[0]))); + row.appendChild(simpleRow(scaleNanos(msg.data.v4_rtt[1]))); + t.appendChild(row); + + row = document.createElement("tr"); + row.appendChild(simpleRow("IPv6")); + row.appendChild(simpleRow(scaleNumber(msg.data.v6_bytes[0]))); + row.appendChild(simpleRow(scaleNumber(msg.data.v6_bytes[1]))); + row.appendChild(simpleRow(scaleNumber(msg.data.v6_packets[0]))); + row.appendChild(simpleRow(scaleNumber(msg.data.v6_packets[1]))); + row.appendChild(simpleRow(scaleNanos(msg.data.v6_rtt[0]))); + row.appendChild(simpleRow(scaleNanos(msg.data.v6_rtt[1]))); + t.appendChild(row); + + t.appendChild(tbody); + + // Display it + while (target.children.length > 1) { + target.removeChild(target.lastChild); + } + target.appendChild(t); + } + } +} \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/ip_protocols.js b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/ip_protocols.js new file mode 100644 index 00000000..76c83e23 --- /dev/null +++ b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/ip_protocols.js @@ -0,0 +1,64 @@ +import {BaseDashlet} from "./base_dashlet"; +import {simpleRow, theading} from "../helpers/builders"; +import {scaleNumber, scaleNanos} from "../helpers/scaling"; + +export class IpProtocols extends BaseDashlet { + constructor(slot) { + super(slot); + } + + title() { + return "IP Protocols"; + } + + subscribeTo() { + return [ "IpProtocols" ]; + } + + buildContainer() { + let base = super.buildContainer(); + base.style.height = "250px"; + base.style.overflow = "auto"; + return base; + } + + setup() { + super.setup(); + } + + onMessage(msg) { + if (msg.event === "IpProtocols") { + let target = document.getElementById(this.id); + + let t = document.createElement("table"); + t.classList.add("table", "table-striped", "tiny"); + + let th = document.createElement("thead"); + th.appendChild(theading("Protocol")); + th.appendChild(theading("DL ⬇️")); + th.appendChild(theading("UL ⬆️")); + t.appendChild(th); + + let tbody = document.createElement("tbody"); + + msg.data.forEach((r) => { + let row = document.createElement("tr"); + + row.appendChild(simpleRow(r[0])); + row.appendChild(simpleRow(scaleNumber(r[1][0]))); + row.appendChild(simpleRow(scaleNumber(r[1][1]))); + + t.appendChild(row); + }); + + + t.appendChild(tbody); + + // Display it + while (target.children.length > 1) { + target.removeChild(target.lastChild); + } + target.appendChild(t); + } + } +} \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/js_build/src/dashlets/worst10_retransmits.js b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/worst10_retransmits.js new file mode 100644 index 00000000..f34e6fc2 --- /dev/null +++ b/src/rust/lqosd/src/node_manager/js_build/src/dashlets/worst10_retransmits.js @@ -0,0 +1,90 @@ +import {BaseDashlet} from "./base_dashlet"; +import {RttHistogram} from "../graphs/rtt_histo"; +import {theading} from "../helpers/builders"; +import {scaleNumber, rttCircleSpan} from "../helpers/scaling"; + +export class Worst10Retransmits extends BaseDashlet { + constructor(slot) { + super(slot); + } + + title() { + return "Worst 10 TCP Re-transmits"; + } + + subscribeTo() { + return [ "WorstRetransmits" ]; + } + + buildContainer() { + let base = super.buildContainer(); + base.style.height = "250px"; + base.style.overflow = "auto"; + return base; + } + + setup() { + super.setup(); + } + + onMessage(msg) { + if (msg.event === "WorstRetransmits") { + let target = document.getElementById(this.id); + + let t = document.createElement("table"); + t.classList.add("table", "table-striped", "tiny"); + + let th = document.createElement("thead"); + th.appendChild(theading("")); + th.appendChild(theading("IP Address/Circuit")); + th.appendChild(theading("DL ⬇️")); + th.appendChild(theading("UL ⬆️")); + th.appendChild(theading("RTT (ms)")); + th.appendChild(theading("TCP Retransmits")); + th.appendChild(theading("Shaped")); + t.appendChild(th); + + let tbody = document.createElement("tbody"); + msg.data.forEach((r) => { + let row = document.createElement("tr"); + + let circle = document.createElement("td"); + circle.appendChild(rttCircleSpan(r.median_tcp_rtt)); + row.appendChild(circle); + + let ip = document.createElement("td"); + ip.innerText = r.ip_address; + row.append(ip); + + let dl = document.createElement("td"); + dl.innerText = scaleNumber(r.bits_per_second[0]); + row.append(dl); + + let ul = document.createElement("td"); + ul.innerText = scaleNumber(r.bits_per_second[1]); + row.append(ul); + + let rtt = document.createElement("td"); + rtt.innerText = r.median_tcp_rtt.toFixed(2); + row.append(rtt); + + let tcp_xmit = document.createElement("td"); + tcp_xmit.innerText = r.tcp_retransmits[0] + " / " + r.tcp_retransmits[1]; + row.append(tcp_xmit); + + let shaped = document.createElement("td"); + shaped.innerText = r.plan[0] + " / " + r.plan[1]; + row.append(shaped); + + t.appendChild(row); + }); + t.appendChild(tbody); + + // Display it + while (target.children.length > 1) { + target.removeChild(target.lastChild); + } + target.appendChild(t); + } + } +} \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/js_build/src/helpers/builders.js b/src/rust/lqosd/src/node_manager/js_build/src/helpers/builders.js index 85838e3c..e5ce5e10 100644 --- a/src/rust/lqosd/src/node_manager/js_build/src/helpers/builders.js +++ b/src/rust/lqosd/src/node_manager/js_build/src/helpers/builders.js @@ -8,4 +8,10 @@ export function theading(text) { let th = document.createElement("th"); th.innerText = text; return th; +} + +export function simpleRow(text) { + let td = document.createElement("td"); + td.innerText = text; + return td; } \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/ws/published_channels.rs b/src/rust/lqosd/src/node_manager/ws/published_channels.rs index c225e0c8..9dd69a3a 100644 --- a/src/rust/lqosd/src/node_manager/ws/published_channels.rs +++ b/src/rust/lqosd/src/node_manager/ws/published_channels.rs @@ -9,6 +9,10 @@ pub enum PublishedChannels { FlowCount, TopDownloads, WorstRTT, + WorstRetransmits, TopFlowsBytes, TopFlowsRate, + EndpointsByCountry, + EtherProtocols, + IpProtocols, } diff --git a/src/rust/lqosd/src/node_manager/ws/ticker.rs b/src/rust/lqosd/src/node_manager/ws/ticker.rs index fcf99e02..6fbd4d1a 100644 --- a/src/rust/lqosd/src/node_manager/ws/ticker.rs +++ b/src/rust/lqosd/src/node_manager/ws/ticker.rs @@ -5,6 +5,7 @@ mod flow_counter; mod top_10; mod ipstats_conversion; mod top_flows; +mod flow_endpoints; use std::sync::Arc; use crate::node_manager::ws::publish_subscribe::PubSub; @@ -22,8 +23,12 @@ pub(super) async fn channel_ticker(channels: Arc) { flow_counter::flow_count(channels.clone()), top_10::top_10_downloaders(channels.clone()), top_10::worst_10_downloaders(channels.clone()), + top_10::worst_10_retransmit(channels.clone()), top_flows::top_flows_bytes(channels.clone()), top_flows::top_flows_rate(channels.clone()), + flow_endpoints::endpoints_by_country(channels.clone()), + flow_endpoints::ether_protocols(channels.clone()), + flow_endpoints::ip_protocols(channels.clone()), ); } } \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/ws/ticker/flow_endpoints.rs b/src/rust/lqosd/src/node_manager/ws/ticker/flow_endpoints.rs new file mode 100644 index 00000000..71325839 --- /dev/null +++ b/src/rust/lqosd/src/node_manager/ws/ticker/flow_endpoints.rs @@ -0,0 +1,64 @@ +use std::sync::Arc; + +use serde_json::json; + +use lqos_bus::BusResponse; + +use crate::node_manager::ws::publish_subscribe::PubSub; +use crate::node_manager::ws::published_channels::PublishedChannels; +use crate::throughput_tracker; + +pub async fn endpoints_by_country(channels: Arc) { + if !channels.is_channel_alive(PublishedChannels::EndpointsByCountry).await { + return; + } + + if let BusResponse::CurrentEndpointsByCountry(countries) = throughput_tracker::current_endpoints_by_country() { + let message = json!( + { + "event": PublishedChannels::EndpointsByCountry.to_string(), + "data": countries, + } + ).to_string(); + channels.send(PublishedChannels::EndpointsByCountry, message).await; + } +} + +pub async fn ether_protocols(channels: Arc) { + if !channels.is_channel_alive(PublishedChannels::EtherProtocols).await { + return; + } + + if let BusResponse::EtherProtocols { v4_bytes, v6_bytes, v4_packets, v6_packets, v4_rtt, v6_rtt } = throughput_tracker::ether_protocol_summary() { + let message = json!( + { + "event": PublishedChannels::EtherProtocols.to_string(), + "data": { + "v4_bytes": v4_bytes, + "v6_bytes": v6_bytes, + "v4_packets": v4_packets, + "v6_packets": v6_packets, + "v4_rtt": v4_rtt, + "v6_rtt": v6_rtt, + }, + } + ).to_string(); + channels.send(PublishedChannels::EtherProtocols, message).await; + } +} + +pub async fn ip_protocols(channels: Arc) { + if !channels.is_channel_alive(PublishedChannels::IpProtocols).await { + return; + } + + if let BusResponse::IpProtocols(ip_data) = throughput_tracker::ip_protocol_summary() { + let message = json!( + { + "event": PublishedChannels::IpProtocols.to_string(), + "data": ip_data, + } + ).to_string(); + channels.send(PublishedChannels::IpProtocols, message).await; + } +} \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/ws/ticker/top_10.rs b/src/rust/lqosd/src/node_manager/ws/ticker/top_10.rs index b73f347d..2072c99e 100644 --- a/src/rust/lqosd/src/node_manager/ws/ticker/top_10.rs +++ b/src/rust/lqosd/src/node_manager/ws/ticker/top_10.rs @@ -4,6 +4,7 @@ use lqos_bus::BusResponse; use crate::node_manager::ws::publish_subscribe::PubSub; use crate::node_manager::ws::published_channels::PublishedChannels; use crate::node_manager::ws::ticker::ipstats_conversion::IpStatsWithPlan; +use crate::throughput_tracker; use crate::throughput_tracker::{top_n, worst_n}; pub async fn top_10_downloaders(channels: Arc) { @@ -46,4 +47,25 @@ pub async fn worst_10_downloaders(channels: Arc) { ).to_string(); channels.send(PublishedChannels::WorstRTT, message).await; } +} + +pub async fn worst_10_retransmit(channels: Arc) { + if !channels.is_channel_alive(PublishedChannels::WorstRetransmits).await { + return; + } + + if let BusResponse::WorstRetransmits(top) = throughput_tracker::worst_n_retransmits(0, 10) { + let result: Vec = top + .iter() + .map(|stat| stat.into()) + .collect(); + + let message = json!( + { + "event": PublishedChannels::WorstRetransmits.to_string(), + "data": result + } + ).to_string(); + channels.send(PublishedChannels::WorstRetransmits, message).await; + } } \ No newline at end of file