scout_apm 1.1.0.pre1 → 1.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +6 -0
  3. data/lib/scout_apm/agent/reporting.rb +67 -77
  4. data/lib/scout_apm/agent.rb +56 -9
  5. data/lib/scout_apm/background_job_integrations/delayed_job.rb +19 -0
  6. data/lib/scout_apm/background_job_integrations/sidekiq.rb +60 -0
  7. data/lib/scout_apm/bucket_name_splitter.rb +2 -2
  8. data/lib/scout_apm/capacity.rb +2 -1
  9. data/lib/scout_apm/context.rb +1 -5
  10. data/lib/scout_apm/environment.rb +16 -1
  11. data/lib/scout_apm/instruments/action_controller_rails_2.rb +13 -3
  12. data/lib/scout_apm/instruments/action_controller_rails_3.rb +20 -20
  13. data/lib/scout_apm/instruments/active_record.rb +5 -8
  14. data/lib/scout_apm/instruments/delayed_job.rb +56 -0
  15. data/lib/scout_apm/instruments/middleware.rb +44 -0
  16. data/lib/scout_apm/instruments/mongoid.rb +1 -1
  17. data/lib/scout_apm/instruments/moped.rb +2 -2
  18. data/lib/scout_apm/instruments/net_http.rb +1 -2
  19. data/lib/scout_apm/instruments/process/process_cpu.rb +5 -1
  20. data/lib/scout_apm/instruments/process/process_memory.rb +5 -1
  21. data/lib/scout_apm/instruments/sinatra.rb +14 -2
  22. data/lib/scout_apm/layaway.rb +33 -79
  23. data/lib/scout_apm/layaway_file.rb +2 -1
  24. data/lib/scout_apm/layer.rb +115 -0
  25. data/lib/scout_apm/layer_converter.rb +196 -0
  26. data/lib/scout_apm/metric_meta.rb +24 -4
  27. data/lib/scout_apm/metric_stats.rb +14 -4
  28. data/lib/scout_apm/request_manager.rb +26 -0
  29. data/lib/scout_apm/request_queue_time.rb +54 -0
  30. data/lib/scout_apm/serializers/payload_serializer.rb +8 -1
  31. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +1 -0
  32. data/lib/scout_apm/slow_transaction.rb +3 -0
  33. data/lib/scout_apm/store.rb +122 -190
  34. data/lib/scout_apm/tracer.rb +54 -83
  35. data/lib/scout_apm/tracked_request.rb +168 -0
  36. data/lib/scout_apm/version.rb +1 -1
  37. data/lib/scout_apm.rb +18 -5
  38. metadata +11 -2
@@ -0,0 +1,44 @@
1
+ module ScoutApm
2
+ module Instruments
3
+ class Middleware
4
+ def initalize(logger=ScoutApm::Agent.instance.logger)
5
+ @logger = logger
6
+ @installed = false
7
+ end
8
+
9
+ def installed?
10
+ @installed
11
+ end
12
+
13
+ def install
14
+ @installed = true
15
+
16
+ if defined?(ActionDispatch) && defined?(ActionDispatch::MiddlewareStack) && defined?(ActionDispatch::MiddlewareStack::Middleware)
17
+ ActionDispatch::MiddlewareStack::Middleware.class_eval do
18
+ def build(app)
19
+ ScoutApm::Agent.instance.logger.info("Building Middleware #{klass.name}")
20
+ new_mw = klass.new(app, *args, &block)
21
+ MiddlewareWrapper.new(new_mw, klass.name)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ class MiddlewareWrapper
28
+ def initialize(app, name)
29
+ @app = app
30
+ @type = "Middleware"
31
+ @name = name
32
+ end
33
+
34
+ def call(env)
35
+ req = ScoutApm::RequestManager.lookup
36
+ req.start_layer( ScoutApm::Layer.new(@type, @name) )
37
+ @app.call(env)
38
+ ensure
39
+ req.stop_layer
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -22,7 +22,7 @@ module ScoutApm
22
22
  ::Mongoid::Collection.class_eval do
