class ScoutApm::LayerConverters::ConverterBase

Constants

MAX_METRICS

To prevent huge traces from being generated, we should stop collecting detailed metrics as we go beyond some reasonably large count.

We should still add up the /all aggregates.

Attributes

context[R]
layer_finder[R]
request[R]
root_layer[R]

Public Class Methods

new(context, request, layer_finder, store=nil) click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 10
def initialize(context, request, layer_finder, store=nil)
  @context = context
  @request = request
  @layer_finder = layer_finder
  @store = store

  @root_layer = request.root_layer
  @backtraces = []
  @limited = false
end

Public Instance Methods

attach_backtraces(metric_hash) click to toggle source

Call this after you finish walking the layers, and want to take the set-aside backtraces and place them into the metas they match

# File lib/scout_apm/layer_converters/converter_base.rb, line 86
def attach_backtraces(metric_hash)
  @backtraces.each do |meta_with_backtrace|
    metric_hash.keys.find { |k| k == meta_with_backtrace }.backtrace = meta_with_backtrace.backtrace
  end
  metric_hash
end
limited?() click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 113
def limited?
  !! @limited
end
make_meta_options(layer) click to toggle source

When we make MetricMeta records, we need to determine a few things from layer.

# File lib/scout_apm/layer_converters/converter_base.rb, line 122
def make_meta_options(layer)
  scope_hash = make_meta_options_scope(layer)
  desc_hash = make_meta_options_desc_hash(layer)

  scope_hash.merge(desc_hash)
end
make_meta_options_desc_hash(layer, max_desc_length=32768) click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 148
def make_meta_options_desc_hash(layer, max_desc_length=32768)
  if layer.desc
    desc_s = layer.desc.to_s
    trimmed_desc = desc_s[0 .. max_desc_length]
    {:desc => trimmed_desc}
  else
    {}
  end
end
make_meta_options_scope(layer) click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 129
def make_meta_options_scope(layer)
  # This layer is scoped under another thing. Typically that means this is a layer under a view.
  # Like: Controller -> View/users/show -> ActiveRecord/user/find
  #   in that example, the scope is the View/users/show
  if subscoped?(layer)
    {:scope => subscope_name}

  # We don't scope the controller under itself
  elsif layer == scope_layer
    {}

  # This layer is a top level metric ("ActiveRecord", or "HTTP" or
  # whatever, directly under the controller), so scope to the
  # Controller
  else
    {:scope => scope_layer.legacy_metric_name}
  end
end
over_metric_limit?(metric_hash) click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 105
def over_metric_limit?(metric_hash)
  if metric_hash.size > MAX_METRICS
    @limited = true
  else
    false
  end
end
register_hooks(walker) click to toggle source

Subscoping

Keep a list of subscopes, but only ever use the front one. The rest get pushed/popped in cases when we have many levels of subscopable layers. This lets us push/pop without otherwise keeping track very closely.

# File lib/scout_apm/layer_converters/converter_base.rb, line 32
def register_hooks(walker)
  @subscope_layers = []

  walker.before do |layer|
    if layer.subscopable?
      @subscope_layers.push(layer)
    end
  end

  walker.after do |layer|
    if layer.subscopable?
      @subscope_layers.pop
    end
  end
end
scope_layer() click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 21
def scope_layer
  layer_finder.scope
end
skip_layer?(layer) click to toggle source

Sometimes we start capturing a layer without knowing if we really want to make an entry for it. See ActiveRecord instrumentation for an example. We start capturing before we know if a query is cached or not, and want to skip any cached queries.

# File lib/scout_apm/layer_converters/converter_base.rb, line 215
def skip_layer?(layer)
  return false if layer.annotations.nil?
  return true  if layer.annotations[:ignorable]
end
store_aggregate_metric(layer, metric_hash, allocation_metric_hash) click to toggle source

Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)

# File lib/scout_apm/layer_converters/converter_base.rb, line 192
def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
    meta = MetricMeta.new("#{layer.type}/all")

    metric_hash[meta] ||= MetricStats.new(false)
    allocation_metric_hash[meta] ||= MetricStats.new(false)

    # timing
    stat = metric_hash[meta]
    stat.update!(layer.total_call_time, layer.total_exclusive_time)

    # allocations
    stat = allocation_metric_hash[meta]
    stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
end
store_backtrace(layer, meta) click to toggle source

Call this as you are processing each layer. It will store off backtraces

# File lib/scout_apm/layer_converters/converter_base.rb, line 74
def store_backtrace(layer, meta)
  return unless layer.backtrace

  bt = ScoutApm::Utils::BacktraceParser.new(layer.backtrace).call
  if bt.any?
    meta.backtrace = bt
    @backtraces << meta
  end
end
store_specific_metric(layer, metric_hash, allocation_metric_hash) click to toggle source

This is the detailed metric - type, name, backtrace, annotations, etc.

# File lib/scout_apm/layer_converters/converter_base.rb, line 164
def store_specific_metric(layer, metric_hash, allocation_metric_hash)
  return false if over_metric_limit?(metric_hash)

  meta_options = make_meta_options(layer)

  meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
  meta.extra.merge!(layer.annotations) if layer.annotations

  store_backtrace(layer, meta)

  metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
  allocation_metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))

  # timing
  stat = metric_hash[meta]
  stat.update!(layer.total_call_time, layer.total_exclusive_time)

  # allocations
  stat = allocation_metric_hash[meta]
  stat.update!(layer.total_allocations, layer.total_exclusive_allocations)

  if LimitedLayer === layer
    metric_hash[meta].call_count = layer.count
    allocation_metric_hash[meta].call_count = layer.count
  end
end
subscope_name() click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 52
def subscope_name
  @subscope_layers.first.legacy_metric_name
end
subscoped?(layer) click to toggle source
# File lib/scout_apm/layer_converters/converter_base.rb, line 48
def subscoped?(layer)
  @subscope_layers.first && layer != @subscope_layers.first # Don't scope under ourself.
end