sidekiq-heartbeat_monitor 1.0.1.4 → 1.0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 114ff3014d1ea216913a8baf3b5d5373059fcbea
4
- data.tar.gz: e25b151522f80dc4ece1879a6ed09c1915ef2a2e
3
+ metadata.gz: a7220abb41d5b0d785ef913cad349184e8e61e10
4
+ data.tar.gz: ef32dc8543669e7e375de343d6e69433d674d8d5
5
5
  SHA512:
6
- metadata.gz: 6b794db468711f841ef6a3516bb36b137ac1a563b5ac70fa7e5aab2f4dcaed6cc4e05b72cb57628ae3323716a539bfdcbf79d17948dcd2b1d45d0e0cedd0e1fb
7
- data.tar.gz: 59b6100b413ec2399cb473ea9deed3ee5c636dfac6445e05fce6fa201ece914b69f0ae4212e06113aef90b96222ae17665da244427032c91182f93934500e3d0
6
+ metadata.gz: 45121b850630fa2c1df916c733014d26198506ccfc10315dfd36f9b16551427792a29a78c44d54bf36752c3d4d208bf4e60c332f62f667669a482bcb24f0a4cb
7
+ data.tar.gz: fd515bb0ab900404ad85e536e0b512a603f75cc5a88d941b0755d553dbfa1ce0bc5c47a6c91b8bc40615f0b83ff37896128c7d20f9858d04bba1419f84937425
@@ -1,29 +1,38 @@
1
1
  require "sidekiq"
2
2
  require 'sidekiq/heartbeat_monitor/version'
3
3
  require 'sidekiq/heartbeat_monitor/config'
4
+ require 'sidekiq/heartbeat_monitor/util'
4
5
  require 'sidekiq/heartbeat_monitor/scheduler'
5
6
  require 'sidekiq/heartbeat_monitor/worker'
6
7
 
7
8
  module Sidekiq
8
9
  module HeartbeatMonitor
9
- def self.configure(*args, &block)
10
- Config.configure(*args, &block)
11
- end
10
+ def self.configure(options = {})
11
+ options = options.symbolize_keys
12
+ global_options = options.except(:queues)
13
+
14
+ @global_config = Config.new(**global_options)
12
15
 
13
- def self.send_backed_up_alert(*args, &block)
14
- Config.send_backed_up_alert(*args, &block)
16
+ @queue_config = {}
17
+ options[:queues].to_a.each do |queue_name, queue_options|
18
+ @queue_config[queue_name.to_s] = Config.new(**global_options.deep_merge(queue_options))
19
+ end
15
20
  end
16
21
 
17
- def self.send_slowed_down_alert(*args, &block)
18
- Config.send_slowed_down_alert(*args, &block)
22
+ def self.config(queue = nil)
23
+ return @global_config if queue.blank?
24
+
25
+ queue_name = queue.is_a?(String) || queue.is_a?(Symbol) ? queue.to_s : queue.name.to_s
26
+ @queue_config[queue_name] || @global_config
19
27
  end
20
28
 
21
- def self.send_test!
22
- test_queue = Sidekiq::Queue.new('test')
29
+ def self.send_test!(queue_name = nil)
30
+ test_queue = Sidekiq::Queue.new(queue_name || 'test')
23
31
 
24
32
  send_backed_up_alert("Test backed up alert!", test_queue)
25
33
  send_slowed_down_alert("Test slowed down alert!", test_queue)
26
34
  end
35
+
27
36
  end
28
37
  end
29
38
 
@@ -2,73 +2,96 @@ require 'sidekiq/cron/job'
2
2
  module Sidekiq
3
3
  module HeartbeatMonitor
4
4
  class Config
5
- class << self
6
- def configure(max_queue_size: nil, on_backed_up: nil, on_slowed_down: nil, dont_repeat_for: nil, slack_notifier_url: nil)
7
- @max_queue_size = max_queue_size unless max_queue_size.nil?
5
+ attr_accessor :max_queue_size, :max_heartbeat_delay
8
6
 
9
- @dont_repeat_for = dont_repeat_for unless dont_repeat_for.nil?
7
+ ##
8
+ # @param max_queue_size [Integer] The maximum queue size (default: 5000 or SIDEKIQ_MONITOR_MAX_QUEUE_SIZE environment value)
9
+ # @param on_backed_up [Proc|Array<Proc>] On backed up run this or these procs
10
+ # @param on_slowed_down [Proc|Array<Proc>] On slowed down run this or these procs
11
+ # @param dont_repeat_for [Integer] The don't repeat for (optional, default: 5.minutes)
12
+ # @param slack_notifier_url [String] The slack notifier url (optional)
13
+ # @param max_heartbeat_delay [Integer] The maximum heartbeat delay (default: 5 minute, max: 5 days)
14
+ def initialize(max_queue_size: nil, on_backed_up: nil, on_slowed_down: nil, dont_repeat_for: nil, slack_notifier_url: nil, max_heartbeat_delay: nil)
15
+ @max_queue_size = max_queue_size || ENV.fetch('SIDEKIQ_MONITOR_MAX_QUEUE_SIZE', 5000).to_i
10
16
 