23
23
  include ScoutApm::Tracer
24
24
  (::Mongoid::Collections::Operations::ALL - [:<<, :[]]).each do |method|
25
- instrument_method method, :metric_name => "MongoDB/\#{@klass}/#{method}"
25
+ instrument_method method, :type => "MongoDB", :name => '#{@klass}/#{method}'
26
26
  end
27
27
  end
28
28
  end
@@ -23,8 +23,8 @@ module ScoutApm
23
23
  def process_with_scout_instruments(operation, &callback)
24
24
  if operation.respond_to?(:collection)
25
25
  collection = operation.collection
26
- self.class.instrument("MongoDB/Process/#{collection}/#{operation.class.to_s.split('::').last}",
27
- :desc => scout_sanitize_log(operation.log_inspect)) do
26
+ name = "Process/#{collection}/#{operation.class.to_s.split('::').last}"
27
+ self.class.instrument("MongoDB", name, :annotate_layer => { :query => scout_sanitize_log(operation.log_inspect) }) do
28
28
  process_without_scout_instruments(operation, &callback)
29
29
  end
30
30
  end
@@ -22,7 +22,7 @@ module ScoutApm
22
22
  include ScoutApm::Tracer
23
23
 
24
24
  def request_with_scout_instruments(*args,&block)
25
- self.class.instrument("HTTP/request", :desc => "#{(@address+args.first.path.split('?').first)[0..99]}") do
25
+ self.class.instrument("HTTP", "request", :annotate_layer => { :url => "#{(@address+args.first.path.split('?').first)[0..99]}" } ) do
26
26
  request_without_scout_instruments(*args,&block)
27
27
  end
28
28
  end
@@ -30,7 +30,6 @@ module ScoutApm
30
30
  alias request request_with_scout_instruments
31
31
  end
32
32
  end
33
-
34
33
  end
35
34
  end
36
35
  end
@@ -17,8 +17,12 @@ module ScoutApm
17
17
  @last_stime = t.stime
18
18
  end
19
19
 
20
+ def metric_type
21
+ "CPU"
22
+ end
23
+
20
24
  def metric_name
21
- "CPU/Utilization"
25
+ "Utilization"
22
26
  end
23
27
 
24
28
  def human_name
@@ -8,8 +8,12 @@ module ScoutApm
8
8
  @logger = logger
9
9
  end
10
10
 
11
+ def metric_type
12
+ "Memory"
13
+ end
14
+
11
15
  def metric_name
12
- "Memory/Physical"
16
+ "Physical"
13
17
  end
14
18
 
15
19
  def human_name
@@ -29,9 +29,21 @@ module ScoutApm
29
29
 
30
30
  module SinatraInstruments
31
31
  def dispatch_with_scout_instruments!
32
- scout_controller_action = "Controller/Sinatra/#{scout_sinatra_controller_name(@request)}"
33
- self.class.scout_apm_trace(scout_controller_action, :uri => @request.path_info, :ip => @request.ip) do
32
+ scout_controller_action = "Sinatra/#{scout_sinatra_controller_name(@request)}"
33
+
34
+ req = ScoutApm::RequestManager.lookup
35
+ req.annotate_request(:uri => @request.path_info)
36
+ req.context.add_user(:ip => @request.ip)
37
+ req.set_headers(request.headers)
38
+
39
+ req.start_layer( ScoutApm::Layer.new("Controller", scout_controller_action) )
40
+ begin
34
41
  dispatch_without_scout_instruments!
42
+ rescue
43
+ req.error!
44
+ raise
45
+ ensure
46
+ req.stop_layer
35
47
  end
36
48
  end
37
49
 
@@ -1,9 +1,5 @@
1
- # Stores metrics in a file before sending them to the server. Two uses:
1
+ # Stores StoreReportingPeriod objects in a file before sending them to the server.
2
2
  # 1. A centralized store for multiple Agent processes. This way, only 1 checkin is sent to Scout rather than 1 per-process.
