sidekiq 5.2.7 → 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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +845 -8
  3. data/LICENSE.txt +9 -0
  4. data/README.md +54 -54
  5. data/bin/multi_queue_bench +271 -0
  6. data/bin/sidekiq +22 -3
  7. data/bin/sidekiqload +219 -112
  8. data/bin/sidekiqmon +11 -0
  9. data/bin/webload +69 -0
  10. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +120 -0
  11. data/lib/generators/sidekiq/job_generator.rb +59 -0
  12. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  13. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  14. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  15. data/lib/sidekiq/api.rb +757 -373
  16. data/lib/sidekiq/capsule.rb +132 -0
  17. data/lib/sidekiq/cli.rb +210 -233
  18. data/lib/sidekiq/client.rb +145 -103
  19. data/lib/sidekiq/component.rb +128 -0
  20. data/lib/sidekiq/config.rb +315 -0
  21. data/lib/sidekiq/deploy.rb +64 -0
  22. data/lib/sidekiq/embedded.rb +64 -0
  23. data/lib/sidekiq/fetch.rb +49 -42
  24. data/lib/sidekiq/iterable_job.rb +56 -0
  25. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  26. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  27. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  28. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  29. data/lib/sidekiq/job/iterable.rb +306 -0
  30. data/lib/sidekiq/job.rb +385 -0
  31. data/lib/sidekiq/job_logger.rb +34 -7
  32. data/lib/sidekiq/job_retry.rb +164 -109
  33. data/lib/sidekiq/job_util.rb +113 -0
  34. data/lib/sidekiq/launcher.rb +208 -107
  35. data/lib/sidekiq/logger.rb +80 -0
  36. data/lib/sidekiq/manager.rb +42 -46
  37. data/lib/sidekiq/metrics/query.rb +184 -0
  38. data/lib/sidekiq/metrics/shared.rb +109 -0
  39. data/lib/sidekiq/metrics/tracking.rb +150 -0
  40. data/lib/sidekiq/middleware/chain.rb +113 -56
  41. data/lib/sidekiq/middleware/current_attributes.rb +119 -0
  42. data/lib/sidekiq/middleware/i18n.rb +7 -7
  43. data/lib/sidekiq/middleware/modules.rb +23 -0
  44. data/lib/sidekiq/monitor.rb +147 -0
  45. data/lib/sidekiq/paginator.rb +41 -16
  46. data/lib/sidekiq/processor.rb +146 -127
  47. data/lib/sidekiq/profiler.rb +72 -0
  48. data/lib/sidekiq/rails.rb +46 -43
  49. data/lib/sidekiq/redis_client_adapter.rb +113 -0
  50. data/lib/sidekiq/redis_connection.rb +79 -108
  51. data/lib/sidekiq/ring_buffer.rb +31 -0
  52. data/lib/sidekiq/scheduled.rb +112 -50
  53. data/lib/sidekiq/sd_notify.rb +149 -0
  54. data/lib/sidekiq/systemd.rb +26 -0
  55. data/lib/sidekiq/testing/inline.rb +6 -5
  56. data/lib/sidekiq/testing.rb +91 -90
  57. data/lib/sidekiq/transaction_aware_client.rb +51 -0
  58. data/lib/sidekiq/version.rb +7 -1
  59. data/lib/sidekiq/web/action.rb +125 -60
  60. data/lib/sidekiq/web/application.rb +363 -259
  61. data/lib/sidekiq/web/config.rb +120 -0
  62. data/lib/sidekiq/web/csrf_protection.rb +183 -0
  63. data/lib/sidekiq/web/helpers.rb +241 -120
  64. data/lib/sidekiq/web/router.rb +62 -71
  65. data/lib/sidekiq/web.rb +69 -161
  66. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  67. data/lib/sidekiq.rb +94 -182
  68. data/sidekiq.gemspec +26 -16
  69. data/web/assets/images/apple-touch-icon.png +0 -0
  70. data/web/assets/javascripts/application.js +150 -61
  71. data/web/assets/javascripts/base-charts.js +120 -0
  72. data/web/assets/javascripts/chart.min.js +13 -0
  73. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  74. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  75. data/web/assets/javascripts/dashboard-charts.js +194 -0
  76. data/web/assets/javascripts/dashboard.js +41 -293
  77. data/web/assets/javascripts/metrics.js +280 -0
  78. data/web/assets/stylesheets/style.css +766 -0
  79. data/web/locales/ar.yml +72 -65
  80. data/web/locales/cs.yml +63 -62
  81. data/web/locales/da.yml +61 -53
  82. data/web/locales/de.yml +66 -53
  83. data/web/locales/el.yml +44 -24
  84. data/web/locales/en.yml +94 -66
  85. data/web/locales/es.yml +92 -54
  86. data/web/locales/fa.yml +66 -65
  87. data/web/locales/fr.yml +83 -62
  88. data/web/locales/gd.yml +99 -0
  89. data/web/locales/he.yml +66 -64
  90. data/web/locales/hi.yml +60 -59
  91. data/web/locales/it.yml +93 -54
  92. data/web/locales/ja.yml +75 -64
  93. data/web/locales/ko.yml +53 -52
  94. data/web/locales/lt.yml +84 -0
  95. data/web/locales/nb.yml +62 -61
  96. data/web/locales/nl.yml +53 -52
  97. data/web/locales/pl.yml +46 -45
  98. data/web/locales/{pt-br.yml → pt-BR.yml} +84 -56
  99. data/web/locales/pt.yml +52 -51
  100. data/web/locales/ru.yml +69 -63
  101. data/web/locales/sv.yml +54 -53
  102. data/web/locales/ta.yml +61 -60
  103. data/web/locales/tr.yml +101 -0
  104. data/web/locales/uk.yml +86 -61
  105. data/web/locales/ur.yml +65 -64
  106. data/web/locales/vi.yml +84 -0
  107. data/web/locales/zh-CN.yml +106 -0
  108. data/web/locales/{zh-tw.yml → zh-TW.yml} +43 -9
  109. data/web/views/_footer.erb +31 -19
  110. data/web/views/_job_info.erb +94 -75
  111. data/web/views/_metrics_period_select.erb +15 -0
  112. data/web/views/_nav.erb +14 -21
  113. data/web/views/_paging.erb +23 -19
  114. data/web/views/_poll_link.erb +3 -6
  115. data/web/views/_summary.erb +23 -23
  116. data/web/views/busy.erb +139 -87
  117. data/web/views/dashboard.erb +82 -53
  118. data/web/views/dead.erb +31 -27
  119. data/web/views/filtering.erb +6 -0
  120. data/web/views/layout.erb +15 -29
  121. data/web/views/metrics.erb +84 -0
  122. data/web/views/metrics_for_job.erb +58 -0
  123. data/web/views/morgue.erb +60 -70
  124. data/web/views/profiles.erb +43 -0
  125. data/web/views/queue.erb +50 -39
  126. data/web/views/queues.erb +45 -29
  127. data/web/views/retries.erb +65 -75
  128. data/web/views/retry.erb +32 -27
  129. data/web/views/scheduled.erb +58 -52
  130. data/web/views/scheduled_job_info.erb +1 -1
  131. metadata +96 -76
  132. data/.circleci/config.yml +0 -61
  133. data/.github/contributing.md +0 -32
  134. data/.github/issue_template.md +0 -11
  135. data/.gitignore +0 -15
  136. data/.travis.yml +0 -11
  137. data/3.0-Upgrade.md +0 -70
  138. data/4.0-Upgrade.md +0 -53
  139. data/5.0-Upgrade.md +0 -56
  140. data/COMM-LICENSE +0 -97
  141. data/Ent-Changes.md +0 -238
  142. data/Gemfile +0 -23
  143. data/LICENSE +0 -9
  144. data/Pro-2.0-Upgrade.md +0 -138
  145. data/Pro-3.0-Upgrade.md +0 -44
  146. data/Pro-4.0-Upgrade.md +0 -35
  147. data/Pro-Changes.md +0 -759
  148. data/Rakefile +0 -9
  149. data/bin/sidekiqctl +0 -20
  150. data/code_of_conduct.md +0 -50
  151. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  152. data/lib/sidekiq/core_ext.rb +0 -1
  153. data/lib/sidekiq/ctl.rb +0 -221
  154. data/lib/sidekiq/delay.rb +0 -42
  155. data/lib/sidekiq/exception_handler.rb +0 -29
  156. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  157. data/lib/sidekiq/extensions/active_record.rb +0 -40
  158. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  159. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  160. data/lib/sidekiq/logging.rb +0 -122
  161. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  162. data/lib/sidekiq/util.rb +0 -66
  163. data/lib/sidekiq/worker.rb +0 -220
  164. data/web/assets/stylesheets/application-rtl.css +0 -246
  165. data/web/assets/stylesheets/application.css +0 -1144
  166. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  167. data/web/assets/stylesheets/bootstrap.css +0 -5
  168. data/web/locales/zh-cn.yml +0 -68
  169. data/web/views/_status.erb +0 -4
