2014年5月5日

erlang otp

範例內容包含了建立伺服器、監督伺服器、登錄錯誤到日誌、偵測警報這些功能,最後再將整個程式碼包裝成一個獨立的OTP應用。

建立兩個 otp server,一個用來產生質數,一個計算面積,系統錯誤會造成當機,需要一個偵測機制來重新啟動 server,也就是 supervision tree,這可以產生一個監督者,負責觀察 server,當server crash就重新啟動。

OTP 錯誤登錄器可紀錄所有錯誤,產生錯誤報告,另外因為計算很大的質數可能會造成 CPU 過熱,為了避免這個問題,使用 OTP 事件處理框架,來產生警報,以便配置強力風扇。

  1. 說明通用事件處理器的機制
  2. 說明錯誤登錄器的機制
  3. 加入警報管理
  4. 寫出兩個應用伺服器
  5. 實作監督樹,並將伺服器加入到監督樹中
  6. 把整個程式包裝成一個 OTP 應用

通用事件處理器

當程式中,有值得注意的事情 event 發生了,就送出事件訊息到註冊行程,只需要送出訊息通知有事情發生了,不在意送出訊息後會發生什麼事情。

RegProcName ! {event, E}

第一個通用事件處理器

-module(event_handler).
-export([make/1, add_handler/2, event/2]).

%% make a new event handler called Name
%% the handler function is no_op -- so we do nothing with the event
%% 產生一個 event handler,但收到 event 後,不做任何事情
make(Name) ->
    register(Name, spawn(fun() -> my_handler(fun no_op/1) end)).

%% 發送 event及處理該 event 的函數 Fun
add_handler(Name, Fun) -> Name ! {add, Fun}.

%% generate an event
event(Name, X) -> Name ! {event, X}.

my_handler(Fun) ->
    receive
        {add, Fun1} ->
            my_handler(Fun1);
        {event, Any} ->
            % 一開始是使用 no_op(_),後來可透過 add_handler 動態修改事件處理函數
            (catch Fun(Any)),
            my_handler(Fun)
    end.

no_op(_) -> void.

測試

1> event_handler:make(errors).
true
2> event_handler:event(errors, hi).
{event,hi}

發送 event 及 處理 event 的函數

想讓 event handler 做些事情,必須寫一個 callback module,並安裝在事件處理器中。

-module(motor_controller).

-export([add_event_handler/0]).

add_event_handler() ->
    event_handler:add_handler(errors, fun controller/1).

controller(too_hot) ->
    io:format("Turn off the motor~n");
controller(X) ->
    io:format("~w ignored event: ~p~n",[?MODULE, X]).

測試

