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(request) ⇒ ConverterBase

Returns a new instance of ConverterBase.



9
10
11
12
13
14
15
16
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 9

def initialize(request)
  @request = request
  @root_layer = request.root_layer
  @backtraces = []
  @walker = DepthFirstWalker.new(root_layer)

  @limited = false
end

Instance Attribute Details

#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

#walkerObject (readonly)

Returns the value of attribute walker.



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

def walker
  @walker
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



96
97
98
99
100
101
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 96

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

#find_first_layer_of_type(layer_type) ⇒ Object



29
30
31
32
33
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 29

def find_first_layer_of_type(layer_type)
  walker.walk do |layer|
    return layer if layer.type == layer_type
  end
end

#limited?Boolean

Returns:

  • (Boolean)


123
124
125
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 123

def limited?
  !! @limited
end

#make_meta_options(layer) ⇒ Object

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



132
133
134
135
136
137
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 132

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 = 1000) ⇒ Object



158
159
160
161
162
163
164
165
166
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 158

def make_meta_options_desc_hash(layer, max_desc_length=1000)
  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



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 139

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)


115
116
117
118
119
120
121
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 115

def over_metric_limit?(metric_hash)
  if metric_hash.size > MAX_METRICS
    @limited = true
  else
    false
  end
end

#scope_layerObject

Scope is determined by the first Controller we hit. Most of the time there will only be 1 anyway. But if you have a controller that calls another controller method, we may pick that up:

def update
  show
  render :update
end


25
26
27
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 25

def scope_layer
  @scope_layer ||= find_first_layer_of_type("Controller") || find_first_layer_of_type("Job")
end

#setup_subscopable_callbacksObject

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.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 42

def setup_subscopable_callbacks
  @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

#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)


225
226
227
228
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 225

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)



202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 202

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



84
85
86
87
88
89
90
91
92
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 84

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.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 174

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



62
63
64
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 62

def subscope_name
  @subscope_layers.first.legacy_metric_name
end

#subscoped?(layer) ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/scout_apm/layer_converters/converter_base.rb', line 58

def subscoped?(layer)
  @subscope_layers.first && layer != @subscope_layers.first # Don't scope under ourself.
end