-module(utTc).

-compile(inline).
-compile({inline_size, 128}).

-export([
   tc/1
   , tc/2
   , tc/3
   , ts/4
   , tm/5
   , cvrTimeUnit/3
   , test/1
]).

%% Measure the execution time (in nanoseconds) for Fun().
-spec tc(Fun :: function()) -> {Time :: integer(), Value :: term()}.
tc(F) ->
   T1 = erlang:monotonic_time(),
   Val = F(),
   T2 = erlang:monotonic_time(),
   Time = cvrTimeUnit(T2 - T1, native, nanosecond),
   {Time, Val}.

%% Measure the execution time (in nanoseconds) for Fun(Args).
-spec tc(Fun :: function(), Arguments :: [term()]) -> {Time :: integer(), Value :: term()}.
tc(F, A) ->
   T1 = erlang:monotonic_time(),
   Val = apply(F, A),
   T2 = erlang:monotonic_time(),
   Time = cvrTimeUnit(T2 - T1, native, nanosecond),
   {Time, Val}.

%% Measure the execution time (in nanoseconds) for an MFA.
-spec tc(Module :: module(), Function :: atom(), Arguments :: [term()]) -> {Time :: integer(), Value :: term()}.
tc(M, F, A) ->
   T1 = erlang:monotonic_time(),
   Val = apply(M, F, A),
   T2 = erlang:monotonic_time(),
   Time = cvrTimeUnit(T2 - T1, native, nanosecond),
   {Time, Val}.

-spec cvrTimeUnit(Time :: integer(), FromUnit :: erlang:time_unit(), ToUnit :: erlang:time_unit()) -> ConvertedTime :: integer().
cvrTimeUnit(Time, FromUnit, ToUnit) ->
   try
      FU =
         case FromUnit of
            native -> erts_internal:time_unit();
            perf_counter -> erts_internal:perf_counter_unit();
            nanosecond -> 1000000000;
            microsecond -> 1000000;
            millisecond -> 1000;
            second -> 1
         end,
      TU =
         case ToUnit of
            native -> erts_internal:time_unit();
            perf_counter -> erts_internal:perf_counter_unit();
            nanosecond -> 1000000000;
            microsecond -> 1000000;
            millisecond -> 1000;
            second -> 1
         end,
      case Time < 0 of
         true -> (TU * Time - (FU - 1)) div FU;
         _ -> TU * Time div FU
      end
   catch
      _ : _ ->
         erlang:error(badarg, [Time, FromUnit, ToUnit])
   end.

%% 单进程循环测试：LoopTimes是循环次数
%% utTc:ts(LoopTimes, Module, Function, ArgsList).
%% 多进程并发测试：SpawnProcessesCount是并发的进程数 LoopTimes是循环次数
%% utTc:tm(ProcessesCount, LoopTimes, Module, Function, ArgsList).

doTc(M, F, A) ->
   T1 = erlang:monotonic_time(),
   apply(M, F, A),
   T2 = erlang:monotonic_time(),
   cvrTimeUnit(T2 - T1, native, nanosecond).

distribution(List, Aver) ->
   distribution(List, Aver, 0, 0).
distribution([H | T], Aver, Greater, Less) ->
   case H > Aver of
      true ->
         distribution(T, Aver, Greater + 1, Less);
      false ->
         distribution(T, Aver, Greater, Less + 1)
   end;
distribution([], _Aver, Greater, Less) ->
   {Greater, Less}.

%% ===================================================================
%% test: one process test N times
%% ===================================================================
ts(LoopTime, M, F, A) ->
   {Max, Min, Sum, Aver, Greater, Less} = loopTs(LoopTime, M, F, A, LoopTime, 0, 0, 0, []),
   io:format("=====================~n"),
   io:format("execute Args:~p~n", [A]),
   io:format("execute Fun :~p~n", [F]),
   io:format("execute Mod :~p~n", [M]),
   io:format("execute LoopTime:~p~n", [LoopTime]),
   io:format("MaxTime: ~10s(ns) ~10s(s)~n", [integer_to_binary(Max), float_to_binary(Max / 1000000000, [{decimals, 6}, compact])]),
   io:format("MinTime: ~10s(ns) ~10s(s)~n", [integer_to_binary(Min), float_to_binary(Min / 1000000000, [{decimals, 6}, compact])]),
   io:format("SumTime: ~10s(ns) ~10s(s)~n", [integer_to_binary(Sum), float_to_binary(Sum / 1000000000, [{decimals, 6}, compact])]),
   io:format("AvgTime: ~10s(ns) ~10s(s)~n", [float_to_binary(Aver, [{decimals, 6}, compact]), float_to_binary(Aver / 1000000000, [{decimals, 6}, compact])]),
   io:format("Grar   : ~10s(cn) ~10s(~s)~n", [integer_to_binary(Greater), float_to_binary(Greater / LoopTime, [{decimals, 2}]), <<"%">>]),
   io:format("Less   : ~10s(cn) ~10s(~s)~n", [integer_to_binary(Less), float_to_binary(Less / LoopTime, [{decimals, 2}]), <<"%">>]),
   io:format("=====================~n").


