scout_apm 2.6.10 → 3.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -2
  3. data/.rubocop.yml +3 -11
  4. data/CHANGELOG.markdown +4 -362
  5. data/Gemfile +1 -14
  6. data/README.markdown +7 -52
  7. data/Rakefile +1 -0
  8. data/ext/allocations/allocations.c +1 -7
  9. data/ext/allocations/extconf.rb +0 -1
  10. data/ext/rusage/rusage.c +0 -26
  11. data/ext/stacks/extconf.rb +37 -0
  12. data/ext/stacks/scout_atomics.h +86 -0
  13. data/ext/stacks/stacks.c +811 -0
  14. data/lib/scout_apm/agent/logging.rb +69 -0
  15. data/lib/scout_apm/agent/reporting.rb +126 -0
  16. data/lib/scout_apm/agent.rb +259 -138
  17. data/lib/scout_apm/app_server_load.rb +15 -41
  18. data/lib/scout_apm/attribute_arranger.rb +3 -14
  19. data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -70
  20. data/lib/scout_apm/background_job_integrations/sidekiq.rb +24 -31
  21. data/lib/scout_apm/background_worker.rb +12 -23
  22. data/lib/scout_apm/capacity.rb +57 -0
  23. data/lib/scout_apm/config.rb +37 -206
  24. data/lib/scout_apm/context.rb +4 -20
  25. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +12 -0
  26. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +83 -0
  27. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +12 -0
  28. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +88 -0
  29. data/lib/scout_apm/environment.rb +28 -42
  30. data/lib/scout_apm/fake_store.rb +0 -12
  31. data/lib/scout_apm/framework_integrations/rails_2.rb +1 -2
  32. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +6 -17
  33. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  34. data/lib/scout_apm/histogram.rb +3 -12
  35. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  36. data/lib/scout_apm/instant/middleware.rb +54 -202
  37. data/lib/scout_apm/instant_reporting.rb +7 -7
  38. data/lib/scout_apm/instruments/.DS_Store +0 -0
  39. data/lib/scout_apm/instruments/action_controller_rails_2.rb +9 -15
  40. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +76 -124
  41. data/lib/scout_apm/instruments/active_record.rb +29 -324
  42. data/lib/scout_apm/instruments/delayed_job.rb +57 -0
  43. data/lib/scout_apm/instruments/elasticsearch.rb +6 -10
  44. data/lib/scout_apm/instruments/grape.rb +9 -12
  45. data/lib/scout_apm/instruments/http_client.rb +7 -14
  46. data/lib/scout_apm/instruments/influxdb.rb +6 -10
  47. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -15
  48. data/lib/scout_apm/instruments/middleware_summary.rb +5 -11
  49. data/lib/scout_apm/instruments/mongoid.rb +8 -39
  50. data/lib/scout_apm/instruments/moped.rb +6 -11
  51. data/lib/scout_apm/instruments/net_http.rb +9 -27
  52. data/lib/scout_apm/instruments/percentile_sampler.rb +23 -42
  53. data/lib/scout_apm/instruments/process/process_cpu.rb +6 -11
  54. data/lib/scout_apm/instruments/process/process_memory.rb +12 -17
  55. data/lib/scout_apm/instruments/rails_router.rb +6 -12
  56. data/lib/scout_apm/instruments/redis.rb +6 -10
  57. data/lib/scout_apm/instruments/sinatra.rb +4 -5
  58. data/lib/scout_apm/job_record.rb +2 -4
  59. data/lib/scout_apm/layaway.rb +34 -88
  60. data/lib/scout_apm/layaway_file.rb +3 -13
  61. data/lib/scout_apm/layer.rb +60 -25
  62. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +6 -7
  63. data/lib/scout_apm/layer_converters/converter_base.rb +14 -203
  64. data/lib/scout_apm/layer_converters/depth_first_walker.rb +10 -22
  65. data/lib/scout_apm/layer_converters/error_converter.rb +8 -8
  66. data/lib/scout_apm/layer_converters/job_converter.rb +50 -37
  67. data/lib/scout_apm/layer_converters/metric_converter.rb +19 -18
  68. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +13 -13
  69. data/lib/scout_apm/layer_converters/slow_job_converter.rb +116 -52
  70. data/lib/scout_apm/layer_converters/slow_request_converter.rb +120 -51
  71. data/lib/scout_apm/metric_meta.rb +5 -0
  72. data/lib/scout_apm/metric_set.rb +1 -9
  73. data/lib/scout_apm/metric_stats.rb +8 -7
  74. data/lib/scout_apm/middleware.rb +9 -7
  75. data/lib/scout_apm/reporter.rb +24 -71
  76. data/lib/scout_apm/request_histograms.rb +0 -12
  77. data/lib/scout_apm/request_manager.rb +7 -5
  78. data/lib/scout_apm/scored_item_set.rb +0 -7
  79. data/lib/scout_apm/serializers/app_server_load_serializer.rb +0 -4
  80. data/lib/scout_apm/serializers/deploy_serializer.rb +16 -0
  81. data/lib/scout_apm/serializers/directive_serializer.rb +0 -4
  82. data/lib/scout_apm/serializers/payload_serializer.rb +4 -11
  83. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +16 -35
  84. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +1 -2
  85. data/lib/scout_apm/server_integrations/passenger.rb +1 -1
  86. data/lib/scout_apm/server_integrations/puma.rb +2 -5
  87. data/lib/scout_apm/slow_job_policy.rb +13 -25
  88. data/lib/scout_apm/slow_job_record.rb +4 -13
  89. data/lib/scout_apm/slow_request_policy.rb +13 -25
  90. data/lib/scout_apm/slow_transaction.rb +5 -25
  91. data/lib/scout_apm/store.rb +32 -99
  92. data/lib/scout_apm/trace_compactor.rb +312 -0
  93. data/lib/scout_apm/tracer.rb +31 -35
  94. data/lib/scout_apm/tracked_request.rb +95 -262
  95. data/lib/scout_apm/utils/active_record_metric_name.rb +13 -88
  96. data/lib/scout_apm/utils/backtrace_parser.rb +4 -7
  97. data/lib/scout_apm/utils/fake_stacks.rb +87 -0
  98. data/lib/scout_apm/utils/installed_gems.rb +3 -7
  99. data/lib/scout_apm/utils/klass_helper.rb +2 -8
  100. data/lib/scout_apm/utils/null_logger.rb +13 -0
  101. data/lib/scout_apm/utils/sql_sanitizer.rb +5 -16
  102. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -7
  103. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -6
  104. data/lib/scout_apm/utils/unique_id.rb +0 -27
  105. data/lib/scout_apm/version.rb +2 -1
  106. data/lib/scout_apm.rb +25 -84
  107. data/scout_apm.gemspec +3 -17
  108. data/test/test_helper.rb +3 -57
  109. data/test/unit/agent_test.rb +54 -1
  110. data/test/unit/background_job_integrations/sidekiq_test.rb +3 -0
  111. data/test/unit/config_test.rb +12 -25
  112. data/test/unit/context_test.rb +4 -4
  113. data/test/unit/histogram_test.rb +4 -25
  114. data/test/unit/ignored_uris_test.rb +1 -1
  115. data/test/unit/instruments/active_record_instruments_test.rb +5 -0
  116. data/test/unit/layaway_test.rb +2 -62
  117. data/test/unit/serializers/payload_serializer_test.rb +15 -43
  118. data/test/unit/slow_request_policy_test.rb +6 -15
  119. data/test/unit/sql_sanitizer_test.rb +6 -53
  120. data/test/unit/store_test.rb +4 -73
  121. data/test/unit/utils/active_record_metric_name_test.rb +5 -59
  122. data/test/unit/utils/backtrace_parser_test.rb +1 -6
  123. data/tester.rb +53 -0
  124. metadata +28 -229
  125. data/.travis.yml +0 -26
  126. data/Guardfile +0 -43
  127. data/gems/README.md +0 -28
  128. data/gems/octoshark.gemfile +0 -4
  129. data/gems/rails3.gemfile +0 -5
  130. data/gems/rails4.gemfile +0 -4
  131. data/gems/rails5.gemfile +0 -4
  132. data/gems/rails6.gemfile +0 -4
  133. data/lib/scout_apm/agent/exit_handler.rb +0 -65
  134. data/lib/scout_apm/agent/preconditions.rb +0 -81
  135. data/lib/scout_apm/agent_context.rb +0 -261
  136. data/lib/scout_apm/auto_instrument/instruction_sequence.rb +0 -31
  137. data/lib/scout_apm/auto_instrument/layer.rb +0 -23
  138. data/lib/scout_apm/auto_instrument/parser.rb +0 -27
  139. data/lib/scout_apm/auto_instrument/rails.rb +0 -175
  140. data/lib/scout_apm/auto_instrument.rb +0 -5
  141. data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +0 -55
  142. data/lib/scout_apm/background_job_integrations/que.rb +0 -134
  143. data/lib/scout_apm/background_job_integrations/resque.rb +0 -88
  144. data/lib/scout_apm/background_job_integrations/shoryuken.rb +0 -124
  145. data/lib/scout_apm/background_job_integrations/sneakers.rb +0 -87
  146. data/lib/scout_apm/background_recorder.rb +0 -48
  147. data/lib/scout_apm/db_query_metric_set.rb +0 -97
  148. data/lib/scout_apm/db_query_metric_stats.rb +0 -102
  149. data/lib/scout_apm/debug.rb +0 -37
  150. data/lib/scout_apm/detailed_trace.rb +0 -217
  151. data/lib/scout_apm/error.rb +0 -27
  152. data/lib/scout_apm/error_service/error_buffer.rb +0 -39
  153. data/lib/scout_apm/error_service/error_record.rb +0 -211
  154. data/lib/scout_apm/error_service/ignored_exceptions.rb +0 -66
  155. data/lib/scout_apm/error_service/middleware.rb +0 -32
  156. data/lib/scout_apm/error_service/notifier.rb +0 -33
  157. data/lib/scout_apm/error_service/payload.rb +0 -47
  158. data/lib/scout_apm/error_service/periodic_work.rb +0 -17
  159. data/lib/scout_apm/error_service/railtie.rb +0 -11
  160. data/lib/scout_apm/error_service/sidekiq.rb +0 -80
  161. data/lib/scout_apm/error_service.rb +0 -32
  162. data/lib/scout_apm/extensions/config.rb +0 -87
  163. data/lib/scout_apm/extensions/transaction_callback_payload.rb +0 -74
  164. data/lib/scout_apm/git_revision.rb +0 -59
  165. data/lib/scout_apm/instrument_manager.rb +0 -88
  166. data/lib/scout_apm/instruments/action_view.rb +0 -141
  167. data/lib/scout_apm/instruments/http.rb +0 -48
  168. data/lib/scout_apm/instruments/memcached.rb +0 -43
  169. data/lib/scout_apm/instruments/resque.rb +0 -39
  170. data/lib/scout_apm/instruments/samplers.rb +0 -11
  171. data/lib/scout_apm/layer_children_set.rb +0 -86
  172. data/lib/scout_apm/layer_converters/database_converter.rb +0 -70
  173. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +0 -38
  174. data/lib/scout_apm/layer_converters/histograms.rb +0 -15
  175. data/lib/scout_apm/layer_converters/trace_converter.rb +0 -184
  176. data/lib/scout_apm/limited_layer.rb +0 -126
  177. data/lib/scout_apm/logger.rb +0 -158
  178. data/lib/scout_apm/periodic_work.rb +0 -47
  179. data/lib/scout_apm/rack.rb +0 -26
  180. data/lib/scout_apm/remote/message.rb +0 -27
  181. data/lib/scout_apm/remote/recorder.rb +0 -57
  182. data/lib/scout_apm/remote/router.rb +0 -49
  183. data/lib/scout_apm/remote/server.rb +0 -60
  184. data/lib/scout_apm/reporting.rb +0 -143
  185. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +0 -15
  186. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +0 -21
  187. data/lib/scout_apm/synchronous_recorder.rb +0 -30
  188. data/lib/scout_apm/tasks/doctor.rb +0 -75
  189. data/lib/scout_apm/tasks/support.rb +0 -22
  190. data/lib/scout_apm/transaction.rb +0 -13
  191. data/lib/scout_apm/transaction_time_consumed.rb +0 -51
  192. data/lib/scout_apm/utils/gzip_helper.rb +0 -24
  193. data/lib/scout_apm/utils/marshal_logging.rb +0 -90
  194. data/lib/scout_apm/utils/numbers.rb +0 -14
  195. data/lib/scout_apm/utils/scm.rb +0 -14
  196. data/lib/tasks/doctor.rake +0 -11
  197. data/test/tmp/README.md +0 -17
  198. data/test/unit/agent_context_test.rb +0 -15
  199. data/test/unit/auto_instrument/assignments-instrumented.rb +0 -31
  200. data/test/unit/auto_instrument/assignments.rb +0 -31
  201. data/test/unit/auto_instrument/controller-ast.txt +0 -57
  202. data/test/unit/auto_instrument/controller-instrumented.rb +0 -49
  203. data/test/unit/auto_instrument/controller.rb +0 -49
  204. data/test/unit/auto_instrument/rescue_from-instrumented.rb +0 -13
  205. data/test/unit/auto_instrument/rescue_from.rb +0 -13
  206. data/test/unit/auto_instrument_test.rb +0 -54
  207. data/test/unit/db_query_metric_set_test.rb +0 -67
  208. data/test/unit/db_query_metric_stats_test.rb +0 -113
  209. data/test/unit/error_service/error_buffer_test.rb +0 -25
  210. data/test/unit/error_service/ignored_exceptions_test.rb +0 -49
  211. data/test/unit/extensions/periodic_callbacks_test.rb +0 -58
  212. data/test/unit/extensions/transaction_callbacks_test.rb +0 -58
  213. data/test/unit/fake_store_test.rb +0 -10
  214. data/test/unit/git_revision_test.rb +0 -15
  215. data/test/unit/instruments/active_record_test.rb +0 -40
  216. data/test/unit/instruments/net_http_test.rb +0 -27
  217. data/test/unit/instruments/percentile_sampler_test.rb +0 -133
  218. data/test/unit/layer_children_set_test.rb +0 -97
  219. data/test/unit/layer_converters/depth_first_walker_test.rb +0 -70
  220. data/test/unit/layer_converters/metric_converter_test.rb +0 -22
  221. data/test/unit/layer_converters/stubs.rb +0 -33
  222. data/test/unit/limited_layer_test.rb +0 -53
  223. data/test/unit/logger_test.rb +0 -69
  224. data/test/unit/remote/test_message.rb +0 -13
  225. data/test/unit/remote/test_router.rb +0 -33
  226. data/test/unit/remote/test_server.rb +0 -15
  227. data/test/unit/request_histograms_test.rb +0 -17
  228. data/test/unit/tracer_test.rb +0 -76
  229. data/test/unit/tracked_request_test.rb +0 -71
  230. data/test/unit/transaction_test.rb +0 -14
  231. data/test/unit/transaction_time_consumed_test.rb +0 -46
  232. data/test/unit/utils/numbers_test.rb +0 -15
  233. data/test/unit/utils/scm.rb +0 -17
