Elixir入門 21: デバッグ
gumi TECH
Posted on February 12, 2019
本稿はElixir公式サイトの許諾を得て「Debugging」の解説にもとづき、加筆補正を加えて、Elixirでよく行われるデバッグのやり方についてご説明します。
IO.inspect/2
IO.inspect/2
は、もとのコードの動きは変えることなく、第1引数item
を返すので、デバッグに用いると便利です。
inspect(item, opts \\ [])
たとえば、つぎのようにパイプ演算子|>
で続く処理の間にIO.inspect/2
を挟んで、値がどのように変わるのか確かめられます。これにより結果が変わることはありません。
defmodule Example do
def square_sum(first, last) do
for i <- first..last do
i
end
|> IO.inspect
|> Enum.map(fn x -> x * x end)
|> IO.inspect
|> Enum.sum
end
end
iex> Example.square_sum(1, 5)
[1, 2, 3, 4, 5]
[1, 4, 9, 16, 25]
55
IO.inspect/2
の第2引数のオプションに:label
を渡すと、出力する第1引数の項目の前にその文字列が添えられます。よく組み合わせて使われるのが、変数と値のキーワードリストを返すbinding/0
です。IO.inspect/2
に渡せば、関数が呼び出されたときに受け取った引数名と値が確かめられます。
defmodule Example do
def square_sum(first, last) do
IO.inspect binding()
for i <- first..last do
i
end
|> IO.inspect(label: "before")
|> Enum.map(fn x -> x * x end)
|> IO.inspect(label: "after")
|> Enum.sum
end
end
iex> Example.square_sum(1, 5)
[first: 1, last: 5]
before: [1, 2, 3, 4, 5]
after: [1, 4, 9, 16, 25]
55
IO.inspect/2
の第2引数にどのようなフォーマット用のオプションが渡せるかについては「Inspect.Opts」をお読みください。
IEx.pry/0とIEx.break!/2
IO.inspect/2
を使うのは静的なデバッグでした。Elixirのインタラクティブシェルには、対話的にコードをデバッグできる動的なやり方もあります。そのひとつは、静的なIO.inspect binding()
に替わるIEx.pry/0
です。使う前に必ずrequire/2
にIEx
モジュールを与えてください。
defmodule Example do
def square_sum(first, last) do
require IEx; IEx.pry
for i <- first..last do
i
end
|> Enum.map(fn x -> x * x end)
|> Enum.sum
end
end
iex
セッションで関数を呼び出すと、コードの実行は止まり、pry
が動きます。IExから変数やimport
あるいはエイリアスなどがすべて参照できるのです。コードの実行を再開してiex
に戻るにはrespawn/0
を呼び出してください。Mixプロジェクトでも、iex -S mix
で同じようにデバッグできます。
iex> Example.square_sum(1, 5)
Break reached: Example.square_sum/2 (example.exs:3)
1: defmodule Example do
2: def square_sum(first, last) do
3: require IEx; IEx.pry
4: for i <- first..last do
5: i
pry> first
1
pry> last
5
pry> respawn
Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
55
IO.inspect/2
もIEx.pry/0
も、デバッグのためのコードを書き加えなければなりませんでした。break!/2
を使えば、ソースに手は加えずにIExでブレークポイントの設定や管理ができます。
ただし、コードはコンパイルされていなければなりません。たとえば、つぎのコードが書かれたファイルexample.ex
をコンパイルしましょう(「Elixir入門 08: モジュールと関数」「コンパイル」参照)。
defmodule Example do
def square_sum(first, last) do
for i <- first..last do
i
end
|> Enum.map(fn x -> x * x end)
|> Enum.sum
end
end
$ elixirc example.ex
iex
セッションでbreak!/2
を呼び出すと、引数の関数にブレークポイントが加えられて、pry
で引数や変数などが参照できます。現在の位置を示すのがwhereami/1
です。引数により表示するコードの範囲が変えられます。
iex> break! Example.square_sum/2
1
iex> Example.square_sum(1, 5)
Break reached: Example.square_sum/2 (example.ex:2)
1: defmodule Example do
2: def square_sum(first, last) do
3: for i <- first..last do
4: i
pry> first
1
pry> last
5
pry> whereami
Location: example.ex:2
1: defmodule Example do
2: def square_sum(first, last) do
3: for i <- first..last do
4: i
Example.square_sum/2
pry> whereami 9
Location: example.ex:2
1: defmodule Example do
2: def square_sum(first, last) do
3: for i <- first..last do
4: i
5: end
6: |> Enum.map(fn x -> x * x end)
7: |> Enum.sum
8: end
9: end
Example.square_sum/2
pry> respawn
Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
55
break!/2
を使えば、組み込み済み関数の動きも確かめられます(図001およびリンク映像)。たとえば、URI.decode_query/2
は、クエリー文字列をマップにデコードする関数です。
decode_query(query, map \\ %{})
iex> break! URI.decode_query/2
1
iex> URI.decode_query("percent=oh+yes%21", %{"starting" => "map"})
Break reached: URI.decode_query/2 (/private/tmp/elixir-20180507-68757-17gx35t/elixir-1.6.5/lib/elixir/lib/uri.ex:140)
pry> query
"percent=oh+yes%21"
pry> map
%{"starting" => "map"}
pry> respawn
Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
%{"percent" => "oh yes!", "starting" => "map"}
図001■break!/2でデバッグする
break!/2
はソースでなく、コンパイルされたコードをデバッグします。そのため、エイリアスやimport
は参照できません。
デバッガ
Erlang/OTPにはグラフィカルユーザインタフェースのデバッガ:debugger
が備わっています。視覚的にブレークポイントを操作したり、変数の値などが調べられるのです(図002)。
図002■グラフィカルユーザインタフェースによる:debuggerの操作
「Debugging techniques in Elixir」より
iex
シェルでつぎの3つの操作を行うと、デバッガの準備が整います。なお、モジュールはコンパイルされていなければなりません。
-
debugger.start/0
: デバッガを開始して、デバッガのウィンドウを開きます。 -
int.ni/1
: デバッグするモジュールを定めます。 -
int.break/2
: モジュールに加えるブレークポイントの行を指定します。
そのうえでモジュールの関数を呼び出せば、デバッガが使えるようになります。
iex> :debugger.start()
{:ok, #PID<0.86.0>}
iex> :int.ni(Example)
{:module, Example}
iex> :int.break(Example, 6)
:ok
iex> Example.square_sum(1, 5)
[Monitor]ウィンドウ左上のモジュールをダブルクリックすると、[View Module]のウィンドウが開きます(図003)。
図003■MonitorからView Moduleのウィンドウを開く
[Break]メニューから[Line Break...]を選んで開く[Line Break]ダイアログボックスで、ブレークポイントの追加や削除などの操作ができます(図004)。
図004■ブレークポイントを操作する
[Monitor]ウィンドウに戻って右側のブレークポイントが入ったプロセスのリストをダブルクリックして、[Attach Process]ウィンドウを開けばプロセスが操作できます(図005)。プロセスを進めると、ウィンドウ下側右の変数の値がどう変わるか確かめられます。変数をクリックして、その時々の変数値が左側に記録できます。
図005■プロセスを操作すると値の変化が確かめられる
デバッガの操作について詳しくは、Erlangの「Debugger」をお読みください。
オブザーバー
複雑なシステムをデバッグするには、コードをたどるだけでは不十分です。仮想マシン全体やプロセスとアプリケーションを理解し、トレースのメカニズムをつくらなければなりません。Erlangにはそのために:observer
が備わっているのです。グラフィカルユーザインタフェースが開かれ、いくつもの画面でランタイムとプロジェクトを把握する手助けとなります(「Observer」参照)。
iex> :observer.start()
図006■:observerのグラフィカルユーザインタフェース
「MIX AND OTP」の「Dynamic supervisors」では、実際のプロジェクトで「Observer」を扱います。また、:observer
を使えばリモートノードがイントロスペクトできます(「Tracing and observing your remote node」参照)。Phoenixフレームワークが200万の接続を1台のマシンでデバッグした技術です(「The Road to 2 Million Websocket Connections in Phoenix」参照)。
なお、IExでruntime_info/0
を呼び出せば、ランタイム情報が確かめられます。
iex> runtime_info
## System and architecture
Elixir version: 1.6.5
OTP version: 20
ERTS version: 9.3.1
Compiled for: x86_64-apple-darwin17.5.0
Schedulers: 4
Schedulers online: 4
## Memory
Total: 25 MB
Atoms: 379 kB
Binaries: 309 kB
Code: 9611 kB
ETS: 864 kB
Processes: 5666 kB
## Statistics / limits
Uptime: 54 minutes and 7 seconds
Run queue: 0
Atoms: 14407 / 1048576 (1% used)
ETS: 25 / 2053 (1% used)
Ports: 7 / 65536 (0% used)
Processes: 64 / 262144 (0% used)
Showing topics: [:system, :memory, :limits]
Additional topics: [:applications]
To view a specific topic call runtime_info(topic)
その他のツールとコミュニティ
Erlang VMが提供するツールはほかにもあります。
-
: observer
に加え、クラッシュダンプを表示する:crashdump_viewer
も備わっています(「Crashdump Viewer」参照)。 - OSレベルのトレーサーとの統合
- Linux Trace Toolkit(「LTTng and Erlang/OTP」参照)
- DTrace(「DTrace and Erlang/OTP」参照)
- SystemTap(「SystemTap and Erlang/OTP」参照)
- マイクロステートアカウンティングは、短い時間間隔で複数の低レベルタスクでランタイムが費やす時間を測定します(「msacc」参照)。
- Mixには
profile
の名前空間にmix profile.cprof
やmix profile.cprof
などのタスクが備わっています。 - その他
コミュニティがつくった製品化や開発に役立つツールもあります。
- Wobserver: webインタフェースでプロダクションノードを監視します。
- Visualixir: 開発時のプロセスメッセージを視覚化します。
- erlyberly: 開発時にトレースするためのGUIです。
Elixir入門もくじ
- Elixir入門 01: コードを書いて試してみる
- Elixir入門 02: 型の基本
- Elixir入門 03: 演算子の基本
- Elixir入門 04: パターンマッチング
- Elixir入門 05: 条件 - case/cond/if
- Elixir入門 06: バイナリと文字列および文字リスト
- Elixir入門 07: キーワードリストとマップ
- Elixir入門 08: モジュールと関数
- Elixir入門 09: 再帰
- Elixir入門 10: EnumとStream
- Elixir入門 11: プロセス
- Elixir入門 12: 入出力とファイルシステム
- Elixir入門 13: aliasとrequireおよびimport
- Elixir入門 14: モジュールの属性
- Elixir入門 15: 構造体
- Elixir入門 16: プロトコル
- Elixir入門 17: 内包表記
- Elixir入門 18: シギル
- Elixir入門 19: tryとcatchおよびrescue
- Elixir入門 20: 型の仕様とビヘイビア
- Elixir入門 21: デバッグ
- Elixir入門 22: Erlangライブラリ
- Elixir入門 23: つぎのステップ
番外
Posted on February 12, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.