From b79249c4a01af4a3ea5b6ca3630a7b8e0ff36ef5 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Thu, 19 Mar 2026 16:49:06 -0700 Subject: [PATCH] Add more realistic hop stats display --- frontend/src/test/rawPacketStats.test.ts | 18 +++++++++ frontend/src/utils/rawPacketStats.ts | 47 ++++++++++++++++++------ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/frontend/src/test/rawPacketStats.test.ts b/frontend/src/test/rawPacketStats.test.ts index fa1e175..e70b412 100644 --- a/frontend/src/test/rawPacketStats.test.ts +++ b/frontend/src/test/rawPacketStats.test.ts @@ -92,6 +92,24 @@ describe('buildRawPacketStatsSnapshot', () => { expect.objectContaining({ label: 'Control', count: 0 }), ]) ); + expect(stats.hopProfile.map((item) => item.label)).toEqual([ + '0', + '1', + '2-5', + '6-10', + '11-15', + '16+', + ]); + expect(stats.hopProfile).toEqual( + expect.arrayContaining([ + expect.objectContaining({ label: '0', count: 2 }), + expect.objectContaining({ label: '1', count: 1 }), + expect.objectContaining({ label: '2-5', count: 1 }), + expect.objectContaining({ label: '6-10', count: 0 }), + expect.objectContaining({ label: '11-15', count: 0 }), + expect.objectContaining({ label: '16+', count: 0 }), + ]) + ); expect(stats.hopByteWidthProfile).toEqual( expect.arrayContaining([ expect.objectContaining({ label: 'No path', count: 2 }), diff --git a/frontend/src/utils/rawPacketStats.ts b/frontend/src/utils/rawPacketStats.ts index 6784de6..a6f12be 100644 --- a/frontend/src/utils/rawPacketStats.ts +++ b/frontend/src/utils/rawPacketStats.ts @@ -280,6 +280,14 @@ function rankedBreakdown(counts: Map, total: number): RankedPack .map(([label, count]) => ({ label, count, share: share(count, total) })); } +function orderedBreakdown(counts: Map, total: number): RankedPacketStat[] { + return Array.from(counts.entries()).map(([label, count]) => ({ + label, + count, + share: share(count, total), + })); +} + function median(values: number[]): number | null { if (values.length === 0) return null; const sorted = [...values].sort((a, b) => a - b); @@ -297,6 +305,25 @@ function formatTimelineLabel(timestamp: number): string { }); } +function getHopProfileBucket(pathTokenCount: number): string { + if (pathTokenCount <= 0) { + return '0'; + } + if (pathTokenCount === 1) { + return '1'; + } + if (pathTokenCount <= 5) { + return '2-5'; + } + if (pathTokenCount <= 10) { + return '6-10'; + } + if (pathTokenCount <= 15) { + return '11-15'; + } + return '16+'; +} + export function buildRawPacketStatsSnapshot( session: RawPacketStatsSessionState, window: RawPacketStatsWindow, @@ -321,9 +348,12 @@ export function buildRawPacketStatsSnapshot( const payloadCounts = createCountsMap(KNOWN_PAYLOAD_TYPES); const routeCounts = createCountsMap(KNOWN_ROUTE_TYPES); const hopCounts = new Map([ - ['Direct', 0], - ['1 hop', 0], - ['2+ hops', 0], + ['0', 0], + ['1', 0], + ['2-5', 0], + ['6-10', 0], + ['11-15', 0], + ['16+', 0], ]); const hopByteWidthCounts = new Map([ ['No path', 0], @@ -346,13 +376,8 @@ export function buildRawPacketStatsSnapshot( payloadCounts.set(packet.payloadType, (payloadCounts.get(packet.payloadType) ?? 0) + 1); routeCounts.set(packet.routeType, (routeCounts.get(packet.routeType) ?? 0) + 1); - if (packet.pathTokenCount <= 0) { - hopCounts.set('Direct', (hopCounts.get('Direct') ?? 0) + 1); - } else if (packet.pathTokenCount === 1) { - hopCounts.set('1 hop', (hopCounts.get('1 hop') ?? 0) + 1); - } else { - hopCounts.set('2+ hops', (hopCounts.get('2+ hops') ?? 0) + 1); - } + const hopProfileBucket = getHopProfileBucket(packet.pathTokenCount); + hopCounts.set(hopProfileBucket, (hopCounts.get(hopProfileBucket) ?? 0) + 1); const hopByteWidth = inferHopByteWidth(packet); if (packet.pathTokenCount <= 0) { @@ -486,7 +511,7 @@ export function buildRawPacketStatsSnapshot( payloadBreakdown: rankedBreakdown(payloadCounts, packetCount), routeBreakdown: rankedBreakdown(routeCounts, packetCount), topPacketTypes: rankedBreakdown(payloadCounts, packetCount).slice(0, 5), - hopProfile: rankedBreakdown(hopCounts, packetCount), + hopProfile: orderedBreakdown(hopCounts, packetCount), hopByteWidthProfile: rankedBreakdown(hopByteWidthCounts, packetCount), strongestNeighbors, mostActiveNeighbors,