sidekiq-throttled 1.5.2 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07f60ab84ab53725f6189d8a142ccb7d5596ea408a5d52087e2665420c92a276
4
- data.tar.gz: 489d3d9a5a72dc20af8295d6942d4abdc9a27dfffdf917b753a08af78184d59e
3
+ metadata.gz: dcec267e317bfaeabe891a21b776cbddc083cba27a163c82a5e5e0839390fb8d
4
+ data.tar.gz: '09706d9f91fdc770f01a5fb7835d8c35706c2852dfe798ec3e60dcd76bdeb8e9'
5
5
  SHA512:
6
- metadata.gz: bac5ad071e0de8913f80f8c78cec87ee98c61da77a48c46000ba3680f0eeb684b2970c69d256b61cf5d1b680121f2762a0ae3c9220729ee25140ba6cbf3f9598
7
- data.tar.gz: 92d08e2c0f76716816385b24df2c06092fa416ceb22714657967e2e312fe04357000db45fc18ee4f99d5eab57b6c267d93556a8064cb704478590b457ec48f2f
6
+ metadata.gz: 4c90a776bb731f2a140469a03092e8307997602d7723f73517ed804f0f3973cbfed9692baf8f0f98e47320e0b5c4316bbaa15ffc36c70637d68d553e9a537d25
7
+ data.tar.gz: e52c211802a6e740dcd8b1b77ef3d7cad8b39d6a5aa714b7f1619c9ec2391d893140a636562cb6bafdc326ce4bcbd42904bfe053e6410ceec215720d72100b4a
data/README.adoc CHANGED
@@ -79,6 +79,30 @@ class MyWorker
79
79
  end
80
80
  ----
81
81
 
82
+ === Requeue Strategy
83
+ The default requeue strategy `:enqueue` puts jobs immiediately back on the queue if they are being limited, with a potential `cooldown_period`. This is often an appropriate strategy but in some situations may cause the system to repeatedly dequeue and reenque the same job causing excessive redis CPU usage.
84
+
85
+ An alternative requeue strategy `:schedule` is available that schedules the work for the future. This can be less appropriate for highly concurrent jobs, but may be a good way to reduce redis load when the number of jobs to be processed is modest.
86
+
87
+ The alternative `:schedule` strategy may be configured globally with `config.default_requeue_options = { with: :schedule }`
88
+
89
+ It may be configured on a per job basis with:
90
+
91
+ ```ruby
92
+ sidekiq_throttle(
93
+ # Allow 5 jobs to processed within 1 minute, using the
94
+ threshold: { limit: 5, period: 1.minute, requeue: {with: :schedule} }
95
+ )
96
+ ```
97
+
98
+ It is also possible to requeue jobs to another queue:
99
+
100
+ ```ruby
101
+ sidekiq_throttle(
102
+ # Allow 5 jobs to processed within 1 minute, using the
103
+ threshold: { limit: 5, period: 1.minute, requeue: {to: :other_queue, with: :schedule} }
104
+ )
105
+ ```
82
106
 
83
107
  === Web UI
84
108
 
@@ -246,8 +270,8 @@ class MyJob
246
270
  sidekiq_throttle(
247
271
  # Allow maximum 10 concurrent jobs per project at a time and maximum 2 jobs per user
248
272
  concurrency: [
249
- { limit: 10, key_suffix: -> (project_id, user_id) { project_id } },
250
- { limit: 2, key_suffix: -> (project_id, user_id) { user_id } }
273
+ { limit: 10, key_suffix: -> (project_id, user_id) { "project_id:#{project_id}" } },
274
+ { limit: 2, key_suffix: -> (project_id, user_id) { "user_id:#{user_id}" } }
251
275
  ]
252
276
  # For :threshold it works the same
253
277
  )
@@ -309,16 +333,56 @@ lock TTL to fit your needs:
309
333
  sidekiq_throttle(concurrency: { limit: 20, ttl: 1.hour.to_i })
310
334
  ----
311
335
 