@@ -1,353 +1,457 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sidekiq/paginator"
4
+ require "sidekiq/web/helpers"
5
+
3
6
  module Sidekiq
4
- class WebApplication
5
- extend WebRouter
6
-
7
- CONTENT_LENGTH = "Content-Length"
8
- CONTENT_TYPE = "Content-Type"
9
- REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
10
- CSP_HEADER = [
11
- "default-src 'self' https: http:",
12
- "child-src 'self'",
13
- "connect-src 'self' https: http: wss: ws:",
14
- "font-src 'self' https: http:",
15
- "frame-src 'self'",
16
- "img-src 'self' https: http: data:",
17
- "manifest-src 'self'",
18
- "media-src 'self'",
19
- "object-src 'none'",
20
- "script-src 'self' https: http: 'unsafe-inline'",
21
- "style-src 'self' https: http: 'unsafe-inline'",
22
- "worker-src 'self'",
23
- "base-uri 'self'"
24
- ].join('; ').freeze
25
-
26
- def initialize(klass)
27
- @klass = klass
28
- end
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
+ }
39
+
40
+ def initialize(inst)
41
+ @app = inst
42
+ end
29
43
 
30
- def settings
31
- @klass.settings
32
- 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
33
50
 
34
- def self.settings
35
- Sidekiq::Web.settings
36
- 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
37
55
 
