scout_apm 3.0.0.pre1 → 3.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +12 -1
  3. data/lib/scout_apm.rb +3 -0
  4. data/lib/scout_apm/agent.rb +9 -6
  5. data/lib/scout_apm/agent/reporting.rb +5 -3
  6. data/lib/scout_apm/background_job_integrations/delayed_job.rb +47 -1
  7. data/lib/scout_apm/background_worker.rb +4 -0
  8. data/lib/scout_apm/config.rb +2 -1
  9. data/lib/scout_apm/environment.rb +1 -1
  10. data/lib/scout_apm/histogram.rb +11 -2
  11. data/lib/scout_apm/instruments/mongoid.rb +14 -1
  12. data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
  13. data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
  14. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  15. data/lib/scout_apm/layer_converters/converter_base.rb +213 -0
  16. data/lib/scout_apm/layer_converters/slow_job_converter.rb +19 -93
  17. data/lib/scout_apm/layer_converters/slow_request_converter.rb +15 -100
  18. data/lib/scout_apm/metric_set.rb +6 -0
  19. data/lib/scout_apm/reporter.rb +53 -15
  20. data/lib/scout_apm/request_histograms.rb +4 -0
  21. data/lib/scout_apm/scored_item_set.rb +7 -0
  22. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
  23. data/lib/scout_apm/serializers/payload_serializer.rb +9 -3
  24. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +2 -1
  25. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +1 -1
  26. data/lib/scout_apm/slow_job_record.rb +4 -1
  27. data/lib/scout_apm/slow_transaction.rb +18 -2
  28. data/lib/scout_apm/store.rb +42 -11
  29. data/lib/scout_apm/tracked_request.rb +1 -1
  30. data/lib/scout_apm/utils/gzip_helper.rb +24 -0
  31. data/lib/scout_apm/utils/numbers.rb +14 -0
  32. data/lib/scout_apm/version.rb +2 -2
  33. data/test/test_helper.rb +10 -0
  34. data/test/unit/config_test.rb +7 -9
  35. data/test/unit/histogram_test.rb +14 -0
  36. data/test/unit/instruments/percentile_sampler_test.rb +137 -0
  37. data/test/unit/serializers/payload_serializer_test.rb +3 -3
  38. data/test/unit/store_test.rb +51 -0
  39. data/test/unit/utils/numbers_test.rb +15 -0
  40. metadata +10 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d4de385d6ffca53d6dd6e5de87fdc5e89fba54a3
4
- data.tar.gz: 5147b662a5b8c7454b8fdf50d6e82cc24c6cbbf7
3
+ metadata.gz: 83b62021065f23753ffcd91484e74ccd6dbf2058
4
+ data.tar.gz: 7163784d52c0547588f4cda9247eca9a634d29d1
5
5
  SHA512:
6
- metadata.gz: 790273c395b6b7f96bc0ca6a260a3372ca931362991553a9d7fb544d30959c769552532e1873bd0b2d4e9e6e0f86e5567964a75f8b75cb66c8d67f411a1a2384
7
- data.tar.gz: 37ab78f6e6f001ae3c82729daf9b1a008c61d5e70d9139795d4b80ae172de5100a82efe57ba3890301172902aa486a8404e8779914448c8b146584c246e8d0c4
6
+ metadata.gz: f711a6661dee811a41f5a92845f004288ca6eb931b0d49f5bf691d6f1fe0fd9b845d2292519c62e6137cfc49386cf77b43add139381531c6c9b351484e208ac8
7
+ data.tar.gz: d23b7ba32077adc95a23abead06bcbe8d452c2ff845c49bd2ad376ae069feadaed5836ad6c1bd46a075300962c2812d14109c2526804d606baf8d41dc581afd5
data/CHANGELOG.markdown CHANGED
@@ -1,7 +1,19 @@
1
+ # Dev
2
+
3
+ * Adds Git revision detection, which is reported on app load and associated with transaction traces
4
+
1
5
  # 3.0.0
2
6
 
3
7
  * ScoutProf BETA
4
8
 
9
+ # 2.1.9
10
+
11
+ * Send raw histograms of response time, enabling more accurate 95th %iles
12
+ * Gzip payloads
13
+ * Fix Mongoid (5.0) + Mongo (2.1) support
14
+ * Initial Delayed Job support
15
+ * Limit max metric size of a trace to 500.
16
+
5
17
  # 2.1.8
6
18
 
7
19
  * Adds Git revision detection, which is reported on app load and associated with transaction traces
