heroku-qc-autoscale 0.0.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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_gemset_create_on_use_flag=1
2
+ rvm use 1.9.2@qc-autoscale
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in heroku-qc-autoscale.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ Autoscale QueueClassic workers on Heroku
2
+ ==================
3
+
4
+ Add to a Rails 3.x project to auto scale QueueClassic workers on heroku.
5
+
6
+
7
+ Usage
8
+ -----
9
+
10
+ Install as gem
11
+
12
+ gem install heroku-qc-autoscale
13
+
14
+ Add to Gemfile
15
+
16
+ gem "heroku-qc-autoscale"
17
+
18
+
19
+ Create config/initializers/qc_autoscale.rb
20
+
21
+ Heroku::QC::Autoscale.config do |c|
22
+ c.api_key = ENV['HEROKU_API_KEY']
23
+ c.app = ENV['HEROKU_APP']
24
+ c.scale = [1, 15, 30, 40, 50]
25
+ c.active = Rails.env.production?
26
+ end
27
+
28
+ Queue jobs as normal with QueueClassic. Based on your scale table, it will recalculate the
29
+ workers required after each QC#enqueue, and QC#delete.
30
+
31
+ QC.enqueue("Time.now")
32
+
33
+
34
+ Meta
35
+ ----
36
+
37
+ Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push "lib"
6
+ t.test_files = FileList[File.expand_path('../test/**/*_test.rb', __FILE__)]
7
+ t.verbose = true
8
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "heroku-qc-autoscale/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "heroku-qc-autoscale"
7
+ s.version = Heroku::QC::Autoscale::VERSION
8
+ s.authors = ["David Bradford"]
9
+ s.email = ["david@zerobearing.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Auto scale your QueueClassic workers on Heroku. Inspired by mirthlab's Resque auto scale gem.}
12
+ s.description = %q{Add to a Rails 3.x project to auto scale QueueClassic workers on heroku.}
13
+
14
+ s.rubyforge_project = "heroku-qc-autoscale"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_runtime_dependency "heroku-api", "~> 0.3.2"
23
+ s.add_runtime_dependency "activesupport", "~> 3.1.7"
24
+ s.add_runtime_dependency "i18n", "~> 0.6.0"
25
+ s.add_runtime_dependency "queue_classic", "~> 2.0.1"
26
+
27
+ s.add_development_dependency "minitest", "~> 3.3.0"
28
+ s.add_development_dependency "pry"
29
+ end
@@ -0,0 +1,49 @@
1
+ module Heroku
2
+ class Scaler
3
+
4
+ class << self
5
+ def workers
6
+ client.get_app(app).body.fetch("workers", 0).to_i
7
+ end
8
+
9
+ def workers=(qty)
10
+ client.put_workers(app, qty)
11
+ end
12
+
13
+ def job_count
14
+ ::QC::Queries.count.to_i
15
+ end
16
+
17
+ # scale workers based on scale
18
+ def up
19
+ self.workers = calculate_required_workers unless calculate_required_workers <= workers
20
+ end
21
+
22
+ # shutdown if no jobs exist
23
+ def down
24
+ self.workers = 0 if job_count < 1
25
+ end
26
+
27
+ def calculate_required_workers
28
+ scale.rindex{|x| x <= job_count} + 1
29
+ end
30
+
31
+ # the app to scale
32
+ def app
33
+ ::Heroku::QC::Autoscale.app
34
+ end
35
+
36
+ # the scale
37
+ def scale
38
+ ::Heroku::QC::Autoscale.scale || [1, 15, 30, 40, 50]
39
+ end
40
+
41
+ # heroku api client
42
+ def client
43
+ @@client ||= ::Heroku::API.new( ::Heroku::QC::Autoscale.heroku_params )
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,7 @@
1
+ module Heroku
2
+ module QC
3
+ module Autoscale
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/callbacks'
3
+ require 'active_support/core_ext/module'
4
+ require 'queue_classic'
5
+ require 'heroku-api'
6
+
7
+ require "heroku-qc-autoscale/version"
8
+
9
+ require "qc/callbacks"
10
+ require "qc/auto_scale"
11
+ require "heroku/scaler"
12
+
13
+ module Heroku
14
+ module QC
15
+ module Autoscale
16
+ mattr_accessor :api_key, :app, :mock, :scale, :active
17
+
18
+ def self.config(&block)
19
+ yield(self)
20
+ activate if active == true
21
+ end
22
+
23
+ def self.activate
24
+ ::QC::Queue.send(:include, ::QC::QueueCallbacks)
25
+ end
26
+
27
+ def self.heroku_params
28
+ {
29
+ api_key: self.api_key || ENV['HEROKU_API_KEY'],
30
+ mock: self.mock || false
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ module QC
2
+ class AutoScale
3
+
4
+ def after_enqueue(caller)
5
+ Heroku::Scaler.up
6
+ end
7
+
8
+ def after_delete(caller)
9
+ Heroku::Scaler.down
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module QC
2
+ module QueueCallbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include ActiveSupport::Callbacks
7
+ define_callbacks :enqueue, :delete, :scope => [:kind, :name]
8
+ set_callback :enqueue, :after, QC::AutoScale.new
9
+ set_callback :delete, :after, QC::AutoScale.new
10
+
11
+ alias_method_chain :enqueue, :callbacks
12
+ alias_method_chain :delete, :callbacks
13
+ end
14
+
15
+ def enqueue_with_callbacks(method, *args)
16
+ run_callbacks :enqueue do
17
+ enqueue_without_callbacks(method, *args)
18
+ end
19
+ end
20
+
21
+ def delete_with_callbacks(id)
22
+ run_callbacks :delete do
23
+ delete_without_callbacks(id)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,23 @@
1
+ require_relative "./test_helper"
2
+
3
+ describe Heroku::QC::Autoscale do
4
+
5
+ before do
6
+ Heroku::QC::Autoscale.config do |c|
7
+ c.api_key = "123456"
8
+ end
9
+ end
10
+
11
+ subject { Heroku::QC::Autoscale }
12
+
13
+ it "should have api_key" do
14
+ subject.api_key.must_equal("123456")
15
+ end
16
+
17
+ it "should change api_key at runtime" do
18
+ subject.api_key.must_equal("123456")
19
+ subject.api_key = "654321"
20
+ subject.api_key.must_equal("654321")
21
+ end
22
+
23
+ end
@@ -0,0 +1,71 @@
1
+ require_relative "../test_helper"
2
+
3
+ describe Heroku::Scaler do
4
+ include QCHelper
5
+
6
+ QC.define_singleton_method :log do |*args| nil; end # silence QC logger
7
+
8
+ subject { Heroku::Scaler }
9
+
10
+ it "job_count should be 0" do
11
+ subject.job_count.must_equal(0)
12
+ end
13
+
14
+ it "#workers" do
15
+ with_app do |app|
16
+ subject.workers = 1
17
+ subject.workers.must_equal(1)
18
+
19
+ subject.workers = 2
20
+ subject.workers.must_equal(2)
21
+ end
22
+ end
23
+
24
+ describe "scaling up" do
25
+ it "with 5 jobs" do
26
+ with_app do |app|
27
+ 5.times{ QC.enqueue("Time.now") }
28
+ subject.workers.must_equal(1)
29
+ end
30
+ end
31
+
32
+ it "with 16 jobs" do
33
+ with_app do |app|
34
+ 16.times{ QC.enqueue("Time.now") }
35
+ subject.workers.must_equal(2)
36
+ end
37
+ end
38
+
39
+ it "with 31 jobs" do
40
+ with_app do |app|
41
+ 31.times{ QC.enqueue("Time.now") }
42
+ subject.workers.must_equal(3)
43
+ end
44
+ end
45
+
46
+ it "with 131 jobs" do
47
+ with_app do |app|
48
+ 131.times{ QC.enqueue("Time.now") }
49
+ subject.workers.must_equal(5)
50
+ end
51
+ end
52
+ end
53
+
54
+ describe "scaling down" do
55
+
56
+ it "from 31 workers" do
57
+ with_app do |app|
58
+ # add jobs to queue
59
+ 31.times{ QC.enqueue("Time.now") }
60
+ subject.workers.must_equal(3)
61
+
62
+ # do work and scale back down
63
+ 31.times{ QC::Worker.new.work }
64
+ subject.job_count.must_equal(0)
65
+ subject.workers.must_equal(0)
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "../test_helper"
2
+
3
+ describe QC::Queue do
4
+ subject { QC::Queue.new("default-test") }
5
+
6
+ it "should respond to #enqueue_with_callbacks" do
7
+ subject.must_respond_to(:enqueue_with_callbacks)
8
+ end
9
+
10
+ it "should respond to #delete_with_callbacks" do
11
+ subject.must_respond_to(:delete_with_callbacks)
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module QCHelper
2
+
3
+ def setup
4
+ init_db
5
+ end
6
+
7
+ def teardown
8
+ QC.delete_all
9
+ end
10
+
11
+ def init_db(table_name="queue_classic_jobs")
12
+ QC::Conn.execute("SET client_min_messages TO 'warning'")
13
+ QC::Setup.drop
14
+ QC::Setup.create
15
+ QC::Conn.disconnect
16
+ end
17
+
18
+ end
@@ -0,0 +1,39 @@
1
+ # test/test_helper.rb
2
+
3
+ ENV["TEST"] = 'true'
4
+ ENV["DATABASE_URL"] ||= "postgres:///queue_classic_test"
5
+
6
+ $:.unshift File.expand_path("../../lib")
7
+ require 'rubygems'
8
+ require 'minitest/autorun'
9
+ require 'pry'
10
+ require 'time'
11
+
12
+ require 'heroku-qc-autoscale'
13
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
14
+
15
+ Heroku::QC::Autoscale.config do |c|
16
+ c.api_key = "123456"
17
+ c.app = "racehq-test"
18
+ c.mock = true
19
+ c.scale = [1, 15, 30, 40, 50]
20
+ c.active = true
21
+ end
22
+
23
+ # borrowed from heroku-api test helper
24
+ def with_app(params={}, &block)
25
+ params.merge!('name' => Heroku::QC::Autoscale.app) unless params.key?("name")
26
+ heroku = Heroku::Scaler.client
27
+
28
+ begin
29
+ data = heroku.post_app(params).body
30
+ @name = data['name']
31
+ ready = false
32
+ until ready
33
+ ready = heroku.request(:method => :put, :path => "/apps/#{@name}/status").status == 201
34
+ end
35
+ yield(data)
36
+ ensure
37
+ heroku.delete_app(@name) rescue nil
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heroku-qc-autoscale
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Bradford
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: heroku-api
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.3.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.3.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 3.1.7
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 3.1.7
46
+ - !ruby/object:Gem::Dependency
47
+ name: i18n
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.6.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: queue_classic
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.0.1
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.0.1
78
+ - !ruby/object:Gem::Dependency
79
+ name: minitest
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 3.3.0
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 3.3.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: pry
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Add to a Rails 3.x project to auto scale QueueClassic workers on heroku.
111
+ email:
112
+ - david@zerobearing.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - .rvmrc
119
+ - Gemfile
120
+ - README.md
121
+ - Rakefile
122
+ - heroku-qc-autoscale.gemspec
123
+ - lib/heroku-qc-autoscale.rb
124
+ - lib/heroku-qc-autoscale/version.rb
125
+ - lib/heroku/scaler.rb
126
+ - lib/qc/auto_scale.rb
127
+ - lib/qc/callbacks.rb
128
+ - test/autoscale_test.rb
129
+ - test/heroku/scaler_test.rb
130
+ - test/qc/queue_test.rb
131
+ - test/support/qc_helper.rb
132
+ - test/test_helper.rb
133
+ homepage: ''
134
+ licenses: []
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ! '>='
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ segments:
146
+ - 0
147
+ hash: -3951171285968165463
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ segments:
155
+ - 0
156
+ hash: -3951171285968165463
157
+ requirements: []
158
+ rubyforge_project: heroku-qc-autoscale
159
+ rubygems_version: 1.8.21
160
+ signing_key:
161
+ specification_version: 3
162
+ summary: Auto scale your QueueClassic workers on Heroku. Inspired by mirthlab's Resque
163
+ auto scale gem.
164
+ test_files:
165
+ - test/autoscale_test.rb
166
+ - test/heroku/scaler_test.rb
167
+ - test/qc/queue_test.rb
168
+ - test/support/qc_helper.rb
169
+ - test/test_helper.rb