dyno_scaler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +16 -0
- data/.rspec +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +158 -0
- data/LICENSE.txt +22 -0
- data/README.md +103 -0
- data/Rakefile +8 -0
- data/dyno_scaler.gemspec +26 -0
- data/lib/dyno_scaler/configuration.rb +110 -0
- data/lib/dyno_scaler/engine.rb +7 -0
- data/lib/dyno_scaler/heroku.rb +20 -0
- data/lib/dyno_scaler/manager.rb +77 -0
- data/lib/dyno_scaler/version.rb +3 -0
- data/lib/dyno_scaler/workers/resque.rb +64 -0
- data/lib/dyno_scaler/workers.rb +7 -0
- data/lib/dyno_scaler.rb +27 -0
- data/spec/dyno_scaler/configuration_spec.rb +73 -0
- data/spec/dyno_scaler/heroku_spec.rb +22 -0
- data/spec/dyno_scaler/manager_spec.rb +349 -0
- data/spec/dyno_scaler/workers/resque_spec.rb +201 -0
- data/spec/dyno_scaler_spec.rb +11 -0
- data/spec/spec_helper.rb +40 -0
- metadata +155 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3-p0@dyno_scaler --create
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in dyno_scaler.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group(:development) do
|
7
|
+
platforms :mri_19 do
|
8
|
+
gem 'debugger'
|
9
|
+
end
|
10
|
+
|
11
|
+
platforms :jruby do
|
12
|
+
gem 'ruby-debug'
|
13
|
+
gem 'jruby-openssl'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
group(:test) do
|
18
|
+
gem 'simplecov', require: false
|
19
|
+
gem 'rails'
|
20
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dyno_scaler (0.1.0)
|
5
|
+
activesupport
|
6
|
+
heroku-api
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
actionmailer (3.2.9)
|
12
|
+
actionpack (= 3.2.9)
|
13
|
+
mail (~> 2.4.4)
|
14
|
+
actionpack (3.2.9)
|
15
|
+
activemodel (= 3.2.9)
|
16
|
+
activesupport (= 3.2.9)
|
17
|
+
builder (~> 3.0.0)
|
18
|
+
erubis (~> 2.7.0)
|
19
|
+
journey (~> 1.0.4)
|
20
|
+
rack (~> 1.4.0)
|
21
|
+
rack-cache (~> 1.2)
|
22
|
+
rack-test (~> 0.6.1)
|
23
|
+
sprockets (~> 2.2.1)
|
24
|
+
activemodel (3.2.9)
|
25
|
+
activesupport (= 3.2.9)
|
26
|
+
builder (~> 3.0.0)
|
27
|
+
activerecord (3.2.9)
|
28
|
+
activemodel (= 3.2.9)
|
29
|
+
activesupport (= 3.2.9)
|
30
|
+
arel (~> 3.0.2)
|
31
|
+
tzinfo (~> 0.3.29)
|
32
|
+
activeresource (3.2.9)
|
33
|
+
activemodel (= 3.2.9)
|
34
|
+
activesupport (= 3.2.9)
|
35
|
+
activesupport (3.2.9)
|
36
|
+
i18n (~> 0.6)
|
37
|
+
multi_json (~> 1.0)
|
38
|
+
arel (3.0.2)
|
39
|
+
bouncy-castle-java (1.5.0146.1)
|
40
|
+
builder (3.0.4)
|
41
|
+
columnize (0.3.6)
|
42
|
+
connection_pool (0.9.3)
|
43
|
+
debugger (1.2.2)
|
44
|
+
columnize (>= 0.3.1)
|
45
|
+
debugger-linecache (~> 1.1.1)
|
46
|
+
debugger-ruby_core_source (~> 1.1.5)
|
47
|
+
debugger-linecache (1.1.2)
|
48
|
+
debugger-ruby_core_source (>= 1.1.1)
|
49
|
+
debugger-ruby_core_source (1.1.5)
|
50
|
+
diff-lcs (1.1.3)
|
51
|
+
erubis (2.7.0)
|
52
|
+
excon (0.16.10)
|
53
|
+
girl_friday (0.11.1)
|
54
|
+
connection_pool (~> 0.9.0)
|
55
|
+
rubinius-actor
|
56
|
+
heroku-api (0.3.7)
|
57
|
+
excon (~> 0.16.10)
|
58
|
+
hike (1.2.1)
|
59
|
+
i18n (0.6.1)
|
60
|
+
journey (1.0.4)
|
61
|
+
jruby-openssl (0.8.2)
|
62
|
+
bouncy-castle-java (>= 1.5.0146.1)
|
63
|
+
json (1.7.5)
|
64
|
+
json (1.7.5-java)
|
65
|
+
mail (2.4.4)
|
66
|
+
i18n (>= 0.4.0)
|
67
|
+
mime-types (~> 1.16)
|
68
|
+
treetop (~> 1.4.8)
|
69
|
+
mime-types (1.19)
|
70
|
+
multi_json (1.5.0)
|
71
|
+
polyglot (0.3.3)
|
72
|
+
rack (1.4.1)
|
73
|
+
rack-cache (1.2)
|
74
|
+
rack (>= 0.4)
|
75
|
+
rack-protection (1.3.2)
|
76
|
+
rack
|
77
|
+
rack-ssl (1.3.2)
|
78
|
+
rack
|
79
|
+
rack-test (0.6.2)
|
80
|
+
rack (>= 1.0)
|
81
|
+
rails (3.2.9)
|
82
|
+
actionmailer (= 3.2.9)
|
83
|
+
actionpack (= 3.2.9)
|
84
|
+
activerecord (= 3.2.9)
|
85
|
+
activeresource (= 3.2.9)
|
86
|
+
activesupport (= 3.2.9)
|
87
|
+
bundler (~> 1.0)
|
88
|
+
railties (= 3.2.9)
|
89
|
+
railties (3.2.9)
|
90
|
+
actionpack (= 3.2.9)
|
91
|
+
activesupport (= 3.2.9)
|
92
|
+
rack-ssl (~> 1.3.2)
|
93
|
+
rake (>= 0.8.7)
|
94
|
+
rdoc (~> 3.4)
|
95
|
+
thor (>= 0.14.6, < 2.0)
|
96
|
+
rake (10.0.3)
|
97
|
+
rdoc (3.12)
|
98
|
+
json (~> 1.4)
|
99
|
+
redis (3.0.2)
|
100
|
+
redis-namespace (1.2.1)
|
101
|
+
redis (~> 3.0.0)
|
102
|
+
resque (1.23.0)
|
103
|
+
multi_json (~> 1.0)
|
104
|
+
redis-namespace (~> 1.0)
|
105
|
+
sinatra (>= 0.9.2)
|
106
|
+
vegas (~> 0.1.2)
|
107
|
+
rspec (2.12.0)
|
108
|
+
rspec-core (~> 2.12.0)
|
109
|
+
rspec-expectations (~> 2.12.0)
|
110
|
+
rspec-mocks (~> 2.12.0)
|
111
|
+
rspec-core (2.12.2)
|
112
|
+
rspec-expectations (2.12.1)
|
113
|
+
diff-lcs (~> 1.1.3)
|
114
|
+
rspec-mocks (2.12.1)
|
115
|
+
rubinius-actor (0.0.2)
|
116
|
+
rubinius-core-api
|
117
|
+
rubinius-core-api (0.0.1)
|
118
|
+
rubinius-core-api (0.0.1-java)
|
119
|
+
ruby-debug (0.10.4)
|
120
|
+
columnize (>= 0.1)
|
121
|
+
ruby-debug-base (~> 0.10.4.0)
|
122
|
+
ruby-debug-base (0.10.4-java)
|
123
|
+
simplecov (0.7.1)
|
124
|
+
multi_json (~> 1.0)
|
125
|
+
simplecov-html (~> 0.7.1)
|
126
|
+
simplecov-html (0.7.1)
|
127
|
+
sinatra (1.3.3)
|
128
|
+
rack (~> 1.3, >= 1.3.6)
|
129
|
+
rack-protection (~> 1.2)
|
130
|
+
tilt (~> 1.3, >= 1.3.3)
|
131
|
+
sprockets (2.2.2)
|
132
|
+
hike (~> 1.2)
|
133
|
+
multi_json (~> 1.0)
|
134
|
+
rack (~> 1.0)
|
135
|
+
tilt (~> 1.1, != 1.3.0)
|
136
|
+
thor (0.16.0)
|
137
|
+
tilt (1.3.3)
|
138
|
+
treetop (1.4.12)
|
139
|
+
polyglot
|
140
|
+
polyglot (>= 0.3.1)
|
141
|
+
tzinfo (0.3.35)
|
142
|
+
vegas (0.1.11)
|
143
|
+
rack (>= 1.0.0)
|
144
|
+
|
145
|
+
PLATFORMS
|
146
|
+
java
|
147
|
+
ruby
|
148
|
+
|
149
|
+
DEPENDENCIES
|
150
|
+
debugger
|
151
|
+
dyno_scaler!
|
152
|
+
girl_friday
|
153
|
+
jruby-openssl
|
154
|
+
rails
|
155
|
+
resque
|
156
|
+
rspec
|
157
|
+
ruby-debug
|
158
|
+
simplecov
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Vicente Mundim
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# DynoScaler
|
2
|
+
|
3
|
+
[![alt build status][1]][2]
|
4
|
+
|
5
|
+
[1]: https://travis-ci.org/dtmconsultoria/dyno_scaler.png?branch=master
|
6
|
+
[2]: http://travis-ci.org/dtmconsultoria/dyno_scaler
|
7
|
+
|
8
|
+
Scale your dyno workers on Heroku as needed, pay only for what you use!
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'dyno_scaler'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install dyno_scaler
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Just include this module in your Resque job and you're good to go:
|
27
|
+
|
28
|
+
class MyJob
|
29
|
+
include DynoScaler::Workers::Resque
|
30
|
+
|
31
|
+
...
|
32
|
+
end
|
33
|
+
|
34
|
+
You can access the configuration with (for example):
|
35
|
+
|
36
|
+
DynoScaler.configuration.max_workers = 3
|
37
|
+
|
38
|
+
In Rails, you can access it easily in your application.rb:
|
39
|
+
|
40
|
+
config.dyno_scaler.max_workers = 3
|
41
|
+
|
42
|
+
If you want to scale up or down your workers manually, you can use the manager
|
43
|
+
directly:
|
44
|
+
|
45
|
+
DynoScaler.manager.scale_up(options)
|
46
|
+
DynoScaler.manager.scale_down(options)
|
47
|
+
|
48
|
+
You must pass an options hash with the number of workers, the number of pending
|
49
|
+
jobs, and the number of running jobs, like so:
|
50
|
+
|
51
|
+
{
|
52
|
+
workers: 10,
|
53
|
+
working: 3,
|
54
|
+
pending: 5
|
55
|
+
}
|
56
|
+
|
57
|
+
`Resque.info` returns a hash with these keys, so you may just pass it instead:
|
58
|
+
|
59
|
+
DynoScaler.manager.scale_up(Resque.info)
|
60
|
+
|
61
|
+
You can also use the `DynoScaler::Manager#scale_with` method, passing the `Resque.info`:
|
62
|
+
|
63
|
+
DynoScaler.manager.scale_with(Resque.info)
|
64
|
+
|
65
|
+
It will check whether to scale up or down based on the number of workers running,
|
66
|
+
pending jobs, and working jobs.
|
67
|
+
|
68
|
+
## Heroku Deploy
|
69
|
+
|
70
|
+
When deploying to heroku you'll want to add these two configuration keys:
|
71
|
+
|
72
|
+
HEROKU_API_KEY=<YOUR-API-KEY-HERE>
|
73
|
+
HEROKU_APPLICATION=<THE-NAME-OF-YOUR-APP-ON-HEROKU-HERE>
|
74
|
+
|
75
|
+
They are used by the [heroku-api](https://github.com/heroku/heroku.rb) gem to
|
76
|
+
scale dynos of your application.
|
77
|
+
|
78
|
+
## Async
|
79
|
+
|
80
|
+
Whenever DynoScaler needs to scale workers up it will perform a request to the
|
81
|
+
Heroku API. This request may sometimes take longer to return than one would want.
|
82
|
+
Because of this we have a async option that uses
|
83
|
+
[GirlFriday](https://github.com/mperham/girl_friday) to handle this call
|
84
|
+
asynchronously. To enable it, just set it to `true`:
|
85
|
+
|
86
|
+
config.dyno_scaler.async = true
|
87
|
+
|
88
|
+
You can also give it a block to better customize it. It will receive an options
|
89
|
+
hash that can be passed to the `DynoScaler::Manager#scale_with` method, like so:
|
90
|
+
|
91
|
+
MY_QUEUE = GirlFriday::WorkQueue.new(:my_queue, size: 5) do |options|
|
92
|
+
DynoScaler.manager.scale_with(options)
|
93
|
+
end
|
94
|
+
|
95
|
+
config.dyno_scaler.async { MY_QUEUE << options }
|
96
|
+
|
97
|
+
## Contributing
|
98
|
+
|
99
|
+
1. Fork it
|
100
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
101
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
102
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
103
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/dyno_scaler.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dyno_scaler/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "dyno_scaler"
|
8
|
+
gem.version = DynoScaler::VERSION
|
9
|
+
gem.authors = ["Vicente Mundim"]
|
10
|
+
gem.email = ["vicente.mundim@gmail.com"]
|
11
|
+
gem.description = %q{Scale your dyno workers on Heroku as needed}
|
12
|
+
gem.summary = %q{Scale your dyno workers on Heroku as needed}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "heroku-api"
|
21
|
+
gem.add_dependency "activesupport"
|
22
|
+
|
23
|
+
gem.add_development_dependency "rspec"
|
24
|
+
gem.add_development_dependency "resque"
|
25
|
+
gem.add_development_dependency "girl_friday"
|
26
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module DynoScaler
|
4
|
+
class Configuration
|
5
|
+
##
|
6
|
+
# Contains the max amount of workers that are allowed to run concurrently
|
7
|
+
#
|
8
|
+
# @return [Fixnum] default: 1
|
9
|
+
attr_accessor :max_workers
|
10
|
+
|
11
|
+
##
|
12
|
+
# Contains the min amount of workers that should always be running
|
13
|
+
#
|
14
|
+
# @return [Fixnum] default: 0
|
15
|
+
attr_accessor :min_workers
|
16
|
+
|
17
|
+
##
|
18
|
+
# Contains the ratio used to spawn more workers given a number of jobs.
|
19
|
+
#
|
20
|
+
# The given hash should have the number of workers as a key, and the number
|
21
|
+
# of queued jobs that are needed in order to spawn that number of workers
|
22
|
+
# as the value.
|
23
|
+
#
|
24
|
+
# For example, if you wanted to spawn a second worker once 6 jobs are queued
|
25
|
+
# then spawn another third worker once 10 jobs are queued you could configure
|
26
|
+
# this option as:
|
27
|
+
#
|
28
|
+
# config.job_worker_ratio = {
|
29
|
+
# 1 => 1,
|
30
|
+
# 2 => 6,
|
31
|
+
# 3 => 10
|
32
|
+
# }
|
33
|
+
#
|
34
|
+
# @param [Hash] with job worker ratio
|
35
|
+
# @return [Hash] default to { 1 => 1, 2 => 25, 3 => 50, 4 => 75, 5 => 100 }
|
36
|
+
attr_accessor :job_worker_ratio
|
37
|
+
|
38
|
+
##
|
39
|
+
# Default is false when HEROKU_API_KEY environment variable is not set,
|
40
|
+
# otherwise defaults to true.
|
41
|
+
#
|
42
|
+
# @param [Boolean] whether to enable scaling or not
|
43
|
+
# @return [Boolean] default: false
|
44
|
+
attr_accessor :enabled
|
45
|
+
alias enabled? enabled
|
46
|
+
|
47
|
+
##
|
48
|
+
# Default is nil when HEROKU_APP environment variable is not set,
|
49
|
+
# otherwise defaults to its value.
|
50
|
+
#
|
51
|
+
# @param [String] the name of the Heroku application used when scaling workers
|
52
|
+
# @return [String] default: nil
|
53
|
+
attr_accessor :application
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
self.max_workers = 1
|
57
|
+
self.min_workers = 0
|
58
|
+
self.enabled = !ENV['HEROKU_API_KEY'].nil?
|
59
|
+
self.application = ENV['HEROKU_APP']
|
60
|
+
|
61
|
+
self.job_worker_ratio = {
|
62
|
+
1 => 1,
|
63
|
+
2 => 25,
|
64
|
+
3 => 50,
|
65
|
+
4 => 75,
|
66
|
+
5 => 100
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the current configured async Proc or configures one.
|
71
|
+
def async(&block)
|
72
|
+
@async = block if block_given?
|
73
|
+
@async
|
74
|
+
end
|
75
|
+
alias_method :async?, :async
|
76
|
+
|
77
|
+
##
|
78
|
+
# When set to true it will use GirlFriday to asynchronous process the scaling,
|
79
|
+
# otherwise you may pass a Proc that will be called whenever scaling is needed.
|
80
|
+
#
|
81
|
+
# Defaults to false, meaning that scaling is processed synchronously.
|
82
|
+
def async=(value)
|
83
|
+
@async = value == true ? default_async_processor : value
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# The logger to be used to log message
|
88
|
+
#
|
89
|
+
# When using Rails it will default to Rails.logger, otherwise it will be
|
90
|
+
# set a `Logger.new(STDERR)`.
|
91
|
+
#
|
92
|
+
# @param [Logger] the logger to be used
|
93
|
+
# @return [Logger] default: nil
|
94
|
+
def logger
|
95
|
+
@logger ||= defined?(Rails) ? Rails.logger || Logger.new(STDERR) : Logger.new(STDERR)
|
96
|
+
end
|
97
|
+
attr_writer :logger
|
98
|
+
|
99
|
+
private
|
100
|
+
def default_async_processor
|
101
|
+
require 'girl_friday'
|
102
|
+
|
103
|
+
queue = GirlFriday::WorkQueue.new(nil, :size => 1) do |options|
|
104
|
+
DynoScaler.manager.scale_with(options)
|
105
|
+
end
|
106
|
+
|
107
|
+
Proc.new { |options| queue << options }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module DynoScaler
|
4
|
+
class Heroku
|
5
|
+
attr_accessor :application
|
6
|
+
|
7
|
+
def initialize(application)
|
8
|
+
self.application = application
|
9
|
+
end
|
10
|
+
|
11
|
+
def scale_workers(quantity)
|
12
|
+
heroku_client.post_ps_scale(application, 'worker', quantity)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
def heroku_client
|
17
|
+
@heroku_client ||= ::Heroku::API.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module DynoScaler
|
4
|
+
class Manager
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def scale_up(options)
|
8
|
+
return unless config.enabled?
|
9
|
+
|
10
|
+
self.options = options
|
11
|
+
|
12
|
+
scale_to(number_of_workers_needed) if scale_up?
|
13
|
+
end
|
14
|
+
|
15
|
+
def scale_up?
|
16
|
+
workers_needed = number_of_workers_needed
|
17
|
+
|
18
|
+
pending_jobs? && workers_needed > options[:workers] && workers_needed <= config.max_workers
|
19
|
+
end
|
20
|
+
|
21
|
+
def scale_down(options)
|
22
|
+
return unless config.enabled?
|
23
|
+
|
24
|
+
self.options = options
|
25
|
+
|
26
|
+
scale_to(config.min_workers) if scale_down?
|
27
|
+
end
|
28
|
+
|
29
|
+
def scale_down?
|
30
|
+
options[:workers] > config.min_workers && !pending_jobs? && !working_jobs?
|
31
|
+
end
|
32
|
+
|
33
|
+
def scale_with(options)
|
34
|
+
return unless config.enabled?
|
35
|
+
|
36
|
+
action = options[:action] || action_for(options)
|
37
|
+
send(action, options) if action
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def config
|
42
|
+
DynoScaler.configuration
|
43
|
+
end
|
44
|
+
|
45
|
+
def scale_to(number_of_workers)
|
46
|
+
config.logger.info "Scaling workers to #{number_of_workers}"
|
47
|
+
heroku.scale_workers(number_of_workers)
|
48
|
+
end
|
49
|
+
|
50
|
+
def pending_jobs?
|
51
|
+
options[:pending] > 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def working_jobs?
|
55
|
+
options[:working] > 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def number_of_workers_needed
|
59
|
+
value = config.job_worker_ratio.reverse_each.find do |_, pending_jobs|
|
60
|
+
options[:pending] >= pending_jobs
|
61
|
+
end
|
62
|
+
|
63
|
+
value ? value.first : 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def action_for(options)
|
67
|
+
self.options = options
|
68
|
+
|
69
|
+
return :scale_down if scale_down?
|
70
|
+
return :scale_up if scale_up?
|
71
|
+
end
|
72
|
+
|
73
|
+
def heroku
|
74
|
+
@heroku ||= DynoScaler::Heroku.new config.application
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
|
5
|
+
module DynoScaler
|
6
|
+
module Workers
|
7
|
+
module Resque
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :scale_up_enabled
|
12
|
+
class_attribute :scale_down_enabled
|
13
|
+
|
14
|
+
enable_scaling_up
|
15
|
+
enable_scaling_down
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def after_perform_scale_down(*args)
|
20
|
+
info = ::Resque.info
|
21
|
+
working = info[:working] > 0 ? info[:working] - 1 : 0
|
22
|
+
info.merge!(working: working) # we are not working anymore
|
23
|
+
|
24
|
+
dyno_scaler_manager.scale_down(info) if scale_down_enabled?
|
25
|
+
rescue StandardError => e
|
26
|
+
$stderr.puts "Could not scale down workers: #{e}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_enqueue_scale_up(*args)
|
30
|
+
if scale_up_enabled?
|
31
|
+
if DynoScaler.configuration.async?
|
32
|
+
DynoScaler.configuration.async.call(::Resque.info.merge(action: :scale_up))
|
33
|
+
else
|
34
|
+
dyno_scaler_manager.scale_up(::Resque.info)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue StandardError => e
|
38
|
+
$stderr.puts "Could not scale up workers: #{e}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def enable_scaling_up
|
42
|
+
self.scale_up_enabled = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def enable_scaling_down
|
46
|
+
self.scale_down_enabled = true
|
47
|
+
end
|
48
|
+
|
49
|
+
def disable_scaling_up
|
50
|
+
self.scale_up_enabled = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def disable_scaling_down
|
54
|
+
self.scale_down_enabled = false
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def dyno_scaler_manager
|
59
|
+
@manager ||= DynoScaler::Manager.new
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/dyno_scaler.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "dyno_scaler/version"
|
4
|
+
require "active_support/core_ext/class/attribute"
|
5
|
+
require "heroku-api"
|
6
|
+
|
7
|
+
module DynoScaler
|
8
|
+
autoload :Configuration, 'dyno_scaler/configuration'
|
9
|
+
autoload :Heroku, 'dyno_scaler/heroku'
|
10
|
+
autoload :Manager, 'dyno_scaler/manager'
|
11
|
+
autoload :Workers, 'dyno_scaler/workers'
|
12
|
+
|
13
|
+
def self.configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.manager
|
18
|
+
@manager ||= Manager.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.reset!
|
22
|
+
@configuration = nil
|
23
|
+
@manager = nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require "dyno_scaler/engine" if defined?(Rails)
|