Merge pull request #305 from LibreQoE/issue_302_piano_rolls

Issue 302/303/304 piano rolls
This commit is contained in:
Robert Chacón 2023-03-29 07:38:53 -06:00 committed by GitHub
commit 0f1986a258
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 294 additions and 70 deletions

View File

@ -30,7 +30,7 @@ pub struct CakeDiffTinTransit {
pub backlog_bytes: u32,
pub drops: u32,
pub marks: u32,
pub avg_delay_us: u32,
pub base_delay_us: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]

View File

@ -94,7 +94,8 @@
</ul>
</div>
<div class="col-sm-2">
<div id="raw"></div>
<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>
@ -304,8 +305,21 @@
}
ingestDelays(subData, currentX, tin) {
this.delays.store(tin, currentX, 0, subData[0][CDT.tins][tin][CDTT.avg_delay_us] * 0.001);
this.delays.store(tin, currentX, 1, 0.0 - subData[1][CDT.tins][tin][CDTT.avg_delay_us] * 0.001);
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) {
@ -374,7 +388,13 @@
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" } });
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);
}
@ -390,7 +410,12 @@
];
if (this.delaysPlotted == null) {
Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "ms" }, xaxis: { automargin: true, title: "Time since now" } });
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);
@ -520,10 +545,10 @@
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 (down_slot < 0) down_slot = 0;
if (up_slot < 0) up_slot = 0;
if (down_slot > 11) down_slot = 11;
if (up_slot > 11) up_slot = 11;*/
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);
@ -796,22 +821,50 @@
let id = 0;
let activeTab = "pills-home-tab";
var lastCalledTime;
var fps;
var worstDelta = 0;
var paused = false;
var slowMode = false;
function oneSecondCadence() {
//console.log(activeTab);
switch (activeTab) {
case "pills-funnel-tab": {
getFunnel();
} break;
case "pills-flows-tab": {
getFlows();
} break;
default: {
pollQueue();
getThroughput();
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();
}
}
}
setTimeout(oneSecondCadence, 1000);
// 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() {
@ -822,8 +875,45 @@
});
}
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), {
@ -831,7 +921,8 @@
});
id = params.id;
$.get("/api/watch_circuit/" + params.id, () => {
oneSecondCadence();
//updateFrame();
requestAnimationFrame(updateFrame);
});
}

View File

