scout_apm 2.5.1 → 5.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +68 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +5 -5
  5. data/CHANGELOG.markdown +176 -3
  6. data/Gemfile +1 -7
  7. data/LICENSE.md +21 -28
  8. data/gems/README.md +28 -0
  9. data/gems/instruments.gemfile +6 -0
  10. data/gems/octoshark.gemfile +4 -0
  11. data/gems/rails3.gemfile +5 -0
  12. data/gems/rails4.gemfile +4 -0
  13. data/gems/rails5.gemfile +4 -0
  14. data/gems/rails6.gemfile +4 -0
  15. data/gems/sidekiq.gemfile +4 -0
  16. data/gems/typhoeus.gemfile +3 -0
  17. data/lib/scout_apm/agent/preconditions.rb +3 -3
  18. data/lib/scout_apm/agent.rb +22 -0
  19. data/lib/scout_apm/agent_context.rb +21 -2
  20. data/lib/scout_apm/app_server_load.rb +7 -2
  21. data/lib/scout_apm/auto_instrument/instruction_sequence.rb +31 -0
  22. data/lib/scout_apm/auto_instrument/layer.rb +23 -0
  23. data/lib/scout_apm/auto_instrument/parser.rb +27 -0
  24. data/lib/scout_apm/auto_instrument/rails.rb +174 -0
  25. data/lib/scout_apm/auto_instrument.rb +5 -0
  26. data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -1
  27. data/lib/scout_apm/background_job_integrations/faktory.rb +103 -0
  28. data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +55 -0
  29. data/lib/scout_apm/background_job_integrations/que.rb +134 -0
  30. data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
  31. data/lib/scout_apm/background_job_integrations/sidekiq.rb +15 -10
  32. data/lib/scout_apm/background_job_integrations/sneakers.rb +11 -11
  33. data/lib/scout_apm/config.rb +54 -6
  34. data/lib/scout_apm/detailed_trace.rb +3 -2
  35. data/lib/scout_apm/environment.rb +18 -1
  36. data/lib/scout_apm/error.rb +27 -0
  37. data/lib/scout_apm/error_service/error_buffer.rb +39 -0
  38. data/lib/scout_apm/error_service/error_record.rb +211 -0
  39. data/lib/scout_apm/error_service/ignored_exceptions.rb +66 -0
  40. data/lib/scout_apm/error_service/middleware.rb +32 -0
  41. data/lib/scout_apm/error_service/notifier.rb +33 -0
  42. data/lib/scout_apm/error_service/payload.rb +47 -0
  43. data/lib/scout_apm/error_service/periodic_work.rb +17 -0
  44. data/lib/scout_apm/error_service/railtie.rb +11 -0
  45. data/lib/scout_apm/error_service/sidekiq.rb +80 -0
  46. data/lib/scout_apm/error_service.rb +34 -0
  47. data/lib/scout_apm/exceptions.rb +12 -0
  48. data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
  49. data/lib/scout_apm/external_service_metric_set.rb +97 -0
  50. data/lib/scout_apm/external_service_metric_stats.rb +85 -0
  51. data/lib/scout_apm/fake_store.rb +3 -0
  52. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +7 -2
  53. data/lib/scout_apm/git_revision.rb +9 -0
  54. data/lib/scout_apm/ignored_uris.rb +3 -1
  55. data/lib/scout_apm/instant/middleware.rb +4 -1
  56. data/lib/scout_apm/instrument_manager.rb +22 -1
  57. data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -1
  58. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +53 -29
  59. data/lib/scout_apm/instruments/action_view.rb +30 -9
  60. data/lib/scout_apm/instruments/active_record.rb +69 -19
  61. data/lib/scout_apm/instruments/elasticsearch.rb +93 -42
  62. data/lib/scout_apm/instruments/grape.rb +1 -1
  63. data/lib/scout_apm/instruments/http.rb +68 -0
  64. data/lib/scout_apm/instruments/http_client.rb +33 -14
  65. data/lib/scout_apm/instruments/influxdb.rb +2 -2
  66. data/lib/scout_apm/instruments/memcached.rb +58 -0
  67. data/lib/scout_apm/instruments/middleware_detailed.rb +1 -1
  68. data/lib/scout_apm/instruments/middleware_summary.rb +1 -1
  69. data/lib/scout_apm/instruments/mongoid.rb +10 -5
  70. data/lib/scout_apm/instruments/moped.rb +44 -19
  71. data/lib/scout_apm/instruments/net_http.rb +51 -16
  72. data/lib/scout_apm/instruments/rails_router.rb +1 -1
  73. data/lib/scout_apm/instruments/redis.rb +27 -12
  74. data/lib/scout_apm/instruments/redis5.rb +59 -0
  75. data/lib/scout_apm/instruments/sinatra.rb +3 -1
  76. data/lib/scout_apm/instruments/typhoeus.rb +90 -0
  77. data/lib/scout_apm/job_record.rb +4 -2
  78. data/lib/scout_apm/layaway_file.rb +4 -0
  79. data/lib/scout_apm/layer.rb +5 -2
  80. data/lib/scout_apm/layer_children_set.rb +9 -8
  81. data/lib/scout_apm/layer_converters/external_service_converter.rb +65 -0
  82. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
  83. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +2 -0
  84. data/lib/scout_apm/layer_converters/trace_converter.rb +7 -4
  85. data/lib/scout_apm/logger.rb +5 -1
  86. data/lib/scout_apm/middleware.rb +1 -1
  87. data/lib/scout_apm/periodic_work.rb +19 -0
  88. data/lib/scout_apm/remote/message.rb +4 -0
  89. data/lib/scout_apm/remote/server.rb +13 -1
  90. data/lib/scout_apm/reporter.rb +8 -3
  91. data/lib/scout_apm/reporting.rb +2 -1
  92. data/lib/scout_apm/request_histograms.rb +8 -0
  93. data/lib/scout_apm/serializers/app_server_load_serializer.rb +4 -0
  94. data/lib/scout_apm/serializers/directive_serializer.rb +4 -0
  95. data/lib/scout_apm/serializers/external_service_serializer_to_json.rb +15 -0
  96. data/lib/scout_apm/serializers/payload_serializer.rb +4 -3
  97. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +10 -3
  98. data/lib/scout_apm/slow_policy/age_policy.rb +33 -0
  99. data/lib/scout_apm/slow_policy/percent_policy.rb +22 -0
  100. data/lib/scout_apm/slow_policy/percentile_policy.rb +24 -0
  101. data/lib/scout_apm/slow_policy/policy.rb +21 -0
  102. data/lib/scout_apm/slow_policy/speed_policy.rb +16 -0
  103. data/lib/scout_apm/slow_request_policy.rb +18 -77
  104. data/lib/scout_apm/store.rb +31 -1
  105. data/lib/scout_apm/tracer.rb +2 -2
  106. data/lib/scout_apm/tracked_request.rb +35 -4
  107. data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
  108. data/lib/scout_apm/utils/marshal_logging.rb +90 -0
  109. data/lib/scout_apm/utils/sql_sanitizer.rb +47 -7
  110. data/lib/scout_apm/version.rb +1 -1
  111. data/lib/scout_apm.rb +46 -1
  112. data/scout_apm.gemspec +14 -9
  113. data/test/test_helper.rb +2 -2
  114. data/test/tmp/README.md +17 -0
  115. data/test/unit/agent_context_test.rb +29 -0
  116. data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
  117. data/test/unit/auto_instrument/assignments-instrumented.rb +31 -0
  118. data/test/unit/auto_instrument/assignments.rb +31 -0
  119. data/test/unit/auto_instrument/controller-ast.txt +57 -0
  120. data/test/unit/auto_instrument/controller-instrumented.rb +49 -0
  121. data/test/unit/auto_instrument/controller.rb +49 -0
  122. data/test/unit/auto_instrument/hanging_method.rb +6 -0
  123. data/test/unit/auto_instrument/rescue_from-instrumented.rb +13 -0
  124. data/test/unit/auto_instrument/rescue_from.rb +13 -0
  125. data/test/unit/auto_instrument_test.rb +62 -0
  126. data/test/unit/background_job_integrations/sidekiq_test.rb +17 -0
  127. data/test/unit/environment_test.rb +2 -2
  128. data/test/unit/error_service/error_buffer_test.rb +25 -0
  129. data/test/unit/error_service/ignored_exceptions_test.rb +49 -0
  130. data/test/unit/external_service_metric_set_test.rb +67 -0
  131. data/test/unit/external_service_metric_stats_test.rb +106 -0
  132. data/test/unit/ignored_uris_test.rb +6 -0
  133. data/test/unit/instruments/active_record_test.rb +40 -0
  134. data/test/unit/instruments/http_client_test.rb +24 -0
  135. data/test/unit/instruments/http_test.rb +24 -0
  136. data/test/unit/instruments/moped_test.rb +24 -0
  137. data/test/unit/instruments/net_http_test.rb +11 -1
  138. data/test/unit/instruments/redis_test.rb +24 -0
  139. data/test/unit/instruments/typhoeus_test.rb +42 -0
  140. data/test/unit/layer_children_set_test.rb +9 -0
  141. data/test/unit/remote/{test_message.rb → message_test.rb} +0 -0
  142. data/test/unit/remote/{test_router.rb → route_test.rb} +0 -0
  143. data/test/unit/remote/{test_server.rb → server_test.rb} +4 -1
  144. data/test/unit/request_histograms_test.rb +17 -0
  145. data/test/unit/serializers/payload_serializer_test.rb +39 -3
  146. data/test/unit/slow_request_policy_test.rb +41 -13
  147. data/test/unit/sql_sanitizer_test.rb +106 -0
  148. data/test/unit/tracer_test.rb +25 -0
  149. metadata +118 -60
  150. data/.travis.yml +0 -25
  151. data/lib/scout_apm/instruments/.DS_Store +0 -0
  152. data/lib/scout_apm/slow_job_policy.rb +0 -111
  153. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -25
  154. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -26
  155. data/test/unit/instruments/active_record_instruments_test.rb +0 -5
  156. data/test/unit/slow_job_policy_test.rb +0 -6