3
- # 2. Bundling up reports from multiple timeslices to make updates more efficent server-side.
4
- #
5
- # Data is stored in a Hash, where the keys are Time.to_i on the minute. The value is a Hash {:metrics => Hash, :slow_transactions => Array}.
6
- # When depositing data, the new data is either merged with an existing time or placed in a new key.
7
3
  module ScoutApm
8
4
  class Layaway
9
5
  attr_accessor :file
@@ -12,93 +8,51 @@ module ScoutApm
12
8
  @file = ScoutApm::LayawayFile.new
13
9
  end
14
10
 
15
- def deposit_and_deliver
16
- new_metrics = ScoutApm::Agent.instance.store.metric_hash
17
- log_deposited_metrics(new_metrics)
18
- log_deposited_slow_transactions(ScoutApm::Agent.instance.store.slow_transactions)
19
- to_deliver = {}
20
- file.read_and_write do |old_data|
21
- old_data ||= Hash.new
22
- # merge data
23
- # if (1) there's data in the file and (2) there isn't any data yet for the current minute, this means we've
24
- # collected all metrics for the previous slots and we're ready to deliver.
25
- #
26
- # Example w/2 processes:
27
- #
28
- # 12:00:34 ---
29
- # Process 1: old_data.any? => false, so deposits.
30
- # Process 2: old_data_any? => true and old_data[12:00].nil? => false, so deposits.
31
- #
32
- # 12:01:34 ---
33
- # Process 1: old_data.any? => true and old_data[12:01].nil? => true, so delivers metrics.
34
- # Process 2: old_data.any? => true and old_data[12:01].nil? => false, so deposits.
35
- if old_data.any? and old_data[slot].nil?
36
- to_deliver = old_data
37
- old_data = Hash.new
38
- elsif old_data.any?
39
- ScoutApm::Agent.instance.logger.debug "Not yet time to deliver payload for slot [#{Utils::Time.to_s(old_data.keys.sort.last)}]"
11
+ # We're changing the format, so detect if we're loading an old formatted
12
+ # file, and just drop it if so. There's no important data there, since it's
13
+ # used mostly for just syncronizing between processes
14
+ def verify_layaway_file_contents
15
+ file.read_and_write do |existing_data|
16
+ existing_data ||= {}
17
+ if existing_data.keys.all?{|k| k.is_a? StoreReportingPeriodTimestamp } &&
18
+ existing_data.values.all? {|v| v.is_a? StoreReportingPeriod }
19
+ existing_data
40
20
  else
41
- ScoutApm::Agent.instance.logger.debug "There is no data in the layaway file to deliver."
21
+ {}
42
22
  end
43
- old_data[slot]=ScoutApm::Agent.instance.store.merge_data_and_clear(old_data[slot] || {:metrics => {}, :slow_transactions => []})
44
- log_saved_data(old_data,new_metrics)
45
- old_data
46
23
  end
47
- to_deliver.any? ? validate_data(to_deliver) : {}
48
24
  end
49
25
 
50
- # Ensures the data we're sending to the server isn't stale.
51
- # This can occur if the agent is collecting data, and app server goes down w/data in the local storage.
52
- # When it is restarted later data will remain in local storage but it won't be for the current reporting interval.
53
- #
54
- # If the data is stale, an empty Hash is returned. Otherwise, the data from the most recent slot is returned.
55
- def validate_data(data)
56
- data = data.to_a.sort
57
- now = Time.now
58
- if (most_recent = data.first.first) < now.to_i - 2*60
59
- ScoutApm::Agent.instance.logger.debug "Local Storage is stale (#{Utils::Time.to_s(most_recent)}). Not sending data."
60
- {}
61
- else
62
- data.first.last
26
+ def add_reporting_period(time, reporting_period)
27
+ file.read_and_write do |existing_data|
28
+ existing_data ||= Hash.new
29
+ existing_data.merge!(time => reporting_period) {|key, old_val, new_val|
30
+ old_val.merge_metrics!(new_val.metrics).merge_slow_transactions!(new_val.slow_transactions)
31
+ }
63
32
  end