@@ -26,6 +26,10 @@ module ScoutApm
26
26
  # Can be nil if we never reach a Rails Controller
27
27
  attr_reader :headers
28
28
 
29
+ # What kind of request is this? A trace of a web request, or a background job?
30
+ # Use job! and web! to set, and job? and web? to query
31
+ attr_reader :request_type
32
+
29
33
  # This maintains a lookup hash of Layer names and call counts. It's used to trigger fetching a backtrace on n+1 calls.
30
34
  # Note that layer names might not be Strings - can alse be Utils::ActiveRecordMetricName. Also, this would fail for layers
31
35
  # with same names across multiple types.
@@ -35,70 +39,38 @@ module ScoutApm
35
39
  # this is set in the controller instumentation (ActionControllerRails3Rails4 according)
36
40
  attr_accessor :instant_key
37
41
 
38
- # An object that responds to `record!(TrackedRequest)` to store this tracked request
39
- attr_reader :recorder
40
-
41
- # If specified, an override for the name of the request. If unspecified,
42
- # the name is determined from the name of the Controller or Job layer.
43
- attr_accessor :name_override
44
-
45
- # A unique, but otherwise meaningless String to identify this request. UUID
46
- attr_reader :transaction_id
47
-
48
- # When we see these layers, it means a real request is going through the
49
- # system. We toggle a flag to turn on some slightly more expensive
50
- # instrumentation (backtrace collection and the like) that would be too
51
- # expensive in situations where the framework is constantly churning. We
52
- # see that on Sidekiq.
53
- REQUEST_TYPES = ["Controller", "Job"]
42
+ # Whereas the instant_key gets set per-request in reponse to a URL param, dev_trace is set in the config file
43
+ attr_accessor :dev_trace
54
44
 