@@ -16,32 +16,37 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Moped)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting Moped"
23
+ logger.info "Instrumenting Moped. Prepend: #{prepend}"
24
24
 
25
- ::Moped::Node.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::Moped::Node.send(:include, ScoutApm::Tracer)
27
+ ::Moped::Node.send(:prepend, MopedInstrumentationPrepend)
28
+ else
29
+ ::Moped::Node.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def process_with_scout_instruments(operation, &callback)
29
- if operation.respond_to?(:collection)
30
- collection = operation.collection
31
- name = "Process/#{collection}/#{operation.class.to_s.split('::').last}"
32
- self.class.instrument("MongoDB", name, :annotate_layer => { :query => scout_sanitize_log(operation.log_inspect) }) do
33
- process_without_scout_instruments(operation, &callback)
32
+ def process_with_scout_instruments(operation, &callback)
33
+ if operation.respond_to?(:collection)
34
+ collection = operation.collection
35
+ name = "Process/#{collection}/#{operation.class.to_s.split('::').last}"
36
+ self.class.instrument("MongoDB", name, :annotate_layer => { :query => scout_sanitize_log(operation.log_inspect) }) do
37
+ process_without_scout_instruments(operation, &callback)
38
+ end
34
39
  end
35
40
  end
36
- end
37
- alias_method :process_without_scout_instruments, :process
38
- alias_method :process, :process_with_scout_instruments
39
-
40
- # replaces values w/ ?
41
- def scout_sanitize_log(log)
42
- return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
43
- log.gsub(/(=>")((?:[^"]|"")*)"/) do
44
- $1 + '?' + '"'
41
+ alias_method :process_without_scout_instruments, :process
42
+ alias_method :process, :process_with_scout_instruments
43
+
44
+ # replaces values w/ ?
45
+ def scout_sanitize_log(log)
46
+ return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
47
+ log.gsub(/(=>")((?:[^"]|"")*)"/) do
48
+ $1 + '?' + '"'
49
+ end
45
50
  end
