dyno_scaler 0.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.
- 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)
|