3> motor_controller:add_event_handler().
{add,#Fun<motor_controller.0.125151531>}
4> event_handler:event(errors, cool).
motor_controller ignored event: cool
{event,cool}
5> event_handler:event(errors, too_hot).
Turn off the motor
{event,too_hot}

very late binding

如果寫一個函數把 event_handler:event 封裝起來

too_hot() ->
    event_handler:event(errors, too_hot).

如果出錯就呼叫 too_hot(),這會讓事件處理的模式固定。

erlang 處理事件的方式,讓我們能解除「事件發生」和「事件處理」之間的關係,我們可以在任何時候改變處理的方式,只要發送新的事件處理函數給事件處理器,就可以動態調整事件處裡的方法。也可以利用這個機制,在不停止server的狀況下,動態升級程式。

錯誤記錄器 error logger

OTP 內建可客製化的 event logger,可從三個部份討論(1) 函數呼叫,如何登錄錯誤到日誌 (2) configuration 設定錯誤登錄資料儲存的方式 (3) 錯誤的分析報告

登錄錯誤到日誌

@spec error_logger:error_msg(String) -> ok
    發送錯誤訊息到 error logger

@spec error_logger:error_msg(Format, Data) -> ok
    發送錯誤訊息到 error logger,參數跟 io:format(Format, Data) 一樣

@spec error_logger:error_report(Report) -> ok
    發送錯誤報告到 error logger
    @type Report = [{Tag, Data} | term() | string() | term()]
    @type Tag = term()
    @type Data = term()

configuration

預設可以在 erlang shell 中看到所有錯誤,我們可以另外將錯誤寫到單一文字檔,或是製作一天一個檔案的日誌檔,也可以製作轉動的環狀日誌檔。

標準的錯誤登錄器

啟動 erl 時,加入 boot 參數,預設值就是 start_clean

這是「適合編程」的環境,只有一種簡單的錯誤日誌形式

> erl -boot start_clean

這是「適合執行產品系統」的環境,SASL: System Architecture Support Libraries 會負責錯誤登錄、負載保護等等

> erl -boot start_sasl

日誌檔案的組態設定,最好放在 configuration file 裡面。

如果啟用 SASL,但又沒有提供 config file,就會發生錯誤。

D:\projectcase\erlang\erlangotp\ebin>erl -boot start_sasl

=PROGRESS REPORT==== 20-Feb-2014::11:40:27 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.34.0>},
                       {name,alarm_handler},
                       {mfargs,{alarm_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]
.....

=PROGRESS REPORT==== 20-Feb-2014::11:40:27 ===
         application: sasl
          started_at: nonode@nohost
Eshell V5.10.4  (abort with ^G)
1>
控制日誌的內容

錯誤登錄會自動產生三種報告

  1. 監督者報告
    當 OTP 監督者開始或停止監督一個 process 時 所發出的
  2. 進度報告
    當 OTP 監督者啟動或停止時 所發出的
  3. 當機報告
    當一個 OTP 行為終止的理由不正常 或 關閉時 所發出的

另外可以主動呼叫 error_handler module 的 function,來產生幾種等級的日誌報告,error_type 的類型有 error | error_report | info_msg | info_report | warning_msg | warning_report | crash_report | supervisor_report | progress,在分析日誌時,就能使用標籤來幫助我們決定那個日誌項目需要被檢視。

範例1

這會得到錯誤報告,而不是進度或其他報告

%% elog1.config
%% no tty 
[{sasl, [
     {sasl_error_logger, false}
    ]}].

測試

>erl -boot start_sasl -config elog1
Eshell V5.10.4  (abort with ^G)
1> error_logger:error_msg("I'm Error\n").
=ERROR REPORT==== 20-Feb-2014::11:54:08 ===
I'm Error
ok
範例2

會把 shell 所有東西複製到檔案中

%% elog2.config
%% single text file - minimal tty
[{sasl, [
     %% All reports go to this file
     {sasl_error_logger, {file, "d:/temp/error_logs/logfile"}}
    ]}].

測試

>erl -boot start_sasl -config elog2
Eshell V5.10.4  (abort with ^G)
1> error_logger:error_msg("I'm Error\n").

=ERROR REPORT==== 20-Feb-2014::12:01:01 ===
I'm Error
ok

如果 d:/temp/error_logs 目錄不存在,就不會產生檔案 logfile,檔案的內容如下:

=PROGRESS REPORT==== 20-Feb-2014::12:00:59 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.35.0>},
                       {name,alarm_handler},
                       {mfargs,{alarm_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]
....
範例3

所有錯誤日誌都會進入 rotating log 裡面

%% rotating log and minimal tty
[{sasl, [
     {sasl_error_logger, false},    
     %% define the parameters of the rotating log
     %% the log file directory 這裡是填目錄
     {error_logger_mf_dir,"d:/temp/error_logs"},    
         %% # bytes per logfile
     {error_logger_mf_maxbytes,10485760}, % 10 MB
         %% maximum number of logfiles
     {error_logger_mf_maxfiles, 10}
    ]}].
> erl -boot start_sasl -config elog3
範例4

在正式環境,增加 {errlog_type, error} 只記錄 error

%% rotating log and errors
[{sasl, [     
     %% minimise shell error logging
     {sasl_error_logger, false},
         %% only report errors
     {errlog_type, error},
     %% define the parameters of the rotating log
     %% the log file directory
     {error_logger_mf_dir,"d:/temp/error_logs"},    
         %% # bytes per logfile
     {error_logger_mf_maxbytes,10485760}, % 10 MB
         %% maximum number of
     {error_logger_mf_maxfiles, 10}
    ]}].

分析錯誤

rb 模組負責分析錯誤。

rb:help(). 可取得 help
rb:start([{max, 20}]). 啟動報告瀏覽器,只讀取 20 筆記錄
rb:show(1). 顯示 1 號錯誤
rb:list(). 錯誤列表
rb:grep(RegExp). 找出所有符合 RegExp 正規表示式 的報告

>erl -boot start_sasl -config elog3
1> rb:start([{max, 20}]).
rb: reading report...done.
{ok,<0.42.0>}
2> rb:show(1).

PROGRESS REPORT  <0.7.0>                                    2014-02-20 15:57:19
===============================================================================
application                                                                sasl
started_at                                                        nonode@nohost

ok

警報管理

警報處理器是 OTP gen_event 行為的 callback module。

-module(my_alarm_handler).
-behaviour(gen_event).

%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2, 
         handle_info/2, terminate/2]).

