Files
LibreQoS/src/rust/lqos_node_manager/static/circuit_queue.html
Herbert Wolverson da167fb84d Backlog is in bytes
2023-04-12 13:27:49 +00:00

955 lines
41 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/vendor/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/vendor/solid.min.css">
<link rel="stylesheet" href="/lqos.css">
<link rel="icon" href="/favicon.png">
<title>LibreQoS - Local Node Manager</title>
<script src="/lqos.js"></script>
<script src="/vendor/plotly-2.16.1.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>
</head>
<body class="bg-secondary">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/"><img src="/vendor/tinylogo.svg" alt="LibreQoS SVG Logo" width="25"
height="25" />&nbsp;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>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-tree"></i> Tree</a>
</li>
<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>
</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>
</ul>
</div>
<ul class="navbar-nav ms-auto">
<li class="nav-item" id="currentLogin"></li>
<li class="nav-item">
<a class="nav-link" href="#" id="startTest"><i class="fa fa-flag-checkered"></i> Run Bandwidth
Test</a>
</li>
<li class="nav-item ms-auto">
<a class="nav-link" href="/config"><i class="fa fa-gear"></i> Configuration</a>
</li>
<li>
<a class="nav-link btn btn-small black-txt" href="#" id="btnReload"><i class="fa fa-refresh"></i>
Reload LibreQoS</a>
</li>
</ul>
</div>
</nav>
<div id="container" class="pad4">
<div class="row top-shunt">
<div class="col-sm-12 bg-light center-txt">
<div class="row">
<div class="col-sm-4">
<span id="circuitName" class="bold redact"></span>
</div>
<div class="col-sm-6">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-home-tab" data-bs-toggle="pill"
data-bs-target="#pills-home" type="button" role="tab" aria-controls="pills-home"
aria-selected="true">Overview</button>
</li>
<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>
</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 Tree</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-flows-tab" data-bs-toggle="pill"
data-bs-target="#pills-flows" type="button" role="tab" aria-controls="pills-flows"
aria-selected="false">Flows</button>
</li>
</ul>
</div>
<div class="col-sm-2">
<a href="#" class="btn btn-small btn-info" id="btnPause"><i class="fa fa-pause"></i> Pause</a>
<a href="#" class="btn btn-small btn-info" id="btnSlow"><i class="fa fa-hourglass"></i> Slow Mode</a>
</div>
</div>
</div>
</div>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab"
tabindex="0">
<!-- Total Throughput and Backlog -->
<div class="row">
<div class="col-sm-4">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-dashboard"></i> Throughput</h5>
<div id="throughputGraph" class="graph150"></div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-car"></i> Backlog</h5>
<div id="backlogGraph" class="graph150"></div>
</div>
</div>
</div>
<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> Capacity Quantile (Last 10s)</h5>
<div id="capacityQuantile" class="graph150"></div>
</div>
</div>
</div>
</div>
<!-- Delay and Queue Length -->
<div class="row mtop4">
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-hourglass"></i> Delays</h5>
<div id="delayGraph" class="graph150"></div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-fast-forward"></i> Queue Length</h5>
<div id="qlenGraph" class="graph150"></div>
</div>
</div>
</div>
</div>
<div class="row mtop4">
<div class="col-sm-2">
<div class="card bg-light">
<div class="card-body">
Queue Memory: <span id="memory"></span>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-tins" role="tabpanel" aria-labelledby="pills-tins-tab" tabindex="1">
<div class="row" class="mtop4">
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-truck"></i> Tin 1 (Bulk)</h5>
<div id="tinTp_0" class="graph150"></div>
<div id="tinMd_0" class="graph150"></div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-balance-scale"></i> Tin 2 (Best Effort)</h5>
<div id="tinTp_1" class="graph150"></div>
<div id="tinMd_1" class="graph150"></div>
</div>
</div>
</div>
</div>
<div class="row mtop4">
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-television"></i> Tin 3 (Video)</h5>
<div id="tinTp_2" class="graph150"></div>
<div id="tinMd_2" class="graph150"></div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-phone"></i> Tin 4 (Voice)</h5>
<div id="tinTp_3" class="graph150"></div>
<div id="tinMd_3" class="graph150"></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 class="tab-pane fade" id="pills-flows" role="tabpanel" aria-labelledby="pills-flows-tab" tabindex="3">
<div class="row">
<div class="col-sm12">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title"><i class="fa fa-bar-chart"></i> Flows (Last 30 Seconds)</h5>
<p class="alert alert-warning" role="alert">
<i class="fa fa-warning"></i> Gathering packet data can cause high CPU load during
the capture window.
</p>
<div id="packetButtons"></div>
<div id="flowList"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer>&copy; 2022-2023, LibreQoE LLC</footer>
<script>
let throughput = new Object();
let throughput_head = 0;
let circuit_info = null;
function nameCircuit() {
if (circuit_info == null) {
msgPackGet("/api/circuit_info/" + encodeURI(id), (data) => {
circuit_info = data;
let capacity = scaleNumber(circuit_info[CircuitInfo.capacity][0]) + " / " + scaleNumber(circuit_info[CircuitInfo.capacity][1]);
$("#circuitName").text(redactText(circuit_info[CircuitInfo.name]) + " " + capacity);
});
}
}
function displayMemory(data) {
// Fill Base Information
let total_memory = data[QD.current_download][CT.memory_used] + data[QD.current_upload][CT.memory_used];
$("#memory").text(scaleNumber(total_memory));
}
class CombinedPlot {
constructor(capacity) {
this.y = []
for (let i = 0; i < capacity * 2; ++i) {
this.y.push(0);
}
}
store(x, y, value) {
if (value == 0) value = null;
this.y[(x * 2) + y] = value;
}
}
class TinsPlot {
constructor(capacity) {
this.tins = [
new CombinedPlot(capacity),
new CombinedPlot(capacity),
new CombinedPlot(capacity),
new CombinedPlot(capacity)
];
}
store(tin, x, y, value) {
this.tins[tin].store(x, y, value);
}
}
class QueuePlotter {
constructor(capacity) {
this.capacity = capacity;
this.x_axis = [];
this.backlog = new TinsPlot(capacity);
this.delays = new TinsPlot(capacity);
this.queueLen = new CombinedPlot(capacity);
this.throughput = new TinsPlot(capacity);
this.drops = new TinsPlot(capacity);
this.marks = new TinsPlot(capacity);
for (let i = 0; i < capacity; ++i) {
this.x_axis.push(i);
this.x_axis.push(i);
}
}
ingestBacklog(subData, currentX, tin) {
this.backlog.store(tin, currentX, 0, subData[0][CDT.tins][tin][CDTT.backlog_bytes] * 8);
this.backlog.store(tin, currentX, 1, 0.0 - subData[1][CDT.tins][tin][CDTT.backlog_bytes] * 8);
}
ingestDelays(subData, currentX, tin) {
let down = subData[0][CDT.tins][tin][CDTT.avg_delay_us] * 0.001;
let up = subData[1][CDT.tins][tin][CDTT.avg_delay_us] * 0.001;
if (down == 0.0) {
down = null;
} else {
down = Math.log10(down);
}
if (up == 0.0) {
up = null;
} else {
//console.log(up);
up = 0.0 - Math.log10(up);
}
this.delays.store(tin, currentX, 0, down);
this.delays.store(tin, currentX, 1, up);
}
ingestQueueLen(subData, currentX) {
this.queueLen.store(currentX, 0, subData[0][CDT.qlen]);
this.queueLen.store(currentX, 1, 0.0 - subData[1][CDT.qlen]);
}
ingestThroughput(subData, currentX, tin) {
this.throughput.store(tin, currentX, 0, subData[0][CDT.tins][tin][CDTT.sent_bytes] * 8);
this.throughput.store(tin, currentX, 1, 0.0 - (subData[1][CDT.tins][tin][CDTT.sent_bytes] * 8));
}
ingestDrops(subData, currentX, tin) {
this.drops.store(tin, currentX, 0, subData[0][CDT.tins][tin][CDTT.drops]);
this.drops.store(tin, currentX, 1, 0.0 - subData[1][CDT.tins][tin][CDTT.drops]);
}
ingestMarks(subData, currentX, tin) {
this.marks.store(tin, currentX, 0, subData[0][CDT.tins][tin][CDTT.marks]);
this.marks.store(tin, currentX, 1, 0.0 - subData[1][CDT.tins][tin][CDTT.marks]);
}
ingest(data, currentX, hi) {
// We're inserting at currentX, from the history entry indexed
// by hi
if (activeTab == "pills-home-tab") {
this.ingestQueueLen(data[QD.history][hi], currentX);
}
for (let tin = 0; tin < 4; ++tin) {
if (data[QD.history][hi][0][3].length == 4 && data[QD.history][hi][1][3].length == 4) {
if (activeTab == "pills-home-tab") {
this.ingestBacklog(data[QD.history][hi], currentX, tin);
this.ingestDelays(data[QD.history][hi], currentX, tin);
} else if (activeTab == "pills-tins-tab") {
this.ingestThroughput(data[QD.history][hi], currentX, tin);
this.ingestDrops(data[QD.history][hi], currentX, tin);
this.ingestMarks(data[QD.history][hi], currentX, tin);
}
}
}
}
update(data) {
// Iterate the whole history ringbuffer
// Note that we're going backwards. reverse() turned out
// to be surprisingly expensive in JS.
let currentX = this.capacity;
for (let hi = data[QD.history_head]; hi < 600; ++hi) {
this.ingest(data, currentX, hi);
currentX--;
}
for (let hi = 0; hi < data[QD.history_head]; ++hi) {
this.ingest(data, currentX, hi);
currentX--;
}
}
plotBacklog() {
let graph = document.getElementById("backlogGraph");
let graphData = [
{ x: this.x_axis, y: this.backlog.tins[0].y, type: 'scattergl', mode: 'markers', name: 'Bulk', marker: { size: 4 } },
{ x: this.x_axis, y: this.backlog.tins[1].y, type: 'scattergl', mode: 'markers', name: 'Best Effort', marker: { size: 4 } },
{ x: this.x_axis, y: this.backlog.tins[2].y, type: 'scattergl', mode: 'markers', name: 'Video', marker: { size: 4 } },
{ x: this.x_axis, y: this.backlog.tins[3].y, type: 'scattergl', mode: 'markers', name: 'Voice', marker: { size: 4 } },
];
if (this.backlogPlotted == null) {
this.backlogPlotted = true;
Plotly.newPlot(
graph,
graphData,
{
margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 },
yaxis: { automargin: true, title: "Bytes" },
xaxis: { automargin: true, title: "Time since now" } });
} else {
Plotly.redraw(graph, graphData);
}
}
plotDelays() {
let graph = document.getElementById("delayGraph");
let graphData = [
{ x: this.x_axis, y: this.delays.tins[0].y, type: 'scattergl', mode: 'markers', name: 'Bulk', marker: { size: 4 } },
{ x: this.x_axis, y: this.delays.tins[1].y, type: 'scattergl', mode: 'markers', name: 'Best Effort', marker: { size: 4 } },
{ x: this.x_axis, y: this.delays.tins[2].y, type: 'scattergl', mode: 'markers', name: 'Video', marker: { size: 4 } },
{ x: this.x_axis, y: this.delays.tins[3].y, type: 'scattergl', mode: 'markers', name: 'Voice', marker: { size: 4 } },
];
if (this.delaysPlotted == null) {
Plotly.newPlot(
graph,
graphData,
{ margin: { l: 8, r: 0, b: 0, t: 0, pad: 4 },
yaxis: { automargin: true, title: "log10(ms)", range: [-1.0, 1.0] },
xaxis: { automargin: true, title: "Time since now" } });
this.delaysPlotted = true;
} else {
Plotly.redraw(graph, graphData);
}
}
plotQueueLen() {
let graph = document.getElementById("qlenGraph");
let graphData = [
{ x: this.x_axis, y: this.queueLen.y, type: 'scattergl', mode: 'markers', name: 'Queue Length' },
];
if (this.queueLenPlotted == null) {
Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Packets" }, xaxis: { automargin: true, title: "Time since now" } });
this.queueLenPlotted = true;
} else {
Plotly.redraw(graph, graphData);
}
}
plotTinThroughput(tin) {
let graph = document.getElementById("tinTp_" + tin);
let graphData = [
{ x: this.x_axis, y: this.throughput.tins[tin].y, type: 'scatter', mode: 'markers' }
];
if (this.tinsPlotted == null) {
Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Bits" }, xaxis: { automargin: true, title: "Time since now" } });
} else {
Plotly.redraw(graph, graphData);
}
}
plotMarksDrops(tin) {
let graph = document.getElementById("tinMd_" + tin);
let graphData = [
{ x: this.x_axis, y: this.drops.tins[tin].y, name: 'Drops', type: 'scatter', mode: 'markers', marker: { size: 4 } },
{ x: this.x_axis, y: this.marks.tins[tin].y, name: 'Marks', type: 'scatter', mode: 'markers', marker: { size: 4 } },
];
if (this.tinsPlotted == null) {
Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Packets" }, xaxis: { automargin: true, title: "Time since now" } });
} else {
Plotly.redraw(graph, graphData);
}
}
plot() {
if (activeTab == "pills-home-tab") {
this.plotBacklog();
this.plotDelays();
this.plotQueueLen();
} else if (activeTab == "pills-tins-tab") {
for (let tin = 0; tin < 4; ++tin) {
this.plotTinThroughput(tin);
this.plotMarksDrops(tin);
}
this.tinsPlotted = true;
}
}
}
let qp = null;
function pollQueue() {
if (id != null) {
// Name the circuit
nameCircuit();
// Graphs
msgPackGet("/api/raw_queue_by_circuit/" + encodeURI(id), (data) => {
if (qp == null) qp = new QueuePlotter(600);
qp.update(data);
qp.plot();
displayMemory(data);
});
}
}
let ips = [];
class ThroughputMonitor {
constructor(capacity) {
this.capacity = capacity;
this.head = 0;
this.per_ip = {};
this.y = {};
this.x_axis = [];
for (let i = 0; i < capacity; ++i) {
this.x_axis.push(i);
this.x_axis.push(i);
}
this.quantiles = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
];
}
clearQuantiles() {
for (let i = 0; i < 12; ++i) {
this.quantiles[0][i] = 0;
this.quantiles[1][i] = 0;
}
}
ingest(ip, down, up) {
down = down * 8;
up = up * 8;
if (!this.per_ip.hasOwnProperty(ip)) {
this.per_ip[ip] = [];
this.y[ip] = [];
for (let i = 0; i < this.capacity; ++i) {
this.per_ip[ip].push(0);
this.per_ip[ip].push(0);
this.y[ip].push(0);
this.y[ip].push(0);
}
}
this.per_ip[ip][this.head] = down;
this.per_ip[ip][this.head + 1] = 0.0 - up;
this.head += 2;
if (this.head > this.capacity * 2) {
this.head = 0;
}
}
addQuantile(down, up) {
up = 0 - up;
let down_slot = Math.floor((down / circuit_info[CircuitInfo.capacity][0]) * 10.0);
let up_slot = Math.floor((up / circuit_info[CircuitInfo.capacity][1]) * 10.0);
if (down_slot < 0) down_slot = 0;
if (up_slot < 0) up_slot = 0;
if (down_slot > 10) down_slot = 10;
if (up_slot > 10) up_slot = 10;
this.quantiles[0][down_slot] += 1;
this.quantiles[1][up_slot] += 1;
//console.log(down_slot, up_slot);
}
prepare() {
this.clearQuantiles();
for (const ip in this.per_ip) {
let counter = this.capacity * 2;
for (let i = this.head; i < this.capacity * 2; i++) {
this.y[ip][counter] = this.per_ip[ip][i];
counter--;
}
for (let i = 0; i < this.head; i++) {
this.y[ip][counter] = this.per_ip[ip][i];
counter--;
}
for (let i = 2; i < 22; i += 2) {
this.addQuantile(this.y[ip][i], this.y[ip][i + 1]);
}
}
}
plot(target) {
let graph = document.getElementById(target);
let graphData = [];
for (const ip in this.per_ip) {
graphData.push({ x: this.x_axis, y: this.y[ip], name: ip, mode: 'markers', type: 'scattergl', marker: { size: 3 } });
}
if (!this.hasOwnProperty("plotted" + target)) {
Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Traffic (bits)" }, xaxis: { automargin: true, title: "Time since now" } });
this["plotted" + target] = true;
} else {
Plotly.redraw(graph, graphData);
}
}
plotQuantiles() {
let graph = document.getElementById("capacityQuantile");
let graphData = [
{ x: this.quantiles[2], y: this.quantiles[0], name: 'Download', type: 'bar' },
{ x: this.quantiles[2], y: this.quantiles[1], name: 'Upload', type: 'bar' },
];
if (this.plottedQ == null) {
Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: '# Samples' }, xaxis: { automargin: true, title: '% Utilization' } });
this.plottedQ = true;
} else {
Plotly.redraw(graph, graphData);
}
}
}
let tpData = null;
function getThroughput() {
if (id != null) {
msgPackGet("/api/circuit_throughput/" + encodeURI(id), (data) => {
if (tpData == null) tpData = new ThroughputMonitor(300);
ips = [];
for (let i = 0; i < data.length; i++) {
let ip = data[i][0];
ips.push(ip);
let down = data[i][1];
let up = data[i][2];
tpData.ingest(ip, down, up);
}
tpData.prepare();
tpData.plot("throughputGraph");
tpData.plotQuantiles();
});
}
}
let funnels = new ThroughputMonitor(300);
let rtts = {};
let circuitId = "";
let builtFunnelDivs = false;
function getFunnel() {
if (builtFunnelDivs) {
plotFunnels();
return;
}
circuitId = encodeURI(id);
msgPackGet("/api/funnel_for_queue/" + circuitId, (data) => {
let html = "";
// Add the client on top
let row = "<div class='row row220'>";
row += "<div class='col-sm-12'>";
row += "<div class='card bg-light'>";
row += "<h5 class='card-title'><i class='fa fa-hourglass'></i> Client Throughput</h5>";
row += "<div id='tp_client' class='graph98 graph150'></div>";
row += "</div>";
row += "</div>";
row += "</div>";
html += row;
// Funnels
for (let i = 0; i < data.length; ++i) {
//funnels.push(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8);
funnels.ingest(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.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][NetTrans.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][NetTrans.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);
builtFunnelDivs = true;
});
}
let plottedFunnels = {};
function plotFunnels() {
if (tpData != null) tpData.plot("tp_client");
funnels.prepare();
msgPackGet("/api/funnel_for_queue/" + encodeURI(circuitId), (data) => {
for (let i = 0; i < data.length; ++i) {
rtts[data[i][0]].clear();
funnels.ingest(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8);
for (let j = 0; j < data[i][1][NetTrans.rtts].length; j++) {
rtts[data[i][0]].push(data[i][1][NetTrans.rtts][j]);
}
rtts[data[i][0]].plot("rtt" + data[i][0]);
}
for (const [k, v] of Object.entries(funnels.y)) {
let target_div = "tp" + k;
let graphData = [
{ x: funnels.x_axis, y: v, type: 'scatter', mode: 'markers', marker: { size: 3 } }
];
let graph = document.getElementById(target_div);
if (!plotFunnels.hasOwnProperty(target_div)) {
Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Traffic (bits)" }, xaxis: { automargin: true, title: "Time since now" } });
} else {
Plotly.redraw(graph, graphData);
}
}
});
}
function icmpType(n) {
switch (n) {
case 0: return "ECHO REPLY";
case 3: return "DESTINATION UNREACHABLE";
case 4: return "SOURCE QUENCH";
case 8: return "ECHO REQUEST";
case 11: return "TIME EXCEEDED";
case 12: return "PARAMETER PROBLEM";
case 13: return "TIMESTAMP REQUEST";
case 14: return "TIMESTAMP REPLY";
case 15: return "INFO REQUEST";
case 16: return "INFO REPLY";
case 17: return "ADDRESS REQUEST";
case 18: return "ADDRESS REPLY";
default: return "?";
}
}
var madeButtons = false;
var analysisId = null;
var analysisTimer = null;
var analysisBtn = null;
function analyze(id) {
if (analysisId != null) {
alert("Heimdall says: 'STOP CLICKING ME'");
return;
}
let ip = ips[id];
$.get("/api/request_analysis/" + encodeURI(ip), (data) => {
if (data == "Fail") {
alert("Heimdall is busy serving other customers. Your desire is important to him, please try again later.")
return;
}
analysisId = data.Ok.session_id;
analysisBtn = "#dumpBtn_" + id;
analysisTimer = data.Ok.countdown;
analyzeTick();
});
}
function analyzeTick() {
$(analysisBtn).text("Gathering Data for " + analysisTimer + " more seconds");
analysisTimer--;
if (analysisTimer > -1) {
setTimeout(analyzeTick, 1000);
} else {
window.location.href = "/ip_dump?id=" + analysisId + "&circuit_id=" + encodeURI(id);
}
}
function getFlows() {
let ip_list = "";
let ip_btns = "";
for (let i = 0; i < ips.length; ++i) {
ip_list += ips[i] + ",";
if (circuit_info != null) {
ip_btns += "<a id='dumpBtn_" + i + "' href='#' onclick='analyze(\"" + i + "\")' class='btn btn-info'><i class='fa fa-search'></i> Analyze: " + ips[i] + "</a> "
}
}
if (!madeButtons && ips.length > 0 && circuit_info != null) {
ip_btns += "<br />";
madeButtons = true;
$("#packetButtons").html(ip_btns);
}
ip_list = ip_list.substring(0, ip_list.length - 1);
if (ip_list == "") return;
msgPackGet("/api/flows/" + ip_list, (data) => {
//console.log(data);
let html = "<table class='table table-striped'>";
html += "<thead>";
html += "<th>Protocol</th>";
html += "<th>Src</th>";
html += "<th>Src Port</th>";
html += "<th>Dst</th>";
html += "<th>Dst Port</th>";
html += "<th>Pkt In</th>";
html += "<th>Pkt Out</th>";
html += "<th>Bytes In</th>";
html += "<th>Bytes Out</th>";
html += "<th>DSCP In</th>";
html += "<th>DSCP Out</th>";
html += "<th>ECN In</th>";
html += "<th>ECN Out</th>";
html += "</thead>";
for (let i = 0; i < data.length; i++) {
let rpackets = "-";
let rbytes = "-";
let rdscp = "-";
let rcongestion = "-";
if (data[i][1] != null) {
rpackets = data[i][1][FlowTrans.packets];
rbytes = scaleNumber(data[i][1][FlowTrans.bytes]);
rdscp = "0x" + data[i][1][FlowTrans.dscp].toString(16);
rcongestion = ecn(data[i][1][FlowTrans.ecn]);
}
html += "<tr>";
html += "<td>" + data[i][0][FlowTrans.proto] + "</td>";
html += "<td>" + ipToHostname(data[i][0][FlowTrans.src]) + "</td>";
if (data[i][0].proto == "ICMP") {
html += "<td>" + icmpType(data[i][0][FlowTrans.src_port]) + "</td>";
} else {
html += "<td>" + data[i][0][FlowTrans.src_port] + "</td>";
}
html += "<td>" + ipToHostname(data[i][0][FlowTrans.dst]) + "</td>";
if (data[i][0][FlowTrans.proto] == "ICMP") {
if (data[i][1] != null) {
html += "<td>" + icmpType(data[i][1][FlowTrans.src_port]) + "</td>";
} else {
html += "<td></td>";
}
} else {
html += "<td>" + data[i][0][FlowTrans.dst_port] + "</td>";
}
html += "<td>" + data[i][0][FlowTrans.packets] + "</td>";
html += "<td>" + rpackets + "</td>";
html += "<td>" + scaleNumber(data[i][0][FlowTrans.bytes]) + "</td>";
html += "<td>" + rbytes + "</td>";
html += "<td>0x" + data[i][0][FlowTrans.dscp].toString(16) + "</td>";
html += "<td>" + rdscp + "</td>";
html += "<td>" + ecn(data[i][0][FlowTrans.ecn]) + "</td>";
html += "<td>" + rcongestion + "</td>";
html += "</tr>";
}
html += "</tbody></table>";
$("#flowList").html(html);
})
}
let id = 0;
let activeTab = "pills-home-tab";
var lastCalledTime;
var fps;
var worstDelta = 0;
var paused = false;
var slowMode = false;
function showFps() {
if(!lastCalledTime) {
lastCalledTime = Date.now();
fps = 0;
return;
}
delta = (Date.now() - lastCalledTime)/1000;
lastCalledTime = Date.now();
fps = 1/delta;
//$("#fps").text(fps.toFixed(0));
worstDelta = Math.max(delta, worstDelta);
}
function updateFrame() {
showFps();
if (!paused) {
switch (activeTab) {
case "pills-funnel-tab": {
getFunnel();
} break;
case "pills-flows-tab": {
getFlows();
} break;
default: {
pollQueue();
getThroughput();
}
}
}
// Doing this to balance out the FPS
// It will tend towards the slowest
if (slowMode) {
setTimeout(updateFrame, 1000);
} else {
setTimeout(() => {
requestAnimationFrame(updateFrame);
}, worstDelta * 200);
}
}
function wireUpTabEvents() {
// Fire events when the active tab changes
$(document).on('shown.bs.tab', 'button[data-bs-toggle="pill"]', function (e) {
activeTab = e.target.id;
//console.log(activeTab);
});
}
function isSlowMode() {
let slow = localStorage.getItem("slowMode");
if (slow == null) {
localStorage.setItem("slowMode", false);
slow = false;
}
if (slow == "false") {
slow = false;
} else if (slow == "true") {
slow = true;
}
return slow;
}
function start() {
wireUpTabEvents();
$("#btnPause").on('click', () => {
paused = !paused;
if (paused) {
$("#btnPause").html("<i class='fa fa-play'></i> Resume");
} else {
$("#btnPause").html("<i class='fa fa-pause'></i> Pause");
}
});
slowMode = isSlowMode();
if (slowMode) {
$("#btnSlow").html("<i class='fa fa-fast-forward'></i> Fast Mode");
} else {
$("#btnSlow").html("<i class='fa fa-hourglass'></i> Slow Mode");
}
$("#btnSlow").on('click', () => {
slowMode = !slowMode;
localStorage.setItem("slowMode", slowMode);
if (slowMode) {
$("#btnSlow").html("<i class='fa fa-fast-forward'></i> Fast Mode");
} else {
$("#btnSlow").html("<i class='fa fa-hourglass'></i> Slow Mode");
}
});
colorReloadButton();
updateHostCounts();
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
id = params.id;
$.get("/api/watch_circuit/" + params.id, () => {
//updateFrame();
requestAnimationFrame(updateFrame);
});
}
$(document).ready(start);
</script>
</body>
</html>