scout_rails_proxy_proxy 1.0.5
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/.DS_Store +0 -0
- data/.gitignore +5 -0
- data/CHANGELOG.markdown +50 -0
- data/Gemfile +4 -0
- data/README.markdown +44 -0
- data/Rakefile +1 -0
- data/lib/scout_rails_proxy.rb +32 -0
- data/lib/scout_rails_proxy/agent.rb +319 -0
- data/lib/scout_rails_proxy/config.rb +34 -0
- data/lib/scout_rails_proxy/environment.rb +122 -0
- data/lib/scout_rails_proxy/instruments/active_record_instruments.rb +83 -0
- data/lib/scout_rails_proxy/instruments/net_http.rb +14 -0
- data/lib/scout_rails_proxy/instruments/process/process_cpu.rb +27 -0
- data/lib/scout_rails_proxy/instruments/process/process_memory.rb +40 -0
- data/lib/scout_rails_proxy/instruments/rails/action_controller_instruments.rb +46 -0
- data/lib/scout_rails_proxy/instruments/rails3/action_controller_instruments.rb +38 -0
- data/lib/scout_rails_proxy/instruments/sinatra_instruments.rb +33 -0
- data/lib/scout_rails_proxy/layaway.rb +76 -0
- data/lib/scout_rails_proxy/layaway_file.rb +70 -0
- data/lib/scout_rails_proxy/metric_meta.rb +34 -0
- data/lib/scout_rails_proxy/metric_stats.rb +49 -0
- data/lib/scout_rails_proxy/stack_item.rb +18 -0
- data/lib/scout_rails_proxy/store.rb +159 -0
- data/lib/scout_rails_proxy/tracer.rb +105 -0
- data/lib/scout_rails_proxy/transaction_sample.rb +10 -0
- data/lib/scout_rails_proxy/version.rb +3 -0
- data/scout_rails_proxy.gemspec +24 -0
- metadata +75 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module ScoutRailsProxy
|
2
|
+
class Config
|
3
|
+
def initialize(config_path = nil)
|
4
|
+
@config_path = config_path
|
5
|
+
end
|
6
|
+
|
7
|
+
def settings
|
8
|
+
return @settings if @settings
|
9
|
+
load_file
|
10
|
+
end
|
11
|
+
|
12
|
+
def config_path
|
13
|
+
@config_path || File.join(ScoutRailsProxy::Agent.instance.environment.root,"config","scout_rails_proxy.yml")
|
14
|
+
end
|
15
|
+
|
16
|
+
def config_file
|
17
|
+
File.expand_path(config_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_file
|
21
|
+
if !File.exist?(config_file)
|
22
|
+
ScoutRailsProxy::Agent.instance.logger.warn "No config file found at [#{config_file}]."
|
23
|
+
@settings = {}
|
24
|
+
else
|
25
|
+
@settings = YAML.load(ERB.new(File.read(config_file)).result(binding))[ScoutRailsProxy::Agent.instance.environment.env] || {}
|
26
|
+
end
|
27
|
+
rescue Exception => e
|
28
|
+
ScoutRailsProxy::Agent.instance.logger.warn "Unable to load the config file."
|
29
|
+
ScoutRailsProxy::Agent.instance.logger.warn e.message
|
30
|
+
ScoutRailsProxy::Agent.instance.logger.warn e.backtrace
|
31
|
+
@settings = {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# Used to retrieve environment information for this application.
|
2
|
+
module ScoutRailsProxy
|
3
|
+
class Environment
|
4
|
+
def env
|
5
|
+
@env ||= case framework
|
6
|
+
when :rails then RAILS_ENV.dup
|
7
|
+
when :rails3 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
|
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
|
46
|
+
Rails.root
|
47
|
+
elsif framework == :sinatra
|
48
|
+
Sinatra::Application.root
|
49
|
+
else
|
50
|
+
'.'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def app_server
|
55
|
+
@app_server ||= if thin? then :thin
|
56
|
+
elsif passenger? then :passenger
|
57
|
+
elsif webrick? then :webrick
|
58
|
+
elsif unicorn? then :unicorn
|
59
|
+
else nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
### app server related-checks
|
64
|
+
|
65
|
+
def thin?
|
66
|
+
if defined?(::Thin) && defined?(::Thin::Server)
|
67
|
+
# Ensure Thin is actually initialized. It could just be required and not running.
|
68
|
+
ObjectSpace.each_object(Thin::Server) { |x| return true }
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def unicorn?
|
74
|
+
if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
|
75
|
+
# Ensure Unicorn is actually initialized. It could just be required and not running.
|
76
|
+
ObjectSpace.each_object(::Unicorn::HttpServer) { |x| return true }
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Called via +#forking?+ since Passenger forks. Adds an event listener to start the worker thread
|
82
|
+
# inside the passenger worker process.
|
83
|
+
# Background: http://www.modrails.com/documentation/Users%20guide%20Nginx.html#spawning%5Fmethods%5Fexplained
|
84
|
+
def passenger?
|
85
|
+
(defined?(::Passenger) && defined?(::Passenger::AbstractServer)) || defined?(::IN_PHUSION_PASSENGER)
|
86
|
+
end
|
87
|
+
|
88
|
+
def webrick?
|
89
|
+
defined?(::WEBrick) && defined?(::WEBrick::VERSION)
|
90
|
+
end
|
91
|
+
|
92
|
+
def unicorn?
|
93
|
+
if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
|
94
|
+
# ensure Unicorn is actually initialized.
|
95
|
+
ObjectSpace.each_object(::Unicorn::HttpServer) { |x| return true }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# If forking, don't start worker thread in the master process. Since it's started as a Thread, it won't survive
|
100
|
+
# the fork.
|
101
|
+
def forking?
|
102
|
+
passenger? or unicorn?
|
103
|
+
end
|
104
|
+
|
105
|
+
### ruby checks
|
106
|
+
|
107
|
+
def rubinius?
|
108
|
+
RUBY_VERSION =~ /rubinius/i
|
109
|
+
end
|
110
|
+
|
111
|
+
def jruby?
|
112
|
+
defined?(JRuby)
|
113
|
+
end
|
114
|
+
|
115
|
+
### framework checks
|
116
|
+
|
117
|
+
def sinatra?
|
118
|
+
defined?(Sinatra::Application)
|
119
|
+
end
|
120
|
+
|
121
|
+
end # class Environemnt
|
122
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ScoutRailsProxy::Instruments
|
2
|
+
# Contains ActiveRecord instrument, aliasing +ActiveRecord::ConnectionAdapters::AbstractAdapter#log+ calls
|
3
|
+
# to trace calls to the database.
|
4
|
+
module ActiveRecordInstruments
|
5
|
+
def self.included(instrumented_class)
|
6
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
|
7
|
+
instrumented_class.class_eval do
|
8
|
+
unless instrumented_class.method_defined?(:log_without_scout_instruments)
|
9
|
+
alias_method :log_without_scout_instruments, :log
|
10
|
+
alias_method :log, :log_with_scout_instruments
|
11
|
+
protected :log
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end # self.included
|
15
|
+
|
16
|
+
def log_with_scout_instruments(*args, &block)
|
17
|
+
sql, name = args
|
18
|
+
self.class.instrument(scout_ar_metric_name(sql,name), :desc => scout_sanitize_sql(sql)) do
|
19
|
+
log_without_scout_instruments(sql, name, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def scout_ar_metric_name(sql,name)
|
24
|
+
if name && (parts = name.split " ") && parts.size == 2
|
25
|
+
model = parts.first
|
26
|
+
operation = parts.last.downcase
|
27
|
+
metric_name = case operation
|
28
|
+
when 'load' then 'find'
|
29
|
+
when 'indexes', 'columns' then nil # not under developer control
|
30
|
+
when 'destroy', 'find', 'save', 'create' then operation
|
31
|
+
when 'update' then 'save'
|
32
|
+
else
|
33
|
+
if model == 'Join'
|
34
|
+
operation
|
35
|
+
end
|
36
|
+
end
|
37
|
+
metric = "ActiveRecord/#{model}/#{metric_name}" if metric_name
|
38
|
+
metric = "ActiveRecord/SQL/other" if metric.nil?
|
39
|
+
else
|
40
|
+
metric = "ActiveRecord/SQL/Unknown"
|
41
|
+
end
|
42
|
+
metric
|
43
|
+
end
|
44
|
+
|
45
|
+
# Removes actual values from SQL. Used to both obfuscate the SQL and group
|
46
|
+
# similar queries in the UI.
|
47
|
+
def scout_sanitize_sql(sql)
|
48
|
+
return nil if sql.length > 1000 # safeguard - don't sanitize large SQL statements
|
49
|
+
sql = sql.dup
|
50
|
+
sql.gsub!(/\\"/, '') # removing escaping double quotes
|
51
|
+
sql.gsub!(/\\'/, '') # removing escaping single quotes
|
52
|
+
sql.gsub!(/'(?:[^']|'')*'/, '?') # removing strings (single quote)
|
53
|
+
sql.gsub!(/"(?:[^"]|"")*"/, '?') # removing strings (double quote)
|
54
|
+
sql.gsub!(/\b\d+\b/, '?') # removing integers
|
55
|
+
sql.gsub!(/\?(,\?)+/,'?') # replace multiple ? w/a single ?
|
56
|
+
sql
|
57
|
+
end
|
58
|
+
|
59
|
+
end # module ActiveRecordInstruments
|
60
|
+
end # module Instruments
|
61
|
+
|
62
|
+
def add_instruments
|
63
|
+
if defined?(ActiveRecord) && defined?(ActiveRecord::Base)
|
64
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
|
65
|
+
include ::ScoutRailsProxy::Instruments::ActiveRecordInstruments
|
66
|
+
include ::ScoutRailsProxy::Tracer
|
67
|
+
end
|
68
|
+
ActiveRecord::Base.class_eval do
|
69
|
+
include ::ScoutRailsProxy::Tracer
|
70
|
+
end
|
71
|
+
end
|
72
|
+
rescue
|
73
|
+
ScoutRailsProxy::Agent.instance.logger.warn "ActiveRecord instrumentation exception: #{$!.message}"
|
74
|
+
end
|
75
|
+
|
76
|
+
if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 3
|
77
|
+
Rails.configuration.after_initialize do
|
78
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Adding ActiveRecord instrumentation to a Rails 3 app"
|
79
|
+
add_instruments
|
80
|
+
end
|
81
|
+
else
|
82
|
+
add_instruments
|
83
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
if defined?(::Net) && defined?(Net::HTTP)
|
2
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Instrumenting Net::HTTP"
|
3
|
+
Net::HTTP.class_eval do
|
4
|
+
include ScoutRailsProxy::Tracer
|
5
|
+
|
6
|
+
def request_with_scout_instruments(*args,&block)
|
7
|
+
self.class.instrument("HTTP/request", :desc => "#{(@address+args.first.path.split('?').first)[0..99]}") do
|
8
|
+
request_without_scout_instruments(*args,&block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
alias request_without_scout_instruments request
|
12
|
+
alias request request_with_scout_instruments
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ScoutRailsProxy::Instruments
|
2
|
+
module Process
|
3
|
+
class ProcessCpu
|
4
|
+
def initialize(num_processors)
|
5
|
+
@num_processors = num_processors || 1
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
res=nil
|
10
|
+
now = Time.now
|
11
|
+
t = ::Process.times
|
12
|
+
if @last_run
|
13
|
+
elapsed_time = now - @last_run
|
14
|
+
if elapsed_time >= 1
|
15
|
+
user_time_since_last_sample = t.utime - @last_utime
|
16
|
+
system_time_since_last_sample = t.stime - @last_stime
|
17
|
+
res = ((user_time_since_last_sample + system_time_since_last_sample)/(elapsed_time * @num_processors))*100
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@last_utime = t.utime
|
21
|
+
@last_stime = t.stime
|
22
|
+
@last_run = now
|
23
|
+
return res
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ScoutRailsProxy::Instruments
|
2
|
+
module Process
|
3
|
+
class ProcessMemory
|
4
|
+
def run
|
5
|
+
res=nil
|
6
|
+
platform = RUBY_PLATFORM.downcase
|
7
|
+
|
8
|
+
if platform =~ /linux/
|
9
|
+
res = get_mem_from_procfile
|
10
|
+
elsif platform =~ /darwin9/ # 10.5
|
11
|
+
res = get_mem_from_shell("ps -o rsz")
|
12
|
+
elsif platform =~ /darwin1[01]/ # 10.6 & 10.7
|
13
|
+
res = get_mem_from_shell("ps -o rss")
|
14
|
+
end
|
15
|
+
return res
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def get_mem_from_procfile
|
21
|
+
res = nil
|
22
|
+
proc_status = File.open(procfile, "r") { |f| f.read_nonblock(4096).strip }
|
23
|
+
if proc_status =~ /RSS:\s*(\d+) kB/i
|
24
|
+
res= $1.to_f / 1024.0
|
25
|
+
end
|
26
|
+
res
|
27
|
+
end
|
28
|
+
|
29
|
+
def procfile
|
30
|
+
"/proc/#{$$}/status"
|
31
|
+
end
|
32
|
+
|
33
|
+
# memory in MB the current process is using
|
34
|
+
def get_mem_from_shell(command)
|
35
|
+
res = `#{command} #{$$}`.split("\n")[1].to_f / 1024.0 #rescue nil
|
36
|
+
res
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ScoutRailsProxy::Instruments
|
2
|
+
module ActionControllerInstruments
|
3
|
+
def self.included(instrumented_class)
|
4
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
|
5
|
+
instrumented_class.class_eval do
|
6
|
+
unless instrumented_class.method_defined?(:perform_action_without_scout_instruments)
|
7
|
+
alias_method :perform_action_without_scout_instruments, :perform_action
|
8
|
+
alias_method :perform_action, :perform_action_with_scout_instruments
|
9
|
+
private :perform_action
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end # self.included
|
13
|
+
|
14
|
+
# In addition to instrumenting actions, this also sets the scope to the controller action name. The scope is later
|
15
|
+
# applied to metrics recorded during this transaction. This lets us associate ActiveRecord calls with
|
16
|
+
# specific controller actions.
|
17
|
+
def perform_action_with_scout_instruments(*args, &block)
|
18
|
+
scout_controller_action = "Controller/#{controller_path}/#{action_name}"
|
19
|
+
self.class.trace(scout_controller_action, :uri => request.request_uri) do
|
20
|
+
perform_action_without_scout_instruments(*args, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if defined?(ActionController) && defined?(ActionController::Base)
|
27
|
+
ActionController::Base.class_eval do
|
28
|
+
include ScoutRailsProxy::Tracer
|
29
|
+
include ::ScoutRailsProxy::Instruments::ActionControllerInstruments
|
30
|
+
|
31
|
+
def rescue_action_with_scout(exception)
|
32
|
+
ScoutRailsProxy::Agent.instance.store.track!("Errors/Request",1, :scope => nil)
|
33
|
+
ScoutRailsProxy::Agent.instance.store.ignore_transaction!
|
34
|
+
rescue_action_without_scout exception
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :rescue_action_without_scout, :rescue_action
|
38
|
+
alias_method :rescue_action, :rescue_action_with_scout
|
39
|
+
protected :rescue_action
|
40
|
+
end
|
41
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Instrumenting ActionView::Template"
|
42
|
+
ActionView::Template.class_eval do
|
43
|
+
include ::ScoutRailsProxy::Tracer
|
44
|
+
instrument_method :render, :metric_name => 'View/#{path[%r{^(/.*/)?(.*)$},2]}/Rendering', :scope => true
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Rails 3
|
2
|
+
module ScoutRailsProxy::Instruments
|
3
|
+
module ActionControllerInstruments
|
4
|
+
# Instruments the action and tracks errors.
|
5
|
+
def process_action(*args)
|
6
|
+
scout_controller_action = "Controller/#{controller_path}/#{action_name}"
|
7
|
+
#ScoutRailsProxy::Agent.instance.logger.debug "Processing #{scout_controller_action}"
|
8
|
+
self.class.trace(scout_controller_action, :uri => request.fullpath) do
|
9
|
+
begin
|
10
|
+
super
|
11
|
+
rescue Exception => e
|
12
|
+
ScoutRailsProxy::Agent.instance.store.track!("Errors/Request",1, :scope => nil)
|
13
|
+
raise
|
14
|
+
ensure
|
15
|
+
Thread::current[:scout_scope_name] = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# ActionController::Base is a subclass of ActionController::Metal, so this instruments both
|
23
|
+
# standard Rails requests + Metal.
|
24
|
+
if defined?(ActionController) && defined?(ActionController::Metal)
|
25
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Instrumenting ActionController::Metal"
|
26
|
+
ActionController::Metal.class_eval do
|
27
|
+
include ScoutRailsProxy::Tracer
|
28
|
+
include ::ScoutRailsProxy::Instruments::ActionControllerInstruments
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if defined?(ActionView) && defined?(ActionView::PartialRenderer)
|
33
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Instrumenting ActionView::PartialRenderer"
|
34
|
+
ActionView::PartialRenderer.class_eval do
|
35
|
+
include ScoutRailsProxy::Tracer
|
36
|
+
instrument_method :render_partial, :metric_name => 'View/#{@template.virtual_path}/Rendering', :scope => true
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ScoutRailsProxy::Instruments
|
2
|
+
module SinatraInstruments
|
3
|
+
def route_eval_with_scout_instruments(&blockarg)
|
4
|
+
path = unescape(@request.path_info)
|
5
|
+
name = path
|
6
|
+
# Go through each route and look for a match
|
7
|
+
if routes = self.class.routes[@request.request_method]
|
8
|
+
routes.detect do |pattern, keys, conditions, block|
|
9
|
+
if blockarg.equal? block
|
10
|
+
name = pattern.source
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
name.gsub!(%r{^[/^]*(.*?)[/\$\?]*$}, '\1')
|
15
|
+
name = 'root' if name.empty?
|
16
|
+
name = @request.request_method + ' ' + name if @request && @request.respond_to?(:request_method)
|
17
|
+
scout_controller_action = "Controller/Sinatra/#{name}"
|
18
|
+
self.class.trace(scout_controller_action, :uri => @request.path_info) do
|
19
|
+
route_eval_without_scout_instruments(&blockarg)
|
20
|
+
end
|
21
|
+
end # route_eval_with_scout_instrumentss
|
22
|
+
end # SinatraInstruments
|
23
|
+
end # ScoutRailsProxy::Instruments
|
24
|
+
|
25
|
+
if defined?(::Sinatra) && defined?(::Sinatra::Base)
|
26
|
+
ScoutRailsProxy::Agent.instance.logger.debug "Instrumenting Sinatra"
|
27
|
+
::Sinatra::Base.class_eval do
|
28
|
+
include ScoutRailsProxy::Tracer
|
29
|
+
include ::ScoutRailsProxy::Instruments::SinatraInstruments
|
30
|
+
alias route_eval_without_scout_instruments route_eval
|
31
|
+
alias route_eval route_eval_with_scout_instruments
|
32
|
+
end
|
33
|
+
end
|