11
- @on_backed_up = (on_backed_up.is_a?(Enumerable) ? on_backed_up : [on_backed_up]) unless on_backed_up.nil?
12
- @on_slowed_down = (on_slowed_down.is_a?(Enumerable) ? on_slowed_down : [on_slowed_down]) unless on_slowed_down.nil?
17
+ @dont_repeat_for = dont_repeat_for unless dont_repeat_for.nil?
13
18
 
14
- if slack_notifier_url.present?
15
- @notifier = Slack::Notifier.new(slack_notifier_url)
19
+ @max_heartbeat_delay = max_heartbeat_delay || 5.minutes
16
20
 
17
- slack_notifier_callback = [-> (msg, queue) { @notifier.ping(msg) }]
21
+ @on_backed_up = (on_backed_up.is_a?(Enumerable) ? on_backed_up : [on_backed_up]) unless on_backed_up.nil?
22
+ @on_slowed_down = (on_slowed_down.is_a?(Enumerable) ? on_slowed_down : [on_slowed_down]) unless on_slowed_down.nil?
18
23
 
19
- @on_backed_up = @on_backed_up.to_a + slack_notifier_callback
20
- @on_slowed_down = @on_slowed_down.to_a + slack_notifier_callback
21
- end
24
+ setup_slack_notifier!(slack_notifier_url) if slack_notifier_url.present?
22
25
 
23
- unless Sidekiq::Cron::Job.find("sidekiq_monitor").present?
24
- Sidekiq::Cron::Job.create(
25
- name: 'sidekiq_monitor',
26
- cron: '*/15 * * * * *',
27
- klass: Sidekiq::HeartbeatMonitor::Scheduler
28
- )
29
- end
30
- end
26
+ install_cron_job!
27
+ end
31
28
 
32
- def send_test!
33
- test_queue = Sidekiq::Queue.new('test')
34
- send_backed_up_alert("Test backed up alert!", test_queue)
35
- send_slowed_down_alert("Test slowed down alert!", test_queue)
36
- end
29
+ def send_test!
30
+ test_queue = Sidekiq::Queue.new('test')
31
+ send_backed_up_alert("Test backed up alert!", test_queue)
32
+ send_slowed_down_alert("Test slowed down alert!", test_queue)
33
+ end
37
34
 
38
- def send_backed_up_alert(message, q)
39
- if @on_backed_up.blank?
40
- puts ("WARNING: No 'on_backed_up' callback defined for sidekiq-heartbeat_monitor but one of the queues are backed up: #{message}")
41
- return
42
- end
35
+ def send_backed_up_alert!(message, q)
36
+ if @on_backed_up.blank?
37
+ puts ("WARNING: No 'on_backed_up' callback defined for sidekiq-heartbeat_monitor but one of the queues are backed up: #{message}")
38
+ return
39
+ end
43
40
 
44
- if @dont_repeat_for.nil?
41
+ if @dont_repeat_for.nil?
42
+ @on_backed_up.to_a.each { |alert| alert.call(message, q) }
43
+ else
44
+ DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_backed_up_alert") do
45
45
  @on_backed_up.to_a.each { |alert| alert.call(message, q) }
46
- else
47
- DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_backed_up_alert") do
48
- @on_backed_up.to_a.each { |alert| alert.call(message, q) }
49
- end
50
46
  end
51
47
  end
48
+ end
52
49
 
53
- def send_slowed_down_alert(message, q)
54
- if @on_slowed_down.blank?
55
- puts ("WARNING: No 'on_slowed_down' callback defined for sidekiq-heartbeat_monitor but one of the queues are backed up: #{message}")
56
- return
57
- end
50
+ def send_slowed_down_alert!(message, q)
51
+ if @on_slowed_down.blank?
52
+ puts ("WARNING: No 'on_slowed_down' callback defined for sidekiq-heartbeat_monitor but one of the queues are backed up: #{message}")
53
+ return
54
+ end
58
55
 
59
- if @dont_repeat_for.nil?
56
+ if @dont_repeat_for.nil?
57
+ @on_slowed_down.to_a.each { |alert| alert.call(message, q) }
58
+ else
59
+ DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_slowed_down_alert") do
60
60
  @on_slowed_down.to_a.each { |alert| alert.call(message, q) }
61
- else
62
- DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_slowed_down_alert") do
63
- @on_slowed_down.to_a.each { |alert| alert.call(message, q) }
64
- end
65
61
  end