38
- def self.tabs
39
- Sidekiq::Web.tabs
40
- end
56
+ stats_history = Sidekiq::Stats::History.new(days)
57
+ @processed_history = stats_history.processed
58
+ @failed_history = stats_history.failed
41
59
 
42
- def self.set(key, val)
43
- # nothing, backwards compatibility
44
- end
60
+ erb(:dashboard)
61
+ end
45
62
 
46
- get "/" do
47
- @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
48
- stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i)
49
- @processed_history = stats_history.processed
50
- @failed_history = stats_history.failed
63
+ get "/metrics" do
64
+ x = url_params("substr")
65
+ class_filter = (x.nil? || x == "") ? nil : Regexp.new(Regexp.escape(x), Regexp::IGNORECASE)
51
66
 
52
- erb(:dashboard)
53
- end
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))
54
72
 
55
- get "/busy" do
56
- erb(:busy)
57
- end
73
+ header "refresh", 60 if @period == "1h"
74
+ erb(:metrics)
75
+ end
58
76
 
59
- post "/busy" do
60
- if params['identity']
61
- p = Sidekiq::Process.new('identity' => params['identity'])
62
- p.quiet! if params['quiet']
63
- p.stop! if params['stop']
64
- else
65
- processes.each do |pro|
66
- pro.quiet! if params['quiet']
67
- pro.stop! if params['stop']
68
- end
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)
69
89
  end
70
90
 
71
- redirect "#{root_path}busy"
72
- end
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)
73
94
 
74
- get "/queues" do
75
- @queues = Sidekiq::Queue.all
95
+ erb(:busy)
96
+ end
76
97
 
77
- erb(:queues)
78
- end
98
+ post "/busy" do
99
+ if url_params("identity")
100
+ pro = Sidekiq::ProcessSet[url_params("identity")]
79
101
 
80
- get "/queues/:name" do
81
- @name = route_params[:name]
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?
82
107
 
83
- halt(404) unless @name
108
+ pro.quiet! if url_params("quiet")
109
+ pro.stop! if url_params("stop")
110
+ end
111
+ end
84
112
 
85
- @count = (params['count'] || 25).to_i
86
- @queue = Sidekiq::Queue.new(@name)
87
- (@current_page, @total_size, @messages) = page("queue:#{@name}", params['page'], @count)
88
- @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
113
+ redirect "#{root_path}busy"
114
+ end
89
115
 
