Stop storing a node-manager wide ringbuffer of data utilization, instead request it when it's wanted. Use the same MultiRingBuffer class, with added render method, to handle front-page utilization.

This commit is contained in:
Herbert Wolverson 2023-03-06 22:28:36 +00:00
parent 39f605e195
commit fc5b9ad3d4
7 changed files with 56 additions and 158 deletions

View File

@ -46,14 +46,12 @@ fn rocket() -> _ {
static_pages::klingon,
// API calls
tracker::current_throughput,
tracker::throughput_ring,
tracker::cpu_usage,
tracker::ram_usage,
tracker::top_10_downloaders,
tracker::worst_10_rtt,
tracker::rtt_histogram,
tracker::host_counts,
tracker::busy_quantile,
shaped_devices::all_shaped_devices,
shaped_devices::shaped_devices_count,
shaped_devices::shaped_devices_range,

View File

@ -5,9 +5,7 @@
mod cpu_ram;
mod lqosd_stats;
mod shaped_devices;
mod throughput;
pub use cpu_ram::*;
pub use lqosd_stats::*;
pub use shaped_devices::*;
pub use throughput::*;

View File

@ -1,63 +0,0 @@
use lazy_static::*;
use parking_lot::RwLock;
use rocket::serde::Serialize;
lazy_static! {
/// Global storage of the current throughput counter.
pub static ref CURRENT_THROUGHPUT : RwLock<ThroughputPerSecond> = RwLock::new(ThroughputPerSecond::default());
}
lazy_static! {
/// Global storage of the last N seconds throughput buffer.
pub static ref THROUGHPUT_BUFFER : RwLock<ThroughputRingbuffer> = RwLock::new(ThroughputRingbuffer::new());
}
/// Stores total system throughput per second.
#[derive(Debug, Clone, Copy, Serialize, Default)]
#[serde(crate = "rocket::serde")]
pub struct ThroughputPerSecond {
pub bits_per_second: (u64, u64),
pub packets_per_second: (u64, u64),
pub shaped_bits_per_second: (u64, u64),
}
/// How many entries (at one per second) should we keep in the
/// throughput ringbuffer?
const RINGBUFFER_SAMPLES: usize = 300;
/// Stores Throughput samples in a ringbuffer, continually
/// updating. There are always RINGBUFFER_SAMPLES available,
/// allowing for non-allocating/non-growing storage of
/// throughput for the dashboard summaries.
pub struct ThroughputRingbuffer {
readings: Vec<ThroughputPerSecond>,
next: usize,
}
impl ThroughputRingbuffer {
fn new() -> Self {
Self {
readings: vec![ThroughputPerSecond::default(); RINGBUFFER_SAMPLES],
next: 0,
}
}
pub fn store(&mut self, reading: ThroughputPerSecond) {
self.readings[self.next] = reading;
self.next += 1;
self.next %= RINGBUFFER_SAMPLES;
}
pub fn get_result(&self) -> Vec<ThroughputPerSecond> {
let mut result = Vec::with_capacity(RINGBUFFER_SAMPLES);
for i in self.next..RINGBUFFER_SAMPLES {
result.push(self.readings[i]);
}
for i in 0..self.next {
result.push(self.readings[i]);
}
result
}
}

View File

@ -129,7 +129,6 @@ fn watch_for_shaped_devices_changing() -> Result<()> {
async fn get_data_from_server() -> Result<()> {
// Send request to lqosd
let requests = vec![
BusRequest::GetCurrentThroughput,
BusRequest::GetTopNDownloaders { start: 0, end: 10 },
BusRequest::GetWorstRtt { start: 0, end: 10 },
BusRequest::RttHistogram,
@ -137,26 +136,7 @@ async fn get_data_from_server() -> Result<()> {
];
for r in bus_request(requests).await?.iter() {
match r {
BusResponse::CurrentThroughput {
bits_per_second,
packets_per_second,
shaped_bits_per_second,
} => {
{
let mut lock = CURRENT_THROUGHPUT.write();
lock.bits_per_second = *bits_per_second;
lock.packets_per_second = *packets_per_second;
} // Lock scope
{
let mut lock = THROUGHPUT_BUFFER.write();
lock.store(ThroughputPerSecond {
packets_per_second: *packets_per_second,
bits_per_second: *bits_per_second,
shaped_bits_per_second: *shaped_bits_per_second,
});
}
}
match r {
BusResponse::TopDownloaders(stats) => {
*TOP_10_DOWNLOADERS.write() = stats.clone();
}

View File

@ -1,15 +1,15 @@
mod cache;
mod cache_manager;
use self::cache::{
CPU_USAGE, CURRENT_THROUGHPUT, HOST_COUNTS, NUM_CPUS, RAM_USED,
RTT_HISTOGRAM, THROUGHPUT_BUFFER, TOP_10_DOWNLOADERS, TOTAL_RAM,
CPU_USAGE, HOST_COUNTS, NUM_CPUS, RAM_USED,
RTT_HISTOGRAM, TOP_10_DOWNLOADERS, TOTAL_RAM,
WORST_10_RTT,
};
use crate::{auth_guard::AuthGuard, tracker::cache::ThroughputPerSecond};
use crate::auth_guard::AuthGuard;
pub use cache::{SHAPED_DEVICES, UNKNOWN_DEVICES};
pub use cache_manager::update_tracking;
use lazy_static::lazy_static;
use lqos_bus::{IpStats, TcHandle};
use lqos_bus::{IpStats, TcHandle, bus_request, BusRequest, BusResponse};
use lqos_config::LibreQoSConfig;
use parking_lot::Mutex;
use rocket::serde::{json::Json, Deserialize, Serialize};
@ -60,15 +60,31 @@ impl From<&IpStats> for IpStatsWithPlan {
}
}
#[get("/api/current_throughput")]
pub fn current_throughput(_auth: AuthGuard) -> Json<ThroughputPerSecond> {
let result = *CURRENT_THROUGHPUT.read();
Json(result)
/// Stores total system throughput per second.
#[derive(Debug, Clone, Copy, Serialize, Default)]
#[serde(crate = "rocket::serde")]
pub struct ThroughputPerSecond {
pub bits_per_second: (u64, u64),
pub packets_per_second: (u64, u64),
pub shaped_bits_per_second: (u64, u64),
}
#[get("/api/throughput_ring")]
pub fn throughput_ring(_auth: AuthGuard) -> Json<Vec<ThroughputPerSecond>> {
let result = THROUGHPUT_BUFFER.read().get_result();
#[get("/api/current_throughput")]
pub async fn current_throughput(_auth: AuthGuard) -> Json<ThroughputPerSecond> {
let mut result = ThroughputPerSecond::default();
if let Ok(messages) = bus_request(vec![BusRequest::GetCurrentThroughput]).await {
for msg in messages {
if let BusResponse::CurrentThroughput {
bits_per_second,
packets_per_second,
shaped_bits_per_second,
} = msg {
result.bits_per_second = bits_per_second;
result.packets_per_second = packets_per_second;
result.shaped_bits_per_second = shaped_bits_per_second;
}
}
}
Json(result)
}
@ -123,28 +139,3 @@ lazy_static! {
Mutex::new(lqos_config::LibreQoSConfig::load().unwrap());
}
#[get("/api/busy_quantile")]
pub fn busy_quantile(_auth: AuthGuard) -> Json<Vec<(u32, u32)>> {
let (down_capacity, up_capacity) = {
let lock = CONFIG.lock();
(
lock.total_download_mbps as f64 * 1_000_000.0,
lock.total_upload_mbps as f64 * 1_000_000.0,
)
};
let throughput = THROUGHPUT_BUFFER.read().get_result();
let mut result = vec![(0, 0); 10];
throughput.iter().for_each(|tp| {
let (down, up) = tp.bits_per_second;
let (down, up) = (down * 8, up * 8);
//println!("{down_capacity}, {up_capacity}, {down}, {up}");
let (down, up) = (
if down_capacity > 0.0 { down as f64 / down_capacity } else { 0.0 },
if up_capacity > 0.0 { up as f64 / up_capacity } else { 0.0 },
);
let (down, up) = ((down * 10.0) as usize, (up * 10.0) as usize);
result[usize::min(9, down)].0 += 1;
result[usize::min(0, up)].1 += 1;
});
Json(result)
}

View File

@ -229,11 +229,7 @@ class MultiRingBuffer {
}
}
}
/*let v = buffers[rootName];
let dn = { x: v.x_axis, y: v.download, name: "DL", type: 'scatter', fill: null };
let up = { x: v.x_axis, y: v.upload, name: "UL", type: 'scatter', fill: null };
graphData.push(dn);
graphData.push(up);*/
let graph = document.getElementById(target_div);
Plotly.newPlot(
graph,
@ -246,6 +242,24 @@ class MultiRingBuffer {
},
{ responsive: true, displayModeBar: false });
}
plotTotalThroughput(target_div) {
let graph = document.getElementById(target_div);
let totalDown = yValsRingSort(this.data['total'].download, this.data['total'].head, this.data['total'].capacity);
let totalUp = yValsRingSort(this.data['total'].upload, this.data['total'].head, this.data['total'].capacity);
let shapedDown = yValsRingSort(this.data['shaped'].download, this.data['shaped'].head, this.data['shaped'].capacity);
let shapedUp = yValsRingSort(this.data['shaped'].upload, this.data['shaped'].head, this.data['shaped'].capacity);
let x = this.data['total'].x_axis;
let data = [
{x: x, y:totalDown, name: 'Download', type: 'scatter', marker: {color: 'rgb(255,160,122)'}},
{x: x, y:totalUp, name: 'Upload', type: 'scatter', marker: {color: 'rgb(255,160,122)'}},
{x: x, y:shapedDown, name: 'Shaped Download', type: 'scatter', fill: 'tozeroy', marker: {color: 'rgb(124,252,0)'}},
{x: x, y:shapedUp, name: 'Shaped Upload', type: 'scatter', fill: 'tozeroy', marker: {color: 'rgb(124,252,0)'}},
];
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true, title: "Time since now (seconds)"} }, { responsive: true });
}
}
class RingBuffer {

View File

@ -159,40 +159,21 @@
<footer>&copy; 2022-2023, LibreQoE LLC</footer>
<script>
let throughput = new MultiRingBuffer(300);
function updateCurrentThroughput() {
$.get("/api/current_throughput", (tp) => {
$("#ppsDown").text(scaleNumber(tp.packets_per_second[0]));
$("#ppsUp").text(scaleNumber(tp.packets_per_second[1]));
$("#bpsDown").text(scaleNumber(tp.bits_per_second[0]));
$("#bpsUp").text(scaleNumber(tp.bits_per_second[1]));
setTimeout(updateCurrentThroughput, 1000);
});
}
function updateThroughputGraph() {
$.get("/api/throughput_ring", (tp) => {
let graph = document.getElementById("tpGraph");
let x = [];
let y = []; // Down
let y2 = []; // Up
let y3 = []; // Shaped Down
let y4 = []; // Shaped Up
for (i=0; i<300; i++) {
x.push(i);
y.push(tp[i].bits_per_second[0]);
y2.push(0.0 - tp[i].bits_per_second[1]);
y3.push(tp[i].shaped_bits_per_second[0]);
y4.push(0.0 - tp[i].shaped_bits_per_second[1]);
}
let data = [
{x: x, y:y, name: 'Download', type: 'scatter'},
{x: x, y:y2, name: 'Upload', type: 'scatter'},
{x: x, y:y3, name: 'Shaped Download', type: 'scatter', fill: 'tozeroy'},
{x: x, y:y4, name: 'Shaped Upload', type: 'scatter', fill: 'tozeroy'},
];
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true }, xaxis: {automargin: true, title: "Time since now (seconds)"} }, { responsive: true });
//console.log(tp);
setTimeout(updateThroughputGraph, 1000);
throughput.push("pps", tp.packets_per_second[0], tp.packets_per_second[1]);
throughput.push("total", tp.bits_per_second[0], tp.bits_per_second[1]);
throughput.push("shaped", tp.shaped_bits_per_second[0], tp.shaped_bits_per_second[1]);
throughput.plotTotalThroughput("tpGraph");
setTimeout(updateCurrentThroughput, 1000);
});
}
@ -321,14 +302,13 @@
colorReloadButton();
updateCurrentThroughput();
updateThroughputGraph();
updateCpu();
updateRam();
updateTop10();
updateWorst10();
updateHistogram();
updateHostCounts();
updateThroughputQuantile();
//updateThroughputQuantile();
}
$(document).ready(start);