%%%----------------------------------------------------------------------
%%% File    : top.erl
%%% Author  : Klacke <klacke@bluetail.com>
%%% Purpose : 
%%% Created : 13 Apr 2000 by Klacke <klacke@bluetail.com>
%%%----------------------------------------------------------------------

-module(top).
-author('klacke@bluetail.com').



%% This module implements a bunch of top like funcs:
%% we have:

%% top:rt()                 percentage of realtime used by the different procs
%% top:rt(Num, Interval)    display Num procs, every Interval secs

%% top:reds()               which procs make up the most reductions
%% top:reds(Num, Interval)  display Num items every Interval secs

%% top:mem()                which processes use most memory
%% top:mem(Num, Interval)   display Num items every Interval secs

%% defaults are 10 procs, every 5 secs



-export([rt/0,    
	 rt/2]).

-export([reds/0, 
	 reds/2]).

-export([mem/0,
	 mem/2]).


-record(info, {
	  percent,
	  reds,
	  diff,
	  pid,
	  last_reds,
	  mem,
	  cc,
	  ifun,
	  name}).


%% debug only
-export([load/1]).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%
%%% realtime top counter
%%%
%%%

rt() ->
    rt(10, %% Number of procs to display, 
       5   %% and secs between update intervals
      ).


rt(Num, IntervalSecs0) ->
    IntervalSecs = if
	IntervalSecs0 < 3 -> 3;
	true -> IntervalSecs0
    end,
    spawn_link(fun() -> topper(Num, IntervalSecs, []) end).
			   
    

topper(Num, IntervalSecs, Prev) ->
    T = ets:new(topper, [set]),
    erlang:trace(all, true, [running, timestamp]),
    Time = timer:seconds(IntervalSecs),
    timer:send_after(Time, timeout),
    put(before, now()),
    topper(Num, IntervalSecs, T, Prev).
    


topper(Num, IntervalSecs, Tab, Prev) ->
    receive
	timeout ->
	    erlang:trace(all, false, [running, timestamp]),
	    flush(Tab),
	    Take = take(Num, ets:tab2list(Tab), [], 0),
	    TimeTot = tdiff(erase(before), erase(aft)),
	    Prev2 = fmt(TimeTot, Take, Prev),
	    ets:delete(Tab),
	    topper(Num, IntervalSecs, Prev2);
	X ->
	    handle_msg(X, Tab),
	    topper(Num, IntervalSecs, Tab, Prev)
    end.



handle_msg(Msg, Tab) ->
    case Msg of
	{trace_ts, Pid, in, _, Time} ->
	    put(aft, Time),
	    case ets:lookup(Tab, Pid) of
		[{Pid, _, Ack}] ->
		    ets:insert(Tab, {Pid, Time, Ack});
		[] ->
		    ets:insert(Tab, {Pid, Time, 0})
	    end;
	{trace_ts, Pid, out, _, Time} ->
	    put(aft, Time),
	    case ets:lookup(Tab, Pid) of
		[{Pid, Before, Ack}] ->
		    Diff = tdiff(Before, Time),
		    ets:insert(Tab, {Pid, undefined, Ack + Diff});
		[] ->
		    ets:insert(Tab, {Pid, undefined, 0})
	    end;
	_ ->
	    ignore
    end.
	



flush(Tab) ->
    receive X ->
	    handle_msg(X, Tab),
	    flush(Tab)
    after 0 ->
	    ok
    end.


