Files
skywatch/server/scheduler.js
T

130 lines
3.9 KiB
JavaScript

const cron = require('node-cron');
const db = require('./database');
const monitorService = require('./monitorService');
const notificationService = require('./notificationService');
class Scheduler {
constructor() {
this.jobs = new Map();
}
start() {
// Run checks every minute
cron.schedule('* * * * *', async () => {
await this.runChecks();
});
// Archive old checks daily at 2 AM
cron.schedule('0 2 * * *', () => {
this.archiveOldChecks();
});
// Also run archive on startup (with delay to let DB init)
setTimeout(() => {
this.archiveOldChecks();
}, 5000);
console.log('Scheduler started');
}
archiveOldChecks() {
try {
const retentionSetting = db.prepare(`
SELECT value FROM settings WHERE key = 'retention_days'
`).get();
const retentionDays = retentionSetting ? parseInt(retentionSetting.value) : 30;
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
const cutoffString = cutoffDate.toISOString().replace('T', ' ').split('.')[0];
// Move old checks to archived_checks
const oldChecks = db.prepare(`
SELECT * FROM checks WHERE checked_at < ?
`).all(cutoffString);
if (oldChecks.length > 0) {
const insertArchived = db.prepare(`
INSERT INTO archived_checks (resource_id, status, response_time, status_code, error_message, details, checked_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
const deleteOld = db.prepare(`
DELETE FROM checks WHERE id = ?
`);
const transaction = db.transaction(() => {
oldChecks.forEach(check => {
insertArchived.run(
check.resource_id,
check.status,
check.response_time,
check.status_code,
check.error_message,
check.details,
check.checked_at
);
deleteOld.run(check.id);
});
});
transaction();
console.log(`[Archival] Archived ${oldChecks.length} checks older than ${retentionDays} days`);
}
} catch (error) {
console.error('[Archival] Error archiving checks:', error.message);
}
}
async runChecks() {
const resources = db.prepare(`
SELECT * FROM resources WHERE enabled = 1
`).all();
// Stagger checks to avoid thundering herd
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const jitter = (maxMs = 1500) => Math.floor(Math.random() * maxMs);
const baseSpacingMs = 250; // spread starts in 250ms increments
const tasks = resources.map((resource, index) => (async () => {
await sleep(index * baseSpacingMs + jitter());
try {
const result = await monitorService.checkResource(resource);
monitorService.saveCheck(result);
const incident = monitorService.handleIncident(
resource.id,
result.status === 'down'
);
if (incident.type !== 'none') {
const stats = monitorService.getResourceStats(resource.id, 24);
const recentChecks = db.prepare(`
SELECT response_time, status FROM checks
WHERE resource_id = ?
ORDER BY checked_at DESC
LIMIT 12
`).all(resource.id);
stats.recentChecks = recentChecks.reverse();
if (!resource.maintenance_mode) {
await notificationService.sendAlert(resource, incident, stats);
}
}
console.log(`Checked ${resource.name}: ${result.status} (${result.response_time}ms)`);
} catch (error) {
console.error(`Error checking ${resource.name}:`, error.message);
}
})());
await Promise.allSettled(tasks);
// Broadcast updated dashboard to all connected WebSocket clients
if (global.broadcastDashboardUpdate) {
global.broadcastDashboardUpdate();
}
}
}
module.exports = new Scheduler();