scout_apm 3.0.0.pre10 → 3.0.0.pre11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.markdown +47 -0
- data/Guardfile +42 -0
- data/lib/scout_apm.rb +14 -0
- data/lib/scout_apm/agent.rb +58 -1
- data/lib/scout_apm/agent/logging.rb +6 -1
- data/lib/scout_apm/app_server_load.rb +21 -11
- data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +19 -3
- data/lib/scout_apm/background_recorder.rb +43 -0
- data/lib/scout_apm/background_worker.rb +6 -6
- data/lib/scout_apm/config.rb +14 -3
- data/lib/scout_apm/environment.rb +14 -0
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +112 -70
- data/lib/scout_apm/instruments/action_view.rb +49 -0
- data/lib/scout_apm/instruments/active_record.rb +2 -2
- data/lib/scout_apm/instruments/mongoid.rb +10 -2
- data/lib/scout_apm/instruments/resque.rb +40 -0
- data/lib/scout_apm/layer_children_set.rb +7 -2
- data/lib/scout_apm/rack.rb +26 -0
- data/lib/scout_apm/remote/message.rb +23 -0
- data/lib/scout_apm/remote/recorder.rb +57 -0
- data/lib/scout_apm/remote/router.rb +49 -0
- data/lib/scout_apm/remote/server.rb +58 -0
- data/lib/scout_apm/request_manager.rb +1 -1
- data/lib/scout_apm/synchronous_recorder.rb +26 -0
- data/lib/scout_apm/tracked_request.rb +53 -5
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
- data/lib/scout_apm/utils/scm.rb +14 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +2 -0
- data/test/unit/remote/test_message.rb +13 -0
- data/test/unit/remote/test_router.rb +33 -0
- data/test/unit/remote/test_server.rb +15 -0
- data/test/unit/test_tracked_request.rb +87 -0
- data/test/unit/utils/backtrace_parser_test.rb +5 -0
- data/test/unit/utils/scm.rb +17 -0
- metadata +52 -2
@@ -0,0 +1,43 @@
|
|
1
|
+
# Provide a background thread queue to do the processing of
|
2
|
+
# TrackedRequest objects, to remove it from the hot-path of returning a
|
3
|
+
# web response
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
class BackgroundRecorder
|
7
|
+
attr_reader :queue
|
8
|
+
attr_reader :thread
|
9
|
+
attr_reader :logger
|
10
|
+
|
11
|
+
def initialize(logger)
|
12
|
+
@logger = logger
|
13
|
+
@queue = Queue.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
logger.info("Starting BackgroundRecorder")
|
18
|
+
@thread = Thread.new(&method(:thread_func))
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
@thread.kill
|
24
|
+
end
|
25
|
+
|
26
|
+
def record!(request)
|
27
|
+
start unless @thread.alive?
|
28
|
+
@queue.push(request)
|
29
|
+
end
|
30
|
+
|
31
|
+
def thread_func
|
32
|
+
while req = queue.pop
|
33
|
+
begin
|
34
|
+
logger.debug("recording in thread. Queue size: #{queue.size}")
|
35
|
+
# For now, just proxy right back into the TrackedRequest object's record function
|
36
|
+
req.record!
|
37
|
+
rescue => e
|
38
|
+
logger.warn("Error in BackgroundRecorder - #{e.message} : #{e.backtrace}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -36,12 +36,6 @@ module ScoutApm
|
|
36
36
|
|
37
37
|
loop do
|
38
38
|
begin
|
39
|
-
# Bail out if @keep_running is false
|
40
|
-
unless @keep_running
|
41
|
-
ScoutApm::Agent.instance.logger.debug "Background Worker: breaking from loop"
|
42
|
-
break
|
43
|
-
end
|
44
|
-
|
45
39
|
now = Time.now
|
46
40
|
|
47
41
|
# Sleep the correct amount of time to reach next_time
|
@@ -51,6 +45,12 @@ module ScoutApm
|
|
51
45
|
now = Time.now
|
52
46
|
end
|
53
47
|
|
48
|
+
# Bail out if @keep_running is false
|
49
|
+
unless @keep_running
|
50
|
+
ScoutApm::Agent.instance.logger.debug "Background Worker: breaking from loop"
|
51
|
+
break
|
52
|
+
end
|
53
|
+
|
54
54
|
@task.call
|
55
55
|
|
56
56
|
# Adjust the next time to run forward by @periods until it is in the future
|
data/lib/scout_apm/config.rb
CHANGED
@@ -25,7 +25,10 @@ require 'scout_apm/environment'
|
|
25
25
|
# profile - turn on/off scoutprof (only applicable in Gem versions including scoutprof)
|
26
26
|
# proxy - an http proxy
|
27
27
|
# report_format - 'json' or 'marshal'. Marshal is legacy and will be removed.
|
28
|
+
# scm_subdirectory - if the app root lives in source management in a subdirectory. E.g. #{SCM_ROOT}/src
|
28
29
|
# uri_reporting - 'path' or 'full_path' default is 'full_path', which reports URL params as well as the path.
|
30
|
+
# remote_agent_host - Internal: What host to bind to, and also send messages to for remote. Default: 127.0.0.1.
|
31
|
+
# remote_agent_port - What port to bind the remote webserver to
|
29
32
|
#
|
30
33
|
# Any of these config settings can be set with an environment variable prefixed
|
31
34
|
# by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
|
@@ -34,6 +37,7 @@ module ScoutApm
|
|
34
37
|
class Config
|
35
38
|
KNOWN_CONFIG_OPTIONS = [
|
36
39
|
'application_root',
|
40
|
+
'async_recording',
|
37
41
|
'compress_payload',
|
38
42
|
'config_file',
|
39
43
|
'data_file',
|
@@ -52,7 +56,10 @@ module ScoutApm
|
|
52
56
|
'name',
|
53
57
|
'profile',
|
54
58
|
'proxy',
|
59
|
+
'remote_agent_host',
|
60
|
+
'remote_agent_port',
|
55
61
|
'report_format',
|
62
|
+
'scm_subdirectory',
|
56
63
|
'uri_reporting',
|
57
64
|
]
|
58
65
|
|
@@ -127,11 +134,12 @@ module ScoutApm
|
|
127
134
|
|
128
135
|
|
129
136
|
SETTING_COERCIONS = {
|
130
|
-
"
|
131
|
-
"enable_background_jobs" => BooleanCoercion.new,
|
132
|
-
"dev_trace" => BooleanCoercion.new,
|
137
|
+
"async_recording" => BooleanCoercion.new,
|
133
138
|
"detailed_middleware" => BooleanCoercion.new,
|
139
|
+
"dev_trace" => BooleanCoercion.new,
|
140
|
+
"enable_background_jobs" => BooleanCoercion.new,
|
134
141
|
"ignore" => JsonCoercion.new,
|
142
|
+
"monitor" => BooleanCoercion.new,
|
135
143
|
}
|
136
144
|
|
137
145
|
|
@@ -215,7 +223,10 @@ module ScoutApm
|
|
215
223
|
'log_level' => 'info',
|
216
224
|
'profile' => true, # for scoutprof
|
217
225
|
'report_format' => 'json',
|
226
|
+
'scm_subdirectory' => '',
|
218
227
|
'uri_reporting' => 'full_path',
|
228
|
+
'remote_agent_host' => '127.0.0.1',
|
229
|
+
'remote_agent_port' => 7721, # picked at random
|
219
230
|
}.freeze
|
220
231
|
|
221
232
|
def value(key)
|
@@ -24,6 +24,7 @@ module ScoutApm
|
|
24
24
|
]
|
25
25
|
|
26
26
|
BACKGROUND_JOB_INTEGRATIONS = [
|
27
|
+
ScoutApm::BackgroundJobIntegrations::Resque.new,
|
27
28
|
ScoutApm::BackgroundJobIntegrations::Sidekiq.new,
|
28
29
|
ScoutApm::BackgroundJobIntegrations::DelayedJob.new,
|
29
30
|
]
|
@@ -84,6 +85,14 @@ module ScoutApm
|
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
88
|
+
def scm_subdirectory
|
89
|
+
@scm_subdirectory ||= if Agent.instance.config.value('scm_subdirectory').empty?
|
90
|
+
''
|
91
|
+
else
|
92
|
+
Agent.instance.config.value('scm_subdirectory').sub(/^\//, '') # Trim any leading slash
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
87
96
|
def root
|
88
97
|
@root ||= framework_root
|
89
98
|
end
|
@@ -144,6 +153,11 @@ module ScoutApm
|
|
144
153
|
background_job_integration && background_job_integration.name
|
145
154
|
end
|
146
155
|
|
156
|
+
# If both stdin & stdout are interactive and the Rails::Console constant is defined
|
157
|
+
def interactive?
|
158
|
+
defined?(::Rails::Console) && $stdout.isatty && $stdin.isatty
|
159
|
+
end
|
160
|
+
|
147
161
|
### ruby checks
|
148
162
|
|
149
163
|
def rubinius?
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ScoutApm
|
2
2
|
module Instruments
|
3
|
-
# instrumentation for Rails 3 and
|
3
|
+
# instrumentation for Rails 3, 4, and 5 is the same.
|
4
4
|
class ActionControllerRails3Rails4
|
5
5
|
attr_reader :logger
|
6
6
|
|
@@ -20,86 +20,89 @@ module ScoutApm
|
|
20
20
|
# before and after filter timing. Instrumenting Base includes those
|
21
21
|
# filters, at the expense of missing out on controllers that don't use
|
22
22
|
# the full Rails stack.
|
23
|
-
if defined?(::ActionController)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
if defined?(::ActionController)
|
24
|
+
if defined?(::ActionController::Base)
|
25
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting ActionController::Base"
|
26
|
+
::ActionController::Base.class_eval do
|
27
|
+
# include ScoutApm::Tracer
|
28
|
+
include ScoutApm::Instruments::ActionControllerBaseInstruments
|
29
|
+
end
|
28
30
|
end
|
29
|
-
ScoutApm::Agent.instance.logger.info "Installing ScoutProf profiling" if ScoutApm::Agent.instance.config.value('profile')
|
30
|
-
end
|
31
|
-
|
32
|
-
if defined?(::ActionView) && defined?(::ActionView::PartialRenderer)
|
33
|
-
ScoutApm::Agent.instance.logger.info "Instrumenting ActionView::PartialRenderer"
|
34
|
-
::ActionView::PartialRenderer.class_eval do
|
35
|
-
include ScoutApm::Tracer
|
36
|
-
|
37
|
-
instrument_method :render_partial,
|
38
|
-
:type => "View",
|
39
|
-
:name => '#{@template.virtual_path rescue "Unknown Partial"}/Rendering',
|
40
|
-
:scope => true
|
41
31
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
32
|
+
if defined?(::ActionController::Metal)
|
33
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting ActionController::Metal"
|
34
|
+
::ActionController::Metal.class_eval do
|
35
|
+
include ScoutApm::Instruments::ActionControllerMetalInstruments
|
36
|
+
end
|
46
37
|
end
|
47
38
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
:name => '#{args[0].virtual_path rescue "Unknown"}/Rendering',
|
54
|
-
:scope => true
|
39
|
+
if defined?(::ActionController::API)
|
40
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting ActionController::Api"
|
41
|
+
::ActionController::API.class_eval do
|
42
|
+
include ScoutApm::Instruments::ActionControllerAPIInstruments
|
43
|
+
end
|
55
44
|
end
|
56
45
|
end
|
57
|
-
end
|
58
|
-
end
|
59
46
|
|
60
|
-
|
61
|
-
def process_action(*args)
|
62
|
-
req = ScoutApm::RequestManager.lookup
|
63
|
-
req.annotate_request(:uri => scout_transaction_uri(request))
|
64
|
-
|
65
|
-
# IP Spoofing Protection can throw an exception, just move on w/o remote ip
|
66
|
-
req.context.add_user(:ip => request.remote_ip) rescue nil
|
67
|
-
|
68
|
-
req.set_headers(request.headers)
|
69
|
-
|
70
|
-
# Check if this this request is to be reported instantly
|
71
|
-
if instant_key = request.cookies['scoutapminstant']
|
72
|
-
Agent.instance.logger.info "Instant trace request with key=#{instant_key} for path=#{path}"
|
73
|
-
req.instant_key = instant_key
|
74
|
-
end
|
75
|
-
|
76
|
-
req.web!
|
77
|
-
|
78
|
-
layer = ScoutApm::Layer.new("Controller", "#{controller_path}/#{action_name}")
|
47
|
+
end
|
79
48
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
49
|
+
# Returns a new anonymous module each time it is called. So
|
50
|
+
# we can insert this multiple times into the ancestors
|
51
|
+
# stack. Otherwise it only exists the first time you include it
|
52
|
+
# (under Metal, instead of under API) and we miss instrumenting
|
53
|
+
# before_action callbacks
|
54
|
+
def self.build_instrument_module
|
55
|
+
Module.new do
|
56
|
+
def process_action(*args)
|
57
|
+
req = ScoutApm::RequestManager.lookup
|
58
|
+
current_layer = req.current_layer
|
59
|
+
|
60
|
+
# Check if this this request is to be reported instantly
|
61
|
+
if instant_key = request.cookies['scoutapminstant']
|
62
|
+
Agent.instance.logger.info "Instant trace request with key=#{instant_key} for path=#{path}"
|
63
|
+
req.instant_key = instant_key
|
64
|
+
end
|
65
|
+
|
66
|
+
if current_layer && current_layer.type == "Controller"
|
67
|
+
# Don't start a new layer if ActionController::API or ActionController::Base handled it already.
|
68
|
+
super
|
69
|
+
else
|
70
|
+
req.annotate_request(:uri => ScoutApm::Instruments::ActionControllerRails3Rails4.scout_transaction_uri(request))
|
71
|
+
|
72
|
+
# IP Spoofing Protection can throw an exception, just move on w/o remote ip
|
73
|
+
req.context.add_user(:ip => request.remote_ip) rescue nil
|
74
|
+
req.set_headers(request.headers)
|
75
|
+
|
76
|
+
req.web!
|
77
|
+
|
78
|
+
resolved_name = scout_action_name(*args)
|
79
|
+
layer = ScoutApm::Layer.new("Controller", "#{controller_path}/#{resolved_name}")
|
80
|
+
|
81
|
+
if enable_scoutprof? && ScoutApm::Agent.instance.config.value('profile') && ScoutApm::Instruments::Stacks::ENABLED
|
82
|
+
if defined?(ScoutApm::Instruments::Stacks::INSTALLED) && ScoutApm::Instruments::Stacks::INSTALLED
|
83
|
+
# Capture ScoutProf if we can
|
84
|
+
req.enable_profiled_thread!
|
85
|
+
layer.set_root_class(self.class)
|
86
|
+
layer.traced!
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
req.start_layer(layer)
|
91
|
+
begin
|
92
|
+
super
|
93
|
+
rescue
|
94
|
+
req.error!
|
95
|
+
raise
|
96
|
+
ensure
|
97
|
+
req.stop_layer
|
98
|
+
end
|
99
|
+
end
|
86
100
|
end
|
87
101
|
end
|
88
|
-
|
89
|
-
# Start the layer, then execute the user's code
|
90
|
-
req.start_layer(layer)
|
91
|
-
begin
|
92
|
-
super
|
93
|
-
rescue
|
94
|
-
req.error!
|
95
|
-
raise
|
96
|
-
ensure
|
97
|
-
req.stop_layer
|
98
|
-
end
|
99
|
-
end # process_action
|
102
|
+
end
|
100
103
|
|
101
104
|
# Given an +ActionDispatch::Request+, formats the uri based on config settings.
|
102
|
-
def scout_transaction_uri(request)
|
105
|
+
def self.scout_transaction_uri(request)
|
103
106
|
case ScoutApm::Agent.instance.config.value("uri_reporting")
|
104
107
|
when 'path'
|
105
108
|
request.path # strips off the query string for more security
|
@@ -107,8 +110,47 @@ module ScoutApm
|
|
107
110
|
request.filtered_path
|
108
111
|
end
|
109
112
|
end
|
113
|
+
end
|
110
114
|
|
111
|
-
|
115
|
+
module ActionControllerBaseInstruments
|
116
|
+
include ScoutApm::Instruments::ActionControllerRails3Rails4.build_instrument_module
|
117
|
+
|
118
|
+
def scout_action_name(*args)
|
119
|
+
action_name
|
120
|
+
end
|
121
|
+
|
122
|
+
def enable_scoutprof?
|
123
|
+
true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
module ActionControllerMetalInstruments
|
128
|
+
include ScoutApm::Instruments::ActionControllerRails3Rails4.build_instrument_module
|
129
|
+
|
130
|
+
def scout_action_name(*args)
|
131
|
+
action_name = args[0]
|
132
|
+
end
|
133
|
+
|
134
|
+
def enable_scoutprof?
|
135
|
+
false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module ActionControllerAPIInstruments
|
140
|
+
include ScoutApm::Instruments::ActionControllerRails3Rails4.build_instrument_module
|
141
|
+
|
142
|
+
def scout_action_name(*args)
|
143
|
+
action_name
|
144
|
+
end
|
145
|
+
|
146
|
+
def enable_scoutprof?
|
147
|
+
false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Empty, noop module to provide compatibility w/ previous manual instrumentation
|
152
|
+
module ActionControllerRails3Rails4Instruments
|
153
|
+
end
|
112
154
|
end
|
113
155
|
end
|
114
156
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Instruments
|
3
|
+
# instrumentation for Rails 3 and Rails 4 is the same.
|
4
|
+
class ActionView
|
5
|
+
attr_reader :logger
|
6
|
+
|
7
|
+
def initalize(logger=ScoutApm::Agent.instance.logger)
|
8
|
+
@logger = logger
|
9
|
+
@installed = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def installed?
|
13
|
+
@installed
|
14
|
+
end
|
15
|
+
|
16
|
+
def install
|
17
|
+
@installed = true
|
18
|
+
|
19
|
+
if defined?(::ActionView) && defined?(::ActionView::PartialRenderer)
|
20
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting ActionView::PartialRenderer"
|
21
|
+
::ActionView::PartialRenderer.class_eval do
|
22
|
+
include ScoutApm::Tracer
|
23
|
+
|
24
|
+
instrument_method :render_partial,
|
25
|
+
:type => "View",
|
26
|
+
:name => '#{@template.virtual_path rescue "Unknown Partial"}/Rendering',
|
27
|
+
:scope => true
|
28
|
+
|
29
|
+
instrument_method :collection_with_template,
|
30
|
+
:type => "View",
|
31
|
+
:name => '#{@template.virtual_path rescue "Unknown Collection"}/Rendering',
|
32
|
+
:scope => true
|
33
|
+
end
|
34
|
+
|
35
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting ActionView::TemplateRenderer"
|
36
|
+
::ActionView::TemplateRenderer.class_eval do
|
37
|
+
include ScoutApm::Tracer
|
38
|
+
instrument_method :render_template,
|
39
|
+
:type => "View",
|
40
|
+
:name => '#{args[0].virtual_path rescue "Unknown"}/Rendering',
|
41
|
+
:scope => true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
@@ -122,7 +122,7 @@ module ScoutApm
|
|
122
122
|
current_layer.desc = desc
|
123
123
|
end
|
124
124
|
|
125
|
-
log_without_scout_instruments(
|
125
|
+
log_without_scout_instruments(*args, &block)
|
126
126
|
|
127
127
|
# OR: Start a new layer, we didn't pick up instrumentation earlier in the stack.
|
128
128
|
else
|
@@ -130,7 +130,7 @@ module ScoutApm
|
|
130
130
|
layer.desc = desc
|
131
131
|
req.start_layer(layer)
|
132
132
|
begin
|
133
|
-
log_without_scout_instruments(
|
133
|
+
log_without_scout_instruments(*args, &block)
|
134
134
|
ensure
|
135
135
|
req.stop_layer
|
136
136
|
end
|
@@ -32,8 +32,8 @@ module ScoutApm
|
|
32
32
|
### See moped instrument for Moped driven deploys
|
33
33
|
|
34
34
|
### 5.x Mongoid
|
35
|
-
if mongoid_v5? && defined?(::Mongoid::Contextual::Mongo)
|
36
|
-
ScoutApm::Agent.instance.logger.info "Instrumenting Mongoid 5.x"
|
35
|
+
if (mongoid_v5? || mongoid_v6?) && defined?(::Mongoid::Contextual::Mongo)
|
36
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting Mongoid 5.x/6.x"
|
37
37
|
# All the public methods from Mongoid::Contextual::Mongo.
|
38
38
|
# TODO: Geo and MapReduce support (?). They are in other Contextual::* classes
|
39
39
|
methods = [
|
@@ -99,6 +99,14 @@ module ScoutApm
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
def mongoid_v6?
|
103
|
+
if defined?(::Mongoid::VERSION)
|
104
|
+
::Mongoid::VERSION =~ /\A6/
|
105
|
+
else
|
106
|
+
false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
102
110
|
|
103
111
|
# Example of what a filter looks like: => {"founded"=>{"$gte"=>"1980-1-1"}, "name"=>{"$in"=>["Tool", "Deftones", "Melvins"]}}
|
104
112
|
# Approach: find every leaf-node, clear it. inspect the whole thing when done.
|