Files
LibreQoS/src/rust/lqos_node_manager/static/circuit_queue.html
Herbert Wolverson d14526e0a5 ISSUE #183 - Remove the redundant "dashboard" link from all pages,
since the logo already links to the dashboard.
2023-03-20 16:07:08 +00:00

531 lines
26 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">
<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-globe"></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 Funnel</button>
</li>
</ul>
</div>
<div class="col-sm-2">
<div id="raw"></div>
</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-bar-chart"></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-bar-chart"></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</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-bar-chart"></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-bar-chart"></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-bar-chart"></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-bar-chart"></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-bar-chart"></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-bar-chart"></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>
</div>
<footer>&copy; 2022-2023, LibreQoE LLC</footer>
<script>
let throughput = new Object();
let throughput_head = 0;
let circuit_info = null;
function setX(x, counter) {
for (let i=0; i<x.length; i++) {
x[i].push(counter);
}
}
function setY(y, i, data, tin) {
if (data[0] == "None" || data[1] == "None") {
for (let j=0; i<y.length; y++) {
y[j].push(0);
}
} else {
y[0].push(data[0].Cake.tins[tin].sent_bytes * 8); // Download
y[1].push(0.0 - (data[1].Cake.tins[tin].sent_bytes * 8)); // Upload
y[2].push(data[0].Cake.tins[tin].drops); // Down Drops
y[3].push(data[0].Cake.tins[tin].marks); // Down Marks
y[4].push(0.0 - data[1].Cake.tins[tin].drops); // Up Drops
y[5].push(0.0 - data[1].Cake.tins[tin].marks); // Up Marks
// Backlog
y[6].push(data[0].Cake.tins[tin].backlog_bytes * 8);
y[7].push(0.0 - data[1].Cake.tins[tin].backlog_bytes * 8);
// Delays
y[8].push(data[0].Cake.tins[tin].avg_delay_us);
y[9].push(0.0 - data[1].Cake.tins[tin].avg_delay_us);
}
}
function pollQueue() {
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
if (params.id != null) {
// Name the circuit
$.get("/api/circuit_info/" + encodeURI(params.id), (data) => {
circuit_info = data;
$("#circuitName").text(redactText(circuit_info.name));
});
// Fill the raw button
$("#raw").html("<a class='btn btn-sm btn-info' href='/api/raw_queue_by_circuit/" + encodeURI(params.id) + "'><i class='fa fa-search'></i> Raw Data</a>");
// Graphs
$.get("/api/raw_queue_by_circuit/" + encodeURI(params.id), (data) => {
// Fill Base Information
let total_memory = data.current_download.Cake.memory_used + data.current_upload.Cake.memory_used;
$("#memory").text(scaleNumber(total_memory));
// Fill Tin Graphs
let backlogX1 = [];
let backlogY1 = [];
let backlogX2 = [];
let backlogY2 = [];
let delaysX1 = [];
let delaysX2 = [];
let delaysY1 = [];
let delaysY2 = [];
let qlenX1 = [];
let qlenY1 = [];
let qlenX2 = [];
let qlenY2 = [];
for (let tin=0; tin<4; tin++) {
let entries = {
x: [[], [], [], [], [], [], [], [], [], []],
y: [[], [], [], [], [], [], [], [], [], []],
}
let counter = 0;
for (let i=data.history_head; i<600; i++) {
setX(entries.x, counter);
setY(entries.y, i, data.history[i], tin);
if (tin == 0) {
qlenX1.push(counter);
qlenX2.push(counter);
if (data.history[i][0].Cake) {
qlenY1.push(data.history[i][0].Cake.qlen);
} else {
qlenY1.push(0);
}
if (data.history[i][1].Cake) {
qlenY2.push(0.0 - data.history[i][1].Cake.qlen);
} else {
qlenY2.push(0.0);
}
}
counter++;
}
for (let i=0; i<data.history_head; i++) {
setX(entries.x, counter);
setY(entries.y, i, data.history[i], tin);
if (tin == 0) {
qlenX1.push(counter);
qlenX2.push(counter);
if (data.history[i][0].Cake) {
qlenY1.push(data.history[i][0].Cake.qlen);
} else {
qlenY1.push(0.0);
}
if (data.history[i][1].Cake) {
qlenY2.push(0.0 - data.history[i][1].Cake.qlen);
} else {
qlenY2.push(0.0);
}
}
counter++;
}
let graph = document.getElementById("tinTp_" + tin);
let graph_data = [
{x: entries.x[0], y:entries.y[0], name: 'Download', type: 'scatter'},
{x: entries.x[1], y:entries.y[1], name: 'Upload', type: 'scatter'},
];
Plotly.newPlot(graph, graph_data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true} });
graph = document.getElementById("tinMd_" + tin);
graph_data = [
{x: entries.x[2], y:entries.y[2], name: 'Down Drops', type: 'scatter'},
{x: entries.x[3], y:entries.y[3], name: 'Down Marks', type: 'scatter'},
{x: entries.x[4], y:entries.y[4], name: 'Up Drops', type: 'scatter'},
{x: entries.x[5], y:entries.y[5], name: 'Up Marks', type: 'scatter'},
];
Plotly.newPlot(graph, graph_data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true} });
backlogX1.push(entries.x[6]);
backlogX2.push(entries.x[7]);
backlogY1.push(entries.y[6]);
backlogY2.push(entries.y[7]);
delaysX1.push(entries.x[8]);
delaysX2.push(entries.x[9]);
delaysY1.push(entries.y[8]);
delaysY2.push(entries.y[9]);
}
let graph = document.getElementById("backlogGraph");
let graph_data = [
{x: backlogX1[0], y:backlogY1[0], type: 'scatter', name: 'Tin 0 Down'},
{x: backlogX2[0], y:backlogY2[0], type: 'scatter', name: 'Tin 0 Up'},
{x: backlogX1[1], y:backlogY1[1], type: 'scatter', name: 'Tin 1 Down'},
{x: backlogX2[1], y:backlogY2[1], type: 'scatter', name: 'Tin 1 Up'},
{x: backlogX1[2], y:backlogY1[2], type: 'scatter', name: 'Tin 2 Down'},
{x: backlogX2[2], y:backlogY2[2], type: 'scatter', name: 'Tin 2 Up'},
{x: backlogX1[3], y:backlogY1[3], type: 'scatter', name: 'Tin 3 Down'},
{x: backlogX2[3], y:backlogY2[3], type: 'scatter', name: 'Tin 3 Up'},
];
Plotly.newPlot(graph, graph_data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true} });
graph = document.getElementById("delayGraph");
graph_data = [
{x: delaysX1[0], y:delaysY1[0], type: 'scatter', name: 'Tin 0 Down'},
{x: delaysX2[0], y:delaysY2[0], type: 'scatter', name: 'Tin 0 Up'},
{x: delaysX1[1], y:delaysY1[1], type: 'scatter', name: 'Tin 1 Down'},
{x: delaysX2[1], y:delaysY2[1], type: 'scatter', name: 'Tin 1 Up'},
{x: delaysX1[2], y:delaysY1[2], type: 'scatter', name: 'Tin 2 Down'},
{x: delaysX2[2], y:delaysY2[2], type: 'scatter', name: 'Tin 2 Up'},
{x: delaysX1[3], y:delaysY1[3], type: 'scatter', name: 'Tin 3 Down'},
{x: delaysX2[3], y:delaysY2[3], type: 'scatter', name: 'Tin 3 Up'},
];
Plotly.newPlot(graph, graph_data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true} });
graph = document.getElementById("qlenGraph");
graph_data = [
{x: qlenX1, y:qlenY1, type: 'scatter', name: 'Down'},
{x: qlenX2, y:qlenY2, type: 'scatter', name: 'Up'},
];
Plotly.newPlot(graph, graph_data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true} });
});
}
setTimeout(pollQueue, 1000);
}
function getThroughput() {
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
if (params.id != null) {
$.get("/api/circuit_throughput/" + encodeURI(params.id), (data) => {
let 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],
];
for (let i=0; i<data.length; i++) {
let ip = data[i][0];
let down = data[i][1];
let up = data[i][2];
if (throughput[ip] == null) {
throughput[ip] = {
down: [],
up: [],
};
for (let j=0; j<300; j++) {
throughput[ip].down.push(0);
throughput[ip].up.push(0);
}
}
throughput[ip].down[throughput_head] = down * 8;
throughput[ip].up[throughput_head] = up * 8;
}
// Build the graph
let graph = document.getElementById("throughputGraph");
let graph_data = [];
for (const ip in throughput) {
//console.log(ip);
let xUp = [];
let xDown = [];
let yUp = [];
let yDown = [];
let counter = 0;
for (let i=throughput_head; i<300; i++) {
xUp.push(counter);
xDown.push(counter);
yUp.push(0.0 - throughput[ip].up[i]);
yDown.push(throughput[ip].down[i]);
counter++;
}
for (let i=0; i<throughput_head; i++) {
xUp.push(counter);
xDown.push(counter);
yUp.push(0.0 - throughput[ip].up[i]);
yDown.push(throughput[ip].down[i]);
counter++;
}
// Build the quantiles graph
if (circuit_info != null) {
for (let i=0; i<yDown.length; i++) {
let down = yDown[i];
let up = 0.0 - yUp[i];
let down_slot = Math.floor((down / circuit_info.capacity[0]) * 10.0);
let up_slot = Math.floor((up / circuit_info.capacity[1]) * 10.0);
if (down_slot > 0) quantiles[0][down_slot] += 1;
if (up_slot > 0) quantiles[1][up_slot] += 1;
}
}
graph_data.push({x: xDown, y: yDown, name: ip + " Down", type: 'scatter'});
graph_data.push({x: xUp, y: yUp, name: ip + " Up", type: 'scatter'});
}
Plotly.newPlot(graph, graph_data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true} });
throughput_head += 1;
if (throughput_head >= 300) {
throughput_head = 0;
}
graph = document.getElementById("capacityQuantile");
graph_data = [
{x: quantiles[2], y: quantiles[0], name: 'Download', type: 'bar'},
{x: quantiles[2], y: quantiles[1], name: 'Upload', type: 'bar'},
];
Plotly.newPlot(graph, graph_data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: '# Samples' }, xaxis: {automargin: true, title: '% Utilization'} });
});
}
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() {
colorReloadButton();
updateHostCounts();
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
$.get("/api/watch_circuit/" + params.id, () => {
pollQueue();
getThroughput();
getFunnel(params.id);
});
}
$(document).ready(start);
</script>
</body>
</html>