@@ -9,7 +21,6 @@
9
21
  # 2.1.7
10
22
 
11
23
  * Fix allocations extension compilation on Ruby 1.8.7
12
- >>>>>>> master
13
24
 
14
25
  # 2.1.6
15
26
 
data/lib/scout_apm.rb CHANGED
@@ -101,6 +101,8 @@ require 'scout_apm/utils/null_logger'
101
101
  require 'scout_apm/utils/sql_sanitizer'
102
102
  require 'scout_apm/utils/time'
103
103
  require 'scout_apm/utils/unique_id'
104
+ require 'scout_apm/utils/numbers'
105
+ require 'scout_apm/utils/gzip_helper'
104
106
 
105
107
  require 'scout_apm/config'
106
108
  require 'scout_apm/environment'
@@ -140,6 +142,7 @@ require 'scout_apm/serializers/payload_serializer_to_json'
140
142
  require 'scout_apm/serializers/jobs_serializer_to_json'
141
143
  require 'scout_apm/serializers/slow_jobs_serializer_to_json'
142
144
  require 'scout_apm/serializers/metrics_to_json_serializer'
145
+ require 'scout_apm/serializers/histograms_serializer_to_json'
143
146
  require 'scout_apm/serializers/directive_serializer'
144
147
  require 'scout_apm/serializers/app_server_load_serializer'
145
148
 
@@ -129,7 +129,7 @@ module ScoutApm
129
129
 
130
130
  [ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
131
131
  ScoutApm::Instruments::Process::ProcessMemory.new(logger),
132
- ScoutApm::Instruments::PercentileSampler.new(logger, 95),
132
+ ScoutApm::Instruments::PercentileSampler.new(logger, request_histograms_by_time),
133
133
  ].each { |s| store.add_sampler(s) }
134
134
 
135
135
  app_server_load_hook
@@ -231,7 +231,10 @@ module ScoutApm
231
231
  end
232
232
 
233
233
  def background_worker_running?
234
- !! @background_worker_thread
234
+ @background_worker_thread &&
235
+ @background_worker_thread.alive? &&
236
+ @background_worker &&
237
+ @background_worker.running?
235
238
  end
236
239
 
237
240
  # Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes,
@@ -279,10 +282,10 @@ module ScoutApm
279
282
  # Loads the instrumention logic.
280
283
  def load_instruments
281
284
  if !background_job_missing?
282
- case environment.background_job_name
283
- when :delayed_job
284
- install_instrument(ScoutApm::Instruments::DelayedJob)
285
- end
285
+ # case environment.background_job_name
286
+ # when :delayed_job
287
+ # install_instrument(ScoutApm::Instruments::DelayedJob)
288
+ # end
286
289
  else
287
290
  case environment.framework
288
291
  when :rails then install_instrument(ScoutApm::Instruments::ActionControllerRails2)
@@ -58,6 +58,7 @@ module ScoutApm
58
58
  slow_transactions = reporting_period.slow_transactions_payload
59
59
  jobs = reporting_period.jobs
60
60
  slow_jobs = reporting_period.slow_jobs_payload
61
+ histograms = reporting_period.histograms
61
62
 
62
63
  metadata = {
63
64
  :app_root => ScoutApm::Environment.instance.root.to_s,
@@ -68,9 +69,9 @@ module ScoutApm
68
69
  :platform => "ruby",
69
70
  }
70
71
 
71
- log_deliver(metrics, slow_transactions, metadata, slow_jobs)
72
+ log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
72
73
 
73
- payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs)
74
+ payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms)
74
75
  # logger.debug("Payload: #{payload}")
75
76
 
76
77
  reporter.report(payload, headers)
@@ -80,7 +81,7 @@ module ScoutApm
80
81
  logger.debug e.backtrace
81
82
  end
82
83
 
83
- def log_deliver(metrics, slow_transactions, metadata, jobs_traces)
84
+ def log_deliver(metrics, slow_transactions, metadata, jobs_traces, histograms)
84
85
  total_request_count = metrics.
85
86
  select { |meta,stats| meta.metric_name =~ /\AController/ }.
86
87
  inject(0) {|sum, (_, stat)| sum + stat.call_count }
@@ -97,6 +98,7 @@ module ScoutApm
97
98
  metrics_clause = "#{metrics.length} Metrics for #{total_request_count} requests"
98
99
  slow_trans_clause = "#{slow_transactions.length} Slow Transaction Traces"
99
100
  job_clause = "#{jobs_traces.length} Job Traces"
