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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.markdown +47 -0
  4. data/Guardfile +42 -0
  5. data/lib/scout_apm.rb +14 -0
  6. data/lib/scout_apm/agent.rb +58 -1
  7. data/lib/scout_apm/agent/logging.rb +6 -1
  8. data/lib/scout_apm/app_server_load.rb +21 -11
  9. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  10. data/lib/scout_apm/background_job_integrations/sidekiq.rb +19 -3
  11. data/lib/scout_apm/background_recorder.rb +43 -0
  12. data/lib/scout_apm/background_worker.rb +6 -6
  13. data/lib/scout_apm/config.rb +14 -3
  14. data/lib/scout_apm/environment.rb +14 -0
  15. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +112 -70
  16. data/lib/scout_apm/instruments/action_view.rb +49 -0
  17. data/lib/scout_apm/instruments/active_record.rb +2 -2
  18. data/lib/scout_apm/instruments/mongoid.rb +10 -2
  19. data/lib/scout_apm/instruments/resque.rb +40 -0
  20. data/lib/scout_apm/layer_children_set.rb +7 -2
  21. data/lib/scout_apm/rack.rb +26 -0
  22. data/lib/scout_apm/remote/message.rb +23 -0
  23. data/lib/scout_apm/remote/recorder.rb +57 -0
  24. data/lib/scout_apm/remote/router.rb +49 -0
  25. data/lib/scout_apm/remote/server.rb +58 -0
  26. data/lib/scout_apm/request_manager.rb +1 -1
  27. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  28. data/lib/scout_apm/tracked_request.rb +53 -5
  29. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  30. data/lib/scout_apm/utils/scm.rb +14 -0
  31. data/lib/scout_apm/version.rb +1 -1
  32. data/scout_apm.gemspec +2 -0
  33. data/test/unit/remote/test_message.rb +13 -0
  34. data/test/unit/remote/test_router.rb +33 -0
  35. data/test/unit/remote/test_server.rb +15 -0
  36. data/test/unit/test_tracked_request.rb +87 -0
  37. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  38. data/test/unit/utils/scm.rb +17 -0
  39. 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 { |hash, key| hash[key] = Set.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 = children[metric_type]
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
@@ -11,7 +11,7 @@ module ScoutApm
11
11
  def self.find
12
12
  req = Thread.current[:scout_request]
13
13
 
14
- if req && req.recorded?
14
+ if req && (req.stopping? || req.recorded?)
15
15
  nil
16
16
  else
17
17
  req
@@ -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') && Rails.env.development?
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
- ScoutApm::Agent.instance.logger.warn("Error stopping layer, was nil. Root Layer: #{@root_layer.inspect}")
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
- record!
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
- @recorded = true
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