hipstascale 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,96 @@
1
+ module HipstaScale
2
+ class Application
3
+ attr_reader :name
4
+
5
+ def initialize(name, &block)
6
+ @name = name
7
+ instance_eval(&block) if block_given?
8
+ end
9
+
10
+ # We take what NewRelic returns us and add an extra 20% to it base on the
11
+ # recommendation of this blog post:
12
+ # http://engineering.viki.com/2011/08/10/autoscaling-heroku-dynos/
13
+ def current_load
14
+ (new_relic.busy_percent + 20.0) / 100
15
+ end
16
+
17
+ def heroku_key(key=nil)
18
+ @heroku_key = key unless key.nil?
19
+ @heroku_key || HipstaScale.heroku_key
20
+ end
21
+
22
+ def heroku_ps(name=nil)
23
+ @heroku_ps = name unless name.nil?
24
+ @heroku_ps || HipstaScale.heroku_ps
25
+ end
26
+
27
+ def interval(interval=nil)
28
+ @interval = interval unless interval.nil?
29
+ @interval || HipstaScale.interval
30
+ end
31
+
32
+ def load_limit(load=nil)
33
+ @load_limit = load unless load.nil?
34
+ @load_limit || HipstaScale.load_limit
35
+ end
36
+
37
+ def minimum_processes(minimum=nil)
38
+ @minimum_processes = minimum unless minimum.nil?
39
+ @minimum_processes || HipstaScale.minimum_processes
40
+ end
41
+ alias_method :minimum_processes, :minimum_process
42
+
43
+ def new_relic_id(id=nil)
44
+ @new_relic_id = id unless id.nil?
45
+ @new_relic_id
46
+ end
47
+
48
+ def new_relic_key(key=nil)
49
+ @new_relic_key = key unless key.nil?
50
+ @new_relic_key || HipstaScale.new_relic_key
51
+ end
52
+
53
+ def processes_running
54
+ heroku.processes.count
55
+ end
56
+
57
+ def processes_needed
58
+ processes = ((processes_running * current_load) / load_limit).ceil
59
+ processes = minimum_processes if processes < minimum_processes
60
+ processes = maximum_processes if processes > maximum_processes
61
+ processes
62
+ end
63
+
64
+ def scale(count)
65
+ heroku
66
+ end
67
+
68
+ def scale!
69
+ scale(processes_needed)
70
+ end
71
+
72
+ def tick(&block)
73
+ block.call
74
+ expire!
75
+ end
76
+
77
+ private
78
+
79
+ def expire!
80
+ heroku.expire!
81
+ new_relic.expire!
82
+ end
83
+
84
+ def heroku
85
+ @_heroku ||= Heroku.new(self)
86
+ end
87
+
88
+ def maximum_processes
89
+ 100
90
+ end
91
+
92
+ def new_relic
93
+ @_new_relic ||= NewRelic.new(self)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,38 @@
1
+ module HipstaScale
2
+ module Base
3
+ def app(name, &block)
4
+ HipstaScale.apps << Application.new(name, &block)
5
+ end
6
+
7
+ def interval(interval=nil)
8
+ HipstaScale.interval = interval unless interval.nil?
9
+ HipstaScale.interval
10
+ end
11
+
12
+ def heroku_key(key=nil)
13
+ HipstaScale.heroku_key = key unless key.nil?
14
+ HipstaScale.heroku_key
15
+ end
16
+
17
+ def heroku_ps(name=nil)
18
+ HipstaScale.heroku_ps = name unless name.nil?
19
+ HipstaScale.heroku_ps
20
+ end
21
+
22
+ def load_limit(load=nil)
23
+ HipstaScale.load_limit = load unless load.nil?
24
+ HipstaScale.load_limit
25
+ end
26
+
27
+ def minimum_processes(minimum=nil)
28
+ HipstaScale.minimum_processes = minimum unless minimum.nil?
29
+ HipstaScale.minimum_processes
30
+ end
31
+ alias_method :minimum_processes, :minimum_process
32
+
33
+ def new_relic_key(key=nil)
34
+ HipstaScale.new_relic_key = key unless key.nil?
35
+ HipstaScale.new_relic_key
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+ require 'ostruct'
3
+ require 'rest_client'
4
+
5
+ module HipstaScale
6
+ class Heroku
7
+ attr_reader :app
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def expire!
14
+ @_cache = nil
15
+ end
16
+
17
+ def fetch
18
+ return @_cache unless @_cache.nil?
19
+ json = RestClient.get(url, accept: 'json')
20
+ response = JSON.parse(json)
21
+ @_cache = response.map {|ps| OpenStruct.new(ps) }
22
+ end
23
+
24
+ def processes
25
+ fetch.select {|ps| ps.process.match(app.heroku_ps) }
26
+ end
27
+
28
+ def processes_other
29
+ fetch - processes
30
+ end
31
+
32
+ def scale_processes(count)
33
+ RestClient.post("#{url}/scale?type=#{app.heroku_ps}&qty=#{count}", accept: 'json')
34
+ end
35
+
36
+ private
37
+
38
+ def url
39
+ "https://:#{app.heroku_key}@api.heroku.com/apps/#{app.name}/ps"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ require 'json'
2
+ require 'ostruct'
3
+ require 'rest_client'
4
+
5
+ module HipstaScale
6
+ class NewRelic
7
+ BASE_URL = 'https://api.newrelic.com/api/v1'
8
+
9
+ attr_reader :app
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def busy_percent
16
+ fetch.busy_percent
17
+ end
18
+
19
+ def expire!
20
+ @_cache = nil
21
+ end
22
+
23
+ def fetch
24
+ return @_cache unless @_cache.nil?
25
+ json = RestClient.get(url, 'x-api-key' => app.new_relic_key)
26
+ response = JSON.parse(json)
27
+ @_cache = OpenStruct.new(response.first)
28
+ end
29
+
30
+ private
31
+
32
+ def params
33
+ ''.tap do |params|
34
+ params << 'begin=' << (Time.now - (app.interval * 60)).strftime('%Y-%m-%dT%H:%M:%S%z')
35
+ params << '&end=' << Time.now.strftime('%Y-%m-%dT%H:%M:%S%z')
36
+ params << '&field=' << 'busy_percent'
37
+ params << '&metrics[]=' << 'Instance/Busy'
38
+ end
39
+ end
40
+
41
+ def url
42
+ "#{BASE_URL}/applications/#{app.new_relic_id}/data.json?#{params}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ require 'eventmachine'
2
+
3
+ module HipstaScale
4
+ class Process
5
+
6
+ def self.run!
7
+ EventMachine.run do
8
+ HipstaScale.apps.each do |app|
9
+ Process.new(app)
10
+ # Simple way to make sure all of our external calls don't happen all
11
+ # at once.
12
+ sleep(10)
13
+ end
14
+ end
15
+ end
16
+
17
+ def initialize(app)
18
+ logger.info "Check usage for #{app.name} every #{app.interval * 60} seconds"
19
+ EventMachine.add_periodic_timer(app.interval * 60) do
20
+ app.tick do
21
+ logger.info "[#{app.name.upcase}] Instance usage: %.2f%%" % (app.current_load * 100.0)
22
+ logger.info "[#{app.name.upcase}] Amount of processes needed to reach the %.2f%% of target load: #{app.processes_needed}" % (app.load_limit * 100)
23
+
24
+ if app.processes_needed != app.processes_running
25
+ logger.info "[#{app.name.upcase}] Scaling processes from #{app.processes_running} to #{app.processes_needed}"
26
+ app.scale!
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def logger
35
+ HipstaScale.logger
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ require 'logger'
2
+
3
+ module HipstaScale
4
+ autoload :Application, 'hipsta_scale/application'
5
+ autoload :Base, 'hipsta_scale/base'
6
+ autoload :Heroku, 'hipsta_scale/heroku'
7
+ autoload :NewRelic, 'hipsta_scale/new_relic'
8
+ autoload :Process, 'hipsta_scale/process'
9
+
10
+ # We store the global configuration information in the HipstaScale
11
+ # module itself for easy access across the process.
12
+ class << self
13
+ attr_accessor :heroku_key, :heroku_ps, :interval, :new_relic_key
14
+ attr_writer :apps, :load_limit, :logger, :minimum_processes
15
+ end
16
+
17
+ def self.apps
18
+ @apps ||= []
19
+ end
20
+
21
+ def self.load_limit
22
+ @load_limit || 0.8
23
+ end
24
+
25
+ def self.logger
26
+ @logger || Logger.new(STDOUT)
27
+ end
28
+
29
+ def self.minimum_processes
30
+ @minimum_processes || 1
31
+ end
32
+
33
+ def self.run!
34
+ puts apps.inspect
35
+ end
36
+
37
+ at_exit { Process.run! }
38
+ end
39
+
40
+ # Add out Base class (which is the DSL) to the main object space.
41
+ extend HipstaScale::Base
@@ -0,0 +1 @@
1
+ require 'hipsta_scale'
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hipstascale
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeremy Boles
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-30 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: HipstaScale helps create long-running processes that use NewRelic RPM
15
+ information to scale your Heroku instances before you need it.
16
+ email: jeremy@jeremyboles.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/hipsta_scale/application.rb
22
+ - lib/hipsta_scale/base.rb
23
+ - lib/hipsta_scale/heroku.rb
24
+ - lib/hipsta_scale/new_relic.rb
25
+ - lib/hipsta_scale/process.rb
26
+ - lib/hipsta_scale.rb
27
+ - lib/hipstascale.rb
28
+ homepage: https://github.com/jeremyboles/hipstascale
29
+ licenses: []
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 1.8.11
49
+ signing_key:
50
+ specification_version: 3
51
+ summary: HipstaScale
52
+ test_files: []