🐛

RubyKaigi 2021で発表された新しいdebug.gemを試してみた

2021/09/16に公開1

Leaner Technologiesころちゃん(@corocn) です。

RubyKaigi Takeout 2021 に業務で参加してきました。一緒に参加した同僚が型や RBS 周りの記事を書いてくれるようなので、デバッガ周りの話を振り返っておきます。デバッガは日々お世話になっていて最近関心の高いトピックなので選びました。

The Art of Execution Control for Ruby's Debugger

https://www.youtube.com/watch?v=tfXiL5wDA6Q

https://github.com/ruby/debug

Agenda

  • なぜ新しい debug.gem か?
  • インストールと使い方
  • Record and replay を試す
  • VSCode 拡張と Remote Debugging を試す
  • Rails 7 と Kernel#debugger の話

なぜ新しいdebug.gem?

パフォーマンスを改善したい


発表動画14:00あたりから引用

既存のデバッガの場合、ブレークポイント起因でかなりパフォーマンスが落ちてしまうことを知りました。

新しい debug.gem だとオーバーヘッドなしに実行できるようです。lib/debug.rb と比較すると300倍高速化されているように見えます。物凄いですね。RubyMine と比較しても 20 倍です。

自分の場合、Ruby 歴は実は浅いほうで Rails を書きはじめたときは既に IntelliJ 系 IDE が台頭していたのもあり[1]、現在は RubyMine の統合デバッグツール(debase & ruby-debug-ide)と binding.irb を使い分けるスタイルで開発しています。

以前は binding.pry を使っていましたが、Pryはもう古い、時代はIRB - k0kubun's blog を見て binding.irb を使うようになりました。

最近困っていた問題として、RubyMine でブレークポイントを貼ったときに非常に動作が重く、昨今は Docker を利用して開発しているのも相まって開発体験が悪くて非常にストレスが溜まっていましたが、原因がはっきりしましたね。

個人的には RubyMine のデバッグツールが debug.gem に対応してくると嬉しいです。

Ractorに対応したい

Ractor は Ruby3 で導入された並列処理の仕組みですが、Ractor 間でオブジェクトの共有ができないため、オブジェクトを直接触る既存のデバッガでは Ractor をデバッグできないよ〜というふわっとした理解をしています。

発表内では Ractor Programming は難しいと説明されていましたが、Ractor が難しいのではなく並列プログラミングが全般的に難しいという感想を持ちました。

なお、Ractor 対応は進行中とこのことです。リリースが楽しみですね。

弊社でもプロダクトの一部パフォーマンスが必要とされる箇所で Ractor の採用を検討しており、デバッグ含む gem が対応しはじめると利用の現実味がでてきそうです。

インストール

debug.gem は手元ですぐに試せます。

$ gem install debug

または Gemfile に以下を記述して bundle install でも動きます。

gem "debug"

使い方

デバッグの仕方は 2 種類あります。

  • binding.break または binding.b
  • rdbg コマンド

前者は binding.irb や binding.pry と同様、明示的に差し込むパターンです。プログラムを改変したくない場合は後者の rdbg コマンドを使用します。例えば Rails をデバッグしたい場合は次のように実行します。

$ rdbg --command -- rails server

詳しいコマンドは README にすべて載っているので参考にすると良さそうです。
https://github.com/ruby/debug#debug-command-on-the-debug-console

Record and replay

debug.gem の面白い機能の 1 つを試してみます。デバッグ中に record コマンドを使用してポイントを保存しておくと、実行後に履歴を遡ることができる機能です。

まずは、デバッグコンソールで record on コマンドを利用して機能を有効にします。

次に step コマンドを使用して、実行を 7 行目の puts x まで進めてみましょう。

ここで step back コマンドを使うと、実行状態を遡ることができます。
4 行目まで戻って、変数 a の内容を出力してみましょう。

上書きされる前の a の値が出力されましたね。すごい。

遡ってる最中は"replay"の表示が出るようです。

VSCode + Remote Debugging

debug.rem には Remote Debugging 機能があり、なんと VSCode の拡張も用意されています。今回は Rails を VSCode でデバッグできるか試してみます。

環境は次のとおりです。

  • Ruby 3.0.2
  • Rails 6.1.4.1

Extensions から次の拡張をインストールします。"rdbg" で検索するとすぐ見つかります。

https://marketplace.visualstudio.com/items?itemName=KoichiSasada.vscode-rdbg

デバッガの設定のために .vscode/launch.json を作成します。左ペインの "create a launch json file" をクリックすると、"Ruby(rdbg)" がサジェストされるので、クリックで生成されます。

