scout_apm 2.2.0.pre3 → 2.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.markdown +147 -2
  4. data/Guardfile +43 -0
  5. data/Rakefile +2 -2
  6. data/ext/allocations/allocations.c +6 -0
  7. data/ext/allocations/extconf.rb +1 -0
  8. data/ext/rusage/README.md +26 -0
  9. data/ext/rusage/extconf.rb +5 -0
  10. data/ext/rusage/rusage.c +52 -0
  11. data/lib/scout_apm.rb +28 -15
  12. data/lib/scout_apm/agent.rb +89 -37
  13. data/lib/scout_apm/agent/logging.rb +6 -1
  14. data/lib/scout_apm/agent/reporting.rb +9 -6
  15. data/lib/scout_apm/app_server_load.rb +21 -10
  16. data/lib/scout_apm/attribute_arranger.rb +6 -3
  17. data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
  18. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  19. data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
  20. data/lib/scout_apm/background_recorder.rb +43 -0
  21. data/lib/scout_apm/background_worker.rb +19 -15
  22. data/lib/scout_apm/config.rb +138 -28
  23. data/lib/scout_apm/db_query_metric_set.rb +80 -0
  24. data/lib/scout_apm/db_query_metric_stats.rb +102 -0
  25. data/lib/scout_apm/debug.rb +37 -0
  26. data/lib/scout_apm/environment.rb +22 -15
  27. data/lib/scout_apm/git_revision.rb +51 -0
  28. data/lib/scout_apm/histogram.rb +11 -2
  29. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  30. data/lib/scout_apm/instant/middleware.rb +196 -54
  31. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
  32. data/lib/scout_apm/instruments/action_view.rb +49 -0
  33. data/lib/scout_apm/instruments/active_record.rb +127 -3
  34. data/lib/scout_apm/instruments/grape.rb +4 -3
  35. data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
  36. data/lib/scout_apm/instruments/mongoid.rb +24 -3
  37. data/lib/scout_apm/instruments/net_http.rb +7 -2
  38. data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
  39. data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
  40. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  41. data/lib/scout_apm/instruments/resque.rb +40 -0
  42. data/lib/scout_apm/layaway.rb +67 -28
  43. data/lib/scout_apm/layer.rb +19 -59
  44. data/lib/scout_apm/layer_children_set.rb +77 -0
  45. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
  46. data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
  47. data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
  48. data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
  49. data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
  50. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
  51. data/lib/scout_apm/layer_converters/histograms.rb +14 -0
  52. data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
  53. data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
  54. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
  55. data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
  56. data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
  57. data/lib/scout_apm/limited_layer.rb +126 -0
  58. data/lib/scout_apm/metric_meta.rb +0 -5
  59. data/lib/scout_apm/metric_set.rb +9 -1
  60. data/lib/scout_apm/metric_stats.rb +7 -8
  61. data/lib/scout_apm/rack.rb +26 -0
  62. data/lib/scout_apm/remote/message.rb +23 -0
  63. data/lib/scout_apm/remote/recorder.rb +57 -0
  64. data/lib/scout_apm/remote/router.rb +49 -0
  65. data/lib/scout_apm/remote/server.rb +58 -0
  66. data/lib/scout_apm/reporter.rb +51 -15
  67. data/lib/scout_apm/request_histograms.rb +4 -0
  68. data/lib/scout_apm/request_manager.rb +2 -1
  69. data/lib/scout_apm/scored_item_set.rb +7 -0
  70. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
  71. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
  72. data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
  73. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
  74. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
  75. data/lib/scout_apm/server_integrations/puma.rb +5 -2
  76. data/lib/scout_apm/slow_job_policy.rb +1 -10
  77. data/lib/scout_apm/slow_job_record.rb +6 -1
  78. data/lib/scout_apm/slow_request_policy.rb +1 -10
  79. data/lib/scout_apm/slow_transaction.rb +20 -2
  80. data/lib/scout_apm/store.rb +66 -12
  81. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  82. data/lib/scout_apm/tracked_request.rb +136 -71
  83. data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
  84. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  85. data/lib/scout_apm/utils/gzip_helper.rb +24 -0
  86. data/lib/scout_apm/utils/numbers.rb +14 -0
  87. data/lib/scout_apm/utils/scm.rb +14 -0
  88. data/lib/scout_apm/version.rb +1 -1
  89. data/scout_apm.gemspec +5 -4
  90. data/test/test_helper.rb +18 -0
  91. data/test/unit/config_test.rb +59 -8
  92. data/test/unit/db_query_metric_set_test.rb +56 -0
  93. data/test/unit/db_query_metric_stats_test.rb +113 -0
  94. data/test/unit/git_revision_test.rb +15 -0
  95. data/test/unit/histogram_test.rb +14 -0
  96. data/test/unit/instruments/net_http_test.rb +21 -0
  97. data/test/unit/instruments/percentile_sampler_test.rb +137 -0
  98. data/test/unit/layaway_test.rb +20 -0
  99. data/test/unit/layer_children_set_test.rb +88 -0
  100. data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
  101. data/test/unit/layer_converters/metric_converter_test.rb +22 -0
  102. data/test/unit/layer_converters/stubs.rb +33 -0
  103. data/test/unit/limited_layer_test.rb +53 -0
  104. data/test/unit/remote/test_message.rb +13 -0
  105. data/test/unit/remote/test_router.rb +33 -0
  106. data/test/unit/remote/test_server.rb +15 -0
  107. data/test/unit/serializers/payload_serializer_test.rb +3 -12
  108. data/test/unit/store_test.rb +66 -0
  109. data/test/unit/test_tracked_request.rb +87 -0
  110. data/test/unit/utils/active_record_metric_name_test.rb +8 -0
  111. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  112. data/test/unit/utils/numbers_test.rb +15 -0
  113. data/test/unit/utils/scm.rb +17 -0
  114. metadata +125 -30
  115. data/ext/stacks/extconf.rb +0 -37
  116. data/ext/stacks/scout_atomics.h +0 -86
  117. data/ext/stacks/stacks.c +0 -811
  118. data/lib/scout_apm/capacity.rb +0 -57
  119. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
  120. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
  121. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
  122. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
  123. data/lib/scout_apm/instruments/delayed_job.rb +0 -57
  124. data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
  125. data/lib/scout_apm/trace_compactor.rb +0 -312
  126. data/lib/scout_apm/utils/fake_stacks.rb +0 -87
  127. data/tester.rb +0 -53
