Class: BuilderApm::Methods::Instrumenter

Inherits:
Object
  • Object
show all
Defined in:
lib/builder_apm/methods/instrumenter.rb

Instance Method Summary collapse

Constructor Details

#initialize(root_path: Rails.root.to_s) ⇒ Instrumenter

Returns a new instance of Instrumenter.



4
5
6
7
8
# File 'lib/builder_apm/methods/instrumenter.rb', line 4

def initialize(root_path: Rails.root.to_s)
  @this_gem_path = File.expand_path("../../..", __dir__)
  @root_path = root_path
  @call_times = {}
end

Instance Method Details

#process_trace_point(tp) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/builder_apm/methods/instrumenter.rb', line 42

def process_trace_point(tp)
  if tp.event == :call || tp.event == :b_call || tp.event == :c_call
    method_id = "#{tp.defined_class}##{tp.method_id}"
    (@call_times[method_id]||= []) << Process.clock_gettime(Process::CLOCK_MONOTONIC)
    caller_info = caller_locations(4,1).first
    calling_file_path = caller_info.absolute_path
    calling_line_number = caller_info.lineno
  
    method_call = { 
      method: method_id, 
      method_line: "#{tp.path.gsub(@root_path, '')}:#{tp.lineno}",
      triggering_line: "#{calling_file_path.gsub(@root_path, '')}:#{calling_line_number}", 
      children: [], 
      start_time: Time.now.to_f * 1000, 
      sql_events: [] 
    }
  
    (Thread.current[:stack] ||= []).push(method_call)
  else
    method_id = "#{tp.defined_class}##{tp.method_id}"
    
    if @call_times.key?(method_id)
      elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @call_times[method_id].pop
      elapsed_time_in_ms = (elapsed_time * 1000).round(3)
      @call_times.delete(method_id)
  
      method_call = (Thread.current[:stack] ||= []).pop
      method_call[:end_time] = Time.now.to_f * 1000
      method_call[:duration] = elapsed_time_in_ms
  
      if Thread.current[:stack]&.any?
        Thread.current[:stack].last[:children].push(method_call) 
      else 
        Thread.current[:stack].push(method_call) 
      end
    end
  end
end

#setup_traceObject



19
20
21
22
23
24
25
26
27
28
# File 'lib/builder_apm/methods/instrumenter.rb', line 19

def setup_trace
  me = self
  TracePoint.new(:call, :return, :end, :raise) do |tp|
    starttime = Time.now.to_f * 1000
    me.process_trace_point(tp) if me.valid_trace_point?(tp)
    duration = (Time.now.to_f * 1000) - starttime
    
    Thread.current[:method_tracing] = (Thread.current[:method_tracing] ||= 0) + duration
  end
end

#startObject



10
11
12
13
# File 'lib/builder_apm/methods/instrumenter.rb', line 10

def start
  @trace = setup_trace
  @trace.enable
end

#stopObject



15
16
17
# File 'lib/builder_apm/methods/instrumenter.rb', line 15

def stop
  @trace.disable unless @trace.nil?
end

#valid_trace_point?(tp) ⇒ Boolean

Returns:

  • (Boolean)


30
31
32
33
34
35
36
37
38
39
40
# File 'lib/builder_apm/methods/instrumenter.rb', line 30

def valid_trace_point?(tp)
  return false unless Thread.current[:request_id]
  return false unless tp.path.start_with?(@root_path)
  # return false if tp.path.start_with?(@this_gem_path) 


  start_controller = Thread.current[:stack]&.first
  return false if start_controller && "#{tp.defined_class}##{tp.method_id}" == start_controller[:method]

  gems_to_track = BuilderApm.configuration.gems_to_track
  gems_to_track.any? { |gem| tp.path.include?(File::SEPARATOR + gem) }
end