fmt(TotalMicros, Ps, Prev) ->
    L = lists:map(
	  fun({Pid, Ack}) ->
		  {Pid, mkinfo(TotalMicros, Ack, Pid, Prev)}
	  end, Ps),


    io:format("---------------------------------------~n",[]),
    hform(['name', 'icall', 'cc', '%' ,'redsdiff', 'mem']),
    lists:foreach(fun({_, I}) ->
			  hform([I#info.name,
				 I#info.ifun,
				 I#info.cc,
				 I#info.percent,
				 I#info.diff,
				 I#info.mem])
		  end, L),
    L.



mkinfo(TotalMicros, Ack, Pid, Prev) ->
    F = (Ack / TotalMicros) * 100,
    Fstr = if
	       F > 100 -> %% ugh how ??
		   lists:flatten(
		      io_lib:format("~.4g",[F]));
	       F > 0.1 , F < 100->
		   lists:flatten(
		      io_lib:format("~.3g",[F]));
	       F > 0.05 ->
		   "0.05";
	       F > 0.01 ->
		   "0.01";
	       true ->
		   "0.00"
	   end,
    Reds = case process_info(Pid, reductions) of
	       undefined ->
		   0;
	       {_, RR} ->
		   RR
	   end,
    Diff = case lists:keysearch(Pid, 1, Prev) of
	       {value, {_Pid, I}} when Reds /= 0 ->
		   Reds - I#info.last_reds;
	       _ ->
		   0
	   end,

    #info{diff = Diff,
	  percent = Fstr,
	  pid = Pid,
	  last_reds = Reds,
	  mem = pinfo(Pid, memory),
	  cc = pinfo(Pid, current_function),
	  ifun = case pinfo(Pid, initial_call) of
		     {proc_lib, init_p, 5} ->
			 proc_lib:translate_initial_call(Pid);
		     Pinfo ->
			 Pinfo
		 end,
	  name = case process_info(Pid, registered_name) of
		     {_, Name} ->
			 Name;
		     _ ->
			 Pid
		 end}.

pinfo(Pid, Item) ->
    case process_info(Pid, Item) of
	{_Item, X} ->
	    X;
	undefined ->
	    '-'
    end.

hform(L0) ->
    L = lists:map(fun(X) -> io_lib:format("~p", [X]) end, L0),
    ok = io:format(" ~-15s ~-30s ~-30s ~-8s ~-8s ~-7s \n", L).





%% Ack is sorted with least first
take(Num, [], Ack, _) ->
    lists:reverse(Ack);
%% We already have as many as we shall have
take(Num, [{Pid, _, Ack}|Tail], Res, Num) ->
    {Pid2, Min} = hd(Res),
    if Ack > Min ->
	    take(Num, Tail, into(Pid, Ack, tl(Res)), Num);
       true ->
	    take(Num, Tail, Res, Num)
    end;
take(Num, [{Pid, _, Ack} |Tail], Res, Num2) ->
    take(Num, Tail, into(Pid, Ack, Res), Num2+1).
		       

into(Pid, Ack, [{P2, A2} | Tail]) when Ack < A2 ->
    [{Pid, Ack}, {P2, A2} | Tail];
into(Pid, Ack, []) ->
    [{Pid, Ack}];
into(Pid, Ack, [H|T]) ->
    [H|into(Pid, Ack, T)].


%% return micro seconds
tdiff(Before, After) ->
    Elapsed = (element(1,After)*1000000000000 +
                element(2,After)*1000000 + 
                element(3,After)) -
        (element(1,Before)*1000000000000 +
         element(2,Before)*1000000 + element(3,Before)).



%% just for testing
load(I) ->
    lists:foreach(fun(Id) ->
			  spawn_link(fun() -> loader(Id) end)
		  end, lists:seq(1, I)).
loader(I) ->
    lists:seq(1, 777),
    %%timer:sleep(I),
    loader(I).





%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%
%%%% redscounter top
%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



reds() ->
    reds(10, 5).
reds(Num, Int) ->  
    spawn_link(fun() -> redloop0(Num, Int) end).


redloop0(Num, Int) ->
    redloop(Num, Int, get_info([])).


redloop(Take, Int, Prev) ->
    receive
	{'EXIT', _,_} ->
	    exit(normal)
    after Int * 1000 ->
	    P2 = get_info(Prev),
	    rformat(ntake(Take, P2)),
	    redloop(Take, Int, P2)
    end.
	    
    
get_info(Prev) ->
    P = processes(),
    pick(P, Prev, []).



pick([H|T], Prev, Ack) ->
    case lists:keysearch(H, #info.pid, Prev) of
	{value, I} ->
	    I2 = mkinfo(H),
	    Lreds = I#info.reds,
	    I3 = I2#info{diff = I2#info.reds - Lreds,
			 last_reds = Lreds},
	    pick(T, Prev, rinto(I3, Ack));
	false ->  %% New process
	    I2 = mkinfo(H),
	    I3 = I2#info{diff = I2#info.reds},
	    pick(T, Prev,  rinto(I3, Ack))
    end;

pick([], _, Ack) ->
    Ack.


rinto(I, [H|T]) ->
    if
	I#info.diff > H#info.diff ->  %% take it
	    [I, H | T];
	true ->
	    [H | rinto(I, T)]
    end;
rinto(I, []) ->
    [I].



ntake(0, _) ->
    [];
ntake(I, [H|T]) when H#info.diff /= 0 ->
    [H|ntake(I-1, T)];
ntake(_,_) ->
    [].

rformat(L) ->
    io:format("---------------------------------------~n",[]),
    rhform(['name', 'icall', 'cc', 'reds' ,'diff', 'mem']),
    lists:foreach(fun(I) ->
			 rhform([I#info.name,
				 I#info.ifun,
				 I#info.cc,
				 I#info.reds,
				 I#info.diff,
				 I#info.mem])
		  end, L).

%% Needs 6 args in list
rhform(L0) ->
    L = lists:map(fun(X) -> io_lib:format("~p", [X]) end, L0),
    ok = io:format(" ~-15s ~-30s ~-30s ~-8s ~-7s ~-7s \n", L).




mkinfo(Pid) ->
    #info{diff = 0,
	  reds = pinfo(Pid, reductions),
	  pid = Pid,
	  last_reds = 0,
	  mem = pinfo(Pid, memory),
	  cc = pinfo(Pid, current_function),
	  ifun =  case pinfo(Pid, initial_call) of
		     {proc_lib, init_p, 5} ->
			 proc_lib:translate_initial_call(Pid);
		     Pinfo ->
			 Pinfo
		 end,            
	  name = case process_info(Pid, registered_name) of
		     {_, Name} ->
			 Name;
		     _ ->
			 Pid
		 end}.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%
%%%%%% memory topper
%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-record(memi, {
	  total,
	  newheap_size ='-',
	  stack_size ='-',
	  newheap_used='-',
	  oldheap_size='-',
	  oldheap_used='-',
	  message_buffer_size='-',
	  gengcs = '-'
	 }).

mem() ->
    mem(10, 5).
mem(Num, Interval) ->
    spawn_link(fun() -> mem_loop0(Num, Interval) end).

mem_loop0(Num, Interval) ->
    Ps = processes(),
    L = mtake(Ps, Num, []),
    Is = lists:map(fun({Pid, _}) -> {mem_info(Pid), mkinfo(Pid)} end, L),
    io:format("---------------------------------------~n",[]),
    mform(['name', 'icall', total, mbuf, stack, nhsz, nhuse, 
	   ohsz, ohused, gengcs]),
    lists:foreach(
      fun({Mi, I}) ->  
	      mform([I#info.name,
		     I#info.ifun,
		     Mi#memi.total,
		     Mi#memi.message_buffer_size,
		     Mi#memi.stack_size,
		     Mi#memi.newheap_size,
		     Mi#memi.newheap_used,
		     Mi#memi.oldheap_size,
		     Mi#memi.oldheap_used,
		     Mi#memi.gengcs
		    ])
      end, Is),
    timer:sleep(timer:seconds(Interval)),
    mem_loop0(Num, Interval).


mform(L0) ->
    L = lists:map(fun(X) -> io_lib:format("~p", [X]) end, L0),
    ok = io:format(" ~-15s ~-30s ~-7s ~-7s ~-7s ~-7s "
		   "~-7s ~-7s ~-7s ~-4s\n", L).

 
				 
mem_info(Pid) ->
    case catch process_info(Pid, heap_info) of
	{'EXIT', _} ->
	    #memi{total = case process_info(Pid, memory) of
			      undefined ->
				  undefined;
			      {memory, Mem} ->
				  Mem div 4
			  end};
	{heap_info, [{generational, GenGcs},
		     {message_buffer_size, Mbsz},
		     {oldheap_used, Ohu},
		     {oldheap_size, Ohsz},
		     {newheap_used, Nhu},
		     {stack_size, Stcksz},
		     {newheap_size, Nhsz}]} ->
	    #memi {total = case process_info(Pid, memory) of
			      undefined ->
				  undefined;
			      {memory, Mem} ->
				  Mem div 4
			  end,
		   gengcs = GenGcs,
		   message_buffer_size = Mbsz,
		   oldheap_used = Ohu,
		   oldheap_size = Ohsz,
		   newheap_size = Nhsz,
		   stack_size = Stcksz,
		   newheap_used = Nhu};
	undefined ->
	    #memi{total = undefined}
	    
    end.
	

mtake([Pid|Pids], Num, Ack) ->
    case process_info(Pid, memory) of
	{memory, Mem} ->
	    mtake(Pids, Num, minto(Pid, Mem, Num, Ack));
	_ ->
	    mtake(Pids, Num, Ack)
    end;
mtake([], _, Ack) ->
    Ack.


minto(Pid, Mem, 0, List) ->
    [];
minto(Pid, Mem, Num, [{Pid2, Mem2}|T]) ->
    if
	Mem > Mem2 ->
	    [{Pid, Mem} , {Pid2, Mem2} |T];
	true ->
	    [{Pid2, Mem2} | minto(Pid, Mem, Num-1, T)]
    end;
minto(Pid, Mem,_,[]) ->
    [{Pid, Mem}].

 



		       











