heroku-vector 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 00d6ccf09122d48c58eabcc34fb1a8bb16a3696e
4
+ data.tar.gz: be88b82fe45f6764c76dd0d35e7fb020e1223a92
5
+ SHA512:
6
+ metadata.gz: 20ddb99b29041e06d78111a9dd8a3092160855bf1b1ff9e640228caae7964a6f46198f709bf68cf4cc7a105429f8f171bebd4872463764f91506b898d5f1260f
7
+ data.tar.gz: 5c0052f0b3e3b3f227ee0124ed971a8ff341e66a8aec3f92c6632d16923a51fb1342e447a1a56c2d1deb00a51811e279f6d517a524ff435421f742de6ba5389f
@@ -0,0 +1,37 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
35
+ .env
36
+ config.rb
37
+
@@ -0,0 +1,10 @@
1
+ # Releases / Changes
2
+
3
+ ## 0.0.2
4
+
5
+ * Config/usability fixes
6
+ * Awesome Ascii Logo
7
+
8
+ ## 0.0.1
9
+
10
+ * Initial gem release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
@@ -0,0 +1,95 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ heroku-vector (0.0.2)
5
+ activeresource
6
+ dotenv
7
+ eventmachine
8
+ heroku-api
9
+ newrelic_api
10
+ redis
11
+ redis-namespace
12
+ sidekiq
13
+
14
+ GEM
15
+ remote: https://rubygems.org/
16
+ specs:
17
+ activemodel (4.1.6)
18
+ activesupport (= 4.1.6)
19
+ builder (~> 3.1)
20
+ activeresource (4.0.0)
21
+ activemodel (~> 4.0)
22
+ activesupport (~> 4.0)
23
+ rails-observers (~> 0.1.1)
24
+ activesupport (4.1.6)
25
+ i18n (~> 0.6, >= 0.6.9)
26
+ json (~> 1.7, >= 1.7.7)
27
+ minitest (~> 5.1)
28
+ thread_safe (~> 0.1)
29
+ tzinfo (~> 1.1)
30
+ addressable (2.3.6)
31
+ builder (3.2.2)
32
+ celluloid (0.15.2)
33
+ timers (~> 1.1.0)
34
+ coderay (1.1.0)
35
+ connection_pool (2.0.0)
36
+ crack (0.4.2)
37
+ safe_yaml (~> 1.0.0)
38
+ dotenv (0.11.1)
39
+ dotenv-deployment (~> 0.0.2)
40
+ dotenv-deployment (0.0.2)
41
+ eventmachine (1.0.3)
42
+ excon (0.39.5)
43
+ heroku-api (0.3.19)
44
+ excon (~> 0.38)
45
+ multi_json (~> 1.8)
46
+ i18n (0.6.11)
47
+ json (1.8.1)
48
+ metaclass (0.0.4)
49
+ method_source (0.8.2)
50
+ minitest (5.4.0)
51
+ mocha (1.1.0)
52
+ metaclass (~> 0.0.1)
53
+ multi_json (1.10.1)
54
+ newrelic_api (1.2.4)
55
+ pry (0.10.1)
56
+ coderay (~> 1.1.0)
57
+ method_source (~> 0.8.1)
58
+ slop (~> 3.4)
59
+ rails-observers (0.1.2)
60
+ activemodel (~> 4.0)
61
+ rake (10.3.2)
62
+ redis (3.1.0)
63
+ redis-namespace (1.5.1)
64
+ redis (~> 3.0, >= 3.0.4)
65
+ safe_yaml (1.0.3)
66
+ sidekiq (3.2.5)
67
+ celluloid (= 0.15.2)
68
+ connection_pool (>= 2.0.0)
69
+ json
70
+ redis (>= 3.0.6)
71
+ redis-namespace (>= 1.3.1)
72
+ slop (3.6.0)
73
+ thread_safe (0.3.4)
74
+ timecop (0.7.1)
75
+ timers (1.1.0)
76
+ tzinfo (1.2.2)
77
+ thread_safe (~> 0.1)
78
+ vcr (2.9.2)
79
+ webmock (1.18.0)
80
+ addressable (>= 2.3.6)
81
+ crack (>= 0.3.2)
82
+
83
+ PLATFORMS
84
+ ruby
85
+
86
+ DEPENDENCIES
87
+ bundler (~> 1.6)
88
+ heroku-vector!
89
+ minitest
90
+ mocha
91
+ pry
92
+ rake
93
+ timecop
94
+ vcr
95
+ webmock
@@ -0,0 +1,171 @@
1
+ # Heroku Vector
2
+
3
+ ```bash
4
+ ___ ___ __
5
+ / | \ ____ _______ ____ | | __ __ __
6
+ / ~ \_/ __ \ \_ __ \ / _ \ | |/ /| | \
7
+ \ Y /\ ___/ | | \/( <_> )| < | | /
8
+ \___|_ / \___ > |__| \____/ |__|_ \|____/
9
+ ____ ____/ __ \/
10
+ \ \ / / ____ ____ _/ |_ ____ _______
11
+ \ Y / _/ __ \ _/ ___\ \ __\ / _ \ \_ __ \
12
+ \ / \ ___/ \ \___ | | ( <_> ) | | \/
13
+ \___/ \___ > \___ > |__| \____/ |__|
14
+ \/ \/
15
+ ```
16
+
17
+ Simple, linear auto scaling for Heroku dynos.
18
+
19
+ Heroku Vector runs as a multi-threaded process that samples production metrics and linearly scales Heroku dynos up or down as they change.
20
+
21
+ Web Dynos can be scaled using the amount of traffic (RPM) from NewRelic. Sidekiq dynos can be scaled based on how many worker threads are busy.
22
+
23
+ ## Installation
24
+
25
+ Install this gem:
26
+
27
+ $ gem install heroku_vector
28
+
29
+ And then run the scaler proces:
30
+
31
+ $ heroku_vector --help
32
+
33
+ ## Configuration
34
+
35
+ This auto scaler will sample metrics and use the metric values to scale different types of Heroku Dynos up or down.
36
+
37
+ ```ruby
38
+
39
+ # Example config.rb file
40
+ HerokuVector.configure do |config|
41
+
42
+ # Scale 1 web dyno for each 300rpm of traffic
43
+ config.add_dyno_scaler('web', {
44
+ source: HerokuVector::Source::NewRelic,
45
+ period: 60,
46
+ min_dynos: 1,
47
+ max_dynos: 10,
48
+ min_value: 100,
49
+ max_value: 300
50
+ })
51
+
52
+ # Manually specify app config in config.rb like so
53
+ # Alternately, use environment variables
54
+ config.newrelic_api_key = '222222222222222'
55
+
56
+ end
57
+ ```
58
+
59
+ Application config can be wired up in the config.rb file, or passed in via the system Environment or an Environment file:
60
+
61
+ ```bash
62
+ HEROKU_APP_NAME=your-app-name
63
+ HEROKU_API_KEY=1111111111111111
64
+ REDIS_URL=redis://redis.yourcompany.com/
65
+ SIDEKIQ_REDIS_NAMESPACE=sidekiq
66
+ NEWRELIC_API_KEY=222222222222222
67
+ ```
68
+
69
+ ## heroku_vector daemon:
70
+ ```bash
71
+ [master ~/src/heroku-vector]$> ./bin/heroku_vector --help
72
+ heroku_vector: auto-scale dynos on Heroku
73
+
74
+ Usage: heroku_vector [options]
75
+ heroku_vector -s
76
+
77
+ -s, --sample Sample values and exit
78
+ -d, --daemonize Daemonize process
79
+ -e, --envfile PATH Environment file (default: .env)
80
+ -c, --config PATH Config file (default: config.rb)
81
+ -p, --pidfile PATH Daemon pid file (default: heroku_vector.pid)
82
+ -x, --loglevel LEVEL Logging level [fatal/warn/info/debug] (default is info)
83
+ -l, --logfile PATH Logfile path for daemon
84
+ -h, --help Show this message
85
+ ```
86
+
87
+ Once you've configured your API keys and host names, try taking a sample from all your sources:
88
+
89
+ ```bash
90
+ heroku_vector: {:daemonize=>false, :envfile=>"/Users/wpeterson/src/heroku-vector/.env", :config=>"/Users/wpeterson/src/heroku-vector/config.rb", :sample=>true}
91
+ HerokuVector::Source::NewRelic: 8490.0 RPM
92
+ HerokuVector::Source::Sidekiq: 23 busy threads
93
+ ```
94
+
95
+ ## Logging and Debugging
96
+
97
+ Heroku Vector logs either to `STDOUT` or a logfile with useful information about state changes and scaling events.
98
+
99
+ ```bash
100
+ 2014-09-12T16:05:48.473Z INFO: Loading config from '/home/ubuntu/polar-auto-scale/config.rb'
101
+ 2014-09-12T16:05:48.474Z INFO: Loading Scaler: web, {:source=>HerokuVector::Source::NewRelic, :period=>60, :min_dynos=>1, :max_dynos=>4, :min_value=>1000, :max_value=>3000}
102
+ 2014-09-12T16:05:48.474Z INFO: Loading Scaler: worker, {:source=>HerokuVector::Source::Sidekiq, :period=>5, :min_dynos=>1, :max_dynos=>10, :min_value=>0.5, :max_value=>3, :scale_up_by=>3, :scale_down_by=>1}
103
+ 2014-09-12T16:45:01.742Z INFO: Heroku.scale_dynos(worker, 4)
104
+ 2014-09-12T16:50:03.164Z INFO: worker: 4 dynos - 0.4 busy threads below 2.0 - scaling down
105
+ 2014-09-12T16:50:03.247Z INFO: Heroku.scale_dynos(worker, 3)
106
+ 2014-09-12T16:55:04.559Z INFO: worker: 3 dynos - 0.2 busy threads below 1.5 - scaling down
107
+ 2014-09-12T16:55:04.646Z INFO: Heroku.scale_dynos(worker, 2)
108
+ 2014-09-12T17:00:08.391Z INFO: worker: 2 dynos - 0.9 busy threads below 1.0 - scaling down
109
+ 2014-09-12T17:00:08.492Z INFO: Heroku.scale_dynos(worker, 1)
110
+ ```
111
+
112
+ If you're debugging a problem, you can turn on verbose logging by setting the `debug` log level:
113
+
114
+ $ heroku_vector -x debug
115
+
116
+ ## Architecture
117
+
118
+ ![Architecture](https://dl.dropboxusercontent.com/s/a133uy8e0ohwp9t/Architecture.png?dl=0)
119
+
120
+ The auto-scaler runs as a single process, either interactively or as a daemon (`ProcessManager`). Within that process, the `Worker` spawn an EventMachine event loop and runs each `DynoScaler` in it's own thread. Periodically, each `DynoScaler` will sample data and evaluate the scale of your dynos. When the scale of your dynos doesn't match your traffic, the `DynoScaler` will use the Heroku API to scale your dynos up or down.
121
+
122
+ ## Adding a new Source
123
+
124
+ Data is sampled from generic `Source` classes in the Ruby namespace `HerokuVector::Source` and wired up in your configuration. Source objects have a simple contract:
125
+
126
+ * `#sample` - returns a numeric value for this source at this time
127
+ * `#units` - string that describes the data, like 'RPM' for NewRelic
128
+
129
+ You can define your own `Source` classes and then reference them within your config:
130
+
131
+ ```ruby
132
+ # my_data_source.rb
133
+ module HerokuVector::Source
134
+ class MyDataSource
135
+ def sample
136
+ # Always returns 1, simple test case
137
+ 1
138
+ end
139
+
140
+ def units
141
+ 'foos'
142
+ end
143
+ end
144
+ end
145
+
146
+ # Wire Up Your Source in config.rb
147
+ require 'path/to/my_data_source'
148
+
149
+ HerokuVector.configure do |config|
150
+
151
+ # Scale 1 web dyno for each 300rpm of traffic
152
+ config.add_dyno_scaler('web', {
153
+ source: HerokuVector::Source::MyDataSource,
154
+ period: 1
155
+ })
156
+
157
+ ```
158
+
159
+ ## Contributors: :heart:
160
+
161
+ * Your Name Could Go Here
162
+
163
+ ## Contributing
164
+
165
+ 1. Fork it
166
+ 2. Bundle Install (`bundle install`)
167
+ 3. Run the Tests (`rake test`)
168
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
169
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
170
+ 4. Push to the branch (`git push origin my-new-feature`)
171
+ 5. Create new Pull Request
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ require 'dotenv'
7
+ require 'optparse'
8
+
9
+ options = {
10
+ :daemonize => false,
11
+ :envfile => File.expand_path( File.dirname(__FILE__)+'/../.env' ),
12
+ :config => File.expand_path( File.dirname(__FILE__)+'/../config.rb' ),
13
+ :sample => false
14
+ }
15
+
16
+ def show_usage_and_exit
17
+ puts @parser
18
+ exit
19
+ end
20
+
21
+ @parser = OptionParser.new do |opts|
22
+ opts.banner = <<EOL
23
+ heroku_vector: auto-scale dynos on Heroku
24
+
25
+ Usage: heroku_vector [options]
26
+ heroku_vector -s
27
+
28
+ EOL
29
+
30
+ opts.on('-s', '--sample', "Sample values and exit") {|v| options[:sample] = true }
31
+ opts.on('-d', '--daemonize', "Daemonize process") {|v| options[:daemonize] = true }
32
+ opts.on('-e', '--envfile PATH', "Environment file (default: .env)") {|v| options[:envfile] = v }
33
+ opts.on('-c', '--config PATH', "Config file (default: config.rb)") {|v| options[:config] = v }
34
+ opts.on('-p', '--pidfile PATH', "Daemon pid file (default: heroku_vector.pid)") {|v| options[:pidfile] = v }
35
+ opts.on('-x', '--loglevel LEVEL', "Logging level [fatal/warn/info/debug] (default is info)") {|v| options[:loglevel] = v }
36
+ opts.on('-l', '--logfile PATH', "Logfile path for daemon") {|v| options[:logfile] = v }
37
+ opts.on_tail("-h", "--help", "Show this message") { show_usage_and_exit }
38
+ end
39
+ @parser.parse!(ARGV)
40
+
41
+ if options[:envfile] && File.exist?(options[:envfile])
42
+ env = Dotenv.load(options[:envfile])
43
+ end
44
+
45
+ # Write a default pidfile when daemonized
46
+ if options[:daemonize]
47
+ options[:pidfile] ||= File.expand_path( File.dirname(__FILE__)+'/../heroku_vector.pid' )
48
+ options[:logfile] ||= File.expand_path( File.dirname(__FILE__)+'/../heroku_vector.log' )
49
+ end
50
+
51
+ puts "heroku_vector: #{options.inspect}"
52
+
53
+ # Load heroku_vector after all options parses and environment loaded
54
+ require 'heroku_vector'
55
+
56
+ if options[:loglevel]
57
+ level = Logger.const_get(options[:loglevel].upcase) rescue nil
58
+ HerokuVector.logger.level = level if level
59
+ end
60
+
61
+ if options[:sample]
62
+ # Print out a sample value for each valid Source class
63
+ HerokuVector::Source.constants.each do |constant|
64
+ clazz = HerokuVector::Source.const_get(constant)
65
+ next unless clazz.class == Class
66
+
67
+ source = clazz.new
68
+ puts "#{clazz}: #{source.sample} #{source.unit}"
69
+ end
70
+
71
+ exit 0
72
+ end
73
+
74
+ logo_ascii = <<EOL
75
+
76
+ ___ ___ __
77
+ / | \\ ____ _______ ____ | | __ __ __
78
+ / ~ \\_/ __ \\ \\_ __ \\ / _ \\ | |/ /| | \\
79
+ \\ Y /\\ ___/ | | \\/( <_> )| < | | /
80
+ \\___|_ / \\___ > |__| \\____/ |__|_ \\|____/
81
+ ____ ____/ __ \\/
82
+ \\ \\ / / ____ ____ _/ |_ ____ _______
83
+ \\ Y / _/ __ \\ _/ ___\\ \\ __\\ / _ \\ \\_ __ \\
84
+ \\ / \\ ___/ \\ \\___ | | ( <_> ) | | \\/
85
+ \\___/ \\___ > \\___ > |__| \\____/ |__|
86
+ \\/ \\/
87
+ EOL
88
+ puts logo_ascii
89
+
90
+ manager = HerokuVector::ProcessManager.new(options)
91
+ manager.start
@@ -0,0 +1,28 @@
1
+ # Example config.rb file
2
+ # Copy this to config.rb and edit for your configuration
3
+
4
+ HerokuVector.configure do |config|
5
+
6
+ # Scale 1 web dyno for each 300rpm of traffic
7
+ config.add_dyno_scaler('web', {
8
+ source: HerokuVector::Source::NewRelic,
9
+ period: 60,
10
+ min_dynos: 1,
11
+ max_dynos: 10,
12
+ min_value: 100,
13
+ max_value: 300
14
+ })
15
+
16
+ # Scale up Sidekiq workers when there are 6 or more busy threads per worker
17
+ # Scale up two dynos at a time, but scale down 1 dyno at a time
18
+ config.add_dyno_scaler('worker', {
19
+ source: HerokuVector::Source::Sidekiq,
20
+ period: 5,
21
+ min_dynos: 1,
22
+ max_dynos: 10,
23
+ min_value: 0.5,
24
+ max_value: 6,
25
+ scale_up_by: 2,
26
+ scale_down_by: 1
27
+ })
28
+ end