Skip to content

Commit 1fb941a

Browse files
authored
Merge pull request #29 from int2001/performance
Performance
2 parents 4d35b3d + 185132f commit 1fb941a

File tree

1 file changed

+140
-27
lines changed

1 file changed

+140
-27
lines changed

index.js

Lines changed: 140 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ app.use(cors({ origin: '*' }));
4343
// DXCluster connection and spot cache
4444
let spots=[];
4545

46+
// Indexes for faster lookups
47+
const bandIndex = new Map(); // Map<band, Set<spot>>
48+
const frequencyIndex = new Map(); // Map<frequency, spot>
49+
const sourceIndex = new Map(); // Map<source, Set<spot>>
50+
4651

4752
// -----------------------------------
4853
// Utility Functions
@@ -267,23 +272,103 @@ async function handlespot(spot, spot_source = "cluster"){
267272

268273
//lookup band
269274
dxSpot.band=qrg2band(dxSpot.frequency*1000);
270-
275+
271276
//push spot to cache
272277
spots.push(dxSpot);
273-
278+
279+
// Update indexes
280+
updateIndexes(dxSpot);
281+
274282
//empty out spots if maximum retainment is reached
275283
if (spots.length>config.maxcache) {
276-
spots.shift();
284+
const removed = spots.shift();
285+
removeFromIndexes(removed);
277286
}
278287

279288
//reduce spots
289+
const oldLength = spots.length;
280290
spots=reduce_spots(spots);
291+
292+
// If spots were reduced, rebuild indexes
293+
if (spots.length !== oldLength) {
294+
rebuildIndexes();
295+
}
281296

282297
} catch(e) {
283298
console.error("Error processing spot:", e);
284299
}
285300
}
286301

302+
// -----------------------------------
303+
// Index Management Functions
304+
// -----------------------------------
305+
306+
/**
307+
* Updates all indexes when a new spot is added
308+
*/
309+
function updateIndexes(spot) {
310+
// Update band index
311+
if (spot.band) {
312+
if (!bandIndex.has(spot.band)) {
313+
bandIndex.set(spot.band, new Set());
314+
}
315+
bandIndex.get(spot.band).add(spot);
316+
}
317+
318+
// Update frequency index (keep only latest spot per frequency)
319+
const existing = frequencyIndex.get(spot.frequency);
320+
if (!existing || Date.parse(spot.when) > Date.parse(existing.when)) {
321+
frequencyIndex.set(spot.frequency, spot);
322+
}
323+
324+
// Update source index
325+
if (spot.source) {
326+
if (!sourceIndex.has(spot.source)) {
327+
sourceIndex.set(spot.source, new Set());
328+
}
329+
sourceIndex.get(spot.source).add(spot);
330+
}
331+
}
332+
333+
/**
334+
* Removes a spot from all indexes
335+
*/
336+
function removeFromIndexes(spot) {
337+
if (!spot) return;
338+
339+
// Remove from band index
340+
if (spot.band && bandIndex.has(spot.band)) {
341+
bandIndex.get(spot.band).delete(spot);
342+
if (bandIndex.get(spot.band).size === 0) {
343+
bandIndex.delete(spot.band);
344+
}
345+
}
346+
347+
// Remove from frequency index if this is the current spot
348+
if (frequencyIndex.get(spot.frequency) === spot) {
349+
frequencyIndex.delete(spot.frequency);
350+
}
351+
352+
// Remove from source index
353+
if (spot.source && sourceIndex.has(spot.source)) {
354+
sourceIndex.get(spot.source).delete(spot);
355+
if (sourceIndex.get(spot.source).size === 0) {
356+
sourceIndex.delete(spot.source);
357+
}
358+
}
359+
}
360+
361+
/**
362+
* Rebuilds all indexes from scratch
363+
*/
364+
function rebuildIndexes() {
365+
bandIndex.clear();
366+
frequencyIndex.clear();
367+
sourceIndex.clear();
368+
369+
spots.forEach(spot => updateIndexes(spot));
370+
}
371+
287372
function get_singlespot (qrg) {
288373
let ret={};
289374
let youngest=Date.parse('1970-01-01T00:00:00.000Z');
@@ -304,7 +389,22 @@ let consecutiveErrorCount = 0;
304389
const dxccServer = config.dxcc_lookup_wavelog_url; // The WaveLog server
305390
let abortController = null; // For aborting ongoing requests
306391

392+
// DXCC cache: Map<callsign, {data, timestamp}>
393+
const dxccCache = new Map();
394+
const DXCC_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
395+
307396
async function dxcc_lookup(call) {
397+
// Check cache first
398+
const cached = dxccCache.get(call);
399+
if (cached) {
400+
const age = Date.now() - cached.timestamp;
401+
if (age < DXCC_CACHE_TTL) {
402+
return cached.data;
403+
} else {
404+
// Expired, remove from cache
405+
dxccCache.delete(call);
406+
}
407+
}
308408
let timeoutId = null; // Initialize timeoutId to null
309409

310410
try {
@@ -347,6 +447,12 @@ async function dxcc_lookup(call) {
347447
cqz: result.dxcc_cqz || null,
348448
};
349449

450+
// Cache the result
451+
dxccCache.set(call, {
452+
data: returner,
453+
timestamp: Date.now()
454+
});
455+
350456
consecutiveErrorCount = 0; // Reset error count after a successful lookup
351457
abortController = null; // Clear the abort controller after success
352458
return returner;
@@ -373,15 +479,9 @@ async function dxcc_lookup(call) {
373479
* @returns {object} - The latest spot for the given frequency.
374480
*/
375481
function get_singlespot(qrg) {
376-
let ret = {};
377-
let youngest = Date.parse('1970-01-01T00:00:00.000Z');
378-
spots.forEach((single) => {
379-
if ((qrg * 1 === single.frequency) && (Date.parse(single.when) > youngest)) {
380-
ret = single;
381-
youngest = Date.parse(single.when);
382-
}
383-
});
384-
return ret;
482+
// Use frequency index for O(1) lookup
483+
const spot = frequencyIndex.get(qrg * 1);
484+
return spot || {};
385485
}
386486

387487
/**
@@ -390,7 +490,9 @@ function get_singlespot(qrg) {
390490
* @returns {array} - An array of spots for the given band.
391491
*/
392492
function get_bandspots(band) {
393-
return spots.filter((single) => single.band === band);
493+
// Use band index for O(1) lookup
494+
const spotSet = bandIndex.get(band);
495+
return spotSet ? Array.from(spotSet) : [];
394496
}
395497

396498
/**
@@ -399,7 +501,9 @@ function get_bandspots(band) {
399501
* @returns {array} - An array of spots for the given source.
400502
*/
401503
function get_sourcespots(source) {
402-
return spots.filter((single) => single.source === source);
504+
// Use source index for O(1) lookup
505+
const spotSet = sourceIndex.get(source);
506+
return spotSet ? Array.from(spotSet) : [];
403507
}
404508

405509
/**
@@ -438,22 +542,31 @@ function get_oldest(spotobj) {
438542
* @returns {array} - Deduplicated array of spots.
439543
*/
440544
function reduce_spots(spotobject) {
441-
let unique = [];
545+
// Use a Map to track the latest spot for each unique combination
546+
// Key: spotted_continent_frequency, Value: spot object
547+
const latestSpots = new Map();
548+
442549
spotobject.forEach((single) => {
443-
if (
444-
single.dxcc_spotter && // Ensure dxcc_spotter exists
445-
single.dxcc_spotted && // Ensure dxcc_spotted exists
446-
!spotobject.find((item) =>
447-
item.spotted === single.spotted &&
448-
item.dxcc_spotter && item.dxcc_spotter.cont === single.dxcc_spotter.cont &&
449-
item.frequency === single.frequency &&
450-
Date.parse(item.when) > Date.parse(single.when)
451-
)
452-
) {
453-
unique.push(single);
550+
// Skip spots without required DXCC data
551+
if (!single.dxcc_spotter || !single.dxcc_spotted) {
552+
return;
553+
}
554+
555+
// Create unique key for deduplication
556+
const key = `${single.spotted}_${single.dxcc_spotter.cont}_${single.frequency}`;
557+
const timestamp = Date.parse(single.when);
558+
559+
// Check if we already have a spot with this key
560+
const existing = latestSpots.get(key);
561+
562+
// Keep this spot if it's newer or if no existing spot
563+
if (!existing || timestamp > Date.parse(existing.when)) {
564+
latestSpots.set(key, single);
454565
}
455566
});
456-
return unique;
567+
568+
// Convert Map values back to array
569+
return Array.from(latestSpots.values());
457570
}
458571

459572
/**

0 commit comments

Comments
 (0)