resque-heroku-scaler 0.2.1

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/HISTORY.md ADDED
@@ -0,0 +1,6 @@
1
+ ## 0.2.1 (2011-11-27)
2
+
3
+ * Separate scaler process from worker hooks to prevent race conditions during worker shutdown
4
+ * Scale manager for Heroku Cedar platform
5
+ * Scale manager for local workers using rush
6
+ * Core test coverage
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Aaron Dunnington
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ Resque Heroku Scaler
2
+ ====================
3
+
4
+ Provides autoscaling for [Resque][rq] workers on [Heroku][hk]. Based on
5
+ previous scaling work developed by [Daniel Huckstep][dh] and
6
+ [Alexander Murmann][am].
7
+
8
+ Autoscaling behavior is provided through a separate monitor process. The
9
+ scaler monitor process polls for pending jobs against the specified Resque
10
+ Redis backend at a configurable interval. The scaler process runs as a worker
11
+ process on Heroku.
12
+
13
+ ##Setup
14
+
15
+ Add the following environment variables to your Heroku environment:
16
+
17
+ * HEROKU_APP
18
+ * HEROKU_USERNAME
19
+ * HEROKU_PASSWORD
20
+
21
+ In your Procfile, configure the scaler as a worker process using:
22
+
23
+ ```
24
+ scaler: bundle exec rake resque:scaler:run
25
+ ```
26
+
27
+ To run the scaler process, use the following command. Note, the scaler process
28
+ is intended to run as a single instance.
29
+
30
+ ```
31
+ heroku scale scaler=1
32
+ ```
33
+
34
+ In your development environment, the scaler process can run local worker
35
+ processes using the rush library. To configure, use the following in
36
+ an initializer.
37
+
38
+ ```ruby
39
+ require 'resque/plugins/resque-heroku-scaler'
40
+
41
+ if Rails.env.development?
42
+ ENV["RUSH_PATH"] ||= File.expand_path('/path/to/app', __FILE__)
43
+ Resque::Plugins::ResqueHerokuScaler.configure do |c|
44
+ c.scale_manager = :local
45
+ end
46
+ end
47
+ ```
48
+
49
+ [rq]: http://github.com/defunkt/resque
50
+ [hk]: http://devcenter.heroku.com/articles/cedar
51
+ [dh]: http://verboselogging.com/2010/07/30/auto-scale-your-resque-workers-on-heroku
52
+ [am]: https://github.com/ajmurmann/resque-heroku-autoscaler
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/*_test.rb']
8
+ t.verbose = true
9
+ end
@@ -0,0 +1,6 @@
1
+ require 'resque/plugins/resque_heroku_scaler/version'
2
+ require 'resque/plugins/resque_heroku_scaler/config'
3
+ require 'resque/plugins/resque_heroku_scaler/manager'
4
+ require 'resque/plugins/resque_heroku_scaler/worker'
5
+ require 'resque/plugins/resque_heroku_scaler/resque'
6
+ require 'resque/plugins/resque_heroku_scaler'
@@ -0,0 +1,71 @@
1
+ module Resque
2
+ module Plugins
3
+ module ResqueHerokuScaler
4
+ module Config
5
+ extend self
6
+
7
+ attr_writer :scale_manager
8
+ attr_writer :scale_interval
9
+ attr_writer :poll_interval
10
+ attr_writer :scale_timeout
11
+ attr_reader :scale_with
12
+
13
+ def scale_manager
14
+ @scale_manager || :heroku
15
+ end
16
+
17
+ def scale_interval
18
+ @scale_interval || 60
19
+ end
20
+
21
+ def poll_interval
22
+ @poll_interval || 5
23
+ end
24
+
25
+ def scale_timeout
26
+ @scale_timeout || 90
27
+ end
28
+
29
+ def scale_for(pending)
30
+ return @scale_with.call(pending) if @scale_with
31
+ default_scale_with(pending)
32
+ end
33
+
34
+ def scale_with=(block)
35
+ @scale_with = block
36
+ end
37
+
38
+ def default_scale_with(pending)
39
+ return 0 if pending <= 0
40
+
41
+ [{
42
+ :workers => 1,
43
+ :jobs => 1
44
+ },
45
+ {
46
+ :workers => 2,
47
+ :jobs => 15
48
+ },
49
+ {
50
+ :workers => 3,
51
+ :jobs => 25
52
+ },
53
+ {
54
+ :workers => 4,
55
+ :jobs => 40
56
+ },
57
+ {
58
+ :workers => 5,
59
+ :jobs => 60
60
+ }
61
+ ].reverse_each do |required_scale|
62
+ if pending >= required_scale[:jobs]
63
+ return required_scale[:workers]
64
+ end
65
+ end
66
+ return 0
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,25 @@
1
+ require 'heroku'
2
+
3
+ module Resque
4
+ module Plugins
5
+ module ResqueHerokuScaler
6
+ module Manager
7
+
8
+ class Heroku
9
+ def initialize(options={})
10
+ @heroku = ::Heroku::Client.new(ENV['HEROKU_USERNAME'], ENV['HEROKU_PASSWORD'])
11
+ end
12
+
13
+ def workers
14
+ @heroku.ps(ENV['HEROKU_APP']).count { |p| p["process"] =~ /worker\.\d?/ }
15
+ end
16
+
17
+ def workers=(qty)
18
+ @heroku.ps_scale(ENV['HEROKU_APP'], :type => 'worker', :qty => qty)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,56 @@
1
+ require 'rush'
2
+
3
+ module Resque
4
+ module Plugins
5
+ module ResqueHerokuScaler
6
+ module Manager
7
+
8
+ class Local
9
+
10
+ def initialize(options={})
11
+ @path = options[:path] || ENV['RUSH_PATH']
12
+ @processes = []
13
+ end
14
+
15
+ def workers
16
+ @processes.length
17
+ end
18
+
19
+ def workers=(qty)
20
+ active = workers
21
+ return if qty == active
22
+ if qty > active
23
+ scale_up(qty-active)
24
+ return
25
+ end
26
+ scale_down(active-qty)
27
+ end
28
+
29
+ def scale_up(qty)
30
+ qty.times do
31
+ process = Rush::Box.new[@path].bash('rake resque:work', :background => true, :env => { :BUNDLE_GEMFILE => '' })
32
+ @processes.push(process) if process
33
+ end
34
+ end
35
+
36
+ def scale_down(qty)
37
+ i = 0
38
+ until i == qty or @processes.empty?
39
+ process = @processes.pop
40
+ kill(process)
41
+ i += 1
42
+ end
43
+ end
44
+
45
+ def kill(process)
46
+ process.children.each do |child|
47
+ kill(child)
48
+ end
49
+ process.kill
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module Resque
2
+ module Plugins
3
+ module ResqueHerokuScaler
4
+ module Manager
5
+ extend self
6
+
7
+ def instance
8
+ @@instance ||= init_manager
9
+ end
10
+
11
+ def init_manager
12
+ handler = Resque::Plugins::ResqueHerokuScaler::Config.scale_manager
13
+ return handler unless [Symbol, Array, String].include? handler.class
14
+
15
+ options = {}
16
+ handler, options = handler if handler.is_a?(Array)
17
+ require File.dirname(__FILE__) + "/manager/#{handler}"
18
+ const_get(handler.to_s.capitalize).new(options)
19
+ end
20
+
21
+ def method_missing(m, *args)
22
+ instance.send(m, *args)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ module Resque
2
+
3
+ alias_method :original_info, :info
4
+
5
+ def scaling
6
+ Worker.scaling
7
+ end
8
+
9
+ def info
10
+ info_with_scale = original_info
11
+ info_with_scale[:scaling] = scaling.size
12
+ return info_with_scale
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ namespace :resque do
2
+ namespace :scaler do
3
+ task :setup
4
+
5
+ desc "Start Resque Heroku Scaler process"
6
+ task :run => :setup do
7
+ require 'resque/plugins/resque-heroku-scaler'
8
+ Resque::Plugins::ResqueHerokuScaler.run
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module ResqueHerokuScaler
4
+ Version = VERSION = "0.2.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ module Resque
2
+
3
+ class Worker
4
+ alias_method :original_unregister_worker, :unregister_worker
5
+
6
+ def work(interval = 5.0, &block)
7
+ interval = Float(interval)
8
+ $0 = "resque: Starting"
9
+ startup
10
+
11
+ loop do
12
+ wait_for_scale if scaling?
13
+ break if shutdown?
14
+
15
+ if not paused? and job = reserve
16
+ log "got: #{job.inspect}"
17
+ job.worker = self
18
+ run_hook :before_fork, job
19
+ working_on job
20
+
21
+ if @child = fork
22
+ srand # Reseeding
23
+ procline "Forked #{@child} at #{Time.now.to_i}"
24
+ Process.wait(@child)
25
+ else
26
+ procline "Processing #{job.queue} since #{Time.now.to_i}"
27
+ perform(job, &block)
28
+ exit! unless @cant_fork
29
+ end
30
+
31
+ done_working
32
+ @child = nil
33
+ else
34
+ break if interval.zero?
35
+ log! "Sleeping for #{interval} seconds"
36
+ procline paused? ? "Paused" : "Waiting for #{@queues.join(',')}"
37
+ sleep interval
38
+ end
39
+ end
40
+
41
+ ensure
42
+ unregister_worker
43
+ end
44
+
45
+ def wait_for_scale
46
+ redis.set("scale:#{self}", true)
47
+ sleep 1 while scaling? and not shutdown?
48
+ redis.del("scale:#{self}")
49
+ end
50
+
51
+ def unregister_worker
52
+ redis.del("scale:#{self}")
53
+ original_unregister_worker
54
+ end
55
+
56
+ def scaling?
57
+ redis.exists(:scale)
58
+ end
59
+
60
+ def self.scaling
61
+ names = all
62
+ return [] unless names.any?
63
+
64
+ names.map! { |name| "scale:#{name}" }
65
+
66
+ reportedly_scaling = {}
67
+
68
+ begin
69
+ reportedly_scaling = redis.mapped_mget(*names).reject do |key, value|
70
+ value.nil? || value.empty?
71
+ end
72
+ rescue Redis::Distributed::CannotDistribute
73
+ names.each do |name|
74
+ value = redis.get name
75
+ reportedly_scaling[name] = value unless value.nil? || value.empty?
76
+ end
77
+ end
78
+
79
+ reportedly_scaling.keys.map do |key|
80
+ find key.sub("scale:", '')
81
+ end.compact
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,106 @@
1
+ module Resque
2
+ module Plugins
3
+
4
+ module ResqueHerokuScaler
5
+ class << self
6
+
7
+ def run
8
+ startup
9
+ loop do
10
+ begin
11
+ scale
12
+ rescue Exception => e
13
+ log "Scale failed with #{e.class.name} #{e.message}"
14
+ end
15
+ wait_for_scale
16
+ end
17
+ end
18
+
19
+ def scale
20
+ required = scale_for(pending)
21
+ active = workers
22
+
23
+ return if required == active
24
+
25
+ log "Scale workers from #{active} to #{required}"
26
+
27
+ if required > active
28
+ scale_workers(required)
29
+ return
30
+ end
31
+
32
+ signal_workers
33
+ stop = timeout
34
+ wait_for_workers until ready_to_scale(active) or timeout?(stop)
35
+ scale_workers(required)
36
+
37
+ ensure
38
+ resume_workers
39
+ end
40
+
41
+ def wait_for_scale
42
+ sleep Resque::Plugins::ResqueHerokuScaler::Config.scale_interval
43
+ end
44
+
45
+ def wait_for_workers
46
+ sleep Resque::Plugins::ResqueHerokuScaler::Config.poll_interval
47
+ end
48
+
49
+ def scale_for(pending)
50
+ Resque::Plugins::ResqueHerokuScaler::Config.scale_for(pending)
51
+ end
52
+
53
+ def scale_workers(qty)
54
+ Resque::Plugins::ResqueHerokuScaler::Manager.workers = qty
55
+ end
56
+
57
+ def workers
58
+ Resque::Plugins::ResqueHerokuScaler::Manager.workers
59
+ end
60
+
61
+ def signal_workers
62
+ Resque.redis.set(:scale, true)
63
+ end
64
+
65
+ def resume_workers
66
+ Resque.redis.del(:scale)
67
+ end
68
+
69
+ def timeout?(stop)
70
+ Time.now >= stop
71
+ end
72
+
73
+ def timeout
74
+ Time.now + Resque::Plugins::ResqueHerokuScaler::Config.scale_timeout
75
+ end
76
+
77
+ def pending
78
+ Resque.info[:pending].to_i
79
+ end
80
+
81
+ def ready_to_scale(active)
82
+ Resque.info[:scaling] == active
83
+ end
84
+
85
+ def configure
86
+ yield Resque::Plugins::ResqueHerokuScaler::Config
87
+ end
88
+
89
+ def startup
90
+ STDOUT.sync = true
91
+ trap('TERM') do
92
+ log "Shutting down scaler"
93
+ exit
94
+ end
95
+ log "Starting scaler"
96
+ resume_workers
97
+ end
98
+
99
+ def log(message)
100
+ puts "*** #{message}"
101
+ end
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,61 @@
1
+ require 'test_helper'
2
+
3
+ class ConfigTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @config = Resque::Plugins::ResqueHerokuScaler::Config
6
+ end
7
+
8
+ def test_scale_manager_default
9
+ assert_equal :heroku, @config.scale_manager
10
+ end
11
+
12
+ def test_scale_interval_default
13
+ assert_equal 60, @config.scale_interval
14
+ end
15
+
16
+ def test_poll_interval_default
17
+ assert_equal 5, @config.poll_interval
18
+ end
19
+
20
+ def test_scale_timeout_default
21
+ assert_equal 90, @config.scale_timeout
22
+ end
23
+
24
+ def test_scale_for_default
25
+ assert_equal 2, @config.scale_for(20)
26
+ end
27
+
28
+ def test_scale_with_default
29
+ assert_equal nil, @config.scale_with
30
+ end
31
+
32
+ def test_custom_scale_with
33
+ @config.scale_with = Proc.new { |pending| 99 }
34
+ assert_equal 99, @config.scale_for(2)
35
+ @config.scale_with = nil
36
+ end
37
+
38
+ def test_scale_manager
39
+ @config.scale_manager = :local
40
+ assert_equal :local, @config.scale_manager
41
+ @config.scale_manager = :heroku
42
+ end
43
+
44
+ def test_scale_interval
45
+ @config.scale_interval = 99
46
+ assert_equal 99, @config.scale_interval
47
+ @config.scale_interval = 60
48
+ end
49
+
50
+ def test_poll_interval
51
+ @config.poll_interval = 99
52
+ assert_equal 99, @config.poll_interval
53
+ @config.poll_interval = 5
54
+ end
55
+
56
+ def test_scale_timeout
57
+ @config.scale_timeout = 99
58
+ assert_equal 99, @config.scale_timeout
59
+ @config.scale_timeout = 90
60
+ end
61
+ end
@@ -0,0 +1,68 @@
1
+ require 'test_helper'
2
+
3
+ class ResqueHerokuScalerTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @scaler = Resque::Plugins::ResqueHerokuScaler
6
+ @config = Resque::Plugins::ResqueHerokuScaler::Config
7
+
8
+ @redis = mock('Mock Redis')
9
+ @redis.stubs(:set).with(:scale, true).returns(true)
10
+ @redis.stubs(:del).with(:scale).returns(true)
11
+ Resque.stubs(:redis).returns(@redis)
12
+
13
+ @manager = mock('Mock Manager')
14
+ Resque::Plugins::ResqueHerokuScaler::Manager.stubs(:instance).returns(@manager)
15
+ end
16
+
17
+ def test_no_scale_for_zero_jobs
18
+ Resque.stubs(:info).returns({ :pending => 0, :scaling => 0 })
19
+ @manager.expects(:workers).returns(0)
20
+ @manager.expects(:workers=).never
21
+ @scaler.scale()
22
+ end
23
+
24
+ def test_scale_up_for_pending_job
25
+ Resque.stubs(:info).returns({ :pending => 1, :scaling => 0 })
26
+ @manager.expects(:workers).returns(0)
27
+ @manager.expects(:workers=).with(1)
28
+ @scaler.scale()
29
+ end
30
+
31
+ def test_scale_down_timeout
32
+ @config.scale_timeout = 1
33
+ Resque.stubs(:info).returns({ :pending => 0, :scaling => 0 })
34
+ @manager.expects(:workers).returns(1)
35
+ @manager.expects(:workers=).with(0)
36
+ @scaler.scale()
37
+ @config.scale_timeout = 90
38
+ end
39
+
40
+ def test_scale_down_for_zero_jobs
41
+ Resque.stubs(:info).returns({ :pending => 0, :scaling => 1 })
42
+ @manager.expects(:workers).returns(1)
43
+ @manager.expects(:workers=).with(0)
44
+ @scaler.scale()
45
+ end
46
+
47
+ def test_configure
48
+ @scaler.configure do |c|
49
+ c.scale_manager = :local
50
+ c.scale_interval = 30
51
+ c.poll_interval = 10
52
+ c.scale_timeout = 20
53
+ c.scale_with = Proc.new { |pending| 99 }
54
+ end
55
+
56
+ assert_equal :local, @config.scale_manager
57
+ assert_equal 30, @config.scale_interval
58
+ assert_equal 10, @config.poll_interval
59
+ assert_equal 20, @config.scale_timeout
60
+ assert_equal 99, @config.scale_for(2)
61
+
62
+ @config.scale_manager = :heroku
63
+ @config.scale_interval = 60
64
+ @config.poll_interval = 5
65
+ @config.scale_timeout = 90
66
+ @config.scale_with = nil
67
+ end
68
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest/autorun'
2
+ require 'mocha'
3
+ require 'resque'
4
+ require 'resque/plugins/resque-heroku-scaler'
@@ -0,0 +1,26 @@
1
+ require 'test_helper'
2
+
3
+ class WorkerTest < MiniTest::Unit::TestCase
4
+ def test_wait_for_scale
5
+ Resque.redis.stubs(:exists).with(:scale).returns(true)
6
+ Resque.redis.expects(:set).with(regexp_matches(/^scale:(.)*$/), true)
7
+ Resque.redis.expects(:del).with(regexp_matches(/^scale:(.)*$/))
8
+
9
+ worker = Resque::Worker.new(['*'])
10
+ worker.stubs(:shutdown?).returns(true)
11
+ worker.stubs(:register_worker).returns(true)
12
+ worker.stubs(:unregister_worker).returns(true)
13
+ worker.work(0)
14
+ end
15
+
16
+ def test_unregister_worker
17
+ Resque.redis.expects(:del).with(regexp_matches(/^scale:(.)*$/))
18
+ Resque.redis.stubs(:del).with(regexp_matches(/^worker:(.)*$/))
19
+ Resque.redis.stubs(:del).with(regexp_matches(/^stat:(.)*$/))
20
+
21
+ worker = Resque::Worker.new(['*'])
22
+ worker.stubs(:shutdown?).returns(true)
23
+ worker.stubs(:register_worker).returns(true)
24
+ worker.work(0)
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-heroku-scaler
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.2.1
6
+ platform: ruby
7
+ authors:
8
+ - Aaron Dunnington
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-11-27 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: resque
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 1.19.0
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: heroku
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 2.14.0
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ description: " This gem provides autoscaling behavior for Resque jobs on Heroku.\n"
39
+ email: spirogh@gmail.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - README.md
48
+ - Rakefile
49
+ - LICENSE
50
+ - HISTORY.md
51
+ - lib/resque/plugins/resque-heroku-scaler.rb
52
+ - lib/resque/plugins/resque_heroku_scaler/config.rb
53
+ - lib/resque/plugins/resque_heroku_scaler/manager/heroku.rb
54
+ - lib/resque/plugins/resque_heroku_scaler/manager/local.rb
55
+ - lib/resque/plugins/resque_heroku_scaler/manager.rb
56
+ - lib/resque/plugins/resque_heroku_scaler/resque.rb
57
+ - lib/resque/plugins/resque_heroku_scaler/tasks.rb
58
+ - lib/resque/plugins/resque_heroku_scaler/version.rb
59
+ - lib/resque/plugins/resque_heroku_scaler/worker.rb
60
+ - lib/resque/plugins/resque_heroku_scaler.rb
61
+ - test/config_test.rb
62
+ - test/resque_heroku_scaler_test.rb
63
+ - test/test_helper.rb
64
+ - test/worker_test.rb
65
+ has_rdoc: true
66
+ homepage: http://github.com/spiro/resque-heroku-scaler
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options: []
71
+
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.6.1
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Resque plugin to autoscale Heroku workers
93
+ test_files: []
94
+