64
- rescue
65
- ScoutApm::Agent.instance.logger.debug $!.message
66
- ScoutApm::Agent.instance.logger.debug $!.backtrace
67
33
  end
68
34
 
69
- # Data is stored under timestamp-keys, aligned to the beginning of the
70
- # current minute
71
- def slot
72
- t = Time.now
73
- t -= t.sec
74
- t.to_i
75
- end
35
+ REPORTING_INTERVAL = 60 # seconds
36
+ MAX_INTERVALS = 5
76
37
 
77
- def log_deposited_metrics(new_metrics)
78
- request_count = new_metrics.
79
- to_a.
80
- select { |meta, stats| meta.metric_name =~ /\AController/ }.
81
- map { |meta, stats| stats.call_count }.
82
- inject(0) { |total, i| total + i }
38
+ # Returns an array of ReportingPeriod objects that are ready to be pushed to the server
39
+ def periods_ready_for_delivery
40
+ ready_for_delivery = []
83
41
 
84
- ScoutApm::Agent.instance.logger.debug "Depositing #{request_count} requests into #{Utils::Time.to_s(slot)} slot."
85
- end
42
+ file.read_and_write do |existing_data|
43
+ existing_data ||= {}
44
+ ready_for_delivery = existing_data.select {|time, rp| should_send?(rp) } # Select off the values we want
45
+
46
+ # Rewrite anything not plucked out back to the file
47
+ existing_data.reject {|k, v| ready_for_delivery.keys.include?(k) }
48
+ end
86
49
 
87
- def log_deposited_slow_transactions(new_slow_transactions)
88
- ScoutApm::Agent.instance.logger.debug "Depositing #{new_slow_transactions.size} slow transactions into #{Utils::Time.to_s(slot)} slot."
50
+ return ready_for_delivery.values
89
51
  end
90
52
 
91
- def log_saved_data(old_data,new_metrics)
92
- ScoutApm::Agent.instance.logger.debug "Saving the following #{old_data.size} time slots locally:"
93
- old_data.each do |k,v|
94
- controller_count = 0
95
- new_metrics.each do |meta,stats|
96
- if meta.metric_name =~ /\AController/
97
- controller_count += stats.call_count
98
- end
99
- end
100
- ScoutApm::Agent.instance.logger.debug "#{Utils::Time.to_s(k)} => #{controller_count} requests and #{v[:slow_transactions].size} slow transactions"
101
- end
53
+ # We just want to send anything older than X
54
+ def should_send?(reporting_period)
55
+ reporting_period.timestamp.age_in_seconds > REPORTING_INTERVAL
102
56
  end
103
57
  end
104
58
  end
@@ -42,8 +42,9 @@ module ScoutApm
42
42
  "Change the path by setting the `data_file` key in scout_apm.yml"
43
43
  )
44
44
  ScoutApm::Agent.instance.logger.debug(e.backtrace.join("\n\t"))
45
+
45
46
  # ensure the in-memory metric hash is cleared so data doesn't continue to accumulate.
46
- ScoutApm::Agent.instance.store.metric_hash = {}
47
+ # ScoutApm::Agent.instance.store.metric_hash = {}
47
48
  end
48
49
 
49
50
  def get_data(f)
