瀏覽代碼

ft: callGrind添加

master
SisMaker 3 年之前
父節點
當前提交
f43683b689
共有 2 個檔案被更改,包括 184 行新增200 行删除
  1. +182
    -198
      src/callgrind/tpCallGrind.erl
  2. +2
    -2
      test/lg_SUITE.erl

+ 182
- 198
src/callgrind/tpCallGrind.erl 查看文件

@ -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}.

+ 2
- 2
test/lg_SUITE.erl 查看文件

@ -42,7 +42,7 @@ callgrind_running(Config) ->
#{mode => profile, running => true}), #{mode => profile, running => true}),
do_callgrind_running(), do_callgrind_running(),
eTpf:stop(), eTpf:stop(),
tpCallGrind:profile_many(
tpCallGrind:pfm(
PrivDir ++ "/callgrind_running.lz4.*", PrivDir ++ "/callgrind_running.lz4.*",
PrivDir ++ "/callgrind_running.out", PrivDir ++ "/callgrind_running.out",
#{running => true}), #{running => true}),
@ -80,7 +80,7 @@ callgrind_running_cycle(Config) ->
#{mode => profile, running => true}), #{mode => profile, running => true}),
do_callgrind_running_cycle(), do_callgrind_running_cycle(),
eTpf:stop(), eTpf:stop(),
tpCallGrind:profile_many(
tpCallGrind:pfm(
PrivDir ++ "/callgrind_running_cycle.lz4.*", PrivDir ++ "/callgrind_running_cycle.lz4.*",
PrivDir ++ "/callgrind_running_cycle.out", PrivDir ++ "/callgrind_running_cycle.out",
#{running => true}), #{running => true}),

Loading…
取消
儲存