scout_apm 3.0.0.pre1 → 3.0.0.pre2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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