debug
is Ruby's new default debugger included in Ruby 3.1. This new debugger has replaced byebug
in Rails 7. Not only does debug
provide us with a wide range of functionality, but it also provides some advanced features.
In this article, we will explore and understand a few advanced features of debug
.
1. Seamless integration with VSCode
Below steps can be followed to integrate debug
gem in VSCode.
- Install extension VSCode rdbg Ruby Debugger - Visual Studio Marketplace in your VSCode.
- Open the file you want to debug in the VSCode.
- Register the breakpoint by clicking on the line you want to set the breakpoint and press the
F9
key. - Start the debugging by pressing the
F5
key or by selectingStart debugging
in theRun
menu.
2. Postmortem debugging to debug the dead Ruby process
When you debug a program and an exception is raised, the debugger will exit immediately. To avoid this, we can make use of the postmortem feature.
Let's say we have a simple program to calculate the percentage.
# computation.rb
def percentage(numerator, denominator)
(numerator / denominator) * 100.0
end
# call the above function by passing the denominator as 0
percentage(10, 0)
When you run the program with the debugger and continue the debugging process, the debugger will suspend immediately throwing ZeroDivisionError
.
# start the debugging using `rdbg` command
19:22:35:~/Users/Examples >% rdbg computation.rb
[1, 6] in computation.rb
=> 1| def percentage(numerator, denominator)
2| (numerator / denominator) * 100.0
3| end
4|
5|
6| percentage(10, 0)
(rdbg)
# continue the debugging process
(rdbg) continue
Traceback (most recent call last):
2: from computation.rb:6:in `<main>'
1: from computation.rb:2:in `percentage'
computation.rb:2:in `/': divided by 0 (ZeroDivisionError)
# debugging process gets suspended
19:54:15:~/Users/Examples
To remain in the debugging mode even after the exception is raised, we can use the postmortem feature. Run the program with the debugger, then set the configuration with the command config set postmortem true
.
Start the program in debugging mode.
# start the debugging using `rdbg` command
20:02:38:~/Users/Examples >% rdbg computation.rb
[1, 6] in computation.rb
=> 1| def def percentage(numerator, denominator)
2| (numerator / denominator) * 100.0
3| end
4|
5|
6| percentage(10, 0)
(rdbg)
Enable the postmortem feature.
# command to enable postmortem
(rdbg) config set postmortem true
postmortem = true
Continue the program and the exception will be raised, but the debugger won't be suspended.
# continue the debugging process
(rdbg) continue # continue command
Enter postmortem mode with #<ZeroDivisionError: divided by 0>
computation.rb:2:in `/'
computation.rb:2:in `percentage'
computation.rb:6:in `<main>'
# debugger is still active
(rdbg:postmortem)
Backtrace the issue with bt
command.
# command to backtrace
(rdbg:postmortem) bt
=>#0 [C] Integer#/ at computation.rb:2
#1 Object#percentage(numerator=10, denominator=0) at computation.rb:2
#2 <main> at computation.rb:6
(rdbg:postmortem)
3. Supports record & replay debugging
This feature allows recording the execution information. Using this we can go back to the execution again by using step back
command. This will help us to check the last state of the program before the breakpoint.
Let's take a simple example that calculates simple interest. Add a breakpoint at the start of the function.
# computation.rb
def calculate_simple_interest
debugger
puts 'Enter investment:'
investment = Integer(gets.chomp)
puts 'Enter interest rate:'
interest_rate = Integer(gets.chomp)
puts 'Enter number of years:'
time = Integer(gets.chomp)
interest = (investment * interest_rate * time) / 100
puts "Interest amount is: #{interest}"
end
# call to the above function
calculate_simple_interest
Run the program and start the debugging.
# start the debugging using `rdbg` command
18:09:24:/Users/Examples >% rdbg computation.rb
[1, 10] in computation.rb
=> 1| def calculate_simple_interest
2| debugger
3|
4| puts 'Enter investment:'
5| investment = Integer(gets.chomp)
6|
7| puts 'Enter interest rate:'
8| interest_rate = Integer(gets.chomp)
9|
10| puts 'Enter number of years:'
=>#0 <main> at computation.rb:1
(rdbg)
To start the recording, execute the command record on
.
# command to start the record
(rdbg) record on
Recorder for #<Thread:0x00007fef0c85fb58 run>: on (0 records)
(rdbg)
Using next
command go to the end of the program and check the values of all the variables using info
command. You will be able to see the values of all four variables.
# command to move the breakpoint to the new line
(rdbg) next
[9, 17] in computation.rb
9|
10| puts 'Enter number of years:'
11| time = Integer(gets.chomp)
12|
13| interest = (investment * interest_rate * time) / 100
=> 14| puts "Interest amount is: #{interest}"
15| end
16|
# command to list the values of the variables
(rdbg) info
%self = main
investment = 12
interest_rate = 12
time = 12
interest = 17
(rdbg)
Now, to go back to the last state, enter the command step back
. Now you will only be able to see values of investment
, interest_rate
& time
.
# command to move to the previous state
(rdbg) step back
[replay] [8, 17] in computation.rb
[replay] 8| interest_rate = Integer(gets.chomp)
[replay] 9|
[replay] 10| puts 'Enter number of years:'
[replay] 11| time = Integer(gets.chomp)
[replay] 12|
[replay] => 13| interest = (investment * interest_rate * time) / 100
[replay] 14| puts "Interest amount is: #{interest}"
[replay] 15| end
[replay] 16|
[replay] 17| calculate_simple_interest
# command to list the values of the variables
(rdbg) info
[replay] %self = main
[replay] investment = 12
[replay] interest_rate = 12
[replay] time = 12
[replay] interest = nil
(rdbg)
You can move back again to the previous state by entering the command step back
. Now you will only be able to see the value of investment
& interest_rate
.
# command to move to the previous state
(rdbg) step back
[replay] [6, 15] in computation.rb
[replay] 6|
[replay] 7| puts 'Enter interest rate:'
[replay] 8| interest_rate = Integer(gets.chomp)
[replay] 9|
[replay] 10| puts 'Enter number of years:'
[replay] => 11| time = Integer(gets.chomp)
[replay] 12|
[replay] 13| interest = (investment * interest_rate * time) / 100
[replay] 14| puts "Interest amount is: #{interest}"
[replay] 15| end
# command to list the values of the variables
(rdbg) info
[replay] %self = main
[replay] investment = 12
[replay] interest_rate = 12
[replay] time = nil
[replay] interest = nil
(rdbg)
Comparing the performance of the debug
gem with the other debuggers
We already have existing debuggers like lib/debug.rb
, byebug
, debase
, etc. The reason to use the new debugger is its performance.
Let's have a simple function to find the Fibonacci series.
def fibonacci(n)
if n < 0
raise # breakpoint
elsif n < 2
n
else
fibonacci(n - 1) + fibonacci(n - 2)
end
end
# running above method in the irb
irb(main):010:0> require 'benchmark'
irb(main):011:0> Benchmark.bm { |x| x.report{ fibonacci(35) } }
Here are the benchmark scores in seconds
for various debuggers for the above example.
Without Breakpoint (sec) | With Breakpoint (sec) | |
---|---|---|
rdbg(debug.gem) | 0.92 | 0.92 |
Ruby < 3.1 | 0.93 | N/A |
RubyMine | 0.97 | 22.6 |
Byebug | 1.23 | 75.15 |
old lib/debug.rb | 221.88 | 285.99 |
Looking at the comparison in the above benchmarks, we can clearly notice that the new debugger is much faster than the rest of the debuggers.