fixing error logs

pull/27/head
accius 3 days ago
parent 9b23800670
commit 14fa020b1b

File diff suppressed because it is too large Load Diff

@ -27,6 +27,78 @@ const ITURHFPROP_DATA = process.env.ITURHFPROP_DATA || '/opt/iturhfprop';
// Temp directory for input/output files // Temp directory for input/output files
const TEMP_DIR = '/tmp/iturhfprop'; const TEMP_DIR = '/tmp/iturhfprop';
// === CACHING AND CIRCUIT BREAKER ===
const predictionCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const CACHE_MAX_SIZE = 100;
// Circuit breaker state
let circuitBreakerOpen = false;
let consecutiveFailures = 0;
let lastFailureTime = 0;
const FAILURE_THRESHOLD = 5;
const CIRCUIT_RESET_TIME = 60 * 1000; // 1 minute
// Rate limiting - track last log time to avoid log spam
let lastErrorLogTime = 0;
const ERROR_LOG_INTERVAL = 10000; // Only log errors every 10 seconds
function getCacheKey(params) {
// Round coordinates to 1 decimal place for better cache hits
const key = `${params.txLat.toFixed(1)},${params.txLon.toFixed(1)}-${params.rxLat.toFixed(1)},${params.rxLon.toFixed(1)}-${params.month}-${params.hour}-${params.ssn}`;
return key;
}
function getFromCache(key) {
const cached = predictionCache.get(key);
if (cached && (Date.now() - cached.timestamp < CACHE_TTL)) {
return cached.data;
}
predictionCache.delete(key);
return null;
}
function setCache(key, data) {
// Enforce max cache size
if (predictionCache.size >= CACHE_MAX_SIZE) {
const oldestKey = predictionCache.keys().next().value;
predictionCache.delete(oldestKey);
}
predictionCache.set(key, { data, timestamp: Date.now() });
}
function checkCircuitBreaker() {
if (!circuitBreakerOpen) return false;
// Check if enough time has passed to try again
if (Date.now() - lastFailureTime > CIRCUIT_RESET_TIME) {
circuitBreakerOpen = false;
consecutiveFailures = 0;
console.log('[Circuit Breaker] Reset - allowing requests');
return false;
}
return true;
}
function recordFailure() {
consecutiveFailures++;
lastFailureTime = Date.now();
if (consecutiveFailures >= FAILURE_THRESHOLD) {
circuitBreakerOpen = true;
// Only log this once
if (Date.now() - lastErrorLogTime > ERROR_LOG_INTERVAL) {
console.log(`[Circuit Breaker] OPEN - ${consecutiveFailures} consecutive failures, pausing for ${CIRCUIT_RESET_TIME/1000}s`);
lastErrorLogTime = Date.now();
}
}
}
function recordSuccess() {
consecutiveFailures = 0;
circuitBreakerOpen = false;
}
// Middleware // Middleware
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
@ -168,7 +240,7 @@ function parseOutputFile(outputPath) {
snr: snr, snr: snr,
reliability: bcr reliability: bcr
}); });
console.log(`[Parse] Freq ${freq} MHz: SNR=${snr} dB, BCR=${bcr}%`); // Removed per-frequency logging to reduce spam
} }
} }
} }
@ -180,10 +252,17 @@ function parseOutputFile(outputPath) {
results.muf = parseFloat(mufMatch[1]); results.muf = parseFloat(mufMatch[1]);
} }
console.log(`[Parse] Found ${results.frequencies.length} frequency results`); // Only log success occasionally
if (results.frequencies.length > 0 && Date.now() - lastErrorLogTime > ERROR_LOG_INTERVAL) {
console.log(`[Parse] Found ${results.frequencies.length} frequency results, MUF=${results.muf || 'N/A'}`);
}
return results; return results;
} catch (err) { } catch (err) {
console.error('[Parse Error]', err.message); // Only log errors occasionally
if (Date.now() - lastErrorLogTime > ERROR_LOG_INTERVAL) {
console.error('[Parse Error]', err.message);
lastErrorLogTime = Date.now();
}
return { error: err.message, frequencies: [] }; return { error: err.message, frequencies: [] };
} }
} }
@ -192,6 +271,18 @@ function parseOutputFile(outputPath) {
* Run ITURHFProp prediction * Run ITURHFProp prediction
*/ */
async function runPrediction(params) { async function runPrediction(params) {
// Check circuit breaker first
if (checkCircuitBreaker()) {
return { error: 'Circuit breaker open - service temporarily unavailable', frequencies: [] };
}
// Check cache
const cacheKey = getCacheKey(params);
const cached = getFromCache(cacheKey);
if (cached) {
return cached;
}
const id = crypto.randomBytes(8).toString('hex'); const id = crypto.randomBytes(8).toString('hex');
const inputPath = path.join(TEMP_DIR, `input_${id}.txt`); const inputPath = path.join(TEMP_DIR, `input_${id}.txt`);
const outputPath = path.join(TEMP_DIR, `output_${id}.txt`); const outputPath = path.join(TEMP_DIR, `output_${id}.txt`);
@ -209,14 +300,15 @@ async function runPrediction(params) {
const inputContent = generateInputFile(params); const inputContent = generateInputFile(params);
fs.writeFileSync(inputPath, inputContent); fs.writeFileSync(inputPath, inputContent);
console.log(`[ITURHFProp] Running prediction ${id}`); // Only log occasionally to avoid spam
console.log(`[ITURHFProp] TX: ${params.txLat}, ${params.txLon} -> RX: ${params.rxLat}, ${params.rxLon}`); const shouldLog = Date.now() - lastErrorLogTime > ERROR_LOG_INTERVAL;
console.log(`[ITURHFProp] Input file:\n${inputContent}`); if (shouldLog) {
console.log(`[ITURHFProp] Running prediction ${id}: TX(${params.txLat.toFixed(1)},${params.txLon.toFixed(1)}) -> RX(${params.rxLat.toFixed(1)},${params.rxLon.toFixed(1)})`);
}
// Run ITURHFProp // Run ITURHFProp
const startTime = Date.now(); const startTime = Date.now();
const cmd = `${ITURHFPROP_PATH} ${inputPath} ${outputPath}`; const cmd = `${ITURHFPROP_PATH} ${inputPath} ${outputPath}`;
console.log(`[ITURHFProp] Command: ${cmd}`);
try { try {
execStdout = execSync(cmd, { execStdout = execSync(cmd, {
@ -224,40 +316,39 @@ async function runPrediction(params) {
encoding: 'utf8', encoding: 'utf8',
env: { ...process.env, LD_LIBRARY_PATH: '/opt/iturhfprop:' + (process.env.LD_LIBRARY_PATH || '') } env: { ...process.env, LD_LIBRARY_PATH: '/opt/iturhfprop:' + (process.env.LD_LIBRARY_PATH || '') }
}); });
console.log(`[ITURHFProp] stdout: ${execStdout}`);
} catch (execError) { } catch (execError) {
execStderr = execError.stderr?.toString() || ''; execStderr = execError.stderr?.toString() || '';
execStdout = execError.stdout?.toString() || ''; execStdout = execError.stdout?.toString() || '';
console.error('[ITURHFProp] Execution error!');
console.error('[ITURHFProp] Exit code:', execError.status);
console.error('[ITURHFProp] stderr:', execStderr);
console.error('[ITURHFProp] stdout:', execStdout);
// Don't throw - try to read output anyway // Only log errors periodically to avoid spam
if (Date.now() - lastErrorLogTime > ERROR_LOG_INTERVAL) {
console.error(`[ITURHFProp] Error (exit ${execError.status}): ${execStdout.substring(0, 100)}`);
lastErrorLogTime = Date.now();
}
recordFailure();
// Return empty result but cache it to avoid repeated failures
const errorResult = { error: `Exit code ${execError.status}`, frequencies: [], cached: false };
setCache(cacheKey, errorResult);
return errorResult;
} }
const elapsed = Date.now() - startTime; const elapsed = Date.now() - startTime;
console.log(`[ITURHFProp] Completed in ${elapsed}ms`);
// Check output file
if (fs.existsSync(outputPath)) {
const rawOutput = fs.readFileSync(outputPath, 'utf8');
const stats = fs.statSync(outputPath);
console.log(`[ITURHFProp] Output file exists, size: ${stats.size} bytes`);
console.log(`[ITURHFProp] Raw output (first 2000 chars):\n${rawOutput.substring(0, 2000)}`);
} else {
console.log(`[ITURHFProp] Output file NOT FOUND at ${outputPath}`);
// Check if there's a report file in /tmp
const tmpFiles = fs.readdirSync('/tmp').filter(f => f.startsWith('RPT') || f.startsWith('PDD'));
console.log(`[ITURHFProp] Report files in /tmp: ${tmpFiles.join(', ') || 'none'}`);
}
// Parse output // Parse output
const results = parseOutputFile(outputPath); const results = parseOutputFile(outputPath);
if (results.frequencies.length === 0) {
recordFailure();
const errorResult = { error: 'No frequency results', frequencies: [], elapsed };
setCache(cacheKey, errorResult);
return errorResult;
}
// Success!
recordSuccess();
results.elapsed = elapsed; results.elapsed = elapsed;
results.execStdout = execStdout;
results.execStderr = execStderr;
results.inputContent = inputContent;
results.params = { results.params = {
txLat: params.txLat, txLat: params.txLat,
txLon: params.txLon, txLon: params.txLon,
@ -268,6 +359,9 @@ async function runPrediction(params) {
ssn: params.ssn ssn: params.ssn
}; };
// Cache successful result
setCache(cacheKey, results);
return results; return results;
} finally { } finally {
@ -301,7 +395,7 @@ app.get('/api/health', (req, res) => {
const ionosDataExists = fs.existsSync(ITURHFPROP_DATA + '/Data/ionos12.bin'); const ionosDataExists = fs.existsSync(ITURHFPROP_DATA + '/Data/ionos12.bin');
res.json({ res.json({
status: binaryExists && dataSubExists && libp533Exists && ionosDataExists ? 'healthy' : 'degraded', status: binaryExists && dataSubExists && libp533Exists && ionosDataExists && !circuitBreakerOpen ? 'healthy' : 'degraded',
service: 'iturhfprop', service: 'iturhfprop',
version: '1.0.0', version: '1.0.0',
engine: 'ITURHFProp (ITU-R P.533-14)', engine: 'ITURHFProp (ITU-R P.533-14)',
@ -310,6 +404,11 @@ app.get('/api/health', (req, res) => {
libp372: libp372Exists ? 'found' : 'missing', libp372: libp372Exists ? 'found' : 'missing',
dataDir: dataSubExists ? 'found' : 'missing', dataDir: dataSubExists ? 'found' : 'missing',
ionosData: ionosDataExists ? 'found' : 'missing', ionosData: ionosDataExists ? 'found' : 'missing',
circuitBreaker: {
open: circuitBreakerOpen,
consecutiveFailures,
cacheSize: predictionCache.size
},
paths: { paths: {
binary: ITURHFPROP_PATH, binary: ITURHFPROP_PATH,
data: ITURHFPROP_DATA data: ITURHFPROP_DATA

@ -1891,12 +1891,19 @@
</div> </div>
{/* 24 hour cells */} {/* 24 hour cells */}
{Array.from({ length: 24 }, (_, hour) => { {Array.from({ length: 24 }, (_, hour) => {
// For current hour, use currentBands (same hybrid data as bars view) // For current hour, try to use currentBands (same hybrid data as bars view)
// For other hours, use hourlyPredictions // Fall back to hourlyPredictions if currentBands doesn't have this band
let rel; let rel = 0;
if (hour === currentHour) { if (hour === currentHour && currentBands?.length > 0) {
const currentBandData = currentBands.find(b => b.band === band); const currentBandData = currentBands.find(b => b.band === band);
rel = currentBandData?.reliability || 0; if (currentBandData) {
rel = currentBandData.reliability || 0;
} else {
// Band not in currentBands, use hourlyPredictions
const bandData = hourlyPredictions?.[band];
const hourData = bandData?.find(h => h.hour === hour);
rel = hourData?.reliability || 0;
}
} else { } else {
const bandData = hourlyPredictions?.[band]; const bandData = hourlyPredictions?.[band];
const hourData = bandData?.find(h => h.hour === hour); const hourData = bandData?.find(h => h.hour === hour);
@ -1976,7 +1983,7 @@
<span><span style={{ color: 'var(--text-muted)' }}>K </span><span style={{ color: solarData.kIndex >= 4 ? '#ff4444' : '#00ff88' }}>{solarData.kIndex}</span></span> <span><span style={{ color: 'var(--text-muted)' }}>K </span><span style={{ color: solarData.kIndex >= 4 ? '#ff4444' : '#00ff88' }}>{solarData.kIndex}</span></span>
</div> </div>
{currentBands.slice(0, 11).map((band, idx) => ( {(currentBands || []).slice(0, 11).map((band, idx) => (
<div key={band.band} style={{ <div key={band.band} style={{
display: 'grid', display: 'grid',
gridTemplateColumns: '32px 1fr 40px', gridTemplateColumns: '32px 1fr 40px',

Loading…
Cancel
Save

Powered by TurnKey Linux.