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,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
|