66
62
  end
63
+ end
64
+
65
+ private
66
+
67
+ def setup_slack_notifier!(slack_notifier_url)
68
+ @notifier = Slack::Notifier.new(slack_notifier_url)
69
+
70
+ slack_notifier_callback = [-> (msg, queue) { @notifier.ping(msg) }]
71
+
72
+ @on_backed_up = @on_backed_up.to_a + slack_notifier_callback
73
+ @on_slowed_down = @on_slowed_down.to_a + slack_notifier_callback
74
+ end
75
+
76
+ def install_cron_job!
77
+ job = Sidekiq::Cron::Job.find("sidekiq_monitor")
78
+
79
+ target_job = Sidekiq::Cron::Job.new(
80
+ name: 'sidekiq_monitor',
81
+ cron: '* * * * *',
82
+ klass: Sidekiq::HeartbeatMonitor::Scheduler
83
+ )
67
84
 
68
- def max_queue_size
69
- @max_queue_size || ENV.fetch('SIDEKIQ_MONITOR_MAX_QUEUE_SIZE', 5000).to_i
85
+ if job.present?
86
+ if job.cron != target_job.cron && job.klass.to_s != target_job.klass
87
+ job.destroy
88
+ target_job.save
89
+ end
90
+ else
91
+ target_job.save
70
92
  end
71
93
  end
94
+
72
95
  end
73
96
  end
74
97
  end
@@ -1,37 +1,53 @@
1
1
  module Sidekiq
2
2
  module HeartbeatMonitor
3
3
  class Scheduler
4
+ TIME_BETWEEN_HEARTBEATS = 60
5
+
4
6
  include Sidekiq::Worker
7
+ include Sidekiq::HeartbeatMonitor::Util
5
8
  sidekiq_options retry: 0
6
9
 
7
10
  ##
8
11
  # Checks to see if queues are backed up by 1000 or more jobs and also schedules the heartbeat job.
9
12
  def perform
10
- Sidekiq::Queue.all.each do |q|
11
- check_queue_size(q, Sidekiq::HeartbeatMonitor::Config.max_queue_size)
12
-
13
- Sidekiq::HeartbeatMonitor::Worker.client_push(
14
- 'class' => Sidekiq::HeartbeatMonitor::Worker,
15
- 'args' => [q.name, 15],
16
- 'queue' => q.name
17
- )
13
+ Sidekiq.redis do |redis|
14
+ Sidekiq::Queue.all.each do |q|
15
+ queue_config = Sidekiq::HeartbeatMonitor.config(q)
16
+ check_queue_size!(q, queue_config)
17
+
18
+ key = "Sidekiq:HeartbeatMonitor:Worker-#{q.name}.enqueued_at"
19
+
20
+ last_enqueued_at = redis.get(key).to_i
21
+
22
+ if last_enqueued_at > 577997505 # Enqueued after Jan 2, 2020 when this code was written
23
+ time_since_enqueued = Time.now.to_i - last_enqueued_at
24
+ if (time_since_enqueued - TIME_BETWEEN_HEARTBEATS) > queue_config.max_heartbeat_delay
25
+ queue_config.send_slowed_down_alert!("⚠️ _#{q.name}_ queue is taking longer than #{format_time_str(time_since_enqueued)} to reach jobs.", q)
26
+ else
27
+ next
28
+ end
29
+ end
30
+
31
+ redis.set(key, Time.now.to_i, ex: 1.week)
32
+
33
+ Sidekiq::HeartbeatMonitor::Worker.client_push(
34
+ 'class' => Sidekiq::HeartbeatMonitor::Worker,
35
+ 'args' => [q.name],
36
+ 'queue' => q.name
37
+ )
38
+ end
39
+
18
40
  end
19
41
  end
20
42
 
21
- def check_queue_size(q, max_queue_size)
43
+ def check_queue_size!(q, queue_config)
44
+ max_queue_size = queue_config.max_queue_size
45
+
22
46
  if q.size > max_queue_size
23
- send_server_alert("⚠️ Queue #{q.name} has more than #{max_queue_size} jobs waiting to be processed. Current size is #{q.size}", q)
47
+ queue_config.send_backed_up_alert!("⚠️ _#{q.name}_ queue has more than #{max_queue_size} jobs waiting to be processed. Current size is #{q.size}", q)
24
48
  end
25
49
  end
26
50
 
27
- ##
28
- # @param msg [String] Message to post
29
- # @param queue_name [Sidekiq::Queue] Queue we're concerned with
30
- def send_server_alert(msg, q)
31
- Sidekiq::HeartbeatMonitor::Config.send_backed_up_alert(msg, q)
32
-
33
- true
34
- end
35
51
  end
36
52
  end
37
53
  end
