feat: live bandwidth tracking (RX/TX MB/s + avg response size)
- Track data_received / data_sent bytes in live metric aggregation - Compute bwInMBps, bwOutMBps, avgRespKB per second - Bandwidth row below stats strip: ↓ RX | ↑ TX | Avg size - RX colour: green <20 MB/s, yellow 20-50 MB/s, red >50 MB/s - Warning banner when RX >20 MB/s: bandwidth may cause p99 spikes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -171,12 +171,13 @@ app.post('/api/tests', (req, res) => {
|
|||||||
|
|
||||||
// Live metric aggregation state
|
// Live metric aggregation state
|
||||||
const live = {
|
const live = {
|
||||||
durations: [], // http_req_duration values (ms)
|
durations: [],
|
||||||
p90durations: [], // sampled for percentile calc
|
|
||||||
totalReqs: 0,
|
totalReqs: 0,
|
||||||
failedReqs: 0,
|
failedReqs: 0,
|
||||||
checksTotal: 0,
|
checksTotal: 0,
|
||||||
checksPassed: 0,
|
checksPassed: 0,
|
||||||
|
bytesIn: 0, // data_received (bytes)
|
||||||
|
bytesOut: 0, // data_sent (bytes)
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
lastBroadcast: 0,
|
lastBroadcast: 0,
|
||||||
};
|
};
|
||||||
@@ -317,6 +318,10 @@ function ingestPoint(obj, live) {
|
|||||||
} else if (metric === 'checks') {
|
} else if (metric === 'checks') {
|
||||||
live.checksTotal++;
|
live.checksTotal++;
|
||||||
if (data.value === 1) live.checksPassed++;
|
if (data.value === 1) live.checksPassed++;
|
||||||
|
} else if (metric === 'data_received') {
|
||||||
|
live.bytesIn += data.value;
|
||||||
|
} else if (metric === 'data_sent') {
|
||||||
|
live.bytesOut += data.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +339,12 @@ function computeLiveMetrics(live) {
|
|||||||
const p95 = pct(95);
|
const p95 = pct(95);
|
||||||
const score = computeScore({ p95, httpErrRate, checksRate });
|
const score = computeScore({ p95, httpErrRate, checksRate });
|
||||||
|
|
||||||
|
const bwInMBps = +(live.bytesIn / elapsed / 1048576).toFixed(2); // MB/s
|
||||||
|
const bwOutMBps = +(live.bytesOut / elapsed / 1048576).toFixed(2);
|
||||||
|
const avgRespKB = live.totalReqs > 0
|
||||||
|
? +(live.bytesIn / live.totalReqs / 1024).toFixed(1)
|
||||||
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalReqs: live.totalReqs,
|
totalReqs: live.totalReqs,
|
||||||
reqPerSec: +(live.totalReqs / elapsed).toFixed(1),
|
reqPerSec: +(live.totalReqs / elapsed).toFixed(1),
|
||||||
@@ -349,6 +360,9 @@ function computeLiveMetrics(live) {
|
|||||||
p99: +pct(99).toFixed(1),
|
p99: +pct(99).toFixed(1),
|
||||||
max: sorted.length ? +sorted[sorted.length - 1].toFixed(1) : 0,
|
max: sorted.length ? +sorted[sorted.length - 1].toFixed(1) : 0,
|
||||||
score,
|
score,
|
||||||
|
bwInMBps,
|
||||||
|
bwOutMBps,
|
||||||
|
avgRespKB,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ function updateGauges(m) {
|
|||||||
setStat('sc-p90', fmtMs(m.p90));
|
setStat('sc-p90', fmtMs(m.p90));
|
||||||
setStat('sc-p95', fmtMs(m.p95));
|
setStat('sc-p95', fmtMs(m.p95));
|
||||||
setStat('sc-p99', fmtMs(m.p99));
|
setStat('sc-p99', fmtMs(m.p99));
|
||||||
|
updateBandwidth(m);
|
||||||
|
|
||||||
// colour p95 cell based on threshold (5 s)
|
// colour p95 cell based on threshold (5 s)
|
||||||
const p95cell = document.getElementById('sc-p95');
|
const p95cell = document.getElementById('sc-p95');
|
||||||
@@ -101,6 +102,23 @@ function updateGauges(m) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateBandwidth(m) {
|
||||||
|
const inEl = document.getElementById('bw-in');
|
||||||
|
const outEl = document.getElementById('bw-out');
|
||||||
|
const sizeEl = document.getElementById('bw-size');
|
||||||
|
const warn = document.getElementById('bw-warning');
|
||||||
|
if (!inEl) return;
|
||||||
|
|
||||||
|
inEl.textContent = m.bwInMBps + ' MB/s';
|
||||||
|
outEl.textContent = m.bwOutMBps + ' MB/s';
|
||||||
|
sizeEl.textContent = m.avgRespKB + ' KB';
|
||||||
|
|
||||||
|
// Flag if inbound bandwidth looks high (>20 MB/s = ~160 Mbps — notable)
|
||||||
|
const high = m.bwInMBps > 20;
|
||||||
|
inEl.style.color = m.bwInMBps > 50 ? 'var(--red)' : m.bwInMBps > 20 ? 'var(--yellow)' : 'var(--green)';
|
||||||
|
warn.style.display = high ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
function resetGauges() {
|
function resetGauges() {
|
||||||
['gauge-score','gauge-checks','gauge-http'].forEach(id => {
|
['gauge-score','gauge-checks','gauge-http'].forEach(id => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
@@ -114,6 +132,14 @@ function resetGauges() {
|
|||||||
['sc-reqs','sc-rps','sc-avg','sc-p90','sc-p95','sc-p99'].forEach(id => setStat(id, '--'));
|
['sc-reqs','sc-rps','sc-avg','sc-p90','sc-p95','sc-p99'].forEach(id => setStat(id, '--'));
|
||||||
const tb = document.getElementById('threshold-banner');
|
const tb = document.getElementById('threshold-banner');
|
||||||
if (tb) { tb.style.display = 'none'; tb.innerHTML = ''; }
|
if (tb) { tb.style.display = 'none'; tb.innerHTML = ''; }
|
||||||
|
const bwIn = document.getElementById('bw-in');
|
||||||
|
if (bwIn) { bwIn.textContent = '-- MB/s'; bwIn.style.color = ''; }
|
||||||
|
const bwOut = document.getElementById('bw-out');
|
||||||
|
if (bwOut) bwOut.textContent = '-- MB/s';
|
||||||
|
const bwSize = document.getElementById('bw-size');
|
||||||
|
if (bwSize) bwSize.textContent = '-- KB';
|
||||||
|
const bwWarn = document.getElementById('bw-warning');
|
||||||
|
if (bwWarn) bwWarn.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderThresholds(summary) {
|
function renderThresholds(summary) {
|
||||||
|
|||||||
@@ -176,6 +176,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bandwidth row -->
|
||||||
|
<div class="bw-row" id="bw-row">
|
||||||
|
<div class="bw-item">
|
||||||
|
<span class="bw-label">↓ RX</span>
|
||||||
|
<span class="bw-val" id="bw-in">-- MB/s</span>
|
||||||
|
</div>
|
||||||
|
<div class="bw-divider"></div>
|
||||||
|
<div class="bw-item">
|
||||||
|
<span class="bw-label">↑ TX</span>
|
||||||
|
<span class="bw-val" id="bw-out">-- MB/s</span>
|
||||||
|
</div>
|
||||||
|
<div class="bw-divider"></div>
|
||||||
|
<div class="bw-item">
|
||||||
|
<span class="bw-label">Avg size</span>
|
||||||
|
<span class="bw-val" id="bw-size">-- KB</span>
|
||||||
|
</div>
|
||||||
|
<div id="bw-warning" class="bw-warning" style="display:none">
|
||||||
|
⚠ High bandwidth — may be causing p99 spikes
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Threshold results -->
|
<!-- Threshold results -->
|
||||||
<div id="threshold-banner" style="display:none"></div>
|
<div id="threshold-banner" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -307,6 +307,42 @@ textarea { font-family: var(--mono); resize: vertical; }
|
|||||||
letter-spacing: .05em;
|
letter-spacing: .05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── BANDWIDTH ROW ──────────────────────────────────────────────────────────── */
|
||||||
|
.bw-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: .8rem 1rem;
|
||||||
|
margin-top: .75rem;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.bw-item { display: flex; align-items: center; gap: .5rem; }
|
||||||
|
.bw-label {
|
||||||
|
font-size: .72rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.bw-val {
|
||||||
|
font-size: .95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--mono);
|
||||||
|
transition: color .4s;
|
||||||
|
}
|
||||||
|
.bw-divider { width: 1px; height: 1.2rem; background: var(--border); flex-shrink: 0; }
|
||||||
|
.bw-warning {
|
||||||
|
font-size: .78rem;
|
||||||
|
color: var(--yellow);
|
||||||
|
background: rgba(245,158,11,.08);
|
||||||
|
border: 1px solid rgba(245,158,11,.25);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: .25rem .65rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── THRESHOLD BANNER ───────────────────────────────────────────────────────── */
|
/* ── THRESHOLD BANNER ───────────────────────────────────────────────────────── */
|
||||||
#threshold-banner {
|
#threshold-banner {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
Reference in New Issue
Block a user