Class: ScoutApm::LayerConverters::ConverterBase

Inherits:
Object
  • Object
show all
Defined in:
lib/scout_apm/layer_converters/converter_base.rb

Constant Summary collapse

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.

500

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, request, layer_finder, store = nil) ⇒ ConverterBase

Returns a new instance of ConverterBase.



10
11
12
13
14
15
16
17
18
19
# 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

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



5
6
7
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 5

def context
  @context
end

#layer_finderObject (readonly)

Returns the value of attribute layer_finder.



8
9
10
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 8

def layer_finder
  @layer_finder
end

#requestObject (readonly)

Returns the value of attribute request.



6
7
8
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 6

def request
  @request
end

#root_layerObject (readonly)

Returns the value of attribute root_layer.



7
8
9
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 7

def root_layer
  @root_layer
end

Instance Method Details

#attach_backtraces(metric_hash) ⇒ Object

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



86
87
88
89
90
91
# 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?Boolean

Returns:

  • (Boolean)


113
114
115
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 113

def limited?
  !! @limited
end

#make_meta_options(layer) ⇒ Object

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



122
123
124
125
126
127
# 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) ⇒ Object



148
149
150
151
152
153
154
155
156
# 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) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
108
109
110
111
# 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) ⇒ Object

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.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 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_layerObject



21
22
23
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 21

def scope_layer
  layer_finder.scope
end

#skip_layer?(layer) ⇒ Boolean

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.

Returns:

  • (Boolean)


215
216
217
218
# 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) ⇒ Object

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



192
193
194
195
196
197
198
199
200
201
202
203
204
205
# 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) ⇒ Object

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



74
75
76
77
78
79
80
81
82
# 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) ⇒ Object

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



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# 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_nameObject



52
53
54
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 52

def subscope_name
  @subscope_layers.first.legacy_metric_name
end

#subscoped?(layer) ⇒ Boolean

Returns:

  • (Boolean)


48
49
50
# 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