@@ -0,0 +1,115 @@
1
+ module ScoutApm
2
+ class Layer
3
+ # Type: a general name for the kind of thing being tracked.
4
+ # Examples: "Middleware", "ActiveRecord", "Controller", "View"
5
+ #
6
+ attr_reader :type
7
+
8
+ # Name: a more specific name of this single item
9
+ # Examples: "Rack::Cache", "User#find", "users/index", "users/index.html.erb"
10
+ attr_reader :name
11
+
12
+ # An array of children layers, in call order.
13
+ # For instance, if we are in a middleware, there will likely be only a single
14
+ # child, which is another middleware. In a Controller, we may have a handful
15
+ # of children: [ActiveRecord, ActiveRecord, View, HTTP Call].
16
+ #
17
+ # This useful to get actual time spent in this layer vs. children time
18
+ attr_reader :children
19
+
20
+ # Time objects recording the start & stop times of this layer
21
+ attr_reader :start_time, :stop_time
22
+
23
+ # The description of this layer. Will contain additional details specific to the type of layer.
24
+ # For an ActiveRecord metric, it will contain the SQL run
25
+ # For an outoing HTTP call, it will contain the remote URL accessed
26
+ # Leave blank if there is nothing to note
27
+ attr_reader :desc
28
+
29
+ # If this layer took longer than a fixed amount of time, store the
30
+ # backtrace of where it occurred.
31
+ attr_reader :backtrace
32
+
33
+
34
+ def initialize(type, name, start_time = Time.now)
35
+ @type = type
36
+ @name = name
37
+ @start_time = start_time
38
+ @children = [] # In order of calls
39
+ @desc = nil
40
+ end
41
+
42
+ def add_child(child)
43
+ @children << child
44
+ end
45
+
46
+ def record_stop_time!(stop_time = Time.now)
47
+ @stop_time = stop_time
48
+ end
49
+
50
+ def desc=(desc)
51
+ @desc = desc
52
+ end
53
+
54
+ def subscopable!
55
+ @subscopable = true
56
+ end
57
+
58
+ def subscopable?
59
+ @subscopable
60
+ end
61
+
62
+ # This is the old style name. This function is used for now, but should be
63
+ # removed, and the new type & name split should be enforced through the
64
+ # app.
65
+ def legacy_metric_name
66
+ "#{type}/#{name}"
67
+ end
68
+
69
+ def store_backtrace(bt)
70
+ @backtrace = bt
71
+ end
72
+
73
+ ######################################
74
+ # Debugging Helpers
75
+ ######################################
76
+
77
+ # May not be safe to call in every rails app, relies on Time#iso8601
78
+ def to_s
79
+ name_clause = "#{type}/#{name}"
80
+
81
+ total_string = total_call_time == 0 ? nil : "Total: #{total_call_time}"
82
+ self_string = total_exclusive_time == 0 ? nil : "Self: #{total_exclusive_time}"
83
+ timing_string = [total_string, self_string].compact.join(", ")
84
+
85
+ time_clause = "(Start: #{start_time.iso8601} / Stop: #{stop_time.try(:iso8601)} [#{timing_string}])"
86
+ desc_clause = "Description: #{desc.inspect}"
87
+ children_clause = "Children: #{children.length}"
88
+
89
+ "<Layer: #{name_clause} #{time_clause} #{desc_clause} #{children_clause}>"
90
+ end
91
+
92
+ ######################################
93
+ # Time Calculations
94
+ ######################################
95
+
96
+ def total_call_time
97
+ if stop_time
98
+ stop_time - start_time
99
+ else
100
+ # Shouldn't have called this yet. Return 0
101
+ 0
102
+ end
103
+ end
104
+
105
+ def total_exclusive_time
106
+ total_call_time - child_time
107
+ end
108
+
109
+ def child_time
110
+ children.
111
+ map { |child| child.total_call_time }.
112
+ inject(0) { |sum, time| sum + time }
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,196 @@
1
+ module ScoutApm
2
+ class LayerConverterBase
3
+ attr_reader :walker
4
+ attr_reader :request
5
+ attr_reader :root_layer
6
+
7
+ def initialize(request)
8
+ @request = request
9
+ @root_layer = request.root_layer
10
+ @walker = LayerDepthFirstWalker.new(root_layer)
11
+ end
12
+
13
+ # Scope is determined by the first Controller we hit. Most of the time
14
+ # there will only be 1 anyway. But if you have a controller that calls
15
+ # another controller method, we may pick that up:
16
+ # def update
17
+ # show
18
+ # render :update
19
+ # end
20
+ def scope_layer
21
+ @scope_layer ||= walker.walk do |layer|
22
+ if layer.type == "Controller"
23
+ break layer
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ # Take a TrackedRequest and turn it into a hash of:
31
+ # MetricMeta => MetricStats
32
+ # This will do some merging of metrics as duplicate calls are seen.
33
+ class LayerMetricConverter < LayerConverterBase
34
+ def call
35
+ scope = scope_layer
36
+
37
+ # TODO: Track requests that never reach a Controller (for example, when
38
+ # Middleware decides to return rather than passing onward)
39
+ return {} unless scope
40
+
41
+ create_metrics
42
+ end
43
+
44
+ # Full metrics from this request. These get aggregated in Store for the
45
+ # overview metrics, or stored permanently in a SlowTransaction
46
+ # Some merging of metrics will happen here, so if a request calls the same
47
+ # ActiveRecord or View repeatedly, it'll get merged.
48
+ def create_metrics
49
+ metric_hash = Hash.new
50
+
51
+ walker.walk do |layer|
52
+ meta_options = if layer == scope_layer # We don't scope the controller under itself
53
+ {}
54
+ else
55
+ {:scope => scope_layer.legacy_metric_name}
56
+ end
57
+
58
+ meta_options.merge!(:desc => layer.desc) if layer.desc
59
+
60
+ meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
61
+ meta.extra.merge!(:backtrace => layer.backtrace) if layer.backtrace
62
+ metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
63
+
64
+ stat = metric_hash[meta]
65
+ stat.update!(layer.total_call_time, layer.total_exclusive_time)
66
+ end
67
+
68
+ metric_hash
69
+ end
70
+ end
71
+
72
+ class LayerErrorConverter < LayerConverterBase
73
+ def call
74
+ scope = scope_layer
75
+
76
+ # Should we mark a request as errored out if a middleware raises?
77
+ # How does that interact w/ a tool like Sentry or Honeybadger?
78
+ return {} unless scope
79
+ return {} unless request.error?
80
+
81
+ meta = MetricMeta.new("Errors/#{scope.legacy_metric_name}", {})
82
+ stat = MetricStats.new
83
+ stat.update!(1)
84
+
85
+ { meta => stat }
86
+ end
87
+ end
88
+
89
+ # Take a TrackedRequest and turn it into either nil, or a SlowTransaction record
90
+ class LayerSlowTransactionConverter < LayerConverterBase
91
+ SLOW_REQUEST_TIME_THRESHOLD = 2 # seconds
92
+
93
+ def call
94
+ return nil unless should_capture_slow_request?
95
+
96
+ scope = scope_layer
97
+ return nil unless scope
98
+
99
+ uri = request.annotations[:uri] || ""
100
+ SlowTransaction.new(uri,
101
+ scope.legacy_metric_name,
102
+ root_layer.total_call_time,
103
+ create_metrics,
104
+ request.context,
105
+ root_layer.stop_time,
106
+ request.stackprof)
107
+ end
108
+
109
+ # Full metrics from this request. These get aggregated in Store for the
110
+ # overview metrics, or stored permanently in a SlowTransaction
111
+ # Some merging of metrics will happen here, so if a request calls the same
112
+ # ActiveRecord or View repeatedly, it'll get merged.
113
+ def create_metrics
114
+ metric_hash = Hash.new
115
+
116
+ subscope_layers = []
117
+
118
+ walker.before do |layer|
119
+ if layer.subscopable?
120
+ subscope_layers.push(layer)
121
+ end
122
+ end
123
+
124
+ walker.after do |layer|
125
+ if layer.subscopable?
126
+ subscope_layers.pop
127
+ end
128
+ end
129
+
130
+ walker.walk do |layer|
131
+ meta_options = if subscope_layers.first && layer != subscope_layers.first # Don't scope under ourself.
132
+ subscope_name = subscope_layers.first.legacy_metric_name
133
+ {:scope => subscope_name}
134
+ elsif layer == scope_layer # We don't scope the controller under itself
135
+ {}
136
+ else
137
+ {:scope => scope_layer.legacy_metric_name}
138
+ end
139
+
140
+ meta_options.merge!(:desc => layer.desc) if layer.desc
141
+
142
+ meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
143
+ meta.extra.merge!(:backtrace => layer.backtrace) if layer.backtrace
144
+ metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
145
+
146
+ stat = metric_hash[meta]
147
+ stat.update!(layer.total_call_time, layer.total_exclusive_time)
148
+ end
149
+
150
+ metric_hash
151
+ end
152
+
153
+ def should_capture_slow_request?
154
+ root_layer.total_call_time > SLOW_REQUEST_TIME_THRESHOLD
155
+ end
156
+ end
157
+
158
+ class LayerDepthFirstWalker
159
+ attr_reader :root_layer
160
+
161
+ def initialize(root_layer)
162
+ @root_layer = root_layer
163
+ end
164
+
165
+ def before(&block)
166
+ @before_block = block
167
+ end
168
+
169
+ def after(&block)
170
+ @after_block = block
171
+ end
172
+
173
+ def walk(layer=root_layer, &block)
174
+ layer.children.each do |child|
175
+ @before_block.call(child) if @before_block
176
+ yield child
177
+ walk(child, &block)
178
+ @after_block.call(child) if @after_block
179
+ end
180
+ nil
181
+ end
182
+
183
+ # Do this w/o using recursion, since it's prone to stack overflows
184
+ # Takes a block to run over each layer
185
+ # def walk
186
+ # layer_stack = [root_layer]
187
+
188
+ # while layer_stack.any?
189
+ # current_layer = layer_stack.pop
190
+ # current_layer.children.reverse.each { |child| layer_stack.push(child) }
191
+
192
+ # yield current_layer
193
+ # end
194
+ # end
195
+ end
196
+ end
@@ -1,11 +1,12 @@
1
1
  # Contains the meta information associated with a metric. Used to lookup Metrics in to Store's metric_hash.
