Skip to content

Instantly share code, notes, and snippets.

@billthompson
Last active August 1, 2025 20:01
Show Gist options
  • Select an option

  • Save billthompson/9ed7aa108ea1f3c773e37f4cfab74410 to your computer and use it in GitHub Desktop.

Select an option

Save billthompson/9ed7aa108ea1f3c773e37f4cfab74410 to your computer and use it in GitHub Desktop.
Debugging the Unknown - A script to help bring clarity to complex domains.

Debugging the Unknown

When exploring a new Ruby codebase, I have a pattern of using caller to understand the callstack and build out a visual representation - E.G:

.
└── ApplicationController/
    └── UsersController#create/
        └── UsersService.create/
            ├── UsersService::UsersCreate#call/
            │   ├── UsersService::UsersValidator#validate
            │   └── MetricsService.increment_new_users
            └── UsersService.build_create_response

Getting Started

  • Put a breakpoint somewhere.
  • Get the current callstack via caller.
  • Collect callstack via: data = callee.reject { |l| l.include?("/gems/") }
  • Dump the callstack via: puts Base64.encode64(Marshal.dump(data))
  • Paste the data into a file next to the script with the name: callee-to-tree.txt.
  • Execute the script: ./callee-to-tree.rb.
  • Format the indentation such that invoked methods are nested 1 level deeper than the callee.
  • Paste the output to an ASCII tree maker like https://tree.nathanfriend.com/.

Tips

  • Keep a plain version of your output so that you can add to it easily as you explore more parts of the codebase and regenerate a new tree.
  • Keep a file per flow (Users Create, Users Delete, etc).
#!/usr/bin/env ruby
require 'base64'
class CalleeToTree
def build_stack
locs = load_callers!
locs.reject! { |p| p.include?('/gems/') }
locs.reverse!
indent = 0
tree = locs.each_with_object([]) do |l, memo|
fq_path, fq_loc = l.split(" ")
path, line_number, _extra = fq_path.split(":")
path.gsub!('/home/bt/', '')
loc = fq_loc.gsub(/[`'"<>]/, '')
if loc.include?('block')
indent += 1
elsif indent > 0
indent -= 1
end
padding = ' ' * indent
memo << "#{padding}#{loc} - #{path}:#{line_number}"
end
tree
end
def load_callers!
contents = File.read("callstack.txt")
::Marshal.load(Base64.decode64(contents))
end
def print(data)
puts data
end
class << self
def print_callers
util = self.new
callers = util.build_stack
util.print(callers)
end
end
end
if __FILE__ == $PROGRAM_NAME
puts CalleeToTree.print_callers
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment