Posts Tagged ‘conf’

An easy way to handle configuration parameters in Erlang

June 28, 2011 Leave a comment

In one of my previous post (text in Italian, code in English), I showed you how to read an Erlang configuration file and parse it to retrieve a specific value used by your application.

This technique is really easy to implement and allows user to modify on the fly the configuration by changing the configuration file, but unfortunately it introduces also several drawbacks. You should already know that reading/writing files is quite an expensive operation in almost every language around, well Erlang is not excluded; moreover this kind of approach introduces some security problems (e.g. what if the configuration file is corrupted during runtime?).

Improving our configuration

How can we deal configuration in an easy and better way? Well, we know that Erlang processes can share messages in a very fast and effective way, thus we can think of creating a process (in my case a gen_server named mod_conf) which handles requests for configuration parameters.

What should mod_conf do? First of all it should read during its initialization the original configuration file and store the list of tuples in its state, given an atom (i.e. a configuration tag) it should also be able to retrieve from its state the associated value.

Since we can use the nice interface provided by the gen_server behaviour, we may think to add two more functionalities to mod_conf: lookup/1, and update/2. The first of the two functions above will return given a tag the current value stored in the state of the gen_server for that tag, while the second function will update given a tag and a new value the current state store in the state.

Let’s see how we can implement this:



%% API

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

-export([lookup/1, update/2]).

-define(SERVER, ?MODULE).

-record(state, {conf}).

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

init([]) ->
    {ok, Conf} = file:consult("configuration.cfg"),
    {ok, #state{conf=Conf}}.

handle_call({lookup, Tag}, _From, State) ->
    Reply = case lists:keyfind(Tag, 1, State#state.conf) of
                {Tag, Value} ->
                false ->
                    {error, noinstance}
    {reply, Reply, State};

handle_call({update, {Tag, Value}}, _From, State) ->
    NewConf = lists:keyreplace(Tag, 1, State#state.conf, {Tag, Value}),
    Reply = ok,
    {reply, Reply, State#state{conf=NewConf}};

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

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

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

terminate(_Reason, _State) ->

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

lookup(Tag) ->
    gen_server:call(?SERVER, {lookup, Tag}).

update(Tag, Value) ->
    gen_server:call(?SERVER, {update, {Tag, Value}}).

So far, so good. Now we can try to benchmark our implementation and see its performances, we can try also to compare the results achieved by using this kind of approach with the results obtained by using a normal configuration file.

-define(CONFTYPE, [file, pserver]).
-define(KEYS, [tag1, tag2, tag3, tag4, tag5, tag6]).
-define(RETRIES, 100000).
-define(KEYNUM, 6).

start() ->
        lists:foreach(fun(ConfType) ->
                                      test(ConfType, 0, ?RETRIES)
                      end, ?CONFTYPE).

test(ConfType, PartialTime, 0) ->
        AvgTime = PartialTime/?RETRIES,
        io:format("Avg-Time: ~pus. Method: ~p.~n", [AvgTime, ConfType]);

test(file, PartialTime, Times) ->
        {Time, _Val} = timer:tc(file_utility,
                                [lists:nth(random:uniform(?KEYNUM), ?KEYS)]),
        test(file, PartialTime+Time, Times-1);

test(pserver, PartialTime, Times) ->
        {Time, _Val} = timer:tc(process_conf,
                                [lists:nth(random:uniform(?KEYNUM), ?KEYS)]),
        test(pserver, PartialTime+Time, Times-1).

In order to run the previous test you must create and compile another module (file_utility) which exports the following function:

get_conf_value(What) ->
    {ok, Conf} = file:consult("configuration.cfg"),
    case lists:keyfind(What, 1, Conf) of
        {What, Value} ->
        false ->
            {error, noinstance}

Ok, time for testing!

paolo@paolo-laptop:~/Desktop/various/test$ erl
Erlang R13B03 (erts-5.7.4)
Eshell V5.7.4  (abort with ^G)
1> test:start().
Avg-Time: 728.07486us. Method: file.
Avg-Time: 9.8088us. Method: pserver.

Fair enough, the behaviour of our implementation seems much faster..but in fact this implementation is not totally safe! Using only one process to handle configuration may create a bottleneck in case of a huge amount of requests, therefore if you are in this situation if preferable to use a process pool.

Ok, this is all for today, but before the conclusion of this post I would like to say one thing more: this is just a really simple demo on how things can be implemented to improve your systems, but other ways are possible in erlang…I suggest you to read this post on stackoverflow to get a better insight on the topic!