%% init(Args) must return {ok, State}
init(Args) ->
    io:format("*** my_alarm_handler init:~p~n",[Args]),
    {ok, 0}.

handle_event({set_alarm, tooHot}, N) ->
    error_logger:error_msg("*** Tell the Engineer to turn on the fan~n"),
    {ok, N+1};
handle_event({clear_alarm, tooHot}, N) ->
    error_logger:error_msg("*** Danger over. Turn off the fan~n"),
    {ok, N};
handle_event(Event, N) ->
    io:format("*** unmatched event:~p~n",[Event]),
    {ok, N}.

handle_call(_Request, N) -> Reply = N, {ok, Reply,  N}.

handle_info(_Info, N)    -> {ok, N}.

terminate(_Reason, _N)   -> ok.

測試

>erl -boot start_sasl -config elog3

% 一開始的 set_alarm,什麼事都不會發生,因為預設的處理器不做任何事情
1> alarm_handler:set_alarm(tooHot).
ok

=INFO REPORT==== 20-Feb-2014::15:01:57 ===
    alarm_handler: {set,tooHot}

% 替換 alarm_handler
2> gen_event:swap_handler(alarm_handler, {alarm_handler, swap}, {my_alarm_handler, xyz}).
*** my_alarm_handler init:{xyz,{alarm_handler,[tooHot]}}
ok

% 再一次呼叫 set_alarm 就會改用自訂的 alarm handler
3> alarm_handler:set_alarm(tooHot).

=ERROR REPORT==== 20-Feb-2014::15:02:49 ===
*** Tell the Engineer to turn on the fan
ok

% 清除 tooHot 的警報
4> alarm_handler:clear_alarm(tooHot).

=ERROR REPORT==== 20-Feb-2014::15:03:09 ===
*** Danger over. Turn off the fan
ok

查看警報的日誌報告

>erl -boot start_sasl -config elog3
Eshell V5.10.4  (abort with ^G)
1> rb:start([{max,20}]).
rb: reading report...done.
{ok,<0.42.0>}
2> rb:list().
  No                Type   Process       Date     Time
  ==                ====   =======       ====     ====
  14            progress  <0.30.0> 2014-02-20 15:57:19
  13            progress  <0.30.0> 2014-02-20 15:57:19
  12            progress  <0.30.0> 2014-02-20 15:57:19
  11            progress  <0.30.0> 2014-02-20 15:57:19
  10            progress  <0.24.0> 2014-02-20 15:57:19
   9            progress  <0.30.0> 2014-02-20 15:58:12
   8         info_report  <0.30.0> 2014-02-20 16:00:25
   7               error  <0.30.0> 2014-02-20 16:00:36
   6               error  <0.30.0> 2014-02-20 16:00:39
   5            progress  <0.30.0> 2014-02-20 16:02:03
   4            progress  <0.30.0> 2014-02-20 16:02:03
   3            progress  <0.30.0> 2014-02-20 16:02:03
   2            progress  <0.30.0> 2014-02-20 16:02:03
   1            progress  <0.24.0> 2014-02-20 16:02:03
