scout_apm 3.0.0.pre10 → 3.0.0.pre11
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/.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.
|