From ee29cc627f6ec9f09a5a862f98154d45335706af Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Fri, 3 Apr 2026 21:09:17 -0700 Subject: [PATCH] perf: parallelize expanded group fetches, use hashIndex Map lookup (#552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #388 — expanded groups were fetched sequentially with O(n) `packets.find()` lookups. ## Changes 1. **Parallel fetch**: Replaced sequential `for...of + await` loop in `loadPackets()` with `Promise.all()` so all expanded group children are fetched concurrently. 2. **O(1) Map lookup**: Replaced 3 instances of `packets.find(p => p.hash === hash)` with `hashIndex.get(hash)`: - `loadPackets()` expanded group restore (~line 553) - `select-observation` click handler (~line 1015) - `pktToggleGroup()` (~line 2012) ## Perf justification - **Before**: N expanded groups → N sequential API calls + N × O(packets.length) array scans - **After**: N parallel API calls + N × O(1) Map lookups - Typical N is 1-3 (minor severity as noted in issue), but the fix is trivial and correct ## Tests All existing tests pass: `test-packet-filter.js` (62), `test-aging.js` (29), `test-frontend-helpers.js` (433). Co-authored-by: you --- public/packets.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/public/packets.js b/public/packets.js index 5dcf3273..0f5e21aa 100644 --- a/public/packets.js +++ b/public/packets.js @@ -547,19 +547,22 @@ // Ambiguous hops are already resolved by HopResolver client-side // No need for per-observer server API calls - // Restore expanded group children + // Restore expanded group children (parallel fetch, Map lookup) if (groupByHash && expandedHashes.size > 0) { - for (const hash of expandedHashes) { - const group = packets.find(p => p.hash === hash); - if (group) { - try { - const childData = await api(`/packets?hash=${hash}&limit=20`); - group._children = childData.packets || []; - sortGroupChildren(group); - } catch {} - } else { - // Group no longer in results — remove from expanded + const expandedArr = [...expandedHashes]; + const results = await Promise.all(expandedArr.map(hash => { + const group = hashIndex.get(hash); + if (!group) return { hash, group: null, data: null }; + return api(`/packets?hash=${hash}&limit=20`) + .then(data => ({ hash, group, data })) + .catch(() => ({ hash, group, data: null })); + })); + for (const { hash, group, data } of results) { + if (!group) { expandedHashes.delete(hash); + } else if (data) { + group._children = data.packets || []; + sortGroupChildren(group); } } } @@ -1012,7 +1015,7 @@ } else if (action === 'select-observation') { const parentHash = row.dataset.parentHash; - const group = packets.find(p => p.hash === parentHash); + const group = hashIndex.get(parentHash); const child = group?._children?.find(c => String(c.id) === String(value)); if (child) { const parentData = group._fetchedData; @@ -2009,7 +2012,7 @@ const data = await api(`/packets/${hash}`); const pkt = data.packet; if (!pkt) return; - const group = packets.find(p => p.hash === hash); + const group = hashIndex.get(hash); if (group && data.observations) { group._children = data.observations.map(o => clearParsedCache({...pkt, ...o, _isObservation: true})); group._fetchedData = data;