%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2002-2016. 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%
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Copyright (c) 2001 by Erik Johansson.  All Rights Reserved 
%% Time-stamp: <2008-04-20 14:53:36 richard>
%% ====================================================================
%%  Module   :	hipe_timer
%%  Purpose  :  
%%  Notes    : 
%%  History  :	* 2001-03-15 Erik Johansson (happi@it.uu.se): Created.
%% ====================================================================
%%  Exports  :
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-module(hipe_timer).

-export([tr/1, t/1, timer/1, time/1, empty_time/0]).
-export([advanced/2]).

t(F) ->
  {EWT,ERT} = empty_time(),
  {WT,RT} = time(F),
  {WT-EWT,(RT-ERT)/1000}.

tr(F) ->
  {EWT,ERT} = empty_time(),
  {R,{WT,RT}} = timer(F),
  {R,{WT-EWT,(RT-ERT)/1000}}.

empty_time() ->
  WTA = erlang:monotonic_time(),
  {A,_} = erlang:statistics(runtime),
  WTB = erlang:monotonic_time(),
  {B,_} = erlang:statistics(runtime),
  {(WTB-WTA)/erlang:convert_time_unit(1, seconds, native),B-A}.

time(F) -> 
  WTA = erlang:monotonic_time(),
  {A,_} = erlang:statistics(runtime),
  F(),
  WTB = erlang:monotonic_time(),
  {B,_} = erlang:statistics(runtime),
  {(WTB-WTA)/erlang:convert_time_unit(1, seconds, native),B-A}.

timer(F) -> 
  WTA = erlang:monotonic_time(),
  {A,_} = erlang:statistics(runtime),
  R = F(),
  WTB = erlang:monotonic_time(),
  {B,_} = erlang:statistics(runtime),
  {R,{(WTB-WTA)/erlang:convert_time_unit(1, seconds, native),B-A}}.

advanced(_Fun, I) when I < 2 -> false;
advanced(Fun, Iterations) ->
  R = Fun(),
  Measurements = [t(Fun) || _ <- lists:seq(1, Iterations)],
  {Wallclock, RunTime} = split(Measurements),
  WMin = lists:min(Wallclock),
  RMin = lists:min(RunTime),
  WMax = lists:max(Wallclock),
  RMax = lists:max(RunTime),
  WMean = mean(Wallclock),
  RMean = mean(RunTime),
  WMedian = median(Wallclock),
  RMedian = median(RunTime),
  WVariance = variance(Wallclock),
  RVariance = variance(RunTime),
  WStddev = stddev(Wallclock),
  RStddev = stddev(RunTime),
  WVarCoff = 100 * WStddev / WMean,
  RVarCoff = 100 * RStddev / RMean,
  WSum = lists:sum(Wallclock),
  RSum = lists:sum(RunTime),
  [{wallclock,[{min, WMin},
	       {max, WMax},
	       {mean, WMean},
	       {median, WMedian},
	       {variance, WVariance},
	       {stdev, WStddev},
	       {varcoff, WVarCoff},
	       {sum, WSum},
	       {values, Wallclock}]},
   {runtime,[{min, RMin},
	     {max, RMax},
	     {mean, RMean},
	     {median, RMedian},
	     {variance, RVariance},
	     {stdev, RStddev},
	     {varcoff, RVarCoff},
	     {sum, RSum},
	     {values, RunTime}]},
   {iterations, Iterations},
   {result, R}].

split(M) -> 
  split(M, [], []).

split([{W,R}|More], AccW, AccR) ->
  split(More, [W|AccW], [R|AccR]);
split([], AccW, AccR) ->
  {AccW, AccR}.

mean(L) ->
  mean(L, 0, 0).

mean([V|Vs], No, Sum) ->
  mean(Vs, No+1, Sum+V);
mean([], No, Sum) when No > 0 ->
  Sum/No;
mean([], _No, _Sum) ->
  exit(empty_list).
  
median(L) ->
  S = length(L),
  SL = lists:sort(L),
  case even(S) of
    true ->
      (lists:nth((S div 2), SL) + lists:nth((S div 2) + 1, SL)) / 2;
    false ->
      lists:nth((S div 2), SL)
  end.

even(S) ->
  (S band 1) =:= 0.

%% diffs(L, V) ->
%%   [X - V || X <- L].

square_diffs(L, V) ->
  [(X - V) * (X - V) || X <- L].

variance(L) ->
  Mean = mean(L),
  N = length(L),
  if N > 1 ->
      lists:sum(square_diffs(L,Mean)) / (N-1);
     true -> exit('too few values')
  end.

stddev(L) ->
  math:sqrt(variance(L)).
