Class: ScoutApm::CallSet

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

Overview

Encapsulates our logic to determine when a backtrace should be collected.

Constant Summary collapse

N_PLUS_ONE_MAGIC_NUMBER =

Fetch backtraces on this number of calls to a layer. The caller data is only collected on this call to limit overhead.

5
N_PLUS_ONE_TIME_THRESHOLD =

Minimum time in seconds before we start performing any work. This is to prevent doing a lot of work on already fast calls.

150/1000.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCallSet

Returns a new instance of CallSet.



10
11
12
13
14
15
16
17
# File 'lib/scout_apm/call_set.rb', line 10

def initialize
  @items = [] # An array of Layer descriptions that are associated w/a single Layer name (ex: User/find). Note this may contain nil items.
  @grouped_items = Hash.new { |h, k| h[k] = [] } # items groups by their normalized name since multiple layers could have the same layer name.
  @call_count = 0
  @captured = false # cached for performance
  @start_time = Time.now
  @past_start_time = false # cached for performance
end

Instance Attribute Details

#call_countObject (readonly)

Returns the value of attribute call_count.



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

def call_count
  @call_count
end

Instance Method Details

#at_magic_number?Boolean

Returns:

  • (Boolean)


45
46
47
# File 'lib/scout_apm/call_set.rb', line 45

def at_magic_number?
  grouped_items[unique_name_for(@items.last)].size >= N_PLUS_ONE_MAGIC_NUMBER
end

#capture_backtrace?Boolean

We’re selective on capturing a backtrace for two reasons:

  • Grouping ActiveRecord calls requires us to sanitize the SQL. This isn’t cheap.

  • Capturing backtraces isn’t cheap.

Returns:

  • (Boolean)


39
40
41
42
43
# File 'lib/scout_apm/call_set.rb', line 39

def capture_backtrace?
  if !@captured && @call_count >= N_PLUS_ONE_MAGIC_NUMBER && past_time_threshold? && at_magic_number?
    @captured = true
  end
end

#grouped_itemsObject



49
50
51
52
53
54
55
# File 'lib/scout_apm/call_set.rb', line 49

def grouped_items
  if @grouped_items.any? 
    @grouped_items
  else
    @grouped_items.merge!(@items.group_by { |item| unique_name_for(item) })
  end
end

#past_time_threshold?Boolean

Limit our workload if time across this set of calls is small.

Returns:

  • (Boolean)


31
32
33
34
# File 'lib/scout_apm/call_set.rb', line 31

def past_time_threshold?
  return true if @past_time_threshold # no need to check again once past
  @past_time_threshold = (Time.now-@start_time) >= N_PLUS_ONE_TIME_THRESHOLD
end

#unique_name_for(item) ⇒ Object

Determine this items’ “hash key”



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

def unique_name_for(item)
  item.to_s
end

#update!(item = nil) ⇒ Object



19
20
21
22
23
24
25
26
27
28
# File 'lib/scout_apm/call_set.rb', line 19

def update!(item = nil)
  if @captured # No need to do any work if we've already captured a backtrace.
    return
  end
  @call_count += 1
  @items << item
  if @grouped_items.any? # lazy grouping as normalizing items can be expensive.
    @grouped_items[unique_name_for(item)] << item
  end
end