scout_apm 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ # Contains methods specific to logging (initializing the log file, applying the log level, applying the log format, etc.)
2
+ module ScoutApm
3
+ class Agent
4
+ module Logging
5
+ def log_path
6
+ "#{environment.root}/log"
7
+ end
8
+
9
+ def init_logger
10
+ @log_file = "#{log_path}/scout_apm.log"
11
+ begin
12
+ @logger = Logger.new(@log_file)
13
+ @logger.level = log_level
14
+ apply_log_format
15
+ rescue Exception => e
16
+ @logger = Logger.new(STDOUT)
17
+ apply_log_format
18
+ @logger.error "Unable to access log file: #{e.message}"
19
+ end
20
+ @logger
21
+ end
22
+
23
+ def apply_log_format
24
+ def logger.format_message(severity, timestamp, progname, msg)
25
+ # since STDOUT isn't exclusive like the scout_apm.log file, apply a prefix.
26
+ prefix = @logdev.dev == STDOUT ? "scout_apm " : ''
27
+ prefix + "[#{timestamp.strftime("%m/%d/%y %H:%M:%S %z")} #{Socket.gethostname} (#{$$})] #{severity} : #{msg}\n"
28
+ end
29
+ end
30
+
31
+ def log_level
32
+ case config.settings['log_level'].downcase
33
+ when "debug" then Logger::DEBUG
34
+ when "info" then Logger::INFO
35
+ when "warn" then Logger::WARN
36
+ when "error" then Logger::ERROR
37
+ when "fatal" then Logger::FATAL
38
+ else Logger::INFO
39
+ end
40
+ end
41
+ end # module Logging
42
+ include Logging
43
+ end # class Agent
44
+ end # moudle ScoutApm
@@ -0,0 +1,110 @@
1
+ # Methods related to sending metrics to scoutapp.com.
2
+ module ScoutApm
3
+ class Agent
4
+ module Reporting
5
+ CA_FILE = File.join( File.dirname(__FILE__), *%w[.. .. .. data cacert.pem] )
6
+ VERIFY_MODE = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
7
+
8
+ # Called in the worker thread. Merges in-memory metrics w/those on disk and reports metrics
9
+ # to the server.
10
+ def process_metrics
11
+ logger.debug "Processing metrics"
12
+ run_samplers
13
+ payload = layaway.deposit_and_deliver
14
+ metrics = payload[:metrics]
15
+ slow_transactions = payload[:slow_transactions]
16
+ if payload.any?
17
+ add_metric_ids(metrics)
18
+ logger.warn "Some data may be lost - metric size is at limit" if metrics.size == ScoutApm::Store::MAX_SIZE
19
+ # for debugging, count the total number of requests
20
+ controller_count = 0
21
+ metrics.each do |meta,stats|
22
+ if meta.metric_name =~ /\AController/
23
+ controller_count += stats.call_count
24
+ end
25
+ end
26
+ payload = Marshal.dump(:metrics => metrics, :slow_transactions => slow_transactions)
27
+ slow_transactions_kb = Marshal.dump(slow_transactions).size/1024 # just for performance debugging
28
+ logger.debug "#{config.settings['name']} Delivering total payload [#{payload.size/1024} KB] for #{controller_count} requests and slow transactions [#{slow_transactions_kb} KB] for #{slow_transactions.size} transactions of durations: #{slow_transactions.map(&:total_call_time).join(',')}."
29
+ response = post( checkin_uri,
30
+ payload,
31
+ "Content-Type" => "application/octet-stream" )
32
+ if response and response.is_a?(Net::HTTPSuccess)
33
+ directives = Marshal.load(response.body)
34
+ self.metric_lookup.merge!(directives[:metric_lookup])
35
+ if directives[:reset]
36
+ logger.info "Resetting metric_lookup."
37
+ self.metric_lookup = Hash.new
38
+ end
39
+ logger.debug "Metric Cache Size: #{metric_lookup.size}"
40
+ elsif response
41
+ logger.warn "Error on checkin to #{checkin_uri.to_s}: #{response.inspect}"
42
+ end
43
+ end
44
+ rescue
45
+ logger.warn "Error on checkin to #{checkin_uri.to_s}"
46
+ logger.info $!.message
47
+ logger.debug $!.backtrace
48
+ end
49
+
50
+ # Before reporting, lookup metric_id for each MetricMeta. This speeds up
51
+ # reporting on the server-side.
52
+ def add_metric_ids(metrics)
53
+ metrics.each do |meta,stats|
54
+ if metric_id = metric_lookup[meta]
55
+ meta.metric_id = metric_id
56
+ end
57
+ end
58
+ end
59
+
60
+ def checkin_uri
61
+ URI.parse("#{config.settings['host']}/apps/checkin.scout?key=#{config.settings['key']}&name=#{CGI.escape(config.settings['name'])}")
62
+ end
63
+
64
+ def post(uri, body, headers = Hash.new)
65
+ response = nil
66
+ request(uri) do |connection|
67
+ post = Net::HTTP::Post.new( uri.path +
68
+ (uri.query ? ('?' + uri.query) : ''),
69
+ HTTP_HEADERS.merge(headers) )
70
+ post.body = body
71
+ response=connection.request(post)
72
+ end
73
+ response
74
+ end
75
+
76
+ def request(uri, &connector)
77
+ response = nil
78
+ response = http(uri).start(&connector)
79
+ logger.debug "got response: #{response.inspect}"
80
+ case response
81
+ when Net::HTTPSuccess, Net::HTTPNotModified
82
+ logger.debug "/checkin OK"
83
+ when Net::HTTPBadRequest
84
+ logger.warn "/checkin FAILED: The Account Key [#{config.settings['key']}] is invalid."
85
+ else
86
+ logger.debug "/checkin FAILED: #{response.inspect}"
87
+ end
88
+ rescue Exception
89
+ logger.debug "Exception sending request to server: #{$!.message}\n#{$!.backtrace}"
90
+ ensure
91
+ response
92
+ end
93
+
94
+ # Take care of the http proxy, if specified in config.
95
+ # Given a blank string, the proxy_uri URI instance's host/port/user/pass will be nil.
96
+ # Net::HTTP::Proxy returns a regular Net::HTTP class if the first argument (host) is nil.
97
+ def http(url)
98
+ proxy_uri = URI.parse(config.settings['proxy'].to_s)
99
+ http = Net::HTTP::Proxy(proxy_uri.host,proxy_uri.port,proxy_uri.user,proxy_uri.password).new(url.host, url.port)
100
+ if url.is_a?(URI::HTTPS)
101
+ http.use_ssl = true
102
+ http.ca_file = CA_FILE
103
+ http.verify_mode = VERIFY_MODE
104
+ end
105
+ http
106
+ end
107
+ end # module Reporting
108
+ include Reporting
109
+ end # class Agent
110
+ end # module ScoutApm
@@ -0,0 +1,217 @@
1
+ module ScoutApm
2
+ # The agent gathers performance data from a Ruby application. One Agent instance is created per-Ruby process.
3
+ #
4
+ # Each Agent object creates a worker thread (unless monitoring is disabled or we're forking).
5
+ # The worker thread wakes up every +Agent#period+, merges in-memory metrics w/those saved to disk,
6
+ # saves tshe merged data to disk, and sends it to the Scout server.
7
+ class Agent
8
+ # Headers passed up with all API requests.
9
+ HTTP_HEADERS = { "Agent-Hostname" => Socket.gethostname }
10
+ # see self.instance
11
+ @@instance = nil
12
+
13
+ # Accessors below are for associated classes
14
+ attr_accessor :store
15
+ attr_accessor :layaway
16
+ attr_accessor :config
17
+ attr_accessor :environment
18
+
19
+ attr_accessor :logger
20
+ attr_accessor :log_file # path to the log file
21
+ attr_accessor :options # options passed to the agent when +#start+ is called.
22
+ attr_accessor :metric_lookup # Hash used to lookup metric ids based on their name and scope
23
+
24
+ # All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
25
+ def self.instance(options = {})
26
+ @@instance ||= self.new(options)
27
+ end
28
+
29
+ # Note - this doesn't start instruments or the worker thread. This is handled via +#start+ as we don't
30
+ # want to start the worker thread or install instrumentation if (1) disabled for this environment (2) a worker thread shouldn't
31
+ # be started (when forking).
32
+ def initialize(options = {})
33
+ @started = false
34
+ @options ||= options
35
+ @store = ScoutApm::Store.new
36
+ @layaway = ScoutApm::Layaway.new
37
+ @config = ScoutApm::Config.new(options[:config_path])
38
+ @metric_lookup = Hash.new
39
+ @process_cpu=ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors)
40
+ @process_memory=ScoutApm::Instruments::Process::ProcessMemory.new
41
+ end
42
+
43
+ def environment
44
+ @environment ||= ScoutApm::Environment.new
45
+ end
46
+
47
+ # This is called via +ScoutApm::Agent.instance.start+ when ScoutApm is required in a Ruby application.
48
+ # It initializes the agent and starts the worker thread (if appropiate).
49
+ def start(options = {})
50
+ @options.merge!(options)
51
+ init_logger
52
+ logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{Socket.gethostname}]"
53
+ if !config.settings['monitor'] and !@options[:force]
54
+ logger.warn "Monitoring isn't enabled for the [#{environment.env}] environment."
55
+ return false
56
+ elsif !environment.app_server
57
+ logger.warn "Couldn't find a supported app server. Not starting agent."
58
+ return false
59
+ elsif started?
60
+ logger.warn "Already started agent."
61
+ return false
62
+ end
63
+ @started = true
64
+ logger.info "Starting monitoring. Framework [#{environment.framework}] App Server [#{environment.app_server}]."
65
+ start_instruments
66
+ if !start_background_worker?
67
+ logger.debug "Not starting worker thread"
68
+ install_passenger_events if environment.app_server == :passenger
69
+ install_unicorn_worker_loop if environment.app_server == :unicorn
70
+ install_rainbows_worker_loop if environment.app_server == :rainbows
71
+ return
72
+ end
73
+ start_background_worker
74
+ handle_exit
75
+ logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
76
+ end
77
+
78
+ # at_exit, calls Agent#shutdown to wrapup metric reporting.
79
+ def handle_exit
80
+ if environment.sinatra? || environment.jruby? || environment.rubinius?
81
+ logger.debug "Exit handler not supported"
82
+ else
83
+ at_exit do
84
+ logger.debug "Shutdown!"
85
+ # MRI 1.9 bug drops exit codes.
86
+ # http://bugs.ruby-lang.org/issues/5218
87
+ if environment.ruby_19?
88
+ status = $!.status if $!.is_a?(SystemExit)
89
+ shutdown
90
+ exit status if status
91
+ else
92
+ shutdown
93
+ end
94
+ end # at_exit
95
+ end
96
+ end
97
+
98
+ # Called via an at_exit handler, it (1) stops the background worker and (2) runs it a final time.
99
+ # The final run ensures metrics are stored locally to the layaway / reported to scoutapp.com. Otherwise,
100
+ # in-memory metrics would be lost and a gap would appear on restarts.
101
+ def shutdown
102
+ return if !started?
103
+ @background_worker.stop
104
+ @background_worker.run_once
105
+ end
106
+
107
+ def started?
108
+ @started
109
+ end
110
+
111
+ def gem_root
112
+ File.expand_path(File.join("..","..",".."), __FILE__)
113
+ end
114
+
115
+ # The worker thread will automatically start UNLESS:
116
+ # * A supported application server isn't detected (example: running via Rails console)
117
+ # * A supported application server is detected, but it forks (Passenger). In this case,
118
+ # the agent is started in the forked process.
119
+ def start_background_worker?
120
+ !environment.forking? or environment.app_server == :thin
121
+ end
122
+
123
+ def install_passenger_events
124
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
125
+ logger.debug "Passenger is starting a worker process. Starting worker thread."
126
+ self.class.instance.start_background_worker
127
+ end
128
+ # The agent's at_exit hook doesn't run when a Passenger process stops.
129
+ # This does run when a process stops.
130
+ PhusionPassenger.on_event(:stopping_worker_process) do
131
+ logger.debug "Passenger is stopping a worker process, shutting down the agent."
132
+ ScoutApm::Agent.instance.shutdown
133
+ end
134
+ end
135
+
136
+ def install_unicorn_worker_loop
137
+ logger.debug "Installing Unicorn worker loop."
138
+ Unicorn::HttpServer.class_eval do
139
+ old = instance_method(:worker_loop)
140
+ define_method(:worker_loop) do |worker|
141
+ ScoutApm::Agent.instance.start_background_worker
142
+ old.bind(self).call(worker)
143
+ end
144
+ end
145
+ end
146
+
147
+ def install_rainbows_worker_loop
148
+ logger.debug "Installing Rainbows worker loop."
149
+ Rainbows::HttpServer.class_eval do
150
+ old = instance_method(:worker_loop)
151
+ define_method(:worker_loop) do |worker|
152
+ ScoutApm::Agent.instance.start_background_worker
153
+ old.bind(self).call(worker)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes,
159
+ # processes data, either saving it to disk or reporting to Scout.
160
+ def start_background_worker
161
+ logger.debug "Creating worker thread."
162
+ @background_worker = ScoutApm::BackgroundWorker.new
163
+ @background_worker_thread = Thread.new do
164
+ @background_worker.start { process_metrics }
165
+ end # thread new
166
+ logger.debug "Done creating worker thread."
167
+ end
168
+
169
+ # Called from #process_metrics, which is run via the background worker.
170
+ def run_samplers
171
+ begin
172
+ cpu_util=@process_cpu.run # returns a hash
173
+ logger.debug "Process CPU: #{cpu_util.inspect} [#{environment.processors} CPU(s)]"
174
+ store.track!("CPU/Utilization",cpu_util,:scope => nil) if cpu_util
175
+ rescue => e
176
+ logger.info "Error reading ProcessCpu"
177
+ logger.debug e.message
178
+ logger.debug e.backtrace.join("\n")
179
+ end
180
+
181
+ begin
182
+ mem_usage=@process_memory.run # returns a single number, in MB
183
+ logger.debug "Process Memory: #{mem_usage}MB"
184
+ store.track!("Memory/Physical",mem_usage,:scope => nil) if mem_usage
185
+ rescue => e
186
+ logger.info "Error reading ProcessMemory"
187
+ logger.debug e.message
188
+ logger.debug e.backtrace.join("\n")
189
+ end
190
+ end
191
+
192
+ # Loads the instrumention logic.
193
+ def load_instruments
194
+ case environment.framework
195
+ when :rails
196
+ require File.expand_path(File.join(File.dirname(__FILE__),'instruments/rails/action_controller_instruments.rb'))
197
+ when :rails3_or_4
198
+ require File.expand_path(File.join(File.dirname(__FILE__),'instruments/rails3_or_4/action_controller_instruments.rb'))
199
+ end
200
+ require File.expand_path(File.join(File.dirname(__FILE__),'instruments/active_record_instruments.rb'))
201
+ require File.expand_path(File.join(File.dirname(__FILE__),'instruments/net_http.rb'))
202
+ require File.expand_path(File.join(File.dirname(__FILE__),'instruments/moped_instruments.rb'))
203
+ require File.expand_path(File.join(File.dirname(__FILE__),'instruments/mongoid_instruments.rb'))
204
+ rescue
205
+ logger.warn "Exception loading instruments:"
206
+ logger.warn $!.message
207
+ logger.warn $!.backtrace
208
+ end
209
+
210
+ # Injects instruments into the Ruby application.
211
+ def start_instruments
212
+ logger.debug "Installing instrumentation"
213
+ load_instruments
214
+ end
215
+
216
+ end # class Agent
217
+ end # module ScoutApm
@@ -0,0 +1,43 @@
1
+ # Used to run a given task every 60 seconds.
2
+ class ScoutApm::BackgroundWorker
3
+ # in seconds, time between when the worker thread wakes up and runs.
4
+ PERIOD = 60
5
+
6
+ def initialize
7
+ @keep_running = true
8
+ end
9
+
10
+ def stop
11
+ @keep_running = false
12
+ end
13
+
14
+ # Runs the task passed to +start+ once.
15
+ def run_once
16
+ @task.call if @task
17
+ end
18
+
19
+ # Starts running the passed block every 60 seconds (starting now).
20
+ def start(&block)
21
+ @task = block
22
+ begin
23
+ ScoutApm::Agent.instance.logger.debug "Starting Background Worker, running every #{PERIOD} seconds"
24
+ next_time = Time.now
25
+ while @keep_running do
26
+ now = Time.now
27
+ while now < next_time
28
+ sleep_time = next_time - now
29
+ sleep(sleep_time) if sleep_time > 0
30
+ now = Time.now
31
+ end
32
+ @task.call
33
+ while next_time <= now
34
+ next_time += PERIOD
35
+ end
36
+ end
37
+ rescue
38
+ ScoutApm::Agent.instance.logger.debug "Background Worker Exception!!!!!!!"
39
+ ScoutApm::Agent.instance.logger.debug $!.message
40
+ ScoutApm::Agent.instance.logger.debug $!.backtrace
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ module ScoutApm
2
+ class Config
3
+ DEFAULTS = {
4
+ 'host' => 'https://apm.scoutapp.com',
5
+ 'log_level' => 'info'
6
+ }
7
+
8
+ def initialize(config_path = nil)
9
+ @config_path = config_path
10
+ end
11
+
12
+ def settings
13
+ return @settings if @settings
14
+ load_file
15
+ end
16
+
17
+ def config_path
18
+ @config_path || File.join(ScoutApm::Agent.instance.environment.root,"config","scout_apm.yml")
19
+ end
20
+
21
+ def config_file
22
+ File.expand_path(config_path)
23
+ end
24
+
25
+ def load_file
26
+ begin
27
+ if !File.exist?(config_file)
28
+ ScoutApm::Agent.instance.logger.warn "No config file found at [#{config_file}]."
29
+ @settings = {}
30
+ else
31
+ @settings = YAML.load(ERB.new(File.read(config_file)).result(binding))[ScoutApm::Agent.instance.environment.env] || {}
32
+ end
33
+ rescue Exception => e
34
+ ScoutApm::Agent.instance.logger.warn "Unable to load the config file."
35
+ ScoutApm::Agent.instance.logger.warn e.message
36
+ ScoutApm::Agent.instance.logger.warn e.backtrace
37
+ @settings = {}
38
+ end
39
+ @settings = DEFAULTS.merge(@settings)
40
+ end
41
+ end # Config
42
+ end # ScoutApm
@@ -0,0 +1,105 @@
1
+ # Encapsulates adding context to requests. Context is stored via a simple Hash.
2
+ #
3
+ # There are 2 types of context: User and Extra.
4
+ # For user-specific context, use @Context#add_user@.
5
+ # For misc context, use @Context#add@.
6
+ class ScoutApm::Context
7
+
8
+ def initialize
9
+ @extra = {}
10
+ @user = {}
11
+ end
12
+
13
+ # Generates a hash representation of the Context.
14
+ # Example: {:monthly_spend => 100, :user => {:ip => '127.0.0.1'}}
15
+ def to_hash
16
+ @extra.merge({:user => @user})
17
+ end
18
+
19
+ def self.current
20
+ Thread.current[:scout_context] ||= new
21
+ end
22
+
23
+ def self.clear!
24
+ Thread.current[:scout_context] = nil
25
+ end
26
+
27
+ # Add context
28
+ # ScoutApm::Context.add(account: current_account.name)
29
+ def add(hash)
30
+ update_context(:extra,hash)
31
+ end
32
+
33
+ def add_user(hash)
34
+ update_context(:user,hash)
35
+ end
36
+
37
+ # Convenience accessor so you can just call @ScoutAPM::Context#add@
38
+ def self.add(hash)
39
+ self.current.add(hash)
40
+ end
41
+
42
+ # Convenience accessor so you can just call @ScoutAPM::Context#add_user@
43
+ def self.add_user(hash)
44
+ self.current.add_user(hash)
45
+ end
46
+
47
+ private
48
+
49
+ def update_context(attr,hash)
50
+ valid_hash = Hash.new
51
+ # iterate over the hash of new context, adding to the valid_hash if validation checks pass.
52
+ hash.each do |key,value|
53
+ # does both checks so we can get logging info on the value even if the key is invalid.
54
+ key_valid = key_valid?({key => value})
55
+ value_valid = value_valid?({key => value})
56
+ if key_valid and value_valid
57
+ valid_hash[key] = value
58
+ end
59
+ end
60
+
61
+ if valid_hash.any?
62
+ instance_variable_get("@#{attr.to_s}").merge!(valid_hash)
63
+ end
64
+ end
65
+
66
+ # Returns true if the obj is one of the provided valid classes.
67
+ def valid_type?(classes, obj)
68
+ valid_type = false
69
+ classes.each do |klass|
70
+ if obj.is_a?(klass)
71
+ valid_type = true
72
+ break
73
+ end
74
+ end
75
+ valid_type
76
+ end
77
+
78
+ # take the entire Hash vs. just the value so the logger output is more helpful on error.
79
+ def value_valid?(key_value)
80
+ # ensure one of our accepted types.
81
+ value = key_value.values.last
82
+ if !valid_type?([String, Symbol, Numeric, Time, Date, TrueClass, FalseClass],value)
83
+ ScoutApm::Agent.instance.logger.warn "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
84
+ false
85
+ else
86
+ true
87
+ end
88
+ end
89
+
90
+ # for consistently with #value_valid?, takes a hash eventhough the value isn't yet used.
91
+ def key_valid?(key_value)
92
+ key = key_value.keys.first
93
+ # ensure a string or a symbol
94
+ if !valid_type?([String, Symbol],key)
95
+ ScoutApm::Agent.instance.logger.warn "The key [#{key}] is not a valid type [#{key.class}]."
96
+ return false
97
+ end
98
+ # only alphanumeric, dash, and underscore allowed.
99
+ if key.to_s.match(/[^\w-]/)
100
+ ScoutApm::Agent.instance.logger.warn "They key name [#{key}] is not valid."
101
+ return false
102
+ end
103
+ true
104
+ end
105
+ end
@@ -0,0 +1,135 @@
1
+ # Used to retrieve environment information for this application.
2
+ module ScoutApm
3
+ class Environment
4
+ def env
5
+ @env ||= case framework
6
+ when :rails then RAILS_ENV.dup
7
+ when :rails3_or_4 then Rails.env
8
+ when :sinatra
9
+ ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
10
+ end
11
+ end
12
+
13
+ def framework
14
+ @framework ||= case
15
+ when defined?(::Rails) && defined?(ActionController)
16
+ if Rails::VERSION::MAJOR < 3
17
+ :rails
18
+ else
19
+ :rails3_or_4
20
+ end
21
+ when defined?(::Sinatra) && defined?(::Sinatra::Base) then :sinatra
22
+ else :ruby
23
+ end
24
+ end
25
+
26
+ def processors
27
+ return @processors if @processors
28
+ unless @processors
29
+ proc_file = '/proc/cpuinfo'
30
+ if !File.exist?(proc_file)
31
+ @processors = 1
32
+ elsif `cat #{proc_file} | grep 'model name' | wc -l` =~ /(\d+)/
33
+ @processors = $1.to_i
34
+ end
35
+ if @processors < 1
36
+ @processors = 1
37
+ end
38
+ end
39
+ @processors
40
+ end
41
+
42
+ def root
43
+ if framework == :rails
44
+ RAILS_ROOT.to_s
45
+ elsif framework == :rails3_or_4
46
+ Rails.root
47
+ elsif framework == :sinatra
48
+ Sinatra::Application.root
49
+ else
50
+ '.'
51
+ end
52
+ end
53
+
54
+ # This needs to be improved. Frequently, multiple app servers gem are present and which
55
+ # ever is checked first becomes the designated app server.
56
+ #
57
+ # I've put Thin and Webrick last as they are often used in development and included in Gemfiles
58
+ # but less likely used in production.
59
+ #
60
+ # Next step: (1) list out all detected app servers (2) install hooks for those that need it (passenger, rainbows, unicorn).
61
+ #
62
+ # Believe the biggest downside is the master process for forking app servers will get a background worker. Not sure how this will
63
+ # impact metrics (it shouldn't process requests).
64
+ def app_server
65
+ @app_server ||= if passenger? then :passenger
66
+ elsif rainbows? then :rainbows
67
+ elsif unicorn? then :unicorn
68
+ elsif thin? then :thin
69
+ elsif webrick? then :webrick
70
+ else nil
71
+ end
72
+ end
73
+
74
+ ### app server related-checks
75
+
76
+ def thin?
77
+ if defined?(::Thin) && defined?(::Thin::Server)
78
+ # Ensure Thin is actually initialized. It could just be required and not running.
79
+ ObjectSpace.each_object(Thin::Server) { |x| return true }
80
+ false
81
+ end
82
+ end
83
+
84
+ # Called via +#forking?+ since Passenger forks. Adds an event listener to start the worker thread
85
+ # inside the passenger worker process.
86
+ # Background: http://www.modrails.com/documentation/Users%20guide%20Nginx.html#spawning%5Fmethods%5Fexplained
87
+ def passenger?
88
+ (defined?(::Passenger) && defined?(::Passenger::AbstractServer)) || defined?(::PhusionPassenger)
89
+ end
90
+
91
+ def webrick?
92
+ defined?(::WEBrick) && defined?(::WEBrick::VERSION)
93
+ end
94
+
95
+ def rainbows?
96
+ if defined?(::Rainbows) && defined?(::Rainbows::HttpServer)
97
+ ObjectSpace.each_object(::Rainbows::HttpServer) { |x| return true }
98
+ end
99
+ end
100
+
101
+ def unicorn?
102
+ if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
103
+ # Ensure Unicorn is actually initialized. It could just be required and not running.
104
+ ObjectSpace.each_object(::Unicorn::HttpServer) { |x| return true }
105
+ end
106
+ end
107
+
108
+ # If forking, don't start worker thread in the master process. Since it's started as a Thread, it won't survive
109
+ # the fork.
110
+ def forking?
111
+ passenger? or unicorn? or rainbows?
112
+ end
113
+
114
+ ### ruby checks
115
+
116
+ def rubinius?
117
+ RUBY_VERSION =~ /rubinius/i
118
+ end
119
+
120
+ def jruby?
121
+ defined?(JRuby)
122
+ end
123
+
124
+ def ruby_19?
125
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/)
126
+ end
127
+
128
+ ### framework checks
129
+
130
+ def sinatra?
131
+ defined?(Sinatra::Application)
132
+ end
133
+
134
+ end # class Environemnt
135
+ end