LoginSignup
15
1

More than 1 year has passed since last update.

Dockerを使ってElixir 1.12で増えた関数を調べたい

Last updated at Posted at 2022-11-28

@torifukukaiou さんが以前取り組まれた内容を複数のDockerコンテナを使ってやってみるという企画です。
あえて違ったアプローチで取り組みます。
執筆時点で最新のElixirバージョンは1.14.2なのですが、あえてオリジナルの記事と同じ1.12.1を対象とします。

やりたいこと

  • Elixir 1.12で追加された関数、削除された関数をコード上で確認する
  • Elixirバージョンごとにノードをたてて、:application.get_key/2の結果を比較する
  • ノードは別々のDockerコンテナから起動する

ノードとは

分散Erlangのドキュメントによると

A distributed Erlang system consists of a number of Erlang runtime systems communicating with each other. Each such runtime system is called a node.

論よりRun
:speaking_head: < :computer:

ElixirバージョンごとにDockerコンテナを準備

まずはhexpm/elixirDockerイメージを使用してElixirが使える仮想Linuxマシンを立ち上げます。

Elixir 1.11.4用の仮想Linuxマシン

docker run --rm -it hexpm/elixir:1.11.4-erlang-24.3.4.6-alpine-3.15.6 /bin/sh

Elixir 1.12.1用の仮想Linuxマシン

docker run --rm -it hexpm/elixir:1.12.1-erlang-24.3.4.6-alpine-3.15.6 /bin/sh

仮想マシンのIPアドレスを探す

他の仮想マシンと接続するにはIPアドレスが必要になりますので、それぞれの仮想マシンのIPアドレスを調べます。いろんなコマンドがありますが、ここではifconfigを使用します。

ifconfig eth0 | grep inet

ノードを起動

ノードの名前とErlangクッキーを指定してIExを起動することにより、ノードが立ち上がります。

iex --name hoge@172.17.0.7 --cookie mycookie

ノードの名前

--nameオプションにノードの場所(IPアドレス)を含む完全修飾名を指定します。
IPアドレスはご自身のものに置き換えてください。
@の左側は任意の文字列で大丈夫です。

Erlangクッキー

ノードが接続する際にクッキーが必要になりますので、--cookieオプションにクラスター共通の文字列を渡します。あるクラスターに属するすべてのノードが同じクッキーを共有することになります。

以上の作業を別々のシェルから実行し、別々の仮想マシンを立ち上げ、ノードを起動します。

分散Erlangシステムを構築する

ノードを接続

hogeノードのIExからfugaノードに接続します。

hogeノードのIEx
:pong = Node.ping(:"fuga@172.17.0.8")

Node.ping/1は、成功した場合は :pong、失敗した場合は:pangを返します。IPアドレスはご自身のものに置き換えてください。

Node.list/0で現在接続されているノードのリストが確認できます。

ノードを接続.png

別ノード内でコードを実行できるか確認

hogeノードのIExからfugaノードに入ってコードを実行します。 試しにIEx.Helpers.runtime_info/0でシステムの情報を取得してみます。

hogeノードのIEx
# hogeノード自身のシステム情報
IO.puts(System.version)
runtime_info

# fugaノードのシステム情報
Node.spawn_link(:"fuga@172.17.0.8", fn -> IO.puts(System.version) end)
Node.spawn_link(:"fuga@172.17.0.8", &runtime_info/0)

別のノードに入ってコードを実行

ちゃんとノード間で通信できることが確認できました。

Elixirすべての関数のリストを取得するコードを定義

:application.get_key/2を利用して、:elixirアプリケーションのモジュールのリストを取得します。

IEx
IEx.configure(inspect: [limit: :infinity])

:application.get_key(:elixir, :modules)

さらにのちに関数リストの差分の計算をしやすいようにあらかじめ:application.get_key/2の結果を加工します。 AllLoadedElixir.get/0という形にまとめることにしました。

それぞれのノードのIExに以下のモジュールを貼り付けます。