336
+ === Scheduling based concurrency tuning
337
+
338
+ The default concurrency throttling algorithm immediately requeues throttled
339
+ jobs. This can lead to a lot of wasted work picking up the same set of still
340
+ throttled jobs repeatedly. This churn also often starves lower priority
341
+ jobs/queues. The `:schedule` requeue strategy delays checking the runability of
342
+ throttled jobs until likely to be runnable. This future time is estimated based
343
+ on the expected runtime of the job and current number of throttled jobs. This
344
+ eliminates -- or greatly reduces -- the negative impacts to non-throttled job
345
+ types and queues and reduces wasted work constantly rechecking the same still
346
+ throttled jobs.
347
+
348
+ **Config items**
349
+
350
+ - `limit` — Maximum number of this job allowed to run simultaneously.
351
+ - `avg_job_duration` — Expected runtime (in seconds) for this job type.
352
+ Choose a value on the high end of what’s plausible; if you set this too low under heavy load, job scheduling will become sub-optimal.
353
+ - `lost_job_threshold` — Duration (in seconds) representing how long a job “owns” its concurrency slot before being considered lost.
354
+ - `ttl` — Deprecated alias for `lost_job_threshold`.
355
+ - `max_delay` — The maximum number of seconds to delay a job when it is throttled.
356
+ Prevents excessively long scheduling delays as the backlog grows.
357
+ _Default: the smaller of 30 minutes or `10 × avg_job_duration`._
358
+
359
+ [source,ruby]
360
+ ----
361
+ sidekiq_throttle(
362
+ concurrency: {
363
+ # only run 10 of this job at a time
364
+ limit: 10,
365
+ # these jobs finish in less than 30 seconds
366
+ avg_job_duration: 30,
367
+ # if it doesn't release its lease in 2 minutes it's considered lost
368
+ lost_job_threshold: 120,
369
+ # maximum delay allowed when throttled
370
+ max_delay: 300
371
+ },
372
+ # requeue using Sidekiq's scheduler
373
+ requeue: { with: :schedule }
374
+ )
375
+ ----
376
+
312
377
 
313
378
  == Supported Ruby Versions
314
379
 
315
380
  This library aims to support and is tested against the following Ruby versions:
316
381
 
317
- * Ruby 2.7.x
318
- * Ruby 3.0.x
319
- * Ruby 3.1.x
320
382
  * Ruby 3.2.x
321
383
  * Ruby 3.3.x
384
+ * Ruby 3.4.x
385
+ * Ruby 4.0.x
322
386
 
323
387
  If something doesn't work on one of these versions, it's a bug.
324
388
 
@@ -338,15 +402,13 @@ dropped.
338
402
 
339
403
  This library aims to support and work with following Sidekiq versions:
340
404
 
341
- * Sidekiq 7.0.x
342
- * Sidekiq 7.1.x
343
- * Sidekiq 7.2.x
405
+ * Sidekiq 8.0.x
406
+ * Sidekiq 8.1.x
344
407
 
345
- And the following Sidekiq Pro versions:
408
+ And (might work with) the following Sidekiq Pro versions:
346
409
 
347
- * Sidekiq Pro 7.0.x
348
- * Sidekiq Pro 7.1.x
349
- * Sidekiq Pro 7.2.x
410
+ * Sidekiq Pro 8.0.x
411
+ * Sidekiq Pro 8.1.x
350
412
 
351
413
  == Development
352
414
 
@@ -361,6 +423,7 @@ If you're working on Sidekiq-Pro support make sure that you have Sidekiq-Pro
361
423
  license set either in the global config, or in `BUNDLE_GEMS\__CONTRIBSYS__COM`
362
424
  environment variable.
363
425
 
426
+
364
427
  == Contributing
365
428
 
366
429
  * Fork sidekiq-throttled on GitHub
@@ -51,7 +51,7 @@ module Sidekiq
51
51
 
52
52
  # @!attribute [w] default_requeue_options
53
53
  def default_requeue_options=(options)
54
- requeue_with = options.delete(:with).intern || :enqueue
54
+ requeue_with = options.delete(:with)&.to_sym || :enqueue
55
55
 
56
56
  @default_requeue_options = options.merge({ with: requeue_with })
57
57
  end
@@ -88,8 +88,8 @@ module Sidekiq
88
88
  # @param [Hash] requeue What to do with jobs that are throttled
89
89
  # @see Registry.add
90
90
  # @return [void]
91
- def sidekiq_throttle(**kwargs)
92
- Registry.add(self, **kwargs)
91
+ def sidekiq_throttle(**)
92
+ Registry.add(self, **)
93
93
  end
94
94
 
95
95
  # Adds current worker to preconfigured throttling strategy. Allows
@@ -17,10 +17,10 @@ module Sidekiq
17
17
  #
18
18
  # @param (see Strategy#initialize)
19
19
  # @return [Strategy]
20
- def add(name, **kwargs)
20
+ def add(name, **)
21
21
  name = name.to_s
22
22
 