@@ -31,7 +31,7 @@ module ScoutApm
31
31
  end
32
32
 
33
33
  def stored!(request)
34
- last_seen[unique_name_for(request)] = Time.now
34
+ last_seen[request.unique_name] = Time.now
35
35
  end
36
36
 
37
37
  # Determine if this job trace should be fully analyzed by scoring it
@@ -61,15 +61,6 @@ module ScoutApm
61
61
 
62
62
  private
63
63
 
64
- def unique_name_for(request)
65
- scope_layer = LayerConverters::ConverterBase.new(request).scope_layer
66
- if scope_layer
67
- scope_layer.legacy_metric_name
68
- else
69
- :unknown
70
- end
71
- end
72
-
73
64
  # Time in seconds
74
65
  # Logarithm keeps huge times from swamping the other metrics.
75
66
  # 1+ is necessary to keep the log function in positive territory.
@@ -20,8 +20,10 @@ module ScoutApm
20
20
  attr_reader :hostname
21
21
  attr_reader :seconds_since_startup
22
22
  attr_reader :score
23
+ attr_reader :git_sha
24
+ attr_reader :truncated_metrics
23
25
 
24
- def initialize(queue_name, job_name, time, total_time, exclusive_time, context, metrics, allocation_metrics, mem_delta, allocations, score)
26
+ def initialize(queue_name, job_name, time, total_time, exclusive_time, context, metrics, allocation_metrics, mem_delta, allocations, score, truncated_metrics)
25
27
  @queue_name = queue_name