ok
3> rb:show(6).

ERROR REPORT  <0.34.0>                                      2014-02-20 16:00:39
===============================================================================

*** Danger over. Turn off the fan
ok
4> rb:show(7).

ERROR REPORT  <0.34.0>                                      2014-02-20 16:00:36
===============================================================================

*** Tell the Engineer to turn on the fan
ok
4> rb:stop().
ok

應用伺服器

有兩個伺服器

質數

-module(prime_server).
-behaviour(gen_server).

-export([new_prime/1, start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     terminate/2, code_change/3]).

%% client functions
start_link() ->
    %% 呼叫 gen_server:start_link(Name, CallBackMod, StartArgs, Opts) 啟動 server
    %% 第一個呼叫的是 Mod:init(StartArgs)
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

new_prime(N) ->
    %% 20000 is a timeout (ms)
    gen_server:call(?MODULE, {prime, N}, 20000).


%% 6 server callback functions

%% 啟動時會被呼叫的函數,回傳值 {ok, State} 裡面的 State 會在其他函數中使用
init([]) ->
    %% Note we must set trap_exit = true if we 
    %% want terminate/2 to be called when the application is stopped
    %% 一定要設定 trap_exit 為 true,這樣在  app 停止時,才會呼叫 terminate/2
    process_flag(trap_exit, true),
    io:format("~p starting~n",[?MODULE]),
    {ok, 0}.

handle_call({prime, K}, _From, N) -> 
    {reply, make_new_prime(K), N+1}.

handle_cast(_Msg, N)  -> {noreply, N}.

handle_info(_Info, N)  -> {noreply, N}.

terminate(_Reason, _N) -> 
    io:format("~p stopping~n",[?MODULE]),
    ok.

code_change(_OldVsn, N, _Extra) -> {ok, N}.

% private function
make_new_prime(K) ->
    if
    K > 100 ->
        alarm_handler:set_alarm(tooHot),
        N = lib_primes:make_prime(K),
        alarm_handler:clear_alarm(tooHot),
        N;
    true ->
        lib_primes:make_prime(K)
    end.

面積

-module(area_server).
-behaviour(gen_server).

-export([area/1, start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     terminate/2, code_change/3]).

%% client functions
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

area(Thing) ->
    gen_server:call(?MODULE, {area, Thing}).


%% 6 server callback functions
%% 啟動時會被呼叫的函數,回傳值 {ok, State} 裡面的 State 會在其他函數中使用
init([]) ->
    %% Note we must set trap_exit = true if we 
    %% want terminate/2 to be called when the application
    %% is stopped
    %% 一定要設定 trap_exit 為 true,這樣在  app 停止時,才會呼叫 terminate/2
    process_flag(trap_exit, true),
    io:format("~p starting~n",[?MODULE]),
    {ok, 0}.

handle_call({area, Thing}, _From, N) -> {reply, compute_area(Thing), N+1}.

handle_cast(_Msg, N)  -> {noreply, N}.

handle_info(_Info, N)  -> {noreply, N}.

terminate(_Reason, _N) -> 
    io:format("~p stopping~n",[?MODULE]),
    ok.

code_change(_OldVsn, N, _Extra) -> {ok, N}.

%% private function
compute_area({square, X})       -> X*X;
compute_area({rectongle, X, Y}) -> X*Y.

監督樹

所有監督樹都是行程樹,樹上方的行程(監督者)會監控下面的行程(工作者),如果工作者發生問題,監督者就可以重新啟動它。監督樹有兩種:

  1. 一對一監督樹
    如果工作者失敗,就會被監督者重新啟動。監督者會同時監督多個行程,一對一就是針對每一個行程,都會單獨地進行重新啟動的機制。
  2. 一對多監督樹
    如果任一工作者失敗,監督者監控的所有工作者都會被 kill 掉,然後重新啟動所有工作行程。

