sidekiq-heroku-scaler 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66b803338f9b37da0c0d1d34ba5a8ed1956fc26b145364870749f5946888bca0
4
- data.tar.gz: a31471faa9c1fd25fcd67633dbca9fbb91dde764e726a9e894d15dc0cf314465
3
+ metadata.gz: 6073af100f49fa9fab13998d784522a9f29012da96c72837fb0fb4f9f66e8c8c
4
+ data.tar.gz: fbe8bb6c5e2d34c01e2984eadec3ec35c99f4f3292e8a553a41c8b3fba8e8fd9
5
5
  SHA512:
6
- metadata.gz: 8464385f63357cb61ff63c6b3c62298b5c7668b745cc8461735ccaa0dcdaf3e2afba7624845cc22dabb18c2801d3f6ba187d45787423fa01eb6b4657e219a1de
7
- data.tar.gz: 1657cc427f1b20cb0b1bb5d1ace58da4c2a12a622821000d5141375fffc7b6fb40cf6a097887f0cec0bc744de45a5edff6ccbcb3d966ec69d6deec4233d78941
6
+ metadata.gz: ba8a451c48eb108b933ee1efa44d273338032d904aa5f2e578257539ecc2820c67277d54ff74ea9ee57712bf4d0e377761bb62b873f09806475b97c8979ae71a
7
+ data.tar.gz: 680fba960076c21a633ace3a088a29045e616c1b96ff24619ceb02747885f7b30cf290bf46909787ad1ae30937de35d62edfcc6ae20e0fd2fd44c54ad49d504f
data/.rubocop.yml CHANGED
@@ -1,9 +1,23 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+ - rubocop-factory_bot
1
6
  AllCops:
2
7
  NewCops: enable
3
- TargetRubyVersion: 2.7
8
+ TargetRubyVersion: 3.0
4
9
  Exclude:
5
10
  - 'bin/*'
6
- Documentation:
7
- Enabled: false
8
- Metrics/LineLength:
11
+ Layout/LineLength:
9
12
  Max: 120
13
+ Style/Documentation:
14
+ Enabled: false
15
+ Style/OpenStructUse:
16
+ Enabled: false
17
+ Naming/FileName:
18
+ Exclude:
19
+ - 'lib/sidekiq-heroku-scaler.rb'
20
+ RSpec/NamedSubject:
21
+ Enabled: false
22
+ RSpec/NestedGroups:
23
+ Max: 8
data/Gemfile CHANGED
@@ -5,3 +5,14 @@ source 'https://rubygems.org'
5
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
7
  gemspec
8
+
9
+ gem 'bundler'
10
+
11
+ gem 'rake'
12
+
13
+ gem 'rspec'
14
+
15
+ gem 'rubocop'
16
+ gem 'rubocop-performance'
17
+ gem 'rubocop-rake'
18
+ gem 'rubocop-rspec'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sidekiq-heroku-scaler (0.3.5)
4
+ sidekiq-heroku-scaler (0.4.0)
5
5
  activesupport (> 5, < 8)
6
6
  platform-api (> 3, < 4)
7
7
  sidekiq (> 4, < 7)
@@ -9,77 +9,96 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- activesupport (7.0.4)
12
+ activesupport (7.0.7.2)
13
13
  concurrent-ruby (~> 1.0, >= 1.0.2)
14
14
  i18n (>= 1.6, < 2)
15
15
  minitest (>= 5.1)
16
16
  tzinfo (~> 2.0)
17
17
  ast (2.4.2)
18
- concurrent-ruby (1.1.10)
19
- connection_pool (2.3.0)
18
+ base64 (0.1.1)
19
+ concurrent-ruby (1.2.2)
20
+ connection_pool (2.4.1)
20
21
  diff-lcs (1.5.0)
21
22
  erubis (2.7.0)
22
- excon (0.92.5)
23
+ excon (0.102.0)
23
24
  heroics (0.1.2)
24
25
  erubis (~> 2.0)
25
26
  excon
26
27
  moneta
27
28
  multi_json (>= 1.9.2)
28
29
  webrick
29
- i18n (1.12.0)
30
+ i18n (1.14.1)
30
31
  concurrent-ruby (~> 1.0)
31
- json (2.6.2)
32
- minitest (5.16.3)
32
+ json (2.6.3)
33
+ language_server-protocol (3.17.0.3)
34
+ minitest (5.19.0)
33
35
  moneta (1.0.0)
34
36
  multi_json (1.15.0)
35
- parallel (1.22.1)
36
- parser (3.1.2.1)
37
+ parallel (1.23.0)
38
+ parser (3.2.2.3)
37
39
  ast (~> 2.4.1)
40
+ racc
38
41
  platform-api (3.5.0)