46
51
  end
47
52
  end
@@ -49,6 +54,26 @@ module ScoutApm
49
54
  end
50
55
 
51
56
  end
57
+
58
+ module MopedInstrumentationPrepend
59
+ def process(operation, &callback)
60
+ if operation.respond_to?(:collection)
61
+ collection = operation.collection
62
+ name = "Process/#{collection}/#{operation.class.to_s.split('::').last}"
63
+ self.class.instrument("MongoDB", name, :annotate_layer => { :query => scout_sanitize_log(operation.log_inspect) }) do
64
+ super(operation, &callback)
65
+ end
66
+ end
67
+ end
68
+
69
+ # replaces values w/ ?
70
+ def scout_sanitize_log(log)
71
+ return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
72
+ log.gsub(/(=>")((?:[^"]|"")*)"/) do
73
+ $1 + '?' + '"'
74
+ end
75
+ end
76
+ end
52
77
  end
53
78
  end
54
79
 
@@ -16,34 +16,69 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Net) && defined?(::Net::HTTP)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting Net::HTTP"
23
+ logger.info "Instrumenting Net::HTTP. Prepend: #{prepend}"
24
24
 
25
- ::Net::HTTP.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::Net::HTTP.send(:include, ScoutApm::Tracer)
27
+ ::Net::HTTP.send(:prepend, NetHttpInstrumentationPrepend)
28
+ else
29
+ ::Net::HTTP.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def request_with_scout_instruments(*args,&block)
29
- self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(args.first)) do
30
- request_without_scout_instruments(*args, &block)
32
+ def request_with_scout_instruments(*args, &block)
33
+ self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(args.first)) do
34
+ request_without_scout_instruments(*args, &block)
35
+ end
31
36
  end