101
+ histogram_clause = "#{histograms.length} Histograms"
100
102
 
101
103
  logger.info "#{time_clause} Delivering #{metrics_clause} and #{slow_trans_clause} and #{job_clause}, #{process_log_str}."
102
104
  # logger.debug("Metrics: #{metrics.pretty_inspect}\nSlowTrans: #{slow_transactions.pretty_inspect}\nMetadata: #{metadata.inspect.pretty_inspect}")
@@ -8,12 +8,58 @@ module ScoutApm
8
8
  end
9
9
 
10
10
  def present?
11
- defined?(::Delayed::Job) && (File.basename($0) =~ /\Adelayed_job/)
11
+ defined?(::Delayed::Job) && File.basename($PROGRAM_NAME).start_with?('delayed_job')
12
12
  end
13
13
 
14
14
  def forking?
15
15
  false
16
16
  end
17
+
18
+ def install
19
+ plugin = Class.new(Delayed::Plugin) do
20
+ require 'delayed_job'
21
+
22
+ callbacks do |lifecycle|
23
+ lifecycle.around(:invoke_job) do |job, *args, &block|
24
+ ScoutApm::Agent.instance.start_background_worker unless ScoutApm::Agent.instance.background_worker_running?
25
+
26
+ name = job.name
27
+ queue = job.queue || "default"
28
+
29
+ req = ScoutApm::RequestManager.lookup
30
+ req.job!
31
+
32
+ begin
33
+ latency = Time.now - job.created_at
34
+ req.annotate_request(:queue_latency => latency)
35
+ rescue
36
+ end
37
+
38
+ queue_layer = ScoutApm::Layer.new('Queue', queue)
39
+ job_layer = ScoutApm::Layer.new('Job', name)
40
+
41
+ begin
42
+ req.start_layer(queue_layer)
43
+ started_queue = true
44
+ req.start_layer(job_layer)
45
+ started_job = true
46
+
47
+ # Call the job itself.
48
+ block.call(job, *args)
49
+ rescue
50
+ req.error!
51
+ raise
52
+ ensure
53
+ req.stop_layer if started_job
54
+ req.stop_layer if started_queue
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ Delayed::Worker.plugins << plugin # ScoutApm::BackgroundJobIntegrations::DelayedJobPlugin
61
+ end
17
62
  end
18
63
  end
19
64
  end
65
+
@@ -11,6 +11,10 @@ module ScoutApm
11
11
  @keep_running = true
12
12
  end
13
13
 
14
+ def running?
15
+ @keep_running
16
+ end
17
+
14
18
  def stop
15
19
  ScoutApm::Agent.instance.logger.debug "Background Worker: stop requested"
16
20
  @keep_running = false
@@ -159,7 +159,8 @@ module ScoutApm
159
159
  'enable_background_jobs' => true,
160
160
  'ignore' => [],
161
161
  'dev_trace' => false,
162
- 'profile' => true # for scoutprof
162
+ 'profile' => true, # for scoutprof
163
+ 'compress_payload' => true,
163
164
  }.freeze
164
165
 
165
166
  def value(key)
@@ -25,7 +25,7 @@ module ScoutApm
25
25
 
26
26
  BACKGROUND_JOB_INTEGRATIONS = [
27
27
  ScoutApm::BackgroundJobIntegrations::Sidekiq.new,
28
- # ScoutApm::BackgroundJobIntegrations::DelayedJob.new
28
+ ScoutApm::BackgroundJobIntegrations::DelayedJob.new,
29
29
  ]
30
30
 