監督樹的指定是透過下面這樣的函數

init(...) ->
    {ok, {RestartStrategy, MaxRestarts, Time},
        [Worker1, Worker2, ...]}.

RestartStrategy 有兩種 (1) one_for_one (2) all_for_one
MaxRestarts 與 Time 是設定一個 「重新啟動的頻率」,如果監督者在 Time 秒內重新啟動工作者超過 MaxRestarts 次,此監督者就會終止所有工作行程,並終止自己。這個機制是為了避免發生「行程當機->重新啟動->行程當機」 的無窮迴圈。

Worker1, Worker2,... 都是 tuple,描述如何重新啟動每一個工作者行程。

Worker
    {Tag, {Mod, Fun, ArgList},
        Restart,
        Shutdown,
        Type,
        [Mod1]}

Tag
    atom,用來稱呼該工作者行程
{Mod, Fun, ArgList}
    定義了監督者會用到的函數,當作 apply(Mod, Fun, ArgList) 的參數
Restart = permanent | transient | temporary
    permanent 行程一定會被重新啟動
    transient 只有在「因非正常結束碼而停止」之後,才會重新啟動
    temporary 不會被重新啟動
Shutdown
    停止 server 時所能耗用的最大時間,超過時間,就會被直接 kill
Type = worker | supervisor
    被監督行程的型別,我們可以在工作者行程的位置改放監督者行程,建構出監督者的樹
[Mod1]
    如果子行程是監督者或 gen_server 行為callback module,這就是 callback module 的名稱

範例

-module(sellaprime_supervisor).
-behaviour(supervisor).

-export([start/0, start_in_shell_for_testing/0, start_link/1, init/1]).

start() ->
    spawn(fun() ->
                  supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = [])
          end).

start_in_shell_for_testing() ->
    {ok, Pid} = supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = []),
    unlink(Pid).

start_link(Args) ->
    supervisor:start_link({local,?MODULE}, ?MODULE, Args).

init([]) ->
    %% Install my personal error handler
    %  替換自訂的 error handler
    gen_event:swap_handler(alarm_handler, 
                           {alarm_handler, swap},
                           {my_alarm_handler, xyz}),

    {ok, {{one_for_one, 3, 10},
          [{tag1, 
            {area_server, start_link, []},
            permanent, 
            10000, 
            worker, 
            [area_server]},
           {tag2, 
            {prime_server, start_link, []},
            permanent, 
            10000, 
            worker, 
            [prime_server]}
          ]}}.

啟動系統

>erl -boot start_sasl -config elog3
Eshell V5.10.4  (abort with ^G)
1> sellaprime_supervisor:start_in_shell_for_testing().
*** my_alarm_handler init:{xyz,{alarm_handler,[]}}
area_server starting
prime_server starting
true
2> area_server:area({square, 10}).
100

% area_server 會停掉,並重新啟動
3> area_server:area({rectangle, 10, 20}).
area_server stopping
area_server starting
** exception exit: {{function_clause,
                        [{area_server,compute_area,
                             [{rectangle,10,20}],
                             [{file,
                                  "d:/projectcase/erlang/erlangotp/src/area_serv
er.erl"},
                              {line,50}]},
                         {area_server,handle_call,3,
                             [{file,
                                  "d:/projectcase/erlang/erlangotp/src/area_serv
er.erl"},
                              {line,37}]},
                         {gen_server,handle_msg,5,
                             [{file,"gen_server.erl"},{line,585}]},
                         {proc_lib,init_p_do_apply,3,
                             [{file,"proc_lib.erl"},{line,239}]}]},
                    {gen_server,call,[area_server,{area,{rectangle,10,20}}]}}
     in function  gen_server:call/2 (gen_server.erl, line 180)
4>
=ERROR REPORT==== 20-Feb-2014::18:08:09 ===
** Generic server area_server terminating
** Last message in was {area,{rectangle,10,20}}
** When Server state == 1
** Reason for termination ==
** {function_clause,
       [{area_server,compute_area,
            [{rectangle,10,20}],
            [{file,"d:/projectcase/erlang/erlangotp/src/area_server.erl"},
             {line,50}]},
        {area_server,handle_call,3,
            [{file,"d:/projectcase/erlang/erlangotp/src/area_server.erl"},
             {line,37}]},
        {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,585}]},
        {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,239}]}]}
