|
@ -2,13 +2,13 @@ |
|
|
|
|
|
|
|
|
-export([pts/0]). |
|
|
-export([pts/0]). |
|
|
-export([pfs/2]). |
|
|
-export([pfs/2]). |
|
|
|
|
|
-export([pfs/3]). |
|
|
-export([pfm/3]). |
|
|
-export([pfm/3]). |
|
|
-export([profile_many/2]). |
|
|
|
|
|
-export([profile_many/3]). |
|
|
|
|
|
|
|
|
-export([pfm/4]). |
|
|
|
|
|
|
|
|
-type opts() :: #{ |
|
|
-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, { |
|
|
-record(call, { |
|
@ -27,27 +27,18 @@ |
|
|
}). |
|
|
}). |
|
|
|
|
|
|
|
|
-record(proc, { |
|
|
-record(proc, { |
|
|
%% Call stack. |
|
|
|
|
|
stack = [] :: [#call{}], |
|
|
|
|
|
%% Profile information waiting to be written to file. |
|
|
|
|
|
mfas = #{} :: #{atom() => #call{}}, |
|
|
|
|
|
%% Timestamp the process got scheduled out. |
|
|
|
|
|
out = undefined :: undefined | non_neg_integer() |
|
|
|
|
|
|
|
|
stack = [] :: [#call{}], %% Call stack. |
|
|
|
|
|
mfas = #{} :: #{atom() => #call{}}, %% Profile information waiting to be written to file. |
|
|
|
|
|
out = undefined :: undefined | non_neg_integer() %% Timestamp the process got scheduled out. |
|
|
}). |
|
|
}). |
|
|
|
|
|
|
|
|
-record(state, { |
|
|
-record(state, { |
|
|
%% Input file name. |
|
|
|
|
|
input :: file:filename_all(), |
|
|
|
|
|
%% Output file name. |
|
|
|
|
|
output :: file:filename_all(), |
|
|
|
|
|
%% Output fd. |
|
|
|
|
|
output_device :: file:io_device(), |
|
|
|
|
|
%% Options. |
|
|
|
|
|
opts :: opts(), |
|
|
|
|
|
%% List of processes. |
|
|
|
|
|
processes = #{} :: #{pid() => #proc{}}, |
|
|
|
|
|
%% Cache of source file information. |
|
|
|
|
|
sources = #{} :: #{mfa() => {string(), pos_integer()}} |
|
|
|
|
|
|
|
|
input :: file:filename_all(), %% Input file name. |
|
|
|
|
|
output :: file:filename_all(), %% Output file name. |
|
|
|
|
|
output_device :: file:io_device(), %% Output fd. |
|
|
|
|
|
opts :: opts(), %% Options. |
|
|
|
|
|
processes = #{} :: #{pid() => #proc{}}, %% List of processes. |
|
|
|
|
|
sources = #{} :: #{mfa() => {string(), pos_integer()}} %% Cache of source file information. |
|
|
}). |
|
|
}). |
|
|
|
|
|
|
|
|
-spec pts() -> eTpf:input(). |
|
|
-spec pts() -> eTpf:input(). |
|
@ -55,103 +46,114 @@ pts() -> |
|
|
[{app, kernel}, {app, stdlib}, {app, looking_glass}]. |
|
|
[{app, kernel}, {app, stdlib}, {app, looking_glass}]. |
|
|
|
|
|
|
|
|
-spec pfs(file:filename_all(), file:filename_all()) -> ok. |
|
|
-spec pfs(file:filename_all(), file:filename_all()) -> ok. |
|
|
pfs(Input, Output) -> |
|
|
|
|
|
pfm(Input, Output, #{}). |
|
|
|
|
|
|
|
|
pfs(InputFile, Output) -> |
|
|
|
|
|
pfs(InputFile, Output, #{}). |
|
|
|
|
|
|
|
|
-spec pfm(file:filename_all(), file:filename_all(), opts()) -> ok. |
|
|
|
|
|
pfm(Input, Output, Opts) -> |
|
|
|
|
|
|
|
|
-spec pfs(file:filename_all(), file:filename_all(), opts()) -> ok. |
|
|
|
|
|
pfs(InputFile, Output, Opts) -> |
|
|
{ok, OutDevice} = file:open(Output, [write]), |
|
|
{ok, OutDevice} = file:open(Output, [write]), |
|
|
State = #state{input = Input, output = Output, output_device = OutDevice, opts = Opts}, |
|
|
|
|
|
write_header(State), |
|
|
|
|
|
{ok, FinalState} = tpFReader:fold(fun handle_event/2, State, Input), |
|
|
|
|
|
|
|
|
State = #state{input = InputFile, output = Output, output_device = OutDevice, opts = Opts}, |
|
|
|
|
|
writeHeader(State), |
|
|
|
|
|
{ok, FinalState} = tpFReader:fold(fun handleEvent/2, State, InputFile), |
|
|
flush(FinalState), |
|
|
flush(FinalState), |
|
|
_ = file:close(OutDevice), |
|
|
_ = file:close(OutDevice), |
|
|
ok. |
|
|
ok. |
|
|
|
|
|
|
|
|
flush(State = #state{processes = Procs}) -> |
|
|
|
|
|
maps:fold(fun(Pid, #proc{mfas = MFAs}, _) -> |
|
|
|
|
|
write_mfas(Pid, MFAs, State) |
|
|
|
|
|
end, undefined, Procs), |
|
|
|
|
|
ok. |
|
|
|
|
|
|
|
|
|
|
|
-spec profile_many(file:filename(), file:filename()) -> ok. |
|
|
|
|
|
profile_many(Wildcard, Prefix) -> |
|
|
|
|
|
profile_many(Wildcard, Prefix, #{}). |
|
|
|
|
|
|
|
|
-spec pfm(file:filename(), filelib:dirname(), file:filename()) -> ok. |
|
|
|
|
|
pfm(Wildcard, Cwd, Prefix) -> |
|
|
|
|
|
pfm(Wildcard, Cwd, Prefix, #{}). |
|
|
|
|
|
|
|
|
-spec profile_many(file:filename(), file:filename(), opts()) -> ok. |
|
|
|
|
|
profile_many(Wildcard, Prefix, Opts) -> |
|
|
|
|
|
Files = filelib:wildcard(Wildcard), |
|
|
|
|
|
|
|
|
-spec pfm(file:filename(), filelib:dirname(), file:filename(), opts()) -> ok. |
|
|
|
|
|
pfm(Wildcard, Cwd, Prefix, Opts) -> |
|
|
|
|
|
Files = lists:sort(filelib:wildcard(Wildcard, Cwd)), |
|
|
Seq = lists:seq(1, length(Files)), |
|
|
Seq = lists:seq(1, length(Files)), |
|
|
OutFiles = [Prefix ++ "." ++ integer_to_list(N) || N <- Seq], |
|
|
OutFiles = [Prefix ++ "." ++ integer_to_list(N) || N <- Seq], |
|
|
Many = lists:zip(Files, OutFiles), |
|
|
Many = lists:zip(Files, OutFiles), |
|
|
Refs = [monitor(process, spawn_link(?MODULE, profile, [Input, Output, Opts])) |
|
|
|
|
|
|| {Input, Output} <- Many], |
|
|
|
|
|
wait_for_procs(Refs). |
|
|
|
|
|
|
|
|
Refs = [monitor(process, spawn_link(?MODULE, pfs, [Input, Output, Opts])) || {Input, Output} <- Many], |
|
|
|
|
|
waitForProcs(Refs). |
|
|
|
|
|
|
|
|
|
|
|
flush(State = #state{processes = Procs}) -> |
|
|
|
|
|
maps:fold( |
|
|
|
|
|
fun(Pid, #proc{mfas = MFAs}, _) -> |
|
|
|
|
|
writeMfas(Pid, MFAs, State) |
|
|
|
|
|
end, |
|
|
|
|
|
undefined, Procs), |
|
|
|
|
|
ok. |
|
|
|
|
|
|
|
|
%% We do not need to worry about failure because we are linked. |
|
|
%% We do not need to worry about failure because we are linked. |
|
|
wait_for_procs([]) -> |
|
|
|
|
|
|
|
|
waitForProcs([]) -> |
|
|
ok; |
|
|
ok; |
|
|
wait_for_procs(Refs) -> |
|
|
|
|
|
|
|
|
waitForProcs(Refs) -> |
|
|
receive |
|
|
receive |
|
|
%% We purposefully ignore any stray messages. |
|
|
%% We purposefully ignore any stray messages. |
|
|
{'DOWN', R, process, _, _} -> |
|
|
{'DOWN', R, process, _, _} -> |
|
|
wait_for_procs(Refs -- [R]) |
|
|
|
|
|
|
|
|
waitForProcs(Refs -- [R]) |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
%% We handle trace events one by one, keeping track of the |
|
|
|
|
|
%% execution stack for each process. |
|
|
|
|
|
|
|
|
|
|
|
%% We don't care about match spec results for callgrind. |
|
|
|
|
|
handle_event({call, Pid, Ts, MFA, _MSpec}, State) -> |
|
|
|
|
|
handle_event({call, Pid, Ts, MFA}, State); |
|
|
|
|
|
handle_event({call, Pid, Ts, MFA}, State0) -> |
|
|
|
|
|
Proc = case is_process_profiled(Pid, State0) of |
|
|
|
|
|
{true, P} -> P; |
|
|
|
|
|
{empty, P} -> P; |
|
|
|
|
|
false -> #proc{} |
|
|
|
|
|
end, |
|
|
|
|
|
{Source, State} = find_source(MFA, State0), |
|
|
|
|
|
handle_call(Pid, convert_mfa(MFA), Source, Ts, Proc, State); |
|
|
|
|
|
handle_event({return_to, Pid, Ts, MFA}, State) -> |
|
|
|
|
|
case is_process_profiled(Pid, State) of |
|
|
|
|
|
{true, Proc} -> handle_return_to(Pid, convert_mfa(MFA), Ts, Proc, State); |
|
|
|
|
|
_ -> State |
|
|
|
|
|
|
|
|
%% We handle trace events one by one, keeping track of the execution stack for each process. |
|
|
|
|
|
%% 我们一一处理跟踪事件,跟踪每个进程的执行堆栈。 |
|
|
|
|
|
|
|
|
|
|
|
%% 我们不关心匹配规范结果 for callgrind. |
|
|
|
|
|
handleEvent({call, Pid, Ts, MFA, _MSpec}, State) -> |
|
|
|
|
|
handleEvent({call, Pid, Ts, MFA}, State); |
|
|
|
|
|
handleEvent({call, Pid, Ts, MFA}, State0) -> |
|
|
|
|
|
Proc = |
|
|
|
|
|
case isProcProfiled(Pid, State0) of |
|
|
|
|
|
{true, P} -> |
|
|
|
|
|
P; |
|
|
|
|
|
{empty, P} -> |
|
|
|
|
|
P; |
|
|
|
|
|
false -> |
|
|
|
|
|
#proc{} |
|
|
|
|
|
end, |
|
|
|
|
|
{Source, State} = findSource(MFA, State0), |
|
|
|
|
|
handleCall(Pid, convertMfa(MFA), Source, Ts, Proc, State); |
|
|
|
|
|
handleEvent({return_to, Pid, Ts, MFA}, State) -> |
|
|
|
|
|
case isProcProfiled(Pid, State) of |
|
|
|
|
|
{true, Proc} -> |
|
|
|
|
|
handleReturnTo(Pid, convertMfa(MFA), Ts, Proc, State); |
|
|
|
|
|
_ -> |
|
|
|
|
|
State |
|
|
end; |
|
|
end; |
|
|
%% Process exited. Unfold the stacktrace entirely. |
|
|
%% Process exited. Unfold the stacktrace entirely. |
|
|
%% |
|
|
%% |
|
|
%% We use the atom exit because we know it will not match |
|
|
%% We use the atom exit because we know it will not match |
|
|
%% a function call and will therefore unfold everything. |
|
|
%% a function call and will therefore unfold everything. |
|
|
handle_event({exit, Pid, Ts, _Reason}, State0) -> |
|
|
|
|
|
case is_process_profiled(Pid, State0) of |
|
|
|
|
|
|
|
|
handleEvent({exit, Pid, Ts, _Reason}, State0) -> |
|
|
|
|
|
case isProcProfiled(Pid, State0) of |
|
|
{true, Proc} -> |
|
|
{true, Proc} -> |
|
|
State = #state{processes = Procs} = handle_return_to(Pid, exit, Ts, Proc, State0), |
|
|
|
|
|
|
|
|
State = #state{processes = Procs} = handleReturnTo(Pid, exit, Ts, Proc, State0), |
|
|
%% Remove the pid from the state to save memory. |
|
|
%% Remove the pid from the state to save memory. |
|
|
State#state{processes = maps:remove(Pid, Procs)}; |
|
|
State#state{processes = maps:remove(Pid, Procs)}; |
|
|
_ -> |
|
|
_ -> |
|
|
State0 |
|
|
State0 |
|
|
end; |
|
|
end; |
|
|
handle_event({in, Pid, Ts, _MFA}, State = #state{opts = #{running := true}}) -> |
|
|
|
|
|
case is_process_profiled(Pid, State) of |
|
|
|
|
|
{true, Proc} -> handle_in(Pid, Ts, Proc, State); |
|
|
|
|
|
|
|
|
handleEvent({in, Pid, Ts, _MFA}, State = #state{opts = #{running := true}}) -> |
|
|
|
|
|
case isProcProfiled(Pid, State) of |
|
|
|
|
|
{true, Proc} -> handleIn(Pid, Ts, Proc, State); |
|
|
_ -> State |
|
|
_ -> State |
|
|
end; |
|
|
end; |
|
|
handle_event({out, Pid, Ts, _MFA}, State = #state{opts = #{running := true}}) -> |
|
|
|
|
|
case is_process_profiled(Pid, State) of |
|
|
|
|
|
{true, Proc} -> handle_out(Pid, Ts, Proc, State); |
|
|
|
|
|
|
|
|
handleEvent({out, Pid, Ts, _MFA}, State = #state{opts = #{running := true}}) -> |
|
|
|
|
|
case isProcProfiled(Pid, State) of |
|
|
|
|
|
{true, Proc} -> handleOut(Pid, Ts, Proc, State); |
|
|
_ -> State |
|
|
_ -> State |
|
|
end; |
|
|
end; |
|
|
%% Ignore all other events. We do not need them for building the callgrind file. |
|
|
%% Ignore all other events. We do not need them for building the callgrind file. |
|
|
handle_event(_, State) -> |
|
|
|
|
|
|
|
|
handleEvent(_, State) -> |
|
|
State. |
|
|
State. |
|
|
|
|
|
|
|
|
is_process_profiled(Pid, #state{processes = Procs}) -> |
|
|
|
|
|
|
|
|
isProcProfiled(Pid, #state{processes = Procs}) -> |
|
|
case maps:get(Pid, Procs, undefined) of |
|
|
case maps:get(Pid, Procs, undefined) of |
|
|
%% We never received events for this process. Ignore. |
|
|
%% We never received events for this process. Ignore. |
|
|
undefined -> false; |
|
|
|
|
|
|
|
|
undefined -> |
|
|
|
|
|
false; |
|
|
%% We received events but are not in a known function currently. Ignore. |
|
|
%% We received events but are not in a known function currently. Ignore. |
|
|
Proc = #proc{stack = []} -> {empty, Proc}; |
|
|
|
|
|
|
|
|
#proc{stack = []} = Proc -> |
|
|
|
|
|
{empty, Proc}; |
|
|
%% All good! |
|
|
%% All good! |
|
|
Proc -> {true, Proc} |
|
|
|
|
|
|
|
|
Proc -> |
|
|
|
|
|
{true, Proc} |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
%% We track a number of different things: |
|
|
%% We track a number of different things: |
|
@ -173,15 +175,12 @@ is_process_profiled(Pid, #state{processes = Procs}) -> |
|
|
%% compared to an imperative language. |
|
|
%% compared to an imperative language. |
|
|
|
|
|
|
|
|
%% Recursive call. Just increase the call count. |
|
|
%% Recursive call. Just increase the call count. |
|
|
handle_call(Pid, MFA, _Source, _Ts, |
|
|
|
|
|
Proc0 = #proc{stack = [Call = #call{mfa = MFA, count = Count} | Stack0]}, |
|
|
|
|
|
State = #state{processes = Procs}) -> |
|
|
|
|
|
|
|
|
handleCall(Pid, MFA, _Source, _Ts, Proc0 = #proc{stack = [Call = #call{mfa = MFA, count = Count} | Stack0]}, State = #state{processes = Procs}) -> |
|
|
Stack = [Call#call{count = Count + 1} | Stack0], |
|
|
Stack = [Call#call{count = Count + 1} | Stack0], |
|
|
Proc = Proc0#proc{stack = Stack}, |
|
|
Proc = Proc0#proc{stack = Stack}, |
|
|
State#state{processes = Procs#{Pid => Proc}}; |
|
|
State#state{processes = Procs#{Pid => Proc}}; |
|
|
%% Non-recursive call. |
|
|
%% Non-recursive call. |
|
|
handle_call(Pid, MFA, Source, Ts, Proc0 = #proc{stack = Stack0}, |
|
|
|
|
|
State = #state{processes = Procs}) -> |
|
|
|
|
|
|
|
|
handleCall(Pid, MFA, Source, Ts, Proc0 = #proc{stack = Stack0}, State = #state{processes = Procs}) -> |
|
|
Stack = [#call{mfa = MFA, source = Source, ts = Ts, self_ts = Ts} | Stack0], |
|
|
Stack = [#call{mfa = MFA, source = Source, ts = Ts, self_ts = Ts} | Stack0], |
|
|
Proc = Proc0#proc{stack = Stack}, |
|
|
Proc = Proc0#proc{stack = Stack}, |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
@ -217,58 +216,44 @@ handle_call(Pid, MFA, Source, Ts, Proc0 = #proc{stack = Stack0}, |
|
|
%% and the return time for the end. Here again we also |
|
|
%% and the return time for the end. Here again we also |
|
|
%% update the sub call times. |
|
|
%% update the sub call times. |
|
|
|
|
|
|
|
|
handle_return_to(Pid, MFA, Ts, Proc0 = #proc{stack = [Current0 | Stack0], mfas = MFAs0}, |
|
|
|
|
|
State = #state{processes = Procs}) -> |
|
|
|
|
|
{Returned0, Stack1} = lists:splitwith( |
|
|
|
|
|
fun(#call{mfa = E}) -> E =/= MFA end, |
|
|
|
|
|
Stack0), |
|
|
|
|
|
|
|
|
handleReturnTo(Pid, MFA, Ts, Proc0 = #proc{stack = [Current0 | Stack0], mfas = MFAs0}, State = #state{processes = Procs}) -> |
|
|
|
|
|
{Returned0, Stack1} = lists:splitwith(fun(#call{mfa = E}) -> E =/= MFA end, Stack0), |
|
|
#call{ts = CurrentTs, self_ts = CurrentSelfTs, self = CurrentSelf} = Current0, |
|
|
#call{ts = CurrentTs, self_ts = CurrentSelfTs, self = CurrentSelf} = Current0, |
|
|
Current = Current0#call{incl = Ts - CurrentTs, self = CurrentSelf + Ts - CurrentSelfTs}, |
|
|
Current = Current0#call{incl = Ts - CurrentTs, self = CurrentSelf + Ts - CurrentSelfTs}, |
|
|
Returned = update_tail_calls([Current | Returned0], Ts), |
|
|
|
|
|
Stack = update_stack(Returned, Stack1, Ts), |
|
|
|
|
|
|
|
|
Returned = updateTailCalls([Current | Returned0], Ts), |
|
|
|
|
|
Stack = updateStack(Returned, Stack1, Ts), |
|
|
%% Save the profile information in the state, potentially flushing it |
|
|
%% Save the profile information in the state, potentially flushing it |
|
|
%% to disk if the stack is empty. |
|
|
%% to disk if the stack is empty. |
|
|
MFAs1 = update_mfas(Returned, MFAs0), |
|
|
|
|
|
MFAs = case Stack of |
|
|
|
|
|
[] -> |
|
|
|
|
|
write_mfas(Pid, MFAs1, State), |
|
|
|
|
|
#{}; |
|
|
|
|
|
_ -> |
|
|
|
|
|
MFAs1 |
|
|
|
|
|
end, |
|
|
|
|
|
|
|
|
MFAs1 = updateMfas(Returned, MFAs0), |
|
|
|
|
|
MFAs = |
|
|
|
|
|
case Stack of |
|
|
|
|
|
[] -> |
|
|
|
|
|
writeMfas(Pid, MFAs1, State), |
|
|
|
|
|
#{}; |
|
|
|
|
|
_ -> |
|
|
|
|
|
MFAs1 |
|
|
|
|
|
end, |
|
|
Proc = Proc0#proc{stack = Stack, mfas = MFAs}, |
|
|
Proc = Proc0#proc{stack = Stack, mfas = MFAs}, |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
|
|
|
|
|
|
|
update_tail_calls([Call], _) -> |
|
|
|
|
|
|
|
|
updateTailCalls([Call], _) -> |
|
|
[Call]; |
|
|
[Call]; |
|
|
update_tail_calls([ |
|
|
|
|
|
Callee = #call{ts = CalleeTs}, |
|
|
|
|
|
Caller0 = #call{ts = CallerTs, self_ts = CallerSelfTs, self = CallerSelf} |
|
|
|
|
|
| Tail], ReturnToTs) -> |
|
|
|
|
|
Caller1 = Caller0#call{ |
|
|
|
|
|
incl = ReturnToTs - CallerTs, |
|
|
|
|
|
self = CallerSelf + CalleeTs - CallerSelfTs |
|
|
|
|
|
}, |
|
|
|
|
|
Caller = update_sub_calls(Callee, Caller1), |
|
|
|
|
|
[Callee | update_tail_calls([Caller | Tail], ReturnToTs)]. |
|
|
|
|
|
|
|
|
updateTailCalls([Callee = #call{ts = CalleeTs}, Caller0 = #call{ts = CallerTs, self_ts = CallerSelfTs, self = CallerSelf} | Tail], ReturnToTs) -> |
|
|
|
|
|
Caller1 = Caller0#call{incl = ReturnToTs - CallerTs, self = CallerSelf + CalleeTs - CallerSelfTs}, |
|
|
|
|
|
Caller = updateSubCalls(Callee, Caller1), |
|
|
|
|
|
[Callee | updateTailCalls([Caller | Tail], ReturnToTs)]. |
|
|
|
|
|
|
|
|
%% Update nothing; there's nothing in the stack. |
|
|
%% Update nothing; there's nothing in the stack. |
|
|
update_stack(_, [], _) -> |
|
|
|
|
|
|
|
|
updateStack(_, [], _) -> |
|
|
[]; |
|
|
[]; |
|
|
%% Update the incl/self value based on the top-level function we return from, |
|
|
%% Update the incl/self value based on the top-level function we return from, |
|
|
%% but only update the sub calls based on the function we directly called. |
|
|
%% but only update the sub calls based on the function we directly called. |
|
|
update_stack(Returned, |
|
|
|
|
|
[Caller0 = #call{self_ts = CallerSelfTs, self = CallerSelf} | Stack], |
|
|
|
|
|
ReturnToTs) -> |
|
|
|
|
|
|
|
|
updateStack(Returned, [Caller0 = #call{self_ts = CallerSelfTs, self = CallerSelf} | Stack], ReturnToTs) -> |
|
|
Callee = #call{ts = CalleeTs} = hd(lists:reverse(Returned)), |
|
|
Callee = #call{ts = CalleeTs} = hd(lists:reverse(Returned)), |
|
|
Caller = Caller0#call{ |
|
|
|
|
|
self_ts = ReturnToTs, |
|
|
|
|
|
self = CallerSelf + CalleeTs - CallerSelfTs |
|
|
|
|
|
}, |
|
|
|
|
|
[update_sub_calls(Callee, Caller) | Stack]. |
|
|
|
|
|
|
|
|
|
|
|
update_sub_calls(Callee = #call{mfa = MFA, incl = CallerIncl, count = CallerCount, |
|
|
|
|
|
wait_incl = CallerWaitIncl}, Caller = #call{calls = SubCalls}) -> |
|
|
|
|
|
|
|
|
Caller = Caller0#call{self_ts = ReturnToTs, self = CallerSelf + CalleeTs - CallerSelfTs}, |
|
|
|
|
|
[updateSubCalls(Callee, Caller) | Stack]. |
|
|
|
|
|
|
|
|
|
|
|
updateSubCalls(Callee = #call{mfa = MFA, incl = CallerIncl, count = CallerCount, wait_incl = CallerWaitIncl}, Caller = #call{calls = SubCalls}) -> |
|
|
case maps:get(MFA, SubCalls, undefined) of |
|
|
case maps:get(MFA, SubCalls, undefined) of |
|
|
%% Add the callee to the subcalls but remove the callee's subcalls |
|
|
%% Add the callee to the subcalls but remove the callee's subcalls |
|
|
%% since we don't need those here. |
|
|
%% since we don't need those here. |
|
@ -289,46 +274,39 @@ update_sub_calls(Callee = #call{mfa = MFA, incl = CallerIncl, count = CallerCoun |
|
|
%% We keep track of how many times the process was scheduled out |
|
|
%% We keep track of how many times the process was scheduled out |
|
|
%% per function, and how long. |
|
|
%% per function, and how long. |
|
|
|
|
|
|
|
|
handle_in(Pid, InTs, Proc0 = #proc{stack = [Current0 | Stack0], out = OutTs}, |
|
|
|
|
|
State = #state{processes = Procs}) -> |
|
|
|
|
|
#call{wait = Wait, wait_incl = WaitIncl, |
|
|
|
|
|
wait_count = WaitCount, wait_count_incl = WaitCountIncl |
|
|
|
|
|
} = Current0, |
|
|
|
|
|
|
|
|
handleIn(Pid, InTs, Proc0 = #proc{stack = [Current0 | Stack0], out = OutTs}, State = #state{processes = Procs}) -> |
|
|
|
|
|
#call{wait = Wait, wait_incl = WaitIncl, wait_count = WaitCount, wait_count_incl = WaitCountIncl} = Current0, |
|
|
ThisWait = InTs - OutTs, |
|
|
ThisWait = InTs - OutTs, |
|
|
%% We increase the wait time for self first. |
|
|
%% We increase the wait time for self first. |
|
|
Current = Current0#call{wait = Wait + ThisWait, wait_incl = WaitIncl + ThisWait, |
|
|
|
|
|
wait_count = WaitCount + 1, wait_count_incl = WaitCountIncl + 1}, |
|
|
|
|
|
|
|
|
Current = Current0#call{wait = Wait + ThisWait, wait_incl = WaitIncl + ThisWait, wait_count = WaitCount + 1, wait_count_incl = WaitCountIncl + 1}, |
|
|
%% And then for the parent calls to include wait time of subcalls. |
|
|
%% And then for the parent calls to include wait time of subcalls. |
|
|
Stack = [ |
|
|
Stack = [ |
|
|
Call#call{wait_incl = ParentWaitIncl + ThisWait, wait_count_incl = ParentWaitCount + 1} |
|
|
Call#call{wait_incl = ParentWaitIncl + ThisWait, wait_count_incl = ParentWaitCount + 1} |
|
|
|| Call = #call{wait_incl = ParentWaitIncl, wait_count_incl = ParentWaitCount} <- Stack0], |
|
|
|
|
|
|
|
|
|| Call = #call{wait_incl = ParentWaitIncl, wait_count_incl = ParentWaitCount} <- Stack0 |
|
|
|
|
|
], |
|
|
Proc = Proc0#proc{stack = [Current | Stack], out = undefined}, |
|
|
Proc = Proc0#proc{stack = [Current | Stack], out = undefined}, |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
|
|
|
|
|
|
|
handle_out(Pid, Ts, Proc0 = #proc{out = undefined}, |
|
|
|
|
|
State = #state{processes = Procs}) -> |
|
|
|
|
|
|
|
|
handleOut(Pid, Ts, Proc0 = #proc{out = undefined}, State = #state{processes = Procs}) -> |
|
|
Proc = Proc0#proc{out = Ts}, |
|
|
Proc = Proc0#proc{out = Ts}, |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
|
State#state{processes = Procs#{Pid => Proc}}. |
|
|
|
|
|
|
|
|
%% Update the profiling information we currently hold. |
|
|
%% Update the profiling information we currently hold. |
|
|
update_mfas([], MFAs) -> |
|
|
|
|
|
|
|
|
updateMfas([], MFAs) -> |
|
|
MFAs; |
|
|
MFAs; |
|
|
update_mfas([Call = #call{mfa = MFA, incl = Incl, self = Self, wait = Wait, wait_incl = WaitIncl, |
|
|
|
|
|
wait_count = WaitCount, wait_count_incl = WaitCountIncl, |
|
|
|
|
|
count = Count, calls = SubCalls} | Tail], MFAs) -> |
|
|
|
|
|
|
|
|
updateMfas([Call = #call{mfa = MFA, incl = Incl, self = Self, wait = Wait, wait_incl = WaitIncl, wait_count = WaitCount, wait_count_incl = WaitCountIncl, count = Count, calls = SubCalls} | Tail], MFAs) -> |
|
|
case MFAs of |
|
|
case MFAs of |
|
|
#{MFA := AggCall0 = #call{incl = AggIncl, self = AggSelf, wait = AggWait, wait_incl = AggWaitIncl, |
|
|
|
|
|
wait_count = AggWaitCount, wait_count_incl = AggWaitCountIncl, |
|
|
|
|
|
count = AggCount, calls = AggSubCalls0}} -> |
|
|
|
|
|
AggSubCalls = update_mfas(maps:values(SubCalls), AggSubCalls0), |
|
|
|
|
|
|
|
|
#{MFA := AggCall0 = #call{incl = AggIncl, self = AggSelf, wait = AggWait, wait_incl = AggWaitIncl, wait_count = AggWaitCount, wait_count_incl = AggWaitCountIncl, count = AggCount, calls = AggSubCalls0}} -> |
|
|
|
|
|
AggSubCalls = updateMfas(maps:values(SubCalls), AggSubCalls0), |
|
|
AggCall = AggCall0#call{incl = Incl + AggIncl, self = Self + AggSelf, |
|
|
AggCall = AggCall0#call{incl = Incl + AggIncl, self = Self + AggSelf, |
|
|
wait = Wait + AggWait, wait_incl = WaitIncl + AggWaitIncl, |
|
|
wait = Wait + AggWait, wait_incl = WaitIncl + AggWaitIncl, |
|
|
wait_count = WaitCount + AggWaitCount, |
|
|
wait_count = WaitCount + AggWaitCount, |
|
|
wait_count_incl = WaitCountIncl + AggWaitCountIncl, |
|
|
wait_count_incl = WaitCountIncl + AggWaitCountIncl, |
|
|
count = Count + AggCount, calls = AggSubCalls}, |
|
|
|
|
|
update_mfas(Tail, MFAs#{MFA => AggCall}); |
|
|
|
|
|
|
|
|
count = Count + AggCount, calls = AggSubCalls |
|
|
|
|
|
}, |
|
|
|
|
|
updateMfas(Tail, MFAs#{MFA => AggCall}); |
|
|
_ -> |
|
|
_ -> |
|
|
update_mfas(Tail, MFAs#{MFA => Call}) |
|
|
|
|
|
|
|
|
updateMfas(Tail, MFAs#{MFA => Call}) |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
%% The callgrind format is documented at http://valgrind.org/docs/manual/cl-format.html |
|
|
%% The callgrind format is documented at http://valgrind.org/docs/manual/cl-format.html |
|
@ -338,7 +316,7 @@ update_mfas([Call = #call{mfa = MFA, incl = Incl, self = Self, wait = Wait, wait |
|
|
%% |
|
|
%% |
|
|
%% The option 'scope' can be used to enable per process tracking. |
|
|
%% The option 'scope' can be used to enable per process tracking. |
|
|
|
|
|
|
|
|
write_header(#state{output_device = OutDevice, opts = #{running := true}}) -> |
|
|
|
|
|
|
|
|
writeHeader(#state{output_device = OutDevice, opts = #{running := true}}) -> |
|
|
ok = file:write(OutDevice, |
|
|
ok = file:write(OutDevice, |
|
|
"# callgrind format\n" |
|
|
"# callgrind format\n" |
|
|
"events: Total Active Wait WaitCount\n" |
|
|
"events: Total Active Wait WaitCount\n" |
|
@ -347,45 +325,48 @@ write_header(#state{output_device = OutDevice, opts = #{running := true}}) -> |
|
|
"event: Wait : Wait time in microseconds (scheduled out)\n" |
|
|
"event: Wait : Wait time in microseconds (scheduled out)\n" |
|
|
"event: WaitCount : Number of times the process was scheduled out\n" |
|
|
"event: WaitCount : Number of times the process was scheduled out\n" |
|
|
"\n"); |
|
|
"\n"); |
|
|
write_header(#state{output_device = OutDevice}) -> |
|
|
|
|
|
|
|
|
writeHeader(#state{output_device = OutDevice}) -> |
|
|
ok = file:write(OutDevice, |
|
|
ok = file:write(OutDevice, |
|
|
"# callgrind format\n" |
|
|
"# callgrind format\n" |
|
|
"events: Total\n" |
|
|
"events: Total\n" |
|
|
"event: Total : Total time in microseconds\n" |
|
|
"event: Total : Total time in microseconds\n" |
|
|
"\n"). |
|
|
"\n"). |
|
|
|
|
|
|
|
|
write_mfas(Pid, MFAs, State) -> |
|
|
|
|
|
_ = [write_call(Pid, Call, State) || Call <- maps:values(MFAs)], |
|
|
|
|
|
|
|
|
writeMfas(Pid, MFAs, State) -> |
|
|
|
|
|
_ = [writeCall(Pid, Call, State) || Call <- maps:values(MFAs)], |
|
|
ok. |
|
|
ok. |
|
|
|
|
|
|
|
|
write_call(Pid, #call{mfa = MFA, source = {Source, LN}, self = Self, wait = Wait, |
|
|
|
|
|
wait_count = WaitCount, calls = Calls0}, |
|
|
|
|
|
#state{output_device = OutDevice, opts = Opts}) -> |
|
|
|
|
|
|
|
|
writeCall(Pid, #call{mfa = MFA, source = {Source, LN}, self = Self, wait = Wait, wait_count = WaitCount, calls = Calls0}, #state{output_device = OutDevice, opts = Opts}) -> |
|
|
Calls = maps:values(Calls0), |
|
|
Calls = maps:values(Calls0), |
|
|
Ob = case Opts of |
|
|
|
|
|
#{scope := per_process} -> |
|
|
|
|
|
["ob=", io_lib:write(Pid), "\n"]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
RunningCosts = case Opts of |
|
|
|
|
|
#{running := true} -> |
|
|
|
|
|
[ |
|
|
|
|
|
" ", integer_to_list(Self - Wait), |
|
|
|
|
|
" ", integer_to_list(Wait), |
|
|
|
|
|
" ", integer_to_list(WaitCount) |
|
|
|
|
|
]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
file:write(OutDevice, [Ob, |
|
|
|
|
|
"fl=", Source, "\n" |
|
|
|
|
|
|
|
|
Ob = |
|
|
|
|
|
case Opts of |
|
|
|
|
|
#{scope := per_process} -> |
|
|
|
|
|
["ob=", io_lib:write(Pid), "\n"]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
RunningCosts = |
|
|
|
|
|
case Opts of |
|
|
|
|
|
#{running := true} -> |
|
|
|
|
|
[ |
|
|
|
|
|
" ", integer_to_list(Self - Wait), |
|
|
|
|
|
" ", integer_to_list(Wait), |
|
|
|
|
|
" ", integer_to_list(WaitCount) |
|
|
|
|
|
]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
file:write(OutDevice, |
|
|
|
|
|
[ |
|
|
|
|
|
Ob, |
|
|
|
|
|
"fl=", Source, "\n" |
|
|
"fn=", atom_to_list(MFA), "\n", |
|
|
"fn=", atom_to_list(MFA), "\n", |
|
|
integer_to_list(LN), " ", integer_to_list(Self), RunningCosts, "\n", |
|
|
|
|
|
format_subcalls(LN, Calls, Opts), |
|
|
|
|
|
"\n"]). |
|
|
|
|
|
|
|
|
integer_to_list(LN), " ", integer_to_list(Self), RunningCosts, "\n", |
|
|
|
|
|
formatSubcalls(LN, Calls, Opts), |
|
|
|
|
|
"\n" |
|
|
|
|
|
]). |
|
|
|
|
|
|
|
|
format_subcalls(_, [], _) -> |
|
|
|
|
|
|
|
|
formatSubcalls(_, [], _) -> |
|
|
[]; |
|
|
[]; |
|
|
%% @todo We don't need to write the filename for functions in the same module. |
|
|
%% @todo We don't need to write the filename for functions in the same module. |
|
|
%% @todo We also don't want to put the full file name; instead we should remove |
|
|
%% @todo We also don't want to put the full file name; instead we should remove |
|
@ -393,39 +374,41 @@ format_subcalls(_, [], _) -> |
|
|
%% |
|
|
%% |
|
|
%% We only look at where the function is defined, we can't really get |
|
|
%% We only look at where the function is defined, we can't really get |
|
|
%% the actual line number where the call happened, unfortunately. |
|
|
%% the actual line number where the call happened, unfortunately. |
|
|
format_subcalls(LN, [#call{mfa = MFA, source = {Source, TargetLN}, incl = Incl, |
|
|
|
|
|
wait_incl = Wait, wait_count_incl = WaitCount, count = Count, calls = _Calls} | Tail], Opts) -> |
|
|
|
|
|
RunningCosts = case Opts of |
|
|
|
|
|
#{running := true} -> |
|
|
|
|
|
[ |
|
|
|
|
|
" ", integer_to_list(Incl - Wait), |
|
|
|
|
|
" ", integer_to_list(Wait), |
|
|
|
|
|
" ", integer_to_list(WaitCount) |
|
|
|
|
|
]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
[[ |
|
|
|
|
|
"cfi=", Source, "\n" |
|
|
|
|
|
|
|
|
formatSubcalls(LN, [#call{mfa = MFA, source = {Source, TargetLN}, incl = Incl, wait_incl = Wait, wait_count_incl = WaitCount, count = Count, calls = _Calls} | Tail], Opts) -> |
|
|
|
|
|
RunningCosts = |
|
|
|
|
|
case Opts of |
|
|
|
|
|
#{running := true} -> |
|
|
|
|
|
[ |
|
|
|
|
|
" ", integer_to_list(Incl - Wait), |
|
|
|
|
|
" ", integer_to_list(Wait), |
|
|
|
|
|
" ", integer_to_list(WaitCount) |
|
|
|
|
|
]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
[ |
|
|
|
|
|
[ |
|
|
|
|
|
"cfi=", Source, "\n" |
|
|
"cfn=", atom_to_list(MFA), "\n" |
|
|
"cfn=", atom_to_list(MFA), "\n" |
|
|
"calls=", integer_to_list(Count), " ", integer_to_list(TargetLN), "\n", |
|
|
"calls=", integer_to_list(Count), " ", integer_to_list(TargetLN), "\n", |
|
|
integer_to_list(LN), " ", integer_to_list(Incl), RunningCosts, "\n" |
|
|
|
|
|
] | format_subcalls(LN, Tail, Opts)]. |
|
|
|
|
|
|
|
|
integer_to_list(LN), " ", integer_to_list(Incl), RunningCosts, "\n" |
|
|
|
|
|
] | formatSubcalls(LN, Tail, Opts) |
|
|
|
|
|
]. |
|
|
|
|
|
|
|
|
convert_mfa(undefined) -> |
|
|
|
|
|
|
|
|
convertMfa(undefined) -> |
|
|
undefined; |
|
|
undefined; |
|
|
convert_mfa({M0, F0, A0}) -> |
|
|
|
|
|
|
|
|
convertMfa({M0, F0, A0}) -> |
|
|
M = atom_to_binary(M0, latin1), |
|
|
M = atom_to_binary(M0, latin1), |
|
|
F = atom_to_binary(F0, latin1), |
|
|
F = atom_to_binary(F0, latin1), |
|
|
A = integer_to_binary(A0), |
|
|
A = integer_to_binary(A0), |
|
|
binary_to_atom(<<M/binary, $:, F/binary, $/, A/binary>>, latin1). |
|
|
binary_to_atom(<<M/binary, $:, F/binary, $/, A/binary>>, latin1). |
|
|
|
|
|
|
|
|
find_source(MFA, State0 = #state{sources = Cache}) -> |
|
|
|
|
|
|
|
|
findSource(MFA, State0 = #state{sources = Cache}) -> |
|
|
case Cache of |
|
|
case Cache of |
|
|
#{MFA := Source} -> |
|
|
#{MFA := Source} -> |
|
|
{Source, State0}; |
|
|
{Source, State0}; |
|
|
_ -> |
|
|
_ -> |
|
|
State = #state{sources = #{MFA := Source}} = cache_module(MFA, State0), |
|
|
|
|
|
|
|
|
State = #state{sources = #{MFA := Source}} = cacheModule(MFA, State0), |
|
|
{Source, State} |
|
|
{Source, State} |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
@ -443,7 +426,7 @@ find_source(MFA, State0 = #state{sources = Cache}) -> |
|
|
%% |
|
|
%% |
|
|
%% While this is an expensive operation, we cache the result |
|
|
%% While this is an expensive operation, we cache the result |
|
|
%% and therefore this function will only be called once per module. |
|
|
%% and therefore this function will only be called once per module. |
|
|
cache_module(MFA = {Module, _, _}, State0 = #state{sources = Cache}) -> |
|
|
|
|
|
|
|
|
cacheModule(MFA = {Module, _, _}, State0 = #state{sources = Cache}) -> |
|
|
try |
|
|
try |
|
|
%% If the module is in the path, we can simply query |
|
|
%% If the module is in the path, we can simply query |
|
|
%% it for the source file. |
|
|
%% it for the source file. |
|
@ -454,7 +437,7 @@ cache_module(MFA = {Module, _, _}, State0 = #state{sources = Cache}) -> |
|
|
%% This allows different users to point to the |
|
|
%% This allows different users to point to the |
|
|
%% same source at different locations on their machine. |
|
|
%% same source at different locations on their machine. |
|
|
{_, Src} = lists:keyfind(source, 1, Info), |
|
|
{_, Src} = lists:keyfind(source, 1, Info), |
|
|
cache_module(MFA, State0, Src) |
|
|
|
|
|
|
|
|
cacheModule(MFA, State0, Src) |
|
|
catch _:_ -> |
|
|
catch _:_ -> |
|
|
%% Either the module was not found, or it doesn't |
|
|
%% Either the module was not found, or it doesn't |
|
|
%% have a 'source' key in the compile info. |
|
|
%% have a 'source' key in the compile info. |
|
@ -466,7 +449,7 @@ cache_module(MFA = {Module, _, _}, State0 = #state{sources = Cache}) -> |
|
|
State0#state{sources = Cache#{MFA => {atom_to_list(Module) ++ ".erl", 1}}} |
|
|
State0#state{sources = Cache#{MFA => {atom_to_list(Module) ++ ".erl", 1}}} |
|
|
end. |
|
|
end. |
|
|
|
|
|
|
|
|
cache_module(MFA = {Module, _, _}, State = #state{sources = Cache0}, Src) -> |
|
|
|
|
|
|
|
|
cacheModule(MFA = {Module, _, _}, State = #state{sources = Cache0}, Src) -> |
|
|
{Module, Beam, _} = code:get_object_code(Module), |
|
|
{Module, Beam, _} = code:get_object_code(Module), |
|
|
{ok, {Module, Chunks}} = beam_lib:chunks(Beam, [abstract_code]), |
|
|
{ok, {Module, Chunks}} = beam_lib:chunks(Beam, [abstract_code]), |
|
|
[{abstract_code, {raw_abstract_v1, Forms}}] = Chunks, |
|
|
[{abstract_code, {raw_abstract_v1, Forms}}] = Chunks, |
|
@ -475,8 +458,9 @@ cache_module(MFA = {Module, _, _}, State = #state{sources = Cache0}, Src) -> |
|
|
%% We cannot currently retrieve line number information |
|
|
%% We cannot currently retrieve line number information |
|
|
%% for list comprehensions and funs. We therefore |
|
|
%% for list comprehensions and funs. We therefore |
|
|
%% cache the correct file with line number set to 1. |
|
|
%% cache the correct file with line number set to 1. |
|
|
Cache = case Cache1 of |
|
|
|
|
|
#{MFA := _} -> Cache1; |
|
|
|
|
|
_ -> Cache1#{MFA => {Src, 1}} |
|
|
|
|
|
end, |
|
|
|
|
|
|
|
|
Cache = |
|
|
|
|
|
case Cache1 of |
|
|
|
|
|
#{MFA := _} -> Cache1; |
|
|
|
|
|
_ -> Cache1#{MFA => {Src, 1}} |
|
|
|
|
|
end, |
|
|
State#state{sources = Cache}. |
|
|
State#state{sources = Cache}. |