26
28
  @job_name = job_name
27
29
  @time = time
@@ -34,7 +36,10 @@ module ScoutApm
34
36
  @allocations = allocations
35
37
  @seconds_since_startup = (Time.now - ScoutApm::Agent.instance.process_start_time)
36
38
  @hostname = ScoutApm::Environment.instance.hostname
39
+ @git_sha = ScoutApm::Environment.instance.git_revision.sha
37
40
  @score = score
41
+ @truncated_metrics = truncated_metrics
42
+
38
43
  ScoutApm::Agent.instance.logger.debug { "Slow Job [#{metric_name}] - Call Time: #{total_call_time} Mem Delta: #{mem_delta}"}
39
44
  end
40
45
 
@@ -31,7 +31,7 @@ module ScoutApm
31
31
  end
32
32
 
33
33
  def stored!(request)
34
- last_seen[unique_name_for(request)] = Time.now
34
+ last_seen[request.unique_name] = Time.now
35
35
  end
36
36
 
37
37
  # Determine if this request trace should be fully analyzed by scoring it
@@ -61,15 +61,6 @@ module ScoutApm
61
61
 
62
62
  private
63
63
 
64
- def unique_name_for(request)
65
- scope_layer = LayerConverters::ConverterBase.new(request).scope_layer
66
- if scope_layer
67
- scope_layer.legacy_metric_name
68
- else
69
- :unknown
70
- end
71
- end
72
-
73
64
  # Time in seconds
74
65
  # Logarithm keeps huge times from swamping the other metrics.
75
66
  # 1+ is necessary to keep the log function in positive territory.
@@ -15,8 +15,11 @@ module ScoutApm
15
15
  attr_reader :allocations
16
16
  attr_accessor :hostname # hack - we need to reset these server side.
17
17
  attr_accessor :seconds_since_startup # hack - we need to reset these server side.
18
+ attr_accessor :git_sha # hack - we need to reset these server side.
18
19
 
19
- def initialize(uri, metric_name, total_call_time, metrics, allocation_metrics, context, time, raw_stackprof, mem_delta, allocations, score)
20
+ attr_reader :truncated_metrics # True/False that says if we had to truncate the metrics of this trace
21
+
22
+ def initialize(uri, metric_name, total_call_time, metrics, allocation_metrics, context, time, raw_stackprof, mem_delta, allocations, score, truncated_metrics)
20
23
  @uri = uri
21
24
  @metric_name = metric_name
22
25
  @total_call_time = total_call_time
@@ -30,6 +33,9 @@ module ScoutApm
30
33
  @seconds_since_startup = (Time.now - ScoutApm::Agent.instance.process_start_time)
31
34
  @hostname = ScoutApm::Environment.instance.hostname
32
35
  @score = score
36
+ @git_sha = ScoutApm::Environment.instance.git_revision.sha
37
+ @truncated_metrics = truncated_metrics
38
+
33
39
  ScoutApm::Agent.instance.logger.debug { "Slow Request [#{uri}] - Call Time: #{total_call_time} Mem Delta: #{mem_delta} Score: #{score}"}
34
40
  end
35
41
 
@@ -44,7 +50,19 @@ module ScoutApm
44
50
  end
45
51
 
46
52
  def as_json
47
- json_attributes = [:key, :time, :total_call_time, :uri, [:context, :context_hash], :score, :prof, :mem_delta, :allocations, :seconds_since_startup, :hostname]
53
+ json_attributes = [:key,
54
+ :time,
55
+ :total_call_time,
56
+ :uri,
57
+ [:context, :context_hash],
58
+ :score,
59
+ :prof,
60
+ :mem_delta,
61
+ :allocations,
62
+ :seconds_since_startup,
63
+ :hostname,
64
+ :git_sha,
65
+ :truncated_metrics]
48
66
  ScoutApm::AttributeArranger.call(self, json_attributes)
49
67
  end
50
68
 
@@ -23,10 +23,33 @@ module ScoutApm
23
23
  reporting_periods[current_timestamp]
24
24
  end
25
25
 
26
+ def find_period(timestamp = nil)
27
+ if timestamp
28
+ @reporting_periods[timestamp]
29
+ else
30
+ current_period
31
+ end
32
+ end
33
+
26
34
  # Save newly collected metrics
27
35
  def track!(metrics, options={})
28
36
  @mutex.synchronize {
29
- current_period.absorb_metrics!(metrics)
37
+ period = find_period(options[:timestamp])
38
+ period.absorb_metrics!(metrics)
39
+ }
40
+ end
41
+
42
+ def track_histograms!(histograms, options={})
43
+ @mutex.synchronize {
44
+ period = find_period(options[:timestamp])
45
+ period.merge_histograms!(histograms)
46
+ }
47
+ end
48
+
49
+ def track_db_query_metrics!(db_query_metric_set, options={})
50
+ @mutex.synchronize {
51
+ period = find_period(options[:timestamp])
52
+ period.merge_db_query_metrics!(db_query_metric_set)
30
53
  }
31
54
  end
32
55
 
@@ -67,19 +90,21 @@ module ScoutApm
67
90
  def write_to_layaway(layaway, force=false)
68
91
  ScoutApm::Agent.instance.logger.debug("Writing to layaway#{" (Forced)" if force}")
69
92
 
70
- @mutex.synchronize {
71
- reporting_periods.select { |time, rp| force || time.timestamp < current_timestamp.timestamp}.
93
+ reporting_periods.select { |time, rp| force || (time.timestamp < current_timestamp.timestamp) }.
94
+ each { |time, rp| collect_samplers(rp) }.
72
95
  each { |time, rp| write_reporting_period(layaway, time, rp) }
73
- }
74
96
  end
75
97
 
76
98
  def write_reporting_period(layaway, time, rp)
77
- collect_samplers(rp)
78
- layaway.write_reporting_period(rp)
99
+ @mutex.synchronize {
100
+ layaway.write_reporting_period(rp)
101
+ }
79
102
  rescue => e
80
103
  ScoutApm::Agent.instance.logger.warn("Failed writing data to layaway file: #{e.message} / #{e.backtrace}")
81
104
  ensure
82
- reporting_periods.delete(time)
105
+ ScoutApm::Agent.instance.logger.debug("Before delete, reporting periods length: #{reporting_periods.size}")
106
+ deleted_items = reporting_periods.delete(time)
107
+ ScoutApm::Agent.instance.logger.debug("After delete, reporting periods length: #{reporting_periods.size}. Did delete #{deleted_items}")
83
108
  end
84
109
 
85
110
  ######################################
@@ -91,12 +116,10 @@ module ScoutApm
91
116
  def collect_samplers(rp)
92
117
  @samplers.each do |sampler|
93
118
  begin
94
- metrics = sampler.metrics(rp.timestamp)
95
- rp.absorb_metrics!(metrics)
119
+ sampler.metrics(rp.timestamp, self)
96
120
  rescue => e
97
121
  ScoutApm::Agent.instance.logger.info "Error reading #{sampler.human_name} for period: #{rp}"
98
- ScoutApm::Agent.instance.logger.debug e.message
99
- ScoutApm::Agent.instance.logger.debug e.backtrace.join("\n")
122
+ ScoutApm::Agent.instance.logger.debug "#{e.message}\n\t#{e.backtrace.join("\n\t")}"
100
123
  end
101
124
  end
102
125
  end
@@ -159,19 +182,28 @@ module ScoutApm
159
182
  # A ScoredItemSet holding the "best" traces for the period
160
183
  attr_reader :job_traces
161
184
 
185
+ # An Array of HistogramsReport
186
+ attr_reader :histograms
187
+
162
188
  # A StoreReportingPeriodTimestamp representing the time that this
163
189
  # collection of metrics is for
164
190
  attr_reader :timestamp
165
191
 
166
192
  attr_reader :metric_set
167
193
 
194
+ attr_reader :db_query_metric_set
195
+
168
196
  def initialize(timestamp)
