%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%

%%
-module(mnesia_cost).
-export([go/0, go/1]).

%% 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:monotonic_time(),
    statistics(runtime),
    do_times(How, ?TIMES, F),
    {_, RunTime} = statistics(runtime),
    T2 = erlang:monotonic_time(),
    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) ->
    erlang:convert_time_unit(After-Before, native, milli_seconds).

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.
