Easier to read queue data, now showing graphs for throughput, delays, queue length, etc.

This commit is contained in:
Herbert Wolverson 2023-01-06 17:55:19 +00:00
parent c1df274ae9
commit 418e262b24
8 changed files with 353 additions and 94 deletions

View File

@ -1,4 +1,5 @@
mod ip_stats;
use std::net::IpAddr;
use anyhow::Result;
pub use ip_stats::{IpMapping, IpStats, XdpPpingResult};
use serde::{Deserialize, Serialize};
@ -19,6 +20,7 @@ pub enum BusRequest {
GetCurrentThroughput,
GetTopNDownloaders(u32),
GetWorstRtt(u32),
GetHostCounter,
MapIpToFlow {
ip_address: String,
tc_handle: TcHandle,
@ -56,6 +58,7 @@ pub enum BusResponse {
packets_per_second: (u64, u64),
shaped_bits_per_second: (u64, u64),
},
HostCounters(Vec<(IpAddr, u64, u64)>),
TopDownloaders(Vec<IpStats>),
WorstRtt(Vec<IpStats>),
MappedIps(Vec<IpMapping>),

View File

@ -49,6 +49,8 @@ fn rocket() -> _ {
unknown_devices::unknown_devices_range,
queue_info::raw_queue_by_circuit,
queue_info::run_btest,
queue_info::circuit_name,
queue_info::current_circuit_throughput,
config_control::get_nic_list,
config_control::get_current_python_config,
config_control::get_current_lqosd_config,

View File

@ -1,8 +1,68 @@
use lqos_bus::{BusResponse, BUS_BIND_ADDRESS, BusSession, BusRequest, encode_request, decode_response};
use rocket::response::content::RawJson;
use rocket::serde::json::Json;
use rocket::tokio::io::{AsyncWriteExt, AsyncReadExt};
use rocket::tokio::net::TcpStream;
use crate::cache_control::NoCache;
use crate::tracker::SHAPED_DEVICES;
use std::net::IpAddr;
#[get("/api/circuit_name/<circuit_id>")]
pub async fn circuit_name(circuit_id: String) -> NoCache<Json<String>> {
if let Some(device) = SHAPED_DEVICES.read().devices.iter().find(|d| d.circuit_id == circuit_id) {
NoCache::new(Json(device.circuit_name.clone()))
} else {
let result = "Nameless".to_string();
NoCache::new(Json(result))
}
}
#[get("/api/circuit_throughput/<circuit_id>")]
pub async fn current_circuit_throughput(circuit_id: String) -> NoCache<Json<Vec<(String, u64, u64)>>> {
let mut result = Vec::new();
// Get a list of host counts
// This is really inefficient, but I'm struggling to find a better way.
// TODO: Fix me up
let mut stream = TcpStream::connect(BUS_BIND_ADDRESS).await.unwrap();
let test = BusSession {
auth_cookie: 1234,
requests: vec![
BusRequest::GetHostCounter,
],
};
let msg = encode_request(&test).unwrap();
stream.write(&msg).await.unwrap();
// Receive reply
let mut buf = Vec::new();
let _ = stream.read_to_end(&mut buf).await.unwrap();
let reply = decode_response(&buf).unwrap();
for msg in reply.responses.iter() {
match msg {
BusResponse::HostCounters(hosts) => {
let devices = SHAPED_DEVICES.read();
for (ip, down, up) in hosts.iter() {
let lookup = match ip {
IpAddr::V4(ip) => ip.to_ipv6_mapped(),
IpAddr::V6(ip) => *ip,
};
if let Some(c) = devices.trie.longest_match(lookup) {
if devices.devices[*c.1].circuit_id == circuit_id {
result.push((
ip.to_string(),
*down,
*up
));
}
}
}
}
_ => {}
}
}
NoCache::new(Json(result))
}
#[get("/api/raw_queue_by_circuit/<circuit_id>")]
pub async fn raw_queue_by_circuit(circuit_id: String) -> NoCache<RawJson<String>> {

View File

@ -23,7 +23,6 @@
<li class="nav-item">
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-globe"></i> Network Layout</a>
</li>
@ -49,98 +48,146 @@
<div id="container" style="padding: 4px;">
<div class="row">
<div class="col-sm-2">
<div class="card bg-light">
<div class="card-body">
<div id="raw"></div>
<div class="row" style="margin-top: -4px; margin-bottom: 4px;">
<div class="col-sm-12 bg-light" style="text-align: center;">
<div class="row">
<div class="col-sm-4">
<span id="circuitName" style="font-weight: bold"></span>
</div>
</div>
</div>
<div class="col-sm-2">
<div class="card bg-light">
<div class="card-body">
Queue Memory: <span id="memory"></span><br />
Queue Length: <span id="qlen"></span>
<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>
</ul>
</div>
</div>
</div>
<div class="col-sm-2">
<div class="card bg-light">
<div class="card-body">
Average Delay: <span id="avgDelay"></span><br />
Peak Delay: <span id="peakDelay"></span>
</div>
</div>
</div>
<div class="col-sm-2">
<div class="card bg-light">
<div class="card-body">
Backlog: <span id="backlog"></span>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 4px">
<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" style="height: 150px"></div>
<div id="tinMd_0" style="height: 150px"></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" style="height: 150px"></div>
<div id="tinMd_1" style="height: 150px"></div>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 4px">
<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" style="height: 150px"></div>
<div id="tinMd_2" style="height: 150px"></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" style="height: 150px"></div>
<div id="tinMd_3" style="height: 150px"></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-6">
<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" style="height: 150px"></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> Backlog</h5>
<div id="backlogGraph" style="height: 150px"></div>
</div>
</div>
</div>
</div>
<!-- Delay and Queue Length -->
<div class="row" style="margin-top: 4px;">
<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" style="height: 150px"></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" style="height: 150px"></div>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 4px;">
<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="0">
<div class="row" style="margin-top: 4px">
<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" style="height: 150px"></div>
<div id="tinMd_0" style="height: 150px"></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" style="height: 150px"></div>
<div id="tinMd_1" style="height: 150px"></div>
</div>
</div>
</div>
</div>
<div class="row" style="margin-top: 4px">
<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" style="height: 150px"></div>
<div id="tinMd_2" style="height: 150px"></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" style="height: 150px"></div>
<div id="tinMd_3" style="height: 150px"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer>Copyright (c) 2022, LibreQoE LLC</footer>
<script>
let throughput = new Object();
let throughput_head = 0;
function setX(x, counter) {
for (let i=0; i<6; i++) {
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") {
y[0].push(0);
y[1].push(0);
y[2].push(0);
y[3].push(0);
y[4].push(0);
y[5].push(0);
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
@ -148,6 +195,14 @@
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);
}
}
@ -156,43 +211,60 @@
get: (searchParams, prop) => searchParams.get(prop),
});
if (params.id != null) {
// Name the circuit
$.get("/api/circuit_name/" + encodeURI(params.id), (data) => {
$("#circuitName").text(data);
});
// 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));
let queue_length = data.current_download.Cake.qlen + data.current_upload.Cake.qlen;
$("#qlen").text(scaleNumber(queue_length));
let avgDelay = "";
let peakDelay = "";
let backlog = "";
for (let i=0; i<4; i++) {
avgDelay += data.current_download.Cake.tins[i].avg_delay_us + " / ";
peakDelay += data.current_download.Cake.tins[i].peak_delay_us + " / ";
backlog += data.current_download.Cake.tins[i].backlog_bytes + " / ";
}
avgDelay = avgDelay.substring(0, avgDelay.length - 2) + " us";
peakDelay = peakDelay.substring(0, peakDelay.length - 2) + " us";
backlog = backlog.substring(0, backlog.length - 2);
$("#avgDelay").text(avgDelay);
$("#peakDelay").text(peakDelay);
$("#backlog").text(backlog);
// 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: [[], [], [], [], [], []],
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);
qlenY1.push(data.history[i][0].Cake.qlen);
qlenY2.push(0.0 - data.history[i][1].Cake.qlen);
}
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);
qlenY1.push(data.history[i][0].Cake.qlen);
qlenY2.push(0.0 - data.history[i][1].Cake.qlen);
}
counter++;
}
let graph = document.getElementById("tinTp_" + tin);
@ -210,17 +282,122 @@
{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) => {
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++;
}
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;
}
});
}
setTimeout(getThroughput, 1000);
}
function start() {
colorReloadButton();
updateHostCounts();
pollQueue();
getThroughput();
}
$(document).ready(start);