55
- # Layers of type 'AutoInstrument' are not recorded if their total_call_time doesn't exceed this threshold.
56
- # AutoInstrument layers are frequently of short duration. This throws out this deadweight that is unlikely to be optimized.
57
- AUTO_INSTRUMENT_TIMING_THRESHOLD = 5/1_000.0 # units = seconds
58
-
59
- def initialize(agent_context, store)
60
- @agent_context = agent_context
45
+ def initialize(store)
61
46
  @store = store #this is passed in so we can use a real store (normal operation) or fake store (instant mode only)
62
47
  @layers = []
63
48
  @call_set = Hash.new { |h, k| h[k] = CallSet.new }
64
49
  @annotations = {}
65
50
  @ignoring_children = 0
66
- @context = Context.new(agent_context)
51
+ @context = Context.new
67
52
  @root_layer = nil
68
53
  @error = false
69
- @stopping = false
70
54
  @instant_key = nil
71
55
  @mem_start = mem_usage
72
- @recorder = agent_context.recorder
73
- @real_request = false
74
- @transaction_id = ScoutApm::Utils::TransactionId.new.to_s
75
- ignore_request! if @recorder.nil?
56
+ @dev_trace = ScoutApm::Agent.instance.config.value('dev_trace') && Rails.env.development?
76
57
  end
