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,40 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Instruments
|
3
|
+
module Resque
|
4
|
+
def around_perform_with_scout_instruments(*args)
|
5
|
+
job_name = self.to_s
|
6
|
+
queue = find_queue
|
7
|
+
|
8
|
+
if job_name == "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper"
|
9
|
+
job_name = args.first["job_class"] rescue job_name
|
10
|
+
queue = args.first["queue_name"] rescue queue_name
|
11
|
+
end
|
12
|
+
|
13
|
+
req = ScoutApm::RequestManager.lookup
|
14
|
+
req.job!
|
15
|
+
|
16
|
+
begin
|
17
|
+
req.start_layer(ScoutApm::Layer.new('Queue', queue))
|
18
|
+
started_queue = true
|
19
|
+
req.start_layer(ScoutApm::Layer.new('Job', job_name))
|
20
|
+
started_job = true
|
21
|
+
|
22
|
+
yield
|
23
|
+
rescue => e
|
24
|
+
req.error!
|
25
|
+
raise
|
26
|
+
ensure
|
27
|
+
req.stop_layer if started_job
|
28
|
+
req.stop_layer if started_queue
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_queue
|
33
|
+
return @queue if @queue
|
34
|
+
return queue if self.respond_to?(:queue)
|
35
|
+
return "unknown"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -27,18 +27,23 @@ module ScoutApm
|
|
27
27
|
private :children
|
28
28
|
|
29
29
|
def initialize(unique_cutoff = DEFAULT_UNIQUE_CUTOFF)
|
30
|
-
@children = Hash.new
|
30
|
+
@children = Hash.new
|
31
31
|
@limited_layers = nil # populated when needed
|
32
32
|
@unique_cutoff = unique_cutoff
|
33
33
|
end
|
34
34
|
|
35
|
+
def child_set(metric_type)
|
36
|
+
children[metric_type] = Set.new if !children.has_key?(metric_type)
|
37
|
+
children[metric_type]
|
38
|
+
end
|
39
|
+
|
35
40
|
# Add a new layer into this set
|
36
41
|
# Only add completed layers - otherwise this will collect up incorrect info
|
37
42
|
# into the created LimitedLayer, since it will "freeze" any current data for
|
38
43
|
# total_call_time and similar methods.
|
39
44
|
def <<(child)
|
40
45
|
metric_type = child.type
|
41
|
-
set =
|
46
|
+
set = child_set(metric_type)
|
42
47
|
|
43
48
|
if set.size >= unique_cutoff
|
44
49
|
# find limited_layer
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Rack
|
3
|
+
def self.install!
|
4
|
+
ScoutApm::Agent.instance.start(:skip_app_server_check => true)
|
5
|
+
ScoutApm::Agent.instance.start_background_worker
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.transaction(endpoint_name, env)
|
9
|
+
req = ScoutApm::RequestManager.lookup
|
10
|
+
req.annotate_request(:uri => env["REQUEST_PATH"]) rescue nil
|
11
|
+
req.context.add_user(:ip => env["REMOTE_ADDR"]) rescue nil
|
12
|
+
|
13
|
+
req.web!
|
14
|
+
req.start_layer(ScoutApm::Layer.new('Controller', endpoint_name))
|
15
|
+
|
16
|
+
begin
|
17
|
+
yield
|
18
|
+
rescue
|
19
|
+
req.error!
|
20
|
+
raise
|
21
|
+
ensure
|
22
|
+
req.stop_layer
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Remote
|
3
|
+
class Message
|
4
|
+
attr_reader :type
|
5
|
+
attr_reader :command
|
6
|
+
attr_reader :args
|
7
|
+
|
8
|
+
def initialize(type, command, *args)
|
9
|
+
@type = type
|
10
|
+
@command = command
|
11
|
+
@args = args
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.decode(msg)
|
15
|
+
Marshal.load(msg)
|
16
|
+
end
|
17
|
+
|
18
|
+
def encode
|
19
|
+
Marshal.dump(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Remote
|
3
|
+
class Recorder
|
4
|
+
attr_reader :logger
|
5
|
+
attr_reader :remote_agent_host
|
6
|
+
attr_reader :remote_agent_port
|
7
|
+
|
8
|
+
def initialize(remote_agent_host, remote_agent_port, logger)
|
9
|
+
@remote_agent_host = remote_agent_host
|
10
|
+
@remote_agent_port = remote_agent_port
|
11
|
+
@logger = logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
# nothing to do
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
# nothing to do
|
21
|
+
end
|
22
|
+
|
23
|
+
def record!(request)
|
24
|
+
begin
|
25
|
+
t1 = Time.now
|
26
|
+
# Mark this request as recorded, so the next lookup on this thread, it
|
27
|
+
# can be recreated
|
28
|
+
request.recorded!
|
29
|
+
|
30
|
+
# Only send requests that we actually want. Incidental http &
|
31
|
+
# background thread stuff can just be dropped
|
32
|
+
unless request.job? || request.web?
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
request.prepare_to_dump!
|
37
|
+
message = ScoutApm::Remote::Message.new('record', 'record!', request)
|
38
|
+
encoded = message.encode
|
39
|
+
logger.debug "Remote Agent: Posting a message of length: #{encoded.length}"
|
40
|
+
post(encoded)
|
41
|
+
t2 = Time.now
|
42
|
+
|
43
|
+
logger.debug("Remote Recording took: #{t2.to_f - t1.to_f} seconds")
|
44
|
+
rescue => e
|
45
|
+
logger.debug "Remote: Error while sending to collector: #{e.inspect}, #{e.backtrace.join("\n")}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def post(encoded_message)
|
50
|
+
http = Net::HTTP.new(remote_agent_host, remote_agent_port)
|
51
|
+
request = Net::HTTP::Post.new("/users")
|
52
|
+
request.body = encoded_message
|
53
|
+
response = http.request(request)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Remote
|
3
|
+
class Router
|
4
|
+
attr_reader :logger
|
5
|
+
attr_reader :routes
|
6
|
+
|
7
|
+
# If/When we add different types, this signature should change to a hash
|
8
|
+
# of {type => Object}, rather than building it in the initializer here.
|
9
|
+
#
|
10
|
+
# Keys of routes should be strings
|
11
|
+
def initialize(recorder, logger)
|
12
|
+
@routes = {
|
13
|
+
'record' => recorder
|
14
|
+
}
|
15
|
+
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
|
19
|
+
# A message is a 2 element array [:type, :command, [args]].
|
20
|
+
# For this first creation, this should be ['record', 'record', [TrackedRequest]] (the args arg should always be an array, even w/ only 1 item)
|
21
|
+
#
|
22
|
+
# Where
|
23
|
+
# type: ['recorder']
|
24
|
+
# command: any function supported on that type of object
|
25
|
+
# args: any array of arguments
|
26
|
+
#
|
27
|
+
# Raises on unknown message
|
28
|
+
#
|
29
|
+
# Returns whatever the recipient object returns
|
30
|
+
def handle(msg)
|
31
|
+
message = Remote::Message.decode(msg)
|
32
|
+
assert_type(message)
|
33
|
+
call_route(message)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def assert_type(message)
|
39
|
+
if ! routes.keys.include?(message.type.to_s)
|
40
|
+
raise "Unknown type: #{message.type.to_s}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def call_route(message)
|
45
|
+
routes[message.type].send(message.command, *message.args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Web Server bound to localhost that listens for remote agent reports. Forwards
|
2
|
+
# onto the router
|
3
|
+
module ScoutApm
|
4
|
+
module Remote
|
5
|
+
class Server
|
6
|
+
attr_reader :router
|
7
|
+
attr_reader :bind
|
8
|
+
attr_reader :port
|
9
|
+
attr_reader :logger
|
10
|
+
|
11
|
+
def initialize(bind, port, router, logger)
|
12
|
+
@router = router
|
13
|
+
@logger = logger
|
14
|
+
@bind = bind
|
15
|
+
@port = port
|
16
|
+
@server = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
@server = WEBrick::HTTPServer.new(
|
21
|
+
:BindAddress => bind,
|
22
|
+
:Port => port,
|
23
|
+
:AccessLog => [],
|
24
|
+
:Logger => @logger
|
25
|
+
)
|
26
|
+
|
27
|
+
@server.mount_proc '/' do |request, response|
|
28
|
+
router.handle(request.body)
|
29
|
+
|
30
|
+
# arbitrary response, client doesn't expect anything in particular
|
31
|
+
response.body = 'Ok'
|
32
|
+
end
|
33
|
+
|
34
|
+
@thread = Thread.new do
|
35
|
+
begin
|
36
|
+
logger.debug("Remote: Starting Server on #{bind}:#{port}")
|
37
|
+
|
38
|
+
@server.start
|
39
|
+
|
40
|
+
logger.debug("Remote: Server returned after #start call, thread exiting")
|
41
|
+
rescue => e
|
42
|
+
logger.debug("Remote: Server Exception, #{e}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def running?
|
48
|
+
@thread.alive?
|
49
|
+
@server && @server.status == :Running
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop
|
53
|
+
@server.stop
|
54
|
+
@thread.kill
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Provide a synchronous approach to recording TrackedRequests
|
2
|
+
# Doesn't attempt to background the work, or do it elsewhere. It happens
|
3
|
+
# inline, in the caller thread right when record! is called
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
class SynchronousRecorder
|
7
|
+
attr_reader :logger
|
8
|
+
|
9
|
+
def initialize(logger)
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
# nothing to do
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
# nothing to do
|
20
|
+
end
|
21
|
+
|
22
|
+
def record!(request)
|
23
|
+
request.record!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -42,6 +42,9 @@ module ScoutApm
|
|
42
42
|
# Whereas the instant_key gets set per-request in reponse to a URL param, dev_trace is set in the config file
|
43
43
|
attr_accessor :dev_trace
|
44
44
|
|
45
|
+
# An object that responds to `record!(TrackedRequest)` to store this tracked request
|
46
|
+
attr_reader :recorder
|
47
|
+
|
45
48
|
def initialize(store)
|
46
49
|
@store = store #this is passed in so we can use a real store (normal operation) or fake store (instant mode only)
|
47
50
|
@layers = []
|
@@ -51,12 +54,19 @@ module ScoutApm
|
|
51
54
|
@context = Context.new
|
52
55
|
@root_layer = nil
|
53
56
|
@error = false
|
57
|
+
@stopping = false
|
54
58
|
@instant_key = nil
|
55
59
|
@mem_start = mem_usage
|
56
|
-
@dev_trace = ScoutApm::Agent.instance.config.value('dev_trace') &&
|
60
|
+
@dev_trace = ScoutApm::Agent.instance.config.value('dev_trace') && ScoutApm::Agent.instance.environment.env == "development"
|
61
|
+
@recorder = ScoutApm::Agent.instance.recorder
|
62
|
+
|
63
|
+
ignore_request! if @recorder.nil?
|
57
64
|
end
|
58
65
|
|
59
66
|
def start_layer(layer)
|
67
|
+
# If we're already stopping, don't do additional layers
|
68
|
+
return if stopping?
|
69
|
+
|
60
70
|
return if ignoring_children?
|
61
71
|
|
62
72
|
return ignoring_start_layer if ignoring_request?
|
@@ -68,6 +78,9 @@ module ScoutApm
|
|
68
78
|
end
|
69
79
|
|
70
80
|
def stop_layer
|
81
|
+
# If we're already stopping, don't do additional layers
|
82
|
+
return if stopping?
|
83
|
+
|
71
84
|
return if ignoring_children?
|
72
85
|
|
73
86
|
return ignoring_stop_layer if ignoring_request?
|
@@ -79,7 +92,7 @@ module ScoutApm
|
|
79
92
|
# lined up correctly. If stop_layer gets called twice, when it should
|
80
93
|
# only have been called once you'll end up with this error.
|
81
94
|
if layer.nil?
|
82
|
-
|
95
|
+
logger.warn("Error stopping layer, was nil. Root Layer: #{@root_layer.inspect}")
|
83
96
|
stop_request
|
84
97
|
return
|
85
98
|
end
|
@@ -181,11 +194,20 @@ module ScoutApm
|
|
181
194
|
#
|
182
195
|
# * Send the request off to be stored
|
183
196
|
def stop_request
|
197
|
+
@stopping = true
|
198
|
+
|
184
199
|
if ScoutApm::Agent.instance.config.value('profile')
|
185
200
|
ScoutApm::Instruments::Stacks.stop_sampling(true)
|
186
201
|
ScoutApm::Instruments::Stacks.update_indexes(0, 0)
|
187
202
|
end
|
188
|
-
|
203
|
+
|
204
|
+
if recorder
|
205
|
+
recorder.record!(self)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def stopping?
|
210
|
+
@stopping
|
189
211
|
end
|
190
212
|
|
191
213
|
# Enable ScoutProf for this thread
|
@@ -250,13 +272,21 @@ module ScoutApm
|
|
250
272
|
# Persist the Request
|
251
273
|
###################################
|
252
274
|
|
275
|
+
def recorded!
|
276
|
+
@recorded = true
|
277
|
+
end
|
278
|
+
|
253
279
|
# Convert this request to the appropriate structure, then report it into
|
254
280
|
# the peristent Store object
|
255
281
|
def record!
|
256
|
-
|
282
|
+
recorded!
|
257
283
|
|
258
284
|
return if ignoring_request?
|
259
285
|
|
286
|
+
# If we didn't have store, but we're trying to record anyway, go
|
287
|
+
# figure that out. (this happens in Remote Agent scenarios)
|
288
|
+
restore_store if @store.nil?
|
289
|
+
|
260
290
|
# Bail out early if the user asked us to ignore this uri
|
261
291
|
return if ScoutApm::Agent.instance.ignored_uris.ignore?(annotations[:uri])
|
262
292
|
|
@@ -301,7 +331,6 @@ module ScoutApm
|
|
301
331
|
|
302
332
|
allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
|
303
333
|
@store.track!(allocation_metrics)
|
304
|
-
|
305
334
|
end
|
306
335
|
|
307
336
|
# Only call this after the request is complete
|
@@ -415,5 +444,24 @@ module ScoutApm
|
|
415
444
|
def ignoring_recorded?
|
416
445
|
@ignoring_depth <= 0
|
417
446
|
end
|
447
|
+
|
448
|
+
def logger
|
449
|
+
ScoutApm::Agent.instance.logger
|
450
|
+
end
|
451
|
+
|
452
|
+
# Actually go fetch & make-real any lazily created data.
|
453
|
+
# Clean up any cleverness in objects.
|
454
|
+
# Makes this object ready to be Marshal Dumped (or otherwise serialized)
|
455
|
+
def prepare_to_dump!
|
456
|
+
@call_set = nil
|
457
|
+
@store = nil
|
458
|
+
@recorder = nil
|
459
|
+
end
|
460
|
+
|
461
|
+
# Go re-fetch the store based on what the Agent's official one is. Used
|
462
|
+
# after hydrating a dumped TrackedRequest
|
463
|
+
def restore_store
|
464
|
+
@store = ScoutApm::Agent.instance.store
|
465
|
+
end
|
418
466
|
end
|
419
467
|
end
|