import { useMemo, useState } from "react"; import { fmtBytes, type Endpoint, type Session, type Stats, type TimeBucket } from "../api"; import { usePoll } from "../ui"; import { SessionPicker, Bar, PacketsLink } from "../components/chrome"; export default function Statistics({ sessionId }: { sessionId: string }) { const [sessions] = usePoll("/api/captures", 5000); const id = sessionId || sessions?.[0]?.id || ""; const [stats, err] = usePoll(id ? `/api/captures/${id}/statistics?buckets=300` : null, 0); const [eps] = usePoll(id ? `/api/captures/${id}/endpoints?limit=15` : null); const [metric, setMetric] = useState<"bytes" | "packets">("bytes"); const dur = stats ? stats.end - stats.start : 0; const bucketS = stats?.counters?.bucket_seconds || 1; const maxProto = stats?.protocols[0]?.[metric] ?? 1; const maxSize = useMemo(() => Math.max(1, ...(stats?.size_buckets ?? []).map((s) => s[metric])), [stats, metric]); const maxEp = eps?.[0]?.bytes ?? 1; return (

Statistics

Computed from the packet index. Every value is a count of captured packets or bytes, not an estimate.

{err &&
{err}
} {!stats ?
Loading…
: stats.packets === 0 ?
No packets captured yet.
: ( <>

Packets

{stats.packets.toLocaleString()}

Bytes

{fmtBytes(stats.bytes)}

Duration

{dur > 0 ? dur.toFixed(1) + " s" : "–"}
{dur > 0 ? `${Math.round(stats.packets / dur)} pkt/s avg · ${fmtBytes(stats.bytes / dur)}/s` : ""}

TCP streams

{stats.tcp_streams.toLocaleString()}

Traffic over time

{metric} per {bucketS >= 1 ? bucketS + " s" : "bucket"}

Protocol hierarchy

{stats.protocols.slice(0, 15).map((p) => ( ))}
{p.name} {((p[metric] / (metric === "bytes" ? stats.bytes : stats.packets)) * 100).toFixed(1)}%

Top talkers

{eps?.map((e) => ( ))}
AddressTrafficTx / Rx
{e.addr} {fmtBytes(e.tx_bytes)} / {fmtBytes(e.rx_bytes)}

Packet size distribution

{stats.size_buckets.map((b) => ( ))}
{b.name}

Definitions

Protocol
The highest-layer protocol TShark assigned to each packet (one packet counts once).
TCP streams
Distinct tshark stream indices, counted per segment, so a connection crossing a segment boundary counts more than once.
Tx / Rx
Bytes with this address as source / destination. A packet contributes to both its endpoints.
Bytes
On-the-wire frame length as captured.
)}
); } function Timeline({ buckets, metric, t0 }: { buckets: TimeBucket[]; metric: "bytes" | "packets"; t0: number }) { const [hover, setHover] = useState(null); const W = 1000, H = 160, pad = 4; if (!buckets.length) return
No data
; const max = Math.max(1, ...buckets.map((b) => b[metric])); const n = buckets.length; const bw = (W - pad * 2) / n; const bars = buckets.map((b, i) => { const h = (b[metric] / max) * (H - 20); return setHover(i)} onMouseLeave={() => setHover(null)} />; }); const hb = hover != null ? buckets[hover] : null; return (
{bars}
0 s {hb && +{(hb.t - t0).toFixed(1)} s: {metric === "bytes" ? fmtBytes(hb.bytes) : hb.packets + " pkts"}} {(buckets[n - 1].t - t0).toFixed(0)} s
); }