%%%---------------------------------------------------------------------- %%% File : shrerk_scan.erl %%% Author : Mats Cronqvist %%% Purpose : %%% Created : 27 Feb 2006 by Mats Cronqvist %%%---------------------------------------------------------------------- -module(sherk_scan). -author('etxmacr@avc386'). -include_lib("kernel/include/file.hrl"). -export([go/5]). -include("log.hrl"). -record(state, {seq=0, hits=0, cbs, pattern, out, min, max, eof = false}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% go(FileName, Patt, CBs, Min, Max) %%% scans a trace file for messages. filters on sequence number and %%% a pattern. if the sequence number is between Min and Max, and the %%% message matches Patt, the massage is passed to the funs in CBs. %%% %%% Filename - string() %%% Patt - list(term(P))|term(P) - all terms P must match the message. %%% funs, ports, refs and pids ar turned into atoms. %%% CBs - list(function()) - CB|list(CB) %%% CB - fun(F)|atom(M)|{fun(F),term(Init)}|{atom(M),term(Init)} %%% M - if M is '', the default callback is called. it will write the %%% trace message to a file (if Init is a string) or the screen. %%% otherwise, M:go(Msg,Seq,State) is called %%% Msg - the trace message %%% Seq - the trace message sequence number %%% State - the callback functions state %%% Init - the initial value of State. defaults to 'initial' %%% Min - integer() - min sequence number %%% Max - integer() - max sequence number %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% go(FileName, Patt, CBs, '', Max) -> go(FileName, Patt, CBs, 0, Max); go(FileName, _Patt, raw, Min, Max) -> {ok, FD} = file:open(FileName, [read, raw, binary,compressed]), State = #state{min=Min,max=Max}, file_action(FD, State, fun raw/2), file:close(FD); go(FileName, Patt, CBs, Min, Max) -> sherk_ets:new(?MODULE), {ok, FD} = file:open(FileName, [read,raw,binary,compressed]), State = #state{pattern=Patt,cbs=cbs(CBs),min=Min,max=Max}, St = file_action(FD, State, fun do/2), file:close(FD), {hits, St#state.hits}. -define(CHUNKSIZE, 100000). file_action(FD, Stat,Fun) -> file_action(make_chunk(FD, <<>>), FD, Stat, Fun). file_action(_, _FD, #state{eof = true} = Stat, Fun) -> Fun(end_of_trace, Stat); file_action(eof, _FD, Stat, Fun) -> Fun(end_of_trace, Stat); file_action({Term, Rest}, FD, Stat, Fun) -> file_action(make_chunk(FD, Rest), FD, Fun(Term, Stat), Fun). make_chunk(_FD, eof) -> eof; make_chunk(FD, <<0, Size:32, Tal/binary>> = Bin) -> case Tal of <> -> {binary_to_term(Term), Tail}; _ -> make_chunk(FD, get_more_bytes(FD, Bin)) end; make_chunk(FD, B) when size(B) < 5 -> make_chunk(FD, get_more_bytes(FD, B)). get_more_bytes(FD, Rest) -> case file:read(FD, ?CHUNKSIZE) of {ok, Bin} -> <>; _ -> eof end. %%% CBs - CB|list(CB) %%% CB - fun(F)|atom(M)|{fun(F),term(Init)}|{atom(M),term(Init)} cbs([]) -> []; cbs([CB|T]) -> [to_cb(CB)|cbs(T)]; cbs(Term) -> cbs([Term]). to_cb('') -> to_cb({'',standard_io}); to_cb(Mod) when is_atom(Mod) -> to_cb({Mod,initial}); to_cb(Fun) when is_function(Fun) -> to_cb({Fun,initial}); to_cb({'',Out}) -> {fun write_msg/3,Out}; to_cb({Mod,Init}) when is_atom(Mod) -> is_cb(Mod),{{Mod,go},Init}; to_cb({Fun,Init}) when is_function(Fun) -> is_cb(Fun),{Fun,Init}. is_cb(M) when is_atom(M) -> true = lists:member({go,3},M:module_info(exports)); is_cb(F) when is_function(F) -> {arity,3} = erlang:fun_info(F,arity). do(end_of_trace, State) -> do_do(end_of_trace, State); do(_Mess, #state{max = Max, seq = Seq} = Stat) when Max < Seq -> Stat#state{eof=true}; do(Mess, #state{min = Min, seq = Seq} = Stat) when Seq < Min -> case mass(Mess) of [] -> Stat; _ -> Stat#state{seq = Seq+1} end; do(Mess, Stat) -> case mass(Mess) of [] -> Stat; Ms -> do_do(Ms, Stat) end. do_do(end_of_trace = Ms, #state{cbs=CBs, seq=Seq} = State) -> State#state{cbs=do_safe_cbs(CBs, Ms, Seq, [])}; do_do(Mess, #state{pattern=Patt, cbs=CBs, seq=Seq} = State) -> case grep(Patt, Mess) of false -> State#state{seq = Seq+1}; true -> State#state{seq = Seq+1, hits = State#state.hits+1, cbs=do_safe_cbs(CBs, Mess, Seq, [])} end. do_safe_cbs([], _, _, O) -> lists:reverse(O); do_safe_cbs([CB|CBs], Msg, Seq, O) -> do_safe_cbs(CBs, Msg, Seq, [safe_cb(CB,Msg,Seq)|O]). safe_cb({{M,F},State},Msg,Seq) -> {{M,F},M:F(Msg,Seq,State)}; safe_cb({Fun,State},Msg,Seq) -> {Fun,Fun(Msg,Seq,State)}. write_msg(Msg,Seq,F) when is_list(F) -> write_msg(Msg,Seq,open(F)); write_msg(Msg,Seq,FD) -> io:fwrite(FD,"~.9.0w ~w~n",[Seq,Msg]),FD. open(File) -> {ok,FD}=file:open(File,[write]),FD. grep('',_) -> true; grep(P,T) when is_list(P) -> case grp(P,T) of [] -> true; _ -> false end; grep(P,T) -> grep([P],T). grp([], _) -> []; grp(P, []) -> P; grp(P, Fun) when is_function(Fun) -> grp(P, list_to_atom(erlang:fun_to_list(Fun))); grp(P, Port) when is_port(Port) -> grp(P, list_to_atom(erlang:port_to_list(Port))); grp(P, Rf) when is_reference(Rf) -> grp(P, list_to_atom(erlang:ref_to_list(Rf))); grp(P, Pid) when is_pid(Pid) -> grp(P, list_to_atom(pid_to_list(Pid))); grp(P, T) when is_tuple(T) -> case lists:member(T,P) of true -> grp(P--[T], []); false -> grp(P,tuple_to_list(T)) end; grp(P, L) when is_list(L) -> case lists:member(L, P) of true -> grp(P--[L], []); false -> grp(grp(P, hd(L)), tl(L)) end; grp(P, T) -> grp(P--[T], []). raw(end_of_trace,State) -> State; raw(Mess,State) -> case element(1,Mess) of trace -> raw_out(Mess,State); trace_ts -> raw_out(Mess,State); _ -> State end. raw_out(_Mess, State = #state{seq=Seq}) -> case {State#state.min < Seq, Seq < State#state.max} of {true,true} -> %%io:fwrite("~w - ~w~n",[Seq,Mess]), State#state{seq=Seq+1}; {true,false} -> State#state{eof=true}; {false,true} -> State#state{seq=Seq+1} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% format is {Tag, PI, Data, Timestamp} %%% PI is {pid(), Info} %%% Info is atom()|tuple() %%% Timestamp is no_time|now() %%% Data is; %%% %%% send, {PI2, Msg} %%% send_to_non_existing_process, {PI2, Msg} %%% 'receive', Message %%% call, {M,F,A} %%% return_to, {M,F,A} %%% return_from, {{M,F,A}, ReturnValue} %%% spawn, {PI2, {M,F,A}} %%% exit, Reason %%% link, PI2 %%% unlink, PI2 %%% getting_linked, PI2 %%% getting_unlinked, PI2 %%% register registered_name %%% unregister registered_name %%% in, {M,F,A} %%% out, {M,F,A} %%% gc_start, Info %%% gc_end, Info %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% mass(end_of_trace = T) -> T; mass({port_info, Info}) -> handle_porti(Info), []; mass({proc_info, Info}) -> handle_proci(Info), []; mass({trace_info, Info}) -> handle_traci(Info), []; mass({trace, A, B, C}) -> mass(A, B, C, no_time); mass({trace, A, B, C, D}) -> mass(A, B, {C, D}, no_time); mass({trace_ts, A, B, C, TS}) -> mass(A ,B, C, TS); mass({trace_ts, A, B, C, D, TS}) -> mass(A, B, {C, D}, TS); mass(X) -> ?log({unrec_msg, X}), []. mass(Pid, T=send, X, TS) -> mass_send(Pid,T,X,TS); mass(Pid, T=send_to_non_existing_process, X,TS) -> mass_send(Pid,T,X,TS); mass(Pid, T='receive', Message, TS) -> {T,pi(Pid),Message,TS}; mass(Pid, T=call, MFA, TS) -> {T,pi(Pid),MFA,TS}; mass(Pid, T=return_to, MFA, TS) -> {T,pi(Pid),MFA,TS}; mass(Pid, T=return_from, {MFA,R}, TS) -> {T,pi(Pid),{MFA,R},TS}; mass(Pid, T=spawn, {P2, MFA}, TS) -> ins({P2,MFA}),{T,pi(Pid),{pi(P2),MFA},TS}; mass(Pid, T=exit, Reason, TS) -> {T,pi(Pid),Reason,TS}; mass(Pid, T=link, Pd, TS) -> {T,pi(Pid),pi(Pd),TS}; mass(Pid, T=unlink, Pd, TS) when is_pid(Pd) -> {T,pi(Pid),pi(Pd),TS}; mass(Pid, T=unlink, _Crap, TS) -> {T,pi(Pid),unknown,TS}; mass(Pid, T=getting_linked, Pd, TS) -> {T,pi(Pid),pi(Pd),TS}; mass(Pid, T=getting_unlinked, Pd, TS) -> {T,pi(Pid),pi(Pd),TS}; mass(Pid, T=register, Nm, TS) -> ins({Pid,Nm}),{T,pi(Pid),Nm,TS}; mass(Pid, T=unregister, Name, TS) -> del(Name),{T,pi(Pid),Name,TS}; mass(Pid, T=in, MFA, TS) -> {T,pi(Pid),MFA,TS}; mass(Pid, T=out, MFA, TS) -> {T,pi(Pid),MFA,TS}; mass(Pid, T=gc_start, Info, TS) -> {T,pi(Pid),Info,TS}; mass(Pid, T=gc_end, Info, TS) -> {T,pi(Pid),Info,TS}. mass_send(Pid, T, {Msg, To}, TS) when is_pid(To); is_port(To) -> {T,pi(Pid),{pi(To), Msg},TS}; mass_send(Pid, T, {Msg, To}, TS) when is_atom(To) -> {T,pi(Pid),{{find_pid(To),To}, Msg},TS}; mass_send(Pid, T, {Msg, {To,Node}}, TS) when is_atom(To), Node==node(Pid) -> {T,pi(Pid),{{find_pid(To),To}, Msg},TS}; mass_send(Pid, T, {Msg, {To,Node}}, TS) when is_atom(To), is_atom(Node) -> {T,pi(Pid),{{remote,{To,Node}}, Msg},TS}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% pi(file_driver) -> {trace,file_driver}; pi(Port) when is_port(Port) -> case ets_lup(Port) of [] -> {Port, unknown}; Name -> {Port, Name} end; pi(Pid) when is_pid(Pid) -> case ets_lup(Pid) of [] -> {Pid, unknown}; {M,F,A} when is_list(A) -> {Pid, {M,F,length(A)}}; Name -> {Pid, Name} end. find_pid(Name) -> ets_lup(Name). handle_porti(Is) -> lists:foreach(fun(I)->ins(I) end, Is). handle_proci(Is) -> lists:foreach(fun(I)->ins(I) end, Is). handle_traci(_I) -> ok. %%% sherk_scan table translates Pid -> Tag, where Tag can be; %%% atom() - (a registered name) %%% tuple(atom(),atom(),integer()) - an initial call (we missed the spawn) %%% tuple(atom(),atom(),list()) - info from spawn %%% %%% it also translates RegisteredName -> Pid ins({Pid,Reg}) when is_atom(Reg) -> ets_ins({Pid,Reg}),ets_ins({Reg,Pid}); ins({Port,Desc}) when is_port(Port) -> ets_ins({Port,Desc}); ins({Pid,{M,F,A}}) when is_integer(A) -> ets_ins({Pid,{M,F,A}}); ins({Pid,Fun}) when is_function(Fun) -> ets_ins({Pid,funi(Fun)}); ins({Pid,{M,F,As}}) when is_list(As) -> ets_ins({Pid,mangle_ic({M,F,As})}). del(Reg) when is_atom(Reg) -> ?log({unregistered,Reg}),ets_del(Reg). mangle_ic(MFA) -> case MFA of {proc_lib,init_p,[_,_,M,F,A]} -> {proc_lib, trans_init(M,F,A)}; {file,file,[_,FileName,_]} -> {file, {atomize(FileName)}}; {dets,do_open_file,[Tab,_FileName,_,_,_,_,_,_Ram,_,_,_]} -> {dets, {Tab}}; {application_master,start_it,[_,{state,_,ApplD,_,_,_},_,_]} -> {appl_data,App,_,_,_,_,_,_,_} = ApplD, {application_master, {App}}; {erlang,apply,[Fun,[]]} when is_function(Fun) -> funi(Fun); {M,F,As} -> {M,F,As} end. atomize(FileName) -> list_to_atom(hd(lists:reverse(string:tokens(FileName, "/")))). funi(Fun) -> case erlang:fun_info(Fun, module) of {_, rpc} -> case erlang:fun_info(Fun, env) of {_, [_, _, Pid, A, _F, _M]} when is_pid(Pid), is_list(A) -> {rpc, {call_dummy, node(Pid)}}; {_, [_, Pid, A, F, M]} when is_pid(Pid)-> {rpc, {call, node(Pid)}, {M, F, length(A)}}; {_, [Pid, A, F, M]} when is_pid(Pid), is_list(A) -> {rpc, {cast, node(Pid)}, {M, F, length(A)}}; _X -> {rpc} end; {_, Mod} -> case erlang:fun_info(Fun, pid) of {_, Pid} when is_pid(Pid) -> {'fun', {Mod, node(Pid)}}; {_, X} -> {'fun', {Mod, X}} end end. trans_init(gen,init_it,[gen_server,_,_,supervisor,{_,Module,_},_]) -> {supervisor,Module,1}; trans_init(gen,init_it,[gen_server,_,_,_,supervisor,{_,Module,_},_]) -> {supervisor,Module,1}; trans_init(gen,init_it,[gen_server,_,_,supervisor_bridge,[Module|_],_]) -> {supervisor_bridge,Module,1}; trans_init(gen,init_it,[gen_server,_,_,_,supervisor_bridge,[Module|_],_]) -> {supervisor_bridge,Module,1}; trans_init(gen,init_it,[gen_server,_,_,Module,_,_]) -> {gen_server,Module}; trans_init(gen,init_it,[gen_server,_,_,_,Module|_]) -> {gen_server,Module}; trans_init(gen,init_it,[gen_fsm,_,_,Module,_,_]) -> {gen_fsm,Module}; trans_init(gen,init_it,[gen_fsm,_,_,_,Module|_]) -> {gen_fsm,Module}; trans_init(gen,init_it,[gen_event|_]) -> {gen_event}; trans_init(M,F,A) -> {M,F,length(A)}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ets_ins(Rec) -> ets_ins(?MODULE, Rec). ets_ins(Tab, Rec) -> ets:insert(Tab, Rec). ets_lup(Key) -> ets_lup(?MODULE, Key). ets_lup(Tab, Key) -> try ets:lookup(Tab, Key) of [{Key, R}] -> R; R -> R catch _:_ -> [] end. ets_del(Key) -> ets:delete(?MODULE, Key).