Distribunomicon erlang

use many cpu in erlang

erlangは黙っていても、プロセスをちゃんと各CPUに割り当ててくれるか、検証してみる。 テストスクリプトは、前回やったfibを計算するやつ。中々終わらないように、引数を大きくした。

2> test:calc_fibs([100,101,102]).
timeout
3> i().
  :
<0.88.0>              test:fib_send/2                        233 51843320    0
                      test:fib/1                             174
<0.89.0>              test:fib_send/2                        233 51095000    0
                      test:fib/1                             186
<0.90.0>              test:fib_send/2                        233 52452320    0
                      test:fib/1                             184

メインスレッドは、1秒で終了しちゃったけど、その裏で、黙々と計算は行われている。 fib_send が起動スクリプトで、最終的にはfibを実行してる。

このerlangのプロセスが、CPUに割り振られているか、topで監視してみる。topの表示画面を少しカスタマイズした。u sakae で、自分のプロセスのみ表示。1で各CPU毎の負荷を表示。d 10 で、10秒毎に画面を更新。fで、表示内容を変更(矢印キーで移動して、spaceで選択/非選択、Escで抜ける)。こうして、画面をコピー(コピー結果を編集してる)。

top - 07:54:31 up  2:09,  3 users,  load average: 2.69, 1.10, 0.41
Tasks: 148 total,   1 running, 147 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.2 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   2438.9 total,   1130.6 free,    260.7 used,   1047.6 buff/cache
MiB Swap:   2045.0 total,   2045.0 free,      0.0 used.   1985.7 avail Mem

   PID USER        VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  3155 sakae    2503396  35416   5816 S 300.4   1.4   6:53.26 beam.smp
  3162 sakae       2280    740    676 S   0.0   0.0   0:00.00 erl_child_setup
   :

4個あるCPUのうち3個が忙しく動いている事が観測出来る。各CPUを合計した使用率も300%になってる。最初は、 erl_child_setup 側で、実行されてると予想してたんだけど、そちらはdebianOSレベルではお休みモード。このプロセスは何のためにあるの? しばし疑問だ。

epmd

erlangは分散が得意だそうだ。上で見たように、一つのerlangの中で、プロセスが幾つものCPUに分散出来るんだから、複数のerlangを動かす必要もなかろうに、と思うのは、現場を知らないからだな。

何でもerlangでやりたくなったら、目的毎にerlangを立ち上げたくなるぞ。例えば、携帯屋なら、本業の携帯での通信コントロール、課金システム、請求システム、ウザイinfoと称する広告メール発送、等々。

これらを全部ひっくるめて、一つのerlangでやろう、なんて普通は考えない。それぞれの目的別にerlangを立ち上げて、その間をメッセージで繋げばよい。

複数erlangの連携だな。過去に発掘しておいた、無料で読める本を参照してみる。 Distribunomicon 丁寧過ぎて、本でじっくり読みたい所だ。このセクションでは、複数のerlangに名前を付けてあげる所から話は始まる。

debian:~$ erl -sname z3
Erlang/OTP 23 [erts-11.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] [hipe]

Eshell V11.2  (abort with ^G)
(z3@debian)1>

このように、erlを立ち上げる時に、そのerlに名前を付けてあげる。超合金製のerlangだから、マジンガーZより文字を頂きz3とした。z1,z2号機は既に稼働中。

で、説明では、これらが連携する為の司令塔も同時に立ち上がるとな。その名は、 Command (or daemon) epmd だ。

この司令塔との交信は、世界的な共通番号で行われるそうだ。debianでは、下記のようにその番号(正確にはポート番号)が登録されていた。

debian:~$ grep 4369 /etc/services
epmd            4369/tcp                        # Erlang Port Mapper Daemon
epmd            4369/udp

通常、このepmdは一度立ち上がると、意識的に殺さない限り居座るようになっている。ダエモン君だな。サーバーである。けど、クライアントとしても振る舞う事が出来る。

debian:~$ epmd -names
epmd: up and running on port 4369 with data:
name z3 at port 40065
name z2 at port 34111
name z1 at port 34441