169
197
  @timestamp = timestamp
170
198
 
171
199
  @request_traces = ScoredItemSet.new
172
200
  @job_traces = ScoredItemSet.new
173
201
 
202
+ @histograms = []
203
+
174
204
  @metric_set = MetricSet.new
205
+ @db_query_metric_set = DbQueryMetricSet.new
206
+
175
207
  @jobs = Hash.new
176
208
  end
177
209
 
@@ -181,7 +213,9 @@ module ScoutApm
181
213
  merge_metrics!(other.metric_set).
182
214
  merge_slow_transactions!(other.slow_transactions_payload).
183
215
  merge_jobs!(other.jobs).
184
- merge_slow_jobs!(other.slow_jobs_payload)
216
+ merge_slow_jobs!(other.slow_jobs_payload).
217
+ merge_histograms!(other.histograms).
218
+ merge_db_query_metrics!(other.db_query_metric_set)
185
219
  self
186
220
  end
187
221
 
@@ -202,6 +236,11 @@ module ScoutApm
202
236
  self
203
237
  end
204
238
 
239
+ def merge_db_query_metrics!(other_metric_set)
240
+ db_query_metric_set.combine!(other_metric_set)
241
+ self
242
+ end
243
+
205
244
  def merge_slow_transactions!(new_transactions)
206
245
  Array(new_transactions).each do |one_transaction|
207
246
  request_traces << one_transaction
@@ -230,6 +269,17 @@ module ScoutApm
230
269
  self
231
270
  end
232
271
 
272
+ def merge_histograms!(new_histograms)
273
+ new_histograms = Array(new_histograms)
274
+ @histograms = (histograms + new_histograms).
275
+ group_by { |histo| histo.name }.
276
+ map { |(_, histos)|
277
+ histos.inject { |merged, histo| merged.combine!(histo) }
278
+ }
279
+
280
+ self
281
+ end
282
+
233
283
  #################################
234
284
  # Retrieve Metrics for reporting
235
285
  #################################
@@ -249,6 +299,10 @@ module ScoutApm
249
299
  job_traces.to_a
250
300
  end
251
301
 
302
+ def db_query_metrics_payload
303
+ db_query_metric_set.metrics_to_report
304
+ end
305
+
252
306
  #################################
253
307
  # Debug Helpers
254
308
  #################################
@@ -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,26 +54,35 @@ 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)
60
- if ignoring_children?
61
- return
62
- end
67
+ # If we're already stopping, don't do additional layers
68
+ return if stopping?
63
69
 
64
- layer.start_sampling
70
+ return if ignoring_children?
71
+
72
+ return ignoring_start_layer if ignoring_request?
65
73
 
66
74
  start_request(layer) unless @root_layer
67
- @layers[-1].add_child(layer) if @layers.any?
68
75
  @layers.push(layer)
69
76
  end
70
77
 
71
78
  def stop_layer
79
+ # If we're already stopping, don't do additional layers
80
+ return if stopping?
81
+
72
82
  return if ignoring_children?
73
83
 
84
+ return ignoring_stop_layer if ignoring_request?
85
+
74
86
  layer = @layers.pop
75
87
 
76
88
  # Safeguard against a mismatch in the layer tracking in an instrument.
@@ -78,15 +90,16 @@ module ScoutApm
78
90
  # lined up correctly. If stop_layer gets called twice, when it should
79
91
  # only have been called once you'll end up with this error.
80
92
  if layer.nil?
81
- ScoutApm::Agent.instance.logger.warn("Error stopping layer, was nil. Root Layer: #{@root_layer.inspect}")
93
+ logger.warn("Error stopping layer, was nil. Root Layer: #{@root_layer.inspect}")
82
94
  stop_request
83
95
  return
84
96
  end
85
97
 
86
- layer.record_traces!
87
98
  layer.record_stop_time!
88
99
  layer.record_allocations!
89
100
 
