diff options
Diffstat (limited to 'internal/monitor/scheduler.go')
| -rw-r--r-- | internal/monitor/scheduler.go | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/internal/monitor/scheduler.go b/internal/monitor/scheduler.go new file mode 100644 index 0000000..7a06131 --- /dev/null +++ b/internal/monitor/scheduler.go @@ -0,0 +1,182 @@ +package monitor + +import ( + "context" + "log/slog" + "sync" + "time" + + "github.com/Fuwn/kaze/internal/config" + "github.com/Fuwn/kaze/internal/storage" +) + +// Scheduler manages and runs all monitors +type Scheduler struct { + monitors []Monitor + storage *storage.Storage + logger *slog.Logger + wg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc +} + +// NewScheduler creates a new monitor scheduler +func NewScheduler(cfg *config.Config, store *storage.Storage, logger *slog.Logger) (*Scheduler, error) { + ctx, cancel := context.WithCancel(context.Background()) + + s := &Scheduler{ + storage: store, + logger: logger, + ctx: ctx, + cancel: cancel, + } + + // Create monitors from configuration + for _, group := range cfg.Groups { + for _, monCfg := range group.Monitors { + mon, err := New(monCfg) + if err != nil { + cancel() + return nil, err + } + s.monitors = append(s.monitors, mon) + logger.Info("registered monitor", + "name", mon.Name(), + "type", mon.Type(), + "target", mon.Target(), + "interval", mon.Interval()) + } + } + + return s, nil +} + +// Start begins running all monitors +func (s *Scheduler) Start() { + s.logger.Info("starting scheduler", "monitors", len(s.monitors)) + + for _, mon := range s.monitors { + s.wg.Add(1) + go s.runMonitor(mon) + } + + // Start cleanup routine + s.wg.Add(1) + go s.runCleanup() +} + +// Stop gracefully stops all monitors +func (s *Scheduler) Stop() { + s.logger.Info("stopping scheduler") + s.cancel() + s.wg.Wait() + s.logger.Info("scheduler stopped") +} + +// runMonitor runs a single monitor in a loop +func (s *Scheduler) runMonitor(mon Monitor) { + defer s.wg.Done() + + // Run immediately on start + s.executeCheck(mon) + + ticker := time.NewTicker(mon.Interval()) + defer ticker.Stop() + + for { + select { + case <-s.ctx.Done(): + s.logger.Info("monitor stopped", "name", mon.Name()) + return + case <-ticker.C: + s.executeCheck(mon) + } + } +} + +// executeCheck performs a single check and saves the result +func (s *Scheduler) executeCheck(mon Monitor) { + // Create a context with timeout for this check + checkCtx, cancel := context.WithTimeout(s.ctx, mon.Interval()) + defer cancel() + + result := mon.Check(checkCtx) + + // Log the result + logAttrs := []any{ + "name", mon.Name(), + "status", result.Status, + "response_time", result.ResponseTime, + } + if result.StatusCode > 0 { + logAttrs = append(logAttrs, "status_code", result.StatusCode) + } + if result.SSLDaysLeft > 0 { + logAttrs = append(logAttrs, "ssl_days_left", result.SSLDaysLeft) + } + if result.Error != nil { + logAttrs = append(logAttrs, "error", result.Error) + } + + if result.Status == StatusUp { + s.logger.Debug("check completed", logAttrs...) + } else { + s.logger.Warn("check completed", logAttrs...) + } + + // Save to storage + if err := s.storage.SaveCheckResult(s.ctx, result.ToCheckResult()); err != nil { + s.logger.Error("failed to save check result", + "name", mon.Name(), + "error", err) + } +} + +// runCleanup periodically cleans up old data +func (s *Scheduler) runCleanup() { + defer s.wg.Done() + + // Run cleanup daily + ticker := time.NewTicker(24 * time.Hour) + defer ticker.Stop() + + for { + select { + case <-s.ctx.Done(): + return + case <-ticker.C: + s.logger.Info("running database cleanup") + if err := s.storage.Cleanup(s.ctx); err != nil { + s.logger.Error("cleanup failed", "error", err) + } else { + s.logger.Info("cleanup completed") + } + } + } +} + +// GetMonitors returns all registered monitors +func (s *Scheduler) GetMonitors() []Monitor { + return s.monitors +} + +// RunCheck manually triggers a check for a specific monitor +func (s *Scheduler) RunCheck(name string) *Result { + for _, mon := range s.monitors { + if mon.Name() == name { + ctx, cancel := context.WithTimeout(context.Background(), mon.Interval()) + defer cancel() + result := mon.Check(ctx) + + // Save the result + if err := s.storage.SaveCheckResult(context.Background(), result.ToCheckResult()); err != nil { + s.logger.Error("failed to save manual check result", + "name", mon.Name(), + "error", err) + } + + return result + } + } + return nil +} |