23
- @strategies[name] = Strategy.new(name, **kwargs)
23
+ @strategies[name] = Strategy.new(name, **)
24
24
  end
25
25
 
26
26
  # Adds alias for existing strategy.
@@ -14,7 +14,11 @@ module Sidekiq
14
14
  key = @base_key.dup
15
15
  return key unless @key_suffix
16
16
 
17
- key << ":#{@key_suffix.call(*job_args)}"
17
+ key << ":#{key_suffix(job_args)}"
18
+ end
19
+
20
+ def key_suffix(job_args)
21
+ @key_suffix.respond_to?(:call) ? @key_suffix.call(*job_args) : @key_suffix
18
22
  rescue StandardError => e
19
23
  Sidekiq.logger.error "Failed to get key suffix: #{e}"
20
24
  raise e
@@ -1,16 +1,61 @@
1
- local key = KEYS[1]
1
+ local in_progress_jobs_key = KEYS[1]
2
+ local backlog_info_key = KEYS[2]
2
3
  local jid = ARGV[1]
3
4
  local lmt = tonumber(ARGV[2])
4
- local ttl = tonumber(ARGV[3])
5
+ local lost_job_threshold = tonumber(ARGV[3])
5
6
  local now = tonumber(ARGV[4])
6
7
 
7
- redis.call("ZREMRANGEBYSCORE", key, "-inf", "(" .. now)
8
+ -- supporting functions
9
+ local function over_limit()
10
+ return lmt <= redis.call("ZCARD", in_progress_jobs_key)
11
+ end
12
+
13
+ local function job_already_in_progress()
14
+ return redis.call("ZSCORE", in_progress_jobs_key, jid)
15
+ end
16
+
17
+ -- Estimates current backlog size. This function tends to underestimate
18
+ -- the actual backlog. This is intentional. Overestimates are bad as it
19
+ -- can cause unnecessary delays in job processing. Underestimates are much
20
+ -- safer as they only increase workload of sidekiq processors.
21
+ local function est_current_backlog_size()
22
+ local old_size = tonumber(redis.call("HGET", backlog_info_key, "size")) or 0
23
+ local old_timestamp = tonumber(redis.call("HGET", backlog_info_key, "timestamp")) or now
24
+
25
+ local jobs_lost_since_old_timestamp = (now - old_timestamp) / lost_job_threshold * lmt
26
+
27
+ return math.max(old_size - jobs_lost_since_old_timestamp, 0)
28
+ end
29
+
30
+
31
+ local function change_backlog_size(delta)
32
+ local curr_backlog_size = est_current_backlog_size()
33
+
34
+ redis.call("HSET", backlog_info_key, "size", curr_backlog_size + delta)
35
+ redis.call("HSET", backlog_info_key, "timestamp", now)
36
+ redis.call("EXPIRE", backlog_info_key, math.ceil((lost_job_threshold * curr_backlog_size) + 1 / lmt))
37
+ end
38
+
39
+ local function register_job_in_progress()
40
+ redis.call("ZADD", in_progress_jobs_key, now + lost_job_threshold , jid)
41
+ redis.call("EXPIRE", in_progress_jobs_key, lost_job_threshold)
42
+ end
43
+
44
+ local function clear_stale_in_progress_jobs()
45
+ local cleared_count = redis.call("ZREMRANGEBYSCORE", in_progress_jobs_key, "-inf", "(" .. now)
46
+ change_backlog_size(-cleared_count)
47
+ end
48
+
49
+ -- END supporting functions
50
+
51
+ clear_stale_in_progress_jobs()
8
52
 
9
- if lmt <= redis.call("ZCARD", key) and not redis.call("ZSCORE", key, jid) then
53
+ if over_limit() and not job_already_in_progress() then
54
+ change_backlog_size(1)
10
55
  return 1
11
56
  end
12
57
 
13
- redis.call("ZADD", key, now + ttl, jid)
14
- redis.call("EXPIRE", key, ttl)
58
+ register_job_in_progress()
59
+ change_backlog_size(-1)
15
60
 
16
61
  return 0
@@ -26,13 +26,26 @@ module Sidekiq
26
26
  # @param [#to_s] strategy_key
27
27
  # @param [#to_i, #call] limit Amount of allowed concurrent jobs
28
28
  # per processors running for given key.
29
- # @param [#to_i] ttl Concurrency lock TTL in seconds.
29
+ # @param [#to_i] avg_job_duration Average number of seconds needed
30
+ # to complete a job of this type. Default: 300 or 1/3 of lost_job_threshold
31
+ # @param [#to_i] lost_job_threshold Seconds to wait before considering
32
+ # a job lost or dead. Default: 900 or 3 * avg_job_duration
30
33
  # @param [Proc] key_suffix Dynamic key suffix generator.