101
+ @layers[-1].add_child(layer) if @layers.any?
102
+
90
103
  # This must be called before checking if a backtrace should be collected as the call count influences our capture logic.
91
104
  # 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.
92
105
  update_call_counts!(layer)
@@ -96,8 +109,6 @@ module ScoutApm
96
109
 
97
110
  if finalized?
98
111
  stop_request
99
- else
100
- continue_sampling_for_layers if ScoutApm::Agent.instance.config.value('profile')
101
112
  end
102
113
  end
103
114
 
@@ -115,6 +126,8 @@ module ScoutApm
115
126
 
116
127
  BACKTRACE_BLACKLIST = ["Controller", "Job"]
117
128
  def capture_backtrace?(layer)
129
+ return if ignoring_request?
130
+
118
131
  # Never capture backtraces for this kind of layer. The backtrace will
119
132
  # always be 100% framework code.
120
133
  return false if BACKTRACE_BLACKLIST.include?(layer.type)
@@ -158,13 +171,6 @@ module ScoutApm
158
171
  @layers.none?
159
172
  end
160
173
 
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
-
168
174
  # Run at the beginning of the whole request
169
175
  #
170
176
  # * Capture the first layer as the root_layer
@@ -176,21 +182,15 @@ module ScoutApm
176
182
  #
177
183
  # * Send the request off to be stored
178
184
  def stop_request
179
- if ScoutApm::Agent.instance.config.value('profile')
180
- ScoutApm::Instruments::Stacks.stop_sampling(true)
181
- ScoutApm::Instruments::Stacks.update_indexes(0, 0)
182
- end
183
- record!
184
- end
185
+ @stopping = true
185
186
 
186
- # Enable ScoutProf for this thread
187
- def enable_profiled_thread!
188
- ScoutApm::Instruments::Stacks.add_profiled_thread
187
+ if recorder
188
+ recorder.record!(self)
189
+ end
189
190
  end
190
191
 
191
- # Disable ScoutProf for this thread
192
- def disable_profiled_thread!
193
- ScoutApm::Instruments::Stacks.remove_profiled_thread
192
+ def stopping?
193
+ @stopping
194
194
  end
195
195
 
196
196
  ###################################
@@ -236,6 +236,8 @@ module ScoutApm
236
236
  end
237
237
 
238
238
  def instant?
239
+ return false if ignoring_request?
240
+
239
241
  instant_key
240
242
  end
241
243
 
@@ -243,62 +245,61 @@ module ScoutApm
243
245
  # Persist the Request
244
246
  ###################################
245
247
 
248
+ def recorded!
249
+ @recorded = true
250
+ end
251
+
246
252
  # Convert this request to the appropriate structure, then report it into
247
253
  # the peristent Store object
248
254
  def record!
249
- @recorded = true
255
+ recorded!
256
+
257
+ return if ignoring_request?
258
+
259
+ # If we didn't have store, but we're trying to record anyway, go
260
+ # figure that out. (this happens in Remote Agent scenarios)
261
+ restore_store if @store.nil?
250
262
 
251
263
  # Bail out early if the user asked us to ignore this uri
252
264
  return if ScoutApm::Agent.instance.ignored_uris.ignore?(annotations[:uri])
253
265
 
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)
266
+ converters = [
267
+ LayerConverters::Histograms,
268
+ LayerConverters::MetricConverter,
269
+ LayerConverters::ErrorConverter,
270
+ LayerConverters::AllocationMetricConverter,
271
+ LayerConverters::RequestQueueTimeConverter,
272
+ LayerConverters::JobConverter,
273
+ LayerConverters::DatabaseConverter,
274
+
275
+ LayerConverters::SlowJobConverter,
276
+ LayerConverters::SlowRequestConverter,
277
+ ]
278
+
279
+ layer_finder = LayerConverters::FindLayerByType.new(self)
280
+ walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
281
+ converters = converters.map do |klass|
282
+ instance = klass.new(self, layer_finder, @store)
283
+ instance.register_hooks(walker)
284
+ instance
259
285
  end
