scout_apm 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +4 -0
- data/lib/scout_apm.rb +48 -23
- data/lib/scout_apm/agent.rb +93 -130
- data/lib/scout_apm/agent/reporting.rb +34 -63
- data/lib/scout_apm/app_server_load.rb +29 -0
- data/lib/scout_apm/background_worker.rb +6 -6
- data/lib/scout_apm/capacity.rb +48 -48
- data/lib/scout_apm/config.rb +5 -5
- data/lib/scout_apm/context.rb +3 -3
- data/lib/scout_apm/environment.rb +64 -100
- data/lib/scout_apm/framework_integrations/rails_2.rb +32 -0
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +33 -0
- data/lib/scout_apm/framework_integrations/ruby.rb +26 -0
- data/lib/scout_apm/framework_integrations/sinatra.rb +27 -0
- data/lib/scout_apm/instruments/active_record_instruments.rb +1 -1
- data/lib/scout_apm/instruments/mongoid_instruments.rb +1 -1
- data/lib/scout_apm/instruments/moped_instruments.rb +3 -3
- data/lib/scout_apm/instruments/net_http.rb +2 -2
- data/lib/scout_apm/instruments/process/process_cpu.rb +41 -20
- data/lib/scout_apm/instruments/process/process_memory.rb +45 -30
- data/lib/scout_apm/instruments/rails/action_controller_instruments.rb +20 -18
- data/lib/scout_apm/instruments/rails3_or_4/action_controller_instruments.rb +17 -14
- data/lib/scout_apm/layaway.rb +12 -12
- data/lib/scout_apm/layaway_file.rb +1 -1
- data/lib/scout_apm/metric_meta.rb +9 -9
- data/lib/scout_apm/metric_stats.rb +8 -8
- data/lib/scout_apm/reporter.rb +83 -0
- data/lib/scout_apm/serializers/app_server_load_serializer.rb +15 -0
- data/lib/scout_apm/serializers/directive_serializer.rb +15 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +14 -0
- data/lib/scout_apm/server_integrations/null.rb +30 -0
- data/lib/scout_apm/server_integrations/passenger.rb +35 -0
- data/lib/scout_apm/server_integrations/puma.rb +30 -0
- data/lib/scout_apm/server_integrations/rainbows.rb +36 -0
- data/lib/scout_apm/server_integrations/thin.rb +41 -0
- data/lib/scout_apm/server_integrations/unicorn.rb +35 -0
- data/lib/scout_apm/server_integrations/webrick.rb +25 -0
- data/lib/scout_apm/slow_transaction.rb +3 -3
- data/lib/scout_apm/stack_item.rb +19 -17
- data/lib/scout_apm/store.rb +35 -35
- data/lib/scout_apm/tracer.rb +121 -110
- data/lib/scout_apm/utils/sql_sanitizer.rb +2 -2
- data/lib/scout_apm/version.rb +2 -1
- data/test/unit/environment_test.rb +5 -5
- metadata +18 -2
@@ -0,0 +1,29 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class AppServerLoad
|
3
|
+
attr_reader :logger
|
4
|
+
|
5
|
+
def initialize(logger: Agent.instance.logger)
|
6
|
+
@logger = logger
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
logger.info("Sending Startup Info: #{data.inspect}")
|
11
|
+
payload = ScoutApm::Serializers::AppServerLoadSerializer.serialize(data)
|
12
|
+
reporter = Reporter.new(type: :app_server_load)
|
13
|
+
reporter.report(payload)
|
14
|
+
rescue => e
|
15
|
+
logger.debug("Failed Startup Info - #{e.message} \n\t#{e.backtrace.join("\t\n")}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def data
|
19
|
+
{ server_time: Time.now,
|
20
|
+
framework: ScoutApm::Environment.instance.framework_integration.name,
|
21
|
+
framework_version: ScoutApm::Environment.instance.framework_integration.version,
|
22
|
+
ruby_version: RUBY_VERSION,
|
23
|
+
hostname: ScoutApm::Environment.instance.hostname,
|
24
|
+
database_engine: ScoutApm::Environment.instance.database_engine,
|
25
|
+
application_name: ScoutApm::Environment.instance.application_name,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,21 +1,21 @@
|
|
1
|
-
# Used to run a given task every 60 seconds.
|
1
|
+
# Used to run a given task every 60 seconds.
|
2
2
|
class ScoutApm::BackgroundWorker
|
3
3
|
# in seconds, time between when the worker thread wakes up and runs.
|
4
4
|
PERIOD = 60
|
5
|
-
|
5
|
+
|
6
6
|
def initialize
|
7
7
|
@keep_running = true
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def stop
|
11
11
|
@keep_running = false
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
# Runs the task passed to +start+ once.
|
15
15
|
def run_once
|
16
16
|
@task.call if @task
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
# Starts running the passed block every 60 seconds (starting now).
|
20
20
|
def start(&block)
|
21
21
|
@task = block
|
@@ -40,4 +40,4 @@ class ScoutApm::BackgroundWorker
|
|
40
40
|
ScoutApm::Agent.instance.logger.debug $!.backtrace
|
41
41
|
end
|
42
42
|
end
|
43
|
-
end
|
43
|
+
end
|
data/lib/scout_apm/capacity.rb
CHANGED
@@ -1,54 +1,54 @@
|
|
1
1
|
# Encapsulates logic for determining capacity utilization of the Ruby processes.
|
2
2
|
class ScoutApm::Capacity
|
3
|
-
|
3
|
+
attr_reader :processing_start_time, :accumulated_time, :transaction_entry_time
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# Called when a transaction is traced.
|
12
|
-
def start_transaction!
|
13
|
-
@lock.synchronize do
|
14
|
-
@transaction_entry_time = Time.now
|
15
|
-
end
|
16
|
-
end
|
5
|
+
def initialize
|
6
|
+
@processing_start_time = Time.now
|
7
|
+
@lock ||= Mutex.new # the transaction_entry_time could be modified while processing a request or when #process is called.
|
8
|
+
@accumulated_time = 0.0
|
9
|
+
end
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
ScoutApm::Agent.instance.logger.warn "No transaction entry time. Not recording capacity metrics for transaction."
|
25
|
-
end
|
26
|
-
@transaction_entry_time = nil
|
27
|
-
end
|
28
|
-
end
|
11
|
+
# Called when a transaction is traced.
|
12
|
+
def start_transaction!
|
13
|
+
@lock.synchronize do
|
14
|
+
@transaction_entry_time = Time.now
|
15
|
+
end
|
16
|
+
end
|
29
17
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
ScoutApm::Agent.instance.logger.debug "A transaction is running while calculating capacity. Start time: [#{transaction_entry_time}]. Will update the entry time to [#{process_time}]."
|
42
|
-
@transaction_entry_time = process_time # prevent from over-counting capacity usage. update the transaction start time to now.
|
43
|
-
end
|
44
|
-
time_spent = 0.0 if time_spent < 0.0
|
18
|
+
# Called when a transaction completes to record its time used.
|
19
|
+
def finish_transaction!
|
20
|
+
@lock.synchronize do
|
21
|
+
if transaction_entry_time
|
22
|
+
@accumulated_time += (Time.now - transaction_entry_time).to_f
|
23
|
+
else
|
24
|
+
ScoutApm::Agent.instance.logger.warn "No transaction entry time. Not recording capacity metrics for transaction."
|
25
|
+
end
|
26
|
+
@transaction_entry_time = nil
|
27
|
+
end
|
28
|
+
end
|
45
29
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
30
|
+
# Ran when sending metrics to server. Reports capacity usage metrics.
|
31
|
+
def process
|
32
|
+
process_time = Time.now
|
33
|
+
ScoutApm::Agent.instance.logger.debug "Processing capacity usage for [#{@processing_start_time}] to [#{process_time}]. Time Spent: #{@accumulated_time}."
|
34
|
+
@lock.synchronize do
|
35
|
+
time_spent = @accumulated_time
|
36
|
+
@accumulated_time = 0.0
|
37
|
+
# If a transaction is still running, capture its running time up to now and
|
38
|
+
# reset the +transaction_entry_time+ to now.
|
39
|
+
if @transaction_entry_time
|
40
|
+
time_spent += (process_time - @transaction_entry_time).to_f
|
41
|
+
ScoutApm::Agent.instance.logger.debug "A transaction is running while calculating capacity. Start time: [#{transaction_entry_time}]. Will update the entry time to [#{process_time}]."
|
42
|
+
@transaction_entry_time = process_time # prevent from over-counting capacity usage. update the transaction start time to now.
|
43
|
+
end
|
44
|
+
time_spent = 0.0 if time_spent < 0.0
|
45
|
+
|
46
|
+
window = (process_time - processing_start_time).to_f # time period we are evaulating capacity usage.
|
47
|
+
window = 1.0 if window <= 0.0 # prevent divide-by-zero if clock adjusted.
|
48
|
+
capacity = time_spent / window
|
49
|
+
ScoutApm::Agent.instance.logger.debug "Instance/Capacity: #{capacity}"
|
50
|
+
ScoutApm::Agent.instance.store.track!("Instance/Capacity",capacity,:scope => nil)
|
51
|
+
@processing_start_time = process_time
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/scout_apm/config.rb
CHANGED
@@ -6,8 +6,8 @@ require 'scout_apm/environment'
|
|
6
6
|
module ScoutApm
|
7
7
|
class Config
|
8
8
|
DEFAULTS = {
|
9
|
-
'host'
|
10
|
-
'log_level' => 'info'
|
9
|
+
'host' => 'https://apm.scoutapp.com',
|
10
|
+
'log_level' => 'info',
|
11
11
|
}.freeze
|
12
12
|
|
13
13
|
def initialize(config_path = nil)
|
@@ -25,7 +25,7 @@ module ScoutApm
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def config_path
|
28
|
-
@config_path || File.join(ScoutApm::Environment.
|
28
|
+
@config_path || File.join(ScoutApm::Environment.instance.root, "config", "scout_apm.yml")
|
29
29
|
end
|
30
30
|
|
31
31
|
def config_file
|
@@ -37,14 +37,14 @@ module ScoutApm
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def config_environment
|
40
|
-
@config_environment ||= ScoutApm::Environment.
|
40
|
+
@config_environment ||= ScoutApm::Environment.instance.env
|
41
41
|
end
|
42
42
|
|
43
43
|
def load_file
|
44
44
|
settings_hash = {}
|
45
45
|
begin
|
46
46
|
if File.exist?(config_file)
|
47
|
-
settings_hash = YAML.load(ERB.new(File.read(config_file)).result(binding))[config_environment] || {}
|
47
|
+
settings_hash = YAML.load(ERB.new(File.read(config_file)).result(binding))[config_environment] || {}
|
48
48
|
else
|
49
49
|
logger.warn "No config file found at [#{config_file}]."
|
50
50
|
end
|
data/lib/scout_apm/context.rb
CHANGED
@@ -10,7 +10,7 @@ class ScoutApm::Context
|
|
10
10
|
@user = {}
|
11
11
|
end
|
12
12
|
|
13
|
-
# Generates a hash representation of the Context.
|
13
|
+
# Generates a hash representation of the Context.
|
14
14
|
# Example: {:monthly_spend => 100, :user => {:ip => '127.0.0.1'}}
|
15
15
|
def to_hash
|
16
16
|
@extra.merge({:user => @user})
|
@@ -77,7 +77,7 @@ class ScoutApm::Context
|
|
77
77
|
|
78
78
|
# take the entire Hash vs. just the value so the logger output is more helpful on error.
|
79
79
|
def value_valid?(key_value)
|
80
|
-
# ensure one of our accepted types.
|
80
|
+
# ensure one of our accepted types.
|
81
81
|
value = key_value.values.last
|
82
82
|
if !valid_type?([String, Symbol, Numeric, Time, Date, TrueClass, FalseClass],value)
|
83
83
|
ScoutApm::Agent.instance.logger.warn "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
|
@@ -102,4 +102,4 @@ class ScoutApm::Context
|
|
102
102
|
end
|
103
103
|
true
|
104
104
|
end
|
105
|
-
end
|
105
|
+
end
|
@@ -1,68 +1,77 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
1
3
|
# Used to retrieve environment information for this application.
|
2
4
|
module ScoutApm
|
3
5
|
class Environment
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
# I've put Thin and Webrick last as they are often used in development and included in Gemfiles
|
9
|
+
# but less likely used in production.
|
10
|
+
SERVER_INTEGRATIONS = [
|
11
|
+
ScoutApm::ServerIntegrations::Passenger.new(Logger.new(STDOUT)),
|
12
|
+
ScoutApm::ServerIntegrations::Unicorn.new(Logger.new(STDOUT)),
|
13
|
+
ScoutApm::ServerIntegrations::Rainbows.new(Logger.new(STDOUT)),
|
14
|
+
ScoutApm::ServerIntegrations::Puma.new(Logger.new(STDOUT)),
|
15
|
+
ScoutApm::ServerIntegrations::Thin.new(Logger.new(STDOUT)),
|
16
|
+
ScoutApm::ServerIntegrations::Webrick.new(Logger.new(STDOUT)),
|
17
|
+
ScoutApm::ServerIntegrations::Null.new(Logger.new(STDOUT)), # must be last
|
18
|
+
]
|
19
|
+
|
20
|
+
FRAMEWORK_INTEGRATIONS = [
|
21
|
+
ScoutApm::FrameworkIntegrations::Rails2.new,
|
22
|
+
ScoutApm::FrameworkIntegrations::Rails3Or4.new,
|
23
|
+
ScoutApm::FrameworkIntegrations::Sinatra.new,
|
24
|
+
ScoutApm::FrameworkIntegrations::Ruby.new, # Fallback if none match
|
25
|
+
]
|
26
|
+
|
4
27
|
def env
|
5
|
-
@env ||=
|
6
|
-
when :rails
|
7
|
-
RAILS_ENV.dup
|
8
|
-
when :rails3_or_4
|
9
|
-
Rails.env
|
10
|
-
when :sinatra
|
11
|
-
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
12
|
-
when :ruby
|
13
|
-
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
14
|
-
end
|
28
|
+
@env ||= framework_integration.env
|
15
29
|
end
|
16
30
|
|
17
31
|
def framework
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
32
|
+
framework_integration.name
|
33
|
+
end
|
34
|
+
|
35
|
+
def framework_integration
|
36
|
+
@framework ||= FRAMEWORK_INTEGRATIONS.detect{ |integration| integration.present? }
|
37
|
+
end
|
38
|
+
|
39
|
+
def application_name
|
40
|
+
Agent.instance.config.value("name") || framework_integration.application_name
|
28
41
|
end
|
29
42
|
|
30
43
|
def database_engine
|
31
44
|
default = :mysql
|
32
45
|
|
33
46
|
if defined?(ActiveRecord::Base)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
:
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
:sqlite
|
47
|
+
config = ActiveRecord::Base.connection_config
|
48
|
+
if config && config[:adapter]
|
49
|
+
case config[:adapter]
|
50
|
+
when "postgres" then :postgres
|
51
|
+
when "sqlite3" then :sqlite
|
52
|
+
when "mysql" then :mysql
|
53
|
+
else default
|
54
|
+
end
|
43
55
|
else
|
44
56
|
default
|
45
57
|
end
|
46
58
|
else
|
47
|
-
# TODO:
|
59
|
+
# TODO: Figure out how to detect outside of Rails context. (sequel, ROM, etc)
|
48
60
|
default
|
49
61
|
end
|
50
62
|
end
|
51
63
|
|
52
64
|
def processors
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
65
|
-
@processors
|
65
|
+
@processors ||= begin
|
66
|
+
proc_file = '/proc/cpuinfo'
|
67
|
+
processors = if !File.exist?(proc_file)
|
68
|
+
1
|
69
|
+
else
|
70
|
+
lines = File.read("/proc/cpuinfo").lines.to_a
|
71
|
+
lines.grep(/^processor\s*:/i).size
|
72
|
+
end
|
73
|
+
[processors, 1].compact.max
|
74
|
+
end
|
66
75
|
end
|
67
76
|
|
68
77
|
def root
|
@@ -82,73 +91,28 @@ module ScoutApm
|
|
82
91
|
end
|
83
92
|
|
84
93
|
def hostname
|
85
|
-
heroku? ? ENV['DYNO'] : Socket.gethostname
|
94
|
+
@hostname ||= heroku? ? ENV['DYNO'] : Socket.gethostname
|
86
95
|
end
|
87
96
|
|
97
|
+
|
98
|
+
# Returns the whole integration object
|
88
99
|
# This needs to be improved. Frequently, multiple app servers gem are present and which
|
89
100
|
# ever is checked first becomes the designated app server.
|
90
101
|
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
# Next step: (1) list out all detected app servers (2) install hooks for those that need it (passenger, rainbows, unicorn).
|
95
|
-
#
|
96
|
-
# Believe the biggest downside is the master process for forking app servers will get a background worker. Not sure how this will
|
97
|
-
# impact metrics (it shouldn't process requests).
|
98
|
-
def app_server
|
99
|
-
@app_server ||= if passenger? then :passenger
|
100
|
-
elsif rainbows? then :rainbows
|
101
|
-
elsif unicorn? then :unicorn
|
102
|
-
elsif puma? then :puma
|
103
|
-
elsif thin? then :thin
|
104
|
-
elsif webrick? then :webrick
|
105
|
-
else nil
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
### app server related-checks
|
110
|
-
def thin?
|
111
|
-
if defined?(::Thin) && defined?(::Thin::Server)
|
112
|
-
# Ensure Thin is actually initialized. It could just be required and not running.
|
113
|
-
ObjectSpace.each_object(Thin::Server) { |x| return true }
|
114
|
-
false
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# Called via +#forking?+ since Passenger forks. Adds an event listener to start the worker thread
|
119
|
-
# inside the passenger worker process.
|
120
|
-
# Background: http://www.modrails.com/documentation/Users%20guide%20Nginx.html#spawning%5Fmethods%5Fexplained
|
121
|
-
def passenger?
|
122
|
-
(defined?(::Passenger) && defined?(::Passenger::AbstractServer)) || defined?(::PhusionPassenger)
|
102
|
+
# Next step: (1) list out all detected app servers (2) install hooks for those that need it (passenger, rainbows, unicorn).
|
103
|
+
def app_server_integration
|
104
|
+
@app_server = SERVER_INTEGRATIONS.detect{ |integration| integration.present? }
|
123
105
|
end
|
124
106
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
def rainbows?
|
130
|
-
if defined?(::Rainbows) && defined?(::Rainbows::HttpServer)
|
131
|
-
ObjectSpace.each_object(::Rainbows::HttpServer) { |x| return true }
|
132
|
-
false
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def unicorn?
|
137
|
-
if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
|
138
|
-
# Ensure Unicorn is actually initialized. It could just be required and not running.
|
139
|
-
ObjectSpace.each_object(::Unicorn::HttpServer) { |x| return true }
|
140
|
-
false
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def puma?
|
145
|
-
defined?(::Puma) && File.basename($0) == 'puma'
|
107
|
+
# App server's name (symbol)
|
108
|
+
def app_server
|
109
|
+
app_server_integration.name
|
146
110
|
end
|
147
111
|
|
148
|
-
# If forking, don't start worker thread in the master process. Since it's
|
149
|
-
# the fork.
|
112
|
+
# If forking, don't start worker thread in the master process. Since it's
|
113
|
+
# started as a Thread, it won't survive the fork.
|
150
114
|
def forking?
|
151
|
-
|
115
|
+
app_server_integration.forking?
|
152
116
|
end
|
153
117
|
|
154
118
|
### ruby checks
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module FrameworkIntegrations
|
3
|
+
class Rails2
|
4
|
+
def name
|
5
|
+
:rails
|
6
|
+
end
|
7
|
+
|
8
|
+
def version
|
9
|
+
Rails::VERSION::STRING
|
10
|
+
end
|
11
|
+
|
12
|
+
def present?
|
13
|
+
defined?(::Rails) &&
|
14
|
+
defined?(ActionController) &&
|
15
|
+
Rails::VERSION::MAJOR < 3
|
16
|
+
end
|
17
|
+
|
18
|
+
def application_name
|
19
|
+
if defined?(::Rails)
|
20
|
+
::Rails.application.class.to_s
|
21
|
+
.sub(/::Application$/, '')
|
22
|
+
end
|
23
|
+
rescue
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def env
|
28
|
+
RAILS_ENV.dup
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|