sidekiq-heartbeat_monitor 1.0.1.4 → 1.0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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