32
- end
33
37
 
34
- def request_scout_description(req)
35
- path = req.path
36
- path = path.path if path.respond_to?(:path)
38
+ def request_scout_description(req)
39
+ path = req.path
40
+ path = path.path if path.respond_to?(:path)
37
41
 
38
- max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
39
- (@address + path.split('?').first)[0..(max_length - 1)]
40
- end
42
+ # Protect against a nil address value
43
+ if @address.nil?
44
+ return "No Address Found"
45
+ end
46
+
47
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
48
+ (@address + path.split('?').first)[0..(max_length - 1)]
49
+ rescue
50
+ ""
51
+ end
41
52
 
42
- alias request_without_scout_instruments request
43
- alias request request_with_scout_instruments
53
+ alias request_without_scout_instruments request
54
+ alias request request_with_scout_instruments
55
+ end
44
56
  end
45
57
  end
46
58
  end
47
59
  end
60
+
61
+ module NetHttpInstrumentationPrepend
62
+ def request(request, *args, &block)
63
+ self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(args.first)) do
64
+ super(request, *args, &block)
65
+ end
66
+ end
67
+
68
+ def request_scout_description(req)
69
+ path = req.path
70
+ path = path.path if path.respond_to?(:path)
71
+
72
+ # Protect against a nil address value
73
+ if @address.nil?
74
+ return "No Address Found"
75
+ end
76
+
77
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
78
+ (@address + path.split('?').first)[0..(max_length - 1)]
79
+ rescue
80
+ ""
81
+ end
82
+ end
48
83
  end
49
84
  end
@@ -16,7 +16,7 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(ActionDispatch) && defined?(ActionDispatch::Routing) && defined?(ActionDispatch::Routing::RouteSet)
21
21
  @installed = true
22
22
 
@@ -16,28 +16,43 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
20
- if defined?(::Redis) && defined?(::Redis::Client)
19
+ def install(prepend:)
20
+ if defined?(::Redis) && defined?(::Redis::Client) && ::Redis::Client.instance_methods(false).include?(:call)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting Redis"
23
+ logger.info "Instrumenting Redis. Prepend: #{prepend}"
24
24
 