260
-
261
- metrics = LayerConverters::MetricConverter.new(self).call
262
- @store.track!(metrics)
263
-
264
- error_metrics = LayerConverters::ErrorConverter.new(self).call
265
- @store.track!(error_metrics)
266
-
267
- allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
268
- @store.track!(allocation_metrics)
269
-
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)
274
-
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
286
+ walker.walk
287
+ converters.each {|i| i.record! }
288
+
289
+ # If there's an instant_key, it means we need to report this right away
290
+ if web? && instant?
291
+ converter = converters.find{|c| c.class == LayerConverters::SlowRequestConverter}
292
+ trace = converter.call
293
+ ScoutApm::InstantReporting.new(trace, instant_key).call
283
294
  end
284
-
285
- if job?
286
- job_metrics = LayerConverters::JobConverter.new(self).call
287
- @store.track_job!(job_metrics)
288
-
289
- job_converter = LayerConverters::SlowJobConverter.new(self)
290
- @store.track_slow_job!(job_converter)
291
- end
292
-
293
- allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
294
- @store.track!(allocation_metrics)
295
-
296
295
  end
297
296
 
298
297
  # Only call this after the request is complete
299
298
  def unique_name
299
+ return nil if ignoring_request?
300
+
300
301
  @unique_name ||= begin
301
- scope_layer = LayerConverters::ConverterBase.new(self).scope_layer
302
+ scope_layer = LayerConverters::FindLayerByType.new(self).scope
302
303
  if scope_layer
303
304
  scope_layer.legacy_metric_name
304
305
  else
@@ -311,6 +312,8 @@ module ScoutApm
311
312
  # Used to know when we should just create a new one (don't attempt to add
312
313
  # data to an already-recorded request). See RequestManager
313
314
  def recorded?
315
+ return ignoring_recorded? if ignoring_request?
316
+
314
317
  @recorded
315
318
  end
316
319
 
@@ -359,5 +362,67 @@ module ScoutApm
359
362
  def backtrace_threshold
360
363
  dev_trace ? 0.05 : 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
361
364
  end
365
+
366
+ ################################################################################
367
+ # Ignoring the rest of a request
368
+ ################################################################################
369
+
370
+ # At any point in the request, calling code or instrumentation can call
371
+ # `ignore_request!` to immediately stop recording any information about new
372
+ # layers, and delete any existing layer info. This class will still exist,
373
+ # and respond to methods as normal, but `record!` won't be called, and no
374
+ # data will be recorded.
375
+
376
+ def ignore_request!
377
+ return if @ignoring_request
378
+
379
+ # Set instance variable
380
+ @ignoring_request = true
381
+
382
+ # Store data we'll need
383
+ @ignoring_depth = @layers.length
384
+
385
+ # Clear data
386
+ @layers = []
387
+ @root_layer = nil
388
+ @call_set = nil
389
+ @annotations = {}
390
+ @instant_key = nil
391
+ end
392
+
393
+ def ignoring_request?
394
+ @ignoring_request
395
+ end
396
+
397
+ def ignoring_start_layer
398
+ @ignoring_depth += 1
399
+ end
400
+
401
+ def ignoring_stop_layer
402
+ @ignoring_depth -= 1
403
+ end
404
+
405
+ def ignoring_recorded?
406
+ @ignoring_depth <= 0
407
+ end
408
+
409
+ def logger
410
+ ScoutApm::Agent.instance.logger
411
+ end
412
+
413
+ # Actually go fetch & make-real any lazily created data.
414
+ # Clean up any cleverness in objects.
415
+ # Makes this object ready to be Marshal Dumped (or otherwise serialized)
416
+ def prepare_to_dump!
417
+ @call_set = nil
418
+ @store = nil
419
+ @recorder = nil
420
+ end
421
+
422
+ # Go re-fetch the store based on what the Agent's official one is. Used
423
+ # after hydrating a dumped TrackedRequest
424
+ def restore_store
425
+ @store = ScoutApm::Agent.instance.store
426
+ end
362
427
  end
363
428
  end