scout_apm 2.1.8 → 2.1.9

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 +9 -0
  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 +193 -0
  16. data/lib/scout_apm/layer_converters/slow_job_converter.rb +14 -73
  17. data/lib/scout_apm/layer_converters/slow_request_converter.rb +13 -85
  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 +1 -1
  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: a8c4c269396ae187fe901317425d0c984e2906bb
4
- data.tar.gz: 3b2c16163875d6b9dd4fb5b54a3485fa4fc27353
3
+ metadata.gz: b39d56f8f0d746e68ea6efb1563b03a9f61e42a3
4
+ data.tar.gz: 07a2274f0a5951b4d5d33321ee736dfea10e6f20
5
5
  SHA512:
6
- metadata.gz: e8793cf1a126e0e8c7294c633a06648352d301a7214f31aa7b863b9f1d3862c0148585eb711ce671312d17d697e92f834886c630e1974b62370dea03aaaf7aab
7
- data.tar.gz: 84db938d6d9972e14901a3c7b13095a70de32ad0b9dc8c2b688a302bf96c04bb176a0086783c01cc99e47b8c12906d2d05d6ea1a84940f3e8b01c177deb9dcb5
6
+ metadata.gz: bbc3e9460c9e91c4cbbf88fc79612115b399da95f97844dc0719694cab3651a151d31f550f0c2ba6e93c1f23e4868c17a3dc0aa4485c5a1133ec441a4e5b5a3e
7
+ data.tar.gz: feae03802789c2b9de7d62dd63b191474323e84c9474a13d7e5c49d3409e71bd952e75c1f10b43540488a39b761135a4da1078e5467274e43b3c41be5b4c0af2
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,12 @@
1
+ # 2.1.9
2
+
3
+ * Send raw histograms of response time, enabling more accurate 95th %iles
4
+ * Gzip payloads
5
+ * Fix Mongoid (5.0) + Mongo (2.1) support
6
+ * Initial Delayed Job support
7
+ * Limit max metric size of a trace to 500.
8
+
9
+
1
10
  # 2.1.8
2
11
 
3
12
  * Adds Git revision detection, which is reported on app load and associated with transaction traces
data/lib/scout_apm.rb CHANGED
@@ -94,6 +94,8 @@ require 'scout_apm/utils/null_logger'
94
94
  require 'scout_apm/utils/sql_sanitizer'
95
95
  require 'scout_apm/utils/time'
96
96
  require 'scout_apm/utils/unique_id'
97
+ require 'scout_apm/utils/numbers'
98
+ require 'scout_apm/utils/gzip_helper'
97
99
 
98
100
  require 'scout_apm/config'
99
101
  require 'scout_apm/environment'
@@ -132,6 +134,7 @@ require 'scout_apm/serializers/payload_serializer_to_json'
132
134
  require 'scout_apm/serializers/jobs_serializer_to_json'
133
135
  require 'scout_apm/serializers/slow_jobs_serializer_to_json'
134
136
  require 'scout_apm/serializers/metrics_to_json_serializer'
137
+ require 'scout_apm/serializers/histograms_serializer_to_json'
135
138
  require 'scout_apm/serializers/directive_serializer'
136
139
  require 'scout_apm/serializers/app_server_load_serializer'
137
140
 
@@ -130,7 +130,7 @@ module ScoutApm
130
130
 
131
131
  [ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
132
132
  ScoutApm::Instruments::Process::ProcessMemory.new(logger),
133
- ScoutApm::Instruments::PercentileSampler.new(logger, 95),
133
+ ScoutApm::Instruments::PercentileSampler.new(logger, request_histograms_by_time),
134
134
  ].each { |s| store.add_sampler(s) }
135
135
 
136
136
  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,
@@ -273,10 +276,10 @@ module ScoutApm
273
276
  # Loads the instrumention logic.
274
277
  def load_instruments
275
278
  if !background_job_missing?
276
- case environment.background_job_name
277
- when :delayed_job
278
- install_instrument(ScoutApm::Instruments::DelayedJob)
279
- end
279
+ # case environment.background_job_name
280
+ # when :delayed_job
281
+ # install_instrument(ScoutApm::Instruments::DelayedJob)
282
+ # end
280
283
  else
281
284
  case environment.framework
282
285
  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,195 @@ 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
+ stat = metric_hash[meta]
189
+ stat.update!(layer.total_call_time, layer.total_exclusive_time)
190
+
191
+ # allocations
192
+ stat = allocation_metric_hash[meta]
193
+ stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
194
+ end
195
+
196
+ # Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
197
+ def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
198
+ meta = MetricMeta.new("#{layer.type}/all")
199
+
200
+ metric_hash[meta] ||= MetricStats.new(false)
201
+ allocation_metric_hash[meta] ||= MetricStats.new(false)
202
+
203
+ # timing
204
+ stat = metric_hash[meta]
205
+ stat.update!(layer.total_call_time, layer.total_exclusive_time)
206
+
207
+ # allocations
208
+ stat = allocation_metric_hash[meta]
209
+ stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
210
+ end
211
+
212
+ ################################################################################
213
+ # Misc Helpers
214
+ ################################################################################
215
+
216
+ # Sometimes we start capturing a layer without knowing if we really
217
+ # want to make an entry for it. See ActiveRecord instrumentation for
218
+ # an example. We start capturing before we know if a query is cached
219
+ # or not, and want to skip any cached queries.
220
+ def skip_layer?(layer)
221
+ return true if layer.annotations[:ignorable]
222
+ end
30
223
  end
31
224
  end
32
225
  end