77
58
 
78
59
  def start_layer(layer)
79
- # If we're already stopping, don't do additional layers
80
- return if stopping?
81
-
82
- return if ignoring_children?
60
+ if ignoring_children?
61
+ return
62
+ end
83
63
 
84
- return ignoring_start_layer if ignoring_request?
64
+ layer.start_sampling
85
65
 
86
66
  start_request(layer) unless @root_layer
87
-
88
- if REQUEST_TYPES.include?(layer.type)
89
- real_request!
90
- end
67
+ @layers[-1].add_child(layer) if @layers.any?
91
68
  @layers.push(layer)
92
69
  end
93
70
 
94
71
  def stop_layer
95
- # If we're already stopping, don't do additional layers
96
- return if stopping?
97
-
98
72
  return if ignoring_children?
99
73
 
100
- return ignoring_stop_layer if ignoring_request?
101
-
102
74
  layer = @layers.pop
103
75
 
104
76
  # Safeguard against a mismatch in the layer tracking in an instrument.
@@ -106,19 +78,15 @@ module ScoutApm
106
78
  # lined up correctly. If stop_layer gets called twice, when it should
107
79
  # only have been called once you'll end up with this error.
108
80
  if layer.nil?
109
- logger.warn("Error stopping layer, was nil. Root Layer: #{@root_layer.inspect}")
81
+ ScoutApm::Agent.instance.logger.warn("Error stopping layer, was nil. Root Layer: #{@root_layer.inspect}")
110
82
  stop_request
111
83
  return
112
84
  end
113
85
 
86
+ layer.record_traces!
114
87
  layer.record_stop_time!
115
88
  layer.record_allocations!
116
89
 
117
- # Must follow layer.record_stop_time! as the total_call_time is used to determine if the layer is significant.
118
- return if layer_insignificant?(layer)
119
-
120
- @layers[-1].add_child(layer) if @layers.any?
121
-
122
90
  # This must be called before checking if a backtrace should be collected as the call count influences our capture logic.
123
91
  # We call `#update_call_counts in stop layer to ensure the layer has a final desc. Layer#desc is updated during the AR instrumentation flow.
124
92
  update_call_counts!(layer)
@@ -126,21 +94,13 @@ module ScoutApm
126
94
  layer.capture_backtrace!
127
95
  end
128
96
 
129
-
130
97
  if finalized?
131
98
  stop_request
99
+ else
100
+ continue_sampling_for_layers if ScoutApm::Agent.instance.config.value('profile')
132
101
  end
133
102
  end
134
103
 
135
- def real_request!
136
- @real_request = true
137
- end
138
-
139
- # Have we seen a "controller" or "job" layer so far?
140
- def real_request?
141
- @real_request
142
- end
143
-
144
104
  # Grab the currently running layer. Useful for adding additional data as we
145
105
  # learn it. This is useful in ActiveRecord instruments, where we start the