@@ -0,0 +1,20 @@
1
+
2
+ module Sidekiq
3
+ module HeartbeatMonitor
4
+ module Util
5
+
6
+ def format_time_str(sec_backed_up)
7
+ hours = (sec_backed_up - (sec_backed_up % 3600)) / 3600
8
+ minutes = (sec_backed_up - (sec_backed_up % 60)) / 60
9
+ seconds = sec_backed_up % 60
10
+
11
+ nice_backed_up_str = "#{seconds} sec"
12
+ nice_backed_up_str = "#{minutes} min #{nice_backed_up_str}" if minutes > 0
13
+ nice_backed_up_str = "#{hours} hr #{nice_backed_up_str}" if hours > 0
14
+
15
+ nice_backed_up_str
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module HeartbeatMonitor
3
- VERSION = '1.0.1.4'
3
+ VERSION = '1.0.2.0'
4
4
  end
5
5
  end
@@ -3,43 +3,32 @@ module Sidekiq
3
3
  class Worker
4
4
 
5
5
  include Sidekiq::Worker
6
+ include Sidekiq::HeartbeatMonitor::Util
6
7
  sidekiq_options retry: 0
7
8
 
8
9
  ##
9
10
  # Runs every x seconds and ensures that the time between jobs is consistent and
10
11
  # @param queue_name [String] Name of the queue that this heartbeat is running on.
11
- def perform(queue_name, secs_between_beats = 15)
12
- current_run_at = Time.now.utc.to_i
13
- last_run_at = redis.get("Sidekiq/HeartbeatMonitor/Worker/#{queue_name}.last_run_at").to_i
12
+ def perform(queue_name)
13
+ Sidekiq.redis do |redis|
14
+ q = Sidekiq::Queue.all.find{ |q| q.name.to_s == queue_name.to_s }
15
+ queue_config = Sidekiq::HeartbeatMonitor.config(q)
14
16
 
15
- if last_run_at > 0
16
- time_since_last_run = current_run_at - last_run_at
17
- if time_since_last_run > (5.minutes + secs_between_beats)
18
- sec_backed_up = time_since_last_run - secs_between_beats
17
+ key = "Sidekiq:HeartbeatMonitor:Worker-#{queue_name}.enqueued_at"
18
+ enqueued_at = redis.get(key).to_i
19
19
 
20
- send_server_alert("⚠️ queue #{queue_name} took > #{sec_backed_up}s to reach job.", queue_name)
21
- end
22
- end
20
+ return if enqueued_at < 1577997505 # Enqueued before Jan 2, 2020 when this code was written
23
21
 
24
- redis.set("Sidekiq/HeartbeatMonitor/Worker/#{queue_name}.last_run_at", current_run_at, ex: 1.hour)
25
- end
26
-
27
- ##
28
- # @param msg [String] Message to post
29
- # @param queue_name [String] Queue we're concerned with
30
- def send_server_alert(msg, queue_name)
31
- queue = Sidekiq::Queue.all.find{ |q| q.name.to_s == queue_name.to_s }
22
+ time_since_enqueued = Time.now.to_i - enqueued_at
32
23
 
33
- Sidekiq::HeartbeatMonitor::Config.send_slowed_down_alert(msg, queue)
24
+ if time_since_enqueued > queue_config.max_heartbeat_delay
25
+ queue_config.send_slowed_down_alert!("⚠️ _#{queue_name}_ queue took #{format_time_str(time_since_enqueued)} to reach job.", q)
26
+ end
34
27
 
35
- true
28
+ redis.del(key)
29
+ end
36
30
  end
37
31
 
38
- private
39
-
40
- def redis
41
- @@redis ||= defined?($redis) ? $redis : Redis.new
42
- end
43
32
 
44
33
  end
45
34
  end
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency "sidekiq-cron", ">= 0.6"
32
32
  spec.add_dependency "dont_repeat_for", ">= 1"
33
33
  spec.add_dependency "slack-notifier", ">= 0.5"
34
+ spec.add_dependency "dotenv", ">= 1.0"
34
35
 
35
36
  spec.add_development_dependency "rails", ">= 4"
36
37
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-heartbeat_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1.4
4
+ version: 1.0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jay El-Kaake
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-31 00:00:00.000000000 Z
11
+ date: 2020-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: dotenv
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rails
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -170,6 +184,7 @@ files:
170
184
  - lib/sidekiq/heartbeat_monitor.rb
171
185
  - lib/sidekiq/heartbeat_monitor/config.rb
172
186
  - lib/sidekiq/heartbeat_monitor/scheduler.rb
187
+ - lib/sidekiq/heartbeat_monitor/util.rb
173
188
  - lib/sidekiq/heartbeat_monitor/version.rb
174
189
  - lib/sidekiq/heartbeat_monitor/worker.rb
175
190
  - sidekiq-heartbeat_monitor.gemspec