25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
8.4 KiB

  1. -module(tpFlame). %% 火焰图分析
  2. -export([
  3. pfs/2
  4. , pfm/3
  5. ]).
  6. -record(state, {
  7. outputFile = "",
  8. pid,
  9. lastTs,
  10. count = 0,
  11. acc = []
  12. }).
  13. -spec pfs(file:filename_all(), file:filename_all()) -> ok.
  14. pfs(InputFile, OutputFile) ->
  15. {ok, FinalState} = tpFReader:fold(fun handleEvent/2, #state{outputFile = OutputFile}, InputFile),
  16. flush(FinalState).
  17. -spec pfm(file:filename(), filelib:dirname(), file:filename()) -> ok.
  18. pfm(InputFiles, Cwd, OutputFile) ->
  19. PfFiles = lists:sort(filelib:wildcard(InputFiles, Cwd)),
  20. doPfm(PfFiles, #state{outputFile = OutputFile}).
  21. doPfm([], State) ->
  22. flush(State);
  23. doPfm([InputFile | PfFiles], State) ->
  24. {ok, NewState} = tpFReader:fold(fun handleEvent/2, State, InputFile),
  25. doPfm(PfFiles, NewState).
  26. handleEvent(Trace, State) ->
  27. Pid = element(2, Trace),
  28. PidState = getPidState(Pid),
  29. NewPidState = doExpInner(Trace, PidState),
  30. setPidState(Pid, NewPidState),
  31. State.
  32. doExpInner({call, Pid, TS, MFA, BIN}, #state{lastTs = LastTS, acc = Acc, count = Count} = PS) ->
  33. try
  34. %% Calculate time elapsed, TS-LastTs. 计算经过的时间,TS-LastTs。
  35. %% 0. If Acc is empty, then skip step #1. 0. 如果 Acc 为空,则跳过步骤 1。
  36. %% 1. Credit elapsed time to the stack on the top of Acc. 1. 将经过的时间计入 Acc 顶部的堆栈。
  37. %% 2. Push a 0 usec item with this stack onto Acc. 2. 将带有此堆栈的 0 usec 项目推送到 Acc。
  38. Stack = lists:filter(fun(<<"unknown function">>) -> false;(_) -> true end, stackToBin(BIN)),
  39. TrimStack = stackTrim(Stack),
  40. MFABin = mfaToBin(MFA),
  41. NewStack = [MFABin | lists:reverse(TrimStack)],
  42. NewAcc =
  43. case Acc of
  44. [] ->
  45. [{NewStack, 0}];
  46. [{LastStack, LastTime} | Tail] ->
  47. USec = TS - LastTS,
  48. % io:format("Stack1: ~p ~p\n", [Stack1, USec]),
  49. [{NewStack, 0}, {LastStack, LastTime + USec} | Tail]
  50. end,
  51. %% TODO: more state tracking here.
  52. PS#state{pid = Pid, lastTs = TS, count = Count + 1, acc = NewAcc}
  53. catch Class:Reason:StackTrace ->
  54. io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]),
  55. PS
  56. end;
  57. doExpInner({return_to, _Pid, TS, MFA}, #state{lastTs = LastTS, acc = Acc} = PS) when LastTS =/= undefined ->
  58. try
  59. %% Calculate time elapsed, TS-LastTs.
  60. %% 1. Credit elapsed time to the stack on the top of Acc.
  61. %% 2. Push a 0 usec item with the "best" stack onto Acc.
  62. %% "best" = MFA exists in the middle of the stack onto Acc,
  63. %% or else MFA exists at the top of a stack elsewhere in Acc.
  64. [{LastStack, LastTime} | Tail] = Acc,
  65. MFABin = mfaToBin(MFA),
  66. BestStack = lists:dropwhile(fun(SomeMFA) when SomeMFA /= MFABin -> true;(_) -> false end, findMatchingStack(MFABin, Acc)),
  67. USec = TS - LastTS,
  68. NewAcc = [{BestStack, 0}, {LastStack, LastTime + USec} | Tail],
  69. % io:format(user, "return-to: ~p\n", [lists:sublist(Acc2, 4)]),
  70. PS#state{lastTs = TS, acc = NewAcc}
  71. catch Class:Reason:StackTrace ->
  72. io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]),
  73. PS
  74. end;
  75. doExpInner({gc_start, _Pid, TS, _Info}, #state{lastTs = LastTS, acc = Acc} = PS) ->
  76. try
  77. %% Push a 0 usec item onto Acc.
  78. [{LastStack, LastTime} | Tail] = Acc,
  79. NewStack = [<<"GARBAGE-COLLECTION">> | LastStack],
  80. USec = TS - LastTS,
  81. NewAcc = [{NewStack, 0}, {LastStack, LastTime + USec} | Tail],
  82. % io:format(user, "GC 1: ~p\n", [lists:sublist(Acc2, 4)]),
  83. PS#state{lastTs = TS, acc = NewAcc}
  84. catch Class:Reason:StackTrace ->
  85. io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]),
  86. PS
  87. end;
  88. doExpInner({gc_end, _Pid, TS, _Info}, #state{lastTs = LastTS, acc = Acc} = PS) ->
  89. try
  90. %% Push the GC time onto Acc, then push 0 usec item from last exec
  91. %% stack onto Acc.
  92. [{GCStack, GCTime}, {LastExecStack, _} | Tail] = Acc,
  93. USec = TS - LastTS,
  94. NewAcc = [{LastExecStack, 0}, {GCStack, GCTime + USec} | Tail],
  95. % io:format(user, "GC 2: ~p\n", [lists:sublist(Acc2, 4)]),
  96. PS#state{lastTs = TS, acc = NewAcc}
  97. catch Class:Reason:StackTrace ->
  98. io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]),
  99. PS
  100. end;
  101. doExpInner({out, _Pid, TS, MFA}, #state{lastTs = LastTS, acc = Acc} = PS) when LastTS =/= undefined ->
  102. try
  103. %% Push a 0 usec item onto Acc.
  104. %% The MFA reported here probably doesn't appear in the stacktrace
  105. %% given to us by the last 'call', so add it here.
  106. [{LastStack, LastTime} | Tail] = Acc,
  107. MFA_bin = mfaToBin(MFA),
  108. NewStack = [<<"SLEEP">>, MFA_bin | LastStack],
  109. USec = TS - LastTS,
  110. NewAcc = [{NewStack, 0}, {LastStack, LastTime + USec} | Tail],
  111. PS#state{lastTs = TS, acc = NewAcc}
  112. catch Class:Reason:StackTrace ->
  113. io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]),
  114. PS
  115. end;
  116. doExpInner({in, _Pid, TS, MFA}, #state{lastTs = LastTS, acc = Acc} = PS) when LastTS =/= undefined ->
  117. try
  118. %% Push the Sleep time onto Acc, then push 0 usec item from last
  119. %% exec stack onto Acc.
  120. %% The MFA reported here probably doesn't appear in the stacktrace
  121. %% given to us by the last 'call', so add it here.
  122. MFA_bin = mfaToBin(MFA),
  123. [{SleepStack, SleepTime}, {LastExecStack, _} | Tail] = Acc,
  124. USec = TS - LastTS,
  125. NewAcc = [{[MFA_bin | LastExecStack], 0}, {SleepStack, SleepTime + USec} | Tail],
  126. PS#state{lastTs = TS, acc = NewAcc}
  127. catch Class:Reason:StackTrace ->
  128. io:format(user, "~p: ~p:~p @ ~p\n", [?LINE, Class, Reason, StackTrace]),
  129. PS
  130. end;
  131. doExpInner(_Else, PS) ->
  132. % io:format("?? ~P\n", [_Else, 10]),
  133. PS.
  134. findMatchingStack(MFABin, [{H, _Time} | _] = Acc) ->
  135. case lists:member(MFABin, H) of
  136. true ->
  137. H;
  138. _ ->
  139. forFindMatchingStack(MFABin, Acc)
  140. end.
  141. forFindMatchingStack(MFABin, [{[MFABin | _StackTail] = Stack, _Time} | _]) ->
  142. Stack;
  143. forFindMatchingStack(MFABin, [_H | T]) ->
  144. forFindMatchingStack(MFABin, T);
  145. forFindMatchingStack(_MFABin, []) ->
  146. [<<"FIND-MATCHING-FAILED">>].
  147. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  148. intersperse(_, []) -> [];
  149. intersperse(_, [X]) -> [X];
  150. intersperse(Sep, [X | Xs]) -> [X, Sep | intersperse(Sep, Xs)].
  151. stackTrim([<<"proc_lib:init_p_do_apply/3">>, <<"gen_fsm:decode_msg/9">>, <<"gen_fsm:handle_msg/7">>, <<"gen_fsm:loop/7">> | T]) ->
  152. stackTrim([<<"GEN-FSM">> | T]);
  153. stackTrim([<<"GEN-FSM">>, <<"gen_fsm:decode_msg/9">>, <<"gen_fsm:handle_msg/7">>, <<"gen_fsm:loop/7">> | T]) ->
  154. stackTrim([<<"GEN-FSM">> | T]);
  155. stackTrim(Else) ->
  156. Else.
  157. stackToBin(Bin) when is_binary(Bin) ->
  158. [list_to_binary(X) || X <- stack(Bin)];
  159. stackToBin(X) ->
  160. eFmt:formatBin("~w", [X]).
  161. mfaToBin({M, F, A}) ->
  162. eFmt:formatBin("~w:~w/~w", [M, F, A]);
  163. mfaToBin(X) ->
  164. eFmt:formatBin("~w", [X]).
  165. %% Borrowed from redbug.erl
  166. stack(Bin) ->
  167. lists:foldl(fun munge/2, [], string:tokens(binary_to_list(Bin), "\n")).
  168. munge(I, Out) ->
  169. case I of %% lists:reverse(I) of
  170. "..." ++ _ -> ["truncated!!!" | Out];
  171. _ ->
  172. case string:str(I, "Return addr") of
  173. 0 ->
  174. case string:str(I, "cp = ") of
  175. 0 -> Out;
  176. _ -> [mfaf(I) | Out]
  177. end;
  178. _ ->
  179. case string:str(I, "erminate process normal") of
  180. 0 -> [mfaf(I) | Out];
  181. _ -> Out
  182. end
  183. end
  184. end.
  185. mfaf(I) ->
  186. [_, C | _] = string:tokens(I, "()+"),
  187. string:strip(C).
  188. flush(#state{outputFile = OutputFile}) ->
  189. PidStates = get(),
  190. {ok, FH} = file:open(OutputFile, [write, raw, binary, delayed_write]),
  191. io:format("\n\nWriting to ~s for ~w processes...", [OutputFile, length(PidStates)]),
  192. [
  193. [
  194. begin
  195. PidStr = eFmt:formatBin("~w", [Pid]),
  196. Size = byte_size(PidStr),
  197. TimeStr = integer_to_binary(Time),
  198. file:write(FH, [<<"(">>, binary:part(PidStr, 1, Size -2) , <<")">>, $;, intersperse($;, lists:reverse(Stack)), 32, TimeStr, 10])
  199. end || {Stack, Time} <- Acc
  200. ] || {Pid, #state{acc = Acc} = _PS} <- PidStates],
  201. ok = file:close(FH),
  202. io:format("finished!\n"),
  203. ok.
  204. setPidState(Pid, PidState) ->
  205. erlang:put(Pid, PidState).
  206. getPidState(Pid) ->
  207. case erlang:get(Pid) of
  208. undefined ->
  209. io:format("~p ", [Pid]),
  210. #state{};
  211. SomeState ->
  212. SomeState
  213. end.