aboutsummaryrefslogtreecommitdiff
path: root/internal/monitor/scheduler.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/monitor/scheduler.go')
-rw-r--r--internal/monitor/scheduler.go182
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
+}