146
106
  # instrumentation early, and gradually learn more about the request that
@@ -155,12 +115,6 @@ module ScoutApm
155
115
 
156
116
  BACKTRACE_BLACKLIST = ["Controller", "Job"]
157
117
  def capture_backtrace?(layer)
158
- return if ignoring_request?
159
-
160
- # A backtrace has already been recorded. This happens with autoinstruments as
161
- # the partial backtrace is set when creating the layer.
162
- return false if layer.backtrace
163
-
164
118
  # Never capture backtraces for this kind of layer. The backtrace will
165
119
  # always be 100% framework code.
166
120
  return false if BACKTRACE_BLACKLIST.include?(layer.type)
@@ -168,7 +122,7 @@ module ScoutApm
168
122
  # Only capture backtraces if we're in a real "request". Otherwise we
169
123
  # can spend lot of time capturing backtraces from the internals of
170
124
  # Sidekiq, only to throw them away immediately.
171
- return false unless real_request?
125
+ return false unless (web? || job?)
172
126
 
173
127
  # Capture any individually slow layer.
174
128
  return true if layer.total_exclusive_time > backtrace_threshold
@@ -180,34 +134,14 @@ module ScoutApm
180
134
  false
181
135
  end
182
136
 
183
- # Returns +true+ if the total call time of AutoInstrument layers exceeds +AUTO_INSTRUMENT_TIMING_THRESHOLD+ and
184
- # records a Histogram of insignificant / significant layers by file name.
185
- def layer_insignificant?(layer)
186
- result = false # default is significant
187
- if layer.type == 'AutoInstrument'
188
- if layer.total_call_time < AUTO_INSTRUMENT_TIMING_THRESHOLD
189
- result = true # not significant
190
- end
191
- # 0 = not significant, 1 = significant
192
- @agent_context.auto_instruments_layer_histograms.add(layer.file_name, (result ? 0 : 1))
193
- end
194
- result
195
- end
196
-
197
137
  # Maintains a lookup Hash of call counts by layer name. Used to determine if we should capture a backtrace.
198
138
  def update_call_counts!(layer)
199
139
  @call_set[layer.name].update!(layer.desc)
200
140
  end
201
141
 
202
- # Grab backtraces more aggressively when running in dev trace mode
203
- def backtrace_threshold
204
- @agent_context.dev_trace_enabled? ? 0.05 : 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
205
- end
206
-
207
142
  # This may be in bytes or KB based on the OSX. We store this as-is here and only do conversion to MB in Layer Converters.
208
- # XXX: Move this to environment?
209
143
  def mem_usage
210
- ScoutApm::Instruments::Process::ProcessMemory.new(@agent_context).rss
144
+ ScoutApm::Instruments::Process::ProcessMemory.rss
211
145
  end
212
146
 
213
147
  def capture_mem_delta!
@@ -224,6 +158,13 @@ module ScoutApm
224
158
  @layers.none?
225
159
  end
226
160
 
161
+ def continue_sampling_for_layers
162
+ if last_traced_layer = @layers.select{|layer| layer.traced?}.last
163
+ ScoutApm::Instruments::Stacks.update_indexes(@layers.last.frame_index, @layers.last.trace_index)
164
+ ScoutApm::Instruments::Stacks.start_sampling
165
+ end
166
+ end
167
+
227
168
  # Run at the beginning of the whole request
228
169
  #
229
170
  # * Capture the first layer as the root_layer
@@ -235,15 +176,21 @@ module ScoutApm
235
176
  #
236
177
  # * Send the request off to be stored
237
178
  def stop_request
238
- @stopping = true
239
-
240
- if @recorder
241
- @recorder.record!(self)
179
+ if ScoutApm::Agent.instance.config.value('profile')
180
+ ScoutApm::Instruments::Stacks.stop_sampling(true)
181
+ ScoutApm::Instruments::Stacks.update_indexes(0, 0)
242
182
  end
183
+ record!
243
184
  end
244
185
 
245
- def stopping?
246
- @stopping
186
+ # Enable ScoutProf for this thread
187
+ def enable_profiled_thread!
188
+ ScoutApm::Instruments::Stacks.add_profiled_thread
189
+ end
190
+
191
+ # Disable ScoutProf for this thread
192
+ def disable_profiled_thread!
193
+ ScoutApm::Instruments::Stacks.remove_profiled_thread
247
194
  end
248
195
 
249
196
  ###################################
@@ -272,9 +219,23 @@ module ScoutApm
272
219
  @headers = headers
273
220
  end
274
221
 
275
- def instant?
276
- return false if ignoring_request?
222
+ def job!
223
+ @request_type = "job"
224
+ end
225
+
226
+ def job?
227
+ request_type == "job"
228
+ end
277
229
 
230
+ def web!
231
+ @request_type = "web"
232
+ end
233
+
234
+ def web?
235
+ request_type == "web"
236
+ end
237
+
238
+ def instant?
278
239
  instant_key
279
240
  end
280
241
 
@@ -282,109 +243,62 @@ module ScoutApm
282
243
  # Persist the Request
283
244
  ###################################
284
245
 
285
- def recorded!
286
- @recorded = true
287
- end
288
-
289
246
  # Convert this request to the appropriate structure, then report it into
290
247
  # the peristent Store object
291
248
  def record!
