%% Demonstration of the snowball effect triggered by a transient
%% overload and selective receive on a long message queue.
%% Assumes /proc/stat in linux-2.6.3 format.

%% Increate InitialPeriod if your system is too slow.

-module(snowball).
-compile(export_all).

run() ->
    {ok,_} = gen_server:start_link({local,backend}, ?MODULE, 0, []),
    Server = spawn_link(?MODULE, server, [undefined]),
    spawn_link(?MODULE, monitor, [Server, get_ticks(), 0]),
    InitialPeriod = 500,
    Client = spawn_link(?MODULE, client, [Server, InitialPeriod]),
    receive after 2000 -> ok end,
    incr_load(Server, Client, get_ticks()),
    receive after 10000 -> ok end,
    gen_server:call(backend, pause),
    receive after 60000 -> ok end,
    erlang:halt().

incr_load(Server, Client, {PrevBusy,PrevTotal}) ->
    receive after 10000 -> ok end,
    {Busy,Total} = Ticks = get_ticks(),
    Load = (Busy-PrevBusy) * 100 div (Total-PrevTotal),
    io:format("Average load was ~w %.~n", [Load]),
    if Load < 10 -> Client ! speedup, incr_load(Server, Client, Ticks);
       true -> io:format("~nModerate load reached.~n~n")
    end.

monitor(Server, {PrevBusy,PrevTotal}, PrevCount) ->
    receive after 1000 -> ok end,
    {_, MQL} = process_info(Server, message_queue_len),
    {Busy,Total} = Ticks = get_ticks(),
    Count = gen_server:call(backend, read),
    Load = (Busy-PrevBusy) * 100 div (Total-PrevTotal),
    io:format("queue_length=~w  load=~w %  rate=~w/s~n",
	      [MQL, Load, Count-PrevCount]),
    monitor(Server, Ticks, Count).

get_ticks() ->
    {ok, F} = file:open("/proc/stat", [read]),
    case io:fread(F, '', "~*s ~d ~d ~d ~d") of
	{ok, [T1,T2,T3,T4]} -> file:close(F), {T1+T2+T3, T1+T2+T3+T4};
	_ -> exit(unsupported_proc_stat_format)
    end.

utime() ->
    {MS,S,US} = now(),
    (MS*1000000+S)*1000000 + US.

%%%% Client.

client(Server, Period) ->
    io:format("~nClient sending ~w messages/s.~n~n", [1000000 div Period]),
    client(Server, Period, utime()).

client(Server, Period, Next) ->
    Wait = lists:max([0, Next-utime()]) div 1000,
    receive speedup -> client(Server, Period*4 div 5)
    after Wait -> Server ! "hello", client(Server, Period, Next+Period)
    end.

%%%% Server.

server(State) ->
    receive Msg ->
	    NewState = gen_server:call(backend, {State,Msg}),
	    server(NewState)
    end.

%%%% Backend.

init(State) ->
    {ok, State}.

handle_call(pause, _From, State) ->
    io:format("~nBackend pausing for 1 second.~n~n"),
    receive after 1000 -> ok end,
    {reply, ok, State};
handle_call(read, _From, State) ->
    {reply, State, State};
handle_call({S,_Msg}, _From, State) ->
    {reply, S, State+1}.

%%%%