View File

@ -89,6 +89,7 @@ async fn main() -> Result<()> {
BusRequest::GetCurrentThroughput => {
throughput_tracker::current_throughput()
}
BusRequest::GetHostCounter => throughput_tracker::host_counters(),
BusRequest::GetTopNDownloaders(n) => throughput_tracker::top_n(*n),
BusRequest::GetWorstRtt(n) => throughput_tracker::worst_n(*n),
BusRequest::MapIpToFlow {

View File

@ -28,6 +28,7 @@ pub(crate) fn make_queue_diff(previous: &QueueType, current: &QueueType) -> Resu
pub struct CakeDiff {
pub bytes: u64,
pub packets: u64,
pub qlen: u64,
pub tins: Vec<CakeDiffTin>,
}
@ -37,6 +38,7 @@ pub struct CakeDiffTin {
pub backlog_bytes: u64,
pub drops: u64,
pub marks: u64,
pub avg_delay_us: u64,
}
fn cake_diff(previous: &QueueType, current: &QueueType) -> Result<QueueDiff> {
@ -47,14 +49,16 @@ fn cake_diff(previous: &QueueType, current: &QueueType) -> Result<QueueDiff> {
//println!("{} - {} = {}", new.sent_bytes, prev.sent_bytes, new.sent_bytes -prev.sent_bytes);
CakeDiffTin {
sent_bytes: new.sent_bytes - prev.sent_bytes,
backlog_bytes: new.backlog_bytes - prev.backlog_bytes,
backlog_bytes: new.backlog_bytes,
drops: new.drops - prev.drops,
marks: new.ecn_marks - prev.ecn_marks,
avg_delay_us: new.avg_delay_us,
}
}).collect();
return Ok(QueueDiff::Cake(CakeDiff{
bytes: new.bytes - prev.bytes,
packets: new.packets - prev.packets,
qlen: new.qlen,
tins,
}));
}

View File

@ -179,7 +179,7 @@ pub(crate) struct TcCake {
target_us: u64,
interval_us: u64,
peak_delay_us: u64,
avg_delay_us: u64,
pub(crate) avg_delay_us: u64,
base_delay_us: u64,
sent_packets: u64,
way_indirect_hits: u64,

View File

@ -54,6 +54,18 @@ pub fn current_throughput() -> BusResponse {
}
}
pub fn host_counters() -> BusResponse {
let mut result = Vec::new();
let tp = THROUGHPUT_TRACKER.read();
tp.raw_data.iter().for_each(|(k,v)| {
let ip = k.as_ip();
let (down, up) = v.bytes_per_second;
result.push((ip, down, up));
});
BusResponse::HostCounters(result)
}
#[inline(always)]
fn retire_check(cycle: u64, recent_cycle: u64) -> bool {
cycle < recent_cycle + RETIRE_AFTER_SECONDS