sidekiq_utils 1.0.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 +7 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +96 -0
- data/LICENSE +21 -0
- data/README.md +185 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/sidekiq_utils/additional_serialization.rb +56 -0
- data/lib/sidekiq_utils/deprioritize.rb +19 -0
- data/lib/sidekiq_utils/enqueued_jobs_helper.rb +45 -0
- data/lib/sidekiq_utils/find_optional.rb +24 -0
- data/lib/sidekiq_utils/job_counter.rb +161 -0
- data/lib/sidekiq_utils/latency_alert.rb +97 -0
- data/lib/sidekiq_utils/middleware/client/additional_serialization.rb +20 -0
- data/lib/sidekiq_utils/middleware/client/deprioritize.rb +14 -0
- data/lib/sidekiq_utils/middleware/client/job_counter.rb +17 -0
- data/lib/sidekiq_utils/middleware/server/additional_serialization.rb +20 -0
- data/lib/sidekiq_utils/middleware/server/find_optional.rb +20 -0
- data/lib/sidekiq_utils/middleware/server/job_counter.rb +15 -0
- data/lib/sidekiq_utils/middleware/server/memory_monitor.rb +46 -0
- data/lib/sidekiq_utils/middleware/server/throughput_monitor.rb +25 -0
- data/lib/sidekiq_utils/redis_monitor_storage.rb +81 -0
- data/lib/sidekiq_utils/views/job_counts.erb +34 -0
- data/lib/sidekiq_utils/views/memory.erb +33 -0
- data/lib/sidekiq_utils/views/throughput.erb +27 -0
- data/lib/sidekiq_utils/web_extensions/job_counter.rb +35 -0
- data/lib/sidekiq_utils/web_extensions/memory_monitor.rb +26 -0
- data/lib/sidekiq_utils/web_extensions/throughput_monitor.rb +32 -0
- data/lib/sidekiq_utils.rb +21 -0
- data/sidekiq_utils.gemspec +84 -0
- metadata +158 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module SidekiqUtils
|
4
|
+
class LatencyAlert
|
5
|
+
REDIS_KEY = "sidekiq_queue_latency_alert"
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def check!
|
9
|
+
alerts = {}
|
10
|
+
Sidekiq::Queue.all.each do |queue|
|
11
|
+
threshold = config['alert_thresholds'][queue.name]
|
12
|
+
next if threshold == :disabled
|
13
|
+
threshold ||= config['alert_thresholds']['default'] || 10.minutes
|
14
|
+
if (latency = queue.latency) > threshold
|
15
|
+
alerts[queue.name] = latency
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if alerts.blank?
|
20
|
+
if should_alert_back_to_normal?
|
21
|
+
Sidekiq.redis { |r| r.del(REDIS_KEY) }
|
22
|
+
slack_alert("All queues under their thresholds.")
|
23
|
+
end
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
|
27
|
+
alert_message = ["Sidekiq queue latency over threshold:"]
|
28
|
+
alerts.each do |queue, latency|
|
29
|
+
alert_message << "Queue #{queue} is #{formatted_latency(latency)} behind"
|
30
|
+
end
|
31
|
+
alert_message = alert_message.join("\n")
|
32
|
+
if should_alert_again?(alert_message)
|
33
|
+
slack_alert(alert_message)
|
34
|
+
end
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def config
|
40
|
+
@config ||= (YAML.load(ERB.new(
|
41
|
+
File.read('config/sidekiq_utils.yml')).result) || {})
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def formatted_latency(latency)
|
46
|
+
latency_days = (latency.to_f / 1.day).floor
|
47
|
+
if latency < 1.day
|
48
|
+
formatted_latency =
|
49
|
+
ActionController::Base.helpers.distance_of_time_in_words(latency)
|
50
|
+
else
|
51
|
+
formatted_latency = "#{latency_days} #{"day".pluralize(latency_days)}"
|
52
|
+
end
|
53
|
+
if latency > 1.day
|
54
|
+
latency_in_day = latency - latency_days * 1.day
|
55
|
+
if latency_in_day >= 45.minutes
|
56
|
+
formatted_latency += " and " +
|
57
|
+
ActionController::Base.helpers.
|
58
|
+
distance_of_time_in_words(latency_in_day)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
formatted_latency
|
62
|
+
end
|
63
|
+
|
64
|
+
def slack_alert(alert_message)
|
65
|
+
Array.wrap(config['channels_to_alert']).each do |slack_name|
|
66
|
+
Slack.send_message(
|
67
|
+
slack_name, alert_message,
|
68
|
+
icon: ':alarm_clock:', username: 'Sidekiq alerts')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def should_alert_back_to_normal?
|
73
|
+
Sidekiq.redis do |redis|
|
74
|
+
redis.get(REDIS_KEY).present?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def should_alert_again?(message)
|
79
|
+
Sidekiq.redis do |redis|
|
80
|
+
last_alert = redis.get(REDIS_KEY)
|
81
|
+
last_alert = JSON.parse(last_alert) if last_alert
|
82
|
+
if !last_alert ||
|
83
|
+
last_alert['message_hash'] != Digest::SHA1.hexdigest(message) ||
|
84
|
+
last_alert['time'] < (config['repeat_alert_every'] || 60).to_i.minutes.ago.to_i
|
85
|
+
redis.set(REDIS_KEY, {
|
86
|
+
'message_hash' => Digest::SHA1.hexdigest(message),
|
87
|
+
'time' => Time.now.to_i,
|
88
|
+
}.to_json)
|
89
|
+
true
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module Middleware
|
3
|
+
module Client
|
4
|
+
class AdditionalSerialization
|
5
|
+
def call(worker_class, job, queue, redis_pool)
|
6
|
+
if job['class'] == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
7
|
+
# this is handled in ActiveJob, as it would otherwise raise an
|
8
|
+
# exception before it even gets here
|
9
|
+
return yield
|
10
|
+
end
|
11
|
+
|
12
|
+
job['args'] = job['args'].map do |arg|
|
13
|
+
::SidekiqUtils::AdditionalSerialization.wrap_argument(arg)
|
14
|
+
end
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module Middleware
|
3
|
+
module Client
|
4
|
+
class Deprioritize
|
5
|
+
def call(worker_class, job, queue, redis_pool)
|
6
|
+
if Thread.current[:deprioritize_worker_classes].to_a.include?(worker_class)
|
7
|
+
job['queue'] = 'low'
|
8
|
+
end
|
9
|
+
yield
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module Middleware
|
3
|
+
module Client
|
4
|
+
class JobCounter
|
5
|
+
def call(worker_class, job, queue, redis_pool)
|
6
|
+
unless job['at']
|
7
|
+
# don't count when jobs get put on the scheduled set, because
|
8
|
+
# otherwise we'll double-count them when they get popped and moved
|
9
|
+
# to a work queue.
|
10
|
+
SidekiqUtils::JobCounter.increment(job)
|
11
|
+
end
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
class AdditionalSerialization
|
5
|
+
def call(worker, job, queue)
|
6
|
+
if job['class'] == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
7
|
+
# this is handled in ActiveJob, as it would otherwise raise an
|
8
|
+
# exception before it even gets here
|
9
|
+
return yield
|
10
|
+
end
|
11
|
+
|
12
|
+
job['args'] = job['args'].map do |arg|
|
13
|
+
::SidekiqUtils::AdditionalSerialization.unwrap_argument(arg)
|
14
|
+
end
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
class FindOptional
|
5
|
+
def call(worker, job, queue)
|
6
|
+
begin
|
7
|
+
yield
|
8
|
+
rescue SidekiqUtils::FindOptional::NotFoundError
|
9
|
+
if queue == 'retry_once'
|
10
|
+
# do nothing; this is already the retry and it failed again
|
11
|
+
else
|
12
|
+
worker.class.set(queue: :retry_once).
|
13
|
+
perform_in(30.seconds, *job['args'])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
class JobCounter
|
5
|
+
def call(worker, job, queue)
|
6
|
+
# we decrement here whether the job succeeds or not, because
|
7
|
+
# re-enqueuing from the retry queue triggers the client middleware
|
8
|
+
# and thus another increment even in the case of an error
|
9
|
+
SidekiqUtils::JobCounter.decrement(job)
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'objspace'
|
2
|
+
|
3
|
+
module SidekiqUtils
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
class MemoryMonitor
|
7
|
+
def call(worker, job, queue)
|
8
|
+
return yield unless Sidekiq.options[:concurrency] == 1
|
9
|
+
|
10
|
+
objects_before = count_allocated_objects
|
11
|
+
memory_before = get_allocated_memory
|
12
|
+
|
13
|
+
GC.start(full_mark: true)
|
14
|
+
GC.disable
|
15
|
+
begin
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
GC.enable
|
19
|
+
GC.start(full_mark: true)
|
20
|
+
objects_after = count_allocated_objects
|
21
|
+
memory_after = get_allocated_memory
|
22
|
+
|
23
|
+
object_growth = objects_after - objects_before
|
24
|
+
SidekiqUtils::RedisMonitorStorage.store(
|
25
|
+
'sidekiq_memory', 'object', job, object_growth)
|
26
|
+
Sidekiq.logger.info("Object growth: #{object_growth}")
|
27
|
+
|
28
|
+
memory_growth = memory_after - memory_before
|
29
|
+
SidekiqUtils::RedisMonitorStorage.store(
|
30
|
+
'sidekiq_memory', 'memory', job, memory_growth)
|
31
|
+
Sidekiq.logger.info("Memory growth: #{memory_growth}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def count_allocated_objects
|
37
|
+
ObjectSpace.each_object.inject(0) {|count, obj| count + 1 }
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_allocated_memory
|
41
|
+
ObjectSpace.memsize_of_all
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
class ThroughputMonitor
|
5
|
+
def call(worker, job, queue)
|
6
|
+
start = Time.now
|
7
|
+
begin
|
8
|
+
yield
|
9
|
+
ensure
|
10
|
+
elapsed = Time.now - start
|
11
|
+
elapsed_ms = (elapsed * 1_000).round
|
12
|
+
|
13
|
+
SidekiqUtils::RedisMonitorStorage.store(
|
14
|
+
'sidekiq_elapsed', 'elapsed', job, elapsed_ms)
|
15
|
+
Sidekiq.redis do |redis|
|
16
|
+
redis.hset('sidekiq_last_run',
|
17
|
+
SidekiqUtils::RedisMonitorStorage.job_prefix(job),
|
18
|
+
Time.now.to_i)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module RedisMonitorStorage
|
3
|
+
class << self
|
4
|
+
def add_first_argument_to_job_key(*klasses)
|
5
|
+
@first_argument_to_job_key_for ||= []
|
6
|
+
@first_argument_to_job_key_for |= klasses
|
7
|
+
end
|
8
|
+
|
9
|
+
def store(key, prefix, job, value)
|
10
|
+
Sidekiq.redis do |redis|
|
11
|
+
redis.multi do
|
12
|
+
redis.hincrby(key, full_prefix(job, prefix, 'sum'), value)
|
13
|
+
redis.hincrby(key, full_prefix(job, prefix, 'count'), 1)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def retrieve(top_level_key, prefix)
|
19
|
+
data = {}
|
20
|
+
Sidekiq.redis {|r| r.hgetall(top_level_key) }.each do |key, value|
|
21
|
+
(job, prefix_type, date, value_type) = key.split('||')
|
22
|
+
next unless prefix_type == prefix
|
23
|
+
|
24
|
+
if Date.parse(date) < 1.week.ago
|
25
|
+
# expired data, get rid of it
|
26
|
+
Sidekiq.redis {|r| r.hdel(top_level_key, key) }
|
27
|
+
else
|
28
|
+
data[job] ||= { 'sum' => 0, 'count' => 0 }
|
29
|
+
data[job][value_type] += value.to_i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
data.each do |job, values|
|
34
|
+
values['average'] = (values['sum'].to_f / values['count'].to_i).round
|
35
|
+
end
|
36
|
+
data
|
37
|
+
end
|
38
|
+
|
39
|
+
def full_prefix(job, prefix = nil, last_prefix = nil)
|
40
|
+
job_prefix = job_prefix(job)
|
41
|
+
full_prefix = [job_prefix, prefix, Date.today.to_s(:medium), last_prefix]
|
42
|
+
full_prefix.compact.join('||')
|
43
|
+
end
|
44
|
+
|
45
|
+
def job_prefix(job, unwrap_arguments: false)
|
46
|
+
arguments = arguments(job)
|
47
|
+
if unwrap_arguments
|
48
|
+
arguments = arguments.
|
49
|
+
map {|arg| SidekiqUtils::AdditionalSerialization.unwrap_argument(arg) }
|
50
|
+
end
|
51
|
+
|
52
|
+
if active_job?(job)
|
53
|
+
job_prefix = job['wrapped']
|
54
|
+
else
|
55
|
+
job_prefix = job['class']
|
56
|
+
end
|
57
|
+
|
58
|
+
case job_prefix
|
59
|
+
when 'ActionMailer::DeliveryJob'
|
60
|
+
job_prefix += "[#{arguments[0..1].join('#')}]"
|
61
|
+
when *(@first_argument_to_job_key_for.to_a)
|
62
|
+
job_prefix += "[#{arguments[0]}]"
|
63
|
+
end
|
64
|
+
|
65
|
+
job_prefix
|
66
|
+
end
|
67
|
+
|
68
|
+
def arguments(job)
|
69
|
+
if active_job?(job)
|
70
|
+
job['args'].first['arguments']
|
71
|
+
else
|
72
|
+
job['args']
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def active_job?(job)
|
77
|
+
job['class'] == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<header class='row'>
|
2
|
+
<div class='col-sm-5'>
|
3
|
+
<h3>Enqueued Job Counts</h3>
|
4
|
+
</div>
|
5
|
+
</header>
|
6
|
+
|
7
|
+
<p>
|
8
|
+
This data may not be 100% accurate if jobs haven been manually deleted
|
9
|
+
or queues have been manually cleared.
|
10
|
+
</p>
|
11
|
+
<table class='table table-striped table-bordered table-white'>
|
12
|
+
<thead>
|
13
|
+
<th>Queue</th>
|
14
|
+
<th>Job</th>
|
15
|
+
<th>Count</th>
|
16
|
+
<th>Approx. sequential run time (days:hours:minutes)</th>
|
17
|
+
</thead>
|
18
|
+
<tbody>
|
19
|
+
<% @counts.each do |values| %>
|
20
|
+
<tr>
|
21
|
+
<td><%= values[:queue] %></td>
|
22
|
+
<td><%= values[:job] %></td>
|
23
|
+
<td><%= ActiveSupport::NumberHelper.number_to_delimited(values[:count]) %></td>
|
24
|
+
<td>
|
25
|
+
<% if values[:avg_runtime] %>
|
26
|
+
<%= values[:runtime_day] %>:<%= values[:runtime_hour].to_s.rjust(2, '0') %>:<%= values[:runtime_min].to_s.rjust(2, '0') %>
|
27
|
+
<% else %>
|
28
|
+
-/-
|
29
|
+
<% end %>
|
30
|
+
</td>
|
31
|
+
</tr>
|
32
|
+
<% end %>
|
33
|
+
</tbody>
|
34
|
+
</table>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<header class='row'>
|
2
|
+
<div class='col-sm-5'>
|
3
|
+
<h3>Memory</h3>
|
4
|
+
</div>
|
5
|
+
</header>
|
6
|
+
|
7
|
+
<p>
|
8
|
+
Data from the trailing seven days. Only a small percentage of jobs get
|
9
|
+
profiled. The "absolute" numbers refer only to those jobs that did get
|
10
|
+
profiled.
|
11
|
+
</p>
|
12
|
+
<table class='table table-striped table-bordered table-white'>
|
13
|
+
<thead>
|
14
|
+
<tr>
|
15
|
+
<th>Job</th>
|
16
|
+
<th>Memory growth (absolute)</th>
|
17
|
+
<th>Memory growth (per job)</th>
|
18
|
+
<th>Object growth (absolute)</th>
|
19
|
+
<th>Object growth (per job)</th>
|
20
|
+
</tr>
|
21
|
+
</thead>
|
22
|
+
<tbody>
|
23
|
+
<% @memory.each do |(job, avg_memory, avg_object, abs_memory, abs_object)| %>
|
24
|
+
<tr>
|
25
|
+
<td><%= job %></td>
|
26
|
+
<td><%= ActiveSupport::NumberHelper.number_to_human_size(abs_memory) %></td>
|
27
|
+
<td><%= ActiveSupport::NumberHelper.number_to_human_size(avg_memory) %></td>
|
28
|
+
<td><%= ActiveSupport::NumberHelper.number_to_delimited(abs_object) %></td>
|
29
|
+
<td><%= ActiveSupport::NumberHelper.number_to_delimited(avg_object) %></td>
|
30
|
+
</tr>
|
31
|
+
<% end %>
|
32
|
+
</tbody>
|
33
|
+
</table
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<header class='row'>
|
2
|
+
<div class='col-sm-5'>
|
3
|
+
<h3>Throughput</h3>
|
4
|
+
</div>
|
5
|
+
</header>
|
6
|
+
|
7
|
+
<p>Data from the trailing seven days.</p>
|
8
|
+
<table class='table table-striped table-bordered table-white'>
|
9
|
+
<thead>
|
10
|
+
<tr>
|
11
|
+
<th>Job</th>
|
12
|
+
<th>Avg. execution time (minute:second.millisecond)</th>
|
13
|
+
<th>Total processed</th>
|
14
|
+
<th>Last run at</th>
|
15
|
+
</tr>
|
16
|
+
</thead>
|
17
|
+
<tbody>
|
18
|
+
<% @throughput.each do |(job, avg_execution_time_ms, total_processed, last_run_at)| %>
|
19
|
+
<tr>
|
20
|
+
<td><%= job %></td>
|
21
|
+
<td><%= Time.at(avg_execution_time_ms.to_f / 1_000).utc.strftime("%M:%S.%L") %></td>
|
22
|
+
<td><%= ActiveSupport::NumberHelper.number_to_delimited(total_processed) %></td>
|
23
|
+
<td><%= last_run_at %></td>
|
24
|
+
</tr>
|
25
|
+
<% end %>
|
26
|
+
<tbody>
|
27
|
+
</table
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module WebExtensions
|
3
|
+
module JobCounter
|
4
|
+
def self.registered(app)
|
5
|
+
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
6
|
+
|
7
|
+
require 'active_support/number_helper'
|
8
|
+
app.get("/job_counts") do
|
9
|
+
@throughput = SidekiqUtils::RedisMonitorStorage.
|
10
|
+
retrieve('sidekiq_elapsed', 'elapsed')
|
11
|
+
@counts = []
|
12
|
+
SidekiqUtils::JobCounter.counts.each do |queue, job_counts|
|
13
|
+
job_counts.each do |job, count|
|
14
|
+
values = { queue: queue, job: job, count: count }
|
15
|
+
if (values[:avg_runtime] = @throughput[job].try!(:[], 'average'))
|
16
|
+
execution_time = values[:count] * values[:avg_runtime].to_f / 1_000
|
17
|
+
values[:runtime_day] = days =
|
18
|
+
(execution_time / 1.day).floor
|
19
|
+
values[:runtime_hour] = hours =
|
20
|
+
((execution_time - days.days) / 1.hour).floor
|
21
|
+
values[:runtime_min] = (
|
22
|
+
(execution_time - days.days - hours.hours) / 1.minute
|
23
|
+
).round
|
24
|
+
end
|
25
|
+
@counts << values
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@counts.sort_by! {|x| -1 * x[:count] }
|
29
|
+
|
30
|
+
render(:erb, File.read(File.join(view_path, "job_counts.erb")))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module WebExtensions
|
3
|
+
module MemoryMonitor
|
4
|
+
def self.registered(app)
|
5
|
+
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
6
|
+
|
7
|
+
require 'active_support/number_helper'
|
8
|
+
app.get("/memory") do
|
9
|
+
memory = SidekiqUtils::RedisMonitorStorage.retrieve('sidekiq_memory', 'memory')
|
10
|
+
object = SidekiqUtils::RedisMonitorStorage.retrieve('sidekiq_memory', 'object')
|
11
|
+
|
12
|
+
@memory = (memory.keys | object.keys).map do |job|
|
13
|
+
[job,
|
14
|
+
memory[job]['average'],
|
15
|
+
object[job]['average'],
|
16
|
+
memory[job]['sum'],
|
17
|
+
object[job]['sum'],
|
18
|
+
]
|
19
|
+
end.sort_by {|x| -x[3] }
|
20
|
+
|
21
|
+
render(:erb, File.read(File.join(view_path, "memory.erb")))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SidekiqUtils
|
2
|
+
module WebExtensions
|
3
|
+
module ThroughputMonitor
|
4
|
+
def self.registered(app)
|
5
|
+
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
6
|
+
|
7
|
+
app.get("/throughput") do
|
8
|
+
last_run_at = {}
|
9
|
+
Sidekiq.redis {|r| r.hgetall('sidekiq_last_run') }.each do |job, last_run|
|
10
|
+
last_run_at[job] = last_run
|
11
|
+
end
|
12
|
+
@throughput = SidekiqUtils::RedisMonitorStorage.
|
13
|
+
retrieve('sidekiq_elapsed', 'elapsed').map do |job, values|
|
14
|
+
|
15
|
+
if last_run_at[job]
|
16
|
+
last_run_time = Time.at(Integer(last_run_at[job])).
|
17
|
+
in_time_zone('US/Eastern').to_s(:long) + ' ET'
|
18
|
+
else
|
19
|
+
last_run_time = 'n/a'
|
20
|
+
end
|
21
|
+
[job,
|
22
|
+
values['average'],
|
23
|
+
values['count'],
|
24
|
+
last_run_time,
|
25
|
+
]
|
26
|
+
end.sort_by {|x| -x[2] }
|
27
|
+
render(:erb, File.read(File.join(view_path, "throughput.erb")))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'sidekiq_utils/middleware/client/additional_serialization'
|
2
|
+
require 'sidekiq_utils/middleware/client/deprioritize'
|
3
|
+
require 'sidekiq_utils/middleware/client/job_counter'
|
4
|
+
|
5
|
+
require 'sidekiq_utils/middleware/server/additional_serialization'
|
6
|
+
require 'sidekiq_utils/middleware/server/find_optional'
|
7
|
+
require 'sidekiq_utils/middleware/server/job_counter'
|
8
|
+
require 'sidekiq_utils/middleware/server/memory_monitor'
|
9
|
+
require 'sidekiq_utils/middleware/server/throughput_monitor'
|
10
|
+
|
11
|
+
require 'sidekiq_utils/web_extensions/job_counter'
|
12
|
+
require 'sidekiq_utils/web_extensions/memory_monitor'
|
13
|
+
require 'sidekiq_utils/web_extensions/throughput_monitor'
|
14
|
+
|
15
|
+
require 'sidekiq_utils/redis_monitor_storage'
|
16
|
+
require 'sidekiq_utils/additional_serialization'
|
17
|
+
require 'sidekiq_utils/deprioritize'
|
18
|
+
require 'sidekiq_utils/enqueued_jobs_helper'
|
19
|
+
require 'sidekiq_utils/find_optional'
|
20
|
+
require 'sidekiq_utils/job_counter'
|
21
|
+
require 'sidekiq_utils/latency_alert'
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Generated by juwelier
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: sidekiq_utils 1.0.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "sidekiq_utils".freeze
|
9
|
+
s.version = "1.0.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib".freeze]
|
13
|
+
s.authors = ["Magnus von Koeller".freeze]
|
14
|
+
s.date = "2017-12-08"
|
15
|
+
s.description = "Tools that make working with a major Sidekiq installation more fun.".freeze
|
16
|
+
s.email = "magnus@angel.co".freeze
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"lib/sidekiq_utils.rb",
|
29
|
+
"lib/sidekiq_utils/additional_serialization.rb",
|
30
|
+
"lib/sidekiq_utils/deprioritize.rb",
|
31
|
+
"lib/sidekiq_utils/enqueued_jobs_helper.rb",
|
32
|
+
"lib/sidekiq_utils/find_optional.rb",
|
33
|
+
"lib/sidekiq_utils/job_counter.rb",
|
34
|
+
"lib/sidekiq_utils/latency_alert.rb",
|
35
|
+
"lib/sidekiq_utils/middleware/client/additional_serialization.rb",
|
36
|
+
"lib/sidekiq_utils/middleware/client/deprioritize.rb",
|
37
|
+
"lib/sidekiq_utils/middleware/client/job_counter.rb",
|
38
|
+
"lib/sidekiq_utils/middleware/server/additional_serialization.rb",
|
39
|
+
"lib/sidekiq_utils/middleware/server/find_optional.rb",
|
40
|
+
"lib/sidekiq_utils/middleware/server/job_counter.rb",
|
41
|
+
"lib/sidekiq_utils/middleware/server/memory_monitor.rb",
|
42
|
+
"lib/sidekiq_utils/middleware/server/throughput_monitor.rb",
|
43
|
+
"lib/sidekiq_utils/redis_monitor_storage.rb",
|
44
|
+
"lib/sidekiq_utils/views/job_counts.erb",
|
45
|
+
"lib/sidekiq_utils/views/memory.erb",
|
46
|
+
"lib/sidekiq_utils/views/throughput.erb",
|
47
|
+
"lib/sidekiq_utils/web_extensions/job_counter.rb",
|
48
|
+
"lib/sidekiq_utils/web_extensions/memory_monitor.rb",
|
49
|
+
"lib/sidekiq_utils/web_extensions/throughput_monitor.rb",
|
50
|
+
"sidekiq_utils.gemspec"
|
51
|
+
]
|
52
|
+
s.homepage = "http://github.com/venturehacks/sidekiq_angels".freeze
|
53
|
+
s.licenses = ["MIT".freeze]
|
54
|
+
s.rubygems_version = "2.6.13".freeze
|
55
|
+
s.summary = "Tools that make working with a major Sidekiq installation more fun.".freeze
|
56
|
+
|
57
|
+
if s.respond_to? :specification_version then
|
58
|
+
s.specification_version = 4
|
59
|
+
|
60
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
61
|
+
s.add_runtime_dependency(%q<sidekiq>.freeze, [">= 4.0.0"])
|
62
|
+
s.add_development_dependency(%q<shoulda>.freeze, [">= 0"])
|
63
|
+
s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
64
|
+
s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
65
|
+
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
66
|
+
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<sidekiq>.freeze, [">= 4.0.0"])
|
69
|
+
s.add_dependency(%q<shoulda>.freeze, [">= 0"])
|
70
|
+
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
71
|
+
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
72
|
+
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
73
|
+
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
74
|
+
end
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<sidekiq>.freeze, [">= 4.0.0"])
|
77
|
+
s.add_dependency(%q<shoulda>.freeze, [">= 0"])
|
78
|
+
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
79
|
+
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
80
|
+
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
81
|
+
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|