%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%
%%

%%
-module(mnesia_cost).
-compile(export_all).

%% This code exercises the mnesia system and produces a bunch
%% of measurements on what various things cost

-define(TIMES, 1000).  %% set to at least 1000 when running for real !!

%% This is the record we perform all ops on in this test

-record(item, {a = 1234,
	       b = foobar,
	       c = "1.2.3.4",
	       d = {'Lennart', 'Hyland'},
	       e = true 
	      }).

go() ->
    go([node() | nodes()]).
    
go(Nodes) when hd(Nodes) == node() ->
    {ok, Out} = file:open("MNESIA_COST", write),
    put(out, Out),
    
    rpc:multicall(Nodes, mnesia, lkill, []),
    ok = mnesia:delete_schema(Nodes),
    ok = mnesia:create_schema(Nodes),
    rpc:multicall(Nodes, mnesia, start, []),
    TabDef = [{attributes, record_info(fields, item)}],
    {atomic, ok} = mnesia:create_table(item, TabDef),

    round("single ram copy", "no index"),
    {atomic, ok} = mnesia:add_table_index(item, #item.e),
    round("single ram copy", "One index"),

    {atomic, ok} = mnesia:add_table_index(item, #item.c),
    round("single ram copy", "Two indexes"),

    {atomic, ok} = mnesia:del_table_index(item, #item.e),
    {atomic, ok} = mnesia:del_table_index(item, #item.c),

    {atomic, ok} = mnesia:change_table_copy_type(item, node(), disc_copies),
    round("single disc copy", "no index"),

    {atomic, ok} = mnesia:change_table_copy_type(item, node(), ram_copies),

    case length(Nodes) of
	Len when Len < 2 ->
	    format("<WARNING> replication skipped. Too few nodes.", []);
	_Len ->
	    N2 = lists:nth(2, Nodes),
	    {atomic, ok} = mnesia:add_table_copy(item, N2, ram_copies),
	    round("2 replicated ram copy", "no index")
    end,
    file:close(Out),
    erase(out),
    ok.

round(Replication, Index) ->
    run(Replication, Index, [write],
	fun() -> mnesia:write(#item{}) end),


    run(Replication, Index, [read],
	fun() -> mnesia:read({item, 1234}) end),

    run(Replication, Index, [read, write],
	fun() -> mnesia:read({item, 1234}),
		 mnesia:write(#item{}) end),

    run(Replication, Index, [wread, write],
	fun() -> mnesia:wread({item, 1234}),
		 mnesia:write(#item{}) end),


    run(Replication, Index, [match, write, write, write],
	fun() -> mnesia:match_object({item, 1, '_', '_', '_', true}),
		 mnesia:write(#item{a =1}),
		 mnesia:write(#item{a =2}),
		 mnesia:write(#item{a =3}) end).


format(F, As) ->
    io:format(get(out), F, As).

run(What, OtherInfo, Ops, F) ->
    run(t, What, OtherInfo, Ops, F).

run(How, What, OtherInfo, Ops, F) ->
    T1 = erlang:now(),
    statistics(runtime),
    do_times(How, ?TIMES, F),
    {_, RunTime} = statistics(runtime),
    T2 = erlang:now(),
    RealTime = subtr(T1, T2),
    report(How, What, OtherInfo, Ops, RunTime, RealTime).

report(t, What, OtherInfo, Ops, RunTime, RealTime) ->
    format("~s, ~s,  transaction call ", [What, OtherInfo]),
    format("Ops is ", []),
    lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops),

    format("~n   ~w/~w Millisecs/Trans ~w/~w MilliSecs/Operation ~n~n",
	      [RunTime/?TIMES, 
	       RealTime/?TIMES,
	       RunTime/(?TIMES*length(Ops)),
	       RealTime/(?TIMES*length(Ops))]);

report(dirty, What, OtherInfo, Ops, RunTime, RealTime) ->
    format("~s, ~s, dirty calls ", [What, OtherInfo]),
    format("Ops is ", []),
    lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops),

    format("~n   ~w/~w Millisecs/Bunch ~w/~w MilliSecs/Operation ~n~n",
	      [RunTime/?TIMES, 
	       RealTime/?TIMES,
	       RunTime/(?TIMES*length(Ops)),
	       RealTime/(?TIMES*length(Ops))]).


subtr(Before, After) ->
    E =(element(1,After)*1000000000000
	+element(2,After)*1000000+element(3,After)) -
        (element(1,Before)*1000000000000
         +element(2,Before)*1000000+element(3,Before)),
    E div 1000.

do_times(t, I, F) ->
    do_trans_times(I, F);
do_times(dirty, I, F) ->
    do_dirty(I, F).

do_trans_times(I, F) when I /= 0 ->
    {atomic, _} = mnesia:transaction(F),
    do_trans_times(I-1, F);
do_trans_times(_,_) -> ok.

do_dirty(I, F) when I /= 0 ->
    F(),
    do_dirty(I-1, F);
do_dirty(_,_) -> ok.

    
    
table_load([N1,N2| _ ] = Ns) ->    
    Nodes = [N1,N2],
    rpc:multicall(Ns, mnesia, lkill, []),
    ok = mnesia:delete_schema(Ns),
    ok = mnesia:create_schema(Nodes),
    rpc:multicall(Nodes, mnesia, start, []),
    TabDef = [{disc_copies,[N1]},{ram_copies,[N2]},
	      {attributes,record_info(fields,item)},{record_name,item}],
    Tabs   = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1,400)],
    
    [mnesia:create_table(Tab,TabDef) || Tab <- Tabs],

%%     InitTab = fun(Tab) ->
%% 		      mnesia:write_lock_table(Tab),
%% 		      InitRec = fun(Key) -> mnesia:write(Tab,#item{a=Key},write) end,
%% 		      lists:foreach(InitRec, lists:seq(1,100))
%% 	      end,
%%     
%%    {Time,{atomic,ok}} = timer:tc(mnesia,transaction, [fun() ->lists:foreach(InitTab, Tabs) end]),
    mnesia:dump_log(),
%%    io:format("Init took ~p msec ~n", [Time/1000]),
    rpc:call(N2, mnesia, stop, []),    timer:sleep(1000),
    mnesia:stop(), timer:sleep(500),
    %% Warmup
    ok = mnesia:start([{no_table_loaders, 1}]),    
    timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
    mnesia:dump_log(),
    rpc:call(N2, mnesia, dump_log, []),
    io:format("Initialized ~n",[]),

    mnesia:stop(), timer:sleep(1000),
    ok = mnesia:start([{no_table_loaders, 1}]),
    {T1, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
    io:format("Loading from disc with 1 loader ~p msec~n",[T1/1000]),
    mnesia:stop(), timer:sleep(1000),
    ok = mnesia:start([{no_table_loaders, 4}]),
    {T2, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
    io:format("Loading from disc with 4 loader ~p msec~n",[T2/1000]),

    %% Warmup
    rpc:call(N2, ?MODULE, remote_load, [Tabs,4]),
    io:format("Initialized ~n",[]),

    
    T3 = rpc:call(N2, ?MODULE, remote_load, [Tabs,1]),
    io:format("Loading from net with 1 loader ~p msec~n",[T3/1000]),
    
    T4 = rpc:call(N2, ?MODULE, remote_load, [Tabs,4]),
    io:format("Loading from net with 4 loader ~p msec~n",[T4/1000]),

    ok.

remote_load(Tabs,Loaders) ->
    ok = mnesia:start([{no_table_loaders, Loaders}]),
%%    io:format("~p ~n", [mnesia_controller:get_info(500)]),
    {Time, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
    timer:sleep(1000), mnesia:stop(), timer:sleep(1000),
    Time.