90
- erb(:queue)
91
- end
116
+ get "/queues" do
117
+ @queues = Sidekiq::Queue.all
92
118
 
93
- post "/queues/:name" do
94
- Sidekiq::Queue.new(route_params[:name]).clear
119
+ erb(:queues)
120
+ end
95
121
 
96
- redirect "#{root_path}queues"
97
- end
122
+ QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
98
123
 
99
- post "/queues/:name/delete" do
100
- name = route_params[:name]
101
- Sidekiq::Job.new(params['key_val'], name).delete
124
+ get "/queues/:name" do
125
+ @name = route_params(:name)
102
126
 
103
- redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
104
- end
127
+ halt(404) if !@name || @name !~ QUEUE_NAME
105
128
 
106
- get '/morgue' do
107
- @count = (params['count'] || 25).to_i
108
- (@current_page, @total_size, @dead) = page("dead", params['page'], @count, reverse: true)
109
- @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
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) }
110
133
 
111
- erb(:morgue)
112
- end
134
+ erb(:queue)
135
+ end
113
136
 
114
- get "/morgue/:key" do
115
- halt(404) unless key = route_params[:key]
137
+ post "/queues/:name" do
138
+ queue = Sidekiq::Queue.new(route_params(:name))
116
139
 
117
- @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
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
118
147
 
119
- if @dead.nil?
120
- redirect "#{root_path}morgue"
121
- else
122
- erb(:dead)
148
+ redirect "#{root_path}queues"
123
149
  end
124
- end
125
150
 
126
- post '/morgue' do
127
- redirect(request.path) unless params['key']
151
+ post "/queues/:name/delete" do
152
+ name = route_params(:name)
153
+ Sidekiq::JobRecord.new(url_params("key_val"), name).delete
128
154
 
129
- params['key'].each do |key|
130
- job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
131
- retry_or_delete_or_kill job, params if job
155
+ redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
132
156
  end
133
157
 
134
- redirect_with_query("#{root_path}morgue")
135
- end
136
-
137
- post "/morgue/all/delete" do
138
- Sidekiq::DeadSet.new.clear
158
+ get "/morgue" do
159
+ x = url_params("substr")
139
160
 
140
- redirect "#{root_path}morgue"
141
- end
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
142
168
 
143
- post "/morgue/all/retry" do
144
- Sidekiq::DeadSet.new.retry_all
169
+ erb(:morgue)
170
+ end
145
171
 
146
- redirect "#{root_path}morgue"
147
- end
172
+ get "/morgue/:key" do
173
+ key = route_params(:key)
174
+ halt(404) unless key
148
175
 
149
- post "/morgue/:key" do
150
- halt(404) unless key = route_params[:key]
176
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
151
177
 
152
- job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
153
- retry_or_delete_or_kill job, params if job
178
+ if @dead.nil?
179
+ redirect "#{root_path}morgue"
180
+ else
181
+ erb(:dead)
182
+ end
183
+ end
154
184
 
155
- redirect_with_query("#{root_path}morgue")
156
- end
185
+ post "/morgue" do
186
+ redirect(request.path) unless url_params("key")
157
187
 
158
- get '/retries' do
159
- @count = (params['count'] || 25).to_i
160
- (@current_page, @total_size, @retries) = page("retry", params['page'], @count)
161
- @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
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
162
192
 
163
- erb(:retries)
164
- end
193
+ redirect_with_query("#{root_path}morgue")
194
+ end
165
195
 
166
- get "/retries/:key" do
167
- @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
196
+ post "/morgue/all/delete" do
197
+ Sidekiq::DeadSet.new.clear
168
198
 
169
- if @retry.nil?
170
- redirect "#{root_path}retries"
171
- else
172
- erb(:retry)
199
+ redirect "#{root_path}morgue"
173
200
  end
174
- end
175
201
 
176
- post '/retries' do
177
- redirect(request.path) unless params['key']
202
+ post "/morgue/all/retry" do
203
+ Sidekiq::DeadSet.new.retry_all
178
204
 
179
- params['key'].each do |key|
180
- job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
181
- retry_or_delete_or_kill job, params if job
205
+ redirect "#{root_path}morgue"
182
206
  end