4> area_server:area({square, 20}).
400
5> prime_server:new_prime(20).
Generating a 20 digit prime ..........................
11511342604390163281
6> prime_server:new_prime(120).
Generating a 120 digit prime .
=ERROR REPORT==== 20-Feb-2014::18:08:48 ===
*** Tell the Engineer to turn on the fan
................................................................................
31109310729316846838871494679201035944025034010885289775272667568919068598600821
8311374378220338982779418876998717503511

=ERROR REPORT==== 20-Feb-2014::18:08:51 ===
*** Danger over. Turn off the fan

接下來查閱報表。

7> rb:start([{max, 20}]).
rb: reading report...done.
{ok,<0.53.0>}
8> rb:list().
  No                Type      Process       Date     Time
  ==                ====      =======       ====     ====
  13            progress     <0.30.0> 2014-02-20 18:07:21
  12            progress     <0.30.0> 2014-02-20 18:07:21
  11            progress     <0.30.0> 2014-02-20 18:07:21
  10            progress     <0.30.0> 2014-02-20 18:07:21
   9            progress     <0.24.0> 2014-02-20 18:07:21
   8            progress     <0.24.0> 2014-02-20 18:07:39
   7            progress     <0.24.0> 2014-02-20 18:07:39
   6               error     <0.24.0> 2014-02-20 18:08:09
   5        crash_report  area_server 2014-02-20 18:08:09
   4   supervisor_report     <0.24.0> 2014-02-20 18:08:09
   3            progress     <0.24.0> 2014-02-20 18:08:09
   2               error     <0.30.0> 2014-02-20 18:08:48
   1               error     <0.30.0> 2014-02-20 18:08:51
ok
9> rb:show(5).