292
- recorded!
293
-
294
- return if ignoring_request?
295
-
296
- # If we didn't have store, but we're trying to record anyway, go
297
- # figure that out. (this happens in Remote Agent scenarios)
298
- restore_from_dump! if @agent_context.nil?
249
+ @recorded = true
299
250
 
300
251
  # Bail out early if the user asked us to ignore this uri
301
- return if @agent_context.ignored_uris.ignore?(annotations[:uri])
302
-
303
- apply_name_override
304
-
305
- @agent_context.transaction_time_consumed.add(unique_name, root_layer.total_call_time)
306
-
307
- context.add(:transaction_id => transaction_id)
308
-
309
- # Make a constant, then call converters.dup.each so it isn't inline?
310
- converters = {
311
- :histograms => LayerConverters::Histograms,
312
- :metrics => LayerConverters::MetricConverter,
313
- :errors => LayerConverters::ErrorConverter,
314
- :allocation_metrics => LayerConverters::AllocationMetricConverter,
315
- :queue_time => LayerConverters::RequestQueueTimeConverter,
316
- :job => LayerConverters::JobConverter,
317
- :db => LayerConverters::DatabaseConverter,
318
-
319
- :slow_job => LayerConverters::SlowJobConverter,
320
- :slow_req => LayerConverters::SlowRequestConverter,
321
-
322
- # This is now integrated into the slow_job and slow_req converters, so that
323
- # we get the exact same set of traces either way. We can call it
324
- # directly when we move away from the legacy trace styles.
325
- # :traces => LayerConverters::TraceConverter,
326
- }
327
-
328
- walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
329
- converter_instances = converters.inject({}) do |memo, (slug, klass)|
330
- instance = klass.new(@agent_context, self, layer_finder, @store)
331
- instance.register_hooks(walker)
332
- memo[slug] = instance
333
- memo
334
- end
335
- walker.walk
336
- converter_results = converter_instances.inject({}) do |memo, (slug,i)|
337
- memo[slug] = i.record!
338
- memo
252
+ return if ScoutApm::Agent.instance.ignored_uris.ignore?(annotations[:uri])
253
+
254
+ # Update immediate and long-term histograms for both job and web requests
255
+ if unique_name != :unknown
256
+ ScoutApm::Agent.instance.request_histograms.add(unique_name, root_layer.total_call_time)
257
+ ScoutApm::Agent.instance.request_histograms_by_time[@store.current_timestamp].
258
+ add(unique_name, root_layer.total_call_time)
339
259
  end
340
260
 
341
- @agent_context.extensions.run_transaction_callbacks(converter_results,context,layer_finder.scope)
261
+ metrics = LayerConverters::MetricConverter.new(self).call
262
+ @store.track!(metrics)
342
263
 
343
- # If there's an instant_key, it means we need to report this right away
344
- if web? && instant?
345
- converter = converters.find{|c| c.class == LayerConverters::SlowRequestConverter}
346
- trace = converter.call
347
- ScoutApm::InstantReporting.new(trace, instant_key).call
348
- end
264
+ error_metrics = LayerConverters::ErrorConverter.new(self).call
265
+ @store.track!(error_metrics)
349
266
 
350
- if web? || job?
351
- ensure_background_worker
352
- end
353
- end
267
+ allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
268
+ @store.track!(allocation_metrics)
354
269
 
355
- # This request is a job transaction iff it has a 'Job' layer
356
- # Use this only during recording
357
- def job?
358
- layer_finder.job != nil
359
- end
270
+ if web?
271
+ # Don't #call this - that's the job of the ScoredItemSet later.
272
+ slow_converter = LayerConverters::SlowRequestConverter.new(self)
273
+ @store.track_slow_transaction!(slow_converter)
360
274
 
361
- # This request is a web transaction iff it has a 'Controller' layer
362
- # Use this only during recording
363
- def web?
364
- layer_finder.controller != nil
365
- end
275
+ queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
276
+ @store.track!(queue_time_metrics)
277
+
278
+ # If there's an instant_key, it means we need to report this right away
279
+ if instant?
280
+ trace = slow_converter.call
281
+ ScoutApm::InstantReporting.new(trace, instant_key).call()
282
+ end
283
+ end
366
284
 
285
+ if job?
286
+ job_metrics = LayerConverters::JobConverter.new(self).call
287
+ @store.track_job!(job_metrics)
367
288
 
368
- def layer_finder
369
- @layer_finder ||= LayerConverters::FindLayerByType.new(self)
370
- end
289
+ job_converter = LayerConverters::SlowJobConverter.new(self)
290
+ @store.track_slow_job!(job_converter)
291
+ end
371
292
 
372
- # Ensure the background worker thread is up & running - a fallback if other
373
- # detection doesn't achieve this at boot.
374
- def ensure_background_worker
375
- agent = ScoutApm::Agent.instance
376
- agent.start
377
- rescue => e
378
- true
379
- end
293
+ allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
294
+ @store.track!(allocation_metrics)
380
295
 
296
+ end
381
297
 
382
298
  # Only call this after the request is complete
383
299
  def unique_name
384
- return nil if ignoring_request?
385
-
386
300
  @unique_name ||= begin
387
- scope_layer = LayerConverters::FindLayerByType.new(self).scope
301
+ scope_layer = LayerConverters::ConverterBase.new(self).scope_layer
388
302
  if scope_layer
