Class: SeigenWatchdog::Monitor

Inherits:
Object
  • Object
show all
Defined in:
lib/seigen_watchdog/monitor.rb

Overview

Monitor class that checks limiters and invokes the killer when needed

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(check_interval:, killer:, limiters:, logger: nil, on_exception: nil, before_kill: nil) ⇒ Monitor

Interval in seconds between checks, nil to disable background thread The killer to invoke when a limit is exceeded Hash of limiters to check Optional logger for debugging Optional callback when an exception occurs Optional callback invoked before killing, receives exceeded limiter



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/seigen_watchdog/monitor.rb', line 36

def initialize(check_interval:, killer:, limiters:, logger: nil, on_exception: nil, before_kill: nil)
  @check_interval = check_interval
  @killer = killer
  @limiters = limiters.transform_keys(&:to_sym)
  @logger = logger
  @on_exception = on_exception
  @before_kill = before_kill
  @checks = 0
  @last_check_time = nil
  @time_killed = nil
  @thread = nil
  @running = false
  @mutex = Mutex.new

  # Call started on all limiters to initialize their state
  @limiters.each_value(&:started)

  if @check_interval
    log_info('Monitor started with background thread')
    start_background_thread
  else
    log_info('Monitor initialized without background thread; manual checks required')
  end
end

Instance Attribute Details

#checksObject (readonly)

@rbs!

attr_reader checks: Integer


21
22
23
# File 'lib/seigen_watchdog/monitor.rb', line 21

def checks
  @checks
end

Instance Method Details

#check_onceObject

Performs a single check of all limiters Returns true if any limiter exceeded and killer was invoked



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/seigen_watchdog/monitor.rb', line 64

def check_once
  increment_checks
  log_debug("Performing check ##{@checks}")

  name, limiter = @limiters.find { |_k, v| v.exceeded? }
  if limiter
    if killed?
      log_debug("Limit exceeded but killer already invoked #{seconds_since_killed} seconds ago")
    else
      log_info("Limit exceeded: #{name}, invoking killer")
      perform_kill(name, limiter)
    end
    true
  else
    log_debug('All limiters within bounds')
    false
  end
rescue StandardError => e
  handle_exception(e)
  false
end

#killed?Boolean

Returns true if the killer has been invoked

Returns:

  • (Boolean)


97
98
99
# File 'lib/seigen_watchdog/monitor.rb', line 97

def killed?
  !@time_killed.nil?
end

#limiter(name) ⇒ Object

Returns a limiter by name



134
135
136
# File 'lib/seigen_watchdog/monitor.rb', line 134

def limiter(name)
  @limiters.fetch(name.to_sym)
end

#limitersObject

Returns a hash of all limiters Returns a new hash with the same keys and values Modifications to the hash won’t affect internal state, but limiter instances are shared



142
143
144
# File 'lib/seigen_watchdog/monitor.rb', line 142

def limiters
  @limiters.to_h { |k, v| [k, v] }
end

#running?Boolean

Checks if the background thread is running Returns true if the background thread is running

Returns:

  • (Boolean)


127
128
129
# File 'lib/seigen_watchdog/monitor.rb', line 127

def running?
  @running && @thread&.alive?
end

#seconds_after_last_checkObject

Returns the number of seconds since the last check Seconds since last check, or nil if no check has been performed



89
90
91
92
93
# File 'lib/seigen_watchdog/monitor.rb', line 89

def seconds_after_last_check
  return nil if @last_check_time.nil?

  Process.clock_gettime(Process::CLOCK_MONOTONIC) - @last_check_time
end

#seconds_since_killedObject

Returns the number of seconds since the killer was invoked Seconds since killer was invoked, or nil if killer has not been invoked



104
105
106
107
108
# File 'lib/seigen_watchdog/monitor.rb', line 104

def seconds_since_killed
  return nil if @time_killed.nil?

  Process.clock_gettime(Process::CLOCK_MONOTONIC) - @time_killed
end

#stopObject

Stops the background thread if running



112
113
114
115
116
117
118
119
120
121
122
# File 'lib/seigen_watchdog/monitor.rb', line 112

def stop
  if @thread
    @mutex.synchronize { @running = false }
    @thread.join(5) # Wait up to 5 seconds for thread to finish
    @thread = nil
    log_debug('Monitor stopped')
  end

  # Call stopped on all limiters to clean up their state
  @limiters.each_value(&:stopped)
end