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 +4 -4
- data/lib/sidekiq/heartbeat_monitor.rb +18 -9
- data/lib/sidekiq/heartbeat_monitor/config.rb +70 -47
- data/lib/sidekiq/heartbeat_monitor/scheduler.rb +34 -18
- data/lib/sidekiq/heartbeat_monitor/util.rb +20 -0
- data/lib/sidekiq/heartbeat_monitor/version.rb +1 -1
- data/lib/sidekiq/heartbeat_monitor/worker.rb +14 -25
- data/sidekiq-heartbeat_monitor.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7220abb41d5b0d785ef913cad349184e8e61e10
|
|
4
|
+
data.tar.gz: ef32dc8543669e7e375de343d6e69433d674d8d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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(
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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.
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
@notifier = Slack::Notifier.new(slack_notifier_url)
|
|
19
|
+
@max_heartbeat_delay = max_heartbeat_delay || 5.minutes
|
|
16
20
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
end
|
|
22
|
-
end
|
|
20
|
+
return if enqueued_at < 1577997505 # Enqueued before Jan 2, 2020 when this code was written
|
|
23
21
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|