import { WingfoilClient } from "@wingfoil/client";
import { LatencyTracker } from "@wingfoil/client/tracing";
import {
LatencyChart,
FlameGraph,
BreakdownPanel,
StatusPill,
} from "@wingfoil/telemetry";
const STAGES = [
{ name: 'ws_recv→ws_publish', color: '#58a6ff', value: (rt) => delta(rt, 0, 1) },
{ name: 'ws_publish→gw_recv', color: '#3fb950', value: (rt) => delta(rt, 1, 2) },
{ name: 'gw_recv→gw_price', color: '#f0883e', value: (rt) => delta(rt, 2, 3) },
{ name: 'gw_price→fix_send', color: '#f85149', value: (rt) => delta(rt, 3, 4) },
{ name: 'fix_send→fix_recv', color: '#bc8cff', value: (rt) => delta(rt, 4, 5) },
{ name: 'fix_recv→gw_publish', color: '#79c0ff', value: (rt) => delta(rt, 5, 6) },
{ name: 'gw_publish→ws_sub_recv', color: '#ff7b72', value: (rt) => delta(rt, 6, 7) },
{ name: 'ws_sub_recv→ws_send', color: '#a371f7', value: (rt) => delta(rt, 7, 8) },
{ name: 'wire RTT (out+in)', color: '#e3b341', value: (rt) => rt.wireRttNs },
];
const FLAME_LAYERS = [
{ name: 'browser', detail: 'browser → ws_server → browser (full RTT)', color: '#58a6ff', value: (rt) => rt.rttNs },
{ name: 'ws_server', detail: 'ws_recv → ws_send (this server, end-to-end)', color: '#3fb950', value: (rt) => delta(rt, 0, 8) },
{ name: 'iceoryx2', detail: 'ws_publish → ws_sub_recv (shm + fix_gw)', color: '#bc8cff', value: (rt) => delta(rt, 1, 7) },
{ name: 'fix_gw', detail: 'gw_recv → gw_publish (this process)', color: '#f0883e', value: (rt) => delta(rt, 2, 6) },
{ name: 'lmax', detail: 'fix_send → fix_recv (FIX/TLS to LMAX + match)', color: '#f85149', value: (rt) => delta(rt, 4, 5) },
];
function delta(rt, a, b) {
const A = rt.stamps[a], B = rt.stamps[b];
if (A == null || B == null) return 0;
return Math.max(0, B - A);
}
const client = new WingfoilClient({
url: `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`,
codec: 'json',
});
const tracker = new LatencyTracker({
client,
outbound: 'orders',
inbound: 'fills',
echo: 'latency_echo',
});
document.getElementById('session').textContent = tracker.sessionHex.slice(0, 8) + '…';
(function initGrafana() {
const origin = `${location.protocol}//${location.hostname}:3000`;
const url = `${origin}/d/wingfoil-latency-e2e/wingfoil-latency-end-to-end` +
`?var-session=${tracker.sessionHex}` +
`&kiosk=tv&theme=dark&refresh=5s&from=now-15m&to=now`;
document.getElementById('grafana').src = url;
document.getElementById('grafana-pop').href = url;
})();
let sent = 0, filled = 0, sumPx = 0, pricedFills = 0;
let nextSide = 0;
let ticker = null;
function startStream() {
const rate = parseInt(document.getElementById('rate').value, 10);
const qty = parseInt(document.getElementById('qty').value, 10);
const sideSel = document.getElementById('side').value;
if (ticker) clearInterval(ticker);
ticker = setInterval(() => {
let side;
if (sideSel === 'alt') { side = nextSide; nextSide ^= 1; }
else side = parseInt(sideSel, 10);
sent += 1;
document.getElementById('sent').textContent = sent.toLocaleString();
tracker.send({ side, qty });
}, Math.max(50, Math.floor(1000 / rate)));
const btn = document.getElementById('start');
btn.textContent = 'stop'; btn.classList.add('stop');
btn.onclick = stopStream;
}
function stopStream() {
if (ticker) { clearInterval(ticker); ticker = null; }
const btn = document.getElementById('start');
btn.textContent = 'start'; btn.classList.remove('stop');
btn.onclick = startStream;
}
window.addEventListener('DOMContentLoaded', () => {
new StatusPill({ host: document.getElementById('status'), client });
const chart = new LatencyChart({
host: document.getElementById('chart'),
stages: STAGES,
});
const flame = new FlameGraph({
host: document.getElementById('flamegraph'),
layers: FLAME_LAYERS,
tipEl: document.getElementById('flamegraph-tip'),
metaEl: document.getElementById('flamegraph-meta'),
legendHost: document.getElementById('flame-legend'),
});
const breakdown = new BreakdownPanel({
buckets: [
{ el: document.getElementById('bd-total'), value: (rt) => rt.rttNs },
{ el: document.getElementById('bd-resident'), value: (rt) => rt.serverResidentNs },
{ el: document.getElementById('bd-wire'), value: (rt) => rt.wireRttNs },
],
});
document.getElementById('start').onclick = startStream;
tracker.onResponse((rt) => {
const fill = rt.payload;
filled += 1;
document.getElementById('filled').textContent = filled.toLocaleString();
if (fill.fill_price_bps > 0 && fill.filled_qty > 0) {
sumPx += fill.fill_price_bps;
pricedFills += 1;
document.getElementById('px').textContent = (sumPx / pricedFills / 10000).toFixed(5);
}
const rttMs = (rt.rttNs / 1_000_000).toFixed(2);
document.getElementById('rtt').textContent = rttMs + ' ms';
document.getElementById('rtt-meta').textContent = `last fill ${rttMs} ms · ${filled} fills`;
chart.push(rt);
flame.push(rt);
breakdown.push(rt);
});
});