389
303
  scope_layer.legacy_metric_name
390
304
  else
@@ -397,8 +311,6 @@ module ScoutApm
397
311
  # Used to know when we should just create a new one (don't attempt to add
398
312
  # data to an already-recorded request). See RequestManager
399
313
  def recorded?
400
- return ignoring_recorded? if ignoring_request?
401
-
402
314
  @recorded
403
315
  end
404
316
 
@@ -443,88 +355,9 @@ module ScoutApm
443
355
  @ignoring_children > 0
444
356
  end
445
357
 
446
- ################################################################################
447
- # Ignoring the rest of a request
448
- ################################################################################
449
-
450
- # At any point in the request, calling code or instrumentation can call
451
- # `ignore_request!` to immediately stop recording any information about new
452
- # layers, and delete any existing layer info. This class will still exist,
453
- # and respond to methods as normal, but `record!` won't be called, and no
454
- # data will be recorded.
455
- #
456
- # We still need to keep track of the current layer depth (via
457
- # @ignoring_depth counter) so we know when to report that the class was
458
- # "reported", and ready to be recreated for the next request.
459
-
460
- def ignore_request!
461
- return if @ignoring_request
462
-
463
- # Set instance variable
464
- @ignoring_request = true
465
-
466
- # Store data we'll need
467
- @ignoring_depth = @layers.length
468
-
469
- # Clear data
470
- @layers = []
471
- @root_layer = nil
472
- @call_set = nil
473
- @annotations = {}
474
- @instant_key = nil
475
- end
476
-
477
- def ignoring_request?
478
- @ignoring_request
479
- end
480
-
481
- def ignoring_start_layer
482
- @ignoring_depth += 1
483
- end
484
-
485
- def ignoring_stop_layer
486
- @ignoring_depth -= 1
487
- end
488
-
489
- def ignoring_recorded?
490
- @ignoring_depth <= 0
491
- end
492
-
493
- def logger
494
- @agent_context.logger
495
- end
496
-
497
- ###########################
498
- # Serialization Helpers
499
- ###########################
500
-
501
- # Actually go fetch & make-real any lazily created data.
502
- # Clean up any cleverness in objects.
503
- # Makes this object ready to be Marshal Dumped (or otherwise serialized)
504
- def prepare_to_dump!
505
- @call_set = nil
506
- @store = nil
507
- @recorder = nil
508
- @agent_context = nil
509
- end
510
-
511
- # Go re-fetch the store based on what the Agent's official one is. Used
512
- # after hydrating a dumped TrackedRequest
513
- def restore_from_dump!
514
- @agent_context = ScoutApm::Agent.instance.context
515
- @recorder = @agent_context.recorder
516
- @store = @agent_context.store
517
- end
518
-
519
- private
520
-
521
- def apply_name_override
522
- return unless name_override
523
-
524
- scope_layer = layer_finder.scope
525
- if scope_layer
526
- scope_layer.name = name_override
527
- end
358
+ # Grab backtraces more aggressively when running in dev trace mode
359
+ def backtrace_threshold
360
+ dev_trace ? 0.05 : 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
528
361
  end
529
362
  end
530
363
  end
@@ -1,42 +1,32 @@
1
1
  module ScoutApm
2
2
  module Utils
3
3
  class ActiveRecordMetricName
4
+ DEFAULT_METRIC = "SQL/Unknown"
5
+
4
6
  attr_reader :sql, :name
5
- DEFAULT_METRIC = 'SQL/other'.freeze
6
7
 
7
8
  def initialize(sql, name)
8
- @sql = sql || ""
9
+ @sql = sql
9
10
  @name = name.to_s
10
11
  end
11
12
 
12
13
  # Converts an SQL string and the name (typically assigned automatically
13
14
  # by rails) into a Scout metric_name.
14
15
  #
15
- # This prefers to use the ActiveRecord-provided name over parsing SQL as parsing is slower.
16
- #
17
16
  # sql: SELECT "places".* FROM "places" ORDER BY "places"."position" ASC
18
17
  # name: Place Load
19
18
  # metric_name: Place/find
20
19
  def to_s
21
- return @to_s if @to_s
22
- parsed = parse_operation
23
- if parsed
24
- @to_s = "#{model}/#{parsed}"
20
+ return DEFAULT_METRIC unless name
21
+ return DEFAULT_METRIC unless model && operation
22
+
23
+ if parsed = parse_operation
24
+ "#{model}/#{parsed}"
25
25
  else
26
- @to_s = regex_name(sql)
26
+ "SQL/other"
27
27
  end
28
28
  end
29
29
 
30
- # This only returns a value if a name is provided via +initialize+.
31
- def model
32
- parts.first
33
- end
34
-
35
- # This only returns a value if a name is provided via +initialize+.
36
- def normalized_operation
37
- parse_operation
38
- end
39
-
40
30
  # For the layer lookup.
41
31
  def hash
42
32
  h = name.downcase.hash
@@ -54,14 +44,16 @@ module ScoutApm
54
44
 
55
45
  private
56
46
 
57
- # This only returns a value if a name is provided via +initialize+.
47
+ def model
48
+ parts.first
49
+ end
50
+
58
51
  def operation
59
52
  if parts.length >= 2
60
53
  parts[1].downcase
61
54
  end
62
55
  end
63
56
 
64
- # This only returns a value if a name is provided via +initialize+.
65
57
  def parts
