heroku-vector 0.0.2

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.
@@ -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