これは、erlが起動した時、それぞれのerlへの通信番号を保持してて、その内容を確認するコマンドだ。多分、それぞれの号機と通信する時は、相手の番号を知った上で、直接やりとりするんだろうね。

debian:~$ epmd -dump
epmd: up and running on port 4369 with data:
active name     <z3> at port 40065, fd = 6
active name     <z2> at port 34111, fd = 5
active name     <z1> at port 34441, fd = 4

ちまちまとepmdのソースを見てたら、上記のような隠しパラメータを知った。こういうのOSSの楽しみの一つである。

debian:tmp$ echo -ne "\x00\x01\x6e" | nc localhost 4369
name z3 at port 38783

debianだと、こんな事も出来るとな。

それから、epmdを破壊するコマンドも用意されてる。-killだ。これで、司令塔が居なくなる。

下記は、epmd -names をした時のパケットの流れをwiresharkで追ってみた。 解析したパケットをASCIIで保存するには、File -> Export Packet Dissections -> As Plain Textを選んで保存すれば良い(見たい部分をあらかじめ展開しておく事)。インターフェースは lo0 を選んだけどIPv6が使われていたよ。

Internet Protocol Version 6, Src: ::1, Dst: ::1
Transmission Control Protocol, Src Port: 41334, Dst Port: 4369, Seq: 1, Ack: 1,
Len: 3
    Source Port: 41334
    Destination Port: 4369
     :
    TCP payload (3 bytes)
Erlang Port Mapper Daemon
    Length: 1
    Type: EPMD_NAMES_REQ (110)

 
Internet Protocol Version 6, Src: ::1, Dst: ::1
Transmission Control Protocol, Src Port: 4369, Dst Port: 41334, Seq: 5, Ack: 4,
Len: 66
    Source Port: 4369
    Destination Port: 41334
     :
    TCP payload (66 bytes)
Data (66 bytes)

0000  6e 61 6d 65 20 7a 33 20 61 74 20 70 6f 72 74 20   name z3 at port
0010  34 30 30 36 35 0a 6e 61 6d 65 20 7a 32 20 61 74   40065.name z2 at
0020  20 70 6f 72 74 20 33 34 31 31 31 0a 6e 61 6d 65    port 34111.name
0030  20 7a 31 20 61 74 20 70 6f 72 74 20 33 34 34 34    z1 at port 3444
0040  31 0a                                             1.
    Data: 6e616d65207a3320617420706f72742034303036350a6e61...
    [Length: 66]

名前を表示してねってリクエストで、返答が返ってきてきる。分かり易い例だな。

(z3@debian)1> net_kernel:connect_node( z1@debian ).
true
(z3@debian)2> nodes().
[z1@debian]
(z3@debian)3> net_kernel:connect_node( z2@debian ).
true
(z3@debian)4> nodes().
[z1@debian,z2@debian]

erl的には、相手と通信出来るように、接続出来るようにリクエストを出す。nodes()で、繋がった相手がリストアップされる。

接続要求に対してtrueが返ってきてる。了解しました、無線用語ではラジャーだ。相手側でも、nodes()すれば、対抗erlが登録されてる事を見て取れる。

debian:tmp$ epmd -kill
Killing not allowed - living nodes in database.
debian:tmp$ epmd -kill
Killed

一台でもerlが生きていると、司令塔を削除出来ない。全部のマジンガーZが死ぬと、司令塔を保持しておく意味が無くなるので、任意に破壊できる(ずっと残しておいても良い)。

立ち上がるerlに -sname で名前を付けた。-nameってのもあるんで、やってみると

(z3@debian)6> debian:~$ erl -name z3                                           
2021-05-27 08:14:30.243593
    args: []                                                                   
    format: "Can't set long node name!\nPlease check your configuration\n"     
    label: {error_logger,info_msg}                                             
2021-05-27 08:14:30.255697 supervisor_report .....
  :
Crash dump is being written to: erl_crash.dump...done

資料で、恐ろしい事が起きるって言ってたのは、この事か。

