diff --git a/app/IM/Makefile b/app/IM/Makefile new file mode 100644 index 0000000..cc958f0 --- /dev/null +++ b/app/IM/Makefile @@ -0,0 +1,26 @@ +ERLC = erlc +ERLC_OPTS = +ERL = erl +ERL_LIB_DIR = + +SRCD = src +EBIND = ebin-$(OTP) + +ERLF = $(wildcard $(SRCD)/*.erl) +BEAMF = $(patsubst $(SRCD)/%.erl,$(EBIND)/%.beam,$(ERLF)) + +.PHONY: app clean + +app: $(BEAMF) + +$(EBIND)/%.beam: $(SRCD)/%.erl + $(ERLC) $(ERLC_OPTS) -o$(EBIND) $< + +$(BEAMF): | $(EBIND) + +$(EBIND): + mkdir -p $(EBIND) + +clean: + $(RM) -rf ebin-* + diff --git a/app/IM/src/chat_db.erl b/app/IM/src/chat_db.erl new file mode 100644 index 0000000..daba780 --- /dev/null +++ b/app/IM/src/chat_db.erl @@ -0,0 +1,354 @@ +%%%-------------------------------------------------------------------- +%%% CHAT_DB MODULE +%%% +%%% @author: Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Chat_DB module for the Distributed Erlang instant messenger (IM) +%%% application developed as a real benchmark for the Scalable +%%% Distributed Erlang extension of the Erlang/OTP language. +%%% +%%% This module implementes the functionality for the database that +%%% stores the chat sessions happening at a given time in a system +%%% similar to the system described in the Section 2 of the document +%%% "Instant Messenger Architectures Design Proposal". +%%% @end +%%% Created: 1 Jul 2014 by Mario Moro Hernandez +%%%-------------------------------------------------------------------- + +-module(chat_db). +-export([start/1, stop/1, chat_db/1]). + + +%%%==================================================================== +%%% API +%%%==================================================================== + +%%--------------------------------------------------------------------- +%% @doc +%% Starts the chat sessions database. +%% +%% @spec start(DB_Name) +%% @end +%%--------------------------------------------------------------------- +start(DB_Name) -> + global:register_name(DB_Name, spawn(fun() -> chat_db(DB_Name) end)). + +%%-------------------------------------------------------------------- +%% @doc +%% Stops the chat sessions database. +%% +%% @spec stop(DB_Name) +%% @end +%%-------------------------------------------------------------------- +stop(DB_Name) -> + destroy(DB_Name), + global:unregister_name(DB_Name). + +%%-------------------------------------------------------------------- +%% @doc +%% chat_db is first stage of the database process. It creates an ets +%% table after the atom specified as the argument DB_Name. +%% +%% @spec chat_db(DB_Name) +%% @end +%%-------------------------------------------------------------------- +chat_db(DB_Name) -> + case ets:info(DB_Name) of + undefined -> + create(DB_Name), + chat_db_loop(DB_Name); + _ -> + chat_db_loop(DB_Name) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% chat_db_loop constitutes the database process, and offers an inter- +%% face to interact with the ets table, allowing the input or retrieval +%% of information concerning the chat sessions in the system. +%% +%% @spec chat_db_local(DB_Name) +%% @end +%%-------------------------------------------------------------------- +chat_db_loop(DB_Name) -> + receive + {From, add, Session_Name, Session_Pid, C1, C1_Pid, C2, C2_Pid} -> + add_session(DB_Name, Session_Name, Session_Pid, C1, C1_Pid, C2, C2_Pid), + case From of + undefined -> + ok; + _ -> + From ! {session_added, ok} + end, + chat_db_loop(DB_Name); + {From, remove, Session_Name} -> + remove_session(DB_Name, Session_Name), + case From of + undefined -> + ok; + _ -> + From ! {session_removed, ok} + end, + chat_db_loop(DB_Name); + {From, full_db} -> + A = retrieve_db(DB_Name), + case From of + undefined -> + ok; + _ -> + From ! A + end, + chat_db_loop(DB_Name); + {From, peak, Session_Name} -> + S = peak_session(DB_Name, Session_Name), + case From of + undefined -> + ok; + _ -> + From ! S + end, + chat_db_loop(DB_Name); + {From, peak_by_pid, Session_Pid} -> + S = peak_session_pid(DB_Name, Session_Pid), + case From of + undefined -> + ok; + _ -> + From ! S + end, + chat_db_loop(DB_Name); + {From, session_pid, Session_Name} -> + Pid = session_pid(DB_Name, Session_Name), + case From of + undefined -> + ok; + _ -> + From ! Pid + end, + chat_db_loop(DB_Name); + {From, opened_sessions, Client} -> + S = client_sessions(DB_Name, Client), + case From of + undefined -> + ok; + _ -> + From ! S + end, + chat_db_loop(DB_Name); + {From, recover, Target_DB_Name, Source_DB_Name} -> + recover_db(Target_DB_Name, Source_DB_Name), + case From of + undefined -> + ok; + _ -> + From ! {recovered, ok} + end, + chat_db_loop(DB_Name); + {From, stop} -> + stop(DB_Name), + case From of + undefined -> + ok; + _ -> + From ! {chat_session_db_destroyed, ok} + end; + Other -> + io:format("Something failed at Chat_DB. Received: ~p", [Other]), + chat_db_loop(DB_Name) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Creates a new ets table -named Table_Name- to store the different +%% chat sessions active in the system. +%% +%% @spec create(Table_Name) -> Table_Name | {error, Error} +%% @end +%%--------------------------------------------------------------------- +create(Table_Name) -> + ets:new(Table_Name, [set, named_table]). + +%%--------------------------------------------------------------------- +%% @doc +%% Destroys ets table named Table_Name. +%% +%% @spec destroy(Table_Name) -> Table_Name | {error, Error} +%% @end +%%--------------------------------------------------------------------- +destroy(Table_Name) -> + ets:delete(Table_Name). + +%%--------------------------------------------------------------------- +%% @doc +%% Adds a chat session to the chat sessions database. +%% +%% @spec add_session(Table_Name, Chat_Session_Pid, +%% Client_A, Client_B) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +add_session(Table_Name, Chat_Session_Name, Chat_Session_Pid, Client_A, Client_A_Pid, Client_B, Client_B_Pid) -> + ets:insert(Table_Name, {Chat_Session_Name, Chat_Session_Pid, Client_A, Client_A_Pid, Client_B, Client_B_Pid}). + +%%--------------------------------------------------------------------- +%% @doc +%% Removes a chat session from the chat sessions database. +%% +%% @spec remove_session(Table_Name, Chat_Session_Pid) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +remove_session(Table_Name, Chat_Session_Name) -> + ets:delete(Table_Name, Chat_Session_Name). + +%%--------------------------------------------------------------------- +%% @doc +%% Returns the contents of the whole database as a list of lists, where +%% each of the nested lists is one session. (Not in use, only for debug +%% purposes). +%% +%% @spec retrieve_db(Table_Name) -> +%% [[session_1], ..., [session_n]] | {error, Error} +%% @end +%%--------------------------------------------------------------------- +retrieve_db(Table_Name) -> + ets:match(Table_Name, {'$0', '$1', '$2', '$3', '$4', '$5'}). + +%%--------------------------------------------------------------------- +%% @doc +%% Returns a list containing the data of the session passed as argument. +%% +%% @spec peak_session(Table_Name, Session_Name) -> +%% [Session_Name, Session_Pid, +%% Client_A, Client_A_Pid, ClientB, Client_B_Pid] | {error, Error} +%% @end +%%--------------------------------------------------------------------- +peak_session(Table_Name, Session_Name) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2', '$3', '$4', '$5'}, + [{'==', '$0', Session_Name}], + [['$0', '$1', '$2', '$3', '$4', '$5']]}]), + case L == [] of + true -> + L; + false -> + lists:last(L) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Returns a list containing the data of the session corresponding to +%% the pid passed as argument. +%% +%% @spec peak_session(Table_Name, Session_Pid) -> +%% [Session_Name, Session_Pid, +%% Client_A, Client_A_Pid, ClientB, Client_B_Pid] | {error, Error} +%% @end +%%--------------------------------------------------------------------- +peak_session_pid(Table_Name, Session_Pid) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2', '$3', '$4', '$5'}, + [{'==', '$1', Session_Pid}], + [['$0', '$1', '$2', '$3', '$4', '$5']]}]), + case L == [] of + true -> + L; + false -> + lists:last(L) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Returns the Pid of a given session passed as argument. +%% +%% @spec session_pid(Table_Name, Session_Name) -> Session_Pid | [] +%% @end +%%--------------------------------------------------------------------- +session_pid(Table_Name, Session_Name) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2', '$3', '$4', '$5'}, + [{'==', '$0', Session_Name}], + [['$1']]}]), + case L == [] of + true -> + L; + false -> + lists:last(lists:last(L)) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Returns a list containing all the sessions -and the information of +%% these sessions- in which the client passed as the argument is one of +%% participants. It returns an empty list if the client is not involved +%% in any chat at the time of invocation. +%% +%% @spec retrieve_server(Table_Name, Session_Pid) -> +%% [Session_1, ..., Session_k] | [] +%% @end +%%--------------------------------------------------------------------- +client_sessions(Table_Name, Client) -> + ets:safe_fixtable(Table_Name, true), + Sessions = opened_sessions(Table_Name, Client, ets:first(Table_Name), []), + ets:safe_fixtable(Table_Name, false), + Sessions. + +%%--------------------------------------------------------------------- +%% @doc +%% Replicates the chat session table specified in the Source argument, +%% in a new table with name Table_Name. +%% +%% @spec retrieve_server(Table_Name, Session_Pid) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +recover_db(Destination, Source) -> + ets:safe_fixtable(Source, true), + replicate(Source, Destination, ets:first(Source)), + ets:safe_fixtable(Source, false). + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to the function client_sessions(Table_Name, Client). +%% This function traverses the ets table containing all the conversations, +%% and puts in a list all those in which the client is one of the +%% participants. +%% +%% @spec opened_sessions(Table_Name, Client, Cursor, Sessions) -> +%% [Session_1, ..., Session_n] | [] +%% @end +%%--------------------------------------------------------------------- +opened_sessions(Table_Name, Client, Cursor, Sessions) -> + case Cursor of + '$end_of_table' -> + Sessions; + Cursor -> + case (element(3, lists:last(ets:lookup(Table_Name, Cursor))) == Client) or + (element(4, lists:last(ets:lookup(Table_Name, Cursor))) == Client) of + true -> + opened_sessions(Table_Name, Client, ets:next(Table_Name, Cursor), + (Sessions ++ [lists:last(ets:lookup(Table_Name, Cursor))])); + false -> + opened_sessions(Table_Name, Client, ets:next(Table_Name, Cursor), Sessions) + end + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to the function recover_db(Table_Name, Source). This +%% function traverses the source table specified in Source, and feeds the +%% data in the destination table. +%% +%% @spec retrieve_server(Table_Name, Session_Pid) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +replicate(Source, Destination, Key) -> + case Key of + '$end_of_table' -> + true; + _ -> + S = peak_session(Source, Key), + global:whereis_name(Destination) ! {undefined ,add, + lists:nth(1, S), + lists:nth(2, S), + lists:nth(3, S), + lists:nth(4, S), + lists:nth(5, S), + lists:nth(6, S)}, + replicate(Source, Destination, ets:next(Source, Key)) + end. diff --git a/app/IM/src/client.erl b/app/IM/src/client.erl new file mode 100644 index 0000000..6b4bcf0 --- /dev/null +++ b/app/IM/src/client.erl @@ -0,0 +1,285 @@ +%%%-------------------------------------------------------------------- +%%% CLIENT MODULE +%%% +%%% @author: Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Client module for the Distributed Erlang instant messenger (IM) +%%% application developed as a real benchmark for the Scalable +%%% Distributed Erlang extension of the Erlang/OTP language. +%%% +%%% This module implementes the functionality for client processes +%%% in a system similar to the system described in the Section 2 of +%%% the document "Instant Messenger Architectures Design Proposal". +%%% @end +%%% Created: 4 Jul 2014 by Mario Moro Hernandez +%%%-------------------------------------------------------------------- + +-module(client). +-export([login/1,logout/1, message/3, stop_client/1]). + +%%--------------------------------------------------------------------- +%% @doc +%% Starts the client by spawing and registering a client process. +%% +%% @spec start(Client_Name) -> Pid | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +start_client(Client_Name) -> + global:whereis_name(routers_db) ! {retrieve_routers, self()}, + receive + {Routers_List, router_list} -> + Pid = spawn(fun() -> client({Client_Name, Routers_List}) end), + Answer = register(Client_Name, Pid), + Pid ! Answer; + Other -> + io:format("Unable to start the client. No routers list received.~n"), + io:format("start_client/1 received: ~p~n", [Other]) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% Stops the client specified as a parameter by sending an {'EXIT', ok} +%% message. +%% +%% @spec stop(Client_Name) +%% @end +%%-------------------------------------------------------------------- +stop_client(Client_Name) -> + whereis(Client_Name) ! {'EXIT', ok}. + +%%-------------------------------------------------------------------- +%% @doc +%% This is the function that constitutes the client process. It has two +%% different states. Initially, it is spawned and registered using the +%% client name, only. Once it has been registered, it waits until it +%% receives the Pid of its monitor. +%% +%% When the client_monitor Pid has been received, the login process is +%% concluded. +%% +%% @spec client({Client_Name, Client_Monitor_Pid, Chat_Pids}) +%% @end +%%-------------------------------------------------------------------- +client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}) -> + receive + %% Chat Logic + {Sender, Receiver, Message, send_message} -> + Session_Name = chat_session_name(Sender, Receiver), + case retrieve(Session_Name, Chat_Pids) of + [] -> + Router_Pid = pick_router(Routers_List), + case rpc:pinfo(Router_Pid) of + undefined -> + global:whereis_name(routers_db) ! {retrieve_routers, self()}, + receive + {New_Routers_List, router_list} -> + New_Router_Pid = pick_router(Routers_List), + New_Router_Pid ! {Sender, Receiver, Message, send_message}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, New_Routers_List}) + end; + _Other -> + Router_Pid ! {Sender, Receiver, start_chat_session}, + receive + {chat_session_success_sender, Session_Name, Chat_Session_Pid} -> + io:format("Client_A received {chat_session_success, Chat_Session_Pid}.~n"), + New_Chat_Pids = Chat_Pids ++ [{Session_Name, Chat_Session_Pid}], + Chat_Session_Pid ! {Sender, Receiver, now(), Message}, + client({Client_Name, Client_Monitor_Pid, New_Chat_Pids, Routers_List}); + {receiver_not_found, Receiver} -> + io:format("Sorry, but ~p is not logged in.~n", [Receiver]), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + Other -> + io:format("Client received ~p when trying to start a chat session.~n", [Other]), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}) + end + end; + Chat_Session_Pid -> + Chat_Session_Pid ! {Sender, Receiver, now(), Message}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}) + end; + {chat_session_success_receiver, Session_Name, Chat_Session_Pid} -> + io:format("Client_B received {chat_session_success, Chat_Session_Pid}.~n"), + New_Chat_Pids = Chat_Pids ++ [{Session_Name, Chat_Session_Pid}], + Client_Monitor_Pid ! {add_chat_session, {Session_Name, Chat_Session_Pid}}, + client({Client_Name, Client_Monitor_Pid, New_Chat_Pids, Routers_List}); + {Metadata, message_delivered_ok} -> + Timestamp_2 = now(), + {_, _, _, Timestamp_1} = Metadata, + Latency = timer:now_diff(Timestamp_2, Timestamp_1)/2, + {{_Y, _M, _D}, {H, M, S}} = calendar:now_to_local_time(Timestamp_2), + io:format("ok. ~p:~p:~p Latency = ~p microseconds~n", [H, M, S, Latency]), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {Sender, Message, Chat_Session_Pid, Timestamp, receive_message} -> + io:format("~p wrote: ~p~n", [Sender, Message]), + Chat_Session_Pid ! {Sender, Timestamp, message_delivered_ok}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {Chat_Session_Name, {'EXIT', ok}} -> + New_Chat_Pids = lists:keydelete(Chat_Session_Name, 1, Chat_Pids), + io:format("~p removed from Chat_Pids.~n", [Chat_Session_Name]), + Client_Monitor_Pid ! {remove_chat_session, Chat_Session_Name}, + client({Client_Name, Client_Monitor_Pid, New_Chat_Pids, Routers_List}); + %% Recovery logic + {opened_chat_sessions, Client_Monitor_Recover_Pid} -> + Client_Monitor_Recover_Pid ! {chat_sessions, Chat_Pids}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {new_monitor_pid, New_Client_Monitor_Pid} -> + client({Client_Name, New_Client_Monitor_Pid, Chat_Pids, Routers_List}); + %% Logout Logic + {logout} -> + Client_Monitor_Pid ! {Client_Name, self(), logout}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + %% Client Termination Logic + {error, client_already_logged_in} -> + io:format("You are logged in already.~n"), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {'EXIT', ok} -> + unregister(Client_Name), + io:format("~p is stopped now.~n", [Client_Name]); + %% Trap for unexpected messages + Other -> + io:format("Something is failing at client(/4).~n"), + io:format("Received: ~p~n", [Other]), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}) + end; + +%%-------------------------------------------------------------------- +%% @doc +%% client(Client_Name) is the client process when the client is not +%% logged in yet. When the client_monitor Pid has been received, it +%% changes the state and changes to the full logged-in client; i.e., +%% the client({Client_Name, Client_Pid, Client_Monitor_Pid}) function +%% defined above. +%% +%% @spec client(Client_Name) +%% @end +%%-------------------------------------------------------------------- +client({Client_Name, Routers_List}) -> + receive + true -> %% This allows to have a client up, but not logged-in + client({Client_Name, Routers_List}); + false -> + {error, client_already_logged_in}; + {login_success, Client_Monitor_Pid} -> + io:format("Client is successfuly logged in.~n"), + client({Client_Name, Client_Monitor_Pid, [], Routers_List}); + {error, client_already_logged_in} -> + io:format("You are logged in already.~n"), + unregister(Client_Name); + Other -> + io:format("This is failing at client(/2).~n"), + io:format("Received: ~p~n", [Other]), + client({Client_Name, Routers_List}) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% login function starts a client named after the atom passed as +%% parameter and registers it in the system. If the client is logged in +%% already, then returns an error message. +%% +%% @spec login(Client_Name) -> yes | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +login(Client_Name) -> + Client_Pid = whereis(Client_Name), + case Client_Pid of + undefined-> + start_client(Client_Name), + global:whereis_name(routers_db) ! {retrieve_routers, self()}, + receive + {Routers_List, router_list} -> + Router_Pid = pick_router(Routers_List), + Router_Pid ! {Client_Name, whereis(Client_Name), login}; + Other -> + io:format("Login failed at retrieving routers list.~nReceived: ~p~n", [Other]) + end, + yes; + _ -> + io:format("You are logged in already.~n") + %%{error, client_already_logged_in} + end. + +%%--------------------------------------------------------------------- +%% @doc +%% logout unegisters the client from the system. +%% +%% @spec logout(Client_Name) -> ok | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +logout(Client_Name) -> + Client_Pid = whereis(Client_Name), + case Client_Pid of + undefined -> + io:format("Client ~p is not logged in.~n",[Client_Name]), + {error, client_not_logged_in}; + _ -> + Client_Pid ! {logout}, + ok + end. + +%%-------------------------------------------------------------------- +%% @doc +%% message/3 enables the communication between two clients, sending +%% the message passed as the parameter Message to the client specified +%% as the To parameter. +%% +%% @spec message(From, To, Message) -> yes | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +message(From, To, Message) -> + whereis(From) ! {From, To, Message, send_message}, + yes. + +%%-------------------------------------------------------------------- +%% @doc +%% This function returns the pid of a router, from a routers list that +%% is passed as parameter. +%% +%% @spec pick_router(Routers_List) -> Pid | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +pick_router(Routers_List) -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + Router = lists:nth(random:uniform(length(Routers_List)),Routers_List), + {_, Router_Pid} = Router, + Router_Pid. + +%%-------------------------------------------------------------------- +%% @doc +%% chat_session_name/2 returns the name of a chat session, built after +%% the names of the clients passed as parameters. +%% +%% @spec chat_session_name(Sender, Receiver) -> +%% string() | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +chat_session_name(Sender, Receiver) -> + case atom_to_list(Sender) < atom_to_list(Receiver) of + true -> + {Client1, Client2} = {Sender, Receiver}; + false -> + {Client1, Client2} = {Receiver, Sender} + end, + string:join(["chat_session", + atom_to_list(Client1), + atom_to_list(Client2)], "_"). + +%%-------------------------------------------------------------------- +%% @doc +%% This function returns the pid of the chat session specified as the +%% Session_Name argument (if any). +%% +%% @spec chat_session_name(Sender, Receiver) -> +%% Pid | [] +%% @end +%%-------------------------------------------------------------------- +retrieve(Session_Name, Chat_Pids) -> + case lists:keyfind(Session_Name, 1, Chat_Pids) of + false -> + []; + {Chat_Session_Name, Chat_Session_Pid} -> + element(2,{Chat_Session_Name, Chat_Session_Pid}) + end. + diff --git a/app/IM/src/client_db.erl b/app/IM/src/client_db.erl new file mode 100644 index 0000000..76088d5 --- /dev/null +++ b/app/IM/src/client_db.erl @@ -0,0 +1,410 @@ +%%%-------------------------------------------------------------------- +%%% CLIENT_DB MODULE +%%% +%%% @author: Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Client_DB module for the Distributed Erlang instant messenger +%%% (IM) application developed as a real benchmark for the Scalable +%%% Distributed Erlang extension of the Erlang/OTP language. +%%% +%%% This module implementes the functionality for the database that +%%% stores the cients that are loggen in at a given time in a system +%%% similar to the system described in the Section 2 of the document +%%% "Instant Messenger Architectures Design Proposal". +%%% @end +%%% Created: 1 Jul 2014 by Mario Moro Hernandez +%%%-------------------------------------------------------------------- + +-module(client_db). +-export([start/1, stop/1, start_local/1, stop_local/1, client_db/1]). + + +%%%==================================================================== +%%% API +%%%==================================================================== + +%%--------------------------------------------------------------------- +%% @doc +%% Starts the clients database. +%% +%% @spec start(DB_Name) +%% @end +%%--------------------------------------------------------------------- +start(DB_Name) -> + global:register_name(DB_Name, spawn_link(fun() -> client_db(DB_Name) end)). + +%%-------------------------------------------------------------------- +%% @doc +%% Stops the client database. +%% +%% @spec stop(DB_Name) +%% @end +%%-------------------------------------------------------------------- +stop(DB_Name) -> + destroy(DB_Name), + global:unregister_name(DB_Name). + +%%--------------------------------------------------------------------- +%% Starts the clients database, and registers it locally. +%% +%% @spec start_local(DB_Name) +%% @end +%%--------------------------------------------------------------------- +start_local(DB_Name) -> + Pid = spawn_link(fun() -> client_db(DB_Name) end), + register(DB_Name, Pid). + +%%-------------------------------------------------------------------- +%% @doc +%% Stops a client database that has been started locally. +%% +%% @spec stop_local(DB_Name) +%% @end +%%-------------------------------------------------------------------- +stop_local(DB_Name) -> + destroy(DB_Name), + unregister(DB_Name). + +%%-------------------------------------------------------------------- +%% @doc +%% client_db is first stage of the database process. It creates an ets +%% table after the atom specified as the parameter DB_Name. +%% +%% @spec client_db(DB_Name) +%% @end +%%-------------------------------------------------------------------- +client_db(DB_Name) -> + case ets:info(DB_Name) of + undefined -> + create(DB_Name), + client_db_loop(DB_Name); + _ -> + client_db_loop(DB_Name) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% client_db_loop constitutes the database process, and offers an inter- +%% face to interact with the ets table, allowing the input or retrieval +%% of information concerning the clients logged in the system. +%% +%% @spec client_db_loop(DB_Name) +%% @end +%%-------------------------------------------------------------------- +client_db_loop(DB_Name) -> + receive + {From, add, Client_Name, Client_Pid, Client_Monitor_Pid} -> + add_client(DB_Name, Client_Name, Client_Pid, Client_Monitor_Pid), + case From of + undefined -> + ok; + _ -> + From ! {client_added, ok} + end, + client_db_loop(DB_Name); + {From, remove, Client_Name} -> + remove_client(DB_Name, Client_Name), + case From of + undefined -> + ok; + _ -> + From ! {client_removed, ok} + end, + client_db_loop(DB_Name); + {From, full_db} -> + A = retrieve_db(DB_Name), + case From of + undefined -> + ok; + _ -> + From ! A + end, + client_db_loop(DB_Name); + {From, update_pid, Client_Name, Client_Pid} -> + Answer = update_client_pid(DB_Name, Client_Name, Client_Pid), + case From of + undefined -> + ok; + _ -> + From ! Answer + end, + client_db_loop(DB_Name); + {From, update_monitor_pid, Client_Name, Client_Monitor_Pid} -> + Answer = update_client_monitor_pid(DB_Name, Client_Name, Client_Monitor_Pid), + case From of + undefined -> + ok; + _ -> + From ! Answer + end, + client_db_loop(DB_Name); + {From, peak, Client_Name} -> + C = peak_client(DB_Name, Client_Name), + case From of + undefined -> + ok; + _ -> + From ! C + end, + client_db_loop(DB_Name); + {From, peak_by_client_monitor, Client_Monitor_Pid} -> + C = peak_client_monitor(DB_Name, Client_Monitor_Pid), + case From of + undefined -> + ok; + _ -> + From ! C + end, + client_db_loop(DB_Name); + {From, retrieve, Client_Name} -> + C = retrieve_client(DB_Name, Client_Name), + case From of + undefined -> + ok; + _ -> + From ! C + end, + client_db_loop(DB_Name); + {From, client_pid, Client_Name} -> + Pid = client_pid(DB_Name, Client_Name), + case From of + undefined -> + ok; + _ -> + From ! Pid + end, + client_db_loop(DB_Name); + {From, client_monitor_pid, Client_Name} -> + Pid = client_monitor_pid(DB_Name, Client_Name), + case From of + undefined -> + ok; + _ -> + From ! Pid + end, + client_db_loop(DB_Name); + {From, recover, Target_DB_Name, Source_DB_Name} -> + recover_db(Target_DB_Name, Source_DB_Name), + case From of + undefined -> + ok; + _ -> + From ! {recovered, ok} + end, + client_db_loop(DB_Name); + {From, stop} -> + stop(DB_Name), + case From of + undefined -> + ok; + _ -> + From ! {client_db_destroyed, ok} + end + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Creates a new ets table -named Table_Name- to store the different +%% clients active in the system. +%% +%% @spec create(Table_Name) -> Table_Name | {error, Error} +%% @end +%%--------------------------------------------------------------------- +create(Table_Name) -> + ets:new(Table_Name, [set, named_table]). + +%%--------------------------------------------------------------------- +%% @doc +%% Destroys ets table named Table_Name. +%% +%% @spec destroy(Table_Name) -> Table_Name | {error, Error} +%% @end +%%--------------------------------------------------------------------- +destroy(Table_Name) -> + ets:delete(Table_Name). + +%%--------------------------------------------------------------------- +%% @doc +%% Adds a client to the clients database. +%% +%% @spec add_client(Table_Name, Client_name, Client_Pid, +%% Client_Monitor_Pid) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +add_client(Table_Name, Client_Name, Client_Pid, Client_Monitor_Pid) -> + ets:insert(Table_Name, {Client_Name, Client_Pid, Client_Monitor_Pid}). + +%%--------------------------------------------------------------------- +%% @doc +%% Removes a client from the clients database. +%% +%% @spec remove_session(Table_Name, Client_name) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +remove_client(Table_Name, Client_Name) -> + ets:delete(Table_Name, Client_Name). + +%%--------------------------------------------------------------------- +%% @doc +%% Updates a client's Pid. +%% +%% @spec update_client_pid(Table_Name, Client_Monitor_Pid) -> +%% true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +update_client_pid(Table_Name, Client_Name, New_Pid) -> + ets:update_element(Table_Name, Client_Name, {2, New_Pid}). + +%%--------------------------------------------------------------------- +%% @doc +%% Updates a client's monitor Pid. +%% +%% @spec update_client_pid(Table_Name, Client_Monitor_Pid) -> +%% true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +update_client_monitor_pid(Table_Name, Client_Name, New_Monitor_Pid) -> + ets:update_element(Table_Name, Client_Name, {3, New_Monitor_Pid}). + +%%--------------------------------------------------------------------- +%% @doc +%% Returns the contents of the whole database as a list of lists, where +%% each of the nested lists is one client. (Not in use, only for debug +%% purposes +%% +%% @spec retrieve_db(Table_Name) -> +%% [[Client1], ..., [ClientK]] | {error, Error} +%% @end +%%--------------------------------------------------------------------- +retrieve_db(Table_Name) -> + ets:match(Table_Name, {'$0', '$1', '$2'}). + +%%--------------------------------------------------------------------- +%% @doc +%% Returns a list containing the data of the client passed as argument. +%% It does not delete the client from the db. +%% +%% @spec peak_client(Table_Name, Client_Monitor_Pid) -> +%% [Client_Name, Client_Monitor_Pid, Client] | [] +%% @end +%%--------------------------------------------------------------------- +peak_client(Table_Name, Client_Name) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2'}, + [{'==', '$0', Client_Name}], + [['$0', '$1', '$2']]}]), + case L == [] of + true -> + L; + false -> + lists:last(L) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Returns a list containing the data of the client monitored by the +%% monitor that has the pid passed as argument. +%% It does not delete the client from the db. +%% +%% @spec peak_client_monitor(Table_Name, Client_Monitor_Pid) -> +%% [Client_Name, Client_Monitor_Pid, Client] | [] +%% @end +%%--------------------------------------------------------------------- +peak_client_monitor(Table_Name, Client_Monitor_Pid) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2'}, + [{'==', '$2', Client_Monitor_Pid}], + [['$0', '$1', '$2']]}]), + case L == [] of + true -> + L; + false -> + lists:last(L) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Deletes the client passed as argument from the database and returns +%% a list containing the data of the said client. +%% +%% @spec retrieve_client(Table_Name, Client_Monitor_Pid) -> +%% [Client_Monitor_Pid, Client] | {error, Error} +%% @end +%%--------------------------------------------------------------------- +retrieve_client(Table_Name, Client_Name) -> + C = peak_client(Table_Name, Client_Name), + remove_client(Table_Name, Client_Name), + C. + +%%-------------------------------------------------------------------- +%% @doc +%% Returns the Pid of the client passed as argument. +%% +%% @spec client_pid(Table_Name, Session_Name) -> Client_Pid | [] +%% @end +%%-------------------------------------------------------------------- +client_pid(Table_Name, Client_Name) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2'}, + [{'==', '$0', Client_Name}], + [['$1']]}]), + case L == [] of + true -> + L; + false -> + lists:last(lists:last(L)) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Returns the Pid of the client monitor for a given client passed as +%% argument. +%% +%% @spec client_monitor_pid(Table_Name, Session_Name) -> +%% Client_Monitor_Pid | [] +%% @end +%%--------------------------------------------------------------------- +client_monitor_pid(Table_Name, Client_Name)-> + L = ets:select(Table_Name, [{{'$0', '$1', '$2'}, + [{'==', '$0', Client_Name}], + [['$2']]}]), + case L == [] of + true -> + L; + false -> + lists:last(lists:last(L)) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Replicates the clients table specified as the Source argument, in a +%% new table with name Table_Name. +%% +%% @spec retrieve_server(Table_Name, Client_Monitor_Pid) -> +%% true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +recover_db(Destination, Source) -> + ets:safe_fixtable(Source, true), + replicate(Source, Destination, ets:first(Source)), + ets:safe_fixtable(Source, false). + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to the recover_db(Table_Name, Source) function. This +%% function traverses the source table specified in Source, and feeds the +%% data in the destination table. +%% +%% @spec retrieve_server(Table_Name, Session_Pid) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +replicate(Source, Destination, Key) -> + case Key of + '$end_of_table' -> + true; + _ -> + S = peak_client(Source, Key), + global:whereis_name(Destination) ! {undefined ,add, + lists:nth(1, S), + lists:nth(2, S), + lists:nth(3, S)}, + replicate(Source, Destination, ets:next(Source, Key)) + end. diff --git a/app/IM/src/launcher.erl b/app/IM/src/launcher.erl new file mode 100644 index 0000000..f495a0b --- /dev/null +++ b/app/IM/src/launcher.erl @@ -0,0 +1,601 @@ +%%%------------------------------------------------------------------- +%%% LAUNCHER MODULE +%%% +%%% @author Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Launcher module for the Distributed Erlang instant messenger +%%% (IM) application developed as a real benchmark for the +%%% Scalable Distributed Erlang extension of the Erlang/OTP +%%% language. +%%% +%%% This module implements the logic to deploy a system similar +%%% to the system described in the Section 2 of the document +%%% "Instant Messenger Architectures Design Proposal" given a +%%% set of virtual machines to host the system. +%%% @end +%%% Created : 25 Jul 2014 by Mario Moro Hernandez +%%%------------------------------------------------------------------- +-module(launcher). +-export([start/6, launch_router_processes/6, launch_server_supervisors/4]). + +-import(router,[router_supervisor/5, router_process/1, compression_function/2, hash_code/2]). + +%%============================================================================ +%% APPLICATION DEPLOYMENT LOGIC +%%============================================================================ + +%%-------------------------------------------------------------------- +%% @doc +%% start/6 launches the sequence to set up the architecture, and +%% run the IM application. +%% +%% Arguments: +%% Servers_Per_Router_Node: (int) The number of server nodes +%% that are children of a router node. +%% Server_Per_Router_Process: (int) The number of server +%% supervisor processes that are +%% monitored by a router process. +%% Servers_Total: (int) Final number of servers in the system. +%% Clients_Total: (int) Final number of client nodes. +%% List_Domains: (list) List containing all the domains of +%% the hosts in which the system is deployed. +%% This is usually one, but can be more if the +%% application is hosted in a cluster with +%% different domains. +%% Num_of_Hosts: (int) Number of hosts in which the system is +%% deployed. +%% +%% Example: start(2,1,2,4,['domain.do'], 1). +%% start(2,1,2,4,['domain'], 1). +%% +%% These launch a system comprising 1 router, 2 servers, and +%% 4 client nodes on 1 host, with domain domain.do or domain. +%% In this case, there are 2 router processes, each of them +%% monitoring one server supervisor process. +%% +%% @spec start(Servers_Per_Router_Node, Servers_Per_Router_Process, +%% Servers_Total, Clients_Total, List_Domains, Num_of_Hosts) -> +%% Status Messages | {error, reason} +%% @end +%%-------------------------------------------------------------------- +start(Servers_Per_Router_Node, Servers_Per_Router_Process, Servers_Total, Clients_Total, List_Domains, Num_of_Hosts) -> + Num_TS = Servers_Total * Num_of_Hosts, + Architecture_Info = {Servers_Per_Router_Node, + Servers_Per_Router_Process, + Servers_Total, + Num_TS, + Clients_Total}, + case List_Domains == [] of + true -> + io:format("start/4 is finished.~n"); + false -> + [Domain|New_List_Domains] = List_Domains, + case whereis(routers_listener) of + undefined -> + io:format("~n=============================================~n"), + io:format("Initiating the Distributed Instant Messenger.~n"), + io:format("=============================================~n"), + Routers_Listener_Pid = spawn(fun() -> + routers_listener(Num_TS, + [], + [], + [], + []) + end), + register(routers_listener, Routers_Listener_Pid); + _ -> + ok + end, + start_host(Num_of_Hosts, + Architecture_Info, + Domain, + whereis(routers_listener)), + start(Servers_Per_Router_Node, + Servers_Per_Router_Process, + Servers_Total, + Clients_Total, + New_List_Domains, + Num_of_Hosts - 1) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% starts the sequence of that launch each of the components that +%% will be deployed in a particular hosts from the list specified +%% in start/6. +%% +%% @spec +%% start_host(Num_of_Host, Architecture_Info, Domain, +%% Routers_Listener_Pid) -> Status Messages | {error, reason} +%% @end +%%-------------------------------------------------------------------- +start_host(Num_of_Host, Architecture_Info, Domain, Routers_Listener_Pid) -> + {Servers_Per_Router_Node, + Servers_Per_Router_Process, + Servers_Total, + Num_TS, + Clients_Total} = Architecture_Info, + case Servers_Total rem Servers_Per_Router_Node of + 0 -> + Routers = Servers_Total div Servers_Per_Router_Node; + _Greater_than_0 -> + Routers = (Servers_Total div Servers_Per_Router_Node) + 1 + end, + case Servers_Per_Router_Process > Servers_Per_Router_Node of + true -> + S_R_Pr = Servers_Per_Router_Node; + false -> + S_R_Pr = Servers_Per_Router_Process + end, + + Router_Nodes = node_name_generator(Num_of_Host, router, Domain, Routers, 0), + Server_Nodes = node_name_generator(Num_of_Host, server, Domain, Servers_Total, 0), + Client_Nodes = node_name_generator(Num_of_Host, client, Domain, Clients_Total, 0), + Router_Processes = router_names_generator(Num_of_Host, Servers_Total, Servers_Per_Router_Node, S_R_Pr, 0), + + io:format("~n=============================================~n"), + io:format(" Launching Host: ~p ~n", [Num_of_Host]), + io:format("=============================================~n"), + io:format("Router nodes: ~p~n", [Router_Nodes]), + io:format("Server nodes: ~p~n", [Server_Nodes]), + io:format("Client nodes: ~p~n", [Client_Nodes]), + io:format("Router processes: ~p~n", [Router_Processes]), + + Routers_Listener_Pid ! {Client_Nodes, add_client_node}, + case length(Router_Processes) rem Routers of + 0 -> + Router_Pr_per_R_Nd = length(Router_Processes) div Routers; + _Other -> + Router_Pr_per_R_Nd = length(Router_Processes) div Routers + 1 + end, + New_Architecture_Info = {Servers_Per_Router_Node, + Servers_Per_Router_Process, + Num_TS, + Servers_Total, + Routers, + Router_Pr_per_R_Nd}, + Routers_Servers_Lists = {Router_Nodes, Server_Nodes, Router_Processes}, + + launch_router_supervisors(Routers_Servers_Lists, New_Architecture_Info, Routers_Listener_Pid). + +%%-------------------------------------------------------------------- +%% @doc +%% This function determines the router nodes where the router +%% supervisors must be spawned and gathers all the information +%% required for that purpose. +%% +%% @spec launch_router_supervisors(Routers_Servers_Lists, +%% Architecture_Info, Routers_Listener_Pid) -> +%% Status Messages | {error, reason} +%% @end +%%-------------------------------------------------------------------- +launch_router_supervisors(Routers_Servers_Lists, Architecture_Info, Routers_Listener_Pid) -> + {Router_Nodes, Server_Nodes, Router_Processes} = Routers_Servers_Lists, + {Server_Router_Nd, Servers_Router_Pr, Num_Total_Servers, _, _, Router_Pr_Per_R_Nd} = Architecture_Info, + [Router_Nd | New_Router_Nd] = Router_Nodes, + io:format("Router_Pr_per_R_Nd = ~p; Router_Processes = ~p~n", [Router_Pr_Per_R_Nd, Router_Processes]), + case length(Server_Nodes) > Server_Router_Nd of + true -> + {Servers, New_Server_Nd} = lists:split(Server_Router_Nd, Server_Nodes), + {Router_Prcs, New_Router_Processes} = lists:split(Router_Pr_Per_R_Nd, Router_Processes), + io:format("launch_router_supervisors.~nRouter_Nd: ~p~nServers in ~p:~p~nRouter Processes in ~p:~p~n", + [Router_Nd, Router_Nd, Servers,Router_Nd, Router_Prcs]), + New_Rs_Ss_Lists = {New_Router_Nd, New_Server_Nd, New_Router_Processes}, + start_router_supervisor(Router_Nd, + Servers, + Servers_Router_Pr, + Router_Prcs, + Num_Total_Servers, + Routers_Listener_Pid), + launch_router_supervisors(New_Rs_Ss_Lists, Architecture_Info, Routers_Listener_Pid); + false -> + start_router_supervisor(Router_Nd, + Server_Nodes, + Servers_Router_Pr, + Router_Processes, + Num_Total_Servers, + Routers_Listener_Pid), + io:format("launch_router_supervisors.~nRouter_Nd: ~p~nServers in ~p:~p~nRouter Processes in ~p:~p~n", + [Router_Nd, Router_Nd, Server_Nodes,Router_Nd, Router_Processes]) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% This function spawns a router supervisor on the target node and +%% flags the start of the sequence to deploy the router processes. +%% @spec start_router_supervisor(Router_Node, Server_Nodes, Servers_Router_Pr, +%% Router_Processes, Num_Total_Servers, Routers_Listener_Pid) -> +%% ok | {error, reason} +%% @end +%%-------------------------------------------------------------------- +start_router_supervisor(Router_Node, Server_Nodes, Servers_Router_Pr, Router_Processes, Num_Total_Servers, Routers_Listener_Pid) -> + R_Sup_Pid = spawn_link(Router_Node, fun() -> + router_supervisor(undefined, Router_Processes, [], [], []) + end), + Routers_Listener_Pid ! {R_Sup_Pid, add_router_sup}, + R_Sup_Pid ! {Server_Nodes, + Servers_Router_Pr, + Router_Processes, + Num_Total_Servers, + Routers_Listener_Pid, + launch_router_processes}, + ok. + +%%-------------------------------------------------------------------- +%% @doc +%% This function determines the number of router processes that are +%% going to be spawned on a router node and gathers all the information +%% required for that purpose. +%% +%% @spec launch_router_processes(R_Sup_Pid, Server_Nodes, Servers_Router_Pr, +%% Router_Processes, Num_Total_Servers, Routers_Listener_Pid) -> +%% Status Messages | {error, reason} +%% @end +%%-------------------------------------------------------------------- +launch_router_processes(R_Sup_Pid, Server_Nodes, Servers_Router_Pr, Router_Processes, Num_Total_Servers, Routers_Listener_Pid) -> + case Router_Processes of + [] -> + io:format("Router processes start sequence, finished.~n"); + [Router_Process|New_Router_Processes] -> + io:format("launch_router_processes/4 Router_Processes: ~p~n", [Router_Processes]), + case length(Server_Nodes) > Servers_Router_Pr of + true -> + {Servers, New_Server_Nodes} = lists:split(Servers_Router_Pr, Server_Nodes), + start_router_process(Router_Process, + Servers, Servers_Router_Pr, + Num_Total_Servers, + R_Sup_Pid, + Routers_Listener_Pid), + launch_router_processes(R_Sup_Pid, + New_Server_Nodes, + Servers_Router_Pr, + New_Router_Processes, + Num_Total_Servers, + Routers_Listener_Pid); + false -> + start_router_process(Router_Process, + Server_Nodes, + Servers_Router_Pr, + Num_Total_Servers, + R_Sup_Pid, + Routers_Listener_Pid) + end + end. + +%%-------------------------------------------------------------------- +%% @doc +%% This function spawns a router process on the target node and +%% flags the start of the sequence to deploy the server supervisor +%% process on the target node. +%% +%% @spec start_router_process(Router_Name, Server_Nodes, Servers_Router_Pr, +%% Num_Total_Servers, R_Sup_Pid, Routers_Listener_Pid) -> +%% yes | {error, reason} +%% @end +%%-------------------------------------------------------------------- +start_router_process(Router_Name, Server_Nodes, Servers_Router_Pr, Num_Total_Servers, R_Sup_Pid, Routers_Listener_Pid) -> + %% global:register_name(Router_Name, + %% R_Pid = spawn_link(fun() -> + %% router_process({initial_state, R_sup_Pid, [], [], []}) + %% end)), + R_Pid = spawn_link(fun() -> + router_process({initial_state, R_Sup_Pid, [], [], []}) + end), + Routers_Listener_Pid ! {Router_Name, R_Pid, add_router}, + R_Pid ! {Router_Name, + Server_Nodes, + Servers_Router_Pr, + Num_Total_Servers, + Routers_Listener_Pid, + launch_server}, + yes. + +%%-------------------------------------------------------------------- +%% @doc +%% This function determines the target nodes to spawn the corresponding +%% server supervisor processes. +%% +%% @spec launch_server_supervisors(Server_Nodes, Servers_Router_Pr, +%% Num_Total_Servers, Routers_Listener_Pid) -> +%% Status Messages | {error, reason} +%% @end +%%-------------------------------------------------------------------- +launch_server_supervisors(Server_Nodes, Servers_Router_Pr, Num_Total_Servers, Routers_Listener_Pid) -> + case length(Server_Nodes) > Servers_Router_Pr of + true -> + {Servers, New_Server_Nodes} = lists:split(Servers_Router_Pr, Server_Nodes), + io:format("launch_server_supervisors.~nServers:~p~n", [Server_Nodes]), + start_server_supervisors(Servers, Num_Total_Servers, Routers_Listener_Pid), + launch_server_supervisors(New_Server_Nodes, + Servers_Router_Pr, + Num_Total_Servers, + Routers_Listener_Pid); + false -> + io:format("launch_server_supervisors.~nServers:~p~n", [Server_Nodes]), + start_server_supervisors(Server_Nodes, + Num_Total_Servers, + Routers_Listener_Pid) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% This function spawns the server supervisor processes on the target +%% nodes. Then, it notifies the name and pid of the server supervisor +%% to allow the routing of the requests made by the clients. +%% +%% @spec launch_server_supervisors(Server_Nodes, Servers_Router_Pr, +%% Num_Total_Servers, Routers_Listener_Pid) -> +%% Status Messages | {error, reason} +%% @end +%%-------------------------------------------------------------------- +start_server_supervisors(Server_Nodes, Num_Total_Servers, Routers_Listener_Pid) -> + case Server_Nodes == [] of + true -> + io:format("Server supervisors start sequence is finished.~n"); + false -> + [Server|New_Server_Nodes] = Server_Nodes, + S = atom_to_list(Server), + Server_Name = string:left(S, string:chr(S,$@) - 1), + Server_Sup_Pid = spawn_link(Server, + fun() -> server:start_server_supervisor( + Server, string_to_atom(Server_Name), Num_Total_Servers) end), + Routers_Listener_Pid ! {Server_Name, Server_Sup_Pid, add_server}, + start_server_supervisors(New_Server_Nodes, Num_Total_Servers, Routers_Listener_Pid) + end. + +%%%======================================================================== +%%% AUXILIARY FUNCTIONS FOR ROUTERS INFORMATION AND RELIABILITY +%%%======================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% routers_listener/5 is an auxiliary process central to the start +%% sequence of the system. It is responsible for receive the pids +%% of router supervisors, router processes and server supervisors +%% to pass this information to the relevant processes, once these +%% processes are all spawned. +%% +%% @spec routers_listener(Servers_Total, List_Servers, List_Routers, +%% List_Router_Sups, Client_Nodes) -> +%% Status Messages | {error, reason} +%% @end +%%-------------------------------------------------------------------- +routers_listener(Servers_Total, List_Servers, List_Routers, List_Router_Sups, Client_Nodes) -> + receive + {Server_Name, Server_Sup_Pid, add_server} -> + New_List_Servers = lists:append(List_Servers, [{Server_Name, Server_Sup_Pid}]), + io:format("List of servers (routers_listener/4): ~p~n", [New_List_Servers]), + case length(New_List_Servers) == Servers_Total of + true -> + io:format("launching start_routers_db/2 having Client_Nodes: ~p~n", [Client_Nodes]), + Routers_DBs_Pids = launch_routers_db(Client_Nodes, List_Routers, []), + feed_router_sup(List_Router_Sups, List_Routers, Routers_DBs_Pids), + feed_routers(New_List_Servers, List_Routers, List_Routers); + false -> + routers_listener(Servers_Total, New_List_Servers, List_Routers, List_Router_Sups, Client_Nodes) + end; + {Router_Name, Router_Pid, add_router} -> + New_List_Routers = lists:append(List_Routers, [{atom_to_list(Router_Name), Router_Pid}]), + routers_listener(Servers_Total, List_Servers, New_List_Routers, List_Router_Sups, Client_Nodes); + {Router_Sup_Pid, add_router_sup} -> + New_List_Router_Sups = lists:append(List_Router_Sups, [Router_Sup_Pid]), + routers_listener(Servers_Total, List_Servers, List_Routers, New_List_Router_Sups, Client_Nodes); + {New_Clients, add_client_node} -> + New_Client_Nodes = lists:append(Client_Nodes, New_Clients), + routers_listener(Servers_Total, List_Servers, List_Routers, List_Router_Sups, New_Client_Nodes); + Other -> + io:format("Something failed here. routers_listener/4 received: ~p~n", [Other]), + routers_listener(Servers_Total, List_Servers, List_Routers, List_Router_Sups, Client_Nodes) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% launch_routers_db/3 spawns a routers_db process on each of the +%% client nodes started, and returns a list with the pids of all +%% the routers_db processes spawned. +%% +%% @spec launch_routers_db(Client_Nodes, List_Routers, Routers_DBs) -> +%% Routers_DBs (list) | {error, reason} +%% @end +%%-------------------------------------------------------------------- +launch_routers_db(Client_Nodes, List_Routers, Routers_DBs) -> + case Client_Nodes of + [] -> + io:format("All routers_db processes started.~n"), + Routers_DBs; + [Client|New_Client_Nodes] -> + New_Routers_DBs = Routers_DBs ++ start_routers_db(Client, List_Routers), + launch_routers_db(New_Client_Nodes, List_Routers, New_Routers_DBs) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% start_routers_db/2 spawns a routers_db process on the target +%% client node passed as the argument. It returns a list of length +%% 1 containing the pid of the routers_db process spawned. It also +%% flags the process to globally register itself. +%% +%% @spec launch_routers_db(Client_Node, List_Routers) -> +%% [Routers_DB_Pid] | {error, reason} +%% @end +%%-------------------------------------------------------------------- +start_routers_db(Client_Node, List_Routers) -> + io:format("Executing start_routers_db/2~n"), + R_DB_Pid = spawn(Client_Node, fun() -> routers_db(List_Routers) end), + io:format("routers_db Pid = ~p~n", [R_DB_Pid]), + R_DB_Pid ! {register_globally}, + [R_DB_Pid]. + +%%-------------------------------------------------------------------- +%% @doc +%% This process is spawned in all client nodes and it stores the +%% pids of all the routers processes spawned in the system. It is +%% in charge of providing the clients these pids, so the clients +%% can direct their requests to the routers. +%% +%% @spec routers_db(Routers_List) -> Status Messages | Routers_pids (list) +%% @end +%%-------------------------------------------------------------------- +routers_db(Routers_List) -> + receive + {register_globally} -> + io:format("routers_db/1 received {register_globally}~n"), + global:register_name(routers_db, self()), + routers_db(Routers_List); + {register_locally} -> + io:format("routers_db/1 received {register_locally}~n"), + Pid = self(), + register(routers_db, Pid), + routers_db(Routers_List); + {retrieve_routers, Requester_Pid} -> + Requester_Pid ! {Routers_List, router_list}, + routers_db(Routers_List); + {New_Routers_List, receive_router_list}-> + routers_db(New_Routers_List); + Other -> + io:format("Something failed at routers_db/1. Received: ~p~n", [Other]), + routers_db(Routers_List) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% feed_router_sup/3 passes the list of routers spawned in the system +%% to the router supervisors. +%% @spec feed_router_sup(List_Router_Sups, List_Routers, Router_DBs_Pids) -> +%% ok | {error, reason} +%% @end +%%-------------------------------------------------------------------- +feed_router_sup(List_Router_Sups, List_Routers, Router_DBs_Pids) -> + io:format("feed_router_sup/2~n"), + case List_Router_Sups of + [] -> + ok; + [R_Sup_Pid|T] -> + R_Sup_Pid ! {list_routers, List_Routers, Router_DBs_Pids}, + feed_router_sup(T, List_Routers, Router_DBs_Pids) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% feed_routers/3 passes the list of routers and servers spawned in +%% the system to the router processes. This is needed for reliability +%% @spec feed_router_sup(List_Servers, List_Routers, List_Routers_2) -> +%% Status Message | {error, reason} +%% @end +%%-------------------------------------------------------------------- +feed_routers(List_Servers, List_Routers, List_Routers_2) -> + case List_Routers_2 of + [] -> + io:format("Host is deployed.~n"); + %%init:stop(); + [{_, Router_Pid}|New_List_Routers] -> + Router_Pid ! {list_routers, List_Routers}, + Router_Pid ! {list_servers, List_Servers}, + feed_routers(List_Servers, List_Routers, New_List_Routers) + end. + +%%========================================================================= +%% AUXILIARY FUNCTIONS +%%========================================================================= + +%%-------------------------------------------------------------------- +%% @doc +%% This function builds the name of a target node given all the +%% details of the architecture of the system. +%% +%% @spec node_name_generator(Host, Node_Type, Domain, +%% Total_Nodes, Children_Nodes_Per_Node) -> atom() +%% @end +%%-------------------------------------------------------------------- +node_name_generator(Host, Node_Type, Domain, Total_Nodes, Children_Nodes_Per_Node) -> + case Children_Nodes_Per_Node of + 0 -> + [string_to_atom( + string:join([atom_to_list(Node_Type), + integer_to_list(X)], "_") ++ + "@" ++ atom_to_list(Domain)) || + X <- lists:seq(((Host * Total_Nodes) - Total_Nodes + 1), + Host * Total_Nodes)]; + _Other -> + [string_to_atom( + string:join([atom_to_list(Node_Type), + integer_to_list(X), + integer_to_list(Y)], "_") ++ + "@" ++ atom_to_list(Domain)) || + X <- lists:seq(((Host * Total_Nodes) - Total_Nodes + 1), + Host * Total_Nodes), + Y <- lists:seq(1, Children_Nodes_Per_Node)] + end. + +%%-------------------------------------------------------------------- +%% @doc +%% This function builds a list containing the name of all the router +%% processes that will be used in the system. +%% +%% @spec node_name_generator(Host, Node_Type, Domain, +%% Total_Nodes, Children_Nodes_Per_Node) -> list of atom() +%% @end +%%-------------------------------------------------------------------- +router_names_generator(Num_of_Host, Servers_Total, Servers_Router_Node, + Servers_Router_Process, Num_of_Sub_Pr) -> + B = Servers_Total div Servers_Router_Node, + C = Servers_Total div Servers_Router_Process, + io:format("Servers_Router_Node: ~p, Servers_Router_Process: ~p~n", + [Servers_Router_Node, Servers_Router_Process]), + io:format("B = ~p, C = ~p~n", [B, C]), + case Servers_Total of + Servers_Router_Node -> + io:format("true~n"), + D = (Servers_Total div Servers_Router_Process); + _Other -> + io:format("false~n"), + case Servers_Router_Node rem Servers_Router_Process == 0 of + true -> + D = Servers_Total div Servers_Router_Process; + false -> + D = (Servers_Total div Servers_Router_Node) + + (Servers_Total div Servers_Router_Process) + end + end, + io:format("D: ~p~n",[D]), + case D * Servers_Router_Process < Servers_Total of + true -> + A = D + 1; + false -> + A = D + end, + io:format("A: ~p~n", [A]), + Lower_Limit = (Num_of_Host-1) * A + 1, + Upper_Limit = Lower_Limit + A - 1, + case Num_of_Sub_Pr of + 0 -> + [string_to_atom( + string:join(["router", + integer_to_list(X)], "_")) || + X <- lists:seq(Lower_Limit, Upper_Limit)]; + _Another -> + [string_to_atom( + string:join(["router", + integer_to_list(X), + integer_to_list(Y)], "_")) || + X <- lists:seq(Lower_Limit, Upper_Limit), + Y <- lists:seq(1, Num_of_Sub_Pr)] + end. + +%%-------------------------------------------------------------------- +%% @doc +%% string_to_atom/1 takes a string and returns an atom, or an +%% existing atom if that is the case. +%% +%% @spec string_to_atom(String) -> atom() | existing_atom() +%% @end +%%-------------------------------------------------------------------- +string_to_atom(String) -> + try list_to_existing_atom(String) of + Val -> + Val + catch + error:_ -> + list_to_atom(String) + end. diff --git a/app/IM/src/logger.erl b/app/IM/src/logger.erl new file mode 100644 index 0000000..5a04c42 --- /dev/null +++ b/app/IM/src/logger.erl @@ -0,0 +1,667 @@ +%%%-------------------------------------------------------------------- +%%% LOGGER MODULE +%%% +%%% @author: Mario Moro Hernandez +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Logger module is a recorder of messages sent between clients +%%% logged in the IM system. +%%% +%%% This module allows to run throughput and latency experiments, +%%% by recording these measurementes. +%%% @end +%%% +%%% Created: 30 Mar 2014 by Mario Moro Hernandez +%%% Modified: 25 Aug 2014 by Mario Moro Hernandez +%%%-------------------------------------------------------------------- + +-module(logger). +-export([start/1, start_throughput/4, start_latency/3, launch/9, launch_latency/8]). + +%%--------------------------------------------------------------------- +%% @doc +%% start/1 starts a generic logger, which records the latency of +%% successfully delivered messages on a text file named as specified +%% in the argument passed to the function. +%% +%% Argument: +%% - FileName: (String) name of the file. +%% +%% @spec start(FileName) -> File.csv +%% @end +%%---------------------------------------------------------------------- +start(FileName) -> + case whereis(logger) of + undefined-> + {_Status, Fd} = create_file(FileName), + case Fd of + unable_to_open_file -> + io:format("ERROR: latency logger cannot start.~n"), + exit(io_error_create_file); + Pid -> + register(logger, spawn(logger,loop,[Pid])) + end; + _ -> + io:format("ERROR: The logger process has already started.~n") + end. + +%%--------------------------------------------------------------------- +%% @doc +%% launch/9 initiates a throughput logger (see below) on each of the +%% client nodes deployed in the architecture. +%% +%% Arguments: +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Routers: (int) J, number of router nodes. +%% - Servers: (int) K, number of server nodes. +%% - Clients: (int) L, number of client processes. +%% - Num_Node: (int) M, number of client nodes deployed. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% - Timer: (int) T, is the time in seconds in which the logger must +%% be active. +%% - Threshold: (int) R, is the time in microseconds which is +%% considered acceptable for a good service. +%% - Domain: (Atom) Domain or host where the client nodes are deployed. +%% +%% @spec launch(Technology, Routers, Servers, Clients, Num_Nodes, +%% Trials, Timer, Threshold, Domain) -> ok; +%% @end +%%--------------------------------------------------------------------- +launch(Technology, Routers, Servers, Clients, Num_Nodes, Trials, Timer, Threshold, Domain) -> + case Num_Nodes == 0 of + true -> + io:format("All loggers are launched now.~n"), + ok; + false -> + Node = client_node_name(Num_Nodes, Domain), + spawn(Node, fun() -> start_throughput(Technology, + Routers, + Servers, + Clients, + Num_Nodes, + Trials, + Timer, + Threshold, + n) + end), + launch(Technology, Routers, Servers, Clients, Num_Nodes - 1, Trials, Timer, Threshold, Domain) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% start_throughput/9 starts a throughput logger, which records the +%% number of sent messages, received messages, successfully delivered, +%% messages delivered below an arbitrary time threshold, and the +%% quality of the service provided, during specified time-window. +%% The results are written in two text files named after the arguments +%% passed to the function. One of these files is a .csv file ready for +%% being process on a data spreadsheet, or statistical analysis software, +%% whereas the other one is a .txt file that offers a summary in text. +%% +%% Arguments: +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Routers: (int) J, number of router nodes. +%% - Servers: (int) K, number of server nodes. +%% - Clients: (int) L, number of client processes. +%% - Num_Node: (int) M, number of client nodes deployed. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% - Timer: (int) T, is the time in seconds in which the logger must +%% be active. +%% - Threshold: (int) R, is the time in microseconds which is considered +%% acceptable for a good service. +%% - Dump_to_file: (Atom) y, 'Yes', _Other. Not in use. +%% +%% @spec start_throughput(Technology, Routers, Servers, Clients, +%% Num_Node, Num_Trials, Timer, Threshold, +%% Dump_Table_To_File) -> File.txt; File.csv +%% @end +%%-------------------------------------------------------------------- +start_throughput(Technology, Routers, Servers, Clients, Num_Node, Num_Trials, Timer, Threshold, Dump_Table_To_File) -> + case whereis(throughput_logger) of + undefined -> + Table_Name = string:join([Technology, "Throughput", + integer_to_list(Routers), + integer_to_list(Servers), + integer_to_list(Clients), + "Node", + integer_to_list(Num_Node)], "_"), + record_throughput({ets_based, Table_Name, Num_Trials, Timer, Threshold, Dump_Table_To_File}); + _Other -> + io:format("ERROR: The logger process has already started.~n") + end. + +%%--------------------------------------------------------------------- +%% @doc +%% record_throughput/1 starts a throughput logger, which records the +%% number of sent, received, successfully delivered, and delivered +%% below a time-threshold, messages for an specified time-window. +%% The results are output on two text files named after the parameters +%% passed as arguments to the function. +%% +%% Arguments: +%% - Table_Name: (String) S, name of the ets table to record the results. +%% Not in use. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% - Timer: (int) T, is the time in seconds in which the logger must +%% be active. +%% - Threshold: (int) R, is the time in microseconds which is considered +%% acceptable for a good service. +%% - Dump_to_file: (Atom) y, 'Yes', _Other. Not in use. +%% +%% @spec record_throughput({ets_based, Table_Name, Num_Trials, Timer, +%% Threshold, Dump_Table_To_File}) -> +%% File.txt; File.csv +%% @end +%%--------------------------------------------------------------------- +record_throughput({ets_based, Table_Name, Num_Trials, Timer, Threshold, Dump_Table_To_File}) -> + case Num_Trials > 0 of + true -> + case whereis(throughput_logger) of + undefined -> + register(throughput_logger, + spawn(fun() -> loop({initial_state, + Table_Name, + Num_Trials, + Threshold}) + end)); + _Other -> + ok + end, + timer:sleep(Timer * 1000), + whereis(throughput_logger) ! {new_trial, Num_Trials - 1}, + record_throughput({ets_based, Table_Name, Num_Trials - 1, Timer, Threshold, Dump_Table_To_File}); + false -> + whereis(throughput_logger) ! {stop_throughput}, + io:format("Throughput benchmarks finished.~n"), + case Dump_Table_To_File of + 'Yes' -> + File = string_to_atom(Table_Name ++ ".csv"), + R = ets:tab2file(string_to_atom(Table_Name), File), + io:format("R = ~p~n", [R]), + io:format("File ~p has been created.~n", [File]); + _Other -> + ok + end + end. + +%%--------------------------------------------------------------------- +%% @doc +%% start_throughput/4 starts a throughput logger, which records the +%% number of successfully delivered messages for an specified time- +%% window on a text file named after the parameters passed as +%% arguments to the function. +%% +%% Arguments: +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Condition: (String) "i_routers_j_servers_k_clients" where i, +%% j, and k are integers denoting the number of +%% routers, servers and clients composing the +%% benchmarked system. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% - Timer: (int) M, is the time in seconds in which the logger +%% must be active. +%% +%% @spec start(Technology, Condition, Num_of_Trials, Timer) -> File.csv +%% @end +%%--------------------------------------------------------------------- +start_throughput(Technology, Condition, Num_of_trials, Timer) -> + case whereis(throughput_logger) of + undefined -> + {_Status, Fd} = create_file("", Technology, "Throughput", Condition, Num_of_trials), + case Fd of + unable_to_open_file -> + io:format("ERROR: latency logger cannot start.~n"), + exit(io_error_create_file); + _ -> + record_throughput(Technology, Condition, Num_of_trials, Timer, Fd) + end; + _ -> + io:format("ERROR: The logger process has already started.~n") + end. + +%%--------------------------------------------------------------------- +%% @doc +%% record_throughput/4 starts a throughput logger, which records the +%% number of successfully delivered messages for an specified time- +%% window on a text file named after the parameters passed as +%% arguments to the function. +%% +%% Arguments: +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Condition: (String) "i_routers_j_servers_k_clients" where i, +%% j, and K are integers denoting the number of +%% routers, servers and clients composing the +%% benchmarked system. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% - Timer: (int) M, is the time in seconds in which the logger must +%% be active. +%% +%% @spec record_throughput(Technology, Condition, Num_of_Trials, Timer, Fd) +%% -> File.csv +%% @end +%%--------------------------------------------------------------------- +record_throughput(Technology, Condition, Num_of_trials, Timer, Fd) -> + case Num_of_trials >= 1 of + true -> + register(throughput_logger, spawn(fun() -> loop({recording, Fd})end)), + timer:sleep(Timer * 1000), + stop(Fd, throughput_logger), + start_throughput(Technology, Condition, Num_of_trials - 1, Timer); + false -> + io:format("Throughput benchmarks finished.~n") + end. + +%%--------------------------------------------------------------------- +%% @doc +%% launch_latency/8 initiates a latency logger (see below) on each +%% of the client nodes deployed in the architecture. +%% +%% Arguments: +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Routers: (int) J, number of router nodes. +%% - Servers: (int) K, number of server nodes. +%% - Clients: (int) L, number of client processes. +%% - Num_Node: (int) M, number of client nodes deployed. +%% - Series: (int) N, is the number of series to be recorded. +%% - Domain: (Atom) Domain or host where the client nodes are deployed. +%% - Dir: (String) The directory of the .csv output files. +%% +%% @spec launch(Technology, Routers, Servers, Clients, Num_Nodes, +%% Trials, Timer, Threshold, Domain) -> ok; +%% @end +%%--------------------------------------------------------------------- +launch_latency(Technology, Routers, Servers, Clients, Num_Nodes, Series, Domain, Dir) -> + case Num_Nodes == 0 of + true -> + io:format("All loggers are launched now.~n"), + ok; + false -> + Node = client_node_name(Num_Nodes, Domain), + spawn(Node, fun() -> start_latency(Technology, + Routers, + Servers, + Clients, + Series, + Num_Nodes, + Dir) + end), + launch_latency(Technology, Routers, Servers, Clients, Num_Nodes - 1, Series, Domain, Dir) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% start_latency/7 starts a latency logger, which records the latency +%% of 20000 successfully delivered messages on a text file named after +%% the parameters passed as arguments to the function. +%% +%% Arguments: +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Routers: (int) J, number of router nodes. +%% - Servers: (int) K, number of server nodes. +%% - Clients: (int) L, number of client processes. +%% - Num_Node: (int) M, number of client nodes deployed. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% - Dir: (String) The directory of the .csv output files. +%% +%% @spec start_latency(Technology, Routers, Servers, +%% Clients, Num_Node, Series) -> File.csv +%% @end +%%--------------------------------------------------------------------- +start_latency(Technology, Routers, Servers, Clients, Series, Num_Node, Dir) -> + case whereis(latency_logger) of + undefined -> + Condition = string:join([integer_to_list(Routers), + integer_to_list(Servers), + integer_to_list(Clients), + "Node", + integer_to_list(Num_Node)], "_"), + register(latency_logger, spawn(fun() -> + loop(element(2, create_file(Dir, + Technology, + "Latency", + Condition, + Series)), + 0, + Technology, + Condition, + Series, + Dir) + end)); + _Other -> + io:format("ERROR: The logger process has already started.~n") + end. + +%%--------------------------------------------------------------------- +%% @doc +%% start_latency/4 starts a latency logger, which records the latency +%% of 20000 successfully delivered messages on a text file named after +%% the parameters passed as arguments to the function. +%% +%% Arguments: +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Condition: (String) "i_routers_j_servers_k_clients" where i, +%% j, and k are integers denoting the number of +%% routers, servers and clients composing the +%% benchmarked system. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% - Dir: (String) The directory of the .csv output files. +%% +%% @spec start_latency(Technology, Condition, Num_of_Trials) -> File.csv +%% @end +%%--------------------------------------------------------------------- +start_latency(Technology, Condition, Num_of_trials) -> + case whereis(latency_logger) of + undefined -> + {_Status, Fd} = create_file("", Technology, "Latency", Condition, Num_of_trials), + case Fd of + unable_to_open_file -> + io:format("ERROR: latency logger cannot start.~n"), + exit(io_error_create_file); + _ -> + register(latency_logger, spawn(fun() -> loop(Fd, + 0, + Technology, + Condition, + Num_of_trials, + "") + end)) + end; + _ -> + io:format("ERROR: The logger process has already started.~n") + end. + + + +%%--------------------------------------------------------------------- +%% @doc +%% create_file/1 opens a file with name FileName to hold the recorded +%% information during the benchmarking of the system. If the file +%% specified by the FileName argument does not exist, then the +%% function creates the file. +%% +%% Argument: +%% - FileName: (String) name of the file to be opened or created. +%% +%% @spec create_file(Filename) -> {ok, Fd} | +%% {error, unable_to_open_file} +%% @end +%%--------------------------------------------------------------------- +create_file(FileName) -> + case file:open(FileName,[read,write]) of + {ok, Fd} -> + {ok, Eof} = file:position(Fd,eof), + file:position(Fd,Eof), + io:format("INFO: The logger process has been started.~n"), + {ok, Fd}; + _ -> + io:format("ERROR: The file cannot be opened.~n"), + {error, unable_to_open_file} + end. + +%%--------------------------------------------------------------------- +%% @doc +%% create_file/5 opens a file with name "Technology_Condition_Trial.csv" +%% to hold the recorded information during the benchmarking of the +%% system. If the file specified does not exist, then the function +%% creates the file. +%% +%% Arguments: +%% - Dir: (String) The directory of the .csv output files. +%% - Techology: (String) "DErlang" or "SDErlang". +%% - Benchmark: (String) "Latecy" or "Throughput". +%% - Condition: (String) "i_routers_j_servers_k_clients" where i, +%% j, and k are integers denoting the number of +%% routers, servers and clients composing the +%% benchmarked system. +%% - Num_of_trials: (int) N, is the number of series to be recorded. +%% +%% @spec create_file(Technology, Benchmark, Condition, Trial) -> +%% {file_descriptor, Fd} | {error, unable_to_open_file} +%% @end +%%--------------------------------------------------------------------- +create_file(Dir, Technology, Benchmark, Condition, Trial) -> + Tr = integer_to_list(Trial), + FileName = Dir ++ string:join([Technology, Tr, Benchmark, Condition],"_") ++ ".csv", + case file:open(FileName,[read,write]) of + {ok, Fd} -> + {ok, Eof} = file:position(Fd,eof), + file:position(Fd,Eof), + io:format("INFO: The log file has been created.~n"), + {file_descriptor, Fd}; + _ -> + io:format("ERROR: The file cannot be opened.~n"), + {error, unable_to_open_file} + end. + +%%--------------------------------------------------------------------- +%% @doc +%% stop/2 close the file described by file descriptor Fd and unregisters +%% the logger from the global processes registry. +%% +%% Arguments: +%% - Fd: (IoDevide) Reference to the active file. +%% - Logger: (String) name of the logger process to be stopped. +%% +%% @spec stop(Fd, Logger) -> Message. +%% @end +%%--------------------------------------------------------------------- +stop(Fd, Logger) -> + case whereis(Logger) of + undefined -> + io:format("ERROR: The logger process cannot be stopped because it has not started.~n"); + _ -> + file:close(Fd), + unregister(Logger), + io:format("INFO: The logger process has been stopped.~n") + end. + +%%--------------------------------------------------------------------- +%% @doc +%% loop/1 is a recursive function to record the throughput and the +%% latency of the messages into the file and stop the active logger +%% process. +%% +%% Note that this function is to be called by the generic logger and +%% the throughput_logger processes. +%% +%% @spec loop(Fd) -> term() +%% @end +%%--------------------------------------------------------------------- +loop({initial_state, Table_Name, Num_Trials, Threshold}) -> + TN = string_to_atom(Table_Name), + %%ets:new(TN, [named_table, set]), + {_Status, Fd1} = create_file(Table_Name ++ ".csv"), + case Fd1 of + unable_to_open_file -> + io:format("ERROR: throughput logger cannot start.~n"), + exit(io_error_create_file); + _Other -> + ok + end, + {_Status, Fd2} = create_file(Table_Name ++ "_Summary.txt"), + case Fd2 of + unable_to_open_file -> + io:format("ERROR: throughput logger cannot start.~n"), + exit(io_error_create_file); + _Another -> + io:fwrite(Fd2, "=============== Beginning of Benchmarks ===============~n~n",[]) + end, + Statistics = {0,0,0,0}, + loop({recording, Fd1, Fd2, TN, Num_Trials, Threshold, Statistics}); + +loop({recording, Fd1, Fd2, Table_Name, Num_Trials, Threshold, Statistics}) -> + {Sent, Received, Delivered, Dlv_Blw_Thr} = Statistics, + receive + {s, _Metadata, not_delivered} -> + %% {Unique_ID, Session_Name, Sender, Receiver, Timestamp} = Metadata, + %% {{Y,M,D},{Hour,Min,Sec}} = calendar:now_to_local_time(Timestamp), + %% Time = string:join([integer_to_list(Hour), integer_to_list(Min), integer_to_list(Sec)], ":"), + %% Date = string:join([integer_to_list(D), integer_to_list(M), integer_to_list(Y)], "/"), + %% ets:insert(Table_Name, {Unique_ID, Time, Date, Session_Name, Sender, Receiver, lost}), + New_Sent = Sent + 1, + New_Statistics = {New_Sent, Received, Delivered, Dlv_Blw_Thr}, + loop({recording, Fd1, Fd2, Table_Name, Num_Trials, Threshold, New_Statistics}); + {r, _Metadata, received} -> + %% {_Unique_ID, _Session_Name, _Sender, _Receiver, Timestamp} = Metadata, + %% {{Y,M,D},{Hour,Min,Sec}} = calendar:now_to_local_time(Timestamp), + %% Time = string:join([integer_to_list(Hour), integer_to_list(Min), integer_to_list(Sec)], ":"), + %% Date = string:join([integer_to_list(D), integer_to_list(M), integer_to_list(Y)], "/"), + %% ets:insert(Table_Name, {Unique_ID, Time, Date, Session_Name, Sender, Receiver, lost}), + New_Received = Received + 1, + New_Statistics = {Sent, New_Received, Delivered, Dlv_Blw_Thr}, + loop({recording, Fd1, Fd2, Table_Name, Num_Trials, Threshold, New_Statistics}); + {d, _Metadata, Latency} -> + %% {Unique_ID, Session_Name, Sender, Receiver, Timestamp} = Metadata, + %% {{Y,M,D},{Hour,Min,Sec}} = calendar:now_to_local_time(Timestamp), + %% Time = string:join([integer_to_list(Hour), integer_to_list(Min), integer_to_list(Sec)], ":"), + %% Date = string:join([integer_to_list(D), integer_to_list(M), integer_to_list(Y)], "/"), + %% ets:insert(Table_Name, {Unique_ID, Time, Date, Session_Name, Sender, Receiver, Latency}), + New_Delivered = Delivered + 1, + case Latency < Threshold of + true -> + New_Dlv_Blw_Thr = Dlv_Blw_Thr + 1, + New_Statistics = {Sent, Received, New_Delivered, New_Dlv_Blw_Thr}; + false -> + New_Statistics = {Sent, Received, New_Delivered, Dlv_Blw_Thr} + end, + loop({recording, Fd1, Fd2, Table_Name, Num_Trials, Threshold, New_Statistics}); + {new_trial, New_Num_Trials} -> + {{Y, M, D},{Hour,Min,Sec}} = calendar:now_to_local_time(now()), + Time = string:join([integer_to_list(Hour), integer_to_list(Min), integer_to_list(Sec)], ":"), + Date = string:join([integer_to_list(D), integer_to_list(M), integer_to_list(Y)], "/"), + case Sent /= 0 of + true -> + QoS = (Dlv_Blw_Thr / Sent) * 100; + false -> + QoS = "N/A" + end, + io:fwrite(Fd1,"~p,~p,~p,~p,~p,~p,~p,~p,~p~n", [Num_Trials, + Time, + Date, + Threshold, + Sent, + Received, + Delivered, + Dlv_Blw_Thr, + QoS]), + io:fwrite(Fd2, "Trial ~p: ~p ~p~nThreshold:~p~nMessages Sent: ~p~nMessages Received: ~p~nMessages Delivered: ~p~nMessages Delivered below threshold: ~p~nQuality of service:~p \% ~n~n", [Num_Trials, Time, Date, Threshold, Sent, Received, Delivered, Dlv_Blw_Thr, QoS]), + loop({recording, Fd1, Fd2, Table_Name, New_Num_Trials, Threshold, {0,0,0,0}}); + {stop_throughput} -> + io:fwrite(Fd2, "=============== End of Benchmarks ===============~n~n",[]), + file:close(Fd1), + file:close(Fd2), + ok + end; + +loop({recording, Fd}) -> + receive + {d, Metadata, Latency} -> + {Unique_ID, Session_Name, Client_A, Client_B, Timestamp} = Metadata, + {{Y,M,D},{Hour,Min,Sec}} = calendar:now_to_local_time(Timestamp), + io:fwrite(Fd,"~p, ~p:~p:~p ~p/~p/~p,~p,~p,~p,~p~n", [Unique_ID, + Hour, + Min, + Sec, + D, M, Y, + Session_Name, + Client_A, + Client_B, + Latency]), + file:position(Fd,eof), + loop({recording, Fd}); + stop -> + stop(Fd, logger); + stop_throughput -> + stop(Fd, throughput_logger); + _Other -> + loop({recording, Fd}) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% loop/6 is a recursive function to record the latency of the messages +%% into the file and stop the active latency_logger process. +%% +%% @spec loop(Fd, Record, Technology, Condition, Trial) -> term() | ok +%% @end +%%--------------------------------------------------------------------- +loop(Fd, Record, Technology, Condition, Trial, Dir) -> + receive + {d, Metadata, Latency} -> + {Unique_ID, Session_Name, Client_A, Client_B, Timestamp} = Metadata, + {{Y,M,D},{Hour,Min,Sec}} = calendar:now_to_local_time(Timestamp), + io:fwrite(Fd,"~p,~p:~p:~p ~p/~p/~p,~p,~p,~p,~p~n", [Unique_ID, + Hour, + Min, + Sec, + D, M, Y, + Session_Name, + Client_A, + Client_B, + Latency]), + file:position(Fd,eof), + case Record =< 20000 of + true -> + %%io:format("Trial: ~p, Record: ~p~n", [Trial, Record]), + loop(Fd, Record + 1, Technology, Condition, Trial, Dir); + false -> + self() ! {stop_latency, Trial}, + loop(Fd, Record + 1, Technology, Condition, Trial, Dir) + end; + {stop_latency, Trial} -> + case Trial > 1 of + true -> + %%io:format("stop_latency received; case Trial > 0 of true. Trial = ~p~n", [Trial]), + file:close(Fd), + loop(element(2,create_file(Dir, Technology, "Latency", Condition, Trial - 1)), 0, Technology, Condition, Trial - 1, Dir); + false -> + %%io:format("stop_latency received; case Trial > 0 of false. Trial = ~p~n", [Trial]), + stop(Fd, latency_logger), + %%XXX: Notify the coordinator that a logger has finished. + {ok, H} = inet:gethostname(), + DN = list_to_atom(lists:concat(["dashboard@", H])), + case rpc:call(DN, erlang, whereis, [coordinator]) of + undefined -> ok; + _ -> { coordinator, DN } ! logger_stopped + end, + ok + end + end. + +%%=============================================================================== +%% AUXILIARY FUNCTIONS +%%=============================================================================== +%%-------------------------------------------------------------------- +%% @doc +%% string_to_atom/1 takes a string and returns an atom, or an +%% existing atom if that is the case. +%% +%% @spec string_to_atom(String) -> atom() | existing_atom() +%% @end +%%-------------------------------------------------------------------- +string_to_atom(String) -> + try list_to_existing_atom(String) of + Val -> + Val + catch + error:_ -> + list_to_atom(String) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% client_node_name/2 builds an atom corresponding to the name of +%% a target client node. +%% +%% @spec client_node_name(Node_Number, Domain) -> atom() | existing_atom() +%% @end +%%-------------------------------------------------------------------- +client_node_name(Node_Number, Domain) -> + case is_atom(Domain) of + true -> + D = atom_to_list(Domain); + false -> + D = Domain + end, + Str = "client_" ++ integer_to_list(Node_Number) ++ "@" ++ D, + string_to_atom(Str). diff --git a/app/IM/src/monitored_db.erl b/app/IM/src/monitored_db.erl new file mode 100644 index 0000000..6d59725 --- /dev/null +++ b/app/IM/src/monitored_db.erl @@ -0,0 +1,323 @@ +%%%-------------------------------------------------------------------- +%%% MONITORED_DB MODULE +%%% +%%% @author: Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Monitored_DB module is an auxiliary data structure to store the +%%% pids of all the processes monitored by a server supervisor. It +%%% is used to retrieve information about processes when they fail +%%% and they need to be restarted again. +%%% @end +%%% Created: 15 Aug 2014 by Mario Moro Hernandez +%%%-------------------------------------------------------------------- + +-module(monitored_db). +-export([start/1, stop/1, start_local/1, stop_local/1, monitored_db/1]). + + +%%%==================================================================== +%%% API +%%%==================================================================== + +%%--------------------------------------------------------------------- +%% Starts the monitored processes database. +%% +%% @spec start(DB_Name) +%% @end +%%--------------------------------------------------------------------- +start(DB_Name) -> + global:register_name(DB_Name, spawn_link(fun() -> monitored_db(DB_Name) end)). + +%%-------------------------------------------------------------------- +%% @doc +%% Stops the monitored processes database. +%% +%% @spec stop(DB_Name) +%% @end +%%-------------------------------------------------------------------- +stop(DB_Name) -> + destroy(DB_Name), + global:unregister_name(DB_Name). + +%%--------------------------------------------------------------------- +%% Starts the monitored processes database, and registers it locally. +%% +%% @spec start_local(DB_Name) +%% @end +%%--------------------------------------------------------------------- +start_local(DB_Name) -> + Pid = spawn_link(fun() -> monitored_db(DB_Name) end), + register(DB_Name, Pid). + +%%-------------------------------------------------------------------- +%% @doc +%% Stops a monitored processes database that has been started locally. +%% +%% @spec stop_local(DB_Name) +%% @end +%%-------------------------------------------------------------------- +stop_local(DB_Name) -> + destroy(DB_Name), + unregister(DB_Name). + +%%-------------------------------------------------------------------- +%% @doc +%% monitored_db is first stage of the database process. It creates an +%% ets table after the atom specified in the parameter DB_Name. +%% +%% @spec monitored_db(DB_Name) +%% @end +%%-------------------------------------------------------------------- +monitored_db(DB_Name) -> + %% process_flag(trap_exit, true), + case ets:info(DB_Name) of + undefined -> + create(DB_Name), + monitored_db_loop(DB_Name); + _ -> + monitored_db_loop(DB_Name) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% monitored_db_loop constitutes the database process, and offers an +%% interface to interact with the ets table, allowing the input or +%% retrieval of information concerning the clients logged in the system. +%% +%% @spec monitored_db_loop(DB_Name) +%% @end +%%-------------------------------------------------------------------- +monitored_db_loop(DB_Name) -> + receive + {From, add, Process_Pid, Process_Type, Process_Name} -> + add_process(DB_Name, Process_Pid, Process_Type, Process_Name), + case From of + undefined -> + ok; + _ -> + From ! {process_added, ok} + end, + monitored_db_loop(DB_Name); + {From, remove, Process_Pid} -> + remove_process(DB_Name, Process_Pid), + case From of + undefined -> + ok; + _ -> + From ! {process_removed, ok} + end, + monitored_db_loop(DB_Name); + {From, peak, Process_Pid} -> + C = peak_process(DB_Name, Process_Pid), + case From of + undefined -> + ok; + _ -> + From ! C + end, + monitored_db_loop(DB_Name); + {From, retrieve, Process_Pid} -> + C = retrieve_process(DB_Name, Process_Pid), + case From of + undefined -> + ok; + _ -> + From ! C + end, + monitored_db_loop(DB_Name); + %% {From, pid_processes_by_type, Process_Type} -> + %% L = retrieve_pids_by_type(Table_Name, Process_Type), + %% case From of + %% undefined -> + %% ok; + %% _ -> + %% From ! L + %% end, + %% monitored_db_loop(DB_Name); + {From, recover, Target_DB_Name, Source_DB_Name} -> + recover_db(Target_DB_Name, Source_DB_Name), + case From of + undefined -> + ok; + _ -> + From ! {recovered, ok} + end, + monitored_db_loop(DB_Name); + {From, recover_server, Target_DB_Name, Source_DB_Name} -> + io:format("From = ~p~n", [From]), + recover_server_table(Target_DB_Name, Source_DB_Name, From), + monitored_db_loop(DB_Name); + {From, stop} -> + stop(DB_Name), + case From of + undefined -> + ok; + _ -> + From ! {monitored_db_destroyed, ok} + end + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Creates a new ets table -named Table_Name- to store the different +%% clients active in the system. +%% +%% @spec create(Table_Name) -> Table_Name | {error, Error} +%% @end +%%--------------------------------------------------------------------- +create(Table_Name) -> + ets:new(Table_Name, [set, named_table]). + +%%--------------------------------------------------------------------- +%% @doc +%% Destroys ets table named Table_Name. +%% +%% @spec destroy(Table_Name) -> Table_Name | {error, Error} +%% @end +%%--------------------------------------------------------------------- +destroy(Table_Name) -> + ets:delete(Table_Name). + +%%--------------------------------------------------------------------- +%% @doc +%% Adds a process to the monitored processes database. +%% +%% @spec add_process(Table_Name, Process_Pid, Process_Type, +%% Process_Name) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +add_process(Table_Name, Process_Pid, Process_Type, Process_Name) -> + ets:insert(Table_Name, {Process_Pid, Process_Type, Process_Name}). + +%%--------------------------------------------------------------------- +%% @doc +%% Removes a process from the monitored_processes database. +%% +%% @spec remove_process(Table_Name, Process_Pid) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +remove_process(Table_Name, Process_Pid) -> + ets:delete(Table_Name, Process_Pid). + +%%--------------------------------------------------------------------- +%% @doc +%% Returns a list containing the data of the process passed as parameter. +%% It does not delete the process from the db. +%% +%% @spec peak_process(Table_Name, Process_Pid) -> +%% [Process_Pid, Process_Type, Process_Name] +%% | {error, Error} +%% @end +%%--------------------------------------------------------------------- +peak_process(Table_Name, Process_Pid) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2'}, + [{'==', '$0', Process_Pid}], + [['$0', '$1', '$2']]}]), + case L == [] of + true -> + L; + false -> + lists:last(L) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Deletes the process passed as parameter from the database and returns +%% a list containing the data of the said process. +%% +%% @spec retrieve_process(Table_Name, Process_Pid) -> +%% [Client_Monitor_Pid, Client] | {error, Error} +%% @end +%%--------------------------------------------------------------------- +retrieve_process(Table_Name, Process_Pid) -> + C = peak_process(Table_Name, Process_Pid), + remove_process(Table_Name, Process_Pid), + C. + +%%--------------------------------------------------------------------- +%% @doc +%% returns a list containing the pids of all the processes of the type +%% specified in Process_Type. It returns an empty list if there are no +%% processes of the said type. +%% +%% @spec retrieve_process(Table_Name, Process_Type) -> +%% [Pid1(), Pid2(), ..., PidK()] | {error, Error} +%% @end +%%--------------------------------------------------------------------- +%% retrieve_pids_by_type(Table_Name, Process_Type)-> +%% L = ets:select(test, [{{'$0', '$1', '$2'}, [{'==', '$1', Process_Type}],['$0']}]), +%% L. + +%%--------------------------------------------------------------------- +%% @doc +%% Replicates the monitored processes table specified in the Source +%% parameter, in a new table with name Table_Name. +%% +%% @spec recover_db(Destination, Source) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +recover_db(Destination, Source) -> + ets:safe_fixtable(Source, true), + replicate(Source, Destination, ets:first(Source)), + ets:safe_fixtable(Source, false). + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to the recover_db(Table_Name, Source) function. This +%% function traverses the source table specified in Source, and feeds the +%% data in the destination table. +%% +%% @spec retrieve_server(Table_Name, Session_Pid) -> true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +replicate(Source, Destination, Key) -> + case Key of + '$end_of_table' -> + true; + _ -> + S = peak_process(Source, Key), + global:whereis_name(Destination) ! {undefined ,add, + lists:nth(1, S), + lists:nth(2, S), + lists:nth(3, S)}, + replicate(Source, Destination, ets:next(Source, Key)) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Replicates the monitored processes table specified in the Source +%% parameter, in a new table with name Table_Name. Source is the ets +%% table owned by the server_supervisor process. +%% +%% @spec recover_server_table(Destination, Source, Destination_Pid) -> +%% true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +recover_server_table(Destination, Source, Destination_Pid) -> + ets:safe_fixtable(Source, true), + replicate_server_table(Source, Destination, ets:first(Source), Destination_Pid), + ets:safe_fixtable(Source, false). + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to the recover_server_table/3 function. This +%% function traverses the source table specified in Source, and feeds the +%% data in the destination table. +%% +%% @spec replicate_server_table(Source, Destination, Key, Destination_Pid) -> +%% true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +replicate_server_table(Source, Destination, Key, Destination_Pid) -> + case Key of + '$end_of_table' -> + Destination_Pid ! true; + _ -> + S = peak_process(Source, Key), + Destination_Pid ! {undefined ,add, + lists:nth(1, S), + lists:nth(2, S), + lists:nth(3, S)}, + replicate_server_table(Source, Destination, ets:next(Source, Key), Destination_Pid) + end. diff --git a/app/IM/src/rhesus.erl b/app/IM/src/rhesus.erl new file mode 100644 index 0000000..7418e2c --- /dev/null +++ b/app/IM/src/rhesus.erl @@ -0,0 +1,82 @@ +-module(rhesus). +-export([chaos_on/0, chaos_on/1, extract_pids/2]). + +%%=============================================================================== +%% CHAOS GENERATION LOGIC +%%=============================================================================== +chaos_on() -> + {A1, A2, A3} = now(), + random:seed(A1,A2,A3), + Timer = (random:uniform(3595) + 5) * 1000, + io:format("A random process will be killed every ~p seconds.~n", [Timer div 1000]), + chaos_on(Timer, undefined). + +chaos_on(Timer) -> + chaos_on(Timer, undefined). + +chaos_on(Timer, Router_Sup_Pid) -> + case Router_Sup_Pid of + undefined -> + rhesus_gets_nervous(Timer); + Pid -> + case is_process_alive(Pid) of + true -> + rhesus_attack(Timer, Pid); + false -> + rhesus_gets_nervous(Timer) + end + end. + +rhesus_gets_nervous(Timer) -> + case find_router_sup_pid() of + not_found -> + io:format("Router supervisor process not found on this node.~n"); + R_Sup_Pid -> + rhesus_attack(Timer, R_Sup_Pid) + end. + +rhesus_attack(Timer, Router_Sup_Pid)-> + timer:sleep(Timer), + {A1, A2, A3} = now(), + random:seed(A1,A2,A3), + case random:uniform(4) of + 1 -> + Router_Sup_Pid ! rhesus_solves_conflict_router, + chaos_on(Timer, Router_Sup_Pid); + _Other -> + Router_Sup_Pid ! rhesus_solves_conflict_server, + chaos_on(Timer, Router_Sup_Pid) + end. + +find_router_sup_pid() -> + find_router_sup_pid(erlang:processes()). + +find_router_sup_pid(List) -> + case List of + [] -> + not_found; + [H|T] -> + {Name,Tuple} = hd(erlang:process_info(H)), + case Name of + current_function -> + {_,F,_} = Tuple, + case F of + router_supervisor -> + H; + _Other -> + find_router_sup_pid(T) + end; + _Any_other -> + find_router_sup_pid(T) + end + end. + +extract_pids(List_Pids, List_Routers) -> + case List_Routers of + [] -> + List_Pids; + [H|T] -> + {_Name, Pid} = H, + extract_pids([Pid|List_Pids],T) + end. + diff --git a/app/IM/src/router.erl b/app/IM/src/router.erl new file mode 100644 index 0000000..f7379c4 --- /dev/null +++ b/app/IM/src/router.erl @@ -0,0 +1,567 @@ +%%%------------------------------------------------------------------- +%%% ROUTER MODULE +%%% +%%% @author Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Router module for the Distributed Erlang instant messenger (IM) +%%% application developed as a real benchmark for the Scalable +%%% Distributed Erlang extension of the Erlang/OTP language. +%%% +%%% This module implementes the functionality for router nodes in a +%%% system similar to the system described in the Section 2 of the +%%% document "Instant Messenger Architectures Design Proposal". +%%% @end +%%% Created : 25 Jul 2014 by Mario Moro Hernandez +%%%------------------------------------------------------------------- +-module(router). +-export([router_supervisor/5, router_process/1]). + +-import(launcher, [launch_router_processes/6, launch_server_supervisors/4]). + +-import(server, [restart_server_supervisor/1]). + +-import(rhesus, [extract_pids/2]). +%%=============================================================================== +%% ROUTER PROCESSES CODE +%%=============================================================================== + +%%--------------------------------------------------------------------- +%% @doc +%% router_supervisor_monitor/1 is a process that monitors the router +%% supervisor process, and re-starts it in the case of failure. +%% +%% This process has three states. The initial state stablishes the +%% link between router_supervisor and router_supervisor_monitor +%% processes. The final state just listens for any change in the +%% router supervisor information, and traps the router supervisor +%% failure triggering the recovery strategy. Finally, the supervisor +%% recovery state is in charge of the recovery of the router supervisor +%% process. +%% +%% @spec router_supervisor_monitor/1. +%% @end +%%--------------------------------------------------------------------- +router_supervisor_monitor({initial_state, R_Sup_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids}) -> + erlang:monitor(process, R_Sup_Pid), + router_supervisor_monitor({final_state, R_Sup_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids}); + +router_supervisor_monitor({supervisor_recovery_state, R_Sup_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids}) -> + erlang:monitor(process, R_Sup_Pid), + R_Sup_Pid ! {recover_router, self(), Monitored_Routers, Routers_List}, + router_supervisor_monitor({final_state, R_Sup_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids}); + +router_supervisor_monitor({final_state, R_Sup_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids}) -> + process_flag(trap_exit, true), + R_Sup_Mon_Pid = self(), + %%io:format("router_supervisor_monitor pid = ~p~n",[R_Sup_Mon_Pid]), + receive + {'DOWN', _Ref, process, Pid, Reason} -> + case Reason of + killed -> + io:format("R_Sup_Mon received {'DOWN', _Ref, process, ~p, ~p}.~nSpawning new router supervisor~n", [Pid, Reason]), + New_R_S_Pid = spawn(fun() -> router_supervisor(R_Sup_Mon_Pid, + Monitored_Routers, + Routers_List, + Routers_Info, + Routers_DBs_Pids) end), + router_supervisor_monitor({supervisor_recovery_state, New_R_S_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids}); + Other-> + io:format("Reason = ~p~n", [Other]) + end; + {R_Sup_Pid, New_Monitored_Routers, New_Routers_List, New_Routers_Info, New_Routers_DBs_Pids} -> + router_supervisor_monitor({final_state, R_Sup_Pid, New_Monitored_Routers, New_Routers_List, New_Routers_Info, New_Routers_DBs_Pids}) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% router_supervisor/5 constitutes the router supervisor process. +%% This process is in charge of spawning the router processes +%% in the router node, during the deployment of the system. Once +%% the system is deployed, the router supervisor process monitors +%% the router processes and re-starts them if they fail. +%% +%% @spec router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, +%% Routers_List, Routers_Info, Routers_DBs_Pids) -> +%% router_process/1 +%% @end +%%-------------------------------------------------------------------- +router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids) -> + process_flag(trap_exit, true), + R_Sup_Pid = self(), + receive + %% Setup actions + {Server_Nodes, Servers_Router_Pr, Router_Processes, Num_Total_Servers, Routers_Listener_Pid, launch_router_processes} -> + io:format("Launching router processes.~n"), + launch_router_processes(self(), Server_Nodes, Servers_Router_Pr, Router_Processes, Num_Total_Servers, Routers_Listener_Pid), + {New_R_Sup_Mon_Pid, _Ref} = spawn_monitor(fun() -> router_supervisor_monitor({initial_state, + R_Sup_Pid, + Monitored_Routers, + Routers_List, + Routers_Info, + Routers_DBs_Pids}) end), + router_supervisor(New_R_Sup_Mon_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids); + {list_routers, Received_Routers_List, Received_Routers_DBs_Pids} -> + timer:sleep(250), + %%New_Routers_Info = get_routers_info(Received_Routers_List, Routers_Info), + spawn(fun() -> get_routers_info(R_Sup_Pid, Received_Routers_List, Routers_Info) end), + monitor_routers(Monitored_Routers, Received_Routers_List), + %%router_supervisor(Monitored_Routers, Received_Routers_List, New_Routers_Info, Received_Routers_DBs_Pids); + R_Sup_Mon_Pid ! {R_Sup_Pid, Monitored_Routers, Received_Routers_List, Routers_Info, Received_Routers_DBs_Pids}, + router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Received_Routers_List, Routers_Info, Received_Routers_DBs_Pids); + %% Recover router actions + {recover_router, Router_Sup_Mon_Pid, Monitored_Routers, Received_Routers_List} -> + spawn(fun() -> update_routers({router_supervisor, Received_Routers_List, R_Sup_Pid}) end), + monitor_routers(Monitored_Routers, Received_Routers_List), + erlang:monitor(process, Router_Sup_Mon_Pid), + router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Received_Routers_List, Routers_Info, Routers_DBs_Pids); + %% Update monitored routers information + {update_routers_info, Received_Routers_List} -> + %%io:format("Received {update_routers_info, ~p}~n", [Received_Routers_List]), + %%New_Routers_Info = get_routers_info(Received_Routers_List, Routers_Info), + spawn(fun() -> get_routers_info(R_Sup_Pid, Received_Routers_List, Routers_Info) end), + %%router_supervisor(Monitored_Routers, Received_Routers_List, New_Routers_Info, Routers_DBs_Pids), + R_Sup_Mon_Pid ! {R_Sup_Pid, Monitored_Routers, Received_Routers_List, Routers_Info, Routers_DBs_Pids}, + router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Received_Routers_List, Routers_Info, Routers_DBs_Pids); + {new_routers_info, New_Routers_Info} -> + %%io:format("New_Routers_Info = ~p~n", [New_Routers_Info]), + R_Sup_Mon_Pid ! {R_Sup_Pid, Monitored_Routers, Routers_List, New_Routers_Info, Routers_DBs_Pids}, + router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Routers_List, New_Routers_Info, Routers_DBs_Pids); + %% Termination logic + {'EXIT', normal} -> + io:format("router_supervisor() terminated normally.~n"); + {'DOWN', _Ref, process, Pid, Reason} -> + case Pid of + R_Sup_Mon_Pid -> + io:format("Router Supervisor Monitor with pid ~p is down.~n", [Pid]), + {New_R_Sup_Mon_Pid, _} = spawn_monitor(fun() -> router_supervisor_monitor({initial_state, + R_Sup_Pid, + Monitored_Routers, + Routers_List, + Routers_Info, + Routers_DBs_Pids}) end), + router_supervisor(New_R_Sup_Mon_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids); + _Other_Pid -> + {Router_Str, _Router_Pid} = lists:keyfind(Pid, 2, Routers_List), + io:format("========================================================~nRouter ~p is down with reason ~p~nRecovering router ~p~n========================================================~n", [Router_Str, Reason,Router_Str]), + %%io:format("Routers_Info = ~p~n", [Routers_Info]), + %% Get info to spawn a new router + {_R_N,_R_P, List_Monitored_Servers, List_Servers, Server_Nodes} = lists:keyfind(Router_Str, 1, Routers_Info), + %%io:format("Info retrieved: {~p,~p,~p,~p,~p}~n", [R_N, R_P, List_Monitored_Servers, List_Servers, Server_Nodes]), + {New_Router_Pid, _Ref_N_R} = spawn_monitor( + fun() -> router_process({recovery_state, + R_Sup_Pid,%%self(), %% <== this might be problematic + Routers_List, + List_Monitored_Servers, + List_Servers, Server_Nodes}) end), + %io:format("Router_Pid of the new router process: ~p~n", [New_Router_Pid]), + New_Monitored_Routers = lists:keyreplace(Pid, 2, Monitored_Routers, {Router_Str, New_Router_Pid}), + New_List_Routers = lists:keyreplace(Pid, 2, Routers_List, {Router_Str, New_Router_Pid}), + %%io:format("New_Monitored_Routers: ~p~n",[New_Monitored_Routers]), + %%io:format("New_List_Routers: ~p~n", [New_List_Routers]), + spawn(fun () -> update_routers({router_list, New_List_Routers, New_List_Routers}) end), + spawn(fun () -> update_router_dbs(Routers_DBs_Pids, New_List_Routers) end), + %%New_Routers_Info = get_routers_info(New_List_Routers, Routers_Info), + spawn(fun() -> get_routers_info(R_Sup_Pid, New_List_Routers, Routers_Info) end), + %%io:format("New_Routers_Info: ~p~n", [New_Routers_Info]), + %%router_supervisor(New_Monitored_Routers, New_List_Routers, New_Routers_Info, Routers_DBs_Pids); + R_Sup_Mon_Pid ! {R_Sup_Pid, New_Monitored_Routers, New_List_Routers, Routers_Info, Routers_DBs_Pids}, + router_supervisor(R_Sup_Mon_Pid, New_Monitored_Routers, New_List_Routers, Routers_Info, Routers_DBs_Pids) + end; + %% Chaos Generation logic + rhesus_solves_conflict_router -> + Router_Pids = extract_pids([self(), R_Sup_Mon_Pid], Routers_List), + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + exit(lists:nth(random:uniform(length(Router_Pids)),Router_Pids),kill), + router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids); + rhesus_solves_conflict_server -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + {_, Router_Pid} = lists:nth(random:uniform(length(Routers_List)),Routers_List), + Router_Pid ! kill_server_process, + router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids); + %% Trap for unexpected messages + Other -> + io:format("router_supervisor received: ~p~n", [Other]), + router_supervisor(R_Sup_Mon_Pid, Monitored_Routers, Routers_List, Routers_Info, Routers_DBs_Pids) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% router_process/1 has basically two missions: to spawn the server +%% supervisor processes and forward clients messages. +%% +%% The process has three states. During the initial state, the router +%% process spawns the server supervisors and informs the router +%% supervisor process. When the server supervisors are spawned and +%% the information is passed to the router_supervisor, the router +%% process changes of state. +%% +%% In the final state, the router process listens for client messages +%% and handles the server supervisors failures. +%% +%% There is a third state (recovery_state) that serves as the initial +%% state when the recovery strategy has been triggered. +%% +%% @spec router_process/1 -> server_supervisor_loop/4 +%% @end +%%--------------------------------------------------------------------- +router_process({initial_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, Server_Nodes}) -> + process_flag(trap_exit, true), + receive + %% Setup Actions + {Router_Name, Received_Server_Nodes, Servers_Router_Pr, Num_Total_Servers, Routers_Listener_Pid, launch_server} -> + io:format("~p spawning servers.~nServer_Nodes:~p~n", [Router_Name, Received_Server_Nodes]), + launch_server_supervisors(Received_Server_Nodes, Servers_Router_Pr, Num_Total_Servers, Routers_Listener_Pid), + New_List_Monitored_Servers = monitored_servers(Received_Server_Nodes, List_Monitored_Servers), + router_process({initial_state, R_Sup_Pid, List_Routers, New_List_Monitored_Servers, Received_Server_Nodes}); + {list_routers, Received_List_Routers} -> + io:format("router_process({initial_state}) received list of routers: ~p~n", [List_Routers]), + router_process({initial_state, R_Sup_Pid, Received_List_Routers, List_Monitored_Servers, Server_Nodes}); + {list_servers, List_Servers}-> + io:format("router_process({initial_state}) received list of servers: ~p~n", [List_Servers]), + monitor_servers(List_Monitored_Servers, List_Servers), + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + %% Trap for unexpected messages. + Other -> + io:format("Something failed at router_process({initial_state}). It received: ~p~n", [Other]), + router_process({initial_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, Server_Nodes}) + end; + +router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}) -> + receive + %% Client login logic + {Client_Name, Client_Pid, login} -> + %%io:format("forward client login request~n"), + {_, Target_Server_Pid} = lists:nth((compression_function(length(List_Servers), Client_Name) + 1), List_Servers), + Target_Server_Pid ! {Client_Name, Client_Pid, login}, + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + %% Chat session request logic + {Sender, Receiver, start_chat_session} -> + %%io:format("forward start chat session request~n"), + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + {_, Target_Server_Pid} = lists:nth(random:uniform(length(List_Servers)),List_Servers), + Target_Server_Pid ! {Sender, Receiver, start_chat_session}, + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + %% Chaos kill request forward + kill_server_process -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + {_, Target_Server_Pid} = lists:nth(random:uniform(length(List_Servers)),List_Servers), + Target_Server_Pid ! kill_server_process, + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + %% Error Handling + %% monitored process finished normally + {'DOWN', _Ref, process, Pid, normal} -> + New_Monitored_Servers = lists:keydelete(Pid, 2, List_Monitored_Servers), + New_List_Servers = lists:keydelete(Pid, 2, List_Servers), + spawn(fun () -> update_routers({server_list, List_Routers, New_List_Servers}) end), + router_process({final_state, R_Sup_Pid, List_Routers, New_Monitored_Servers, New_List_Servers, Server_Nodes}); + %% monitored process finished abnormally. + {'DOWN', _Ref, process, Pid, Reason} -> + {Server_Str, _Server_Pid} = lists:keyfind(Pid, 2, List_Servers), + io:format("========================================================~nServer ~p is down with reason ~p~n========================================================~n", [Server_Str, Reason]), + Node = node_name(Server_Str, Server_Nodes), + Server = string_to_atom(Server_Str), + case Reason of + noproc -> + New_Monitored_Servers = lists:keydelete(Pid, 2, List_Monitored_Servers), + New_List_Servers = lists:keydelete(Pid, 2, List_Servers), + spawn(fun () -> update_routers({server_list, List_Routers, New_List_Servers}) end), + router_process({final_state, R_Sup_Pid, List_Routers, New_Monitored_Servers, New_List_Servers, Server_Nodes}); + noconnection -> + io:format("Fatal error. Node ~p is down, and thus server ~p cannot be restarted.~n", [Node, Server]), + New_Monitored_Servers = lists:keydelete(Pid, 2, List_Monitored_Servers), + New_List_Servers = lists:keydelete(Pid, 2, List_Servers), + spawn(fun () -> update_routers({server_list, List_Routers, New_List_Servers}) end), + router_process({final_state, R_Sup_Pid, List_Routers, New_Monitored_Servers, New_List_Servers, Server_Nodes}); + _Other -> + Server_Sup_Pid = spawn_link(Node, fun() -> restart_server_supervisor({first_stage, Server}) end), + erlang:monitor(process, Server_Sup_Pid), + unlink(Server_Sup_Pid), + New_Monitored_Servers = lists:keyreplace(Pid, 2, List_Monitored_Servers, {Server_Str, Server_Sup_Pid}), + New_List_Servers = lists:keyreplace(Pid, 2, List_Servers, {Server_Str, Server_Sup_Pid}), + spawn(fun () -> update_routers({server_list, List_Routers, New_List_Servers}) end), + router_process({final_state, R_Sup_Pid, List_Routers, New_Monitored_Servers, New_List_Servers, Server_Nodes}) + end; + %% Termination logic + {'EXIT', normal} -> + io:format("router_process() terminated normally.~n"); + {request_router_info, Dest_Pid} -> + Dest_Pid ! [self(), List_Monitored_Servers, List_Servers, Server_Nodes], + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + %% update information + {list_routers, Received_List_Routers} -> + %%io:format("received list of routers (Router/final_stage): ~p~n", [Received_List_Routers]), + router_process({final_state, R_Sup_Pid, Received_List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + {update_servers_list, Updated_List_Servers} -> + %%io:format("received list of servers (update_servers_list): ~p~n", [Updated_List_Servers]), + %%io:format("List_Routers = ~p~n", [List_Routers]), + R_Sup_Pid ! {update_routers_info, List_Routers}, + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, Updated_List_Servers, Server_Nodes}); + {update_routers_list, Updated_List_Routers} -> + %%io:format("received list of routers (update_routers_list): ~p~n", [Updated_List_Routers]), + R_Sup_Pid ! {update_routers_info, Updated_List_Routers}, + router_process({final_state, R_Sup_Pid, Updated_List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + {update_router_supervisor, New_R_Sup_Pid} -> + %%io:format("received list of routers (update_router_supervisor): ~p~n", [New_R_Sup_Pid]), + router_process({final_state, New_R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}); + Other -> + {Router_Str, _Router_Pid} = lists:keyfind(self(), 2, List_Routers), + io:format("~p router_Process received: ~p~n", [Router_Str, Other]), + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}) + end; + +router_process({recovery_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}) -> + process_flag(trap_exit, true), + monitor_servers(List_Monitored_Servers, List_Servers), + router_process({final_state, R_Sup_Pid, List_Routers, List_Monitored_Servers, List_Servers, Server_Nodes}). + + +%%=============================================================================== +%% AUXILIARY FUNCTIONS +%%=============================================================================== + +%%--------------------------------------------------------------------- +%% @doc +%% monitored_servers/2 builds a list of the servers monitored by one +%% router after the list of server nodes received during deployment. +%% +%% @spec monitored_servers(Server_Nodes, Monitored_Servers) -> list() +%% @end +%%--------------------------------------------------------------------- +monitored_servers(Server_Nodes, Monitored_Servers) -> + case Server_Nodes of + [] -> + Monitored_Servers; + [Server|Tail_Servers] -> + S = atom_to_list(Server), + Server_Name = string:left(S, string:chr(S,$@) - 1), + New_Monitored_Servers = Monitored_Servers ++ [Server_Name], + monitored_servers(Tail_Servers, New_Monitored_Servers) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% monitor_servers/2 traverses a list of servers and establishes a +%% monitor-monitored processes relationship between a router process +%% and a server supervisor process. +%% +%% @spec monitor_servers(Monitored_Servers, Servers_List) -> ok +%% @end +%%--------------------------------------------------------------------- +monitor_servers(Monitored_Servers, Servers_List) -> + case Monitored_Servers of + [] -> + ok; + [Server|Tail_Mon_Serv] -> + {_Serv, Server_Pid} = lists:keyfind(Server, 1, Servers_List), + erlang:monitor(process, Server_Pid), + monitor_servers(Tail_Mon_Serv, Servers_List) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% monitor_routers/2 is similar to monitor_servers/2, but for the +%% router_supervisor - router_process processes. +%% +%% @spec monitor_routers(Monitored_Routers, Routers_List -> ok +%% @end +%%--------------------------------------------------------------------- +monitor_routers(Monitored_Routers, Routers_List) -> + case Monitored_Routers of + [] -> + ok; + [Router|Tail] -> + {_Router, Router_Pid} = lists:keyfind(atom_to_list(Router), 1, Routers_List), + erlang:monitor(process, Router_Pid), + monitor_routers(Tail, Routers_List) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% node_name/2 finds the server node corresponding to a server +%% supervisor. +%% +%% @spec node_name(Server, Server_Nodes) -> node name | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +node_name(Server, Server_Nodes) -> + case Server_Nodes of + [] -> + io:format("Error. There is not server node corresponding to the server process.~n"), + {error, no_server_node_found}; + [Target_Node|Tail] -> + T = atom_to_list(Target_Node), + case Server == string:left(T, string:chr(T,$@) - 1) of + true -> + Target_Node; + false -> + node_name(Server, Tail) + end + end. + +%%--------------------------------------------------------------------- +%% @doc +%% update_routers/1 updates the information stored in a router process. +%% This can be: +%% - The list of server supervisor processes monitored by the +%% router process +%% - The list of router processes in the router monitor. +%% - The list of router processes monitored by the router +%% supervisor process. +%% +%% @spec update_routers({Update, Routers, List}) -> ok | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +update_routers({Update, Routers, List}) -> + case Routers of + [] -> + ok; + [{_R_Name, Router_Pid}|T] -> + case Update of + server_list -> + Router_Pid ! {update_servers_list, List}; + router_list -> + Router_Pid ! {update_routers_list, List}; + router_supervisor -> + Router_Pid ! {update_router_supervisor, List} + end, + update_routers({Update, T, List}) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% update_router_dbs/2 updates the information of the routers_db +%% processes when a router process has changed. +%% +%% @spec update_router_dbs(Routers_DBs, List) -> ok. +%% @end +%%--------------------------------------------------------------------- +update_router_dbs(Routers_DBs, List) -> + case Routers_DBs of + [] -> + ok; + [R_DB_Pid|Tail] -> + R_DB_Pid ! {List, receive_router_list}, + update_router_dbs(Tail, List) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% get_routers_info/3 sends the information of the routers supervised +%% by a router supervisor process to the said router supervisor. +%% +%% This funcion also updates the routers information in the router +%% supervisor if this information has changed. +%% +%% @spec get_routers_info(R_Sup_Pid, Routers_List, Routers_Info) -> list() +%% @end +%%--------------------------------------------------------------------- +get_routers_info(R_Sup_Pid, Routers_List, Routers_Info) -> + case Routers_List of + [] -> + R_Sup_Pid ! {new_routers_info, Routers_Info}; + [{Router_Name, Router_Pid}|Tail] -> + Router_Pid ! {request_router_info, self()}, + receive + [Received_Router_Pid, List_Mon_S, List_S, S_Nds] -> + case lists:keyfind(Router_Name, 1, Routers_Info) of + false -> + New_Routers_Info = lists:append(Routers_Info, + [{Router_Name, + Router_Pid, + List_Mon_S, + List_S, + S_Nds}]); + _ -> + New_Routers_Info = lists:keyreplace(Router_Name, + 1, + Routers_Info, + {Router_Name, + Received_Router_Pid, + List_Mon_S, + List_S, + S_Nds}) + end, + get_routers_info(R_Sup_Pid, Tail, New_Routers_Info); + _Other -> + get_routers_info(R_Sup_Pid, Routers_List, Routers_Info) + end + end. + +%% ------------------ +%% Hashing functions. +%% ------------------ + +%%--------------------------------------------------------------------- +%% @doc +%% compression_function/2 returns the hash value within the interval +%% [1,Number of Servers], for a client name. +%% +%% The compression function is: +%% (((A * I) + B) rem P) rem Num_Servers +%% +%% where: +%% A is randomly generated parameter. +%% B is randomly generated parameter. +%% I is the hash code of the client name. +%% P is a prime number greater than the number of buckets +%% (servers). +%% +%% However, A and B are hard-coded random values to avoid +%% inconsistencies. +%% +%% @spec compression_function(Num_Servers, Client_Name) -> integer() +%% @end +%%--------------------------------------------------------------------- +compression_function(Num_Servers, Client_Name) -> + case is_atom(Client_Name) of + true -> + I = hash_code(atom_to_list(Client_Name), 0); + false -> + I = hash_code(Client_Name, 0) + end, + P = 4294967291, %%highest 32-bit prime number + (((33 * I) + 429496) rem P) rem Num_Servers. + +%%--------------------------------------------------------------------- +%% @doc +%% hash_code/2 calculates a hash code for a given string. +%% +%% @spec hash_code(String, Hash_Code) -> integer() +%% @end +%%--------------------------------------------------------------------- +hash_code(String, Hash_Code) -> + case length(String) > 0 of + true -> + Hash_Shift = Hash_Code bsl 5,%% or Hash_Code bsr 27, + [H|T] = String, + New_Hash_Code = Hash_Shift + H, + hash_code(T, New_Hash_Code); + false -> + Hash_Code + end. + +%% ------------------------- +%% Other auxiliary functions +%% ------------------------- + +%%-------------------------------------------------------------------- +%% @doc +%% string_to_atom/1 takes a string and returns an atom, or an +%% existing atom if that is the case. +%% +%% @spec string_to_atom(String) -> atom() | existing_atom() +%% @end +%%-------------------------------------------------------------------- +string_to_atom(String) -> + try list_to_existing_atom(String) of + Val -> + Val + catch + error:_ -> + list_to_atom(String) + end. diff --git a/app/IM/src/server.erl b/app/IM/src/server.erl new file mode 100644 index 0000000..9649618 --- /dev/null +++ b/app/IM/src/server.erl @@ -0,0 +1,988 @@ +%%%-------------------------------------------------------------------- +%%% SERVER MODULE +%%% +%%% @author: Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Server module for the Reliable Distributed Erlang instant +%%% messenger (RD-IM) application developed as a real benchmark for +%%% the Scalable Distributed Erlang extension of the Erlang/OTP +%%% language. +%%% +%%% This module implementes the functionality for server nodes in a +%%% system similar to the system described in the Section 2 of the +%%% document "Instant Messenger Architectures Design Proposal". +%%% @end +%%% Created: 1 Jul 2014 by Mario Moro Hernandez +%%%-------------------------------------------------------------------- + +-module(server). +-export([start_server_supervisor/3, stop_server_supervisor/1, restart_server_supervisor/1]). + +%%--------------------------------------------------------------------- +%% @doc +%% Starts the server by spawing a server supervisor process. +%% +%% @spec start_server_supervisor(Node, Server_Name, Num_Total_Servers) -> +%% Pid | {error, Error} +%% @end +%%--------------------------------------------------------------------- +start_server_supervisor(Node, Server_Name, Num_Total_Servers) -> + process_flag(trap_exit, true), + Mon_Process_Table = monitored_processes_1, + case ets:info(Mon_Process_Table) of + undefined -> + ets:new(Mon_Process_Table, [set, named_table]); + _Other -> + ok + end, + DBs = [monitored_processes_2] ++ client_db_name(Server_Name, Num_Total_Servers) ++ chat_db_name(Server_Name, Num_Total_Servers), + Monitored_DBs = spawn_databases(Node, DBs, Mon_Process_Table, []), + register_dbs(Monitored_DBs), + recover_monitor_db(monitored_processes_2, monitored_processes_1), + {Client_DBs, Chat_DBs} = server_dbs(Server_Name), + Monitored_Processes = {monitored_processes_1, monitored_processes_2}, + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes). + +%%--------------------------------------------------------------------- +%% @doc +%% This function triggers the recovery strategy for the server +%% supervisor process. It runs in two stages. The first one sets up +%% the monitored_db process if it is not alive, and the ets table +%% that holds the information about the processes monitored by the +%% server_supervisor process. Then, it triggers the recovery strategy +%% for the monitored_db process. +%% The second stage, feeds the information received into the ets +%% table of the server_supervisor process. +%% +%% @spec start_server_supervisor(Node, Server_Name, Num_Total_Servers) -> +%% Pid | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +restart_server_supervisor({first_stage, Server_Name}) -> + process_flag(trap_exit, true), + Mon_Process_Table = monitored_processes_1, + Mon_Process_DB_Pid = whereis(monitored_processes_2), + case Mon_Process_DB_Pid of + undefined -> + {New_Mon_Process_DB_Pid, _Ref} = spawn_monitor(fun() -> monitored_db:monitored_db(monitored_processes_2) end), + register(monitored_processes_2, New_Mon_Process_DB_Pid), + New_Mon_Process_DB_Pid ! {undefined, add, New_Mon_Process_DB_Pid, "Monitor_DB", monitored_processes_2}, + ok; + _ -> + case ets:info(Mon_Process_Table) of + undefined -> + ets:new(Mon_Process_Table, [set, named_table]); + _Other -> + ok + end, + Mon_Process_DB_Pid ! {self(), recover_server, Mon_Process_Table, monitored_processes_2} + end, + restart_server_supervisor({second_stage, Server_Name, {Mon_Process_Table, monitored_processes_2}}); + +restart_server_supervisor({second_stage, Server_Name, Monitored_Processes}) -> + {Mon_Process_Table, _Mon_Process_DB} = Monitored_Processes, + receive + {undefined, add, F1, F2, F3} -> + ets:insert(Mon_Process_Table, {F1, F2, F3}), + N_Mon_Pr = mon_proc(F1, F2, F3, Monitored_Processes), + restart_server_supervisor({second_stage, Server_Name, N_Mon_Pr}); + true -> + {Client_DBs, Chat_DBs} = server_dbs(Server_Name), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% Stops the server with the same pid as the Pid parameter. +%% +%% @spec stop_server_supervisor(Pid) -> {'EXIT', normal} +%% @end +%%-------------------------------------------------------------------- +stop_server_supervisor(Pid) -> + Pid!{'EXIT', normal}. + +%%-------------------------------------------------------------------- +%% @doc +%% This function constitutes the server supervisor process. It +%% handles the requests made to the server, and also monitors all +%% the server processes. +%% +%% @spec server_supervisor_loop(Client_DB_Name, Chat_DB_Name, Monitored_Dbs) +%% @end +%%-------------------------------------------------------------------- +server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes) -> + {Monitored_Proc_Table, Monitored_Proc_DB} = Monitored_Processes, + receive + %%client login request. + {Client_Name, Client_Pid, login} -> + Server_Supervisor_Pid = self(), + {Client_Monitor_Pid, _Cl_Mon_Ref} = spawn_monitor(fun() -> client_monitor(Client_Name, + Client_Pid, + [], + Client_DBs, + Server_Supervisor_Pid) + end), + Client_Monitor_Pid ! {start_client_monitor}, + ets:insert(Monitored_Proc_Table, {Client_Monitor_Pid, "Client_Monitor", Client_Name}), + whereis(Monitored_Proc_DB) ! {undefined, add, Client_Monitor_Pid, "Client_Monitor", Client_Name}, + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + %% chat session request. + {Sender, Receiver, start_chat_session} -> + {Chat_Session_Pid, _Ch_Ses_Ref} = spawn_monitor(fun() -> chat_session(Chat_DBs, + [{Sender, undefined}, + {Receiver, undefined}]) + end), + Chat_Session_Pid ! {self(), Sender, Receiver, start_chat_session}, + ets:insert(Monitored_Proc_Table, {Chat_Session_Pid, "Chat_Session", chat_session_name(Sender, Receiver)}), + whereis(Monitored_Proc_DB) ! {undefined, add, Chat_Session_Pid, "Chat_Session", chat_session_name(Sender, Receiver)}, + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + %% finish server_supervisor. + {error, chat_session_already_started} -> + io:format("Session already exists: chat_session process aborted.~n"), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + %% confirmation server supervisor is down without any problems. + {'EXIT', normal} -> + io:format("server_supervisor is down.~n"); + %% confirmation client is logged in. + {login_success, Client_Monitor_Pid} -> + io:format("Client logged in. Client Monitor Pid: ~p~n", [pid_to_list(Client_Monitor_Pid)]), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + %% confirmation chat session started. (This is never executed). + {session_added, ok} -> + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + %% reliability control. + %% monitored process finished normally + {'DOWN', _Ref, process, Pid, normal} -> + ets:delete(Monitored_Proc_Table, Pid), + whereis(Monitored_Proc_DB) ! {undefined, remove, Pid}, + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + %% monitored process finished abnormally. + {'DOWN', Ref, process, Pid, Reason} -> + io:format("Process with pid ~p and ref ~p is down with reason ~p~n", [Pid, Ref, Reason]), + [_Pid, Type, Name] = look_up_pid(Monitored_Proc_Table, Pid), + case Type of + "Monitor_DB" -> + {New_Monitored_Proc_DB_Pid, _Ref} = spawn_monitor(fun() -> monitored_db:monitored_db(Monitored_Proc_DB) end), + register(Monitored_Proc_DB, New_Monitored_Proc_DB_Pid), + ets:delete(Monitored_Proc_Table, Pid), + ets:insert(Monitored_Proc_Table, {New_Monitored_Proc_DB_Pid, "Monitor_DB", Monitored_Proc_DB}), + spawn(fun() -> recover_monitor_db(Monitored_Proc_DB, Monitored_Proc_Table)end), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + "Client_DB" -> + Tokens = string:tokens(atom_to_list(Name), "_"), + {New_Client_DBs, New_Chat_DBs} = replicate_db(Pid, Tokens, Client_DBs, Chat_DBs, Monitored_Processes), + db_recovery(Tokens), + server_supervisor_loop(Server_Name, New_Client_DBs, New_Chat_DBs, Monitored_Processes); + "Chat_DB" -> + Tokens = string:tokens(atom_to_list(Name), "_"), + {New_Client_DBs, New_Chat_DBs} = replicate_db(Pid, Tokens, Client_DBs, Chat_DBs, Monitored_Processes), + db_recovery(Tokens), + server_supervisor_loop(Server_Name, New_Client_DBs, New_Chat_DBs, Monitored_Processes); + "Chat_Session" -> + chat_session_recovery(Pid, Chat_DBs, Monitored_Processes), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + "Client_Monitor" -> + client_monitor_recovery(Pid, Client_DBs, self(), Monitored_Processes), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes) + end; + {recovered, ok} -> + New_Client_DBs = db_names_swap(Client_DBs), + New_Chat_DBs = db_names_swap(Chat_DBs), + server_supervisor_loop(Server_Name, New_Client_DBs, New_Chat_DBs, Monitored_Processes); + kill_server_process -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + case random:uniform(60) of + Number when Number =< 12 -> + %% Processes_To_Kill = ets:select(Monitored_Proc_Table, + %% [{{'$0', '$1', '$2'}, + %% [{'==', '$1', "Monitored_DB"}], + %% ['$0']}]), + Pid_To_Kill = whereis(monitored_processes_2);%%hd(Processes_To_Kill); + Number when Number > 12, Number =< 24 -> + Processes_To_Kill = ets:select(Monitored_Proc_Table, + [{{'$0', '$1', '$2'}, + [{'==', '$1', "Chat_DB"}], + ['$0']}]), + Pid_To_Kill = lists:nth(random:uniform(length(Processes_To_Kill)),Processes_To_Kill); + Number when Number > 24, Number =< 36 -> + Processes_To_Kill = ets:select(Monitored_Proc_Table, + [{{'$0', '$1', '$2'}, + [{'==', '$1', "Client_DB"}], + ['$0']}]), + Pid_To_Kill = lists:nth(random:uniform(length(Processes_To_Kill)),Processes_To_Kill); + Number when Number > 36, Number =< 48 -> + Pid_To_Kill = self(); + Number when Number > 48, Number =< 54 -> + Processes_To_Kill = ets:select(Monitored_Proc_Table, + [{{'$0', '$1', '$2'}, + [{'==', '$1', "Client_Monitor"}], + ['$0']}]), + Pid_To_Kill = lists:nth(random:uniform(length(Processes_To_Kill)),Processes_To_Kill); + Number when Number > 54 -> + Processes_To_Kill = ets:select(Monitored_Proc_Table, + [{{'$0', '$1', '$2'}, + [{'==', '$1', "Chat_Session"}], + ['$0']}]), + Pid_To_Kill = lists:nth(random:uniform(length(Processes_To_Kill)),Processes_To_Kill) + end, + exit(Pid_To_Kill, kill), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes); + %% Trap for any other messages. + Other -> + io:format("Server supervisor received: ~p~n", [Other]), + server_supervisor_loop(Server_Name, Client_DBs, Chat_DBs, Monitored_Processes) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% This function constitutes the chat_session process. It forwards +%% the messages received from Client A to the Client B. It is also +%% responsible of handling the chat session termiation and notifying +%% the clients when this happens. +%% +%% @spec chat_session(Chat_DBs, Clients_info). +%% @end +%%-------------------------------------------------------------------- +chat_session(Chat_DBs, Clients_Info) -> + {Chat_DB_Name, Chat_DB_Replica} = Chat_DBs, + [{Client_A, Client_A_Pid}, {Client_B, Client_B_Pid}] = Clients_Info, + Session_Name = chat_session_name(Client_A, Client_B), + receive + %% Start Chat Session. + {_, Sender, Receiver, start_chat_session} -> + Session_Name = chat_session_name(Sender, Receiver), + global:whereis_name(Chat_DB_Name) ! {self(), peak, Session_Name}, + chat_session(Chat_DBs, Clients_Info); + %% Session setup. + [] -> + {Client_A, New_Client_A_Pid} = {Client_A, find_client_pid(Client_A, 1)}, + {Client_B, New_Client_B_Pid} = {Client_B, find_client_pid(Client_B, 1)}, + case New_Client_B_Pid of + client_not_found -> + New_Client_A_Pid ! {receiver_not_found, Client_B}; + _ -> + New_Clients_Info = [{Client_A, New_Client_A_Pid},{Client_B, New_Client_B_Pid}], + global:whereis_name(Chat_DB_Name) ! {self(), + add, + Session_Name, + self(), + Client_A, + New_Client_A_Pid, + Client_B, + New_Client_B_Pid}, + global:whereis_name(Chat_DB_Replica) ! {undefined, + add, + Session_Name, + self(), + Client_A, + New_Client_A_Pid, + Client_B, + New_Client_B_Pid}, + chat_session(Chat_DBs, New_Clients_Info) + end; + %% Session already exists. + [_,_,_,_,_,_] -> + chat_session(Chat_DBs, Clients_Info); + {session_added, ok} -> + Client_A_Pid ! {chat_session_success_sender, Session_Name, self()}, + Client_B_Pid ! {chat_session_success_receiver, Session_Name, self()}, + chat_session(Chat_DBs, Clients_Info); + %% Message delivery confirmation + %% (Normal client_ + {From, Timestamp, message_delivered_ok} -> + Metadata = {Session_Name, Client_A, Client_B, Timestamp}, + case From of + Client_A -> + Client_A_Pid ! {Metadata, message_delivered_ok}; + Client_B -> + Client_B_Pid ! {Metadata, message_delivered_ok} + end, + chat_session(Chat_DBs, Clients_Info); + %% Traffic generator (Toxic client). + {From, Unique_ID, Timestamp, message_delivered_ok} -> + Metadata = {Unique_ID, Session_Name, Client_A, Client_B, Timestamp}, + case Client_A of + From -> + Client_A_Pid ! {Metadata, message_delivered_ok}; + _To -> + Client_B_Pid ! {Metadata, message_delivered_ok} + end, + chat_session(Chat_DBs, Clients_Info); + %% Session termination logic and confirmation to clients. + %% Normal client + {_, _, _, finish_chat_session} -> + global:whereis_name(Chat_DB_Name) ! {self(), remove, Session_Name}, + global:whereis_name(Chat_DB_Replica) ! {undefined, remove, Session_Name}, + chat_session(Chat_DBs, Clients_Info); + %% Traffic generator (Toxic client). + {_, _, _, _, finish_chat_session} -> + case global:whereis_name(Chat_DB_Name) of + undefined -> + ok; + Chat_DB_Main_Pid -> + Chat_DB_Main_Pid ! {self(), remove, Session_Name} + end, + case global:whereis_name(Chat_DB_Replica) of + undefined -> + ok; + Chat_DB_Replica_Pid -> + Chat_DB_Replica_Pid ! {undefined, remove, Session_Name} + end, + chat_session(Chat_DBs, Clients_Info); + {session_removed, ok} -> + %%io:format("received {session_removed, ok}~n"), + Client_A_Pid ! {Session_Name, {'EXIT', ok}}, + Client_B_Pid ! {Session_Name, {'EXIT', ok}}; + %% Messages delivery action. + %% Normal client. + {From, To, Timestamp, Message} -> + case Client_A == To of + true -> + Client_A_Pid ! {From, Message, self(), Timestamp, receive_message}; + false -> + Client_B_Pid ! {From, Message, self(), Timestamp, receive_message} + end, + chat_session(Chat_DBs, Clients_Info); + %% Traffic generator (Toxic client). + {Unique_ID, From, To, Timestamp, Message} -> + case Client_A == To of + true -> + Client_A_Pid ! {Unique_ID, From, Message, self(), Timestamp, receive_message}; + false -> + Client_B_Pid ! {Unique_ID, From, Message, self(), Timestamp, receive_message} + end, + chat_session(Chat_DBs, Clients_Info); + %% Trap for any other messages. + Other -> + io:format("Something failed at chat_session({final_state, Chat_DBs, Clients_Info}).~nReceived: ~p~n", [Other]), + chat_session(Chat_DBs, Clients_Info) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% This is the client monitor process. It is responsible for pre- +%% venting a client from logging in twice. It also handles the +%% client's logout logic and finishes all the pending chat sessions +%% a client has opened when it terminates (either normally or +%% abnormally). +%% +%% @spec client_monitor(Client_Name, Client_Pid, +%% Client_DB_Name, Server_Supervisor_Pid). +%% @end +%%-------------------------------------------------------------------- +client_monitor(Client_Name, Client_Pid, Opened_Chat_Sessions, Client_DBs, Server_Supervisor_Pid) -> + process_flag(trap_exit, true), + {Client_DB_Name, Client_DB_Replica} = Client_DBs, + receive + %% Login logic. + {start_client_monitor} -> + global:whereis_name(Client_DB_Name) ! {self(), peak, Client_Name}, + receive + [] -> + erlang:monitor(process, Client_Pid), + global:whereis_name(Client_DB_Name) ! {self(), add, Client_Name, Client_Pid, self()}, + global:whereis_name(Client_DB_Replica) ! {undefined, add, Client_Name, Client_Pid, self()}, + client_monitor(Client_Name, Client_Pid, Opened_Chat_Sessions, Client_DBs, Server_Supervisor_Pid); + Response -> + Client_Pid ! {error, client_already_logged_in}, + io:format("Client_DB response: ~p~n", [Response]) + end; + {client_added, ok} -> + Client_Pid ! {login_success, self()}, + Server_Supervisor_Pid ! {login_success, self()}, + client_monitor(Client_Name, Client_Pid, Opened_Chat_Sessions, Client_DBs, Server_Supervisor_Pid); + %% chat sessions the client is involved. + {add_chat_session, Chat_Session} -> + New_Chat_Sessions = Opened_Chat_Sessions ++ [Chat_Session], + client_monitor(Client_Name, Client_Pid, New_Chat_Sessions, Client_DBs, Server_Supervisor_Pid); + {remove_chat_session, Chat_Session_Name} -> + New_Chat_Sessions = lists:keydelete(Chat_Session_Name, 1, Opened_Chat_Sessions), + client_monitor(Client_Name, Client_Pid, New_Chat_Sessions, Client_DBs, Server_Supervisor_Pid); + {'EXIT', Pid, Reason} -> + {'EXIT', Pid, Reason}; + %% monitored client finished normally. + {'DOWN', _Ref, process, _Pid, normal} -> + ok; + %% monitored client finished abnormally (behave as if client logged out). + {'DOWN', _Ref, process, _Pid, _Reason} -> + global:whereis_name(Client_DB_Name) ! {self(), peak_by_client_monitor, self()}, + receive + [Client_Name, Client_Pid, _Client_Monitor_Pid] -> + global:whereis_name(Client_DB_Name) ! {self(), remove, Client_Name}, + global:whereis_name(Client_DB_Replica) ! {undefined, remove, Client_Name}, + finish_session(Client_Name, Opened_Chat_Sessions); + Other -> + io:format("Something failed while handling abnormal client termination.~nReceived ~p~n", [Other]) + end; + %% Logout logic. + {Client_Name, Client_Pid, logout} -> + global:whereis_name(Client_DB_Name) ! {self(), remove, Client_Name}, + global:whereis_name(Client_DB_Replica) ! {undefined, remove, Client_Name}, + client_monitor(Client_Name, Client_Pid, Opened_Chat_Sessions, Client_DBs, Server_Supervisor_Pid); + {client_removed, ok} -> + Client_Pid ! {'EXIT', ok}, + finish_session(Client_Name, Opened_Chat_Sessions) + end. + +%%=============================================================================== +%% AUXILIARY FUNCTIONS +%%=============================================================================== + +%% ------------------------- +%% Names building functions. +%% ------------------------- + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to build the name of chat sessions. +%% +%% @spec chat_session_name(Sender, Receiver) -> atom() +%% @end +%%-------------------------------------------------------------------- +chat_session_name(Sender, Receiver) -> + case is_atom(Sender) of + true -> + S = atom_to_list(Sender); + false -> + S = Sender + end, + case is_atom(Receiver) of + true -> + R = atom_to_list(Receiver); + false -> + R = Receiver + end, + case S < R of + true -> + {Client_A, Client_B} = {S,R}; + false -> + {Client_A, Client_B} = {R,S} + end, + string:join(["chat_session", Client_A, Client_B], "_"). + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to build the name of client database. +%% +%% @spec client_db_name(Server_Number) -> atom() +%% @end +%%-------------------------------------------------------------------- +client_db_name(Server_Number) -> + Target_DB = string:join(["server", integer_to_list(Server_Number), "1","Client_DB"], "_"), + string_to_atom (Target_DB). + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to build the name of client databases. +%% +%% @spec client_db_name(Server_Name, Num_Total_Servers) -> [atom(), atom()] +%% @end +%%-------------------------------------------------------------------- +client_db_name(Server_Name, Num_Total_Servers) -> + [_, Server_Num] = string:tokens(atom_to_list(Server_Name), "_"), + N1 = "server_" ++ Server_Num ++ "_1_Client_DB", + case list_to_integer(Server_Num) == Num_Total_Servers of + true -> + N2 = "server_1_2_Client_DB"; + false -> + N2 = "server_" ++ integer_to_list(list_to_integer(Server_Num) + 1) ++ "_2_Client_DB" + end, + Client_DB_Name = string_to_atom(N1), + Client_DB_Replica = string_to_atom(N2), + [Client_DB_Name, Client_DB_Replica]. + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to build the name of chat databases. +%% +%% @spec chat_db_name(Server_Name, Num_Total_Servers) -> [atom(), atom()] +%% @end +%%-------------------------------------------------------------------- +chat_db_name(Server_Name, Num_Total_Servers) -> + [_, Server_Num] = string:tokens(atom_to_list(Server_Name), "_"), + N1 = "server_" ++ Server_Num ++ "_1_Chat_DB", + case list_to_integer(Server_Num) == Num_Total_Servers of + true -> + N2 = "server_1_2_Chat_DB"; + false -> + N2 = "server_" ++ integer_to_list(list_to_integer(Server_Num) + 1) ++ "_2_Chat_DB" + end, + Chat_DB_Name = string_to_atom(N1), + Chat_DB_Replica = string_to_atom(N2), + [Chat_DB_Name, Chat_DB_Replica]. + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary functions to build the name of the dabases of a server. +%% +%% @spec server_dbs(Server_Name) -> {{atom(), atom()}, {atom(), atom()}} +%% @end +%%-------------------------------------------------------------------- +server_dbs(Server_Name) -> + [_, Server_Num] = string:tokens(atom_to_list(Server_Name), "_"), + Client_DB_Main = "server_" ++ Server_Num ++ "_1_Client_DB", + Client_DB_Repl = "server_" ++ Server_Num ++ "_2_Client_DB", + Chat_DB_Main = "server_" ++ Server_Num ++ "_1_Chat_DB", + Chat_DB_Repl = "server_" ++ Server_Num ++ "_2_Chat_DB", + Client_DBs = {string_to_atom(Client_DB_Main), string_to_atom(Client_DB_Repl)}, + Chat_DBs = {string_to_atom(Chat_DB_Main), string_to_atom(Chat_DB_Repl)}, + {Client_DBs, Chat_DBs}. + +%% ------------------------------------ +%% Databases creation and registration. +%% ------------------------------------ + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to build up a list of the databases monitored +%% by a server supervisor. It spawns the databases needed and then +%% it returns a list containing the names of the DBs monitored by +%% the server_supervisor process. +%% +%% @spec spawn_databases(Node, DBs, Mon_Process_Table, Monitored_DBs) -> +%% list() +%% @end +%%-------------------------------------------------------------------- +spawn_databases(Node, DBs, Mon_Process_Table, Monitored_DBs) -> + case DBs of + [] -> + Monitored_DBs; + [DB|Tail_DBs] -> + case DB of + monitored_processes_2 -> + Type = "Monitor_DB"; + _Other -> + [_,_,_,T,_] = string:tokens(atom_to_list(DB), "_"), + Type = T ++ "_DB" + end, + Spawned_DB = spawn_db(Node, DB, Type, Mon_Process_Table), + New_Monitored_DBs = Monitored_DBs ++ Spawned_DB, + spawn_databases(Node, Tail_DBs, Mon_Process_Table, New_Monitored_DBs) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to build up a list of the databases monitored +%% by a server supervisor. It spawns the databases needed and then +%% it returns a list containing the names of the DBs monitored by +%% the server_supervisor process. +%% +%% There may be a race condition here between spawn_link and monitor +%% operations. +%% +%% @spec replica_node(Node, Server_Name, Num_Total_Servers) -> list() +%% @end +%%-------------------------------------------------------------------- +spawn_db(Node, DB, Type, Mon_Process_Table) -> + case Type of + "Client_DB" -> + DB_Pid = spawn_link(Node, fun() -> client_db:client_db(DB) end); + "Chat_DB" -> + DB_Pid = spawn_link(Node, fun() -> chat_db:chat_db(DB) end); + "Monitor_DB" -> + DB_Pid = spawn_link(Node, fun() -> monitored_db:monitored_db(monitored_processes_2) end) + end, + _DB_Ref = erlang:monitor(process, DB_Pid), + unlink(DB_Pid), + ets:insert(Mon_Process_Table, {DB_Pid, Type, DB}), + [{DB, DB_Pid}]. + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to register gobally the databases spawned by +%% a server supervisor. +%% +%% @spec register_dbs(DBs) -> ok +%% @end +%%-------------------------------------------------------------------- +register_dbs(DBs) -> + case DBs of + [] -> + ok; + [DB|New_DBs] -> + {Name, Pid} = DB, + case Name of + monitored_processes_2 -> + register(Name, Pid); + _Other -> + global:register_name(Name, Pid) + end, + register_dbs(New_DBs) + end. + +%% ------------------------------------- +%% Process recovery auxiliary functions. +%% ------------------------------------- + +%%--------------------------------------------------------------------- +%% @doc +%% Process Identifier function (Process Recovery). +%% +%% @spec look_up_pid(Table_Name, Process_Pid) -> +%% [Pid(), process_type, process_name] | +%% [undefined, undefined, undefined] +%% @end +%%--------------------------------------------------------------------- +look_up_pid(Table_Name, Process_Pid) -> + L = ets:select(Table_Name, [{{'$0', '$1', '$2'}, + [{'==', '$0', Process_Pid}], + [['$0', '$1', '$2']]}]), + case L == [] of + true -> + [undefined, undefined, undefined]; + false -> + lists:last(L) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Monitor processes setter (Used during server supervisor recovery). +%% +%% @spec mon_proc(Pid, Type, Name, Monitored_Processes) -> {atom(), atom()} +%% @end +%%--------------------------------------------------------------------- +mon_proc(Pid, Type, Name, Monitored_Processes) -> + case Type of + "Monitor_DB" -> + erlang:monitor(process, Pid), + New_Mon_Processes = setelement(2, Monitored_Processes, Name), + New_Mon_Processes; + _Other -> + erlang:monitor(process, Pid), + Monitored_Processes + end. + +%% -------------------------------------- +%% Monitored Processes database recovery. +%% -------------------------------------- + +%%--------------------------------------------------------------------- +%% @doc +%% Replicates the monitored processes table specified in the Source +%% parameter, in a new table with name Table_Name. Source is the ets +%% table owned by the server_supervisor process. +%% +%% @spec recover_server_table(Destination, Source, Destination_Pid) -> +%% true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +recover_monitor_db(Monitored_Proc_DB, Monitored_Proc_Table) -> + ets:safe_fixtable(Monitored_Proc_Table, true), + transfer_data(Monitored_Proc_Table, Monitored_Proc_DB, ets:first(Monitored_Proc_Table)), + ets:safe_fixtable(Monitored_Proc_Table, false). + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to the recover_server_table/3 function. This +%% function traverses the source table specified in Source, and feeds the +%% data in the destination table. +%% +%% @spec replicate_server_table(Source, Destination, Key, Destination_Pid) -> +%% true | {error, Error} +%% @end +%%--------------------------------------------------------------------- +transfer_data(Source, Destination, Key) -> + case Key of + '$end_of_table' -> + true; + _ -> + S = look_up_pid(Source, Key), + whereis(Destination) ! {undefined, add, + lists:nth(1, S), + lists:nth(2, S), + lists:nth(3, S)}, + transfer_data(Source, Destination, ets:next(Source, Key)) + end. + +%% ----------------------------- +%% Databases recovery functions. +%% ----------------------------- + +%%-------------------------------------------------------------------- +%% @doc +%% replicate/5 spawns a new db process and swaps the replica and +%% main databases. +%% +%% @spec replicate_db(Failed_Pid, Tokens, Client_DBs, Chat_DBs, +%% Monitored_Processes) -> {{atom(), atom()},{atom(), atom()}} +%% @end +%%-------------------------------------------------------------------- +replicate_db(Failed_Pid, Tokens, Client_DBs, Chat_DBs, Monitored_Processes) -> + {Monitored_Proc_Table, Monitored_Proc_DB} = Monitored_Processes, + [_, Server_Num, Replica_Flag, Type, _] = Tokens, + case Replica_Flag == "1" of + true -> + DB2_Name_Str = string:join(["server", Server_Num, "2", Type, "DB"], "_"); + false -> + DB2_Name_Str = string:join(["server", Server_Num, "1", Type, "DB"], "_") + end, + DB_Name = string_to_atom(string:join(Tokens, "_")), + %% spawn, register and monitor a new Db + case Type of + "Client" -> + {New_DB_Pid, _Ref} = spawn_monitor(fun() -> client_db:client_db(DB_Name) end), + global:register_name(DB_Name, New_DB_Pid), + ets:insert(Monitored_Proc_Table, {New_DB_Pid, "Client_DB", DB_Name}), + whereis(Monitored_Proc_DB) ! {undefined, add, New_DB_Pid, "Client_DB", DB_Name}, + ets:delete(Monitored_Proc_Table, Failed_Pid), + whereis(Monitored_Proc_DB) ! {undefined, remove, Failed_Pid}; + "Chat" -> + {New_DB_Pid, _New_Ref} = spawn_monitor(fun() -> chat_db:chat_db(DB_Name) end), + global:register_name(DB_Name, New_DB_Pid), + ets:insert(Monitored_Proc_Table, {New_DB_Pid, "Chat_DB", DB_Name}), + whereis(Monitored_Proc_DB) ! {undefined, add, New_DB_Pid, "Chat_DB", DB_Name}, + ets:delete(Monitored_Proc_Table, Failed_Pid), + whereis(Monitored_Proc_DB) ! {undefined, remove, Failed_Pid} + end, + %% return data + case Type of + "Client" when (Replica_Flag == "1") -> + New_Client_DBs = {string_to_atom(DB2_Name_Str), DB_Name}, + {New_Client_DBs, Chat_DBs}; + "Chat" when (Replica_Flag == "1") -> + New_Chat_DBs = {string_to_atom(DB2_Name_Str), DB_Name}, + {Client_DBs, New_Chat_DBs}; + _Other -> + {Client_DBs, Chat_DBs} + end. + +%%-------------------------------------------------------------------- +%% @doc +%% db_recovery/1 fires up the data transfer between replica and +%% main databases. +%% +%% @spec db_recovery(Tokens) -> New DB | {error, reason} +%% @end +%%-------------------------------------------------------------------- +db_recovery(Tokens) -> + [_, Server, Replica_Flag, Type, _] = Tokens, + case Replica_Flag == "1" of + true -> + Src = string:join(["server", Server, "2", Type, "DB"], "_"); + false -> + Src = string:join(["server", Server, "1", Type, "DB"], "_") + end, + Destination = string_to_atom(string:join(Tokens, "_")), + Source = string_to_atom(Src), + global:whereis_name(Source) ! {self(), recover, Destination, Source}. + +%%--------------------------------------------------------------------- +%% @doc +%% db_names_swap/1 swaps main and replica db names to use replica +%% as main while main is recovering. +%% +%% @spec db_names_swap(DBs) -> {atom(), atom()} +%% @end +%%--------------------------------------------------------------------- +db_names_swap(DBs) -> + {DB_Repl, DB_Main} = DBs, + [_, _, Rep_Flag, _, _] = string:tokens(atom_to_list(DB_Repl), "_"), + case Rep_Flag == "2" of + true -> + New_DBs = {DB_Main, DB_Repl}; + false -> + New_DBs = {DB_Repl, DB_Main} + end, + New_DBs. + +%% ------------------------------- +%% Chat Session recovery function. +%% ------------------------------- + +%%--------------------------------------------------------------------- +%% @doc +%% Signals the end of the failed chat session to clients and removes +%% the session from Chat_DBs. +%% +%% @spec chat_session_recovery(Session_Pid, Chat_DBs, +%% Monitored_Processes) -> ok. +%% @end +%%--------------------------------------------------------------------- +chat_session_recovery(Session_Pid, Chat_DBs, Monitored_Processes) -> + {Monitored_Proc_Table, Monitored_Proc_DB} = Monitored_Processes, + {Chat_DB_Name, Chat_DB_Replica} = Chat_DBs, + case is_process_alive(global:whereis_name(Chat_DB_Name)) of %% <=== CHECK THIS!!! + true -> + global:whereis_name(Chat_DB_Name) ! {self(), peak_by_pid, Session_Pid}; + false -> + global:whereis_name(Chat_DB_Replica) ! {self(), peak_by_pid, Session_Pid} + end, + receive + [] -> + io:format("chat_session_recovery received []~n"), + ok; + [Session_Name, Session_Pid, _Client_A, Client_A_Pid, _Client_B, Client_B_Pid] -> + Client_A_Pid ! {Session_Name, {'EXIT', ok}}, + Client_B_Pid ! {Session_Name, {'EXIT', ok}}, + global:whereis_name(Chat_DB_Name) ! {undefined, remove, Session_Name}, + global:whereis_name(Chat_DB_Replica) ! {undefined, remove, Session_Name}, + ets:delete(Monitored_Proc_Table, Session_Pid), + whereis(Monitored_Proc_DB) ! {undefined, remove, Session_Pid} + end. + +%% --------------------------------- +%% Client Monitor recovery function. +%% --------------------------------- + +%%--------------------------------------------------------------------- +%% @doc +%% Spawns a new client monitor, signals the new monitor pid to the +%% client, and updates Client_DBs. +%% +%% @spec client_monitor_recovery(Client_Monitor_Pid, Client_DBs, +%% Server_Supervisor_Pid, Monitored_Processes) -> +%% client_monitor/5 | ok +%% @end +%%--------------------------------------------------------------------- +client_monitor_recovery(Client_Monitor_Pid, Client_DBs, Server_Supervisor_Pid, Monitored_Processes) -> + {Monitored_Proc_Table, Monitored_Proc_DB} = Monitored_Processes, + {Client_DB_Name, Client_DB_Replica} = Client_DBs, + case is_process_alive(global:whereis_name(Client_DB_Name)) of + true -> + global:whereis_name(Client_DB_Name) ! {self(), peak_by_client_monitor, Client_Monitor_Pid}; + false -> + global:whereis_name(Client_DB_Replica) ! {self(), peak_by_client_monitor, Client_Monitor_Pid} + end, + receive + [] -> + io:format("client_monitor_recovery/3 received []~n"), + ok; + [Client_Name, Client_Pid, _Client_Monitor_Pid] -> + Client_Pid ! {opened_chat_sessions, self()}, + receive + {chat_sessions, Chat_Sessions} -> + io:format("Chat_Sessions = ~p~n", [Chat_Sessions]), + Chat_Sessions + end, + {New_Client_Monitor_Pid, _Ref} = spawn_monitor(fun() -> client_monitor(Client_Name, + Client_Pid, + Chat_Sessions, + Client_DBs, + Server_Supervisor_Pid) + end), + Client_Pid ! {new_monitor_pid, New_Client_Monitor_Pid}, + global:whereis_name(Client_DB_Name) ! {undefined, update_monitor_pid, Client_Name, New_Client_Monitor_Pid}, + global:whereis_name(Client_DB_Replica) ! {undefined, update_monitor_pid, Client_Name, New_Client_Monitor_Pid}, + ets:insert(Monitored_Proc_Table, {New_Client_Monitor_Pid, "Client_Monitor", Client_Name}), + whereis(Monitored_Proc_DB) ! {undefined, add, New_Client_Monitor_Pid, "Client_Monitor", Client_Name}, + ets:delete(Monitored_Proc_Table, Client_Monitor_Pid), + whereis(Monitored_Proc_DB) ! {undefined, remove, Client_Monitor_Pid} + end. + +%% --------------------------- +%% Other processing functions. +%% --------------------------- + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to finish remaining opened sessions when +%% client logs out. +%% +%% @spec finish_session(Client_Name, Sessions) -> {all_sessions_finished, ok} +%% @end +%%--------------------------------------------------------------------- +finish_session(Client_Name, Sessions) -> + case Sessions == [] of + true -> + {all_sessions_finished, ok}; + false -> + {Session_Name, Session_Pid} = hd(Sessions), + {Client_A, Client_B} = extract_clients(Session_Name), + Session_Pid ! {Client_A, Client_B, undefined, finish_chat_session}, + finish_session(Client_Name, tl(Sessions)) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to find a client pid. +%% +%% @spec find_client_pid(Client_Name, Server_Number) -> pid() | client_not_found +%% @end +%%--------------------------------------------------------------------- +find_client_pid(Client_Name, Server_Number) -> + Client_DB = client_db_name(Server_Number), %% <-This needs to be changed to use the replica as well. + Client_DB_Pid = global:whereis_name(Client_DB), + case Client_DB_Pid of + undefined -> + client_not_found; + _ -> + Client_DB_Pid ! {self(), client_pid, Client_Name}, + receive + [] -> + find_client_pid(Client_Name, Server_Number + 1); + Client_Pid -> + Client_Pid + end + end. + +%%--------------------------------------------------------------------- +%% @doc +%% Auxiliary function to extract client names given the name of a +%% chat session. +%% +%% @spec extract_clients(Session_Name) -> {Client_A, Client_B} +%% @end +%%--------------------------------------------------------------------- +extract_clients(Session_Name) -> + Tokens = string:tokens(Session_Name, "_"), + Client_A = lists:nth(3, Tokens), + Client_B = lists:nth(4, Tokens), + {Client_A, Client_B}. + +%%-------------------------------------------------------------------- +%% @doc +%% string_to_atom/1 takes a string and returns an atom, or an +%% existing atom if that is the case. +%% +%% @spec string_to_atom(String) -> atom() | existing_atom() +%% @end +%%-------------------------------------------------------------------- +string_to_atom(String) -> + try list_to_existing_atom(String) of + Val -> + Val + catch + error:_ -> + list_to_atom(String) + end. + +%%==================================================================== +%% LEGACY CODE +%%==================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% Auxiliary function to build the name of the target node for the +%% DB replicas (This is legacy code, not in use. Left just in case +%% it is useful in future). +%% +%% @spec replica_node(Node, Server_Name, Num_Total_Servers) -> atom() +%% @end +%%-------------------------------------------------------------------- +%% replica_node(Node, Server_Name, Num_Total_Servers) -> +%% Domain = string:sub_string(atom_to_list(Node), length(Server_Name) + 2), +%% Tokens = string:tokens(Server_Name, "_"), +%% case length(Tokens) == 3 of +%% true -> +%% [_, Router_Num, Server_Num] = Tokens, +%% Target_Server_Num = list_to_integer(Server_Num) + 1, +%% S = "server_" ++ Router_Num, +%% case list_to_integer(Server_Num) == Num_Total_Servers of +%% true -> +%% To_Return = S ++ "_1@" ++ Domain; +%% false -> +%% To_Return = S ++ "_" ++ integer_to_list(Target_Server_Num) ++ "@" ++ Domain +%% end; +%% false -> +%% [_, Server_Num] = Tokens, +%% Target_Server_Num = list_to_integer(Server_Num) + 1, +%% case list_to_integer(Server_Num) == Num_Total_Servers of +%% true -> +%% To_Return = "server_1@" ++ Domain; +%% false -> +%% To_Return = "server_" ++ integer_to_list(Target_Server_Num) ++ "@" ++ Domain +%% end +%% end, +%% string_to_atom(To_Return). diff --git a/app/IM/src/toxic_client.erl b/app/IM/src/toxic_client.erl new file mode 100644 index 0000000..2c5c98e --- /dev/null +++ b/app/IM/src/toxic_client.erl @@ -0,0 +1,759 @@ +%%%-------------------------------------------------------------------- +%%% TOXIC CLIENT MODULE +%%% +%%% @author: Mario Moro Hernandez upon a design by Natalia Chechina +%%% @copyright (C) 2014, RELEASE project +%%% @doc +%%% Toxic Client module for the Distributed Erlang instant messenger +%%% (IM) application developed as a real benchmark for the Scalable +%%% Distributed Erlang extension of the Erlang/OTP language. +%%% +%%% This module implementes the traffic generation logic to load a +%%% system similar to the system described in the Section 2 of the +%%% document "Instant Messenger Architectures Design Proposal". +%%% @end +%%% Created: 31 Jul 2014 by Mario Moro Hernandez +%%%-------------------------------------------------------------------- + +-module(toxic_client). +-export([launch/3, launch_traffic/3, launch_fixed_traffic/3, logout/1, stop_client/1, message/3]). + +-import(client_db, [start_local/1, stop_local/1]). + +%%%=================================================================== +%%% CLIENT CODE +%%%=================================================================== + +%%--------------------------------------------------------------------- +%% @doc +%% Starts the client by spawing and registering a client process. +%% +%% @spec start(Client_Name) -> Pid | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +start_client(Client_Name) -> + global:whereis_name(routers_db) ! {retrieve_routers, self()}, + receive + {Routers_List, router_list} -> + Tokens = string:tokens(Client_Name, "_"), + {Num_Node, _} = string:to_integer(lists:nth(2, Tokens)), + Pid = spawn(fun() -> client({Client_Name, Routers_List}) end), + whereis(client_db_name(Num_Node)) ! {self(), update_pid, Client_Name, Pid}, + receive + true -> + Answer = true; + false -> + Answer = false + end, + Pid ! Answer; + Other -> + io:format("Unable to start the client. No routers list received.~n"), + io:format("start_client/1 received: ~p~n", [Other]) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% Stops the client specified as a parameter by sending an +%% {'EXIT', ok} message. +%% +%% @spec stop(Client_Name) +%% @end +%%-------------------------------------------------------------------- +stop_client(Client_Name) -> + whereis(Client_Name) ! {'EXIT', ok}. + +%%-------------------------------------------------------------------- +%% @doc +%% This is the function that constitutes the client process. It has +%% two different states. Initially, it is spawned and registered +%% using the client name, only. Once it has been registered, it +%% waits until it receives the Pid of its monitor. +%% +%% When the client_monitor Pid has been received, the login process +%% is concluded. +%% +%% @spec client({Client_Name, Client_Monitor_Pid, Chat_Pids}) +%% @end +%%-------------------------------------------------------------------- +client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}) -> + receive + %% Chat Logic + {Sender, Receiver, Message, send_message} -> + Session_Name = chat_session_name(Sender, Receiver), + case retrieve(Session_Name, Chat_Pids) of + [] -> + Router_Pid = pick_router(Routers_List), + case rpc:pinfo(Router_Pid) of + undefined -> + global:whereis_name(routers_db) ! {retrieve_routers, self()}, + self() ! {Sender, Receiver, Message, send_message}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + _Other -> + Router_Pid ! {Sender, Receiver, start_chat_session}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}), + self() ! {Sender, Receiver, Message, send_message} + end; + Chat_Session_Pid -> + Unique_ID = integer_to_list(erlang:crc32(Session_Name)) ++ integer_to_list(sys_time(now())), + Chat_Session_Pid ! {Unique_ID, Sender, Receiver, now(), Message}, + case Message == finish_chat_session of + true -> + ok; + false -> + Metadata = {Unique_ID, Session_Name, Sender, Receiver, now()}, + notify_logger(s, Metadata, not_delivered) + end, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}) + end; + + {New_Routers_List, router_list} -> + client({Client_Name, Client_Monitor_Pid, Chat_Pids, New_Routers_List}); + {chat_session_success_sender, Session_Name, Chat_Session_Pid} -> + %%io:format("Client_A received {chat_session_success, Chat_Session_Pid}.~n"), + New_Chat_Pids = [{Session_Name, Chat_Session_Pid}|Chat_Pids], + client({Client_Name, Client_Monitor_Pid, New_Chat_Pids, Routers_List}); + {receiver_not_found, Receiver} -> + io:format("Sorry, but ~p is not logged in.~n", [Receiver]), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {chat_session_success_receiver, Session_Name, Chat_Session_Pid} -> + %%io:format("Client_B received {chat_session_success, Chat_Session_Pid}.~n"), + New_Chat_Pids = [{Session_Name, Chat_Session_Pid}|Chat_Pids], + Client_Monitor_Pid ! {add_chat_session, {Session_Name, Chat_Session_Pid}}, + client({Client_Name, Client_Monitor_Pid, New_Chat_Pids, Routers_List}); + {Metadata, message_delivered_ok} -> + Timestamp_2 = now(), + {_, _, _, _, Timestamp_1} = Metadata, + Latency = timer:now_diff(Timestamp_2, Timestamp_1)/2, + %%io:format("ok. Latency = ~p microseconds~n", [Latency]), + notify_logger(d, Metadata, Latency), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {Unique_ID, Sender, _Message, Chat_Session_Pid, Timestamp, receive_message} -> + %%io:format("~p wrote: ~p~n", [Sender, Message]), + Chat_Session_Pid ! {Sender, Unique_ID, Timestamp, message_delivered_ok}, + Metadata = {Unique_ID, undefined, Sender, Client_Name, now()}, + notify_logger(r, Metadata, received), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {Chat_Session_Name, {'EXIT', ok}} -> + New_Chat_Pids = lists:keydelete(Chat_Session_Name, 1, Chat_Pids), + %%io:format("~p removed from Chat_Pids.~n", [Chat_Session_Name]), + Client_Monitor_Pid ! {remove_chat_session, Chat_Session_Name}, + client({Client_Name, Client_Monitor_Pid, New_Chat_Pids, Routers_List}); + {opened_chat_sessions, Client_Monitor_Recover_Pid} -> + Client_Monitor_Recover_Pid ! {chat_sessions, Chat_Pids}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {new_monitor_pid, New_Client_Monitor_Pid} -> + client({Client_Name, New_Client_Monitor_Pid, Chat_Pids, Routers_List}); + %% Logout Logic + {logout} -> + Client_Monitor_Pid ! {Client_Name, self(), logout}, + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + %% Client Termination Logic + {error, client_already_logged_in} -> + io:format("You are logged in already.~n"), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}); + {'EXIT', ok} -> + %% THIS NEEDS TO BE CHANGED unregister(Client_Name), + io:format("~p is stopped now.~n", [Client_Name]); + %% Trap for unexpected messages + Other -> + io:format("Something is failing at client(/4).~n"), + io:format("Received: ~p~n", [Other]), + client({Client_Name, Client_Monitor_Pid, Chat_Pids, Routers_List}) + end; + +%%-------------------------------------------------------------------- +%% @doc +%% client/1 is the client process when the client is not logged in +%% yet. When the client_monitor Pid has been received, it changes +%% the state and changes to the full logged-in client; i.e., the +%% client({Client_Name, Client_Pid, Client_Monitor_Pid}) function +%% defined above. +%% +%% @spec client(Client_Name) +%% @end +%%-------------------------------------------------------------------- +client({Client_Name, Routers_List}) -> + receive + true -> %% This allows to have a client up, but not logged-in + client({Client_Name, Routers_List}); + false -> + {error, client_already_logged_in}; + {login_success, Client_Monitor_Pid} -> + io:format("Client is successfuly logged in.~n"), + client({Client_Name, Client_Monitor_Pid, [], Routers_List}); + {error, client_already_logged_in} -> + io:format("You are logged in already.~n"); + %% unregister(Client_Name); + Other -> + io:format("This is failing at client(/2).~n"), + io:format("Received: ~p~n", [Other]), + client({Client_Name, Routers_List}) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% login/1 function starts a client named after the string passed as +%% parameter and registers it in the system. If the client is logged +%% in already, then returns an error message. +%% +%% @spec login(Client_Name) -> yes | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +login(Client_Name) -> + Client_Pid = client_pid(Client_Name), + case Client_Pid of + undefined-> + start_client(Client_Name), + global:whereis_name(routers_db) ! {retrieve_routers, self()}, + receive + {Routers_List, router_list} -> + Router_Pid = pick_router(Routers_List), + Router_Pid ! {Client_Name, client_pid(Client_Name), login}; + Other -> + io:format("Login failed at retrieving routers list.~nReceived: ~p~n", [Other]) + end, + yes; + {error, client_does_not_exist} -> + io:format("Client ~p does not exist.~n", [Client_Name]); + _ -> + io:format("You are logged in already.~n") + %% {error, client_already_logged_in} + end. + +%%--------------------------------------------------------------------- +%% @doc +%% logout/1 unegisters the client from the system. +%% +%% @spec logout(Client_Name) -> ok | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +logout(Client_Name) -> + Client_Pid = client_pid(Client_Name), + case Client_Pid of + undefined -> + io:format("Client ~p is not logged in.~n",[Client_Name]), + {error, client_not_logged_in}; + _ -> + Client_Pid ! {logout}, + ok + end. + +%%-------------------------------------------------------------------- +%% @doc +%% message/3 enables the communication between two clients, sending +%% the message passed as the parameter Message to the client specified +%% as the To parameter. +%% +%% @spec message(From, To, Message) -> yes | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +message(From, To, Message) -> + client_pid(From) ! {From, To, Message, send_message}, + yes. + +%%%==================================================================== +%%% TRAFFIC GENERATION LOGIC +%%%==================================================================== + +%%--------------------------------------------------------------------- +%% @doc +%% launch/3 distributes a number of clients evenly among the number +%% of client nodes, and triggers the clients login logic. +%% +%% @spec launch(Total_Num_Clients, Num_Nodes, Domain) -> ok +%% @end +%%--------------------------------------------------------------------- +launch(Total_Num_Clients, Num_Nodes, Domain) -> + case Num_Nodes == 0 of + true -> + ok; + false -> + Num_Clients_Node = Total_Num_Clients div Num_Nodes, + New_Total_Num_Clients = Total_Num_Clients - Num_Clients_Node, + Node = client_node_name(Num_Nodes, Domain), + spawn(Node, fun() -> launch_node(Node, Num_Clients_Node, Num_Nodes) end), + launch(New_Total_Num_Clients, Num_Nodes - 1, Domain) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% launch_node/3 triggers the logic to set up the clients that will +%% run on an specific node. +%% +%% @spec launch_node(_Node, Num_Clients, Num_Node) -> setup/2 +%% @end +%%--------------------------------------------------------------------- +launch_node(_Node, Num_Clients, Num_Node) -> + io:format("Setting up clients and traffic generators ", []), + setup(Num_Clients, Num_Node). + %% spawn(Node, fun() -> start_traffic(Num_Node, 0, Num_Clients, Num_Clients) end). + +%%--------------------------------------------------------------------- +%% @doc +%% setup/2 sets up the clients database to store the names and the +%% pids of the clients that will be available in the node passed as +%% the second argument to the function. Then, it triggers the clients +%% login logic for the clients in that node +%% +%% @spec setup(Num_Clients, Num_Node) -> setup_clients/2 +%% @end +%%--------------------------------------------------------------------- +setup(Num_Clients, Num_Node) -> + io:format(".", []), + launch_clients_db(Num_Node), + setup_clients(Num_Clients, Num_Node). + +%%--------------------------------------------------------------------- +%% @doc +%% setup_clients/2 logs in to the system as many clients as the number +%% passed as the first argument, and stores the said clients in the +%% local client database of clients available for traffic generation. +%% +%% @spec setup_clients(Num_Clients, Num_Node) -> Status messages. +%% @end +%%--------------------------------------------------------------------- +setup_clients(Num_Clients, Num_Node)-> + io:format(".", []), + case Num_Clients == 0 of + true -> + io:format("~nClients at node client_~p@domain are set up.~n", [Num_Node]); + false -> + Client_Name = client_name(Num_Clients, Num_Node), + Clients_DB_Name = client_db_name(Num_Node), + whereis(Clients_DB_Name) ! {self(), add, Client_Name, undefined, not_in_use}, + receive + {client_added, ok} -> + io:format("Client_Name = ~p~n",[Client_Name]), + login(Client_Name), + setup_clients(Num_Clients - 1, Num_Node); + Other -> + io:format("~n~nError adding toxic clients to the database.~n",[]), + io:format("setup_clients/2 received: ~p~n", [Other]), + io:format("Aborting.~n") + end + end. + +%%--------------------------------------------------------------------- +%% @doc +%% launch_clients_db/1 starts a client_db process and registers it +%% locally to the node specified as the argument. +%% +%% @spec launch_clients_db(Num_Node) -> Status message | {error, Reason} +%% @end +%%--------------------------------------------------------------------- +launch_clients_db(Num_Node) -> + DB_Name = client_db_name(Num_Node), + start_local(DB_Name). + +%%--------------------------------------------------------------------- +%% @doc +%% launch_traffic/3 triggers the traffic generation. It spawns +%% as many traffic generator processes as the half of the clients +%% logged in on each client node. +%% +%% @spec launch_traffic(Total_Num_Clients, Num_Nodes, Domain) -> +%% start_traffic/4 +%% @end +%%--------------------------------------------------------------------- +launch_traffic(Total_Num_Clients, Num_Nodes, Domain) -> + case Num_Nodes == 0 of + true -> + ok; + false -> + Num_Clients_Node = Total_Num_Clients div Num_Nodes, + New_Total_Num_Clients = Total_Num_Clients - Num_Clients_Node, + Node = client_node_name(Num_Nodes, Domain), + io:format("Launching traffic generators at node ~p~n",[Node]), + spawn(Node, fun() -> start_traffic(Num_Nodes, + 0, + Num_Clients_Node, + Num_Clients_Node div 2) + end), + launch_traffic(New_Total_Num_Clients, Num_Nodes - 1, Domain) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% start_traffic/4 starts a number of traffic_generator processes +%% equals to the half of the clients present in the node passed as +%% the first parameter. Note that Total_Nodes is not used (hence the +%% value 0 when this function is spawned in launch_traffic/3). +%% +%% @spec start_traffic(Num_Node, Total_Nodes, +%% Total_Clients, Num_Generators) -> ok +%% @end +%%--------------------------------------------------------------------- +start_traffic(Num_Node, Total_Nodes, Total_Clients, Num_Generators) -> + case Num_Generators == 0 of + true -> + ok; + false -> + spawn(fun() -> traffic_generator(Num_Node, Total_Nodes, Total_Clients)end), + timer:sleep(100), + start_traffic(Num_Node, Total_Nodes, Total_Clients, Num_Generators - 1) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% traffic_generator/3 chooses two random clients from a clients +%% list. Then, it generates random strings of variable length +%% (between 1 and 255 characters), and sends them from one client +%% to the other. +%% +%% It also records a timestamp of the current system clock in +%% microseconds, and sends a tuple {timestamp, message} from a +%% randomly chosen client to another randomly chosen client. +%% +%% @spec traffic_generator() -> interaction_generator/4 +%% @end +%%-------------------------------------------------------------------- +traffic_generator(Num_Node, Total_Nodes, Total_Clients)-> + %%io:format("Traffic Generator started.~n",[]), + Environment = {Num_Node, Total_Nodes, Total_Clients}, + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + Interactions = random:uniform(48) + 12, + {Sender, Receiver} = pick_random_clients(Num_Node, Total_Nodes, Total_Clients), + %%io:format("Sender is ~p.~n",[Sender]), + %%io:format("Receiver is ~p.~n",[Receiver]), + interaction_generator(Environment, Sender, Receiver, Interactions). + +%%--------------------------------------------------------------------- +%% @doc +%% interaction_generator/4 simulates a conversation between the two +%% clients specified as Sender and Receiver. +%% +%% @spec interaction_generator(Environment, Sender, +%% Receiver, Interactions)-> string() | {timestamp, message} +%% @end +%%--------------------------------------------------------------------- +interaction_generator(Environment, Sender, Receiver, Interactions)-> + {Client_A, Client_A_Pid} = Sender, + {Client_B, _Client_B_Pid} = Receiver, + %%io:format("There are ~p interactions left.~n", [Interactions]), + case Interactions > 0 of + true -> + timer:sleep(random:uniform(20) * 1000), + %%io:format("Sending message to: ~p~n", [Client_B]), + Message = [random:uniform(26) + 96 || _ <- lists:seq(1, random:uniform(75))], + Client_A_Pid ! {Client_A, Client_B, Message, send_message}, + interaction_generator(Environment, Receiver, Sender, Interactions - 1); + false -> + Client_A_Pid ! {Client_A, Client_B, finish_chat_session, send_message}, + %%io:format("Interaction finished.~n"), + {Num_Node, Total_Nodes, Total_Clients} = Environment, + traffic_generator(Num_Node, Total_Nodes, Total_Clients) + end. + +%%===================================================================== +%% The following functions are equivalent to the functions for the +%% normal traffic generation. The difference is that they only generate +%% 5 conversations, each of them 2 minutes long, and composed of 15 +%% messages. The generated traffic rate is similar to that of the normal +%% traffic generators. +%%===================================================================== +launch_fixed_traffic(Total_Num_Clients, Num_Nodes, Domain) -> + case Num_Nodes of + 0 -> + ok; + _Other -> + Num_Clients_Node = Total_Num_Clients div Num_Nodes, + New_Total_Num_Clients = Total_Num_Clients - Num_Clients_Node, + Node = client_node_name(Num_Nodes, Domain), + io:format("Launching traffic generators at node ~p~n",[Node]), + spawn(Node, fun() -> start_fixed_traffic(Num_Nodes, + 0, + Num_Clients_Node, + Num_Clients_Node div 2) + end), + launch_fixed_traffic(New_Total_Num_Clients, Num_Nodes - 1, Domain) + end. + +start_fixed_traffic(Num_Node, Total_Nodes, Total_Clients, Num_Generators) -> + case Num_Generators of + 0 -> + ok; + _other -> + spawn(fun() -> fixed_traffic_generator(Num_Node, Total_Nodes, Total_Clients, 5) end), + timer:sleep(100), + start_fixed_traffic(Num_Node, Total_Nodes, Total_Clients, Num_Generators - 1) + end. + +fixed_traffic_generator(Num_Node, Total_Nodes, Total_Clients, Num_Conversations) -> + case Num_Conversations of + 0 -> + ok; + _Other -> + Environment = {Num_Node, Total_Nodes, Total_Clients}, + {Sender, Receiver} = pick_random_clients(Num_Node, Total_Nodes, Total_Clients), + fixed_interaction_generator(Environment, Sender, Receiver, 15, Num_Conversations) + end. + +fixed_interaction_generator(Environment, Sender, Receiver, Interactions, Num_Conversations) -> + {Client_A, Client_A_Pid} = Sender, + {Client_B, _Client_B_Pid} = Receiver, + case Interactions > 0 of + true -> + Message = [random:uniform(26) + 96 || _ <- lists:seq(1, random:uniform(75))], + Client_A_Pid ! {Client_A, Client_B, Message, send_message}, + timer:sleep(8000), + fixed_interaction_generator(Environment, Receiver, Sender, Interactions - 1, Num_Conversations); + false -> + Client_A_Pid ! {Client_A, Client_B, finish_chat_session, send_message}, + {Num_Node, Total_Nodes, Total_Clients} = Environment, + fixed_traffic_generator(Num_Node, Total_Nodes, Total_Clients, Num_Conversations - 1) + end. + +%%-------------------------------------------------------------------- +%% @doc +%% pick_random_clients/2 returns a tuple containing the information +%% of two clients randomly chosen from a clients list. +%% +%% @spec pick_random_clients(Num_Node, Total_Nodes, Total_Clients) -> +%% {Client_A, Client_B} | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +pick_random_clients(Num_Node, _Total_Nodes, Total_Clients) -> + Sender = pick_random_sender(Num_Node, Total_Clients), + Receiver = pick_random_receiver(Num_Node, Total_Clients), + put_back_client(Sender), + {Sender,Receiver}. + +%%-------------------------------------------------------------------- +%% @doc +%% This function returns a tuple {Client_Name, Client_Pid} corres- +%% ponding to a client selected from a clients list. +%% +%% @spec pick_random_sender(Num_Node, Total_Clients) -> +%% {Client_Name, Client_Pid} | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +pick_random_sender(Num_Node, Total_Clients) -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + Num_Client = random:uniform(Total_Clients), + Client_Name = client_name(Num_Client, Num_Node), + Target_DB = whereis(client_db_name(Num_Node)), + Target_DB ! {self(), retrieve, Client_Name}, + receive + [] -> + pick_random_sender(Num_Node, Total_Clients); + [Client, Client_Pid, not_in_use] -> + {Client, Client_Pid} + end. + +%%-------------------------------------------------------------------- +%% @doc +%% This function returns a tuple {Client_Name, Client_Pid} corres- +%% ponding to a client selected from a clients list. +%% +%% @spec pick_random_sender(Num_Node, Total_Clients) -> +%% {Client_Name, Client_Pid} | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +pick_random_receiver(Num_Node, Total_Clients) -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + Num_Client = random:uniform(Total_Clients), + Client_Name = client_name(Num_Client, Num_Node), + Target_DB = whereis(client_db_name(Num_Node)), + Target_DB ! {self(), peak, Client_Name}, + receive + [] -> + pick_random_receiver(Num_Node, Total_Clients); + [Client_Name, Client_Pid, not_in_use] -> + {Client_Name, Client_Pid} + end. + +%%-------------------------------------------------------------------- +%% @doc +%% put_back_client/1 is an auxiliary function to the clients selec- +%% tion routine. It puts the "Client_A" or sender client back to +%% the list that contains all the clients logged in the system. +%% +%% @spec put_back_client(Client) -> ok | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +put_back_client(Client) -> + {Client_Name, Client_Pid} = Client, + Tokens = string:tokens(Client_Name, "_"), + {Num_Node, _} = string:to_integer(lists:nth(2, Tokens)), + Target_DB = whereis(client_db_name(Num_Node)), + Target_DB ! {self(), add, Client_Name, Client_Pid, not_in_use}, + receive + {client_added, ok} -> + ok; + _Other -> + put_back_client(Client) + end. + +%%%=================================================================== +%%% AUXILIARY FUNCTIONS +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% This function returns the pid of a router, from a routers list +%% that is passed as parameter. +%% +%% @spec pick_router(Routers_List) -> Pid | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +pick_router(Routers_List) -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + Router = lists:nth(random:uniform(length(Routers_List)),Routers_List), + {_, Router_Pid} = Router, + Router_Pid. + +%%-------------------------------------------------------------------- +%% @doc +%% chat_session_name/2 returns the name of a chat session, built +%% after the names of the clients passed as parameters. +%% +%% @spec chat_session_name(Sender, Receiver) -> string() | {error, Reason} +%% @end +%%-------------------------------------------------------------------- +chat_session_name(Sender, Receiver) -> + case Sender < Receiver of + true -> + {Client1, Client2} = {Sender, Receiver}; + false -> + {Client1, Client2} = {Receiver, Sender} + end, + string:join(["chat_session", Client1, Client2], "_"). + +%%--------------------------------------------------------------------- +%% @doc +%% client_name/2 is an auxiliary function to build client names. +%% +%% @spec client_name(Num_Client, Num_Node) -> string() +%% @end +%%--------------------------------------------------------------------- +client_name(Num_Client, Num_Node) -> + TC = string:concat("tc", integer_to_list(Num_Client)), + string:join([TC, integer_to_list(Num_Node)], "_"). + +%%--------------------------------------------------------------------- +%% @doc +%% client_db_name/1 is an auxiliary function to build the clients_db +%% name of the node passed as the argument. +%% +%% @spec client_db_name(Node_Number) -> atom() | existing_atom() +%% @end +%%--------------------------------------------------------------------- +client_db_name(Node_Number) -> + Target_DB = string:join(["client", + integer_to_list(Node_Number), + "Client_DB"], "_"), + try list_to_existing_atom(Target_DB) of + Val -> + Val + catch + error:_ -> + list_to_atom(Target_DB) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% client_node_name/2 is an auxiliary function to build the name of +%% a client node given a node number and a domain. +%% +%% @spec client_node_name(Node_Number, Domain) -> atom() | existing_atom() +%% @end +%%--------------------------------------------------------------------- +client_node_name(Node_Number, Domain) -> + case is_atom(Domain) of + true -> + D = atom_to_list(Domain); + false -> + D = Domain + end, + Str = "client_" ++ integer_to_list(Node_Number) ++ "@" ++ D, + string_to_atom(Str). + +%%--------------------------------------------------------------------- +%% @doc +%% retrieve/2 is an auxiliary function to retrieve a chat session +%% pid from a client's sessions registry. +%% +%% @spec retrieve(Session_Name, Chat_Pids) -> Pid() | empty list. +%% @end +%%--------------------------------------------------------------------- +retrieve(Session_Name, Chat_Pids) -> + case lists:keyfind(Session_Name, 1, Chat_Pids) of + false -> + []; + {_Chat_Session_Name, Chat_Session_Pid} -> + Chat_Session_Pid + end. + +%%--------------------------------------------------------------------- +%% @doc +%% client_pid/1 is an auxiliary function to retrieve a client pid. +%% +%% @spec client_pid(Client_Name) -> Pid() +%% @end +%%--------------------------------------------------------------------- +client_pid(Client_Name) -> + Tokens = string:tokens(Client_Name, "_"), + {Num_Node, _} = string:to_integer(lists:nth(2, Tokens)), + Target_DB = whereis(client_db_name(Num_Node)), + Target_DB ! {self(), peak, Client_Name}, + receive + [] -> + {error, client_does_not_exist}; + [Client_Name, Client_Pid, not_in_use] -> + Client_Pid + end. + +%%-------------------------------------------------------------------- +%% @doc +%% string_to_atom/1 takes a string and returns an atom, or an +%% existing atom if that is the case. +%% +%% @spec string_to_atom(String) -> atom() | existing_atom() +%% @end +%%-------------------------------------------------------------------- +string_to_atom(String) -> + try list_to_existing_atom(String) of + Val -> + Val + catch + error:_ -> + list_to_atom(String) + end. + +%%--------------------------------------------------------------------- +%% @doc +%% sys_time/1 returns as an integer the time passed as a tuple +%% +%% @spec sys_time({A1, A2, A3}) -> integer() +%% @end +%%--------------------------------------------------------------------- +sys_time({A1, A2, A3}) -> + ((A1 * 1000000) + A2) * 1000000 + A3. + + +%%--------------------------------------------------------------------- +%% @doc +%% notify_logger/3 abstracts the action of notifying the logger when +%% a message has been sent, received or aknowledged. +%% +%% @spec notify_logger(Action, Metadata, Status) -> +%% ok | {Action, Metadata, Status} +%% @end +%%--------------------------------------------------------------------- +notify_logger(Action, Metadata, Status) -> + case whereis(throughput_logger) of + undefined -> + ok; + Pid_Throughput_Logger -> + Pid_Throughput_Logger ! {Action, Metadata, Status} + end, + case whereis(latency_logger) of + undefined -> + ok; + Pid_Latency_Logger -> + Pid_Latency_Logger ! {Action, Metadata, Status} + end. diff --git a/bench/im_bench/Makefile b/bench/im_bench/Makefile new file mode 100644 index 0000000..3302b9f --- /dev/null +++ b/bench/im_bench/Makefile @@ -0,0 +1,26 @@ +ERLC = erlc +ERLC_OPTS = +ERL = erl +ERL_LIB_DIR = + +SRCD = src +EBIND = ebin-$(OTP) + +ERLF = $(wildcard $(SRCD)/*.erl) +BEAMF = $(patsubst $(SRCD)/%.erl,$(EBIND)/%.beam,$(ERLF)) + +.PHONY: bench clean + +bench: $(BEAMF) + +$(EBIND)/%.beam: $(SRCD)/%.erl + $(ERLC) $(ERLC_OPTS) -o$(EBIND) $< + +$(BEAMF): | $(EBIND) + +$(EBIND): + mkdir -p $(EBIND) + +clean: + $(RM) -rf ebin-* + diff --git a/bench/im_bench/conf/bench.conf b/bench/im_bench/conf/bench.conf new file mode 100644 index 0000000..a8d3aa1 --- /dev/null +++ b/bench/im_bench/conf/bench.conf @@ -0,0 +1,5 @@ +NUMBER_OF_SLAVE_NODES=14 + +SLAVE_NODES=router_1,server_1,server_2,server_3,client_1,client_2,client_3,client_4,client_5,client_6,client_7,client_8,client_9,client_10 + +DEPENDENCIES=IM diff --git a/bench/im_bench/data/.gitignore b/bench/im_bench/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bench/im_bench/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bench/im_bench/src/im_bench.erl b/bench/im_bench/src/im_bench.erl new file mode 100644 index 0000000..0b6fe82 --- /dev/null +++ b/bench/im_bench/src/im_bench.erl @@ -0,0 +1,29 @@ +-module(im_bench). + +-export([bench_args/2, run/3]). + +bench_args(_Version, _Conf) -> + [[]]. + +run(_, _, Conf) -> + %% Setup a coordinator to know when the benchmark finished. This is done by + %% counting the number of loggers that have finished. + register(coordinator, self()), + launcher:start(3, 1, 3, 10, [greedy], 1), + {_,DataDir} = lists:keyfind(datadir, 1, Conf), + logger:launch_latency("Bencherl_test", 1, 3, 8000, 10, 1, greedy, DataDir ++ "/"), + timer:sleep(60000), %XXX: Just to make sure that all clients have logged in. + toxic_client:launch(8000, 10, greedy), + timer:sleep(60000), + toxic_client:launch_traffic(8000, 10, greedy), + loop(10). + +%% loop/1 is a helper function that "prevents" run/3 from finishing until all +%% loggers have halted. Without this function the benchmark would finish after +%% launching the traffic generators and, thus, bencherl would kill all spawned +%% nodes, i.e. routers, servers, etc. +loop(0) -> ok; +loop(N_Loggers) -> + receive + logger_stopped -> loop(N_Loggers - 1) + end.