%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2009. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% Purpose: Profile a system in order to figure out where the %% time goes. %% -module(eprof). -behaviour(gen_server). -export([start/0, stop/0, dump/0, total_analyse/0, start_profiling/1, profile/2, profile/4, profile/1, stop_profiling/0, analyse/0, log/1]). %% Internal exports -export([init/1, call/4, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include_lib("stdlib/include/qlc.hrl"). -import(lists, [flatten/1,reverse/1,keysort/2]). -record(state, {table = notable, proc = noproc, profiling = false, pfunc = undefined, pop = running, ptime = 0, overhead = 0, rootset = []}). %%%%%%%%%%%%%% start() -> gen_server:start({local, eprof}, eprof, [], []). stop() -> gen_server:call(eprof, stop, infinity). profile(Pids, Fun) -> start(), gen_server:call(eprof, {profile,Pids,erlang,apply,[Fun,[]]},infinity). profile(Pids, M, F, A) -> start(), gen_server:call(eprof, {profile,Pids,M,F,A},infinity). dump() -> gen_server:call(eprof, dump, infinity). analyse() -> gen_server:call(eprof, analyse, infinity). log(File) -> gen_server:call(eprof, {logfile, File}, infinity). total_analyse() -> gen_server:call(eprof, total_analyse, infinity). start_profiling(Rootset) -> start(), gen_server:call(eprof, {profile, Rootset}, infinity). stop_profiling() -> gen_server:call(eprof, stop_profiling, infinity). profile(Rs) -> start_profiling(Rs). %%%%%%%%%%%%%%%% init(_) -> process_flag(trap_exit, true), process_flag(priority, max), put(three_one, {3,1}), %To avoid building garbage. {ok, #state{}}. subtr({X1,Y1,Z1}, {X1,Y1,Z2}) -> Z1 - Z2; subtr({X1,Y1,Z1}, {X2,Y2,Z2}) -> (((X1-X2) * 1000000) + Y1 - Y2) * 1000000 + Z1 - Z2. update_call_statistics(Tab, Key, Time) -> try ets:update_counter(Tab, Key, Time) of NewTime when is_integer(NewTime) -> ets:update_counter(Tab, Key, get(three_one)) catch error:badarg -> ets:insert(Tab, {Key,Time,1}) end. update_other_statistics(Tab, Key, Time) -> try ets:update_counter(Tab, Key, Time) catch error:badarg -> ets:insert(Tab, {Key,Time,0}) end. do_messages({trace_ts,From,Op,Mfa,Time}, Tab, undefined,_PrevOp0,_PrevTime0) -> PrevFunc = [From|Mfa], receive {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time) after 0 -> {PrevFunc,Op,Time} end; do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, call, PrevTime0) -> update_call_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)), PrevFunc = case Op of exit -> undefined; out -> undefined; _ -> [From|Mfa] end, receive {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time) after 0 -> {PrevFunc,Op,Time} end; do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, _PrevOp0, PrevTime0) -> update_other_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)), PrevFunc = case Op of exit -> undefined; out -> undefined; _ -> [From|Mfa] end, receive {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time) after 0 -> {PrevFunc,Op,Time} end. %%%%%%%%%%%%%%%%%% handle_cast(_Req, S) -> {noreply, S}. terminate(_Reason,_S) -> call_trace_for_all(false), normal. %%%%%%%%%%%%%%%%%% handle_call({logfile, F}, _FromTag, Status) -> case file:open(F, [write]) of {ok, Fd} -> case get(fd) of undefined -> ok; FdOld -> file:close(FdOld) end, put(fd, Fd), {reply, ok, Status}; {error, _} -> {reply, error, Status} end; handle_call({profile, Rootset}, {From, _Tag}, S) -> link(From), maybe_delete(S#state.table), io:format("eprof: Starting profiling ..... ~n",[]), ptrac(S#state.rootset, false, all()), flush_receive(), Tab = ets:new(eprof, [set, public]), case ptrac(Rootset, true, all()) of false -> {reply, error, #state{}}; true -> uni_schedule(), call_trace_for_all(true), erase(replyto), {reply, profiling, #state{table = Tab, proc = From, profiling = true, rootset = Rootset}} end; handle_call(stop_profiling, _FromTag, S) when S#state.profiling -> ptrac(S#state.rootset, false, all()), call_trace_for_all(false), multi_schedule(), io:format("eprof: Stop profiling~n",[]), ets:delete(S#state.table, nofunc), {reply, profiling_stopped, S#state{profiling = false}}; handle_call(stop_profiling, _FromTag, S) -> {reply, profiling_already_stopped, S}; handle_call({profile, Rootset, M, F, A}, FromTag, S) -> io:format("eprof: Starting profiling..... ~n", []), maybe_delete(S#state.table), ptrac(S#state.rootset, false, all()), flush_receive(), put(replyto, FromTag), Tab = ets:new(eprof, [set, public]), P = spawn_link(eprof, call, [self(), M, F, A]), case ptrac([P|Rootset], true, all()) of true -> uni_schedule(), call_trace_for_all(true), P ! {self(),go}, {noreply, #state{table = Tab, profiling = true, rootset = [P|Rootset]}}; false -> exit(P, kill), erase(replyto), {reply, error, #state{}} end; handle_call(dump, _FromTag, S) -> {reply, dump(S#state.table), S}; handle_call(analyse, _FromTag, S) -> {reply, analyse(S), S}; handle_call(total_analyse, _FromTag, S) -> {reply, total_analyse(S), S}; handle_call(stop, _FromTag, S) -> multi_schedule(), {stop, normal, stopped, S}. %%%%%%%%%%%%%%%%%%% handle_info({trace_ts,_From,_Op,_Func,_Time}=M, S0) when S0#state.profiling -> Start = erlang:now(), #state{table=Tab,pop=PrevOp0,ptime=PrevTime0,pfunc=PrevFunc0, overhead=Overhead0} = S0, {PrevFunc,PrevOp,PrevTime} = do_messages(M, Tab, PrevFunc0, PrevOp0, PrevTime0), Overhead = Overhead0 + subtr(erlang:now(), Start), S = S0#state{overhead=Overhead,pfunc=PrevFunc,pop=PrevOp,ptime=PrevTime}, {noreply,S}; handle_info({trace_ts, From, _, _, _}, S) when not S#state.profiling -> ptrac([From], false, all()), {noreply, S}; handle_info({_P, {answer, A}}, S) -> ptrac(S#state.rootset, false, all()), io:format("eprof: Stop profiling~n",[]), {From,_Tag} = get(replyto), catch unlink(From), ets:delete(S#state.table, nofunc), gen_server:reply(erase(replyto), {ok, A}), multi_schedule(), {noreply, S#state{profiling = false, rootset = []}}; handle_info({'EXIT', P, Reason}, #state{profiling=true,proc=P,table=T,rootset=RootSet}) -> maybe_delete(T), ptrac(RootSet, false, all()), multi_schedule(), io:format("eprof: Profiling failed\n",[]), case erase(replyto) of undefined -> {noreply, #state{}}; FromTag -> gen_server:reply(FromTag, {error, Reason}), {noreply, #state{}} end; handle_info({'EXIT',_P,_Reason}, S) -> {noreply, S}. uni_schedule() -> erlang:system_flag(multi_scheduling, block). multi_schedule() -> erlang:system_flag(multi_scheduling, unblock). %%%%%%%%%%%%%%%%%% call(Top, M, F, A) -> receive {Top,go} -> Top ! {self(), {answer, apply(M,F,A)}} end. call_trace_for_all(Flag) -> erlang:trace_pattern(on_load, Flag, [local]), erlang:trace_pattern({'_','_','_'}, Flag, [local]). ptrac([P|T], How, Flags) when is_pid(P) -> case dotrace(P, How, Flags) of true -> ptrac(T, How, Flags); false when How -> false; false -> ptrac(T, How, Flags) end; ptrac([P|T], How, Flags) when is_atom(P) -> case whereis(P) of undefined when How -> false; undefined when not How -> ptrac(T, How, Flags); Pid -> ptrac([Pid|T], How, Flags) end; ptrac([H|_],_How,_Flags) -> io:format("** eprof bad process ~w~n",[H]), false; ptrac([],_,_) -> true. dotrace(P, How, What) -> case (catch erlang:trace(P, How, What)) of 1 -> true; _Other when not How -> true; _Other -> io:format("** eprof: bad process: ~p,~p,~p~n", [P,How,What]), false end. all() -> [call,arity,return_to,running,timestamp,set_on_spawn]. total_analyse(#state{table=notable}) -> nothing_to_analyse; total_analyse(S) -> #state{table = T, overhead = Overhead} = S, QH = qlc:q([{{From,Mfa},Time,Count} || {[From|Mfa],Time,Count} <- ets:table(T)]), Pcalls = reverse(keysort(2, replicas(qlc:eval(QH)))), Time = collect_times(Pcalls), format("FUNCTION~44s TIME ~n", ["CALLS"]), printit(Pcalls, Time), format("\nTotal time: ~.2f\n", [Time / 1000000]), format("Measurement overhead: ~.2f\n", [Overhead / 1000000]). analyse(#state{table=notable}) -> nothing_to_analyse; analyse(S) -> #state{table = T, overhead = Overhead} = S, Pids = ordsets:from_list(flatten(ets:match(T, {['$1'|'_'],'_', '_'}))), Times = sum(ets:match(T, {'_','$1', '_'})), format("FUNCTION~44s TIME ~n", ["CALLS"]), do_pids(Pids, T, 0, Times), format("\nTotal time: ~.2f\n", [Times / 1000000]), format("Measurement overhead: ~.2f\n", [Overhead / 1000000]). do_pids([Pid|Tail], T, AckTime, Total) -> Pcalls = reverse(keysort(2, to_tups(ets:match(T, {[Pid|'$1'], '$2','$3'})))), Time = collect_times(Pcalls), PercentTotal = 100 * (divide(Time, Total)), format("~n****** Process ~w -- ~s % of profiled time *** ~n", [Pid, fpf(PercentTotal)]), printit(Pcalls, Time), do_pids(Tail, T, AckTime + Time, Total); do_pids([], _, _, _) -> ok. printit([],_) -> ok; printit([{{Mod,Fun,Arity}, Time, Calls} |Tail], ProcTime) -> format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls), fpf(100*(divide(Time,ProcTime)))]), printit(Tail, ProcTime); printit([{{_,{Mod,Fun,Arity}}, Time, Calls} |Tail], ProcTime) -> format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls), fpf(100*(divide(Time,ProcTime)))]), printit(Tail, ProcTime); printit([_|T], Time) -> printit(T, Time). ff(Mod,Fun,Arity) -> pad(flatten(io_lib:format("~w:~w/~w", [Mod,Fun, Arity])),45). pad(Str, Len) -> Strlen = length(Str), if Strlen > Len -> strip_tail(Str, 45); true -> lists:append(Str, mklist(Len-Strlen)) end. strip_tail([_|_], 0) ->[]; strip_tail([H|T], I) -> [H|strip_tail(T, I-1)]; strip_tail([],_I) -> []. fpf(F) -> strip_tail(flatten(io_lib:format("~w", [round(F)])), 5). fint(Int) -> pad(flatten(io_lib:format("~w",[Int])), 10). mklist(0) -> []; mklist(I) -> [$ |mklist(I-1)]. to_tups(L) -> lists:map(fun(List) -> erlang:list_to_tuple(List) end, L). divide(X,Y) -> X / Y. collect_times([]) -> 0; collect_times([Tup|Tail]) -> element(2, Tup) + collect_times(Tail). dump(T) -> L = ets:tab2list(T), format(L). format([H|T]) -> format("~p~n", [H]), format(T); format([]) -> ok. format(F, A) -> io:format(F,A), case get(fd) of undefined -> ok; Fd -> io:format(Fd, F,A) end. maybe_delete(T) -> catch ets:delete(T). sum([[H]|T]) -> H + sum(T); sum([]) -> 0. replicas(L) -> replicas(L, []). replicas([{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Tail], Result) -> case search({Mod,Fun,Arity},Result) of false -> replicas(Tail, [{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Result]); {Ack2, Calls2} -> Result2 = del({Mod,Fun,Arity}, Result), replicas(Tail, [{{Pid, {Mod,Fun,Arity}}, Ack+Ack2,Calls+Calls2} |Result2]) end; replicas([_|T], Ack) -> %% Whimpy replicas(T, Ack); replicas([], Res) -> Res. search(Key, [{{_,Key}, Ack, Calls}|_]) -> {Ack, Calls}; search(Key, [_|T]) -> search(Key, T); search(_Key,[]) -> false. del(Key, [{{_,Key},_Ack,_Calls}|T]) -> T; del(Key, [H | Tail]) -> [H|del(Key, Tail)]; del(_Key,[]) -> []. flush_receive() -> receive {trace_ts, From, _, _, _} when is_pid(From) -> ptrac([From], false, all()), flush_receive(); _ -> flush_receive() after 0 -> ok end. code_change(_OldVsn, State, _Extra) -> {ok,State}.