39
42
  heroics (~> 0.1.1)
40
43
  moneta (~> 1.0.0)
41
44
  rate_throttle_client (~> 0.1.0)
42
- rack (2.2.4)
45
+ racc (1.7.1)
46
+ rack (2.2.8)
43
47
  rainbow (3.1.1)
44
48
  rake (13.0.6)
45
49
  rate_throttle_client (0.1.2)
46
- redis (4.8.0)
47
- regexp_parser (2.5.0)
48
- rexml (3.2.5)
49
- rspec (3.11.0)
50
- rspec-core (~> 3.11.0)
51
- rspec-expectations (~> 3.11.0)
52
- rspec-mocks (~> 3.11.0)
53
- rspec-core (3.11.0)
54
- rspec-support (~> 3.11.0)
55
- rspec-expectations (3.11.1)
50
+ redis (4.8.1)
51
+ regexp_parser (2.8.1)
52
+ rexml (3.2.6)
53
+ rspec (3.12.0)
54
+ rspec-core (~> 3.12.0)
55
+ rspec-expectations (~> 3.12.0)
56
+ rspec-mocks (~> 3.12.0)
57
+ rspec-core (3.12.2)
58
+ rspec-support (~> 3.12.0)
59
+ rspec-expectations (3.12.3)
56
60
  diff-lcs (>= 1.2.0, < 2.0)
57
- rspec-support (~> 3.11.0)
58
- rspec-mocks (3.11.1)
61
+ rspec-support (~> 3.12.0)
62
+ rspec-mocks (3.12.6)
59
63
  diff-lcs (>= 1.2.0, < 2.0)
60
- rspec-support (~> 3.11.0)
61
- rspec-support (3.11.1)
62
- rubocop (1.36.0)
64
+ rspec-support (~> 3.12.0)
65
+ rspec-support (3.12.1)
66
+ rubocop (1.56.1)
67
+ base64 (~> 0.1.1)
63
68
  json (~> 2.3)
69
+ language_server-protocol (>= 3.17.0)
64
70
  parallel (~> 1.10)
65
- parser (>= 3.1.2.1)
71
+ parser (>= 3.2.2.3)
66
72
  rainbow (>= 2.2.2, < 4.0)
67
73
  regexp_parser (>= 1.8, < 3.0)
68
74
  rexml (>= 3.2.5, < 4.0)
69
- rubocop-ast (>= 1.20.1, < 2.0)
75
+ rubocop-ast (>= 1.28.1, < 2.0)
70
76
  ruby-progressbar (~> 1.7)
71
- unicode-display_width (>= 1.4.0, < 3.0)
72
- rubocop-ast (1.21.0)
73
- parser (>= 3.1.1.0)
74
- ruby-progressbar (1.11.0)
75
- sidekiq (6.5.7)
76
- connection_pool (>= 2.2.5)
77
+ unicode-display_width (>= 2.4.0, < 3.0)
78
+ rubocop-ast (1.29.0)
79
+ parser (>= 3.2.1.0)
80
+ rubocop-capybara (2.18.0)
81
+ rubocop (~> 1.41)
82
+ rubocop-factory_bot (2.23.1)
83
+ rubocop (~> 1.33)
84
+ rubocop-performance (1.19.0)
85
+ rubocop (>= 1.7.0, < 2.0)
86
+ rubocop-ast (>= 0.4.0)
87
+ rubocop-rake (0.6.0)
88
+ rubocop (~> 1.0)
89
+ rubocop-rspec (2.23.2)
90
+ rubocop (~> 1.33)
91
+ rubocop-capybara (~> 2.17)
92
+ rubocop-factory_bot (~> 2.22)
93
+ ruby-progressbar (1.13.0)
94
+ sidekiq (6.5.9)
95
+ connection_pool (>= 2.2.5, < 3)
77
96
  rack (~> 2.0)
78
97
  redis (>= 4.5.0, < 5)
79
- tzinfo (2.0.5)
98
+ tzinfo (2.0.6)
80
99
  concurrent-ruby (~> 1.0)
81
- unicode-display_width (2.3.0)
82
- webrick (1.7.0)
100
+ unicode-display_width (2.4.2)
101
+ webrick (1.8.1)
83
102
 
84
103
  PLATFORMS
85
104
  ruby
@@ -89,7 +108,10 @@ DEPENDENCIES
89
108
  rake
90
109
  rspec
91
110
  rubocop
111
+ rubocop-performance
112
+ rubocop-rake
113
+ rubocop-rspec
92
114
  sidekiq-heroku-scaler!
93
115
 
94
116
  BUNDLED WITH
