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