loopTs(0, _M, _F, _A, LoopTime, Max, Min, Sum, List) ->
   Aver = Sum / LoopTime,
   {Greater, Less} = distribution(List, Aver),
   {Max, Min, Sum, Aver, Greater, Less};
loopTs(Index, M, F, A, LoopTime, Max, Min, Sum, List) ->
   Nanosecond = doTc(M, F, A),
   NewSum = Sum + Nanosecond,
   if
      Max == 0 ->
         NewMax = NewMin = Nanosecond;
      Max < Nanosecond ->
         NewMax = Nanosecond,
         NewMin = Min;
      Min > Nanosecond ->
         NewMax = Max,
         NewMin = Nanosecond;
      true ->
         NewMax = Max,
         NewMin = Min
   end,
   loopTs(Index - 1, M, F, A, LoopTime, NewMax, NewMin, NewSum, [Nanosecond | List]).


%% ===================================================================
%% Concurrency test: N processes each test one time
%% ===================================================================

tm(ProcCnt, LoopTime, M, F, A) ->
   loopSpawn(ProcCnt, M, F, A, self(), LoopTime),
   {Max, Min, Sum, Aver, Greater, Less} = collector(ProcCnt, 0, 0, 0, ProcCnt, []),
   io:format("=====================~n"),
   io:format("execute Args:~p~n", [A]),
   io:format("execute Fun :~p~n", [F]),
   io:format("execute Mod :~p~n", [M]),
   io:format("execute LoopTime:~p~n", [LoopTime]),
   io:format("execute ProcCnts:~p~n", [ProcCnt]),
   io:format("PMaxTime: ~10s(ns) ~10s(s)~n", [integer_to_binary(Max), float_to_binary(Max / 1000000000, [{decimals, 6}, compact])]),
   io:format("PMinTime: ~10s(ns) ~10s(s)~n", [integer_to_binary(Min), float_to_binary(Min / 1000000000, [{decimals, 6}, compact])]),
   io:format("PSumTime: ~10s(ns) ~10s(s)~n", [integer_to_binary(Sum), float_to_binary(Sum / 1000000000, [{decimals, 6}, compact])]),
   io:format("PAvgTime: ~10s(ns) ~10s(s)~n", [float_to_binary(Aver, [{decimals, 6}, compact]), float_to_binary(Aver / 1000000000, [{decimals, 6}, compact])]),
   io:format("FAvgTime: ~10s(ns) ~10s(s)~n", [float_to_binary(Aver / LoopTime, [{decimals, 6}, compact]), float_to_binary(Aver / LoopTime / 1000000000, [{decimals, 6}, compact])]),
   io:format("PGrar   : ~10s(cn) ~10s(~s)~n", [integer_to_binary(Greater), float_to_binary(Greater / ProcCnt, [{decimals, 2}]), <<"%">>]),
   io:format("PLess   : ~10s(cn) ~10s(~s)~n", [integer_to_binary(Less), float_to_binary(Less / ProcCnt, [{decimals, 2}]), <<"%">>]),
   io:format("=====================~n").


loopSpawn(0, _, _, _, _, _) ->
   ok;
loopSpawn(ProcCnt, M, F, A, CollectorPid, LoopTime) ->
   spawn_link(fun() -> worker(LoopTime, M, F, A, CollectorPid) end),
   loopSpawn(ProcCnt - 1, M, F, A, CollectorPid, LoopTime).

collector(0, Max, Min, Sum, ProcCnt, List) ->
   Aver = Sum / ProcCnt,
   {Greater, Less} = distribution(List, Aver),
   {Max, Min, Sum, Aver, Greater, Less};
collector(Index, Max, Min, Sum, ProcCnt, List) ->
   receive
      {result, Nanosecond} ->
         NewSum = Sum + Nanosecond,
         if
            Max == 0 ->
               NewMax = NewMin = Nanosecond;
            Max < Nanosecond ->
               NewMax = Nanosecond,
               NewMin = Min;
            Min > Nanosecond ->
               NewMax = Max,
               NewMin = Nanosecond;
            true ->
               NewMax = Max,
               NewMin = Min
         end,
         collector(Index - 1, NewMax, NewMin, NewSum, ProcCnt, [Nanosecond | List])
   after 1800000 ->
      io:format("execute time out~n"),
      ok
   end.

worker(LoopTime, M, F, A, CollectorPid) ->
   SumTime = loopTm(LoopTime, M, F, A, 0),
   CollectorPid ! {result, SumTime}.

loopTm(0, _, _, _, SumTime) ->
   SumTime;
loopTm(LoopTime, M, F, A, SumTime) ->
   Microsecond = doTc(M, F, A),
   loopTm(LoopTime - 1, M, F, A, SumTime + Microsecond).

test(N) ->
   M1 = erlang:monotonic_time(),
   timer:sleep(N),
   M2 = erlang:monotonic_time(),
   Time = cvrTimeUnit(M2 - M1, native, nanosecond),
   io:format("IMY******************111 ~p~n", [Time]),

   S1 = erlang:system_time(nanosecond),
   timer:sleep(N),
   S2 = erlang:system_time(nanosecond),
   io:format("IMY******************222 ~p~n", [S2 - S1]).