31
- def initialize(strategy_key, limit:, ttl: 900, key_suffix: nil)
32
- @base_key = "#{strategy_key}:concurrency.v2"
33
- @limit = limit
34
- @ttl = ttl.to_i
34
+ # @param [#to_i] max_delay Maximum number of seconds to delay a job when it
35
+ # throttled. This prevents jobs from being schedule very far in the future
36
+ # when the backlog is large. Default: the smaller of 30 minutes or 10 * avg_job_duration
37
+ # @deprecated @param [#to_i] ttl Obsolete alias for `lost_job_threshold`.
38
+ # Default: 900 or 3 * avg_job_duration
39
+ def initialize(strategy_key, limit:, avg_job_duration: nil, ttl: nil, # rubocop:disable Metrics/ParameterLists
40
+ lost_job_threshold: ttl, key_suffix: nil, max_delay: nil)
41
+ @base_key = "#{strategy_key}:concurrency.v2"
42
+ @limit = limit
43
+ @avg_job_duration, @lost_job_threshold = interp_duration_args(avg_job_duration, lost_job_threshold)
35
44
  @key_suffix = key_suffix
45
+ @max_delay = max_delay || [(10 * @avg_job_duration), 1_800].min
46
+
47
+ raise(ArgumentError, "lost_job_threshold must be greater than avg_job_duration") if
48
+ @lost_job_threshold <= @avg_job_duration
36
49
  end
37
50
 
38
51
  # @return [Boolean] Whenever strategy has dynamic config
@@ -46,8 +59,8 @@ module Sidekiq
46
59
  return false unless job_limit
47
60
  return true if job_limit <= 0
48
61
 
49
- keys = [key(job_args)]
50
- argv = [jid.to_s, job_limit, @ttl, Time.now.to_f]
62
+ keys = [key(job_args), backlog_info_key(job_args)]
63
+ argv = [jid.to_s, job_limit, @lost_job_threshold, Time.now.to_f]
51
64
 
52
65
  Sidekiq.redis { |redis| 1 == SCRIPT.call(redis, keys: keys, argv: argv) }
53
66
  end
@@ -57,9 +70,8 @@ module Sidekiq
57
70
  job_limit = limit(job_args)
58
71
  return 0.0 if !job_limit || count(*job_args) < job_limit
59
72
 
60
- oldest_jid_with_score = Sidekiq.redis { |redis| redis.zrange(key(job_args), 0, 0, withscores: true) }.first
61
- expiry_time = oldest_jid_with_score.last.to_f
62
- expiry_time - Time.now.to_f
73
+ (estimated_backlog_size(job_args) * @avg_job_duration / limit(job_args))
74
+ .then { |delay_sec| @max_delay * (1 - Math.exp(-delay_sec / @max_delay)) } # limit to max_delay
63
75
  end
64
76
 
65
77
  # @return [Integer] Current count of jobs
@@ -76,7 +88,40 @@ module Sidekiq
76
88
  # Remove jid from the pool of jobs in progress
77
89
  # @return [void]
78
90
  def finalize!(jid, *job_args)
79
- Sidekiq.redis { |conn| conn.zrem(key(job_args), jid.to_s) }
91
+ Sidekiq.redis do |conn|
92
+ conn.zrem(key(job_args), jid.to_s)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def backlog_info_key(job_args)
99
+ "#{key(job_args)}.backlog_info"
100
+ end
101
+
102
+ def estimated_backlog_size(job_args)
103
+ old_size_str, old_timestamp_str =
104
+ Sidekiq.redis { |conn| conn.hmget(backlog_info_key(job_args), "size", "timestamp") }
105
+ old_size = (old_size_str || 0).to_f
106
+ old_timestamp = (old_timestamp_str || Time.now).to_f
107
+
108
+ (old_size - jobs_lost_since(old_timestamp, job_args)).clamp(0, Float::INFINITY)
109
+ end
110
+
111
+ def jobs_lost_since(timestamp, job_args)
112
+ (Time.now.to_f - timestamp) / @lost_job_threshold * limit(job_args)
113
+ end
114
+
115
+ def interp_duration_args(avg_job_duration, lost_job_threshold)
116
+ if avg_job_duration && lost_job_threshold
117
+ [avg_job_duration.to_i, lost_job_threshold.to_i]
118
+ elsif avg_job_duration && lost_job_threshold.nil?
119
+ [avg_job_duration.to_i, avg_job_duration.to_i * 3]
120
+ elsif avg_job_duration.nil? && lost_job_threshold
121
+ [lost_job_threshold.to_i / 3, lost_job_threshold.to_i]
122
+ else
123
+ [300, 900]
124
+ end
80
125
  end