25
- ::Redis::Client.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::Redis::Client.send(:include, ScoutApm::Tracer)
27
+ ::Redis::Client.send(:prepend, RedisClientInstrumentationPrepend)
28
+ else
29
+ ::Redis::Client.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def call_with_scout_instruments(*args, &block)
29
- command = args.first.first rescue "Unknown"
32
+ def call_with_scout_instruments(*args, &block)
33
+ command = args.first.first rescue "Unknown"
30
34
 
31
- self.class.instrument("Redis", command) do
32
- call_without_scout_instruments(*args, &block)
35
+ self.class.instrument("Redis", command) do
36
+ call_without_scout_instruments(*args, &block)
37
+ end
33
38
  end
34
- end
35
39
 
36
- alias_method :call_without_scout_instruments, :call
37
- alias_method :call, :call_with_scout_instruments
40
+ alias_method :call_without_scout_instruments, :call
41
+ alias_method :call, :call_with_scout_instruments
42
+ end
38
43
  end
39
44
  end
40
45
  end
41
46
  end
47
+
48
+ module RedisClientInstrumentationPrepend
49
+ def call(*args, &block)
50
+ command = args.first.first rescue "Unknown"
51
+
52
+ self.class.instrument("Redis", command) do
53
+ super(*args, &block)
54
+ end
55
+ end
56
+ end
42
57
  end
43
58
  end
@@ -0,0 +1,59 @@
1
+ module ScoutApm
2
+ module Instruments
3
+ class Redis5
4
+ attr_reader :context
5
+
6
+ def initialize(context)
7
+ @context = context
8
+ @installed = false
9
+ end
10
+
11
+ def logger
12
+ context.logger
13
+ end
14
+
15
+ def installed?
16
+ @installed
17
+ end
18
+
19
+ def install(prepend:)
20
+ if defined?(::Redis) && defined?(::Redis::Client) && ::Redis::Client.instance_methods(false).include?(:call_v)
21
+ @installed = true
22
+
23
+ logger.info "Instrumenting Redis5. Prepend: #{prepend}"
24
+
25
+ if prepend
26
+ ::Redis::Client.send(:include, ScoutApm::Tracer)
27
+ ::Redis::Client.send(:prepend, Redis5ClientInstrumentationPrepend)
28
+ else
29
+ ::Redis::Client.class_eval do
30
+ include ScoutApm::Tracer
31
+
32
+ def call_with_scout_instruments(args, &block)
33
+ command = args.first rescue "Unknown"
34
+
35
+ self.class.instrument("Redis", command) do
36
+ call_without_scout_instruments(args, &block)
37
+ end
38
+ end
39
+
40
+ alias_method :call_without_scout_instruments, :call_v
41
+ alias_method :call_v, :call_with_scout_instruments
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module Redis5ClientInstrumentationPrepend
49
+ def call(args, &block)
50
+ command = args.first rescue "Unknown"
51
+
52
+ self.class.instrument("Redis", command) do
53
+ super(args, &block)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  # XXX: Is this file used?
2
4
  module ScoutApm
3
5
  module Instruments
@@ -13,7 +15,7 @@ module ScoutApm
13
15
  @installed
14
16
  end
15
17
 
16
- def install
18
+ def install(prepend:)
17
19
  if defined?(::Sinatra) && defined?(::Sinatra::Base) && ::Sinatra::Base.private_method_defined?(:dispatch!)
18
20
  @installed = true
19
21
 