66
58
  name.split(" ")
67
59
  end
@@ -80,73 +72,6 @@ module ScoutApm
80
72
  end
81
73
  end
82
74
  end
83
-
84
-
85
- ########################
86
- # Regex based naming #
87
- ########################
88
- #
89
- WHITE_SPACE = '\s*'
90
- REGEX_OPERATION = '(SELECT|UPDATE|INSERT|DELETE)'
91
- FROM = 'FROM'
92
- INTO = 'INTO'
93
- NON_GREEDY_CONSUME = '.*?'
94
- TABLE = '(?:"|`)?(.*?)(?:"|`)?\s'
95
- COUNT = 'COUNT\(.*?\)'
96
- BEGIN_STATEMENT = 'BEGIN'.freeze # BEGIN is a reserved keyword
97
- COMMIT = 'COMMIT'.freeze
98
-
99
- SELECT_REGEX = /\A#{WHITE_SPACE}(SELECT)#{WHITE_SPACE}(#{COUNT})?#{NON_GREEDY_CONSUME}#{FROM}#{WHITE_SPACE}#{TABLE}/i.freeze
100
- UPDATE_REGEX = /\A#{WHITE_SPACE}(UPDATE)#{WHITE_SPACE}#{TABLE}/i.freeze
101
- INSERT_REGEX = /\A#{WHITE_SPACE}(INSERT)#{WHITE_SPACE}#{INTO}#{WHITE_SPACE}#{TABLE}/i.freeze
102
- DELETE_REGEX = /\A#{WHITE_SPACE}(DELETE)#{WHITE_SPACE}#{FROM}#{TABLE}/i.freeze
103
-
104
- COUNT_LABEL = 'count'.freeze
105
- SELECT_LABEL = 'find'.freeze
106
- UPDATE_LABEL = 'save'.freeze
107
- INSERT_LABEL = 'create'.freeze
108
- DELETE_LABEL = 'destroy'.freeze
109
- UNKNOWN_LABEL = 'SQL/other'.freeze
110
-
111
- # Attempt to do some basic parsing of SQL via regexes to extract the SQL
112
- # verb (select, update, etc) and the table being operated on.
113
- #
114
- # This is a fallback from what ActiveRecord gives us, we prefer its
115
- # names. But sometimes it is giving us a no-name query, and we have to
116
- # attempt to figure it out ourselves.
117
- #
118
- # This relies on ActiveSupport's classify method. If it's not present,
119
- # just skip the attempt to rename here. This could happen in a Grape or
120
- # Sinatra application that doesn't import ActiveSupport. At this point,
121
- # you're already using ActiveRecord, so it's likely loaded anyway.
122
- def regex_name(sql)
123
- # We rely on the ActiveSupport inflections code here. Bail early if we can't use it.
124
- return UNKNOWN_LABEL unless UNKNOWN_LABEL.respond_to?(:classify)
125
-
126
- if match = SELECT_REGEX.match(sql)
127
- operation =
128
- if match[2]
129
- COUNT_LABEL
130
- else
131
- SELECT_LABEL
132
- end
133
- "#{match[3].gsub(/\W/,'').classify}/#{operation}"
134
- elsif match = UPDATE_REGEX.match(sql)
135
- "#{match[2].classify}/#{UPDATE_LABEL}"
136
- elsif match = INSERT_REGEX.match(sql)
137
- "#{match[2].classify}/#{INSERT_LABEL}"
138
- elsif match = DELETE_REGEX.match(sql)
139
- "#{match[2].classify}/#{DELETE_LABEL}"
140
- elsif sql == BEGIN_STATEMENT
141
- "SQL/#{BEGIN_STATEMENT.downcase}"
142
- elsif sql == COMMIT
143
- "SQL/#{COMMIT.downcase}"
144
- else
145
- UNKNOWN_LABEL
146
- end
147
- rescue
148
- UNKNOWN_LABEL
149
- end
150
75
  end
151
76
  end
152
77
  end
@@ -6,15 +6,12 @@ require 'scout_apm/environment'
6
6
  module ScoutApm
7
7
  module Utils
8
8
  class BacktraceParser
9
- # will return this many backtrace frames from the app stack.
10
- APP_FRAMES = 8
9
+
10
+ APP_FRAMES = 3 # will return up to 3 frames from the app stack.
11
11
 
12
12
  attr_reader :call_stack
13
13
 
14
- # call_stack - an +Array+ of calls, typically generated via the +caller+ method.
15
- # Example single line:
16
- # "/Users/dlite/.rvm/rubies/ruby-2.4.5/lib/ruby/2.4.0/irb/workspace.rb:87:in `eval'"
17
- def initialize(call_stack, root=ScoutApm::Agent.instance.context.environment.root)
14
+ def initialize(call_stack, root=ScoutApm::Environment.instance.root)
18
15
  @call_stack = call_stack
19
16
  # We can't use a constant as it'd be too early to fetch environment info
20
17
  #
@@ -27,7 +24,7 @@ module ScoutApm
27
24
  stack = []
28
25
  call_stack.each do |c|
29
26
  if m = c.match(@@app_dir_regex)
30
- stack << ScoutApm::Utils::Scm.relative_scm_path(m[1])
27
+ stack << m[1]
31
28
  break if stack.size == APP_FRAMES
32
29
  end
33
30
  end