diff --git a/include/eTpf.hrl b/include/eTpf.hrl index 9fe010c..f5fea59 100644 --- a/include/eTpf.hrl +++ b/include/eTpf.hrl @@ -19,7 +19,6 @@ -export_type([input/0, userInput/0, traceOpts/0, tracerOpts/0]). -type pattern() :: module() | {app, atom()} | {callback, module(), atom()}. - -type scope() :: {scope, [pid() | port() | all | processes | ports |existing | existing_processes | existing_ports |new | new_processes | new_ports]}. -type input() :: [pattern() | scope()]. diff --git a/src/callgrind/tpCallGrind.erl b/src/callgrind/tpCallGrind.erl index 0139951..5e6de2f 100644 --- a/src/callgrind/tpCallGrind.erl +++ b/src/callgrind/tpCallGrind.erl @@ -9,8 +9,8 @@ %% @todo Add an option with a list of modules to exclude. -type opts() :: #{ - scope => global | per_process, %% Whether we filter the output per process. - running => boolean() %% Whether we compute and save wait times. +scope => global | per_process, %% Whether we filter the output per process. +running => boolean() %% Whether we compute and save wait times. }. -record(call, { diff --git a/src/flame/tpFlame.erl b/src/flame/tpFlame.erl index f44daaf..10ba101 100644 --- a/src/flame/tpFlame.erl +++ b/src/flame/tpFlame.erl @@ -1,173 +1,173 @@ -module(tpFlame). -export([ - pfs/2 %% 分析单个文件 - , pfm/2 %% 分析多个文件 + pfs/2 %% 分析单个文件 + , pfm/2 %% 分析多个文件 ]). -record(state, { - outputPath = "", - pid, - lastTs, - count = 0, - acc = [] + outputPath = "", + pid, + lastTs, + count = 0, + acc = [] }). -spec pfs(file:filename_all(), file:filename_all()) -> ok. pfs(InputFile, OutputPath) -> - {ok, FinalState} = tpFReader:fold(fun handleEvent/2, #state{outputPath = OutputPath}, InputFile), - flush(FinalState). + {ok, FinalState} = tpFReader:fold(fun handleEvent/2, #state{outputPath = OutputPath}, InputFile), + flush(FinalState). -spec pfm(file:filename(), file:filename()) -> ok. pfm(InputFiles, OutputPath) -> - PfFiles = filelib:wildcard(InputFiles), - doPfm(PfFiles, #state{outputPath = OutputPath}). + PfFiles = filelib:wildcard(InputFiles), + doPfm(PfFiles, #state{outputPath = OutputPath}). doPfm([], State) -> - flush(State); + flush(State); doPfm([InputFile | PfFiles], State) -> - {ok, NewState} = tpFReader:fold(fun handleEvent/2, State, InputFile), - doPfm(PfFiles, NewState). + {ok, NewState} = tpFReader:fold(fun handleEvent/2, State, InputFile), + doPfm(PfFiles, NewState). handleEvent({Type, Pid, Ts, Arg}, State) -> - doExp({trace_ts, Pid, Type, Arg, Ts}, State); + doExp({trace_ts, Pid, Type, Arg, Ts}, State); handleEvent({Type, Pid, Ts, Arg, ExtraOrMspec}, State) -> - doExp({trace_ts, Pid, Type, Arg, ExtraOrMspec, Ts}, State); + doExp({trace_ts, Pid, Type, Arg, ExtraOrMspec, Ts}, State); handleEvent({Type, Pid, Ts, Arg, Extra, Mspec}, State) -> - doExp({trace_ts, Pid, Type, Arg, Extra, Mspec, Ts}, State). + doExp({trace_ts, Pid, Type, Arg, Extra, Mspec, Ts}, State). doExp(T, #state{outputPath = OutputPath} = State) -> - trace_ts = element(1, T), - Pid = element(2, T), - PidState = - case erlang:get(Pid) of - undefined -> - io:format("~p ", [Pid]), - #state{outputPath = OutputPath}; - SomeState -> - SomeState - end, - NewPidState = doExpInner(T, PidState), - erlang:put(Pid, NewPidState), - State. + trace_ts = element(1, T), + Pid = element(2, T), + PidState = + case erlang:get(Pid) of + undefined -> + io:format("~p ", [Pid]), + #state{outputPath = OutputPath}; + SomeState -> + SomeState + end, + NewPidState = doExpInner(T, PidState), + erlang:put(Pid, NewPidState), + State. %% in & out, without call context, don't help us doExpInner({trace_ts, _Pid, InOut, _MFA, _TS}, #state{lastTs = undefined} = PS) when InOut == in; InOut == out -> - PS; + PS; %% return_from and return_to, without call context, don't help us doExpInner({trace_ts, _Pid, Return, _MFA, _TS}, #state{lastTs = undefined} = PS) when Return == return_from; Return == return_to -> - PS; + PS; doExpInner({trace_ts, Pid, call, MFA, BIN, TS}, #state{lastTs = LastTS, acc = Acc, count = Count} = PS) -> - try - %% Calculate time elapsed, TS-LastTs. - %% 0. If Acc is empty, then skip step #1. - %% 1. Credit elapsed time to the stack on the top of Acc. - %% 2. Push a 0 usec item with this stack onto Acc. - Stak = - lists:filter( - fun(<<"unknown function">>) -> false; - (_) -> true - end, stak_binify(BIN)), - Stack0 = stak_trim(Stak), - MFA_bin = mfa_binify(MFA), - Stack1 = [MFA_bin | lists:reverse(Stack0)], - Acc2 = - case Acc of - [] -> - [{Stack1, 0}]; - [{LastStack, LastTime} | Tail] -> - USec = TS - LastTS, + try + %% Calculate time elapsed, TS-LastTs. + %% 0. If Acc is empty, then skip step #1. + %% 1. Credit elapsed time to the stack on the top of Acc. + %% 2. Push a 0 usec item with this stack onto Acc. + Stak = + lists:filter( + fun(<<"unknown function">>) -> false; + (_) -> true + end, stak_binify(BIN)), + Stack0 = stak_trim(Stak), + MFA_bin = mfa_binify(MFA), + Stack1 = [MFA_bin | lists:reverse(Stack0)], + Acc2 = + case Acc of + [] -> + [{Stack1, 0}]; + [{LastStack, LastTime} | Tail] -> + USec = TS - LastTS, % io:format("Stack1: ~p ~p\n", [Stack1, USec]), - [{Stack1, 0}, - {LastStack, LastTime + USec} | Tail] - end, - %% TODO: more state tracking here. - PS#state{pid = Pid, lastTs = TS, count = Count + 1, acc = Acc2} - catch Class:Reason:StackTrace -> - io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]), - PS - end; + [{Stack1, 0}, + {LastStack, LastTime + USec} | Tail] + end, + %% TODO: more state tracking here. + PS#state{pid = Pid, lastTs = TS, count = Count + 1, acc = Acc2} + catch Class:Reason:StackTrace -> + io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]), + PS + end; doExpInner({trace_ts, _Pid, return_to, MFA, TS}, #state{lastTs = LastTS, acc = Acc} = S) -> - try - %% Calculate time elapsed, TS-LastTs. - %% 1. Credit elapsed time to the stack on the top of Acc. - %% 2. Push a 0 usec item with the "best" stack onto Acc. - %% "best" = MFA exists in the middle of the stack onto Acc, - %% or else MFA exists at the top of a stack elsewhere in Acc. - [{LastStack, LastTime} | Tail] = Acc, - MFA_bin = mfa_binify(MFA), - BestStack = lists:dropwhile(fun(SomeMFA) when SomeMFA /= MFA_bin -> true; - (_) -> false - end, find_matching_stack(MFA_bin, Acc)), - USec = TS - LastTS, - Acc2 = [{BestStack, 0}, - {LastStack, LastTime + USec} | Tail], + try + %% Calculate time elapsed, TS-LastTs. + %% 1. Credit elapsed time to the stack on the top of Acc. + %% 2. Push a 0 usec item with the "best" stack onto Acc. + %% "best" = MFA exists in the middle of the stack onto Acc, + %% or else MFA exists at the top of a stack elsewhere in Acc. + [{LastStack, LastTime} | Tail] = Acc, + MFA_bin = mfa_binify(MFA), + BestStack = lists:dropwhile(fun(SomeMFA) when SomeMFA /= MFA_bin -> true; + (_) -> false + end, find_matching_stack(MFA_bin, Acc)), + USec = TS - LastTS, + Acc2 = [{BestStack, 0}, + {LastStack, LastTime + USec} | Tail], % io:format(user, "return-to: ~p\n", [lists:sublist(Acc2, 4)]), - S#state{lastTs = TS, acc = Acc2} - catch XX:YY:ZZ -> - io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, XX, YY, ZZ]), - S - end; + S#state{lastTs = TS, acc = Acc2} + catch XX:YY:ZZ -> + io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, XX, YY, ZZ]), + S + end; doExpInner({trace_ts, _Pid, gc_start, _Info, TS}, #state{lastTs = LastTS, acc = Acc} = S) -> - try - %% Push a 0 usec item onto Acc. - [{LastStack, LastTime} | Tail] = Acc, - NewStack = [<<"GARBAGE-COLLECTION">> | LastStack], - USec = TS - LastTS, - Acc2 = [{NewStack, 0}, - {LastStack, LastTime + USec} | Tail], + try + %% Push a 0 usec item onto Acc. + [{LastStack, LastTime} | Tail] = Acc, + NewStack = [<<"GARBAGE-COLLECTION">> | LastStack], + USec = TS - LastTS, + Acc2 = [{NewStack, 0}, + {LastStack, LastTime + USec} | Tail], % io:format(user, "GC 1: ~p\n", [lists:sublist(Acc2, 4)]), - S#state{lastTs = TS, acc = Acc2} - catch _XX:_YY:_ZZ -> - %% io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, _XX, _YY, _ZZ]), - S - end; + S#state{lastTs = TS, acc = Acc2} + catch _XX:_YY:_ZZ -> + %% io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, _XX, _YY, _ZZ]), + S + end; doExpInner({trace_ts, _Pid, gc_end, _Info, TS}, #state{lastTs = LastTS, acc = Acc} = S) -> - try - %% Push the GC time onto Acc, then push 0 usec item from last exec - %% stack onto Acc. - [{GCStack, GCTime}, {LastExecStack, _} | Tail] = Acc, - USec = TS - LastTS, - Acc2 = [{LastExecStack, 0}, {GCStack, GCTime + USec} | Tail], + try + %% Push the GC time onto Acc, then push 0 usec item from last exec + %% stack onto Acc. + [{GCStack, GCTime}, {LastExecStack, _} | Tail] = Acc, + USec = TS - LastTS, + Acc2 = [{LastExecStack, 0}, {GCStack, GCTime + USec} | Tail], % io:format(user, "GC 2: ~p\n", [lists:sublist(Acc2, 4)]), - S#state{lastTs = TS, acc = Acc2} - catch _XX:_YY:_ZZ -> - %% io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, _XX, _YY, _ZZ]), - S - end; + S#state{lastTs = TS, acc = Acc2} + catch _XX:_YY:_ZZ -> + %% io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, _XX, _YY, _ZZ]), + S + end; doExpInner({trace_ts, _Pid, out, MFA, TS}, #state{lastTs = LastTS, acc = Acc} = S) -> - try - %% Push a 0 usec item onto Acc. - %% The MFA reported here probably doesn't appear in the stacktrace - %% given to us by the last 'call', so add it here. - [{LastStack, LastTime} | Tail] = Acc, - MFA_bin = mfa_binify(MFA), - NewStack = [<<"SLEEP">>, MFA_bin | LastStack], - USec = TS - LastTS, - Acc2 = [{NewStack, 0}, - {LastStack, LastTime + USec} | Tail], - S#state{lastTs = TS, acc = Acc2} - catch XX:YY:ZZ -> - io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, XX, YY, ZZ]), - S - end; + try + %% Push a 0 usec item onto Acc. + %% The MFA reported here probably doesn't appear in the stacktrace + %% given to us by the last 'call', so add it here. + [{LastStack, LastTime} | Tail] = Acc, + MFA_bin = mfa_binify(MFA), + NewStack = [<<"SLEEP">>, MFA_bin | LastStack], + USec = TS - LastTS, + Acc2 = [{NewStack, 0}, + {LastStack, LastTime + USec} | Tail], + S#state{lastTs = TS, acc = Acc2} + catch XX:YY:ZZ -> + io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, XX, YY, ZZ]), + S + end; doExpInner({trace_ts, _Pid, in, MFA, TS}, #state{lastTs = LastTS, acc = Acc} = S) -> - try - %% Push the Sleep time onto Acc, then push 0 usec item from last - %% exec stack onto Acc. - %% The MFA reported here probably doesn't appear in the stacktrace - %% given to us by the last 'call', so add it here. - MFA_bin = mfa_binify(MFA), - [{SleepStack, SleepTime}, {LastExecStack, _} | Tail] = Acc, - USec = TS - LastTS, - Acc2 = [{[MFA_bin | LastExecStack], 0}, {SleepStack, SleepTime + USec} | Tail], - S#state{lastTs = TS, acc = Acc2} - catch XX:YY:ZZ -> - io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, XX, YY, ZZ]), - S - end; + try + %% Push the Sleep time onto Acc, then push 0 usec item from last + %% exec stack onto Acc. + %% The MFA reported here probably doesn't appear in the stacktrace + %% given to us by the last 'call', so add it here. + MFA_bin = mfa_binify(MFA), + [{SleepStack, SleepTime}, {LastExecStack, _} | Tail] = Acc, + USec = TS - LastTS, + Acc2 = [{[MFA_bin | LastExecStack], 0}, {SleepStack, SleepTime + USec} | Tail], + S#state{lastTs = TS, acc = Acc2} + catch XX:YY:ZZ -> + io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, XX, YY, ZZ]), + S + end; %exp1_inner(end_of_trace = _Else, #state{pid=Pid, output_path=OutputPath, acc=Acc} = S) -> % {ok, FH} = file:open(OutputPath, [write, raw, binary, delayed_write]), @@ -182,22 +182,22 @@ doExpInner({trace_ts, _Pid, in, MFA, TS}, #state{lastTs = LastTS, acc = Acc} = S % S; doExpInner(_Else, S) -> % io:format("?? ~P\n", [_Else, 10]), - S. + S. find_matching_stack(MFA_bin, [{H, _Time} | _] = Acc) -> - case lists:member(MFA_bin, H) of - true -> - H; - false -> - find_matching_stack2(MFA_bin, Acc) - end. + case lists:member(MFA_bin, H) of + true -> + H; + false -> + find_matching_stack2(MFA_bin, Acc) + end. find_matching_stack2(MFA_bin, [{[MFA_bin | _StackTail] = Stack, _Time} | _]) -> - Stack; + Stack; find_matching_stack2(MFA_bin, [_H | T]) -> - find_matching_stack2(MFA_bin, T); + find_matching_stack2(MFA_bin, T); find_matching_stack2(_MFA_bin, []) -> - [<<"FIND-MATCHING-FAILED">>]. + [<<"FIND-MATCHING-FAILED">>]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -206,63 +206,63 @@ intersperse(_, [X]) -> [X]; intersperse(Sep, [X | Xs]) -> [X, Sep | intersperse(Sep, Xs)]. stak_trim([<<"proc_lib:init_p_do_apply/3">>, <<"gen_fsm:decode_msg/9">>, <<"gen_fsm:handle_msg/7">>, <<"gen_fsm:loop/7">> | T]) -> - stak_trim([<<"GEN-FSM">> | T]); + stak_trim([<<"GEN-FSM">> | T]); stak_trim([<<"GEN-FSM">>, <<"gen_fsm:decode_msg/9">>, <<"gen_fsm:handle_msg/7">>, <<"gen_fsm:loop/7">> | T]) -> - stak_trim([<<"GEN-FSM">> | T]); + stak_trim([<<"GEN-FSM">> | T]); stak_trim(Else) -> - Else. + Else. stak_binify(Bin) when is_binary(Bin) -> - [list_to_binary(X) || X <- stak(Bin)]; + [list_to_binary(X) || X <- stak(Bin)]; stak_binify(X) -> - list_to_binary(io_lib:format("~w", [X])). + list_to_binary(io_lib:format("~w", [X])). mfa_binify({M, F, A}) -> - list_to_binary(io_lib:format("~w:~w/~w", [M, F, A])); + list_to_binary(io_lib:format("~w:~w/~w", [M, F, A])); mfa_binify(X) -> - list_to_binary(io_lib:format("~w", [X])). + list_to_binary(io_lib:format("~w", [X])). %% Borrowed from redbug.erl stak(Bin) -> - lists:foldl(fun munge/2, [], string:tokens(binary_to_list(Bin), "\n")). + lists:foldl(fun munge/2, [], string:tokens(binary_to_list(Bin), "\n")). munge(I, Out) -> - case I of %% lists:reverse(I) of - "..." ++ _ -> ["truncated!!!" | Out]; - _ -> - case string:str(I, "Return addr") of - 0 -> - case string:str(I, "cp = ") of - 0 -> Out; - _ -> [mfaf(I) | Out] - end; - _ -> - case string:str(I, "erminate process normal") of - 0 -> [mfaf(I) | Out]; - _ -> Out - end - end - end. + case I of %% lists:reverse(I) of + "..." ++ _ -> ["truncated!!!" | Out]; + _ -> + case string:str(I, "Return addr") of + 0 -> + case string:str(I, "cp = ") of + 0 -> Out; + _ -> [mfaf(I) | Out] + end; + _ -> + case string:str(I, "erminate process normal") of + 0 -> [mfaf(I) | Out]; + _ -> Out + end + end + end. mfaf(I) -> - [_, C | _] = string:tokens(I, "()+"), - string:strip(C). + [_, C | _] = string:tokens(I, "()+"), + string:strip(C). flush(#state{outputPath = OutputPath}) -> - PidStates = get(), - {ok, FH} = file:open(OutputPath, [write, raw, binary, delayed_write]), - io:format("\n\nWriting to ~s for ~w processes... ", [OutputPath, length(PidStates)]), - _ = [ - [begin - Pid_str0 = lists:flatten(io_lib:format("~w", [Pid])), - Size = length(Pid_str0), - Pid_str = [$(, lists:sublist(Pid_str0, 2, Size - 2), $)], - Time_str = integer_to_list(Time), - file:write(FH, [Pid_str, $;, intersperse($;, lists:reverse(Stack)), 32, Time_str, 10]) - end || {Stack, Time} <- Acc] - || {Pid, #state{acc = Acc} = _S} <- PidStates], - _ = file:close(FH), - io:format("finished!\n"), - ok. + PidStates = get(), + {ok, FH} = file:open(OutputPath, [write, raw, binary, delayed_write]), + io:format("\n\nWriting to ~s for ~w processes... ", [OutputPath, length(PidStates)]), + _ = [ + [begin + Pid_str0 = lists:flatten(io_lib:format("~w", [Pid])), + Size = length(Pid_str0), + Pid_str = [$(, lists:sublist(Pid_str0, 2, Size - 2), $)], + Time_str = integer_to_list(Time), + file:write(FH, [Pid_str, $;, intersperse($;, lists:reverse(Stack)), 32, Time_str, 10]) + end || {Stack, Time} <- Acc] + || {Pid, #state{acc = Acc} = _S} <- PidStates], + _ = file:close(FH), + io:format("finished!\n"), + ok. diff --git a/src/tracer/tpTracerSocket.erl b/src/tracer/tpTracerSocket.erl index 3d31288..edc0e5a 100644 --- a/src/tracer/tpTracerSocket.erl +++ b/src/tracer/tpTracerSocket.erl @@ -55,7 +55,7 @@ trace_loop(State = #state{parent = Parent, timerRef = TRef}, CSocket) -> exit(Reason); {system, From, Request} -> sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {trace_loop, State, CSocket}); - %% Reset the timeout when we receive data. + %% Reset the timeout when we receive data. {tcp, CSocket, _} -> trace_loop(reset_timeout(State), CSocket); {tcp_closed, CSocket} -> @@ -64,20 +64,20 @@ trace_loop(State = #state{parent = Parent, timerRef = TRef}, CSocket) -> close(State, CSocket); {timeout, TRef, ?MODULE} -> close(State, CSocket); - %% Discard the non-blocking send reply when successful. + %% Discard the non-blocking send reply when successful. {inet_reply, CSocket, ok} -> trace_loop(State, CSocket); - %% And close the socket when an error occured. + %% And close the socket when an error occured. {inet_reply, CSocket, _} -> close(State, CSocket); - %% Discard TCP messages from closed sockets. + %% Discard TCP messages from closed sockets. {tcp, _, _} -> trace_loop(State, CSocket); {tcp_closed, _} -> trace_loop(State, CSocket); {tcp_error, _, _} -> trace_loop(State, CSocket); - %% Discard any previous timeout. + %% Discard any previous timeout. {timeout, _, ?MODULE} -> trace_loop(State, CSocket); Msg ->