@@ -0,0 +1,90 @@
1
+ module ScoutApm
2
+ module Instruments
3
+ class Typhoeus
4
+ attr_reader :context
5
+
6
+ def initialize(context)
7
+ @context = context
8
+ @installed = false
9
+ end
10
+
11
+ def logger
12
+ context.logger
13
+ end
14
+
15
+ def installed?
16
+ @installed
17
+ end
18
+
19
+ def install(prepend:)
20
+ if defined?(::Typhoeus)
21
+ @installed = true
22
+
23
+ logger.info "Instrumenting Typhoeus"
24
+
25
+ ::Typhoeus::Request.send(:prepend, TyphoeusInstrumentation)
26
+ ::Typhoeus::Hydra.send(:prepend, TyphoeusHydraInstrumentation)
27
+ end
28
+ end
29
+
30
+ module TyphoeusHydraInstrumentation
31
+ def run(*args, &block)
32
+ layer = ScoutApm::Layer.new("HTTP", "Hydra")
33
+ layer.desc = scout_desc
34
+
35
+ req = ScoutApm::RequestManager.lookup
36
+ req.start_layer(layer)
37
+
38
+ begin
39
+ super(*args, &block)
40
+ ensure
41
+ req.stop_layer
42
+ end
43
+ end
44
+
45
+ def scout_desc
46
+ "#{self.queued_requests.count} requests"
47
+ rescue
48
+ ""
49
+ end
50
+ end
51
+
52
+ module TyphoeusInstrumentation
53
+ def run(*args, &block)
54
+ layer = ScoutApm::Layer.new("HTTP", scout_request_verb)
55
+ layer.desc = scout_desc(scout_request_verb, scout_request_url)
56
+
57
+ req = ScoutApm::RequestManager.lookup
58
+ req.start_layer(layer)
59
+
60
+ begin
61
+ super(*args, &block)
62
+ ensure
63
+ req.stop_layer
64
+ end
65
+ end
66
+
67
+ def scout_desc(verb, uri)
68
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
69
+ (String(uri).split('?').first)[0..(max_length - 1)]
70
+ rescue
71
+ ""
72
+ end
73
+
74
+ def scout_request_url
75
+ self.url
76
+ rescue
77
+ ""
78
+ end
79
+
80
+ def scout_request_verb
81
+ self.options[:method].to_s
82
+ rescue
83
+ ""
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -32,8 +32,10 @@ module ScoutApm
32
32
 
33
33
  # Modifies self and returns self, after merging in `other`.
34
34
  def combine!(other)
35
- same_job = queue_name == other.queue_name && job_name == other.job_name
36
- raise "Mismatched Merge of Background Job" unless same_job
35
+ if !self.eql?(other)
36
+ ScoutApm::Agent.instance.logger.debug("Mismatched Merge of Background Job: (Queue #{queue_name} == #{other.queue_name}) (Name #{job_name} == #{other.job_name}) (Hash #{hash} == #{other.hash})")
37
+ return self
38
+ end
37
39
 
38
40
  @errors += other.errors
39
41
  @metric_set = metric_set.combine!(other.metric_set)
@@ -30,6 +30,10 @@ module ScoutApm
30
30
 
31
31
  def serialize(data)
32
32
  Marshal.dump(data)
33
+ rescue
34
+ ScoutApm::Agent.instance.logger.info("Failed Marshalling LayawayFile")
35
+ ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
36
+ raise
33
37
  end
34
38
 
35
39
  def deserialize(data)
@@ -36,7 +36,10 @@ module ScoutApm
36
36
 
37
37
  # If this layer took longer than a fixed amount of time, store the
38
38
  # backtrace of where it occurred.
39
- attr_reader :backtrace
39
+ attr_accessor :backtrace
40
+
41
+ # The file name associated with the layer. Only used for autoinstruments overhead logging.
42
+ attr_accessor :file_name
40
43
 
41
44
  # As we go through a part of a request, instrumentation can store additional data
42
45
  # Known Keys:
@@ -113,7 +116,7 @@ module ScoutApm
113
116
  # In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint.
114
117
  def caller_array
115
118
  # omits the first several callers which are in the ScoutAPM stack.
116
- if ScoutApm::Agent.instance.context.environment.ruby_2?
119
+ if ScoutApm::Agent.instance.context.environment.ruby_2? || ScoutApm::Agent.instance.context.environment.ruby_3?
117
120
  caller(3...BACKTRACE_CALLER_LIMIT)
118
121
  else
119
122
  caller[3...BACKTRACE_CALLER_LIMIT]
@@ -46,9 +46,15 @@ module ScoutApm
46
46
  set = child_set(metric_type)
47
47
 
48
48
  if set.size >= unique_cutoff
