Class: RubyMethodTracer::CallTree

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_method_tracer/call_tree.rb

Overview

CallTree manages the hierarchical structure of method calls, tracking parent-child relationships and call depths.

It uses a per-thread stack to manage nested calls and builds a tree structure showing the complete call hierarchy.

Note: @calls and @root_calls are shared across threads and protected by a Mutex. The call stack is stored in thread-local storage so that concurrent callers each maintain their own independent call depth.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCallTree

Returns a new instance of CallTree.



16
17
18
19
20
21
# File 'lib/ruby_method_tracer/call_tree.rb', line 16

def initialize
  @calls = []           # All recorded calls (flat list, shared)
  @root_calls = []      # Top-level calls (depth 0, shared)
  @lock = Mutex.new     # Protects @calls and @root_calls
  @thread_key = :"__ruby_method_tracer_call_stack_#{object_id}" # per-instance thread-local key
end

Instance Attribute Details

#callsObject (readonly)

Returns the value of attribute calls.



14
15
16
# File 'lib/ruby_method_tracer/call_tree.rb', line 14

def calls
  @calls
end

#root_callsObject (readonly)

Returns the value of attribute root_calls.



14
15
16
# File 'lib/ruby_method_tracer/call_tree.rb', line 14

def root_calls
  @root_calls
end

Instance Method Details

#call_hierarchyArray<Hash>

Get call hierarchy as nested structure

Returns:

  • (Array<Hash>)

    Root calls with nested children



79
80
81
# File 'lib/ruby_method_tracer/call_tree.rb', line 79

def call_hierarchy
  @lock.synchronize { @root_calls.dup }
end

#clearObject

Clear all recorded calls and reset state

Note: only the current thread’s call stack is cleared; other threads that are mid-trace retain their stacks.



108
109
110
111
112
113
114
# File 'lib/ruby_method_tracer/call_tree.rb', line 108

def clear
  @lock.synchronize do
    @calls.clear
    @root_calls.clear
  end
  thread_call_stack.clear
end

#current_depthInteger

Get the current call depth for the calling thread

Returns:

  • (Integer)

    The current nesting level



72
73
74
# File 'lib/ruby_method_tracer/call_tree.rb', line 72

def current_depth
  thread_call_stack.size
end

#empty?Boolean

Check if the current thread has no active calls

Returns:

  • (Boolean)


119
120
121
# File 'lib/ruby_method_tracer/call_tree.rb', line 119

def empty?
  thread_call_stack.empty?
end

#end_call(status = :success, error = nil) ⇒ Hash?

End tracking a method call

Parameters:

  • status (Symbol) (defaults to: :success)

    :success or :error

  • error (Exception, nil) (defaults to: nil)

    The exception if status is :error

Returns:

  • (Hash, nil)

    The completed call record



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/ruby_method_tracer/call_tree.rb', line 56

def end_call(status = :success, error = nil)
  stack = thread_call_stack
  return nil if stack.empty?

  call_record = stack.pop
  call_record[:status] = status
  call_record[:error] = error
  call_record[:execution_time] = monotonic_time - call_record[:start_time]

  @lock.synchronize { @calls << call_record }
  call_record
end

#start_call(method_name) ⇒ Hash

Start tracking a method call

Parameters:

  • method_name (String)

    The name of the method being called

Returns:

  • (Hash)

    The call record that was pushed to the stack



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/ruby_method_tracer/call_tree.rb', line 27

def start_call(method_name)
  stack = thread_call_stack

  call_record = {
    method_name: method_name,
    start_time: monotonic_time,
    depth: stack.size,
    children: [],
    status: nil,
    error: nil,
    execution_time: nil,
    timestamp: Time.now
  }

  # Add as child to parent if we're nested
  stack.last[:children] << call_record if stack.any?

  # Track root-level calls (lock required since @root_calls is shared)
  @lock.synchronize { @root_calls << call_record } if stack.empty?

  stack.push(call_record)
  call_record
end

#statisticsHash

Calculate statistics from recorded calls

Returns:

  • (Hash)

    Statistics including total calls, time, slowest methods, etc.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/ruby_method_tracer/call_tree.rb', line 86

def statistics
  @lock.synchronize do
    return default_statistics if @calls.empty?

    method_stats = calculate_method_stats

    {
      total_calls: @calls.size,
      total_time: @calls.sum { |c| c[:execution_time] },
      unique_methods: method_stats.size,
      slowest_methods: slowest_methods(method_stats),
      most_called_methods: most_called_methods(method_stats),
      average_time_per_method: average_times(method_stats),
      max_depth: @calls.map { |c| c[:depth] }.max || 0
    }
  end
end