次のようなファイルが作成されました。

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "rdbg",
            "name": "Debug current file with rdbg",
            "request": "launch",
            "script": "${file}",
            "args": [],
            "askParameters": true
        },
        {
            "type": "rdbg",
            "name": "Attach with rdbg",
            "request": "attach"
        }
    ]
}

Gemfile に debug.gem を追加して bundle install しておきます。

group :development do
  gem 'debug'
end

rdbg 経由で Rails を起動します。

$ rdbg --open --command -- bundle exec rails server
DEBUGGER: can not load newer irb for coloring. Write 'gem "debug" in your Gemfile.
DEBUGGER: Debugger can attach via UNIX domain socket (/Users/corocn/.ruby-debug-sock/ruby-debug-corocn-42873)
DEBUGGER: wait for debugger connection...

デバッガの attach with rdbg を実行して、ソケットを選択します。

DEBUGGER: Debugger can attach via UNIX domain socket (/Users/corocn/.ruby-debug-sock/ruby-debug-corocn-42873)
DEBUGGER: wait for debugger connection...
DEBUGGER: Connected.
DEBUGGER: Debugger can attach via UNIX domain socket (/Users/corocn/.ruby-debug-sock/ruby-debug-corocn-42873)
DEBUGGER: wait for debugger connection...

なぜか DEBUGGER: Connected になってもデバッガを待ち続けてサーバーが起動しない様子。Rails で動かないというIssueを見つけましたが、現象が異なったので新しく Issue を立てました。bootsnap と Spring を外す必要があるようで、色々試行錯誤してみましたがまだ動いていません。

https://github.com/ruby/debug/issues/285

不完全燃焼なので rspec ならいけるんじゃない?と思って試したら無事動きました。

$ rdbg --open --command -- bundle exec rspec spec/requests/health_spec.rb
DEBUGGER: can not load newer irb for coloring. Write 'gem "debug" in your Gemfile.
DEBUGGER: Debugger can attach via UNIX domain socket (/Users/corocn/.ruby-debug-sock/ruby-debug-corocn-49313)
DEBUGGER: wait for debugger connection...
DEBUGGER: Connected.

Randomized with seed 41240

Healths
  GET /health
DEBUGGER:  BP - Line  XXX/app/controllers/health_controller.rb:7 (call) is activated.

ちゃんとブレークできたぞ!

Rails 7の話

ruby/debug に DHH が降臨していましたね。
ruby-jp でも話題になってましたが Rails 7 では debug.gem が標準になるようです。

https://github.com/rails/rails/commit/837b0f957715de2db46f2f81d8ac7dccf58a31bd

これに関連して、デバッグの際に binding.break と同じように使えるインターフェイスとして debugger を用意するという話もあるようです。

https://github.com/ruby/debug/issues/261

要約すると次のような話になります。

  • Rails では byebug が 7 年間デフォルトだった。
  • byebug は debugger で呼び出せるようになっている。
  • binding.break より debugger のほうがより直感的だと思う。
  • debug gem 側で対応しなくても、Rails 側でなんとでもできるので些細な点ではある。

今まで知らずに binding.irb を使っていましたが、byebug は Kernel#debugger を生やしてるみたいですね。

module Kernel
  def byebug
    require_relative "core"

    Byebug.attach unless Byebug.mode == :off
  end

  def remote_byebug(host = "localhost", port = nil)
    Byebug.spawn(host, port)

    Byebug.attach
  end

  alias debugger byebug
end

https://github.com/deivid-rodriguez/byebug/blob/ddad523/lib/byebug/attacher.rb#L34-L48

力強いな〜と思いました。

JavaScript も debugger ですし、debugger に統一されていたほうが嬉しいと感じました。

まとめ

今回の RubyKaigi はツール系のアップデートが多くて(周辺ツール好きとしても)興味深いセッションが多くて楽しかったです。高速なデバッガーはハッピーな開発体験をもたらすと思っているので Ractor 対応含めて今後の動向をチェックしていきたいです。

宣伝

Leaner Technologies ではツール好きなエンジニアを募集しています!

https://careers.leaner.co.jp/

脚注
  1. VSCode がなかったのと、PHPer で PHPStorm を愛用していました。 ↩︎

リーナーテックブログ

Discussion

やまびこやまびこ

参考にさせていただいております。
私もVSCode + Remote Debuggingを試しているところです。
リモートはdocker環境なのですが、この記事でのリモート環境はどのような環境でしょうか?
docker上のrailsにvscodeからrdbg attachするところで詰まっています・・・(汗)