wingfoil 6.0.5

graph based stream processing framework
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>wingfoil latency end-to-end</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uplot@1.6.31/dist/uPlot.min.css" />
  <style>
    :root {
      --bg: #0d1117; --panel: #161b22; --fg: #e6edf3; --muted: #8b949e;
      --green: #3fb950; --red: #f85149; --accent: #58a6ff; --border: #21262d;
    }
    * { box-sizing: border-box; }
    body { margin: 0; font: 13px/1.4 system-ui, sans-serif; background: var(--bg); color: var(--fg); }
    a { color: var(--accent); }
    code { background: var(--bg); padding: 1px 5px; border-radius: 3px; font-size: 12px; }

    header {
      padding: 8px 14px; border-bottom: 1px solid var(--border);
      display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
    }
    header h1 { margin: 0; font-size: 14px; font-weight: 600; white-space: nowrap; }
    header .byline { font-size: 11px; color: var(--muted); white-space: nowrap; }
    header .byline code { background: transparent; padding: 0; }
    header .spacer { flex: 1; }
    header .ctl { display: flex; gap: 6px; align-items: center; font-size: 12px; color: var(--muted); }
    header .ctl select, header .ctl input { background: var(--bg); color: var(--fg); border: 1px solid #30363d; border-radius: 4px; padding: 3px 6px; font-size: 12px; }
    header button { background: var(--accent); color: white; border: 0; padding: 5px 14px; border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 12px; }
    header button:disabled { background: #30363d; color: var(--muted); cursor: not-allowed; }
    header button.stop { background: var(--red); }
    header .links { display: flex; gap: 10px; align-items: center; font-size: 11px; }

    .pill { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; }
    .pill.live { background: rgba(63, 185, 80, 0.15); color: var(--green); }
    .pill.connecting { background: rgba(240, 136, 62, 0.15); color: #f0883e; }
    .pill.idle { background: rgba(139, 148, 158, 0.15); color: var(--muted); }

    main { padding: 12px 14px; display: grid; gap: 12px; grid-template-columns: 1fr; }

    .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; }
    .panel-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; gap: 8px; }
    .panel-head h2 { margin: 0; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
    .panel-head .meta { font-size: 11px; color: var(--muted); }

    .stats {
      display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
    }
    .stats .stat { background: var(--bg); border-radius: 4px; padding: 6px 10px; }
    .stats .k { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
    .stats .v { font-size: 16px; font-variant-numeric: tabular-nums; margin-top: 2px; }

    .breakdown {
      display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px;
    }
    .breakdown .seg { background: var(--bg); border-radius: 4px; padding: 6px 10px; border-left: 3px solid var(--accent); }
    .breakdown .seg.resident { border-left-color: var(--green); }
    .breakdown .seg.wire { border-left-color: #f0883e; }
    .breakdown .k { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
    .breakdown .k .hint { text-transform: none; letter-spacing: 0; color: var(--muted); opacity: 0.8; margin-left: 4px; }
    .breakdown .v { font-size: 14px; font-variant-numeric: tabular-nums; margin-top: 2px; }

    .legend {
      margin-top: 10px; padding: 8px 10px; background: var(--bg); border-radius: 4px;
      font-size: 11px; color: var(--muted); line-height: 1.5;
    }
    .legend summary { cursor: pointer; color: var(--fg); font-weight: 600; }
    .legend p { margin: 6px 0 0; }
    .legend code { background: var(--panel); }

    #chart { width: 100%; }

    /* Flamegraph: nested rectangles, browser RTT at the bottom, FIX→LMAX at the top. */
    .flamegraph-wrap { position: relative; }
    #flamegraph { width: 100%; display: block; }
    #flamegraph-tip {
      position: absolute; pointer-events: none; display: none;
      background: var(--bg); color: var(--fg); border: 1px solid var(--border);
      border-radius: 4px; padding: 6px 8px; font-size: 11px; line-height: 1.4;
      font-variant-numeric: tabular-nums; white-space: nowrap; z-index: 5;
    }
    .flame-legend { display: grid; grid-template-columns: 1fr; gap: 4px; margin-top: 8px; font-size: 11px; color: var(--muted); }
    .flame-legend .row { display: flex; align-items: baseline; gap: 6px; }
    .flame-legend .sw { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: middle; flex-shrink: 0; }
    .flame-legend .name { color: var(--fg); font-weight: 600; min-width: 88px; }

    .grafana-wrap { position: relative; }
    #grafana { width: 100%; height: 280px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); }
    .grafana-note { color: var(--muted); font-size: 11px; margin: 6px 0 0; }

    @media (min-width: 980px) {
      main { grid-template-columns: 1fr 1fr; }
      .chart-panel { grid-column: 1 / -1; }
    }
    @media (max-width: 560px) {
      .stats { grid-template-columns: repeat(2, 1fr); }
      header h1 { font-size: 13px; }
      header .grafana-link { display: none; }
      #grafana { height: 220px; }
    }
  </style>
</head>
<body>
  <header>
    <h1>wingfoil latency e2e</h1>
    <span class="byline">Rust DAG stream processor · live FIX round-trip to LMAX London</span>
    <span id="status" class="pill idle">disconnected</span>
    <span style="color:var(--muted);font-size:11px">session <code id="session"></code></span>
    <div class="spacer"></div>
    <div class="ctl">
      <label>side
        <select id="side">
          <option value="alt">alt</option>
          <option value="0">buy</option>
          <option value="1">sell</option>
        </select>
      </label>
      <label>qty <input id="qty" type="number" min="1" value="1" style="width:50px"/></label>
      <label>Hz <input id="rate" type="number" min="1" max="20" value="2" style="width:42px"/></label>
      <button id="start">start</button>
    </div>
    <div class="links">
      <a href="https://github.com/wingfoil-io/wingfoil" target="_blank" rel="noopener">github</a>
      <a href="https://github.com/wingfoil-io/wingfoil/tree/main/wingfoil/examples/latency_e2e" target="_blank" rel="noopener">source</a>
      <a href="/metrics" target="_blank">prometheus</a>
    </div>
  </header>

  <main>
    <section class="panel chart-panel">
      <div class="panel-head">
        <h2>per-hop latency — this session</h2>
        <span class="meta" id="rtt-meta"></span>
      </div>
      <div class="stats">
        <div class="stat"><div class="k">sent</div><div class="v" id="sent">0</div></div>
        <div class="stat"><div class="k">filled</div><div class="v" id="filled">0</div></div>
        <div class="stat"><div class="k">avg fill px</div><div class="v" id="px"></div></div>
        <div class="stat"><div class="k">round-trip</div><div class="v" id="rtt"></div></div>
      </div>
      <div class="breakdown">
        <div class="seg total"><div class="k">rtt mean <span class="hint">last 32 fills</span></div><div class="v" id="bd-total"></div></div>
        <div class="seg resident"><div class="k">resident <span class="hint">ws_recv → ws_send (in-process)</span></div><div class="v" id="bd-resident"></div></div>
        <div class="seg wire"><div class="k">wire <span class="hint">FIX/TLS to LMAX + WS legs</span></div><div class="v" id="bd-wire"></div></div>
      </div>
      <div id="chart" style="margin-top:10px"></div>
      <details class="legend">
        <summary>how to read this</summary>
        <p>
          Each fill traverses nine wingfoil stamp points: <code>ws_recv → ws_publish → gw_recv → gw_price → fix_send → fix_recv → gw_publish → ws_sub_recv → ws_send</code>.
          The chart plots the per-hop delta on a log Y-axis (nanoseconds). The flamegraph below shows the nested call stack: browser at the bottom (full round-trip), each layer above is a strict sub-interval of the layer below — exactly like a CPU flamegraph, but the samples are wall-clock fills.
        </p>
        <p>
          <strong>Resident</strong> is everything inside this server (two processes connected by iceoryx2 shared memory). <strong>Wire</strong> is the FIX-over-TLS round-trip to LMAX plus the WebSocket legs to your browser — derived as <code>rtt_total − resident</code>, both measured in a single clock domain so no NTP sync is needed.
        </p>
      </details>
    </section>

    <section class="panel flamegraph-wrap">
      <div class="panel-head">
        <h2>flamegraph — nested call stack (rolling mean)</h2>
        <span class="meta" id="flamegraph-meta">n=0</span>
      </div>
      <canvas id="flamegraph" height="200"></canvas>
      <div id="flamegraph-tip"></div>
      <div class="flame-legend" id="flame-legend"></div>
    </section>

    <section class="panel grafana-wrap">
      <div class="panel-head">
        <h2>grafana — session traces</h2>
        <a id="grafana-pop" href="#" target="_blank" rel="noopener" style="font-size:11px">↗ open in new window</a>
      </div>
      <iframe id="grafana" title="Grafana — session traces"></iframe>
      <p class="grafana-note">
        Aggregate p50/p99 across all sessions are in Prometheus at
        <a href="/metrics" target="_blank"><code>:9091/metrics</code></a>.
        Per-session traces are pushed to Tempo via OTLP and pre-filtered to this session above.
        Not loading? <code>docker compose -f wingfoil/examples/latency_e2e/docker-compose.yml up -d</code>
      </p>
    </section>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/uplot@1.6.31/dist/uPlot.iife.min.js"></script>
  <script type="importmap">
    {
      "imports": {
        "@wingfoil/client": "https://esm.sh/@wingfoil/client@6.0.5",
        "@wingfoil/client/tracing": "https://esm.sh/@wingfoil/client@6.0.5/tracing",
        "@wingfoil/telemetry": "https://esm.sh/@wingfoil/telemetry@0.1.0"
      }
    }
  </script>
  <script type="module" src="app.js"></script>
</body>
</html>