-module(rb). -export([ start/2, bench/1, statistics_to_csv/2 ]). -export([ create_process_ring/2 ]). %% Creates a ring of N processes create_process_ring(N) when N>0 -> spawn(fun() -> create_process_ring (N-1, self()) end). %% Spawns the process and give each of them its Neighbor's Pid create_process_ring(0, Neighbor) -> loop(Neighbor); create_process_ring(N, Neighbor) -> Pid = spawn(?MODULE, create_process_ring, [N-1, Neighbor]), loop(Pid). %% Terminates a process in the ring cleanup_process(Pid) -> Pid ! cleanup, ok. %% Loop function for a process: %% When a message tuple is received, if the message count M %% is 0, notify the controlling process. %% is higher than 0, send the message to its Neighbor %% When a cleanup message is received, notify the Neighbor and exit. loop(Neighbor) -> receive {message, Parent, 0} -> Parent ! done, loop(Neighbor); {message, Parent, M} -> % io:format("will send message from ~p to ~p (next: ~p).~n", [M, self(), Neighbor]), Neighbor ! {message, Parent, M-1}, loop(Neighbor); cleanup -> cleanup_process(Neighbor) end. %% Send a message to every processes in the ring. send_messages(_Bench, 0, _M) -> ok; send_messages(Bench, N, M) -> Bench ! {message, self(), M}, send_messages(Bench, N-1, M). %% This function is ran by the controlling process: %% Waits for as many completion message as there are processes in the ring. wait_for_completion(0) -> ok; wait_for_completion(N) -> receive done -> wait_for_completion(N-1) end. %% Returns tuples containing information about time spent while sending messages. make_statistics(ProcCount, MsgCountPerProc) when MsgCountPerProc>0 -> {_, Time} = statistics(runtime), TotalMsgCount = ProcCount * MsgCountPerProc, {{proc_count, ProcCount}, {msg_count, TotalMsgCount}, {run_time, Time}, {msg_time, Time/TotalMsgCount}}. %% Sends messages to a ring and waits for completion. launch(Bench, ProcCount, MsgCount) -> garbage_collect(), statistics(runtime), send_messages(Bench, ProcCount, MsgCount), wait_for_completion(ProcCount). %% Creates the process ring, launch the test and cleanup the processes. %% ProcCount is the count of simultaneous processes and MsgCount is the total %% count of messages sent. %% This means there are 'ProcCount div MsgCount' messages sent to the process ring. %% div was chosen to keep the count of messages an integer. start(ProcCount, MsgCount) -> MsgCountPerProc = MsgCount div ProcCount, Bench = create_process_ring(ProcCount), try launch(Bench, ProcCount, MsgCountPerProc), Stats = make_statistics(ProcCount, MsgCountPerProc), Stats after cleanup_process(Bench) end. %% Starts the benchmark for N number of processes %% where N is between 1 and 2^14 (16384 processes). %% Will send MsgCount messages in the ring. bench(MsgCount) -> lists:map(fun(N) -> io:format("Benchmarking ~p processes...~n", [N]), start(N, MsgCount) end, lists:map (fun(N)-> pow(2, N)-1 end, lists:seq(1, 14))). %%% Utility functions %% Computes the power N of U pow(_U, 0) -> 1; pow(U, N) -> U*pow(U, N-1). %% Writes a list of statistics in CSV format to a file. statistics_to_csv(File, L) -> {ok, S} = file:open(File, write), io:format(S, "proc_count;msg_count;run_time;msg_time~n", []), lists:foreach(fun({{proc_count, ProcCount}, {msg_count, TotalMsgCount}, {run_time, RunTime}, {msg_time, MsgTime}}) -> io:format(S, "~w;~w;~w;~w~n",[ProcCount, TotalMsgCount, RunTime, MsgTime]) end, L), file:close(S).