%%%---------------------------------------------------------------------- %%% File : top.erl %%% Author : Klacke %%% Purpose : %%% Created : 13 Apr 2000 by Klacke %%%---------------------------------------------------------------------- -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}].