mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-08-29 07:39:23 +00:00
perf(packets): replace observers.find() linear scans with Map lookups (#468)
## Summary Replace all `observers.find()` linear scans in `packets.js` with O(1) `Map.get()` lookups, eliminating ~300K comparisons per render cycle at 30K+ rows. ## Changes - Added `observerMap` (`Map<id, observer>`) built once when observers load - Replaced all 6 `observers.find()` call sites with `observerMap.get()`: - `obsName()` — called per row for observer name display - Region filter check in packet filtering - Observer dropdown label in filter UI - Group header region lookup - Child row region lookup - Flat row region lookup - Map is cleared on reset and rebuilt on each `loadObservers()` call ## Complexity - **Before:** O(k) per row × 30K rows = O(30K × k) where k = observer count (~10) - **After:** O(1) per row × 30K rows = O(30K) - Map construction: O(k) once, negligible ## Testing - All Go tests pass (`cmd/server`, `cmd/ingestor`) - All frontend tests pass (`test-packet-filter.js`: 62 passed, `test-aging.js`: 29 passed, `test-frontend-helpers.js`: 241 passed) Fixes #383 Co-authored-by: you <you@example.com>
This commit is contained in:
+28
-28
@@ -22,9 +22,9 @@
|
||||
<meta name="twitter:title" content="CoreScope">
|
||||
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
|
||||
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/corescope/master/public/og-image.png">
|
||||
<link rel="stylesheet" href="style.css?v=1775073838">
|
||||
<link rel="stylesheet" href="home.css?v=1775073838">
|
||||
<link rel="stylesheet" href="live.css?v=1775073838">
|
||||
<link rel="stylesheet" href="style.css?v=1775096024">
|
||||
<link rel="stylesheet" href="home.css?v=1775096024">
|
||||
<link rel="stylesheet" href="live.css?v=1775096024">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin="anonymous">
|
||||
@@ -85,30 +85,30 @@
|
||||
<main id="app" role="main"></main>
|
||||
|
||||
<script src="vendor/qrcode.js"></script>
|
||||
<script src="roles.js?v=1775073838"></script>
|
||||
<script src="customize.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1775073838"></script>
|
||||
<script src="hop-resolver.js?v=1775073838"></script>
|
||||
<script src="hop-display.js?v=1775073838"></script>
|
||||
<script src="app.js?v=1775073838"></script>
|
||||
<script src="home.js?v=1775073838"></script>
|
||||
<script src="packet-filter.js?v=1775073838"></script>
|
||||
<script src="packets.js?v=1775073838"></script>
|
||||
<script src="geo-filter-overlay.js?v=1775073838"></script>
|
||||
<script src="map.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1775073838" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="roles.js?v=1775096024"></script>
|
||||
<script src="customize.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1775096024"></script>
|
||||
<script src="hop-resolver.js?v=1775096024"></script>
|
||||
<script src="hop-display.js?v=1775096024"></script>
|
||||
<script src="app.js?v=1775096024"></script>
|
||||
<script src="home.js?v=1775096024"></script>
|
||||
<script src="packet-filter.js?v=1775096024"></script>
|
||||
<script src="packets.js?v=1775096024"></script>
|
||||
<script src="geo-filter-overlay.js?v=1775096024"></script>
|
||||
<script src="map.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1775096024" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+9
-6
@@ -8,7 +8,7 @@
|
||||
// Resolve observer_id to friendly name from loaded observers list
|
||||
function obsName(id) {
|
||||
if (!id) return '—';
|
||||
const o = observers.find(ob => ob.id === id);
|
||||
const o = observerMap.get(id);
|
||||
if (!o) return id;
|
||||
return o.iata ? `${o.name} (${o.iata})` : o.name;
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
let packetsPaused = false;
|
||||
let pauseBuffer = [];
|
||||
let observers = [];
|
||||
let observerMap = new Map(); // id → observer for O(1) lookups (#383)
|
||||
let regionMap = {};
|
||||
const TYPE_NAMES = { 0:'Request', 1:'Response', 2:'Direct Msg', 3:'ACK', 4:'Advert', 5:'Channel Msg', 7:'Anon Req', 8:'Path', 9:'Trace', 11:'Control' };
|
||||
function typeName(t) { return TYPE_NAMES[t] ?? `Type ${t}`; }
|
||||
@@ -349,7 +350,7 @@
|
||||
if (filters.hash && p.hash !== filters.hash) return false;
|
||||
if (RegionFilter.getRegionParam()) {
|
||||
const selectedRegions = RegionFilter.getRegionParam().split(',');
|
||||
const obs = observers.find(o => o.id === p.observer_id);
|
||||
const obs = observerMap.get(p.observer_id);
|
||||
if (!obs || !selectedRegions.includes(obs.iata)) return false;
|
||||
}
|
||||
if (filters.node && !(p.decoded_json || '').includes(filters.node)) return false;
|
||||
@@ -439,6 +440,7 @@
|
||||
hopNameCache = {};
|
||||
totalCount = 0;
|
||||
observers = [];
|
||||
observerMap = new Map();
|
||||
directPacketId = null;
|
||||
directPacketHash = null;
|
||||
groupByHash = true;
|
||||
@@ -450,6 +452,7 @@
|
||||
try {
|
||||
const data = await api('/observers', { ttl: CLIENT_TTL.observers });
|
||||
observers = data.observers || [];
|
||||
observerMap = new Map(observers.map(o => [o.id, o]));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -696,7 +699,7 @@
|
||||
obsTrigger.textContent = 'All Observers ▾';
|
||||
} else if (selectedObservers.size === 1) {
|
||||
const id = [...selectedObservers][0];
|
||||
const o = observers.find(x => String(x.id) === id);
|
||||
const o = observerMap.get(id) || observerMap.get(Number(id));
|
||||
obsTrigger.textContent = (o ? (o.name || o.id) : id) + ' ▾';
|
||||
} else {
|
||||
obsTrigger.textContent = selectedObservers.size + ' Observers ▾';
|
||||
@@ -1023,7 +1026,7 @@
|
||||
headerPathJson = match.path_json;
|
||||
}
|
||||
}
|
||||
const groupRegion = headerObserverId ? (observers.find(o => o.id === headerObserverId)?.iata || '') : '';
|
||||
const groupRegion = headerObserverId ? (observerMap.get(headerObserverId)?.iata || '') : '';
|
||||
let groupPath = [];
|
||||
try { groupPath = JSON.parse(headerPathJson || '[]'); } catch {}
|
||||
const groupPathStr = renderPath(groupPath, headerObserverId);
|
||||
@@ -1055,7 +1058,7 @@
|
||||
const typeClass = payloadTypeColor(c.payload_type);
|
||||
const size = c.raw_hex ? Math.floor(c.raw_hex.length / 2) : 0;
|
||||
const childHashBytes = ((parseInt(c.raw_hex?.slice(2, 4), 16) || 0) >> 6) + 1;
|
||||
const childRegion = c.observer_id ? (observers.find(o => o.id === c.observer_id)?.iata || '') : '';
|
||||
const childRegion = c.observer_id ? (observerMap.get(c.observer_id)?.iata || '') : '';
|
||||
let childPath = [];
|
||||
try { childPath = JSON.parse(c.path_json || '[]'); } catch {}
|
||||
const childPathStr = renderPath(childPath, c.observer_id);
|
||||
@@ -1081,7 +1084,7 @@
|
||||
let decoded, pathHops = [];
|
||||
try { decoded = JSON.parse(p.decoded_json || '{}'); } catch {}
|
||||
try { pathHops = JSON.parse(p.path_json || '[]') || []; } catch {}
|
||||
const region = p.observer_id ? (observers.find(o => o.id === p.observer_id)?.iata || '') : '';
|
||||
const region = p.observer_id ? (observerMap.get(p.observer_id)?.iata || '') : '';
|
||||
const typeName = payloadTypeName(p.payload_type);
|
||||
const typeClass = payloadTypeColor(p.payload_type);
|
||||
const size = p.raw_hex ? Math.floor(p.raw_hex.length / 2) : 0;
|
||||
|
||||
Reference in New Issue
Block a user