mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
The ASN explorer now displays bytes, protocol, RTT and client links.
This commit is contained in:
parent
b4d1d5deff
commit
80995b7f95
@ -10,12 +10,14 @@ let asnList = [];
|
|||||||
let asnData = [];
|
let asnData = [];
|
||||||
let graphMinTime = Number.MAX_SAFE_INTEGER;
|
let graphMinTime = Number.MAX_SAFE_INTEGER;
|
||||||
let graphMaxTime = Number.MIN_SAFE_INTEGER;
|
let graphMaxTime = Number.MIN_SAFE_INTEGER;
|
||||||
let throughputDownMax = 0;
|
|
||||||
let throughputUpMax = 0;
|
|
||||||
|
|
||||||
const itemsPerPage = 20;
|
const itemsPerPage = 20;
|
||||||
let page = 0;
|
let page = 0;
|
||||||
|
|
||||||
|
function unixTimeToDate(unixTime) {
|
||||||
|
return new Date(unixTime * 1000).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
function asnDropdown() {
|
function asnDropdown() {
|
||||||
$.get(LIST_URL, (data) => {
|
$.get(LIST_URL, (data) => {
|
||||||
asnList = data;
|
asnList = data;
|
||||||
@ -45,7 +47,7 @@ function asnDropdown() {
|
|||||||
// Add items
|
// Add items
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
let li = document.createElement("li");
|
let li = document.createElement("li");
|
||||||
li.innerHTML = row.name + " (" + row.count + ")";
|
li.innerHTML = "#" + row.asn + " " + row.name + " (" + row.count + ")";
|
||||||
li.classList.add("dropdown-item");
|
li.classList.add("dropdown-item");
|
||||||
li.onclick = () => {
|
li.onclick = () => {
|
||||||
selectAsn(row.asn);
|
selectAsn(row.asn);
|
||||||
@ -92,14 +94,14 @@ function renderAsn(asn, data) {
|
|||||||
return a.start - b.start;
|
return a.start - b.start;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build the flows display
|
let div = document.createElement("div");
|
||||||
let flowsDiv = document.createElement("div");
|
div.classList.add("row");
|
||||||
|
|
||||||
let minTime = Number.MAX_SAFE_INTEGER;
|
let minTime = Number.MAX_SAFE_INTEGER;
|
||||||
let maxTime = Number.MIN_SAFE_INTEGER;
|
let maxTime = Number.MIN_SAFE_INTEGER;
|
||||||
for (let i= page * itemsPerPage; i<(page+1) * itemsPerPage; i++) {
|
|
||||||
if (i >= data.length) break;
|
|
||||||
let row = data[i];
|
|
||||||
|
|
||||||
|
// Calculate time overall
|
||||||
|
data.forEach((row) => {
|
||||||
// Update min/max time
|
// Update min/max time
|
||||||
if (row.start < minTime) {
|
if (row.start < minTime) {
|
||||||
minTime = row.start;
|
minTime = row.start;
|
||||||
@ -107,63 +109,87 @@ function renderAsn(asn, data) {
|
|||||||
if (row.end > maxTime) {
|
if (row.end > maxTime) {
|
||||||
maxTime = row.end;
|
maxTime = row.end;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
let div = document.createElement("div");
|
|
||||||
div.classList.add("row");
|
|
||||||
|
|
||||||
// Build the heading
|
|
||||||
let headingCol = document.createElement("div");
|
|
||||||
headingCol.classList.add("col-1");
|
|
||||||
|
|
||||||
let ht = "<p class='text-secondary small'>" + scaleNumber(row.total_bytes.down, 0) + " / " + scaleNumber(row.total_bytes.up);
|
|
||||||
|
|
||||||
if (row.rtt[0] !== undefined) {
|
|
||||||
ht += "<br /> RTT: " + scaleNanos(row.rtt[0].nanoseconds, 0);
|
|
||||||
} else {
|
|
||||||
ht += "<br /> RTT: -";
|
|
||||||
}
|
|
||||||
if (row.rtt[1] !== undefined) {
|
|
||||||
ht += " / " + scaleNanos(row.rtt[1].nanoseconds, 0);
|
|
||||||
}
|
|
||||||
ht += "</p>";
|
|
||||||
headingCol.innerHTML = ht;
|
|
||||||
//div.appendChild(headingCol);
|
|
||||||
|
|
||||||
// Build a canvas div, we'll decorate this later
|
|
||||||
let canvasCol = document.createElement("div");
|
|
||||||
canvasCol.classList.add("col-12");
|
|
||||||
let canvas = document.createElement("canvas");
|
|
||||||
canvas.id = "flowCanvas" + i;
|
|
||||||
canvas.style.width = "100%";
|
|
||||||
canvas.style.height = "30px";
|
|
||||||
canvasCol.appendChild(canvas);
|
|
||||||
div.appendChild(canvasCol);
|
|
||||||
|
|
||||||
flowsDiv.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the global time range
|
// Store the global time range
|
||||||
graphMinTime = minTime;
|
graphMinTime = minTime;
|
||||||
graphMaxTime = maxTime;
|
graphMaxTime = maxTime;
|
||||||
|
|
||||||
// Calculate the max down and up for every item
|
// Header row (explain the columns)
|
||||||
let maxDown = 0;
|
let headerDiv = document.createElement("div");
|
||||||
let maxUp = 0;
|
headerDiv.classList.add("row");
|
||||||
data.forEach((row) => {
|
let headerBytes = document.createElement("div");
|
||||||
row.throughput.forEach((value) => {
|
headerBytes.classList.add("col-1", "text-secondary");
|
||||||
if (value.down > maxDown) {
|
headerBytes.innerText = "Bytes";
|
||||||
maxDown = value.down;
|
headerDiv.appendChild(headerBytes);
|
||||||
|
let headerRtt = document.createElement("div");
|
||||||
|
headerRtt.classList.add("col-1", "text-secondary");
|
||||||
|
headerRtt.innerText = "RTT";
|
||||||
|
headerDiv.appendChild(headerRtt);
|
||||||
|
let headerClient = document.createElement("div");
|
||||||
|
headerClient.classList.add("col-1", "text-secondary");
|
||||||
|
headerClient.innerText = "Client";
|
||||||
|
headerDiv.appendChild(headerClient);
|
||||||
|
let headerProtocol = document.createElement("div");
|
||||||
|
headerProtocol.classList.add("col-1", "text-secondary");
|
||||||
|
headerProtocol.innerText = "Protocol";
|
||||||
|
headerDiv.appendChild(headerProtocol);
|
||||||
|
let headerTime1 = document.createElement("div");
|
||||||
|
headerTime1.classList.add("col-4", "text-secondary");
|
||||||
|
headerTime1.innerText = unixTimeToDate(minTime);
|
||||||
|
headerDiv.appendChild(headerTime1);
|
||||||
|
let headerTime2 = document.createElement("div");
|
||||||
|
headerTime2.classList.add("col-4", "text-secondary", "text-end");
|
||||||
|
console.log(maxTime);
|
||||||
|
headerTime2.innerText = unixTimeToDate(maxTime);
|
||||||
|
headerDiv.appendChild(headerTime2);
|
||||||
|
|
||||||
|
let flowsDiv = document.createElement("div");
|
||||||
|
for (let i= page * itemsPerPage; i<(page+1) * itemsPerPage; i++) {
|
||||||
|
if (i >= data.length) break;
|
||||||
|
let row = data[i];
|
||||||
|
|
||||||
|
// Build the headings
|
||||||
|
let totalCol = document.createElement("div");
|
||||||
|
totalCol.classList.add("col-1", "text-secondary", "small");
|
||||||
|
totalCol.innerText = scaleNumber(row.total_bytes.down, 0) + " / " + scaleNumber(row.total_bytes.up);
|
||||||
|
div.appendChild(totalCol);
|
||||||
|
|
||||||
|
let rttCol = document.createElement("div");
|
||||||
|
rttCol.classList.add("col-1", "text-secondary", "small");
|
||||||
|
let rttDown = row.rtt[0] !== undefined ? scaleNanos(row.rtt[0].nanoseconds, 0) : "-";
|
||||||
|
let rttUp = row.rtt[1] !== undefined ? scaleNanos(row.rtt[1].nanoseconds, 0) : "-";
|
||||||
|
rttCol.innerText = rttDown + " / " + rttUp;
|
||||||
|
div.appendChild(rttCol);
|
||||||
|
|
||||||
|
let clientCol = document.createElement("div");
|
||||||
|
clientCol.classList.add("col-1", "text-secondary", "small");
|
||||||
|
if (row.circuit_id !== "") {
|
||||||
|
let clientLink = document.createElement("a");
|
||||||
|
clientLink.href = "/circuit/" + encodeURI(row.circuit_id);
|
||||||
|
clientLink.innerText = row.circuit_name;
|
||||||
|
clientCol.appendChild(clientLink);
|
||||||
|
} else {
|
||||||
|
clientCol.innerText = row.circuit_name;
|
||||||
}
|
}
|
||||||
if (value.up > maxUp) {
|
div.appendChild(clientCol);
|
||||||
maxUp = value.up;
|
|
||||||
}
|
let protocolCol = document.createElement("div");
|
||||||
});
|
protocolCol.classList.add("col-1", "text-secondary", "small");
|
||||||
});
|
protocolCol.innerText = row.protocol;
|
||||||
if (maxDown > throughputDownMax) {
|
div.appendChild(protocolCol);
|
||||||
throughputDownMax = maxDown;
|
|
||||||
}
|
// Build a canvas div, we'll decorate this later
|
||||||
if (maxUp > throughputUpMax) {
|
let canvasCol = document.createElement("div");
|
||||||
throughputUpMax = maxUp;
|
canvasCol.classList.add("col-8");
|
||||||
|
let canvas = document.createElement("canvas");
|
||||||
|
canvas.id = "flowCanvas" + i;
|
||||||
|
canvas.style.width = "100%";
|
||||||
|
canvas.style.height = "20px";
|
||||||
|
canvasCol.appendChild(canvas);
|
||||||
|
div.appendChild(canvasCol);
|
||||||
|
|
||||||
|
flowsDiv.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the data to the page
|
// Apply the data to the page
|
||||||
@ -182,17 +208,25 @@ function renderAsn(asn, data) {
|
|||||||
|
|
||||||
let prevButton = document.createElement("button");
|
let prevButton = document.createElement("button");
|
||||||
nextButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2");
|
nextButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2");
|
||||||
prevButton.innerHTML = "<i class='fa fa-arrow-left'></i> Previous";
|
prevButton.innerHTML = "<i class='fa fa-arrow-left'></i> Prev";
|
||||||
prevButton.onclick = () => {
|
prevButton.onclick = () => {
|
||||||
page--;
|
page--;
|
||||||
if (page < 0) page = 0;
|
if (page < 0) page = 0;
|
||||||
renderAsn(asn, data);
|
renderAsn(asn, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let paginator = document.createElement("span");
|
||||||
|
paginator.classList.add("text-secondary", "small", "ms-2", "me-2");
|
||||||
|
paginator.innerText = "Page " + (page + 1) + " of " + Math.ceil(data.length / itemsPerPage);
|
||||||
|
paginator.id = "paginator";
|
||||||
|
|
||||||
let controlDiv = document.createElement("div");
|
let controlDiv = document.createElement("div");
|
||||||
controlDiv.classList.add("mb-2");
|
controlDiv.classList.add("mb-2");
|
||||||
controlDiv.appendChild(prevButton);
|
controlDiv.appendChild(prevButton);
|
||||||
|
controlDiv.appendChild(paginator);
|
||||||
controlDiv.appendChild(nextButton);
|
controlDiv.appendChild(nextButton);
|
||||||
target.appendChild(controlDiv);
|
target.appendChild(controlDiv);
|
||||||
|
target.appendChild(headerDiv);
|
||||||
|
|
||||||
target.appendChild(flowsDiv);
|
target.appendChild(flowsDiv);
|
||||||
|
|
||||||
@ -257,6 +291,18 @@ function drawTimeline() {
|
|||||||
ctx.lineTo(timeToX(row.end, width), height / 2);
|
ctx.lineTo(timeToX(row.end, width), height / 2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Calculate maxThroughputUp and maxThroughputDown for this row
|
||||||
|
let maxThroughputDown = 0;
|
||||||
|
let maxThroughputUp = 0;
|
||||||
|
row.throughput.forEach((value) => {
|
||||||
|
if (value.down > maxThroughputDown) {
|
||||||
|
maxThroughputDown = value.down;
|
||||||
|
}
|
||||||
|
if (value.up > maxThroughputUp) {
|
||||||
|
maxThroughputUp = value.up;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Draw a throughput down line. Y from y/2 to height, scaled to maxThroughputDown
|
// Draw a throughput down line. Y from y/2 to height, scaled to maxThroughputDown
|
||||||
ctx.strokeStyle = lineColor;
|
ctx.strokeStyle = lineColor;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@ -267,9 +313,9 @@ function drawTimeline() {
|
|||||||
let sampleWidth = (endX - startX) / numberOfSamples;
|
let sampleWidth = (endX - startX) / numberOfSamples;
|
||||||
let x = timeToX(row.start, width);
|
let x = timeToX(row.start, width);
|
||||||
ctx.moveTo(x, height/2);
|
ctx.moveTo(x, height/2);
|
||||||
let trimmedHeight = height - 10;
|
let trimmedHeight = height - 4;
|
||||||
row.throughput.forEach((value, index) => {
|
row.throughput.forEach((value, index) => {
|
||||||
let downPercent = value.down / throughputDownMax;
|
let downPercent = value.down / maxThroughputDown;
|
||||||
let y = (height/2) - (downPercent * (trimmedHeight / 2));
|
let y = (height/2) - (downPercent * (trimmedHeight / 2));
|
||||||
ctx.lineTo(x, y);
|
ctx.lineTo(x, y);
|
||||||
|
|
||||||
@ -280,7 +326,7 @@ function drawTimeline() {
|
|||||||
x = timeToX(row.start, width);
|
x = timeToX(row.start, width);
|
||||||
ctx.moveTo(x, height/2);
|
ctx.moveTo(x, height/2);
|
||||||
row.throughput.forEach((value, index) => {
|
row.throughput.forEach((value, index) => {
|
||||||
let upPercent = value.up / throughputUpMax;
|
let upPercent = value.up / maxThroughputUp;
|
||||||
let y = (height/2) + (upPercent * (trimmedHeight / 2));
|
let y = (height/2) + (upPercent * (trimmedHeight / 2));
|
||||||
ctx.lineTo(x, y);
|
ctx.lineTo(x, y);
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ use axum::Json;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use lqos_utils::units::DownUpOrder;
|
use lqos_utils::units::DownUpOrder;
|
||||||
use lqos_utils::unix_time::{time_since_boot, unix_now};
|
use lqos_utils::unix_time::{time_since_boot, unix_now};
|
||||||
|
use crate::shaped_devices_tracker::SHAPED_DEVICES;
|
||||||
use crate::throughput_tracker::flow_data::{AsnListEntry, RECENT_FLOWS, RttData};
|
use crate::throughput_tracker::flow_data::{AsnListEntry, RECENT_FLOWS, RttData};
|
||||||
|
|
||||||
pub async fn asn_list() -> Json<Vec<AsnListEntry>> {
|
pub async fn asn_list() -> Json<Vec<AsnListEntry>> {
|
||||||
@ -21,6 +22,9 @@ pub struct FlowTimeline {
|
|||||||
retransmit_times_down: Vec<u64>,
|
retransmit_times_down: Vec<u64>,
|
||||||
retransmit_times_up: Vec<u64>,
|
retransmit_times_up: Vec<u64>,
|
||||||
total_bytes: DownUpOrder<u64>,
|
total_bytes: DownUpOrder<u64>,
|
||||||
|
protocol: String,
|
||||||
|
circuit_id: String,
|
||||||
|
circuit_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn flow_timeline(Path(asn_id): Path<u32>) -> Json<Vec<FlowTimeline>> {
|
pub async fn flow_timeline(Path(asn_id): Path<u32>) -> Json<Vec<FlowTimeline>> {
|
||||||
@ -38,6 +42,14 @@ pub async fn flow_timeline(Path(asn_id): Path<u32>) -> Json<Vec<FlowTimeline>> {
|
|||||||
})
|
})
|
||||||
.map(|flow| {
|
.map(|flow| {
|
||||||
|
|
||||||
|
let (circuit_id, mut circuit_name) = {
|
||||||
|
let sd = SHAPED_DEVICES.read().unwrap();
|
||||||
|
sd.get_circuit_id_and_name_from_ip(&flow.0.local_ip).unwrap_or((String::new(), String::new()))
|
||||||
|
};
|
||||||
|
if circuit_name.is_empty() {
|
||||||
|
circuit_name = flow.0.local_ip.as_ip().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
FlowTimeline {
|
FlowTimeline {
|
||||||
start: boot_time + Duration::from_nanos(flow.1.start_time).as_secs(),
|
start: boot_time + Duration::from_nanos(flow.1.start_time).as_secs(),
|
||||||
end: boot_time + Duration::from_nanos(flow.1.last_seen).as_secs(),
|
end: boot_time + Duration::from_nanos(flow.1.last_seen).as_secs(),
|
||||||
@ -54,6 +66,9 @@ pub async fn flow_timeline(Path(asn_id): Path<u32>) -> Json<Vec<FlowTimeline>> {
|
|||||||
.map(|t| boot_time + Duration::from_nanos(*t).as_secs())
|
.map(|t| boot_time + Duration::from_nanos(*t).as_secs())
|
||||||
.collect(),
|
.collect(),
|
||||||
total_bytes: flow.1.bytes_sent.clone(),
|
total_bytes: flow.1.bytes_sent.clone(),
|
||||||
|
protocol: flow.2.protocol_analysis.to_string(),
|
||||||
|
circuit_id,
|
||||||
|
circuit_name,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
Loading…
Reference in New Issue
Block a user