31
31
  FRAMEWORK_INTEGRATIONS = [
@@ -90,7 +90,11 @@ module ScoutApm
90
90
  def combine!(other)
91
91
  mutex.synchronize do
92
92
  other.mutex.synchronize do
93
- @bins = (other.bins + @bins).sort_by {|b| b.value }
93
+ @bins = (other.bins + @bins).
94
+ group_by {|b| b.value }.
95
+ map {|val, bs| [val, bs.inject(0) {|sum, b| sum + b.count }] }.
96
+ map {|val, sum| HistogramBin.new(val,sum) }.
97
+ sort_by { |b| b.value }
94
98
  @total += other.total
95
99
  trim
96
100
  self
@@ -100,7 +104,12 @@ module ScoutApm
100
104
 
101
105
  def as_json
102
106
  mutex.synchronize do
103
- bins.map{|b| [b.value, b.count]}
107
+ bins.map{ |b|
108
+ [
109
+ ScoutApm::Utils::Numbers.round(b.value, 4),
110
+ b.count
111
+ ]
112
+ }
104
113
  end
105
114
  end
106
115
 
@@ -50,11 +50,24 @@ module ScoutApm
50
50
  with_scout_instruments = %Q[
51
51
  def #{method}_with_scout_instruments(*args, &block)
52
52
 
53
+
53
54
  req = ScoutApm::RequestManager.lookup
54
55
  *db, collection = view.collection.namespace.split(".")
55
56
 
56
57
  name = collection + "/#{method}"
57
- filter = ScoutApm::Instruments::Mongoid.anonymize_filter(view.filter)
58
+
59
+ # Between Mongo gem version 2.1 and 2.3, this method name was
60
+ # changed. Accomodate both. If for some reason neither is
61
+ # there, try to continue with an empty "filter" hash.
62
+ raw_filter = if view.respond_to?(:selector)
63
+ view.selector
64
+ elsif view.respond_to?(:filter)
65
+ view.filter
66
+ else
67
+ {}
68
+ end
69
+
70
+ filter = ScoutApm::Instruments::Mongoid.anonymize_filter(raw_filter)
58
71
 
59
72
  layer = ScoutApm::Layer.new("MongoDB", name)
60
73
  layer.desc = filter.inspect
@@ -1,36 +1,53 @@
1
1
  module ScoutApm
2
2
  module Instruments
3
+
4
+ class HistogramReport
5
+ attr_reader :name
6
+ attr_reader :histogram
7
+
8
+ def initialize(name, histogram)
9
+ @name = name
10
+ @histogram = histogram
11
+ end
12
+
13
+ def combine!(other)
14
+ raise "Mismatched Histogram Names" unless name == other.name
15
+ histogram.combine!(other.histogram)
16
+ self
17
+ end
18
+ end
19
+
3
20
  class PercentileSampler
4
21
  attr_reader :logger
5
22
 
6
- attr_reader :percentiles
23
+ # A hash of { time => RequestHistograms }
24
+ attr_reader :histograms
7
25
 
8
- def initialize(logger, percentiles)
26
+ def initialize(logger, histograms)
9
27
  @logger = logger
10
- @percentiles = Array(percentiles)
28
+ @histograms = histograms
11
29
  end
12
30
 
13
31
  def human_name
14
- "Percentiles"
32
+ 'Percentiles'
15
33
  end
16
34
 
17
- # Gets the 95th%ile for the time requested
18
- def metrics(time)
19
- ms = {}
20
- histos = ScoutApm::Agent.instance.request_histograms_by_time[time]
21
- histos.each_name do |name|
22
- percentiles.each do |percentile|
23
- meta = MetricMeta.new("Percentile/#{percentile}/#{name}")
24
- stat = MetricStats.new
25
- stat.update!(histos.quantile(name, percentile))
26
- ms[meta] = stat
27
- end
28
- end
35
+ def metrics(timestamp, store)
36
+ store.track_histograms!(percentiles(timestamp), :timestamp => timestamp)
37
+ end
38
+
39
+ def percentiles(time)
40
+ result = []
29
41
 
30
- # Wipe the histograms we just collected data on
31
- ScoutApm::Agent.instance.request_histograms_by_time.delete(time)
42
+ histogram = histograms.delete(time)
43
+
44
+ return result unless histogram
45
+
46
+ histogram.each_name do |name|
47
+ result << HistogramReport.new(name, histogram.raw(name))
48
+ end
32
49
 
33
- ms
50
+ result
34
51
  end
35
52
  end
36
53
  end
@@ -29,16 +29,17 @@ module ScoutApm
29
29
  "Process CPU"
30
30
  end
31
31
 
32
- def metrics(_time)
32
+ def metrics(timestamp, store)
33
33
  result = run
34
34
  if result
35
35
  meta = MetricMeta.new("#{metric_type}/#{metric_name}")
36
36
  stat = MetricStats.new(false)
37
37
  stat.update!(result)
38
- { meta => stat }
38
+ store.track!({ meta => stat }, :timestamp => timestamp)
39
39
  else
40
40
  {}
41
41
  end
42
+
42
43
  end
43
44
 
44
45
  # TODO: Figure out a good default instead of nil
@@ -33,20 +33,20 @@ module ScoutApm
33
33
  "Process Memory"
34
34
  end
35
35
 
36
- def metrics(_time)
36
+ def metrics(timestamp, store)
37
37
  result = run
38
38
  if result
39
39
  meta = MetricMeta.new("#{metric_type}/#{metric_name}")
40
40
  stat = MetricStats.new(false)
41
41
  stat.update!(result)
42
- { meta => stat }
42
+ store.track!({ meta => stat }, :timestamp => timestamp)
43
43
  else
44
44
  {}
45
45
  end
46
46
  end
47
47
 
48
48
  def run
49
- self.class.rss_in_mb.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
49
+ self.class.rss_in_mb.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
50
50
  end
51
51
  end
52
52
  end
@@ -1,6 +1,7 @@
1
1
  module ScoutApm
2
2
  module LayerConverters
3
3
  class ConverterBase
4
+
4
5
  attr_reader :walker
5
6
  attr_reader :request
6
7
  attr_reader :root_layer
@@ -8,7 +9,10 @@ module ScoutApm
8
9
  def initialize(request)
9
10
  @request = request
10
11
  @root_layer = request.root_layer
12
+ @backtraces = []
11
13
  @walker = DepthFirstWalker.new(root_layer)
14
+
15
+ @limited = false
12
16
  end
13
17
 
14
18
  # Scope is determined by the first Controller we hit. Most of the time
@@ -27,6 +31,215 @@ module ScoutApm
27
31
  return layer if layer.type == layer_type
28
32
  end
29
33
  end
34
+
35
+ ################################################################################
36
+ # Subscoping
37
+ ################################################################################
38
+ #
39
+ # Keep a list of subscopes, but only ever use the front one. The rest
40
+ # get pushed/popped in cases when we have many levels of subscopable
41
+ # layers. This lets us push/pop without otherwise keeping track very closely.
42
+ def setup_subscopable_callbacks
43
+ @subscope_layers = []
44
+
45
+ walker.before do |layer|
46
+ if layer.subscopable?
47
+ @subscope_layers.push(layer)
48
+ end
49
+ end
50
+
51
+ walker.after do |layer|
52
+ if layer.subscopable?
53
+ @subscope_layers.pop
54
+ end
55
+ end
56
+ end
57
+
58
+ def subscoped?(layer)
59
+ @subscope_layers.first && layer != @subscope_layers.first # Don't scope under ourself.
60
+ end
61
+
62
+ def subscope_name
63
+ @subscope_layers.first.legacy_metric_name
64
+ end
65
+
66
+
67
+ ################################################################################
68
+ # Backtrace Handling
69
+ ################################################################################
70
+ #
71
+ # Because we get several layers for the same thing if you call an
72
+ # instrumented thing repeatedly, and only some of them may have
73
+ # backtraces captured, we store the backtraces off into another spot
74
+ # during processing, then at the end, we loop over those saved
75
+ # backtraces, putting them back into the metrics hash.
76
+ #
77
+ # This comes up most often when capturing n+1 backtraces. Because the
78
+ # query may be fast enough to evade our time-limit based backtrace
79
+ # capture, only the Nth item (see TrackedRequest for more detail) has a
80
+ # backtrack captured. This sequence makes sure that we report up that
81
+ # backtrace in the aggregated set of metrics around that call.
82
+
83
+ # Call this as you are processing each layer. It will store off backtraces
84
+ def store_backtrace(layer, meta)
85
+ return unless layer.backtrace
86
+
87
+ bt = ScoutApm::Utils::BacktraceParser.new(layer.backtrace).call
88
+ if bt.any?
89
+ meta.backtrace = bt
90
+ @backtraces << meta
91
+ else
92
+ ScoutApm::Agent.instance.logger.debug { "Unable to capture an app-specific backtrace for #{meta.inspect}\n#{layer.backtrace}" }
93
+ end
94
+ end
95
+
96
+ # Call this after you finish walking the layers, and want to take the
97
+ # set-aside backtraces and place them into the metas they match
98
+ def attach_backtraces(metric_hash)
99
+ @backtraces.each do |meta_with_backtrace|
100
+ metric_hash.keys.find { |k| k == meta_with_backtrace }.backtrace = meta_with_backtrace.backtrace
101
+ end
102
+ metric_hash
103
+ end
104
+
105
+
106
+ ################################################################################
107
+ # Limit Handling
108
+ ################################################################################
109
+
110
+ # To prevent huge traces from being generated, we should stop collecting
111
+ # detailed metrics as we go beyond some reasonably large count.
112
+ #
113
+ # We should still add up the /all aggregates.
114
+
115
+ MAX_METRICS = 500
116
+
117
+ def over_metric_limit?(metric_hash)
118
+ if metric_hash.size > MAX_METRICS
119
+ @limited = true
120
+ else
121
+ false
122
+ end
123
+ end
124
+
125
+ def limited?
126
+ !! @limited
127
+ end
128
+
129
+ ################################################################################
130
+ # Meta Scope
131
+ ################################################################################
132
+
133
+ # When we make MetricMeta records, we need to determine a few things from layer.
134
+ def make_meta_options(layer)
135
+ scope_hash = make_meta_options_scope(layer)
136
+ desc_hash = make_meta_options_desc_hash(layer)
137
+
138
+ scope_hash.merge(desc_hash)
139
+ end
140
+
141
+ def make_meta_options_scope(layer)
142
+ # This layer is scoped under another thing. Typically that means this is a layer under a view.
143
+ # Like: Controller -> View/users/show -> ActiveRecord/user/find
144
+ # in that example, the scope is the View/users/show
145
+ if subscoped?(layer)
146
+ {:scope => subscope_name}
147
+
148
+ # We don't scope the controller under itself
149
+ elsif layer == scope_layer
150
+ {}
151
+
152
+ # This layer is a top level metric ("ActiveRecord", or "HTTP" or
153
+ # whatever, directly under the controller), so scope to the
154
+ # Controller
155
+ else
156
+ {:scope => scope_layer.legacy_metric_name}
157
+ end
158
+ end
159
+
160
+ def make_meta_options_desc_hash(layer)
161
+ if layer.desc
162
+ {:desc => layer.desc.to_s}
163
+ else
164
+ {}
165
+ end
166
+ end
167
+
168
+
169
+ ################################################################################
170
+ # Storing metrics into the hashes
171
+ ################################################################################
172
+
173
+ # This is the detailed metric - type, name, backtrace, annotations, etc.
174
+ def store_specific_metric(layer, metric_hash, allocation_metric_hash)
175
+ return false if over_metric_limit?(metric_hash)
176
+
177
+ meta_options = make_meta_options(layer)
178
+
179
+ meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
180
+ meta.extra.merge!(layer.annotations)
181
+
182
+ store_backtrace(layer, meta)
183
+
184
+ metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
185
+ allocation_metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
186
+
187
+ # Timing
188
+ timing_stat = metric_hash[meta]
189
+ timing_stat.update!(layer.total_call_time, layer.total_exclusive_time)
190
+
191
+ # Allocations
192
+ allocation_stat = allocation_metric_hash[meta]
193
+ allocation_stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
194
+
195
+ # Attach Scoutprof Traces
196
+ timing_stat.add_traces(layer.traces.as_json)
197
+ end
198
+
199
+ # Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
200
+ def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
201
+ meta = MetricMeta.new("#{layer.type}/all")
202
+
203
+ metric_hash[meta] ||= MetricStats.new(false)
204
+ allocation_metric_hash[meta] ||= MetricStats.new(false)
205
+
206
+ # timing
207
+ timing_stat = metric_hash[meta]
208
+ timing_stat.update!(layer.total_call_time, layer.total_exclusive_time)
209
+
210
+ # allocations
211
+ allocation_stat = allocation_metric_hash[meta]
212
+ allocation_stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
213
+ end
214
+
215
+ ################################################################################
216
+ # Misc Helpers
217
+ ################################################################################
218
+
219
+ # Sometimes we start capturing a layer without knowing if we really
220
+ # want to make an entry for it. See ActiveRecord instrumentation for
221
+ # an example. We start capturing before we know if a query is cached
222
+ # or not, and want to skip any cached queries.
223
+ def skip_layer?(layer)
224
+ return true if layer.annotations[:ignorable]
225
+ end
226
+
227
+ # Debug logging for scoutprof traces
228
+ def debug_scoutprof(layer)
229
+ agent = ScoutApm::Agent.instance
230
+ config = agent.config
231
+
232
+ if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
233
+ if config.value('profile')
234
+ agent.logger.debug do
235
+ traces_inspect = layer.traces.inspect
236
+ "****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}, skipped gc: #{layer.traces.skipped_in_gc}, skipped handler: #{layer.traces.skipped_in_handler}, skipped registered #{layer.traces.skipped_in_job_registered}, skipped not_running #{layer.traces.skipped_in_not_running}:\n#{traces_inspect}"
237
+ end
238
+ else
239
+ agent.logger.debug "****** Slow Request #{layer.type} Traces: Scoutprof is not enabled"
240
+ end
241
+ end
242
+ end
30
243
  end
31
244
  end
32
245
  end