scout_apm 0.1.6 → 0.1.7
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.
- 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
|