183
207
 
184
- redirect_with_query("#{root_path}retries")
185
- end
208
+ post "/morgue/:key" do
209
+ key = route_params(:key)
210
+ halt(404) unless key
186
211
 
187
- post "/retries/all/delete" do
188
- Sidekiq::RetrySet.new.clear
212
+ job = Sidekiq::DeadSet.new.fetch(*parse_key(key)).first
213
+ retry_or_delete_or_kill job, request.params if job
189
214
 
190
- redirect "#{root_path}retries"
191
- end
215
+ redirect_with_query("#{root_path}morgue")
216
+ end
192
217
 
193
- post "/retries/all/retry" do
194
- Sidekiq::RetrySet.new.retry_all
218
+ get "/retries" do
219
+ x = url_params("substr")
195
220
 
196
- redirect "#{root_path}retries"
197
- end
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
198
228
 
199
- post "/retries/all/kill" do
200
- Sidekiq::RetrySet.new.kill_all
229
+ erb(:retries)
230
+ end
201
231
 
202
- redirect "#{root_path}retries"
203
- end
232
+ get "/retries/:key" do
233
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
204
234
 
205
- post "/retries/:key" do
206
- job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
235
+ if @retry.nil?
236
+ redirect "#{root_path}retries"
237
+ else
238
+ erb(:retry)
239
+ end
240
+ end
207
241
 
208
- retry_or_delete_or_kill job, params if job
242
+ post "/retries" do
243
+ redirect(request.path) unless url_params("key")
209
244
 
210
- redirect_with_query("#{root_path}retries")
211
- end
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
212
249
 
213
- get '/scheduled' do
214
- @count = (params['count'] || 25).to_i
215
- (@current_page, @total_size, @scheduled) = page("schedule", params['page'], @count)
216
- @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
250
+ redirect_with_query("#{root_path}retries")
251
+ end
217
252
 
218
- erb(:scheduled)
219
- end
253
+ post "/retries/all/delete" do
254
+ Sidekiq::RetrySet.new.clear
255
+ redirect "#{root_path}retries"
256
+ end
220
257
 
221
- get "/scheduled/:key" do
222
- @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
258
+ post "/retries/all/retry" do
259
+ Sidekiq::RetrySet.new.retry_all
260
+ redirect "#{root_path}retries"
261
+ end
223
262
 
224
- if @job.nil?
225
- redirect "#{root_path}scheduled"
226
- else
227
- erb(:scheduled_job_info)
263
+ post "/retries/all/kill" do
264
+ Sidekiq::RetrySet.new.kill_all
265
+ redirect "#{root_path}retries"
228
266
  end
229
- end
230
267
 
231
- post '/scheduled' do
232
- redirect(request.path) unless params['key']
268
+ post "/retries/:key" do
269
+ job = Sidekiq::RetrySet.new.fetch(*parse_key(route_params(:key))).first
270
+
271
+ retry_or_delete_or_kill job, request.params if job
233
272
 
234
- params['key'].each do |key|
235
- job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
236
- delete_or_add_queue job, params if job
273
+ redirect_with_query("#{root_path}retries")
237
274
  end
238
275
 
239
- redirect_with_query("#{root_path}scheduled")
240
- end
276
+ get "/scheduled" do
277
+ x = url_params("substr")
241
278
 
242
- post "/scheduled/:key" do
243
- halt(404) unless key = route_params[:key]
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
244
286
 
245
- job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
246
- delete_or_add_queue job, params if job
287
+ erb(:scheduled)
288
+ end
247
289
 
248
- redirect_with_query("#{root_path}scheduled")
249
- end
290
+ get "/scheduled/:key" do
291
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_key(route_params(:key))).first
250
292
 
251
- get '/dashboard/stats' do
252
- redirect "#{root_path}stats"
253
- end
293
+ if @job.nil?
294
+ redirect "#{root_path}scheduled"
295
+ else
296
+ erb(:scheduled_job_info)
297
+ end
298
+ end
254
299
 
