sidekiq 7.3.0 → 8.0.5
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/Changes.md +158 -0
- data/README.md +16 -13
- data/bin/sidekiqload +31 -22
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +120 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +184 -71
- data/lib/sidekiq/capsule.rb +11 -9
- data/lib/sidekiq/cli.rb +16 -20
- data/lib/sidekiq/client.rb +28 -11
- data/lib/sidekiq/component.rb +62 -2
- data/lib/sidekiq/config.rb +42 -18
- data/lib/sidekiq/deploy.rb +2 -0
- data/lib/sidekiq/embedded.rb +4 -1
- data/lib/sidekiq/iterable_job.rb +3 -0
- data/lib/sidekiq/job/interrupt_handler.rb +2 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +3 -3
- data/lib/sidekiq/job/iterable.rb +82 -7
- data/lib/sidekiq/job_logger.rb +15 -27
- data/lib/sidekiq/job_retry.rb +17 -5
- data/lib/sidekiq/job_util.rb +7 -1
- data/lib/sidekiq/launcher.rb +3 -2
- data/lib/sidekiq/logger.rb +19 -70
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +73 -45
- data/lib/sidekiq/metrics/shared.rb +23 -9
- data/lib/sidekiq/metrics/tracking.rb +22 -12
- data/lib/sidekiq/middleware/current_attributes.rb +12 -4
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +2 -1
- data/lib/sidekiq/paginator.rb +14 -1
- data/lib/sidekiq/processor.rb +26 -19
- data/lib/sidekiq/profiler.rb +72 -0
- data/lib/sidekiq/rails.rb +44 -55
- data/lib/sidekiq/redis_client_adapter.rb +0 -1
- data/lib/sidekiq/redis_connection.rb +22 -4
- data/lib/sidekiq/ring_buffer.rb +2 -0
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +7 -7
- data/lib/sidekiq/version.rb +6 -2
- data/lib/sidekiq/web/action.rb +124 -69
- data/lib/sidekiq/web/application.rb +355 -377
- data/lib/sidekiq/web/config.rb +120 -0
- data/lib/sidekiq/web/helpers.rb +64 -33
- data/lib/sidekiq/web/router.rb +61 -74
- data/lib/sidekiq/web.rb +52 -150
- data/lib/sidekiq.rb +5 -4
- data/sidekiq.gemspec +6 -6
- data/web/assets/javascripts/application.js +6 -13
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +2 -0
- data/web/assets/javascripts/dashboard.js +7 -1
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +766 -0
- data/web/locales/ar.yml +1 -0
- data/web/locales/cs.yml +1 -0
- data/web/locales/da.yml +1 -0
- data/web/locales/de.yml +1 -0
- data/web/locales/el.yml +1 -0
- data/web/locales/en.yml +9 -1
- data/web/locales/es.yml +24 -2
- data/web/locales/fa.yml +1 -0
- data/web/locales/fr.yml +1 -1
- data/web/locales/gd.yml +1 -1
- data/web/locales/he.yml +1 -0
- data/web/locales/hi.yml +1 -0
- data/web/locales/it.yml +40 -1
- data/web/locales/ja.yml +1 -1
- data/web/locales/ko.yml +1 -0
- data/web/locales/lt.yml +1 -0
- data/web/locales/nb.yml +1 -0
- data/web/locales/nl.yml +1 -0
- data/web/locales/pl.yml +1 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +3 -3
- data/web/locales/pt.yml +1 -0
- data/web/locales/ru.yml +1 -0
- data/web/locales/sv.yml +1 -0
- data/web/locales/ta.yml +1 -0
- data/web/locales/tr.yml +2 -2
- data/web/locales/uk.yml +25 -1
- data/web/locales/ur.yml +1 -0
- data/web/locales/vi.yml +1 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -74
- data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -2
- data/web/views/_footer.erb +31 -34
- data/web/views/_job_info.erb +91 -89
- data/web/views/_metrics_period_select.erb +13 -10
- data/web/views/_nav.erb +14 -21
- data/web/views/_paging.erb +23 -21
- data/web/views/_poll_link.erb +2 -2
- data/web/views/_summary.erb +16 -16
- data/web/views/busy.erb +124 -122
- data/web/views/dashboard.erb +63 -64
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +3 -4
- data/web/views/layout.erb +13 -29
- data/web/views/metrics.erb +75 -82
- data/web/views/metrics_for_job.erb +45 -46
- data/web/views/morgue.erb +61 -70
- data/web/views/profiles.erb +43 -0
- data/web/views/queue.erb +54 -52
- data/web/views/queues.erb +43 -41
- data/web/views/retries.erb +66 -75
- data/web/views/retry.erb +32 -27
- data/web/views/scheduled.erb +59 -55
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +27 -29
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -163
- data/web/assets/stylesheets/application.css +0 -758
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_status.erb +0 -4
@@ -1,479 +1,457 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
extend WebRouter
|
6
|
-
|
7
|
-
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
8
|
-
CSP_HEADER_TEMPLATE = [
|
9
|
-
"default-src 'self' https: http:",
|
10
|
-
"child-src 'self'",
|
11
|
-
"connect-src 'self' https: http: wss: ws:",
|
12
|
-
"font-src 'self' https: http:",
|
13
|
-
"frame-src 'self'",
|
14
|
-
"img-src 'self' https: http: data:",
|
15
|
-
"manifest-src 'self'",
|
16
|
-
"media-src 'self'",
|
17
|
-
"object-src 'none'",
|
18
|
-
"script-src 'self' 'nonce-!placeholder!'",
|
19
|
-
"style-src 'self' https: http: 'unsafe-inline'", # TODO Nonce in 8.0
|
20
|
-
"worker-src 'self'",
|
21
|
-
"base-uri 'self'"
|
22
|
-
].join("; ").freeze
|
23
|
-
METRICS_PERIODS = {
|
24
|
-
"1h" => 60,
|
25
|
-
"2h" => 120,
|
26
|
-
"4h" => 240,
|
27
|
-
"8h" => 480
|
28
|
-
}
|
29
|
-
|
30
|
-
def initialize(klass)
|
31
|
-
@klass = klass
|
32
|
-
end
|
3
|
+
require "sidekiq/paginator"
|
4
|
+
require "sidekiq/web/helpers"
|
33
5
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
6
|
+
module Sidekiq
|
7
|
+
class Web
|
8
|
+
class Application
|
9
|
+
extend Router
|
10
|
+
include Router
|
11
|
+
|
12
|
+
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
13
|
+
|
14
|
+
CSP_HEADER_TEMPLATE = [
|
15
|
+
"default-src 'self' https: http:",
|
16
|
+
"child-src 'self'",
|
17
|
+
"connect-src 'self' https: http: wss: ws:",
|
18
|
+
"font-src 'none'",
|
19
|
+
"frame-src 'self'",
|
20
|
+
"img-src 'self' https: http: data:",
|
21
|
+
"manifest-src 'self'",
|
22
|
+
"media-src 'self'",
|
23
|
+
"object-src 'none'",
|
24
|
+
"script-src 'self' 'nonce-!placeholder!'",
|
25
|
+
"style-src 'self' 'nonce-!placeholder!'",
|
26
|
+
"worker-src 'self'",
|
27
|
+
"base-uri 'self'"
|
28
|
+
].join("; ").freeze
|
29
|
+
|
30
|
+
METRICS_PERIODS = {
|
31
|
+
"1h" => {minutes: 60},
|
32
|
+
"2h" => {minutes: 120},
|
33
|
+
"4h" => {minutes: 240},
|
34
|
+
"8h" => {minutes: 480},
|
35
|
+
"24h" => {hours: 24},
|
36
|
+
"48h" => {hours: 48},
|
37
|
+
"72h" => {hours: 72}
|
38
|
+
}
|
65
39
|
|
66
|
-
|
67
|
-
|
40
|
+
def initialize(inst)
|
41
|
+
@app = inst
|
42
|
+
end
|
68
43
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
erb(:metrics)
|
76
|
-
end
|
44
|
+
head "/" do
|
45
|
+
# HEAD / is the cheapest heartbeat possible,
|
46
|
+
# it hits Redis to ensure connectivity
|
47
|
+
_ = Sidekiq.redis { |c| c.llen("queue:default") }
|
48
|
+
""
|
49
|
+
end
|
77
50
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@periods = METRICS_PERIODS
|
83
|
-
minutes = @periods.fetch(@period, @periods.values.first)
|
84
|
-
@query_result = q.for_job(@name, minutes: minutes)
|
85
|
-
erb(:metrics_for_job)
|
86
|
-
end
|
51
|
+
get "/" do
|
52
|
+
@redis_info = redis_info.slice(*REDIS_KEYS)
|
53
|
+
days = (url_params("days") || 30).to_i
|
54
|
+
return halt(401) if days < 1 || days > 180
|
87
55
|
|
88
|
-
|
89
|
-
|
90
|
-
|
56
|
+
stats_history = Sidekiq::Stats::History.new(days)
|
57
|
+
@processed_history = stats_history.processed
|
58
|
+
@failed_history = stats_history.failed
|
91
59
|
|
92
|
-
|
93
|
-
|
60
|
+
erb(:dashboard)
|
61
|
+
end
|
94
62
|
|
95
|
-
|
96
|
-
|
97
|
-
|
63
|
+
get "/metrics" do
|
64
|
+
x = url_params("substr")
|
65
|
+
class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
|
98
66
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
67
|
+
q = Sidekiq::Metrics::Query.new
|
68
|
+
@period = h(url_params("period") || "1h")
|
69
|
+
@periods = METRICS_PERIODS
|
70
|
+
args = @periods.fetch(@period, @periods.values.first)
|
71
|
+
@query_result = q.top_jobs(**args.merge(class_filter: class_filter))
|
104
72
|
|
105
|
-
|
106
|
-
|
107
|
-
end
|
73
|
+
header "refresh", 60 if @period == "1h"
|
74
|
+
erb(:metrics)
|
108
75
|
end
|
109
76
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
77
|
+
get "/metrics/:name" do
|
78
|
+
@name = route_params(:name)
|
79
|
+
@period = h(url_params("period") || "1h")
|
80
|
+
# Periods larger than 8 hours are not supported for histogram chart
|
81
|
+
@period = "8h" if @period.to_i > 8
|
82
|
+
@periods = METRICS_PERIODS.reject { |k, v| k.to_i > 8 }
|
83
|
+
args = @periods.fetch(@period, @periods.values.first)
|
84
|
+
q = Sidekiq::Metrics::Query.new
|
85
|
+
@query_result = q.for_job(@name, **args)
|
86
|
+
|
87
|
+
header "refresh", 60 if @period == "1h"
|
88
|
+
erb(:metrics_for_job)
|
89
|
+
end
|
115
90
|
|
116
|
-
|
117
|
-
|
91
|
+
get "/busy" do
|
92
|
+
@count = (url_params("count") || 100).to_i
|
93
|
+
(@current_page, @total_size, @workset) = page_items(workset, url_params("page"), @count)
|
118
94
|
|
119
|
-
|
95
|
+
erb(:busy)
|
96
|
+
end
|
120
97
|
|
121
|
-
|
122
|
-
|
98
|
+
post "/busy" do
|
99
|
+
if url_params("identity")
|
100
|
+
pro = Sidekiq::ProcessSet[url_params("identity")]
|
123
101
|
|
124
|
-
|
102
|
+
pro.quiet! if url_params("quiet")
|
103
|
+
pro.stop! if url_params("stop")
|
104
|
+
else
|
105
|
+
processes.each do |pro|
|
106
|
+
next if pro.embedded?
|
125
107
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
108
|
+
pro.quiet! if url_params("quiet")
|
109
|
+
pro.stop! if url_params("stop")
|
110
|
+
end
|
111
|
+
end
|
130
112
|
|
131
|
-
|
132
|
-
|
113
|
+
redirect "#{root_path}busy"
|
114
|
+
end
|
133
115
|
|
134
|
-
|
135
|
-
|
116
|
+
get "/queues" do
|
117
|
+
@queues = Sidekiq::Queue.all
|
136
118
|
|
137
|
-
|
138
|
-
queue.pause!
|
139
|
-
elsif Sidekiq.pro? && params["unpause"]
|
140
|
-
queue.unpause!
|
141
|
-
else
|
142
|
-
queue.clear
|
119
|
+
erb(:queues)
|
143
120
|
end
|
144
121
|
|
145
|
-
|
146
|
-
end
|
122
|
+
QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
|
147
123
|
|
148
|
-
|
149
|
-
|
150
|
-
Sidekiq::JobRecord.new(params["key_val"], name).delete
|
124
|
+
get "/queues/:name" do
|
125
|
+
@name = route_params(:name)
|
151
126
|
|
152
|
-
|
153
|
-
end
|
127
|
+
halt(404) if !@name || @name !~ QUEUE_NAME
|
154
128
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
129
|
+
@count = (url_params("count") || 25).to_i
|
130
|
+
@queue = Sidekiq::Queue.new(@name)
|
131
|
+
(@current_page, @total_size, @jobs) = page("queue:#{@name}", url_params("page"), @count, reverse: url_params("direction") == "asc")
|
132
|
+
@jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
|
159
133
|
|
160
|
-
|
161
|
-
|
134
|
+
erb(:queue)
|
135
|
+
end
|
162
136
|
|
163
|
-
|
164
|
-
|
165
|
-
halt(404) unless key
|
137
|
+
post "/queues/:name" do
|
138
|
+
queue = Sidekiq::Queue.new(route_params(:name))
|
166
139
|
|
167
|
-
|
140
|
+
if Sidekiq.pro? && url_params("pause")
|
141
|
+
queue.pause!
|
142
|
+
elsif Sidekiq.pro? && url_params("unpause")
|
143
|
+
queue.unpause!
|
144
|
+
else
|
145
|
+
queue.clear
|
146
|
+
end
|
168
147
|
|
169
|
-
|
170
|
-
redirect "#{root_path}morgue"
|
171
|
-
else
|
172
|
-
erb(:dead)
|
148
|
+
redirect "#{root_path}queues"
|
173
149
|
end
|
174
|
-
end
|
175
150
|
|
176
|
-
|
177
|
-
|
151
|
+
post "/queues/:name/delete" do
|
152
|
+
name = route_params(:name)
|
153
|
+
Sidekiq::JobRecord.new(url_params("key_val"), name).delete
|
178
154
|
|
179
|
-
|
180
|
-
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
181
|
-
retry_or_delete_or_kill job, params if job
|
155
|
+
redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
|
182
156
|
end
|
183
157
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
post "/morgue/all/delete" do
|
188
|
-
Sidekiq::DeadSet.new.clear
|
189
|
-
|
190
|
-
redirect "#{root_path}morgue"
|
191
|
-
end
|
192
|
-
|
193
|
-
post "/morgue/all/retry" do
|
194
|
-
Sidekiq::DeadSet.new.retry_all
|
158
|
+
get "/morgue" do
|
159
|
+
x = url_params("substr")
|
195
160
|
|
196
|
-
|
197
|
-
|
161
|
+
if x && x != ""
|
162
|
+
@dead = search(Sidekiq::DeadSet.new, x)
|
163
|
+
else
|
164
|
+
@count = (url_params("count") || 25).to_i
|
165
|
+
(@current_page, @total_size, @dead) = page("dead", url_params("page"), @count, reverse: true)
|
166
|
+
@dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
167
|
+
end
|
198
168
|
|
199
|
-
|
200
|
-
|
201
|
-
halt(404) unless key
|
169
|
+
erb(:morgue)
|
170
|
+
end
|
202
171
|
|
203
|
-
|
204
|
-
|
172
|
+
get "/morgue/:key" do
|
173
|
+
key = route_params(:key)
|
174
|
+
halt(404) unless key
|
205
175
|
|
206
|
-
|
207
|
-
end
|
176
|
+
@dead = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
208
177
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
178
|
+
if @dead.nil?
|
179
|
+
redirect "#{root_path}morgue"
|
180
|
+
else
|
181
|
+
erb(:dead)
|
182
|
+
end
|
183
|
+
end
|
213
184
|
|
214
|
-
|
215
|
-
|
185
|
+
post "/morgue" do
|
186
|
+
redirect(request.path) unless url_params("key")
|
216
187
|
|
217
|
-
|
218
|
-
|
188
|
+
url_params("key").each do |key|
|
189
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
190
|
+
retry_or_delete_or_kill job, request.params if job
|
191
|
+
end
|
219
192
|
|
220
|
-
|
221
|
-
redirect "#{root_path}retries"
|
222
|
-
else
|
223
|
-
erb(:retry)
|
193
|
+
redirect_with_query("#{root_path}morgue")
|
224
194
|
end
|
225
|
-
end
|
226
195
|
|
227
|
-
|
228
|
-
|
196
|
+
post "/morgue/all/delete" do
|
197
|
+
Sidekiq::DeadSet.new.clear
|
229
198
|
|
230
|
-
|
231
|
-
job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
|
232
|
-
retry_or_delete_or_kill job, params if job
|
199
|
+
redirect "#{root_path}morgue"
|
233
200
|
end
|
234
201
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
post "/retries/all/delete" do
|
239
|
-
Sidekiq::RetrySet.new.clear
|
202
|
+
post "/morgue/all/retry" do
|
203
|
+
Sidekiq::DeadSet.new.retry_all
|
240
204
|
|
241
|
-
|
242
|
-
|
205
|
+
redirect "#{root_path}morgue"
|
206
|
+
end
|
243
207
|
|
244
|
-
|
245
|
-
|
208
|
+
post "/morgue/:key" do
|
209
|
+
key = route_params(:key)
|
210
|
+
halt(404) unless key
|
246
211
|
|
247
|
-
|
248
|
-
|
212
|
+
job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
|
213
|
+
retry_or_delete_or_kill job, request.params if job
|
249
214
|
|
250
|
-
|
251
|
-
|
215
|
+
redirect_with_query("#{root_path}morgue")
|
216
|
+
end
|
252
217
|
|
253
|
-
|
254
|
-
|
218
|
+
get "/retries" do
|
219
|
+
x = url_params("substr")
|
255
220
|
|
256
|
-
|
257
|
-
|
221
|
+
if x && x != ""
|
222
|
+
@retries = search(Sidekiq::RetrySet.new, x)
|
223
|
+
else
|
224
|
+
@count = (url_params("count") || 25).to_i
|
225
|
+
(@current_page, @total_size, @retries) = page("retry", url_params("page"), @count)
|
226
|
+
@retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
227
|
+
end
|
258
228
|
|
259
|
-
|
229
|
+
erb(:retries)
|
230
|
+
end
|
260
231
|
|
261
|
-
|
262
|
-
|
232
|
+
get "/retries/:key" do
|
233
|
+
@retry = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
|
263
234
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
235
|
+
if @retry.nil?
|
236
|
+
redirect "#{root_path}retries"
|
237
|
+
else
|
238
|
+
erb(:retry)
|
239
|
+
end
|
240
|
+
end
|
268
241
|
|
269
|
-
|
270
|
-
|
242
|
+
post "/retries" do
|
243
|
+
redirect(request.path) unless url_params("key")
|
271
244
|
|
272
|
-
|
273
|
-
|
245
|
+
url_params("key").each do |key|
|
246
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(key)).first
|
247
|
+
retry_or_delete_or_kill job, request.params if job
|
248
|
+
end
|
274
249
|
|
275
|
-
|
276
|
-
redirect "#{root_path}scheduled"
|
277
|
-
else
|
278
|
-
erb(:scheduled_job_info)
|
250
|
+
redirect_with_query("#{root_path}retries")
|
279
251
|
end
|
280
|
-
end
|
281
252
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
params["key"].each do |key|
|
286
|
-
job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
|
287
|
-
delete_or_add_queue job, params if job
|
253
|
+
post "/retries/all/delete" do
|
254
|
+
Sidekiq::RetrySet.new.clear
|
255
|
+
redirect "#{root_path}retries"
|
288
256
|
end
|
289
257
|
|
290
|
-
|
291
|
-
|
258
|
+
post "/retries/all/retry" do
|
259
|
+
Sidekiq::RetrySet.new.retry_all
|
260
|
+
redirect "#{root_path}retries"
|
261
|
+
end
|
292
262
|
|
293
|
-
|
294
|
-
|
295
|
-
|
263
|
+
post "/retries/all/kill" do
|
264
|
+
Sidekiq::RetrySet.new.kill_all
|
265
|
+
redirect "#{root_path}retries"
|
266
|
+
end
|
296
267
|
|
297
|
-
|
298
|
-
|
268
|
+
post "/retries/:key" do
|
269
|
+
job = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
|
299
270
|
|
300
|
-
|
301
|
-
end
|
271
|
+
retry_or_delete_or_kill job, request.params if job
|
302
272
|
|
303
|
-
|
304
|
-
|
305
|
-
end
|
273
|
+
redirect_with_query("#{root_path}retries")
|
274
|
+
end
|
306
275
|
|
307
|
-
|
308
|
-
|
309
|
-
redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
|
310
|
-
json(
|
311
|
-
sidekiq: {
|
312
|
-
processed: sidekiq_stats.processed,
|
313
|
-
failed: sidekiq_stats.failed,
|
314
|
-
busy: sidekiq_stats.workers_size,
|
315
|
-
processes: sidekiq_stats.processes_size,
|
316
|
-
enqueued: sidekiq_stats.enqueued,
|
317
|
-
scheduled: sidekiq_stats.scheduled_size,
|
318
|
-
retries: sidekiq_stats.retry_size,
|
319
|
-
dead: sidekiq_stats.dead_size,
|
320
|
-
default_latency: sidekiq_stats.default_queue_latency
|
321
|
-
},
|
322
|
-
redis: redis_stats,
|
323
|
-
server_utc_time: server_utc_time
|
324
|
-
)
|
325
|
-
end
|
276
|
+
get "/scheduled" do
|
277
|
+
x = url_params("substr")
|
326
278
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
279
|
+
if x && x != ""
|
280
|
+
@scheduled = search(Sidekiq::ScheduledSet.new, x)
|
281
|
+
else
|
282
|
+
@count = (url_params("count") || 25).to_i
|
283
|
+
(@current_page, @total_size, @scheduled) = page("schedule", url_params("page"), @count)
|
284
|
+
@scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
285
|
+
end
|
333
286
|
|
334
|
-
|
335
|
-
|
336
|
-
end
|
287
|
+
erb(:scheduled)
|
288
|
+
end
|
337
289
|
|
338
|
-
|
339
|
-
|
340
|
-
q = Sidekiq::Metrics::Query.new
|
341
|
-
@period = h((params[:period] || "")[0..1])
|
342
|
-
@periods = METRICS_PERIODS
|
343
|
-
minutes = @periods.fetch(@period, @periods.values.first)
|
344
|
-
@query_result = q.top_jobs(minutes: minutes, class_filter: Regexp.new(Regexp.escape(x), Regexp::IGNORECASE))
|
290
|
+
get "/scheduled/:key" do
|
291
|
+
@job = Sidekiq::ScheduledSet.new.fetch(*parse_key(route_params(:key))).first
|
345
292
|
|
346
|
-
|
347
|
-
|
293
|
+
if @job.nil?
|
294
|
+
redirect "#{root_path}scheduled"
|
295
|
+
else
|
296
|
+
erb(:scheduled_job_info)
|
297
|
+
end
|
298
|
+
end
|
348
299
|
|
349
|
-
|
350
|
-
|
351
|
-
return redirect "#{root_path}retries" unless x && x != ""
|
300
|
+
post "/scheduled" do
|
301
|
+
redirect(request.path) unless url_params("key")
|
352
302
|
|
353
|
-
|
354
|
-
|
355
|
-
|
303
|
+
url_params("key").each do |key|
|
304
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
305
|
+
delete_or_add_queue job, request.params if job
|
306
|
+
end
|
356
307
|
|
357
|
-
|
358
|
-
|
359
|
-
return redirect "#{root_path}retries" unless x && x != ""
|
308
|
+
redirect_with_query("#{root_path}scheduled")
|
309
|
+
end
|
360
310
|
|
361
|
-
|
362
|
-
|
363
|
-
|
311
|
+
post "/scheduled/:key" do
|
312
|
+
key = route_params(:key)
|
313
|
+
halt(404) unless key
|
364
314
|
|
365
|
-
|
366
|
-
|
367
|
-
return redirect "#{root_path}scheduled" unless x && x != ""
|
315
|
+
job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
|
316
|
+
delete_or_add_queue job, request.params if job
|
368
317
|
|
369
|
-
|
370
|
-
|
371
|
-
end
|
318
|
+
redirect_with_query("#{root_path}scheduled")
|
319
|
+
end
|
372
320
|
|
373
|
-
|
374
|
-
|
375
|
-
|
321
|
+
get "/dashboard/stats" do
|
322
|
+
redirect "#{root_path}stats"
|
323
|
+
end
|
376
324
|
|
377
|
-
|
378
|
-
|
379
|
-
|
325
|
+
get "/stats" do
|
326
|
+
sidekiq_stats = Sidekiq::Stats.new
|
327
|
+
redis_stats = redis_info.slice(*REDIS_KEYS)
|
328
|
+
redis_stats["store_name"] = store_name
|
329
|
+
redis_stats["store_version"] = store_version
|
330
|
+
json(
|
331
|
+
sidekiq: {
|
332
|
+
processed: sidekiq_stats.processed,
|
333
|
+
failed: sidekiq_stats.failed,
|
334
|
+
busy: sidekiq_stats.workers_size,
|
335
|
+
processes: sidekiq_stats.processes_size,
|
336
|
+
enqueued: sidekiq_stats.enqueued,
|
337
|
+
scheduled: sidekiq_stats.scheduled_size,
|
338
|
+
retries: sidekiq_stats.retry_size,
|
339
|
+
dead: sidekiq_stats.dead_size,
|
340
|
+
default_latency: sidekiq_stats.default_queue_latency
|
341
|
+
},
|
342
|
+
redis: redis_stats,
|
343
|
+
server_utc_time: server_utc_time
|
344
|
+
)
|
345
|
+
end
|
380
346
|
|
381
|
-
|
382
|
-
|
383
|
-
|
347
|
+
get "/stats/queues" do
|
348
|
+
json Sidekiq::Stats.new.queues
|
349
|
+
end
|
384
350
|
|
385
|
-
|
386
|
-
|
387
|
-
|
351
|
+
get "/profiles" do
|
352
|
+
erb(:profiles)
|
353
|
+
end
|
388
354
|
|
389
|
-
|
390
|
-
|
391
|
-
|
355
|
+
get "/profiles/:key" do
|
356
|
+
store = config[:profile_store_url]
|
357
|
+
return redirect_to "#{root_path}profiles" unless store
|
358
|
+
|
359
|
+
key = route_params(:key)
|
360
|
+
sid = Sidekiq.redis { |c| c.hget(key, "sid") }
|
361
|
+
|
362
|
+
unless sid
|
363
|
+
require "net/http"
|
364
|
+
data = Sidekiq.redis { |c| c.hget(key, "data") }
|
365
|
+
|
366
|
+
store_uri = URI(store)
|
367
|
+
http = Net::HTTP.new(store_uri.host, store_uri.port)
|
368
|
+
http.use_ssl = store_uri.scheme == "https"
|
369
|
+
request = Net::HTTP::Post.new(store_uri.request_uri)
|
370
|
+
request.body = data
|
371
|
+
request["Accept"] = "application/vnd.firefox-profiler+json;version=1.0"
|
372
|
+
request["User-Agent"] = "Sidekiq #{Sidekiq::VERSION} job profiler"
|
373
|
+
|
374
|
+
resp = http.request(request)
|
375
|
+
# https://raw.githubusercontent.com/firefox-devtools/profiler-server/master/tools/decode_jwt_payload.py
|
376
|
+
rawjson = resp.body.split(".")[1].unpack1("m")
|
377
|
+
sid = Sidekiq.load_json(rawjson)["profileToken"]
|
378
|
+
Sidekiq.redis { |c| c.hset(key, "sid", sid) }
|
379
|
+
end
|
380
|
+
url = config[:profile_view_url] % sid
|
381
|
+
redirect_to url
|
382
|
+
end
|
392
383
|
|
393
|
-
|
394
|
-
|
395
|
-
|
384
|
+
get "/profiles/:key/data" do
|
385
|
+
key = route_params(:key)
|
386
|
+
data = Sidekiq.redis { |c| c.hget(key, "data") }
|
396
387
|
|
397
|
-
|
398
|
-
|
388
|
+
[200, {
|
389
|
+
"content-type" => "application/json",
|
390
|
+
"content-encoding" => "gzip",
|
391
|
+
# allow Firefox Profiler's XHR to fetch this profile data
|
392
|
+
"access-control-allow-origin" => "*"
|
393
|
+
}, [data]]
|
394
|
+
end
|
399
395
|
|
400
|
-
|
401
|
-
locale
|
402
|
-
}
|
396
|
+
post "/change_locale" do
|
397
|
+
locale = url_params("locale")
|
403
398
|
|
404
|
-
|
399
|
+
match = available_locales.find { |available|
|
400
|
+
locale == available
|
401
|
+
}
|
405
402
|
|
406
|
-
|
407
|
-
end
|
403
|
+
session[:locale] = match if match
|
408
404
|
|
409
|
-
|
410
|
-
|
411
|
-
return [404, {Rack::CONTENT_TYPE => "text/plain", Web::X_CASCADE => "pass"}, ["Not Found"]] unless action
|
405
|
+
reload_page
|
406
|
+
end
|
412
407
|
|
413
|
-
|
414
|
-
|
415
|
-
self.class.run_befores(app, action)
|
416
|
-
action.instance_exec env, &action.block
|
417
|
-
ensure
|
418
|
-
self.class.run_afters(app, action)
|
408
|
+
def redis(&)
|
409
|
+
Thread.current[:sidekiq_redis_pool].with(&)
|
419
410
|
end
|
420
411
|
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
else
|
426
|
-
# rendered content goes here
|
412
|
+
def call(env)
|
413
|
+
action = match(env)
|
414
|
+
return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
|
415
|
+
|
427
416
|
headers = {
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
417
|
+
"content-type" => "text/html",
|
418
|
+
"cache-control" => "private, no-store",
|
419
|
+
"content-language" => action.locale,
|
420
|
+
"content-security-policy" => process_csp(env, CSP_HEADER_TEMPLATE),
|
421
|
+
"x-content-type-options" => "nosniff"
|
432
422
|
}
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
end
|
423
|
+
env["response_headers"] = headers
|
424
|
+
resp = catch(:halt) do
|
425
|
+
Thread.current[:sidekiq_redis_pool] = env[:redis_pool]
|
426
|
+
action.instance_exec env, &action.block
|
427
|
+
ensure
|
428
|
+
Thread.current[:sidekiq_redis_pool] = nil
|
429
|
+
end
|
441
430
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
431
|
+
case resp
|
432
|
+
when Array
|
433
|
+
# redirects go here
|
434
|
+
resp
|
435
|
+
else
|
436
|
+
# rendered content goes here
|
437
|
+
# we'll let Rack calculate Content-Length for us.
|
438
|
+
[200, env["response_headers"], [resp]]
|
439
|
+
end
|
447
440
|
end
|
448
|
-
end
|
449
|
-
|
450
|
-
def self.before(path = nil, &block)
|
451
|
-
befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
|
452
|
-
end
|
453
|
-
|
454
|
-
def self.after(path = nil, &block)
|
455
|
-
afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
|
456
|
-
end
|
457
|
-
|
458
|
-
def self.run_befores(app, action)
|
459
|
-
run_hooks(befores, app, action)
|
460
|
-
end
|
461
|
-
|
462
|
-
def self.run_afters(app, action)
|
463
|
-
run_hooks(afters, app, action)
|
464
|
-
end
|
465
|
-
|
466
|
-
def self.run_hooks(hooks, app, action)
|
467
|
-
hooks.select { |p, _| !p || p =~ action.env[WebRouter::PATH_INFO] }
|
468
|
-
.each { |_, b| action.instance_exec(action.env, app, &b) }
|
469
|
-
end
|
470
441
|
|
471
|
-
|
472
|
-
|
473
|
-
|
442
|
+
def process_csp(env, input)
|
443
|
+
input.gsub("!placeholder!", env[:csp_nonce])
|
444
|
+
end
|
474
445
|
|
475
|
-
|
476
|
-
|
446
|
+
# Used by extensions to add helper methods accessible to
|
447
|
+
# any defined endpoints in Application. Careful with generic
|
448
|
+
# method naming as there's no namespacing so collisions are
|
449
|
+
# possible.
|
450
|
+
def self.helpers(mod)
|
451
|
+
Sidekiq::Web::Action.send(:include, mod)
|
452
|
+
end
|
453
|
+
helpers WebHelpers
|
454
|
+
helpers Paginator
|
477
455
|
end
|
478
456
|
end
|
479
457
|
end
|