それぞれのノードのIEx
defmodule AllLoadedElixir do
  def get do
    {:ok, modules} = :application.get_key(:elixir, :modules)

    functions =
      modules
      |> Enum.filter(&String.starts_with?("#{&1}", "Elixir."))
      |> Enum.reduce([], fn mod, acc ->
        mod.__info__(:functions)
        |> Enum.map(fn {f, a} -> "#{mod}.#{f}/#{a}"end)
        |> Kernel.++(acc)
      end)
      |> Enum.map(fn "Elixir." <> mfa -> mfa end)
      |> Enum.sort()

    %{
      version: System.version(),
      otp_release: System.otp_release(),
      functions: functions
    }
  end
end

使い方は簡単です。

AllLoadedElixir使い方
AllLoadedElixir.get

:code.all_loaded/0だと:elixirアプリケーション以外のモジュールも含んでいる印象を持ったので、:application.get_key/2を使って明示的に:elixirアプリケーションのモジュールを取得することにしました。

関数リストを比較

:rpc.call/4を用いて別ノード内でコードを実行します。Node.spawn/2と似ていますが、:rpc.call/4を使うと別ノード内で計算した結果を簡単に受信できます。

hogeノードのIEx
# hogeノードの結果(Elixir 1.11)
old = AllLoadedElixir.get

# fugaノードの結果(Elixir 1.12)
new = :rpc.call(:"fuga@172.17.0.8", AllLoadedElixir, :get, [])

removed = old.functions -- new.functions
added = new.functions -- old.functions

length(removed)
length(added)

結果発表

追加された関数

for(f <- added, do: IO.puts(['* ', f]))
  • Calendar.ISO.parse_date/2
  • Calendar.ISO.parse_naive_datetime/2
  • Calendar.ISO.parse_time/2
  • Calendar.ISO.parse_utc_datetime/2
  • Code.cursor_context/1
  • Code.cursor_context/2
  • Code.ensure_compiled!/1
  • Code.ensure_loaded!/1
  • Date.range/3
  • Date.to_iso_days/1
  • DateTime.to_iso8601/3
  • Enum.count_until/2
  • Enum.count_until/3
  • Enum.product/1
  • Enum.zip_reduce/3
  • Enum.zip_reduce/4
  • Enum.zip_with/2
  • Enum.zip_with/3
  • ErlangError.error_info/2
  • Float.pow/2
  • IO.binstream/0
  • IO.binstream/1
  • IO.stream/0
  • IO.stream/1
  • Integer.extended_gcd/2
  • Integer.pow/2
  • List.first/2
  • List.last/2
  • Module.Types.Expr.of_expr/4
  • Module.Types.Unify.collect_var_indexes/2
  • Module.Types.Unify.collect_var_indexes/3
  • Module.Types.Unify.lift_types/2
  • Module.Types.Unify.refine_var!/4
  • Module.Types.Unify.restore_var!/3
  • Module.delete_definition/2
  • Module.get_definition/2
  • Module.reserved_attributes/0
  • Range.new/3
  • Range.size/1
  • Registry.Supervisor.start_link/6
  • Registry.values/3
  • Stream.zip_with/2
  • Stream.zip_with/3
  • System.SignalHandler.handle_call/2
  • System.SignalHandler.handle_event/2
  • System.SignalHandler.handle_info/2
  • System.SignalHandler.init/1
  • System.shell/1
  • System.shell/2
  • System.trap_signal/2
  • System.trap_signal/3
  • System.untrap_signal/2
  • Tuple.product/1
  • Tuple.sum/1
  • URI.decode_query/3
  • URI.encode_query/2
  • URI.query_decoder/2

削除された関数

for(f <- removed, do: IO.puts(['* ', f]))
  • Module.Types.Expr.of_expr/3
  • Module.Types.Pattern.unify_kinds/2
  • Module.Types.Unify.refine_var/4
  • Module.Types.lift_type/2
  • Module.Types.lift_types/2
  • Registry.Supervisor.start_link/5

Elixir公式のリリースドキュメントにざっくりと変更内容が書かれています。

答えがあっているのかどうかは知りませんが、楽しく遊べたのでこの記事はここでお開きとさせていただきます。

ご参考までに

15
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
1