95
- 2.2.32
117
+ 2.4.10
data/README.md CHANGED
@@ -30,7 +30,8 @@ scale_strategy = SidekiqHerokuScaler::Strategy::Latency.new(
30
30
  max_latency: 5.minutes.to_i,
31
31
  min_latency: 1.minute.to_i,
32
32
  inc_count: 2, # default 1
33
- dec_count: 2 # default 1
33
+ dec_count: 2, # default 1
34
+ smart_decrease: true # default false
34
35
  )
35
36
  ```
36
37
  or
@@ -22,7 +22,7 @@ module SidekiqHerokuScaler
22
22
  end
23
23
 
24
24
  def sidekiq_workers
25
- @sidekiq_workers ||= formations.select { |formation| formation['command'].match(/sidekiq/) }
25
+ @sidekiq_workers ||= formations.select { |formation| formation['command'].include?('sidekiq') }
26
26
  .map { |formation| formation['type'] }
27
27
  end
28
28
 
@@ -12,13 +12,19 @@ module SidekiqHerokuScaler
12
12
  end
13
13
 
14
14
  def perform
15
- autoscalable_workers.each(&method(:autoscale_one))
15
+ autoscalable_workers.each do |worker_name|
16
+ autoscale_one(worker_name)
17
+ end
16
18
  end
17
19
 
18
20
  private
19
21
 
20
22
  attr_reader :heroku_client, :strategy, :workers
21
23
 
24
+ def autoscalable_workers
25
+ heroku_client.sidekiq_workers & workers
26
+ end
27
+
22
28
  def autoscale_one(worker_name)
23
29
  formation = heroku_client.formation_for(worker_name)
24
30
  return if formation.blank?
@@ -28,18 +34,33 @@ module SidekiqHerokuScaler
28
34
  process_formation(sidekiq_worker)
29
35
  end
30
36
 
37
+ def decrease_delta_for(sidekiq_worker, delta)
38
+ return delta unless strategy.smart_decrease
39
+
40
+ processes = sidekiq_worker.processes.reverse.take_while do |process|
41
+ process['busy'].zero? && process['quiet'] == 'false'
42
+ end
43
+
44
+ processes.size < delta.abs ? -processes.size : delta
45
+ end
46
+
31
47
  def process_formation(sidekiq_worker)
32
48
  if strategy.increase?(sidekiq_worker)
33
- heroku_client.update_formation(sidekiq_worker.formation_id,
34
- strategy.safe_quantity(sidekiq_worker.quantity + strategy.inc_count))
49
+ update_formation(sidekiq_worker, strategy.inc_count)
35
50
  elsif strategy.decrease?(sidekiq_worker)
36
- heroku_client.update_formation(sidekiq_worker.formation_id,
37
- strategy.safe_quantity(sidekiq_worker.quantity - strategy.dec_count))
51
+ stop_sidekiq_workers(sidekiq_worker, -strategy.dec_count)
38
52
  end
39
53
  end
40
54
 
41
- def autoscalable_workers
42
- heroku_client.sidekiq_workers & workers
55
+ def stop_sidekiq_workers(sidekiq_worker, delta)
56
+ delta = decrease_delta_for(sidekiq_worker, delta)
57
+
58
+ update_formation(sidekiq_worker, delta)
59
+ end
60
+
61
+ def update_formation(sidekiq_worker, delta)
62
+ heroku_client.update_formation(sidekiq_worker.formation_id,
63
+ strategy.safe_quantity(sidekiq_worker.quantity + delta))
43
64
  end
44
65
  end
45
66
  end
@@ -27,7 +27,7 @@ module SidekiqHerokuScaler
27
27
  end
28
28
  end
29
29
 
30
- def option_parser(opts)
30
+ def option_parser(opts) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
31
31
  OptionParser.new do |o|
32
32
  o.on '-c', '--concurrency INT', 'processor threads to use' do |arg|
33
33
  opts[:concurrency] = Integer(arg)
@@ -52,7 +52,7 @@ module SidekiqHerokuScaler
52
52
  end
53
53
  end
54
54
 
55
- def parse_config(path)
55
+ def parse_config(path) # rubocop:disable Metrics/MethodLength
56
56
  erb = ERB.new(File.read(path))
57
57
  erb.filename = File.expand_path(path)
58
58
  opts = load_yaml(erb.result) || {}
@@ -90,7 +90,7 @@ module SidekiqHerokuScaler
90
90
  opts
91
91
  end
92
92
 
93
- def setup_options(args)
93
+ def setup_options(args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
94
94
  # parse CLI options
95
95
  opts = parse_options(args)
96
96
 
@@ -3,24 +3,27 @@
3
3
  module SidekiqHerokuScaler
4
4
  module Strategy
5
5
  class Latency
6
- attr_reader :inc_count, :dec_count
6
+ attr_reader :inc_count, :dec_count, :smart_decrease
7
7
 
8
+ # rubocop:disable Metrics/ParameterLists
8
9
  def initialize(min_dynos_count:, max_dynos_count:,
9
10
  max_latency:, min_latency:,
10
- inc_count: nil, dec_count: nil)
11
+ inc_count: nil, dec_count: nil,
12
+ smart_decrease: false)
11
13
  @min_dynos_count = min_dynos_count
12
14
  @max_dynos_count = max_dynos_count
13
15
  @max_latency = max_latency
14
16
  @min_latency = min_latency
15
17
  @inc_count = (inc_count || 1).to_i
16
18
  @dec_count = (dec_count || 1).to_i
19
+ @smart_decrease = smart_decrease
17
20
  end
21
+ # rubocop:enable Metrics/ParameterLists
18
22
 
19
23
  def increase?(sidekiq_worker)
20
24
  sidekiq_worker.quantity < max_dynos_count &&
21
25
  (sidekiq_worker.latency > max_latency ||
22
- (sidekiq_worker.quantity.zero? && sidekiq_worker.latency.positive?)) &&
23
- sidekiq_worker.queues_size > sidekiq_worker.quantity * sidekiq_worker.concurrency
26
+ (sidekiq_worker.quantity.zero? && sidekiq_worker.latency.positive?))
24
27
  end
25
28
 
26
29
  def decrease?(sidekiq_worker)
@@ -33,7 +36,7 @@ module SidekiqHerokuScaler
33
36
  def safe_quantity(quantity)
34
37
  return min_dynos_count if quantity < min_dynos_count
35
38
 
36
- quantity > max_dynos_count ? max_dynos_count : quantity
39
+ [quantity, max_dynos_count].min
37
40
  end
38
41
 
39
42
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SidekiqHerokuScaler
4
- VERSION = '0.3.5'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -5,6 +5,8 @@ require 'sidekiq-heroku-scaler/sidekiq_config'
5
5
 
6
6
  module SidekiqHerokuScaler
7
7
  class Worker
8
+ attr_reader :worker_name
9
+
8
10
  def initialize(worker_name, formation)
9
11
  @worker_name = worker_name
10
12
  @formation = formation
@@ -34,9 +36,13 @@ module SidekiqHerokuScaler
34
36
  queues.sum { |queue| Sidekiq::Queue.new(queue).size }
35
37
  end
36
38
 
39
+ def processes
40
+ @processes ||= Sidekiq::ProcessSet.new.select { |process| process.identity.match?(/\A#{worker_name}\./) }
41
+ end
42
+
37
43
  private
38
44
 
39
- attr_reader :formation, :worker_name
45
+ attr_reader :formation
40
46
 
41
47
  def build_process
42
48
  command = formation.command.gsub(/.*sidekiq(\s|\z)/, '').split
@@ -48,12 +54,8 @@ module SidekiqHerokuScaler
48
54
  process['queues'] || []
49
55
  end
50
56
 
51
- def process_set
52
- @process_set ||= Sidekiq::ProcessSet.new
53
- end
54
-
55
57
  def process
56
- @process ||= process_set.detect { |p| p.identity.match(/\A#{worker_name}\./) } || build_process
58
+ @process ||= processes.first || build_process
57
59
  end
58
60
  end
59
61
  end
@@ -22,10 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_development_dependency 'bundler'
26
- spec.add_development_dependency 'rake'
27
- spec.add_development_dependency 'rspec'
28
- spec.add_development_dependency 'rubocop'
25
+ spec.required_ruby_version = '>= 3.0.0'
29
26
 
30
27
  spec.add_dependency 'activesupport', '> 5', '< 8'
31
28
  spec.add_dependency 'platform-api', '> 3', '< 4'
metadata CHANGED
@@ -1,71 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-heroku-scaler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vital Ryabchinskiy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-22 00:00:00.000000000 Z
11
+ date: 2023-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rubocop
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
13
  - !ruby/object:Gem::Dependency
70
14
  name: activesupport
71
15
  requirement: !ruby/object:Gem::Requirement
@@ -166,14 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
166
110
  requirements:
167
111
  - - ">="
168
112
  - !ruby/object:Gem::Version
169
- version: '0'
113
+ version: 3.0.0
170
114
  required_rubygems_version: !ruby/object:Gem::Requirement
171
115
  requirements:
172
116
  - - ">="
173
117
  - !ruby/object:Gem::Version
174
118
  version: '0'
175
119
  requirements: []
176
- rubygems_version: 3.3.7
120
+ rubygems_version: 3.4.10
177
121
  signing_key:
178
122
  specification_version: 4
179
123
  summary: Sidekiq Heroku Scaler