CRASH REPORT  <0.43.0>                                      2014-02-20 18:08:09
===============================================================================
Crashing process
   initial_call                              {area_server,init,['Argument__1']}
   pid                                                                 <0.43.0>
   registered_name                                                  area_server
   error_info
         {exit,
            {function_clause,
                [{area_server,compute_area,
                     [{rectangle,10,20}],
                     [{file,
                          "d:/projectcase/erlang/erlangotp/src/area_server.erl",
                      {line,50}]},
                 {area_server,handle_call,3,
                     [{file,
                          "d:/projectcase/erlang/erlangotp/src/area_server.erl",
                      {line,37}]},
                 {gen_server,handle_msg,5,
                     [{file,"gen_server.erl"},{line,585}]},
                 {proc_lib,init_p_do_apply,3,
                     [{file,"proc_lib.erl"},{line,239}]}]},
            [{gen_server,terminate,6,[{file,"gen_server.erl"},{line,744}]},
             {proc_lib,init_p_do_apply,3,
                 [{file,"proc_lib.erl"},{line,239}]}]}
   ancestors                                   [sellaprime_supervisor,<0.40.0>]
   messages                                                                  []
   links                                                             [<0.42.0>]
   dictionary                                                                []
   trap_exit                                                               true
   status                                                               running
   heap_size                                                                987
   stack_size                                                                27
   reductions                                                               200

ok

包裝應用

%% sellaprime.app
{application, sellaprime, 
 [{description, "The Prime Number Shop"},
  {vsn, "1.0"},
  {modules, [sellaprime_app, sellaprime_supervisor, area_server, 
         prime_server, lib_lin, lib_primes, my_alarm_handler]},    
  {registered,[area_server, prime_server, sellaprime_super]},
  {applications, [kernel,stdlib]},
  {mod, {sellaprime_app,[]}},
  {start_phases, []}
 ]}.

sellaprime.app 的 callback module,必須有 start/2 跟 stop/1 function

% sellaprime_app.erl
-module(sellaprime_app).
-behaviour(application).
-export([start/2, stop/1]).

start(_Type, StartArgs) ->
    sellaprime_supervisor:start_link(StartArgs).

stop(_State) ->
    ok.

測試
application:loaded_applications(). 取得 otp 載入的applications
application:load(sellaprime). 載入 sellaprime.app
application:start(sellaprime). 啟動 sellaprime
application:stop(sellaprime). 停止 sellaprime
application:unload(sellaprime). 卸載 sellaprime.app

>erl -boot start_sasl -config elog3
Eshell V5.10.4  (abort with ^G)
1> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.16.4"},
 {sasl,"SASL  CXC 138 11","2.3.4"},
 {stdlib,"ERTS  CXC 138 10","1.19.4"}]
2> application:load(sellaprime).
ok
3> application:loaded_applications().
[{sellaprime,"The Prime Number Shop","1.0"},
 {kernel,"ERTS  CXC 138 10","2.16.4"},
 {sasl,"SASL  CXC 138 11","2.3.4"},
 {stdlib,"ERTS  CXC 138 10","1.19.4"}]
4> application:start(sellaprime).
*** my_alarm_handler init:{xyz,{alarm_handler,[]}}
area_server starting
prime_server starting
ok
5> area_server:area({square, 20}).
400
6> prime_server:new_prime(20).
Generating a 20 digit prime ...................
28361723754284313301
7> application:stop(sellaprime).
prime_server stopping
area_server stopping
ok
=INFO REPORT==== 21-Feb-2014::10:29:12 ===
    application: sellaprime
    exited: stopped
    type: temporary
8> application:loaded_applications().
[{sellaprime,"The Prime Number Shop","1.0"},
 {kernel,"ERTS  CXC 138 10","2.16.4"},
 {sasl,"SASL  CXC 138 11","2.3.4"},
 {stdlib,"ERTS  CXC 138 10","1.19.4"}]
9> application:unload(sellaprime).
ok
10> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.16.4"},
 {sasl,"SASL  CXC 138 11","2.3.4"},
 {stdlib,"ERTS  CXC 138 10","1.19.4"}]
11> init:stop().
ok

完整的應用包含的檔案

  1. area_server.erl
    area_server 這是 gen_server 的 callback module
  2. prime_server.erl
    prime_server 這是 gen_server 的 callback module
    2.1 lib_primes.erl, lib_lin.erl
    產生 primes 的 module
  3. sellaprime_supervisor.erl
    監督者 callback module
  4. sellaprime_app.erl
    sellaprime application callback module
  5. my_alarm_handler.erl
    gen_event 事件處理 callback module
  6. sellaprime.app
    sellaprime application
  7. elog3.config
    error logger configuration

運作流程如下

  1. 啟動系統 > erl -boot start_sasl -config elog3.config
    sellaprime.app 必須在 erlang 啟動的根目錄,或在該目錄的某個次目錄
    應用控制器會在 sellaprime.app 中取得 {mod, ...} 宣告,也就是 sellaprime_app.erl

  2. 呼叫 sellaprime_app:start/2

  3. sellaprime_app:start/2 內部呼叫 sellaprime_supervisor:start_link/2,然後啟動了 sellaprime 監督者

  4. 呼叫 sellaprime_supervisor:init/1,這會安裝一個 error logger 處理器,並回傳重新啟動的策略

  5. sellaprime 的監督者會啟動 prime_server 與 area_server

  6. 呼叫 application:stop(sellaprime) 或 init:stop() 就可以停止 sellaprime application

GUI 應用監控器

這是可以檢視 application 的 GUI 程式

appmon:start().

參考

Erlang and OTP in Action
Programming Erlang: Software for a Concurrent World