255
- get '/stats' do
256
- sidekiq_stats = Sidekiq::Stats.new
257
- redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
258
- json(
259
- sidekiq: {
260
- processed: sidekiq_stats.processed,
261
- failed: sidekiq_stats.failed,
262
- busy: sidekiq_stats.workers_size,
263
- processes: sidekiq_stats.processes_size,
264
- enqueued: sidekiq_stats.enqueued,
265
- scheduled: sidekiq_stats.scheduled_size,
266
- retries: sidekiq_stats.retry_size,
267
- dead: sidekiq_stats.dead_size,
268
- default_latency: sidekiq_stats.default_queue_latency
269
- },
270
- redis: redis_stats,
271
- server_utc_time: server_utc_time
272
- )
273
- end
300
+ post "/scheduled" do
301
+ redirect(request.path) unless url_params("key")
274
302
 
275
- get '/stats/queues' do
276
- json Sidekiq::Stats::Queues.new.lengths
277
- end
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
278
307
 
279
- def call(env)
280
- action = self.class.match(env)
281
- return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass" }, ["Not Found"]] unless action
308
+ redirect_with_query("#{root_path}scheduled")
309
+ end
282
310
 
283
- resp = catch(:halt) do
284
- app = @klass
285
- self.class.run_befores(app, action)
286
- begin
287
- resp = action.instance_exec env, &action.block
288
- ensure
289
- self.class.run_afters(app, action)
290
- end
311
+ post "/scheduled/:key" do
312
+ key = route_params(:key)
313
+ halt(404) unless key
314
+
315
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_key(key)).first
316
+ delete_or_add_queue job, request.params if job
291
317
 
292
- resp
318
+ redirect_with_query("#{root_path}scheduled")
293
319
  end
294
320
 
295
- resp = case resp
296
- when Array
297
- resp
298
- else
299
- headers = {
300
- "Content-Type" => "text/html",
301
- "Cache-Control" => "no-cache",
302
- "Content-Language" => action.locale,
303
- "Content-Security-Policy" => CSP_HEADER
304
- }
321
+ get "/dashboard/stats" do
322
+ redirect "#{root_path}stats"
323
+ end
305
324
 
306
- [200, headers, [resp]]
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
+ )
307
345
  end
308
346
 
309
- resp[1] = resp[1].dup
347
+ get "/stats/queues" do
348
+ json Sidekiq::Stats.new.queues
349
+ end
310
350
 
311
- resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
351
+ get "/profiles" do
352
+ erb(:profiles)
353
+ end
312
354
 
313
- resp
314
- end
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
315
383
 
316
- def self.helpers(mod=nil, &block)
317
- if block_given?
318
- WebAction.class_eval(&block)
319
- else
320
- WebAction.send(:include, mod)
384
+ get "/profiles/:key/data" do
385
+ key = route_params(:key)
386
+ data = Sidekiq.redis { |c| c.hget(key, "data") }
387
+
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]]
321
394
  end
322
- end
323
395
 
324
- def self.before(path=nil, &block)
325
- befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
326
- end
396
+ post "/change_locale" do
397
+ locale = url_params("locale")
327
398
 
328
- def self.after(path=nil, &block)
329
- afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
330
- end
399
+ match = available_locales.find { |available|
400
+ locale == available
401
+ }
331
402
 
332
- def self.run_befores(app, action)
333
- run_hooks(befores, app, action)
334
- end
403
+ session[:locale] = match if match
335
404
 
336
- def self.run_afters(app, action)
337
- run_hooks(afters, app, action)
338
- end
405
+ reload_page
406
+ end
339
407
 
340
- def self.run_hooks(hooks, app, action)
341
- hooks.select { |p,_| !p || p =~ action.env[WebRouter::PATH_INFO] }.
342
- each {|_,b| action.instance_exec(action.env, app, &b) }
343
- end
408
+ def redis(&)
409
+ Thread.current[:sidekiq_redis_pool].with(&)
410
+ end
344
411
 
345
- def self.befores
346
- @befores ||= []
347
- end
412
+ def call(env)
413
+ action = match(env)
414
+ return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
415
+
416
+ headers = {
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"
422
+ }
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
348
430
 
349
- def self.afters
350
- @afters ||= []
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
440
+ end
441
+
442
+ def process_csp(env, input)
443
+ input.gsub("!placeholder!", env[:csp_nonce])
444
+ end
445
+
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
351
455
  end
352
456
  end
353
457
  end