81
126
  end
82
127
  end
@@ -3,6 +3,6 @@
3
3
  module Sidekiq
4
4
  module Throttled
5
5
  # Gem version
6
- VERSION = "1.5.2"
6
+ VERSION = "2.1.0"
7
7
  end
8
8
  end
@@ -62,12 +62,13 @@ module Sidekiq
62
62
 
63
63
  # @return [String]
64
64
  def humanize_integer(int)
65
- digits = int.to_s.chars
66
- str = digits.shift(digits.count % 3).join
67
-
68
- str << " " << digits.shift(3).join while digits.count.positive?
69
-
70
- str.strip
65
+ int.to_s.chars
66
+ .reverse
67
+ .each_slice(3)
68
+ .map(&:reverse)
69
+ .reverse
70
+ .map(&:join)
71
+ .join(",")
71
72
  end
72
73
  end
73
74
  end
@@ -1,43 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # stdlib
4
3
  require "pathname"
5
4
 
6
- # 3rd party
7
5
  require "sidekiq"
8
6
  require "sidekiq/web"
9
7
 
10
- # internal
11
8
  require_relative "./registry"
12
9
  require_relative "./web/stats"
13
10
 
14
11
  module Sidekiq
15
12
  module Throttled
16
- # Provides Sidekiq tab to monitor and reset throttled stats.
17
13
  module Web
18
- VIEWS = Pathname.new(__dir__).join("web")
19
- THROTTLED_TPL = VIEWS.join("throttled.html.erb").read.freeze
14
+ ROOT = Pathname.new(__dir__).join("../../../web").expand_path.realpath.freeze
15
+ VIEWS = ROOT.join("views").freeze
20
16
 
21
- class << self
22
- # @api private
23
- def registered(app)
24
- register_throttled_tab app
17
+ def self.registered(app)
18
+ app.get("/throttled") do
19
+ erb :index, views: VIEWS
25
20
  end
26
21
 
27
- private
28
-
29
- def register_throttled_tab(app)
30
- app.get("/throttled") { erb THROTTLED_TPL.dup }
31
-
32
- app.post("/throttled/:id/reset") do
33
- Registry.get(params[:id], &:reset!)
34
- redirect "#{root_path}throttled"
35
- end
22
+ app.post("/throttled/:id/reset") do
23
+ Registry.get(route_params(:id), &:reset!)
24
+ redirect "#{root_path}throttled"
36
25
  end
37
26
  end
38
27
  end
39
28
  end
40
29
  end
41
30
 
42
- Sidekiq::Web.register Sidekiq::Throttled::Web
43
- Sidekiq::Web.tabs["Throttled"] = "throttled"
31
+ Sidekiq::Web.configure do |config|
32
+ config.register_extension(
33
+ Sidekiq::Throttled::Web,
34
+ name: "throttled",
35
+ tab: %w[Throttled],
36
+ index: %w[throttled],
37
+ root_dir: Sidekiq::Throttled::Web::ROOT.to_s
38
+ )
39
+ end
@@ -0,0 +1,41 @@
1
+ <section>
2
+ <header>
3
+ <h1>Throttled</h1>
4
+ </header>
5
+
6
+ <div class="table_container">
7
+ <table class="throttled">
8
+ <thead>
9
+ <tr>
10
+ <th>Name</th>
11
+ <th>Concurrency</th>
12
+ <th>Threshold</th>
13
+ <th>Actions</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <% Sidekiq::Throttled::Registry.each_with_static_keys do |name, strategy| %>
18
+ <tr>
19
+ <td style="vertical-align:middle;"><%= name %></td>
20
+ <td style="vertical-align:middle;text-align:center;">
21
+ <% strategy.concurrency.each do |concurrency| %>
22
+ <%= Sidekiq::Throttled::Web::Stats.new(concurrency).to_html %>
23
+ <% end %>
24
+ </td>
25
+ <td style="vertical-align:middle;text-align:center;">
26
+ <% strategy.threshold.each do |threshold| %>
27
+ <%= Sidekiq::Throttled::Web::Stats.new(threshold).to_html %>
28
+ <% end %>
29
+ </td>
30
+ <td style="vertical-align:middle;text-align:center;">
31
+ <form action="<%= root_path %>throttled/<%= CGI.escape name %>/reset" method="post">
32
+ <%= csrf_tag %>
33
+ <button class="btn btn-danger" type="submit">Reset</button>
34
+ </form>
35
+ </td>
36
+ </tr>
37
+ <% end %>
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+ </section>
@@ -0,0 +1 @@
1
+ index.erb
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-throttled
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Zapparov
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-01-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: concurrent-ruby
@@ -44,15 +43,14 @@ dependencies:
44
43
  requirements:
45
44
  - - ">="
46
45
  - !ruby/object:Gem::Version
47
- version: '6.5'
46
+ version: '8.0'
48
47
  type: :runtime
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
- version: '6.5'
55
- description:
53
+ version: '8.0'
56
54
  email:
57
55
  - alexey@zapparov.com
58
56
  executables: []
@@ -83,18 +81,18 @@ files:
83
81
  - lib/sidekiq/throttled/version.rb
84
82
  - lib/sidekiq/throttled/web.rb
85
83
  - lib/sidekiq/throttled/web/stats.rb
86
- - lib/sidekiq/throttled/web/throttled.html.erb
87
84
  - lib/sidekiq/throttled/worker.rb
85
+ - web/views/index.erb
86
+ - web/views/index.html.erb
88
87
  homepage: https://github.com/ixti/sidekiq-throttled
89
88
  licenses:
90
89
  - MIT
91
90
  metadata:
92
91
  homepage_uri: https://github.com/ixti/sidekiq-throttled
93
- source_code_uri: https://github.com/ixti/sidekiq-throttled/tree/v1.5.2
92
+ source_code_uri: https://github.com/ixti/sidekiq-throttled/tree/v2.1.0
94
93
  bug_tracker_uri: https://github.com/ixti/sidekiq-throttled/issues
95
- changelog_uri: https://github.com/ixti/sidekiq-throttled/blob/v1.5.2/CHANGES.md
94
+ changelog_uri: https://github.com/ixti/sidekiq-throttled/blob/v2.1.0/CHANGES.md
96
95
  rubygems_mfa_required: 'true'
97
- post_install_message:
98
96
  rdoc_options: []
99
97
  require_paths:
100
98
  - lib
@@ -102,15 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
102
100
  requirements:
103
101
  - - ">="
104
102
  - !ruby/object:Gem::Version
105
- version: '2.7'
103
+ version: '3.2'
106
104
  required_rubygems_version: !ruby/object:Gem::Requirement
107
105
  requirements:
108
106
  - - ">="
109
107
  - !ruby/object:Gem::Version
110
108
  version: '0'
111
109
  requirements: []
112
- rubygems_version: 3.5.22
113
- signing_key:
110
+ rubygems_version: 3.6.9
114
111
  specification_version: 4
115
112
  summary: Concurrency and rate-limit throttling for Sidekiq
116
113
  test_files: []
@@ -1,35 +0,0 @@
1
- <h3>Throttled</h3>
2
-
3
- <div class="table_container">
4
- <table class="table table-hover table-bordered table-striped table-white">
5
- <thead>
6
- <tr>
7
- <th>Name</th>
8
- <th style="text-align:center;">Concurrency</th>
9
- <th style="text-align:center;">Threshold</th>
10
- <th style="text-align:center;">Actions</th>
11
- </tr>
12
- </thead>
13
- <% Sidekiq::Throttled::Registry.each_with_static_keys do |name, strategy| %>
14
- <tr>
15
- <td style="vertical-align:middle;"><%= name %></td>
16
- <td style="vertical-align:middle;text-align:center;">
17
- <% strategy.concurrency.each do |concurrency| %>
18
- <%= Sidekiq::Throttled::Web::Stats.new(concurrency).to_html %>
19
- <% end %>
20
- </td>
21
- <td style="vertical-align:middle;text-align:center;">
22
- <% strategy.threshold.each do |threshold| %>
23
- <%= Sidekiq::Throttled::Web::Stats.new(threshold).to_html %>
24
- <% end %>
25
- </td>
26
- <td style="vertical-align:middle;text-align:center;">
27
- <form action="<%= root_path %>throttled/<%= CGI.escape name %>/reset" method="post">
28
- <%= csrf_tag %>
29
- <button class="btn btn-danger" type="submit">Reset</button>
30
- </form>
31
- </td>
32
- </tr>
33
- <% end %>
34
- </table>
35
- </div>