2
- class ScoutApm::MetricMeta
2
+ module ScoutApm
3
+ class MetricMeta
3
4
  include ScoutApm::BucketNameSplitter
4
5
 
5
6
  def initialize(metric_name, options = {})
6
7
  @metric_name = metric_name
7
8
  @metric_id = nil
8
- @scope = Thread::current[:scout_apm_sub_scope] || Thread::current[:scout_apm_scope_name]
9
+ @scope = options[:scope]
9
10
  @desc = options[:desc]
10
11
  @extra = {}
11
12
  end
@@ -14,6 +15,20 @@ class ScoutApm::MetricMeta
14
15
  attr_accessor :client_id
15
16
  attr_accessor :desc, :extra
16
17
 
18
+ # Unsure if type or bucket is a better name.
19
+ def type
20
+ bucket
21
+ end
22
+
23
+ # A key metric is the "core" of a request - either the Rails controller reached, or the background Job executed
24
+ def key_metric?
25
+ self.class.key_metric?(metric_name)
26
+ end
27
+
28
+ def self.key_metric?(metric_name)
29
+ !!(metric_name =~ /\A(Controller|Job)\//)
30
+ end
31
+
17
32
  # To avoid conflicts with different JSON libaries
18
33
  def to_json(*a)
19
34
  %Q[{"metric_id":#{metric_id || 'null'},"metric_name":#{metric_name.to_json},"scope":#{scope.to_json || 'null'}}]
@@ -31,7 +46,11 @@ class ScoutApm::MetricMeta
31
46
  end
32
47
 
33
48
  def eql?(o)
34
- self.class == o.class && metric_name.downcase.eql?(o.metric_name.downcase) && scope == o.scope && client_id == o.client_id && desc == o.desc
49
+ self.class == o.class &&
50
+ metric_name.downcase == o.metric_name.downcase &&
51
+ scope == o.scope &&
52
+ client_id == o.client_id &&
53
+ desc == o.desc
35
54
  end
36
55
 
37
56
  def as_json
@@ -39,4 +58,5 @@ class ScoutApm::MetricMeta
39
58
  # query, stack_trace
40
59
  ScoutApm::AttributeArranger.call(self, json_attributes)
41
60
  end
42
- end # class MetricMeta
61
+ end
62
+ end