It took a long time to decide to learn Erlang instead of Haskell or OCaml/F#. What I really want to learn for now are the OTP principles of distributed application. So I wrote my first Erlang script (not yet an application) and it confirmed my point of view: Erlang make easy the development of distributed program! really!!!
At my work we needed to test performance of a TCP server (and we’ll used Grinder because most of our API is Python). I’ve decided to use Erlang to do the same thing as an exercise: grind.erl (of course it will never become neither as huge as Grinder neither as efficient as Tsung; I even do not hope it can be usefull). But with few lines of code, I was able to reproduce the same behavior as Grinder: launch on multiple machine multiple tester agents executing some test function and gather the results of all the tests.
Ok, I did not told all the truth: that’s not a very small number of lines: arround 260 (with tests and all). But all the code is for running multiple time a test function and gathering some statistics on its result (run_task). Distributed this behaviour among multiple Erlang node is only a map call (distribute_task)!
Here is a code extract:
distribute_task(NodeLst, Task) ->
NodeLstLen = length(NodeLst),
StatListener = spawn(grind, statistic_gathering, [now(), NodeLstLen]),
io:format(
"~w create statistic gathering process ~w on node ~w~n",
[self(), StatListener, node()]
),
TaskRunnerCreator = fun(Node) -> spawn(
Node,
grind,
run_task,
[StatListener, Task]
) end,
Runners = lists:map(TaskRunnerCreator, NodeLst),
io:format(
"~w create a list of task runners: ~w~n",
[self(), Runners]
),
Runners.
And here is a usage example:
grind:init([node(), grinderl@node.net]).
grind:distribute_task(
[node(), grinderl@node.net], % use 2 nodes
{
% on each node run the test function in 50 concurrent process
{concurrent, 50},
% two statistics to gather
[{mean, writetime}, % a real value (mean, std. dev., min, max, med will be retrieved
{count, writer_val} % a occurence counter
],
% foo function to test: must return a tuple {ok|error, Pid, ValLst}
fun(Writer, WritenValue) ->
FWrite = fun() -> io:format("~s got ~w~n", [Writer, WritenValue]) end,
{WriteTime, _Res} = timeit(FWrite),
{ok, self(), [WriteTime, {Writer, WritenValue}]}
end,
% arguments to used for each call of the test function
[{rr, ["bea", "pouf"]}, % first is taken in the list with a round-robin style
{choice, [0, 1, 12, 42, 77]} % second argument is randomly choosen from the list
]
}
)
This first steps was to become more familiar with Erlang language. My next steps will be:
- use edoc …
- use logger/trace services instead of printing every function call
- use generic services (gen_event, gen_server)
- create an OTP application (application, supervisor)
- use Mnesia to gather statistics
- what about OTP release
- embed everything in distribution package (automake/autoconf ?)