All files / src/lib/monitoring alertScheduler.ts

100% Statements 196/196
94.73% Branches 18/19
100% Functions 7/7
100% Lines 196/196

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 1971x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14x 14x 14x 14x 14x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 14x 1x 1x 1x 1x 1x 1x 1x 14x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 1x 1x 1x 1x 1x 7x 14x 1x 1x 1x 1x 1x 1x 1x 15x 15x 15x 15x 15x 1x 1x 1x 14x 14x 14x 14x 14x 14x 9x 14x 14x 14x 14x 14x 15x 5x 5x 15x 1x 1x 1x 1x 45x 45x 31x 31x 14x 14x 14x 14x 14x 14x 14x 14x 14x 14x 14x 14x 14x 14x 1x 1x 1x 1x 8x 8x 8x 1x 1x 1x 1x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 22x 22x 22x 22x 22x 22x 22x 22x 22x  
/**
 * Alert Scheduler
 *
 * Schedules periodic evaluation of performance alert configurations.
 * Can be started/stopped dynamically and runs at configurable intervals.
 */
 
import { evaluateAlerts, getAlertStats } from './alertEngine';
import { logger } from '@/lib/logging';
 
/**
 * Scheduler state
 */
interface SchedulerState {
  isRunning: boolean;
  intervalId: NodeJS.Timeout | null;
  lastRunAt: Date | null;
  lastRunDurationMs: number | null;
  consecutiveErrors: number;
  totalRuns: number;
  totalAlertsFired: number;
}
 
const state: SchedulerState = {
  isRunning: false,
  intervalId: null,
  lastRunAt: null,
  lastRunDurationMs: null,
  consecutiveErrors: 0,
  totalRuns: 0,
  totalAlertsFired: 0,
};
 
/**
 * Default evaluation interval (1 minute)
 */
const DEFAULT_INTERVAL_MS = 60000;
 
/**
 * Maximum consecutive errors before auto-stop
 */
const MAX_CONSECUTIVE_ERRORS = 5;
 
/**
 * Run a single evaluation cycle
 */
async function runEvaluation(): Promise<void> {
  const startTime = Date.now();
 
  try {
    const results = await evaluateAlerts();
 
    // Count alerts fired
    const alertsFired = results.filter(r => r.shouldAlert).length;
 
    // Update state
    state.lastRunAt = new Date();
    state.lastRunDurationMs = Date.now() - startTime;
    state.consecutiveErrors = 0;
    state.totalRuns++;
    state.totalAlertsFired += alertsFired;
 
    if (alertsFired > 0) {
      logger.info(`Alert evaluation complete: ${alertsFired} alerts triggered`, {
        category: 'PERFORMANCE',
        totalConfigs: results.length,
        alertsFired,
        durationMs: state.lastRunDurationMs,
      });
    }
  } catch (error) {
    state.consecutiveErrors++;
    state.lastRunAt = new Date();
    state.lastRunDurationMs = Date.now() - startTime;
 
    logger.error('Alert evaluation failed', error instanceof Error ? error : new Error(String(error)), {
      category: 'PERFORMANCE',
      consecutiveErrors: state.consecutiveErrors,
    });
 
    // Auto-stop if too many consecutive errors
    if (state.consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
      logger.error(`Alert scheduler auto-stopped after ${MAX_CONSECUTIVE_ERRORS} consecutive errors`, new Error('Max consecutive errors reached'), {
        category: 'PERFORMANCE',
      });
      stopAlertScheduler();
    }
  }
}
 
/**
 * Start the alert scheduler
 *
 * @param intervalMs - Interval between evaluations in milliseconds (default: 60000)
 * @param runImmediately - Whether to run an evaluation immediately (default: true)
 */
export function startAlertScheduler(
  intervalMs: number = DEFAULT_INTERVAL_MS,
  runImmediately: boolean = true
): void {
  if (state.isRunning) {
    logger.warn('Alert scheduler is already running', { category: 'PERFORMANCE' });
    return;
  }
 
  state.isRunning = true;
  state.consecutiveErrors = 0;
 
  // Set up the interval
  state.intervalId = setInterval(() => {
    runEvaluation().catch((err) => logger.error('Evaluation failed', err instanceof Error ? err : new Error(String(err)), { category: 'PERFORMANCE' }));
  }, intervalMs);
 
  logger.info(`Alert scheduler started with ${intervalMs}ms interval`, { category: 'PERFORMANCE' });
 
  // Run immediately if requested
  if (runImmediately) {
    runEvaluation().catch((err) => logger.error('Initial evaluation failed', err instanceof Error ? err : new Error(String(err)), { category: 'PERFORMANCE' }));
  }
}
 
/**
 * Stop the alert scheduler
 */
export function stopAlertScheduler(): void {
  if (!state.isRunning) {
    return;
  }
 
  if (state.intervalId) {
    clearInterval(state.intervalId);
    state.intervalId = null;
  }
 
  state.isRunning = false;
 
  logger.info('Alert scheduler stopped', {
    category: 'PERFORMANCE',
    totalRuns: state.totalRuns,
    totalAlertsFired: state.totalAlertsFired,
  });
}
 
/**
 * Check if the scheduler is running
 */
export function isSchedulerRunning(): boolean {
  return state.isRunning;
}
 
/**
 * Get scheduler status and statistics
 */
export async function getSchedulerStatus(): Promise<{
  isRunning: boolean;
  lastRunAt: Date | null;
  lastRunDurationMs: number | null;
  consecutiveErrors: number;
  totalRuns: number;
  totalAlertsFired: number;
  alertStats: Awaited<ReturnType<typeof getAlertStats>>;
}> {
  const alertStats = await getAlertStats();
 
  return {
    isRunning: state.isRunning,
    lastRunAt: state.lastRunAt,
    lastRunDurationMs: state.lastRunDurationMs,
    consecutiveErrors: state.consecutiveErrors,
    totalRuns: state.totalRuns,
    totalAlertsFired: state.totalAlertsFired,
    alertStats,
  };
}
 
/**
 * Manually trigger an evaluation cycle
 * Useful for testing or immediate checks
 */
export async function triggerManualEvaluation(): Promise<Awaited<ReturnType<typeof evaluateAlerts>>> {
  logger.info('Manual alert evaluation triggered', { category: 'PERFORMANCE' });
  return evaluateAlerts();
}
 
/**
 * Reset scheduler statistics
 */
export function resetSchedulerStats(): void {
  state.totalRuns = 0;
  state.totalAlertsFired = 0;
  state.consecutiveErrors = 0;
  state.lastRunAt = null;
  state.lastRunDurationMs = null;
 
  logger.info('Alert scheduler statistics reset', { category: 'PERFORMANCE' });
}