@ -73,11 +73,33 @@
<script>
var packets = [];
var flows = {};
var pages = 0;
var PAGE_SIZE = 1000;
var target = "";
var current_page = 0;
var capacity = [];
var activeFilter = null;
var activeSet = null;
var activeChart = 0;
var activePage = 0;
function filter(newFilter) {
activeFilter = newFilter;
if (newFilter == null) {
activeSet = packets;
} else {
activeSet = packets.filter(packet => packet.flow_id == activeFilter);
}
pages = Math.ceil((activeSet.length / PAGE_SIZE));
paginator(0);
viewPage(0);
}
function setChart(n) {
activeChart = n;
paginator(activePage);
viewPage(activePage);
}
function proto(n) {
switch (n) {
@ -115,37 +137,80 @@ if (hdr->cwr) flags |= 128;
function zoomIn() {
PAGE_SIZE /= 2;
current_page /= 2;
activePage /= 2;
pages = packets.length / PAGE_SIZE;
viewPage(current_page);
viewPage(activePage);
}
function zoomOut() {
PAGE_SIZE *= 2;
current_page *= 2;
activePage *= 2;
pages = packets.length / PAGE_SIZE;
viewPage(current_page);
viewPage(activePage);
}
function paginator(active) {
activePage = active;
let paginator = "<a href='/api/pcap/" + target + "/capture-" + circuit_id + "-" + starting_timestamp + ".pcap' class='btn btn-warning'>Download PCAP Dump</a> ";
paginator += "<a href='#' class='btn btn-info' onClick='zoomIn();'>Zoom In</a> ";
paginator += "<a href='#' class='btn btn-info' onClick='zoomOut();'>Zoom Out</a> ( Or drag an area of the graph) <br />";
paginator += "<div style='margin: 4px; padding: 6px; background-color: #ddd; border: solid 1px black;'>";
paginator += "<strong>Jump to page</strong>: ";
for (let i=0; i<pages; i++) {
if (i == active) {
paginator += " " + i + " ";
} else {
paginator += "<a href='#' onclick='viewPage(" + i + ");'>" + i + "</a> ";
}
paginator += "<select>"
for (let i=0; i<pages; i++) {
if (i == active) {
paginator += "<option selected>" + i + "</option>";
} else {
paginator += "<option onclick='viewPage(" + i + ");'>" + i + "</option> ";
}
$("#pages").html(paginator);
}
paginator += "</select> | ";
// Offer flow filtering
paginator += "<strong>Filter Flows</strong>: ";
paginator += "<select>";
if (activeFilter == null) {
paginator += "<option selected onclick='filter(null);'>View All</option>";
} else {
paginator += "<option onclick='filter(null);'>View All</option>";
}
Object.keys(flows).forEach(key => {
if (activeFilter == key) {
paginator += "<option selected onclick='filter(\"" + key + "\");'>" + key + "</option>";
} else {
paginator += "<option onclick='filter(\"" + key + "\");'>" + key + "</option>";
}
});
paginator += "</select> | ";
// Offer graph choices
paginator += "<strong>Graph</strong>: ";
paginator += "<select>";
if (activeChart == 0) {
paginator += "<option selected>Packet-Size Chart</option>";
} else {
paginator += "<option onclick='setChart(0);'>Packet-Size Chart</option>";
}
if (activeChart == 1) {
paginator += "<option selected>Piano Roll Flow Chart</option>";
} else {
paginator += "<option onclick='setChart(1);'>Piano Roll Flow Chart</option>";
}
if (activeChart == 2) {
paginator += "<option selected>TCP Window Chart</option>";
} else {
paginator += "<option onclick='setChart(2);'>TCP Window Chart</option>";
}
paginator += "</select>";
paginator += "</div>";
$("#pages").html(paginator);
}
function viewPage(n) {
let start = n * PAGE_SIZE;
let end = Math.min(start + PAGE_SIZE, packets.length);
let end = Math.min(start + PAGE_SIZE, activeSet.length);
if (start > packets.length) {
console.log("OOps");
}
@ -156,46 +221,99 @@ if (hdr->cwr) flags |= 128;
let y2_axis = [];
for (let i=start; i<end; ++i) {
html += "<tr>";
html += "<td>" + packets[i].timestamp + "</td>";
html += "<td>" + proto(packets[i].ip_protocol) + "</td>";
html += "<td>" + activeSet[i].timestamp + "</td>";
html += "<td>" + proto(activeSet[i].ip_protocol) + "</td>";
if (packets[i].ip_protocol == 6) {
html += "<td>" + tcp_flags(packets[i].tcp_flags) + "</td>";
html += "<td>" + packets[i].tcp_tsval + "/" + packets[i].tcp_tsecr + "</td>";
html += "<td>" + packets[i].tcp_window + "</td>";
if (activeSet[i].ip_protocol == 6) {
html += "<td>" + tcp_flags(activeSet[i].tcp_flags) + "</td>";
html += "<td>" + activeSet[i].tcp_tsval + "/" + activeSet[i].tcp_tsecr + "</td>";
html += "<td>" + activeSet[i].tcp_window + "</td>";
} else {
html += "<td></td><td></td><td></td>";
}
if (packets[i].ip_protocol != 1) {
html += "<td>" + packets[i].src + ":" + packets[i].src_port + " -> " + packets[i].dst + ":" + packets[i].dst_port + "</td>";
if (activeSet[i].ip_protocol != 1) {
html += "<td>" + activeSet[i].src + ":" + activeSet[i].src_port + " -> " + activeSet[i].dst + ":" + activeSet[i].dst_port + "</td>";
} else {
html += "<td>" + packets[i].src + " -> " + packets[i].dst + "</td>";
html += "<td>" + activeSet[i].src + " -> " + activeSet[i].dst + "</td>";
}
html += "<td>" + packets[i].size + "</td>";
html += "<td>" + ecn(packets[i].ecn) + "</td>";
html += "<td>0x" + packets[i].dscp.toString(16) + "</td>";
html += "<td>" + activeSet[i].size + "</td>";
html += "<td>" + ecn(activeSet[i].ecn) + "</td>";
html += "<td>0x" + activeSet[i].dscp.toString(16) + "</td>";
html += "</tr>";
x_axis.push(packets[i].timestamp);
if (packets[i].src == target) {
y1_axis.push(packets[i].size);
x_axis.push(activeSet[i].timestamp);
if (activeSet[i].src == target) {
y1_axis.push(activeSet[i].size);
y2_axis.push(0);
} else {
y1_axis.push(0);
y2_axis.push(0.0 - packets[i].size);
y2_axis.push(0.0 - activeSet[i].size);
}
}
html += "</table>";
$("#dump").html(html);
paginator(n);
// Make the graph
// Make the graph
let graph = document.getElementById("graph");
let data = [
{x: x_axis, y:y1_axis, name: 'Download', type: 'scatter', mode: 'markers', error_x: { type: 'percent', value: capacity[0], symetric: false, valueminus: 0 }},
{x: x_axis, y:y2_axis, name: 'Upload', type: 'scatter', mode: 'markers', error_x: { type: 'percent', value: capacity[1], symetric: false, valueminus: 0 }},
];
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: 'Bytes' }, xaxis: {automargin: true, title: "Nanoseconds"} }, { responsive: true });
if (activeChart == 0) {
// Render the timeline chart
let data = [
{x: x_axis, y:y1_axis, name: 'Download', type: 'scatter', mode: 'markers', error_x: { type: 'percent', value: capacity[0], symetric: false, valueminus: 0 }},
{x: x_axis, y:y2_axis, name: 'Upload', type: 'scatter', mode: 'markers', error_x: { type: 'percent', value: capacity[1], symetric: false, valueminus: 0 }},
];
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: 'Bytes' }, xaxis: {automargin: true, title: "Nanoseconds"} }, { responsive: true });
} else if (activeChart == 1) {
// Render the piano roll chart
let flowGraphY = {};
for (var i=start; i<end; ++i) {
let flow_id = activeSet[i].flow_id;
if (flowGraphY.hasOwnProperty(flow_id)) {
flowGraphY[flow_id].x.push(activeSet[i].timestamp);
flowGraphY[flow_id].y.push(flows[flow_id].flowCounter);
} else {
flowGraphY[flow_id] = {
"x": [ activeSet[i].timestamp ],
"y": [ flows[flow_id].flowCounter ],
}
}
}
let data = [];
for (flow in flowGraphY) {
//console.log(flowGraphY[flow]);
data.push({
x: flowGraphY[flow].x, y: flowGraphY[flow].y, name: flow, type: 'scatter', mode: 'markers',
});
}
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: 'Flow' }, xaxis: {automargin: true, title: "Nanoseconds"} }, { responsive: true });
} else if (activeChart == 2) {
// Render the window chart
let flowGraphY = {};
for (var i=start; i<end; ++i) {
let flow_id = activeSet[i].flow_id;
if (flow_id.includes("TCP")) {
if (flowGraphY.hasOwnProperty(flow_id)) {
flowGraphY[flow_id].x.push(activeSet[i].timestamp);
flowGraphY[flow_id].y.push(activeSet[i].tcp_window);
} else {
flowGraphY[flow_id] = {
"x": [ activeSet[i].timestamp ],
"y": [ activeSet[i].tcp_window ],
}
}
}
}
let data = [];
for (flow in flowGraphY) {
//console.log(flowGraphY[flow]);
data.push({
x: flowGraphY[flow].x, y: flowGraphY[flow].y, name: flow, type: 'scatter', mode: 'markers',
});
}
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: 'Window Size' }, xaxis: {automargin: true, title: "Nanoseconds"} }, { responsive: true });
}
}
let circuit_id = null;
@ -217,17 +335,32 @@ if (hdr->cwr) flags |= 128;
target = params.id;
$.get("/api/packet_dump/" + params.id, (data) => {
data.sort((a,b) => a.timestamp - b.timestamp);
let min_ts = null;
for (let i=0; i<data.length; ++i) {
if (min_ts == null || min_ts > data[i].timestamp) {
min_ts = data[i].timestamp;
// Find the minimum timestamp
let min_ts = data.reduce((prev, curr) => prev.timestamp < curr.timestamp ? prev : curr).timestamp;
// Set the displayed timestamp to be (ts - min)
data.forEach(packet => packet.timestamp -= min_ts);
// Divide the packets into flows and append the flow_id
let flowCounter = 0;
data.forEach(packet => {
let flow_id = proto(packet.ip_protocol) + " " + packet.src + ":" + packet.src_port + " <-> " + packet.dst + ":" + packet.dst_port;
let reverse_flow_id = proto(packet.ip_protocol) + " " + packet.dst + ":" + packet.dst_port + " <-> " + packet.src + ":" + packet.src_port;
if (flows.hasOwnProperty(flow_id)) {
packet.flow_id = flow_id;
} else if (flows.hasOwnProperty(reverse_flow_id)) {
packet.flow_id = reverse_flow_id;
} else {
flows[flow_id] = { flowCounter };
packet.flow_id = flow_id;
flowCounter++;
}
}
for (let i=0; i<data.length; ++i) {
data[i].timestamp -= min_ts;
}
});
packets = data;
pages = Math.ceil((packets.length / PAGE_SIZE));
activeSet = packets;
pages = Math.ceil((activeSet.length / PAGE_SIZE));
starting_timestamp = min_ts;
paginator(0);
viewPage(0);

View File

@ -46,7 +46,7 @@ pub struct CakeDiffTin {
pub backlog_bytes: u32,
pub drops: u32,
pub marks: u32,
pub avg_delay_us: u32,
pub base_delay_us: u32,
}
fn cake_diff(
@ -65,7 +65,7 @@ fn cake_diff(
backlog_bytes: new.backlog_bytes,
drops: new.drops.saturating_sub(prev.drops),
marks: new.ecn_marks.saturating_sub(prev.ecn_marks),
avg_delay_us: new.avg_delay_us,
base_delay_us: new.base_delay_us,
})
.collect();
return Ok(QueueDiff::Cake(CakeDiff {

View File

@ -101,7 +101,7 @@ impl Into<CakeDiffTinTransit> for CakeDiffTin {
backlog_bytes: self.backlog_bytes,
drops: self.drops,
marks: self.marks,
avg_delay_us: self.avg_delay_us,
base_delay_us: self.base_delay_us,
}
}
}