49
- # find limited_layer
50
- @limited_layers || init_limited_layers
51
- @limited_layers[metric_type].absorb(child)
49
+ # find or create limited_layer
50
+ @limited_layers ||= Hash.new
51
+ layer = if @limited_layers.has_key?(metric_type)
52
+ @limited_layers[metric_type]
53
+ else
54
+ @limited_layers[metric_type] = LimitedLayer.new(metric_type)
55
+ end
56
+
57
+ layer.absorb(child)
52
58
  else
53
59
  # we have space just add it
54
60
  set << child
@@ -76,10 +82,5 @@ module ScoutApm
76
82
  def size
77
83
  @children.size
78
84
  end
79
-
80
- # hold off initializing this until we know we need it
81
- def init_limited_layers
82
- @limited_layers ||= Hash.new { |hash, key| hash[key] = LimitedLayer.new(key) }
83
- end
84
85
  end
85
86
  end
@@ -0,0 +1,65 @@
1
+ module ScoutApm
2
+ module LayerConverters
3
+ class ExternalServiceConverter < ConverterBase
4
+ def initialize(*)
5
+ super
6
+ @external_service_metric_set = ExternalServiceMetricSet.new(context)
7
+ end
8
+
9
+ def register_hooks(walker)
10
+ super
11
+
12
+ return unless scope_layer
13
+
14
+ walker.on do |layer|
15
+ next if skip_layer?(layer)
16
+ stat = ExternalServiceMetricStats.new(
17
+ domain_name(layer),
18
+ operation_name(layer), # operation name/verb. GET/POST/PUT etc.
19
+ scope_layer.legacy_metric_name, # controller_scope
20
+ 1, # count, this is a single call, so 1
21
+ layer.total_call_time
22
+ )
23
+ @external_service_metric_set << stat
24
+ end
25
+ end
26
+
27
+ def record!
28
+ # Everything in the metric set here is from a single transaction, which
29
+ # we want to keep track of. (One web call did a User#find 10 times, but
30
+ # only due to 1 http request)
31
+ @external_service_metric_set.increment_transaction_count!
32
+ @store.track_external_service_metrics!(@external_service_metric_set)
33
+
34
+ nil # not returning anything in the layer results ... not used
35
+ end
36
+
37
+ def skip_layer?(layer)
38
+ layer.type != 'HTTP' ||
39
+ layer.limited? ||
40
+ super
41
+ end
42
+
43
+ private
44
+
45
+ # If we can't name the domain name, default to:
46
+ DEFAULT_DOMAIN = "Unknown"
47
+
48
+ def domain_name(layer)
49
+ domain = ""
50
+ desc_str = layer.desc.to_s
51
+ desc_str = 'http://' + desc_str unless desc_str =~ /^http/i
52
+ domain = URI.parse(desc_str).host
53
+ rescue
54
+ # Do nothing
55
+ ensure
56
+ domain = DEFAULT_DOMAIN if domain.to_s.blank?
57
+ domain
58
+ end
59
+
60
+ def operation_name(layer)
61
+ "all" # Hardcode to "all" until we support breakout by verb
62
+ end
63
+ end
64
+ end
65
+ end
@@ -5,6 +5,10 @@
5
5
  # show
6
6
  # render :update
7
7
  # end
8
+
9
+ # This doesn't cache the negative result when searching for a controller / job,
10
+ # so that we can ask again later after more of the request has occurred and
11
+ # correctly find it.
8
12
  module ScoutApm
9
13
  module LayerConverters
10
14
  class FindLayerByType
@@ -11,6 +11,8 @@ module ScoutApm
11
11
  def record!
12
12
  return unless request.web?
13
13
 
14
+ return unless context.config.value('record_queue_time')
15
+
14
16
  return unless headers
15
17
 
16
18
  raw_start = locate_timestamp
@@ -58,6 +58,9 @@ module ScoutApm
58
58
  code = "" # User#index for instance
59
59
 
60
60
  spans = create_spans(request.root_layer)
61
+ if limited?
62
+ tags[:"scout.reached_span_cap"] = true
63
+ end
61
64
 