debian:~$ head erl_crash.dump
=erl_crash_dump:0.5
Thu May 27 08:14:30 2021
Slogan: Kernel pid terminated (application_controller) ({application_start_failure,kernel,{{shutdown,....
System version: Erlang/OTP 23 [erts-11.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] [hipe]
  :

理由を探ってみたよ。自主的に落ちたんだね。-snameってのは、簡単に言うと1つのホスト(パソコン)内での名前付けなんだな。それに対して、-nameの方は、FQDNでの指定なのかな。いわゆる、正式なホスト名の指定。@の右側は、DNSサーバーで名前解決してねって事だな。

debian:~$ erl -name z3@10.0.2.15
Erlang/OTP 23 [erts-11.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] [hipe]

Eshell V11.2  (abort with ^G)
(z3@10.0.2.15)1>
ob$ erl -name ob@aa.bb.cc.128 -setcookie NTTxKDDIxSB
Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]

Eshell V11.2  (abort with ^G)
(ob@aa.bb.cc.128)1> net_kernel:connect_node( 'pen@aa.bb.cc.129' ).
true
(ob@aa.bb.cc.128)2> nodes().
['pen@aa.bb.cc.129']

それから、異なるhost(違うパソコン)と接続したい場合、-setcookie は必須である。これを設定(勿論、両host共、同一な文字列)しておかないと、どう頑張っても接続は失敗する。いわば、合言葉だな。

上の例は、同一サブネット上にあるOpenBSDなhostとDebian機を接続した例である。こういう実験にはVMwareは最適である。尚、epmdは自hostのport番号しかハンドリングしない。

そういう意味では、ダイナミックDNSサーバーもどき(但し、提供するのは、host内にたむろするerlの待ち受けポートだけど)。

ob$ epmd -d
epmd: Thu May 27 15:50:58 2021: epmd running - daemon = 0
epmd: Thu May 27 15:50:58 2021: there is already a epmd running at port 4369 on ipaddr 0.0.0.0

このepmdは、広く世界中からの質問に答えるやつだ。ここを攻撃されて、毒素を注入されたらどうしよう? 設定で何とかなるのかな?

with ruby

Erlangのノード間通信をRubyで実装してみる

debian:tmp$ irb
irb(main):001:0> require_relative 'erlang'
=> true
irb(main):002:0> erl = Erlang::Erl.new('node1@debian', "testtest")
Traceback (most recent call last):
       10: from /usr/local/bin/irb:23:in `<main>'
        9: from /usr/local/bin/irb:23:in `load'
        8: from /usr/local/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
        7: from (irb):2:in `<main>'
        6: from (irb):2:in `new'
        5: from /tmp/erlang.rb:289:in `initialize'
        4: from /tmp/erlang.rb:289:in `each'
        3: from /tmp/erlang.rb:290:in `block in initialize'
        2: from /tmp/erlang.rb:302:in `connect'
        1: from /tmp/erlang.rb:253:in `connect'
RuntimeError (handshake error (status: snot_allowed))
debian:~$ erl -sname node1 -setcookie testtest
Erlang/OTP 23 [erts-11.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] [hipe]

Eshell V11.2  (abort with ^G)
(node1@debian)1> =ERROR REPORT==== 27-May-2021::09:06:57.008559 ===
 ** node1@debian: Connection attempt from node node1@debian rejected since it cannot handle ["BIG_CREATION",

            "UTF8_ATOMS"].**

(node1@debian)1>

これ、erl側が期待してる特性をruby側が持っていないって事だな。足りない特性BITを、 Distribution Protocol を見て宣言。そして、接続時のflagsにも追加したよ。 そしたら、状況が変わって

(fst@pen)1> =ERROR REPORT==== 28-May-2021::06:25:24.595486 ===

 ** Cannot get connection id for node fst@pen

idが取れないと言ってきた。この状態の時、ruby側のerlもどきは

sakae@pen:/tmp$ ./con.rb
./con.rb:256:in `connect': handshake error (status: snok) (RuntimeError)
        from ./con.rb:305:in `connect'
        from ./con.rb:293:in `block in initialize'
        from ./con.rb:292:in `each'
        from ./con.rb:292:in `initialize'
        from ./con.rb:360:in `new'
        from ./con.rb:360:in `<main>'

こんなエラーが出てる。

247      # handshake
248      version = 5
249      flags = DFLAG_EXTENDED_REFERENCES | DFLAG_EXTENDED_PIDS_PORTS | DFLAG_\
   NEW_FUN_TAGS | DFLAG_NEW_FLOATS | DFLAG_MAP_TAG | DFLAG_BIG_CREATION | DFLAG\
   _UTF8_ATOMS
250      msg = 'n' + [version].pack("n*") + [flags].pack("N*") + selfnode.to_s
251      write_msg(soc, msg)
252
253      status = read_msg(soc)
254      if status != 's' + 'ok'
255        soc.close()
256        raise("handshake error (status: #{status})")
257      end
258
259      challenge_msg = read_msg(soc)

該当箇所は、本当に接続の一番最初の所。チャレンジも始まる前段階だ。ちょいと気になるversionだけど、資料によれば、

HighestVersion
    The highest distribution protocol version this node can handle. The value 
    in OTP 23 and later is 6. Older nodes only support version 5.
LowestVersion
    The lowest distribution version that this node can handle. Should be 5 to 
    support connections to nodes older than OTP 23.

OTP 23 を堺に変わっているんですなあ。5と言う事は古いOTPだな。それに合わせてみるか。 OTP 21 でも同様のエラーだったよ。はて、どうしたものか? 将棋名人、米長さんみたいに、長考してみるか(いえね、今彼の伝記を読んでいるもので。。。。。。長考してます、AIよりも考えてる時間が長いぞ)。散歩中に思い付いた。

ruby側はerlの真似をするんだから、本物のerl側のsnameを指定するのは、論理的に破綻してるよ。と言う事で、再実験。

ob$ erl -sname fst -setcookie KDDI
Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]

Eshell V11.2  (abort with ^G)
(fst@ob)1> Hello!

(fst@ob)1> =ERROR REPORT==== 28-May-2021::16:13:31.385263 ===
 ** Node ruby@ob not responding **
 ** Removing (timedout) connection **

接続出来て、Helloまでは、送り込めた。その後はruby側のトラブルにより通信が途絶したって言ってる。まあ、少しは進んだな。

ob$ irb
irb(main):001:0> require_relative 'con'
=> true
irb(main):002:0>  erl = Erlang::Erl.new('ruby@ob', "KDDI")
=>
#<Erlang::Erl:0x000008a18271b138
...
irb(main):003:0> erl.nodes
=> ["fst@ob"]
irb(main):004:0> erl.rpc_call(erl.nodes[0], :io, :format, ["Hello!\n"])
unknown tag 119
unknown tag 0
#<Thread:0x000008a182719a18 /tmp/con.rb:307 run> terminated with exception (report_on_exception is true):
/tmp/con.rb:316:in `block in connect': undefined method `id' for #<StringIO:0x000008a0ad8a0470> (NoMethodError)
/tmp/con.rb:340:in `pop': execution expired (Timeout::Error)
        from /tmp/con.rb:340:in `block in rpc_call'
        from /usr/local/lib/ruby/3.0/timeout.rb:112:in `timeout'
        from /tmp/con.rb:339:in `rpc_call'
        from (irb):4:in `<main>'
        from /usr/local/lib/ruby/gems/3.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
        from /usr/local/bin/irb:23:in `load'
        from /usr/local/bin/irb:23:in `<main>'

erl.nodesまでは成功してる。epmdとのセッションは上手くいった。次の処理がまずいのだな。

意味もなく、今度はdebian機で再確認。まずはepmdの状態

debian:tmp$ epmd -names
epmd: up and running on port 4369 with data:
name snd at port 42383
name fst at port 42979

それから、irb

debian:tmp$ irb
irb(main):001:0>  require_relative 'con'
=> true
irb(main):002:0> erl = Erlang::Erl.new('ruby@debian', "KDDI")
=> #<Erlang::Erl:0x029771a4 @node=:"ruby@debian", @cookie="KDDI", @nodes={"...
irb(main):003:0>  erl.nodes
=> ["snd@debian", "fst@debian"]
irb(main):004:0> pp erl
#<Erlang::Erl:0x029771a4
 @cookie="KDDI",
 @msgqueue={},
 @node=:"ruby@debian",
 @nodes=
  {"snd@debian"=>
    #<Erlang::Node:0x02976d58
     @name="snd@debian",
     @port=42383,
     @soc=#<TCPSocket:fd 6, AF_INET, 127.0.0.1, 36420>>,
   "fst@debian"=>
    #<Erlang::Node:0x02976628
     @name="fst@debian",
     @port=42979,
     @soc=#<TCPSocket:fd 7, AF_INET, 127.0.0.1, 47268>>}>

対応が、ここまでの部は、よく分かったよ。

byebug and tcpdump

思い出したようにrubyのdebuggerであるbyebugなんてのを取り出してみる(今年の2月にやったな)。

sakae@pen:/tmp$ byebug con.rb

[1, 20] in /tmp/con.rb
  :
(byebug) c
unknown tag 119
unknown tag 0
Stopped by breakpoint 1 at /tmp/con.rb:316
 :
(byebug) p m
"p\x83h\x03a\x02w\x00Xw\bruby@pen\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x83h\x02Z\x00\x01w\bruby@pen\x00\x00\x00\x01\x00\x00\x00\x01w\x02ok"

こんな返事が返ってきてるんだけど、プチ見難いな。この際、行き来してるパケットをキャプチャーしてみるか。

root@pen:/tmp# tcpdump -i lo -s 300 -X port 39559 >LOG
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 300 bytes
^C19 packets captured
38 packets received by filter
0 packets dropped by kernel

久しぶりにtcpdumの登場です。-s0にして、全パケットをHex化した方が良かったかな。突然出て来たport 39559 は、対抗するerlが待ち構えているやつだ。

16:15:19.781822 IP localhost.47996 > localhost.39559: Flags [P.], seq 41:199, ac
k 45, win 512, options [nop,nop,TS val 3044367889 ecr 3044367888], length 158
        0x0000:  4500 00d2 8ad9 4000 4006 b14a 7f00 0001  E.....@.@..J....
        0x0010:  7f00 0001 bb7c 9a87 a35d 3d0b 86c2 4796  .....|...]=...G.
        0x0020:  8018 0200 fec6 0000 0101 080a b575 5e11  .............u^.
        0x0030:  b575 5e10 0000 009a 7083 6804 6f00 0000  .u^.....p.h.o...
        0x0040:  0100 0667 6400 0872 7562 7940 7065 6e00  ...gd..ruby@pen.
        0x0050:  0000 0100 0000 0000 6400 0064 0003 7265  ........d..d..re
        0x0060:  7883 6803 6400 0924 6765 6e5f 6361 6c6c  x.h.d..$gen_call
        0x0070:  6802 6764 0008 7275 6279 4070 656e 0000  h.gd..ruby@pen..
        0x0080:  0001 0000 0000 0072 0001 6400 0872 7562  .......r..d..rub
        0x0090:  7940 7065 6e01 0000 0001 6805 6400 0463  y@pen.....h.d..c
        0x00a0:  616c 6c64 0002 696f 6400 0666 6f72 6d61  alld..iod..forma
        0x00b0:  746c 0000 0001 6b00 1148 656c 6c6f 2c20  tl....k..Hello,.
        0x00c0:  4920 616d 2072 7562 790a 6a64 0004 7573  I.am.ruby.jd..us
        0x00d0:  6572                                     er
16:15:19.782199 IP localhost.39559 > localhost.47996: Flags [P.], seq 45:108, ac
k 199, win 512, options [nop,nop,TS val 3044367889 ecr 3044367889], length 63
        0x0000:  4500 0073 d586 4000 4006 66fc 7f00 0001  E..s..@.@.f.....
        0x0010:  7f00 0001 9a87 bb7c 86c2 4796 a35d 3da9  .......|..G..]=.
        0x0020:  8018 0200 fe67 0000 0101 080a b575 5e11  .....g.......u^.
        0x0030:  b575 5e11 0000 003b 7083 6803 6102 7700  .u^....;p.h.a.w.
        0x0040:  5877 0872 7562 7940 7065 6e00 0000 0100  Xw.ruby@pen.....
        0x0050:  0000 0000 0000 0083 6802 5a00 0177 0872  ........h.Z..w.r
        0x0060:  7562 7940 7065 6e00 0000 0100 0000 0177  uby@pen........w
        0x0070:  026f 6b                                  .ok

後のパケットがerlからruby側への返答だ。このパケットを解析する所でtagの119が意味不って事で落ちている(ruby側で解析ルーチンが組み込まれていないんだから当然の事)。

12.30 SMALLATOMUTF8EXT がそれっぽい。119, 88, 90 のtagを追加した。

--- /home/sakae/con.rb  Fri May 28 16:51:05 2021
+++ con.rb      Sun May 30 07:35:31 2021
@@ -83,8 +83,10 @@
   TYPE_INTEGER   = 98
   TYPE_FLOAT     = 99
   TYPE_ATOM      = 100
+  TYPE_ATOM_UTF  = 119
   TYPE_PORT      = 102
   TYPE_PID       = 103
+  TYPE_PID_EXT   = 88
   TYPE_SMALL_TUPLE = 104
   TYPE_LARGE_TUPLE = 105
   TYPE_NIL       = 106
@@ -95,6 +97,7 @@
   TYPE_LARGE_BIG = 111
   TYPE_FUN       = 112
   TYPE_NEW_REF   = 114
+  TYPE_NEW2_REF  = 90
   TYPE_MAP       = 116

   @@decoder = {
@@ -102,6 +105,7 @@
     TYPE_INTEGER   => lambda{|io| io.read(4).unpack("l>")[0]},
     TYPE_NEW_FLOAT => lambda{|io| io.read(8).unpack("G")[0]},
     TYPE_ATOM      => lambda{|io| io.read(r_int16(io)).to_sym},
+    TYPE_ATOM_UTF      => lambda{|io| io.read(r_int8(io)).to_sym},
     TYPE_SMALL_TUPLE => lambda{|io| Tuple.new((1..r_int8(io)).map{from_binary(io)})},
     TYPE_LARGE_TUPLE => lambda{|io| Tuple.new((1..r_int32(io)).map{from_binary(io)})},
     TYPE_NIL       => lambda{|io| [] },
@@ -114,8 +118,12 @@
     TYPE_MAP       => lambda{|io| (1..r_int32(io)).reduce({}){|acc| acc[from_binary(io)] = from_binary(io); acc} },
     TYPE_PID       => lambda{|io| node = from_binary(io); (id,b,c) = io.read(9).unpack("NNc")
                                   Pid.new(node, id, b, c) },
+    TYPE_PID_EXT   => lambda{|io| node = from_binary(io); (id,b,c) = io.read(12).unpack("NNI")
+                                  Pid.new(node, id, b, c) },
     TYPE_NEW_REF   => lambda{|io| l = r_int16(io); node = from_binary(io); c = r_int8(io)
                                   Ref.new(node, c, (1..l).map{ r_int32(io)}) },
+    TYPE_NEW2_REF   => lambda{|io| l = r_int16(io); node = from_binary(io); c = r_int32(io)
+                                  Ref.new(node, c, (1..l).map{ r_int32(io)}) },
     TYPE_FUN       => lambda{|io| Fun.new(io.read(r_int32(io)-4))}
   }
   @@encoder = {

動作確認は、下記のようにする。

# erl -sname fst -setcookie KDDI
# run erl as above, then irb
# irb
#  require_relative 'con'
#  erl = Erlang::Erl.new('ruby@ob', "KDDI")       # adj @ob for your ENV
#  erl.nodes
#  erl.rpc_call(erl.nodes[0], :io, :format, ["Hello, I am ruby\n"])
#  erl.eval(erl.nodes[0], "1 + 1.")

OTPも版を重ねる毎に、どんどん新しく変化してくのね。真似するのは大変だあ。でも、楽しめたよ。元ネタを提供して下さった方に感謝です。

etc