62
65
  DetailedTrace.new(
63
66
  transaction_id,
@@ -117,7 +120,9 @@ module ScoutApm
117
120
  tags)
118
121
 
119
122
  layer.children.each do |child|
120
- unless over_span_limit?(result)
123
+ # Don't create spans from limited layers. These don't have start/stop times and our processing can't
124
+ # handle these yet.
125
+ unless over_span_limit?(result) || child.is_a?(LimitedLayer)
121
126
  result += create_spans(child, span_id)
122
127
  end
123
128
  end
@@ -152,10 +157,8 @@ module ScoutApm
152
157
  # Limit Handling
153
158
  ################################################################################
154
159
 
155
- # To prevent huge traces from being generated, we should stop collecting
160
+ # To prevent huge traces from being generated, we stop collecting
156
161
  # spans as we go beyond some reasonably large count.
157
- # Until the root cause of https://github.com/scoutapp/scout_apm_ruby/issues/267 is addressed,
158
- # keep `MAX_SPANS` less than `DEFAULT_UNIQUE_CUTOFF`.
159
162
  MAX_SPANS = 1500
160
163
 
161
164
  def over_span_limit?(spans)
@@ -70,13 +70,17 @@ module ScoutApm
70
70
 
71
71
  def build_logger
72
72
  logger_class.new(@log_destination)
73
+ rescue => e
74
+ logger = ::Logger.new(STDERR)
75
+ logger.error("Error while building ScoutApm logger: #{e.message}. Falling back to STDERR")
76
+ logger
73
77
  end
74
78
 
75
79
  def logger_class
76
80
  klass = @opts.fetch(:logger_class, ::Logger)
77
81
  case klass
78
82
  when String
79
- result = KlassHelper.lookup(klass)
83
+ result = Utils::KlassHelper.lookup(klass)
80
84
  if result == :missing_class
81
85
  ::Logger
82
86
  else
@@ -26,7 +26,7 @@ module ScoutApm
26
26
  ScoutApm::Agent.instance.start
27
27
  @started = ScoutApm::Agent.instance.context.started? && ScoutApm::Agent.instance.background_worker_running?
28
28
  rescue => e
29
- ScoutApm::Agent.instance.context.logger("Failed to start via Middleware: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
29
+ ScoutApm::Agent.instance.context.logger.info("Failed to start via Middleware: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
30
30
  end
31
31
  end
32
32
  end
@@ -12,10 +12,29 @@ module ScoutApm
12
12
  ScoutApm::Debug.instance.call_periodic_hooks
13
13
  @reporting.process_metrics
14
14
  clean_old_percentiles
15
+
16
+ if context.config.value('auto_instruments')
17
+ log_autoinstrument_significant_counts rescue nil
18
+ end
15
19
  end
16
20
 
17
21
  private
18
22
 
23
+ def log_autoinstrument_significant_counts
24
+ # Ex key/value -
25
+ # "/Users/dlite/projects/scout/apm/app/controllers/application_controller.rb"=>[[0.0, 689], [1.0, 16]]
26
+ hists = context.auto_instruments_layer_histograms.as_json
27
+ hists_summary = hists.map { |file, buckets|
28
+ total = buckets.map(&:last).inject(0) { |sum, count| sum + count }
29
+ significant = (buckets.last.last / total.to_f).round(2)
30
+ [
31
+ file,
32
+ {:total => total, :significant => significant}
33
+ ]
34
+ }.to_h
35
+ context.logger.debug("AutoInstrument Significant Layer Histograms: #{hists_summary.pretty_inspect}")
36
+ end
37
+
19
38
  # XXX: Move logic into a RequestHistogramsByTime class that can keep the timeout logic in it
20
39
  def clean_old_percentiles
21
40
  context.
@@ -17,6 +17,10 @@ module ScoutApm
17
17
 
18
18
  def encode
19
19
  Marshal.dump(self)
20
+ rescue
21
+ ScoutApm::Agent.instance.logger.info("Failed Marshalling Remote::Message")
22
+ ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(self).dive) rescue nil
23
+ raise
20
24
  end
21
25
  end
22
26
  end