%%%-------------------------------------------------------------------
%%% @author Torbjorn Tornkvist <kruskakli@gmail.com>
%%% @copyright (C) 2017, Torbjorn Tornkvist
%%% @doc The `edbg' tracer.
%%%
%%% The function {@link edbg:fstart/2} takes one argument containing
%%% the list of modules we want to trace, and a second argument
%%% containing various options. The trace output will be stored on file.
%%%
%%% So in the example below we want to trace on three modules:
%%% yaws_server, yaws, yaws_config, from the Yaws webserver.
%%% With the `max_msgs' option we restrict the allowed number
%%% of trace messages to 10000.
%%%
%%% ```
%%% 1> edbg:fstart([yaws_server,yaws,yaws_config],[{max_msgs,10000}]).
%%% ok
%%% '''
%%%
%%% Now run some traffic toward yaws and when done, stop the tracing:
%%%
%%% ```
%%% 2> edbg:fstop().
%%% ok
%%% '''
%%%
%%% Here we rely on using the default filename for storing the trace output,
%%% hence we don't have to specify it here when loading the trace info to
%%% be displayed.
%%%
%%% ```
%%% 3> edbg:file().
%%%
%%% (h)elp (a)t [<N>] (d)own (u)p (t)op (b)ottom
%%% (s)how <N> [<ArgN>] (r)etval <N> ra(w) <N>
%%% (pr)etty print record <N> <ArgN>
%%% (f)ind <RegExp> [<ArgN> <ArgRegExp>] (fr) <RetRegExp>
%%% (on)/(off) send_receive | memory
%%% (p)agesize <N> (q)uit
%%% (set) <Var> <N> [<ArgN>] (let) <Var> <Expr>
%%% (eval) <Expr> (xall/xnall) <Mod>
%%% '''
%%%
%%% As can be seen, we first get a compact help text showing what commands
%%% we can use. Then follows the beginning of the trace output.
%%% Each line is prefixed with a number that we use for reference.
%%% The indentation shows the depth of the call chain.
%%%
%%% ```
%%% 0: <0.255.0> yaws_server:gserv_loop/4
%%% 1: <0.258.0> yaws_server:gserv_loop/4
%%% 2: <0.233.0> yaws:month/1
%%% 4: <0.259.0> yaws_server:peername/2
%%% 6: <0.258.0> yaws_server:close_accepted_if_max/2
%%% 8: <0.258.0> yaws_server:acceptor/1
%%% 10: <0.258.0> yaws_server:gserv_loop/4
%%% 11: <0.281.0> yaws_server:acceptor0/2
%%% 12: <0.281.0> yaws_server:do_accept/1
%%% ...snip...
%%% '''
%%%
%%% As you can see, we get a pretty output where we can follow
%%% the chain of execution without drowning in output which would
%%% be the case if we should have displayed the contents of all
%%% the arguments to the functions.
%%%
%%% Instead, we can now inspect a particular call of interest,
%%% let's say line 4; we use the `(s)how' command to display
%%% the function clause heads in order to help us decide which
%%% argument to inspect.
%%%
%%% ```
%%% tlist> s 4
%%%
%%% Call: yaws_server:peername/2
%%% -----------------------------------
%%%
%%% peername(CliSock, ssl) ->
%%%
%%% peername(CliSock, nossl) ->
%%%
%%% -----------------------------------
%%% '''
%%%
%%% To show what the second argument contained,
%%% we add 2 to the show command:
%%%
%%% ```
%%% tlist> s 4 2
%%%
%%% Call: yaws_server:peername/2 , argument 2:
%%% -----------------------------------
%%% nossl
%%% '''
%%%
%%% We can also see what the function returned:
%%%
%%% ```
%%% tlist> r 4
%%%
%%% Call: yaws_server:peername/2 , return value:
%%% -----------------------------------
%%% {{127,0,0,1},35871}
%%% '''
%%%
%%% To display (again) the function call chain,
%%% you use the `a(t)' command. With no arguments it will just
%%% re-display the trace output. If you want to go to a particular
%%% line you just give that as an argument.
%%% Example, go to line 10 in the example above:
%%%
%%% ```
%%% tlist> a 10
%%% 10: <0.258.0> yaws_server:gserv_loop/4
%%% 11: <0.281.0> yaws_server:acceptor0/2
%%% 12: <0.281.0> yaws_server:do_accept/1
%%% 13: <0.259.0> yaws_server:aloop/4
%%% ...snip...
%%% '''
%%%
%%% To change the number of lines shown of the trace output.
%%% Set it with the `p(age)' command.
%%% Example, display (roughly) 50 lines:
%%%
%%% ```
%%% tlist> p 50
%%% '''
%%%
%%% The amount of trace output can be huge so we can search
%%% for a particular function call that we are interested in.
%%% Note that you can specify a RegExp for searching among
%%% the Mod:Fun calls.
%%%
%%% ```
%%% tlist> f yaws:decode_b
%%% 32: <0.537.0> yaws:decode_base64/1
%%% 33: <0.537.0> yaws:decode_base64/2
%%% 34: <0.537.0> yaws:d/1
%%% ...snip...
%%% '''
%%%
%%% We can also search in a particular argument of a particular
%%% function call. Here the second argument of yaws:setopts should
%%% contain the string: 'packet_size':
%%%
%%% ```
%%% tlist> f yaws:setopts 2 packet_size
%%% 22: <0.537.0> yaws:setopts/3
%%% 24: <0.537.0> yaws:do_recv/3
%%% 26: <0.537.0> yaws:http_collect_headers/5
%%% ...snip...
%%% '''
%%%
%%% We can now verify that it found it:
%%%
%%% ```
%%% tlist> s 22 2
%%%
%%% Call: yaws:setopts/3 , argument 2:
%%% -----------------------------------
%%% [{packet,httph},{packet_size,16384}]
%%% '''
%%%
%%% To search among the return values we use the 'fr' command:
%%%
%%% ```
%%% tlist> fr GET
%%% 184: <0.537.0> yaws:make_allow_header/1
%%% 187: <0.537.0> yaws_server:deliver_accumulated/1
%%% 188: <0.537.0> yaws:outh_get_content_encoding/0
%%% 190: <0.537.0> yaws:outh_set_content_encoding/1
%%% ...snip...
%%% '''
%%%
%%% We can now verify that it found it:
%%%
%%% ```
%%% tlist> r 184
%%%
%%% Call: yaws:make_allow_header/1 , return value:
%%% -----------------------------------
%%% ["Allow: GET, POST, OPTIONS, HEAD\r\n"]
%%% '''
%%%
%%% To see more examples visit the `edbg' wiki at:
%%% [https://github.com/etnt/edbg/wiki/Tracing]
%%%
%%% @end
%%% Created : 4 Sep 2017 by Torbjorn Tornkvist <kruskakli@gmail.com>
%%%-------------------------------------------------------------------
-module(edbg_tracer).
-export([file/0
, file/1
, fhelp/0
, fstart/0
, fstart/1
, fstart/2
, fstop/0
, get_traced_pid/0
, lts/0
, send/2
, start_my_tracer/0
, tlist/0
, xfile/0
, xfile/1
]).
-import(edbg_file_tracer,
[add_mf_f/1
, cfg_file_f/1
, dump_output_eager_f/0
, dump_output_lazy_f/0
, fname/2
, get_config/0
, get_trace_spec/0
, log_file_f/1
, max_msgs_f/1
, memory_f/0
, mname/2
, monotonic_ts_f/0
, new_mf/0
, send_receive_f/0
, set_config/2
, set_on_f/1
, start_trace/0
, stop_trace/0
, trace_spec_f/1
, trace_time_f/1
]).
%% Internal export
-export([tloop/3,
ploop/1,
rloop/2
]).
-include("edbg_trace.hrl").
%%-define(TEST, true).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-define(mytracer, mytracer).
-define(inside(At,Cur,Page), ((Cur >=At) andalso (Cur =< (At+Page)))).
-define(inside_vl(At,Cur,VL,Page), ((Cur >=At) andalso (VL =< Page))).
-record(tlist,
{
level = maps:new(), % Key=<pid> , Val=<level>
at = 1,
current = 1,
page = 20,
send_receive = true, % do (not) show send/receive msgs
memory = true, % do (not) show memory info
bs = erl_eval:new_bindings(),
%% -- num of visible lines --
%% If display of send/receive msgs is turned off
%% then this counter keeps track of how many lines
%% we are currently displaying, making it possible
%% to fill the 'page'.
vlines = 0
}).
-record(t, {
trace_max = 10000,
tracer,
elixir = false
}).
%% @private
start_my_tracer() ->
case whereis(?mytracer) of
Pid when is_pid(Pid) ->
Pid;
_ ->
Pid = spawn(fun() -> tinit(#t{}) end),
register(?mytracer, Pid),
Pid
end.
%% @private
fhelp() ->
S = "\n"
"edbg:fstart(ModFunList, Opts)\n"
"edbg:fstop()\n"
"edbg:file()\n"
"edbg:file(FileName)\n"
"edbg:fpid(Pid)\n"
"edbg:fpid(Pid, Opts)\n"
"\n"
"ModFunList is a list of module names (atoms) or tuples {ModName, FunName}.\n"
"\n"
"Opts is a list of option tuples:\n"
"\n"
"{log_file, FileName} : file where to store trace output\n"
" default is: edbg.trace_result\n"
"{cfg_file, FileName} : file where to store config\n"
" default is: edbg_trace.config\n"
" use 'false' to turn it off\n"
"{max_msgs, MaxNumOfMsgs} : max number of trace messages\n"
" default = 1000\n"
"{trace_time, Seconds} : max time to trace\n"
" default = 10 seconds\n"
"{trace_spec, Spec} : see the erlang:trace/3 docs\n"
" default = all\n"
"dump_output_eager : trace output goes to file often\n"
"dump_output_lazy : trace output goes to file when done (default)\n"
"monotonic_ts : show the elapsed monotonic nano seconds\n"
"send_receive : trace send/receive messages from 'known' pids\n"
"memory : track the memory usage of the 'known' pids\n",
io:format("~s~n",[S]).
%% dbg:tracer(process,{fun(Trace,N) ->
%% io:format("TRACE (#~p): ~p~n",[N,Trace]),
%% N+1
%% end, 0}).
%%dbg:p(all,clear).
%%dbg:p(all,[c]).
%% @private
file() ->
file("./edbg.trace_result").
%% @private
file(Fname) ->
file(Fname, false).
%% @private
xfile() ->
xfile("./edbg.trace_result").
%% @private
xfile(Fname) ->
file(Fname, true).
file(Fname, IsElixir) ->
catch stop_trace(),
catch edbg_file_tracer:stop(),
try file:read_file(Fname) of
{ok, Tdata} ->
call(start_my_tracer(), {elixir, IsElixir}),
%% We expect Tdata to be a list of trace tuples as
%% a binary in the external term form.
call(start_my_tracer(), {load_trace_data,
binary_to_term(Tdata)}),
tlist();
Error ->
Error
catch
_:Err ->
{error, Err}
end.
%% @private
fstart() ->
edbg_file_tracer:start(),
edbg_file_tracer:load_config(),
start_trace().
%% @private
fstart(ModFunList) ->
fstart(ModFunList, []).
%% @private
fstart(ModFunList, Options)
when is_list(ModFunList) andalso
is_list(Options) ->
edbg_file_tracer:start(),
MF = new_mf(),
MFs = lists:foldr(fun({Mname,Fname}, Acc) ->
[add_mf_f(fname(mname(MF, Mname), Fname))|Acc];
(Mname, Acc) when is_atom(Mname) ->
[add_mf_f(mname(MF, Mname))|Acc];
(X, Acc) ->
io:format("Ignoring ModFun: ~p~n",[X]),
Acc
end, [], ModFunList),
Opts = lists:foldr(fun({log_file, Lname}, Acc) ->
[log_file_f(Lname)|Acc];
({cfg_file, Lname}, Acc) ->
[cfg_file_f(Lname)|Acc];
({max_msgs, Max}, Acc) ->
[max_msgs_f(Max)|Acc];
({trace_time, Time}, Acc) ->
[trace_time_f(Time)|Acc];
({trace_spec, Spec}, Acc) ->
[trace_spec_f(Spec)|Acc];
(dump_output_lazy, Acc) ->
[dump_output_lazy_f()|Acc];
(dump_output_eager, Acc) ->
[dump_output_eager_f()|Acc];
(monotonic_ts, Acc) ->
[monotonic_ts_f()|Acc];
(send_receive, Acc) ->
[send_receive_f()|Acc];
(memory, Acc) ->
[memory_f()|Acc];
(set_on_spawn = K, Acc) ->
[set_on_f(K)|Acc];
(set_on_first_spawn = K, Acc) ->
[set_on_f(K)|Acc];
(set_on_link = K, Acc) ->
[set_on_f(K)|Acc];
(set_on_first_link = K, Acc) ->
[set_on_f(K)|Acc];
(X, Acc) ->
io:format("Ignoring Option: ~p~n",[X]),
Acc
end, [], Options),
set_config(MFs++Opts, get_config()),
start_trace().
%% @private
fstop() ->
edbg_file_tracer:stop_trace(),
edbg_file_tracer:stop().
%% @private
get_traced_pid() ->
case get_trace_spec() of
Pid when is_pid(Pid) -> Pid;
_ -> undefined
end.
call(MyTracer, Msg) ->
MyTracer ! {self(), Msg},
receive
{MyTracer, Result} ->
Result
end.
%% @private
lts() ->
{ok,[X]} = file:consult("trace.edbg"),
call(start_my_tracer(), X).
%% @private
tlist() ->
Self = self(),
Prompt = spawn_link(fun() -> prompt(Self) end),
print_help(),
?mytracer ! at,
ploop(Prompt).
%% @private
ploop(Prompt) ->
receive
{'EXIT', Prompt, _} ->
true;
quit ->
true;
_ ->
?MODULE:ploop(Prompt)
end.
prompt(Pid) when is_pid(Pid) ->
Prompt = "tlist> ",
rloop(Pid, Prompt).
%% @private
rloop(Pid, Prompt) ->
case string:tokens(b2l(io:get_line(Prompt)), "\n") of
["eval"++X] -> xeval(?mytracer, X);
["xnall"++X] -> xnall(?mytracer, X);
["xall"++X] -> xall(?mytracer, X);
["let"++X] -> xlet(?mytracer, X);
["set"++X] -> xset(?mytracer, X);
["off"++X] -> off(?mytracer, X);
["on"++X] -> on(?mytracer, X);
["pr"++X] -> show_record(?mytracer, X);
["fr"++X] -> find_retv(?mytracer, X);
["f"++X] -> find(?mytracer, X);
["d"++_] -> ?mytracer ! down;
["u"++_] -> ?mytracer ! up;
["t"++_] -> ?mytracer ! top;
["b"++_] -> ?mytracer ! bottom;
["a"++X] -> at(?mytracer, X);
["s"++X] -> show(?mytracer, X);
["r"++X] -> show_return(?mytracer, X);
["w"++X] -> show_raw(?mytracer, X);
["p"++X] -> set_page(?mytracer, X);
["h"++_] -> print_help();
["q"++_] ->
?mytracer ! quit,
Pid ! quit,
exit(normal);
_X ->
?info_msg("prompt got: ~p~n",[_X])
end,
?MODULE:rloop(Pid, Prompt).
b2l(B) when is_binary(B) ->
erlang:binary_to_list(B);
b2l(L) when is_list(L) ->
L.
%% Find a match among the return values
find_retv(Pid, X) ->
Xstr = string:strip(X),
Pid ! {find, {ret, Xstr}}.
%% Search by Regexp
find(Pid, X) ->
try
Xstr = string:strip(X),
{Str,Args} = find_args(Xstr),
Pid ! {find, {rexp, Str, Args}}
catch
_:_ -> false
end.
find_args(AStr) ->
find_args(AStr, []).
find_args([$\\,$\\|T], Acc) ->
find_args(T, [$\\|Acc]);
find_args([$\\,$\s|T], Acc) ->
find_args(T, [$\s|Acc]);
find_args([$\s|T], Acc) ->
Str = lists:reverse(Acc),
case string:tokens(T, " ") of
[An,Av] ->
{Str, {list_to_integer(An),Av}};
_ ->
{Str, undefined}
end;
find_args([H|T], Acc) ->
find_args(T, [H|Acc]);
find_args([], Acc) ->
Str = lists:reverse(Acc),
{Str, undefined}.
-ifdef(EUNIT).
find_args_test_() ->
[?_assertMatch({"lists:rev", undefined},
find_args("lists:rev")),
?_assertMatch({"lists:rev", {1,"rune"}},
find_args("lists:rev 1 rune")),
?_assertMatch({"abc def", undefined},
find_args("abc\\ def")),
?_assertMatch({"abc def", {2,"rune"}},
find_args("abc\\ def 2 rune"))
].
-endif.
on(Pid, X) ->
case string:strip(X) of
"send_receive" ->
Pid ! {on, send_receive};
"memory" ->
Pid ! {on, memory};
_ ->
false
end.
off(Pid, X) ->
case string:strip(X) of
"send_receive" ->
Pid ! {off, send_receive};
"memory" ->
Pid ! {off, memory};
_ ->
false
end.
%% Unload/Remove the current a module and re-load it from the code path!
xnall(_Pid, X) ->
try
{ModStr, _} = get_first_token(string:strip(X, left)),
Module = list_to_atom(ModStr),
reload_module(Module),
c:m(Module),
true
catch
_:_ -> false
end.
reload_module(Module) ->
case code:delete(Module) of
false ->
code:purge(Module),
code:delete(Module);
true ->
true
end,
{module, Module} = code:load_file(Module).
%% Re-Compile and load a module with the export-all compiler option!
xall(_Pid, X) ->
try
{ModStr, _} = get_first_token(string:strip(X, left)),
Module = list_to_atom(ModStr),
recompile_as_export_all(Module),
c:m(Module),
true
catch
_:_ -> false
end.
recompile_as_export_all(Module) ->
%%Cs = Module:module_info(compile),
%%{source, SrcFname} = lists:keyfind(source, 1, Cs),
SrcFname = edbg:find_source(Module),
File = filename:rootname(SrcFname, ".erl"),
{ok, Module, Code} = compile:file(File, [export_all,binary]),
code:load_binary(Module, File, Code).
xeval(Pid, X) ->
try
Pid ! {xeval, X}
catch
_:_ -> false
end.
xlet(Pid, X) ->
try
{Var, ExprStr} = get_first_token(string:strip(X, left)),
Pid ! {xlet, Var, ExprStr}
catch
_:_ -> false
end.
get_first_token(Str) ->
string:take(Str, " ", true, leading).
xset(Pid, X) ->
try
case string:tokens(string:strip(X), " ") of
[Var,A] ->
%% Bind Var to Return-Value
Pid ! {xset, Var, list_to_integer(A)};
[Var,A,B] ->
%% Bind Var to Argument-Value
Pid ! {xset, Var, list_to_integer(A), list_to_integer(B)};
[Var,A,B,R] ->
%% Bind Var to a record field in the Argument-Value
%% Example: string:tokens("#arg.client_ip_port","#.").
case string:tokens(string:strip(R), "#.") of
[Record,Field] ->
Pid ! {xset, Var,
list_to_integer(A),list_to_integer(B),
Record, Field};
_ ->
?info_msg("~ncould not parse record field: ~p~n",[R]),
Pid ! {xset, Var,
list_to_integer(A), list_to_integer(B)}
end
end
catch
_:_ -> false
end.
show(Pid, X) ->
parse_integers(Pid, X, show).
show_record(Pid, X) ->
parse_integers(Pid, X, show_record).
set_page(Pid, X) ->
parse_integers(Pid, X, set_page).
at(Pid, X) ->
parse_integers(Pid, X, at).
show_return(Pid, X) ->
parse_integers(Pid, X, show_return).
show_raw(Pid, X) ->
parse_integers(Pid, X, show_raw).
parse_integers(Pid, X, Msg) ->
try
case string:tokens(string:strip(b2l(X)), b2l(" ")) of
[] ->
Pid ! Msg;
[A] ->
Pid ! {Msg, list_to_integer(A)};
[A,B] ->
Pid ! {Msg, list_to_integer(A), list_to_integer(B)}
end
catch
_:_ -> false
end.
print_help() ->
S1 = " (h)elp (a)t [<N>] (d)own (u)p (t)op (b)ottom",
S2 = " (s)how <N> [<ArgN>] (r)etval <N> ra(w) <N>",
S3 = " (pr)etty print record <N> <ArgN>",
S4 = " (f)ind <RegExp> [<ArgN> <ArgRegExp>] (fr) <RetRegExp>",
S5 = " (on)/(off) send_receive | memory",
S6 = " (p)agesize <N> (q)uit",
S7 = " (set) <Var> <N> [<ArgN>] (let) <Var> <Expr>",
S8 = " (eval) <Expr> (xall/xnall) <Mod>",
S = io_lib:format("~n~s~n~s~n~s~n~s~n~s~n~s~n~s~n~s~n",
[S1,S2,S3,S4,S5,S6,S7,S8]),
?info_msg(?help_hi(S), []).
tinit(X) ->
process_flag(trap_exit, true),
?MODULE:tloop(X, #tlist{}, []).
%% @private
tloop(#t{trace_max = MaxTrace} = X, Tlist, Buf) ->
receive
%% FROM THE TRACE FILTER
%% Trace everything until Max is reached.
{trace, From, {N,_Trace} = Msg} when N =< MaxTrace ->
reply(From, ok),
?MODULE:tloop(X, Tlist ,[Msg|Buf]);
%% Max is reached; stop tracing!
{trace, From , {N,_Trace} = _Msg} when N > MaxTrace ->
reply(From, stop),
dbg:stop_clear(),
?MODULE:tloop(X#t{tracer = undefined}, Tlist ,Buf);
%% FROM EDBG
{From, {elixir, Bool}} ->
From ! {self(), ok},
?MODULE:tloop(X#t{elixir = Bool}, Tlist ,Buf);
{max, N} ->
?MODULE:tloop(X#t{trace_max = N}, Tlist ,Buf);
{set_page, Page} ->
?MODULE:tloop(X, Tlist#tlist{page = Page} ,Buf);
{show_raw, N} ->
dbg:stop_clear(),
case lists:keyfind(N, 1, Buf) of
{_, Msg} ->
?info_msg("~n~p~n", [Msg]);
_ ->
?err_msg("not found~n",[])
end,
?MODULE:tloop(X, Tlist ,Buf);
{show_return, N} ->
dbg:stop_clear(),
case get_return_value(N, lists:reverse(Buf)) of
{ok, {M,F,Alen}, RetVal} ->
Sep = pad(35, $-),
?info_msg("~nCall: ~p:~p/~p , return value:~n~s~n~p~n",
[M,F,Alen,Sep,RetVal]);
not_found ->
?info_msg("~nNo return value found!~n",[])
end,
?MODULE:tloop(X, Tlist ,Buf);
{show, N} ->
dbg:stop_clear(),
mlist(N, Buf),
?MODULE:tloop(X, Tlist ,Buf);
{show, N, ArgN} ->
dbg:stop_clear(),
try
case lists:keyfind(N, 1, Buf) of
{_, ?CALL(_Pid, MFA, _As)} ->
show_arg(ArgN, MFA);
{_, ?CALL_TS(_Pid, MFA, _TS, _As)} ->
show_arg(ArgN, MFA);
_ ->
?err_msg("not found~n",[])
end
catch
_:_ -> ?err_msg("not found~n",[])
end,
?MODULE:tloop(X, Tlist ,Buf);
{show_record, N, ArgN} ->
dbg:stop_clear(),
try
case lists:keyfind(N, 1, Buf) of
{_, ?CALL(_Pid, MFA, _As)} ->
show_rec(ArgN, MFA);
{_, ?CALL_TS(_Pid, MFA, _TS, _As)} ->
show_rec(ArgN, MFA);
_ ->
?err_msg("not found~n",[])
end
catch
_:_ ->
?err_msg("not found~n",[])
end,
?MODULE:tloop(X, Tlist ,Buf);
%% Set Variable to the specified Return-Value
{xset, Var, N} ->
dbg:stop_clear(),
NewTlist =
try
case get_return_value(N, lists:reverse(Buf)) of
{ok, {_M,_F,_Alen}, RetVal} ->
add_binding(Tlist, Var, RetVal);
not_found ->
?info_msg("~nNo return value found!~n",[]),
Tlist
end
catch
_:_ ->
?err_msg("unexpected error~n",[]),
Tlist
end,
?MODULE:tloop(X, NewTlist ,Buf);
%% Set Variable to the specified Argument-Value
{xset, Var, N, ArgN} ->
dbg:stop_clear(),
NewTlist =
try
case lists:keyfind(N, 1, Buf) of
{_, ?CALL(_Pid, MFA, _As)} ->
Val = get_arg(ArgN, MFA),
add_binding(Tlist, Var, Val);
{_, ?CALL_TS(_Pid, MFA, _TS, _As)} ->
Val = get_arg(ArgN, MFA),
add_binding(Tlist, Var, Val);
_ ->
?err_msg("not found~n",[]),
Tlist
end
catch
_:_ ->
?err_msg("unexpected error~n",[]),
Tlist
end,
?MODULE:tloop(X, NewTlist ,Buf);
%% Set Variable to the specified record field of Argument-Value
{xset, Var, N, ArgN, RecordStr, FieldStr} ->
dbg:stop_clear(),
Record = list_to_atom(RecordStr),
Field = list_to_atom(FieldStr),
NewTlist =
try
case lists:keyfind(N, 1, Buf) of
{_, ?CALL(_Pid, MFA, _As)} ->
Val = get_arg(ArgN, MFA),
FieldIndex = record_field_index(MFA, Record, Field),
add_binding(Tlist, Var, element(FieldIndex,Val));
{_, ?CALL_TS(_Pid, MFA, _TS, _As)} ->
Val = get_arg(ArgN, MFA),
FieldIndex = record_field_index(MFA, Record, Field),
add_binding(Tlist, Var, element(FieldIndex,Val));
_ ->
?err_msg("not found~n",[]),
Tlist
end
catch
_:_ ->
?err_msg("unexpected error~n",[]),
Tlist
end,
?MODULE:tloop(X, NewTlist ,Buf);
{xlet, Var, ExprStr} ->
dbg:stop_clear(),
NewTlist =
try
{ok, Tokens, _} = erl_scan:string(ExprStr),
{ok, Exprs} = erl_parse:parse_exprs(Tokens),
{value, Result, _} = erl_eval:exprs(Exprs, Tlist#tlist.bs),
?info_msg("~p~n",[Result]),
add_binding(Tlist, Var, Result)
catch
_:_ ->
?err_msg("unexpected error~n",[]),
Tlist
end,
?MODULE:tloop(X, NewTlist ,Buf);
{xeval, ExprStr} ->
dbg:stop_clear(),
try
{ok, Tokens, _} = erl_scan:string(ExprStr),
{ok, Exprs} = erl_parse:parse_exprs(Tokens),
case catch erl_eval:exprs(Exprs, Tlist#tlist.bs) of
{value, Result, _} ->
?info_msg("~p~n",[Result]);
Else ->
?err_msg("~n~p~n",[Else])
end
catch
_:Err ->
?err_msg("parse/eval error: ~n~p~n",[Err])
end,
?MODULE:tloop(X, Tlist ,Buf);
{find, What} ->
NewTlist = do_find(Tlist, Buf, What),
?MODULE:tloop(X, NewTlist ,Buf);
top ->
NewTlist = list_trace(Tlist#tlist{at = 0}, Buf),
?MODULE:tloop(X, NewTlist, Buf);
bottom ->
{N,_} = hd(Buf),
NewTlist = list_trace(Tlist#tlist{at = N}, Buf),
?MODULE:tloop(X, NewTlist, Buf);
{on, send_receive} ->
?info_msg("turning on display of send/receive messages~n",[]),
?MODULE:tloop(X, Tlist#tlist{send_receive = true}, Buf);
{on, memory} ->
?info_msg("turning on display of memory usage~n",[]),
?MODULE:tloop(X, Tlist#tlist{memory = true}, Buf);
{off, send_receive} ->
?info_msg("turning off display of send/receive messages~n",[]),
?MODULE:tloop(X, Tlist#tlist{send_receive = false}, Buf);
{off, memory} ->
?info_msg("turning off display of memory usage~n",[]),
?MODULE:tloop(X, Tlist#tlist{memory = false}, Buf);
at ->
NewAt = erlang:max(0, Tlist#tlist.at - Tlist#tlist.page - 1),
NewTlist = list_trace(Tlist#tlist{at = NewAt}, Buf),
?MODULE:tloop(X, NewTlist, Buf);
{at, At} ->
NewTlist = list_trace(Tlist#tlist{at = At}, Buf),
?MODULE:tloop(X, NewTlist, Buf);
up ->
NewAt = erlang:max(0, Tlist#tlist.at - (2*Tlist#tlist.page)),
NewTlist = list_trace(Tlist#tlist{at = NewAt}, Buf),
?MODULE:tloop(X, NewTlist, Buf);
down ->
dbg:stop_clear(),
NewTlist = list_trace(Tlist, Buf),
?MODULE:tloop(X, NewTlist, Buf);
{raw, N} ->
case lists:keyfind(N, 1, Buf) of
{_,V} -> ?info_msg("~p~n",[V]);
_ -> ?info_msg("nothing found!~n",[])
end,
?MODULE:tloop(X, Tlist ,Buf);
stop ->
dbg:stop_clear(),
?MODULE:tloop(X#t{tracer = undefined}, Tlist ,Buf);
quit ->
dbg:stop_clear(),
exit(quit);
{From, {load_trace_data, TraceData}} ->
From ! {self(), ok},
?MODULE:tloop(X, Tlist ,TraceData);
{From, {start, Start, Opts}} ->
TraceSetupMod = get_trace_setup_mod(Opts),
TraceMax = get_trace_max(Opts),
case TraceSetupMod:start_tracer(Start) of
{ok, NewTracer} ->
From ! {self(), started},
save_start_trace({start, Start, Opts}),
?MODULE:tloop(X#t{trace_max = TraceMax,
tracer = NewTracer}, Tlist ,[]);
{error, _} = Error ->
From ! {self(), Error},
?MODULE:tloop(X, Tlist ,Buf)
end;
_X ->
%%?info_msg("mytracer got: ~p~n",[_X]),
?MODULE:tloop(X, Tlist ,Buf)
end.
%% Find a match among the return values
do_find(Tlist, Buf, {ret, Str}) ->
case find_retval(Tlist#tlist.at, Buf, Str) of
not_found ->
?info_msg("not found~n",[]),
Tlist;
NewAt ->
list_trace(Tlist#tlist{at = NewAt}, Buf)
end;
do_find(Tlist, Buf, {rexp, Str, undefined}) ->
case xfind_mf(Tlist#tlist.at, Buf, Str) of
not_found ->
?info_msg("not found~n",[]),
Tlist;
NewAt ->
list_trace(Tlist#tlist{at = NewAt}, Buf)
end;
do_find(Tlist, Buf, {rexp, Str, {An,Av}}) ->
case xfind_mf_av(Tlist#tlist.at, Buf, Str, An, Av) of
not_found ->
?info_msg("not found~n",[]),
Tlist;
NewAt ->
list_trace(Tlist#tlist{at = NewAt}, Buf)
end.
add_binding(#tlist{bs = Bs} = T, Var, Val) ->
T#tlist{bs = erl_eval:add_binding(list_to_atom(Var), Val, Bs)}.
get_arg(ArgN, {_M,_F,A}) ->
lists:nth(ArgN, A).
show_arg(ArgN, {M,F,A}) ->
Sep = pad(35, $-),
ArgStr = "argument "++integer_to_list(ArgN)++":",
?info_msg("~nCall: ~p:~p/~p , ~s~n~s~n~p~n",
[M,F,length(A),ArgStr,Sep,lists:nth(ArgN,A)]).
show_rec(ArgN, {M,F,A}) ->
Sep = pad(35, $-),
Fname = edbg:find_source(M),
{ok, Defs} = pp_record:read(Fname),
ArgStr = "argument "++integer_to_list(ArgN)++":",
?info_msg("~nCall: ~p:~p/~p , ~s~n~s~n~s~n",
[M,F,length(A),ArgStr,Sep,
pp_record:print(lists:nth(ArgN,A), Defs)]).
%% Do a RegExp search in "Mod:Fun"
xfind_mf(At, Buf, Str) ->
%% First get the set of trace messages to investigate
L = lists:takewhile(
fun({N,_}) when N>=At -> true;
(_) -> false
end, Buf),
%% Discard non-matching calls
R = lists:dropwhile(
fun({_N, ?CALL(_Pid, {M,F,_}, _As)}) ->
re_match(atom_to_list(M)++":"++atom_to_list(F), Str);
({_N, ?CALL_TS(_Pid, {M,F,_}, _TS, _As)}) ->
re_match(atom_to_list(M)++":"++atom_to_list(F), Str);
(_) ->
true
end, lists:reverse(L)),
case R of
[{N,_}|_] -> N;
_ -> not_found
end.
xfind_mf_av(At, Buf, Str, An, Av) ->
%% First get the set of trace messages to investigate
L = lists:takewhile(
fun({N,_}) when N>=At -> true;
(_) -> false
end, Buf),
%% Discard non-matching calls
R = lists:dropwhile(
fun({_N, ?CALL(_Pid, {M,F,A}, _As)}) when length(A) >= An ->
Arg = lists:nth(An, A),
re_match(atom_to_list(M)++":"++atom_to_list(F), Str)
orelse
re_match(lists:flatten(io_lib:format("~p",[Arg])), Av);
({_N, ?CALL_TS(_Pid, {M,F,A}, _TS, _As)}) when length(A) >= An ->
Arg = lists:nth(An, A),
re_match(atom_to_list(M)++":"++atom_to_list(F), Str)
orelse
re_match(lists:flatten(io_lib:format("~p",[Arg])), Av);
(_) ->
true
end, lists:reverse(L)),
case R of
[{N,_}|_] -> N;
_ -> not_found
end.
re_match(Str, Rexp) ->
try re:run(Str, Rexp) of
nomatch -> true;
_ -> false
catch
_:_ -> true
end.
get_buf_at(At, Buf) ->
lists:takewhile(
fun({N,_}) when N>=At -> true;
(_) -> false
end, Buf).
get_buf_before_at(At, Buf) ->
lists:dropwhile(
fun({N,_}) when N>=At -> true;
(_) -> false
end, Buf).
find_retval(At, Buf, Str) ->
%% First get the set of trace messages to investigate
L = get_buf_at(At, Buf),
%% Discard non-matching return values
try
lists:foldl(
fun({N, ?RETURN_FROM(_Pid, MFA, Value, _As)}=X,_Acc) ->
do_find_retval(N, Str, Value, X, MFA, Buf);
({N, ?RETURN_FROM_TS(_Pid, MFA, Value, _TS, _As)}=X,_Acc) ->
do_find_retval(N, Str, Value, X, MFA, Buf);
(X, Acc) ->
[X|Acc]
end, [], lists:reverse(L)),
not_found
catch
throw:{matching_call,{N,_}} -> N;
_:_ -> not_found
end.
do_find_retval(At, Str, Value, X, MFA, Buf) ->
L = get_buf_before_at(At, Buf),
ValStr = lists:flatten(io_lib:format("~p",[Value])),
try re:run(ValStr, Str) of
nomatch -> [X|Buf];
_ -> find_matching_call(MFA, L, 0)
catch
_:_ -> [X|Buf]
end.
%% X = {Trace(_ts), Pid, CallOrReturnFrom, MFA, ...}
-define(m(X), element(1,element(4,X))).
-define(f(X), element(2,element(4,X))).
-define(a(X), element(3,element(4,X))).
-define(l(X), length(element(3,element(4,X)))).
%% Will throw exception at success; crash if nothing is found!
find_matching_call({M,F,A}, [{_N,Trace}=X|_], 0)
when ?is_trace_call(Trace) andalso
?m(Trace) == M andalso
?f(Trace) == F andalso
?l(Trace) == A ->
throw({matching_call,X});
find_matching_call({M,F,A}=MFA, [{_N,Trace}|L], N)
when ?is_trace_call(Trace) andalso
?m(Trace) == M andalso
?f(Trace) == F andalso
?l(Trace) == A ->
find_matching_call(MFA, L, N-1);
find_matching_call({M,F,A}=MFA, [{_N,Trace}|L], N)
when ?is_trace_return_from(Trace) andalso
?m(Trace) == M andalso
?f(Trace) == F andalso
?a(Trace) == A ->
find_matching_call(MFA, L, N+1);
find_matching_call(MFA, [{_N,_Trace}|L], N) ->
find_matching_call(MFA, L, N).
get_trace_setup_mod(Opts) ->
get_opts(Opts, setup_mod, edbg_trace_filter).
get_trace_max(Opts) ->
get_opts(Opts, trace_max, 10000).
get_opts(Opts, Key, Default) ->
case lists:keyfind(Key, 1, Opts) of
{Key, Mod} -> Mod;
_ -> Default
end.
save_start_trace(X) ->
{ok,Fd} = file:open("trace.edbg",[write]),
try
io:format(Fd, "~p.~n", [X])
after
file:close(Fd)
end.
field_size([{N,_}|_]) ->
integer_to_list(length(integer_to_list(N)));
field_size(_) ->
"1". % shouldn't happen...
list_trace(Tlist, Buf) ->
maybe_put_first_timestamp(Buf),
Fs = field_size(Buf),
Zlist =
lists:foldr(
%% C A L L
fun({N, ?CALL(Pid, {M,F,A}, As)},
#tlist{level = LevelMap,
memory = MemoryP,
at = At,
vlines = VL,
page = Page} = Z)
when ?inside_vl(At,N,VL,Page) ->
Level = maps:get(Pid, LevelMap, 0),
MPid = mpid(MemoryP, Pid, As),
?info_msg("~"++Fs++".s:~s ~s ~p:~p/~p~n",
[integer_to_list(N),pad(Level),MPid,M,F,length(A)]),
Z#tlist{vlines = VL + 1,
level = maps:put(Pid, Level+1, LevelMap)};
({N, ?CALL_TS(Pid, {M,F,A}, TS, As)},
#tlist{level = LevelMap,
memory = MemoryP,
at = At,
vlines = VL,
page = Page} = Z)
when ?inside_vl(At,N,VL,Page) ->
Level = maps:get(Pid, LevelMap, 0),
MPid = mpid(MemoryP, Pid, As),
?info_msg("~"++Fs++".s:~s ~s ~p:~p/~p - ~p~n",
[integer_to_list(N),pad(Level),MPid,M,F,length(A),
xts(TS)]),
Z#tlist{vlines = VL + 1,
level = maps:put(Pid, Level+1, LevelMap)};
({_N, ?CALL(Pid, {_M,_F,_A}, _As}),
#tlist{level = LevelMap} = Z) ->
Level = maps:get(Pid, LevelMap, 0),
Z#tlist{level = maps:put(Pid, Level+1, LevelMap)};
({_N, ?CALL_TS(Pid, {_M,_F,_A}, _TS, _As)},
#tlist{level = LevelMap} = Z) ->
Level = maps:get(Pid, LevelMap, 0),
Z#tlist{level = maps:put(Pid, Level+1, LevelMap)};
%% R E T U R N _ F R O M
({_N, ?RETURN_FROM(Pid, _MFA, _Value, _As)},
#tlist{level = LevelMap} = Z) ->
Level = maps:get(Pid, LevelMap, 0),
Z#tlist{level = maps:put(Pid,erlang:max(Level-1,0),LevelMap)};
({_N, ?RETURN_FROM_TS(Pid, _MFA, _Value, _TS, _As)},
#tlist{level = LevelMap} = Z) ->
Level = maps:get(Pid, LevelMap, 0),
Z#tlist{level = maps:put(Pid,erlang:max(Level-1,0),LevelMap)};
%% S E N D
({N, ?SEND(FromPid, Msg, ToPid, _As)},
#tlist{send_receive = true,
level = LevelMap,
at = At,
vlines = VL,
page = Page} = Z)
when ?inside_vl(At,N,VL,Page) ->
Level = maps:get(FromPid, LevelMap, 0),
?info_msg("~"++Fs++".s:~s >>> Send(~p) -> To(~p) ~s~n",
[integer_to_list(N),pad(Level),
FromPid,ToPid,truncate(Msg)]),
Z#tlist{vlines = VL + 1};
({_N, ?SEND(_FromPid, _Msg, _ToPid, _As)}, Z) ->
Z;
({N, ?SEND_TS(FromPid, Msg, ToPid, TS, _As)},
#tlist{send_receive = true,
level = LevelMap,
at = At,
vlines = VL,
page = Page} = Z)
when ?inside_vl(At,N,VL,Page) ->
Level = maps:get(FromPid, LevelMap, 0),
?info_msg("~"++Fs++".s:~s >>> Send(~p) -> To(~p) ~s - ~p~n",
[integer_to_list(N),pad(Level),
FromPid,ToPid,truncate(Msg),xts(TS)]),
Z#tlist{vlines = VL + 1};
({_N, ?SEND_TS(_FromPid, _Msg, _ToPid, _TS, _As)}, Z) ->
Z;
%% R E C E I V E
({N, ?RECEIVE(ToPid, Msg, _As)},
#tlist{send_receive = true,
level = LevelMap,
at = At,
vlines = VL,
page = Page} = Z)
when ?inside_vl(At,N,VL,Page) ->
Level = maps:get(ToPid, LevelMap, 0),
?info_msg("~"++Fs++".s:~s <<< Receive(~p) ~s~n",
[integer_to_list(N),pad(Level),
ToPid,truncate(Msg)]),
Z#tlist{vlines = VL + 1};
({_N, ?RECEIVE(_ToPid, _Msg, _As)}, Z) ->
Z;
({N, ?RECEIVE_TS(ToPid, Msg, TS, _As)},
#tlist{send_receive = true,
level = LevelMap,
at = At,
vlines = VL,
page = Page} = Z)
when ?inside_vl(At,N,VL,Page) ->
Level = maps:get(ToPid, LevelMap, 0),
?info_msg("~"++Fs++".s:~s <<< Receive(~p) ~s - ~p~n",
[integer_to_list(N),pad(Level),
ToPid,truncate(Msg),xts(TS)]),
Z#tlist{vlines = VL + 1};
({_N, ?RECEIVE_TS(_ToPid, _Msg, _TS, _As)}, Z) ->
Z
end, Tlist#tlist{vlines = 0,
level = maps:new()}, Buf),
NewAt = Tlist#tlist.at + Tlist#tlist.page + 1,
Zlist#tlist{at = NewAt}.
mpid(true = _MemoryP, Pid, As) ->
case lists:keyfind(memory, 1, As) of
{_, Mem} when is_integer(Mem) ->
pid_to_list(Pid)++"("++integer_to_list(Mem)++")";
_ ->
pid_to_list(Pid)
end;
mpid(_MemoryP , Pid, _As) ->
pid_to_list(Pid).
truncate(Term) ->
truncate(Term, 20).
truncate(Term, Length) ->
string:slice(io_lib:format("~p",[Term]), 0, Length)++"...".
%% Elapsed monotonic time since first trace message
xts(TS) ->
case get(first_monotonic_timestamp) of
undefined ->
0;
XTS ->
TS - XTS
end.
maybe_put_first_timestamp(Buf) ->
case get(first_monotonic_timestamp) of
undefined ->
case Buf of
[{_N, ?CALL_TS(_Pid, _MFA, _TS, _S)}|_] ->
put(first_monotonic_timestamp,
get_first_monotonic_timestamp(Buf));
[{_N, ?RETURN_FROM_TS(_Pid, _MFA, _Value, _TS, _As)}|_] ->
put(first_monotonic_timestamp,
get_first_monotonic_timestamp(Buf));
[{_N, ?SEND_TS(_FromPid, _Msg, _ToPid, _TS, _As)}|_] ->
put(first_monotonic_timestamp,
get_first_monotonic_timestamp(Buf));
[{_N, ?RECEIVE_TS(_FromPid, _Msg, _TS, _As)}|_] ->
put(first_monotonic_timestamp,
get_first_monotonic_timestamp(Buf));
_ ->
undefined
end;
TS ->
TS
end.
get_first_monotonic_timestamp(Buf) ->
case lists:reverse(Buf) of
[{_N, ?CALL_TS(_Pid, _MFA, TS, _S)}|_] ->
TS;
[{_N, ?RETURN_FROM_TS(_Pid, _MFA, _Value, TS, _As)}|_] ->
TS;
[{_N, ?SEND_TS(_FromPid, _Msg, _ToPid, TS, _As)}|_] ->
TS;
[{_N, ?RECEIVE_TS(_FromPid, _Msg, TS, _As)}|_] ->
TS
end.
get_return_value(N, [{I,_}|T]) when I < N ->
get_return_value(N, T);
get_return_value(N, [{N, ?CALL(_Pid, {M,F,A}, _As)}|T]) ->
find_return_value({M,F,length(A)}, T);
get_return_value(N, [{N, ?CALL_TS(_Pid, {M,F,A}, _TS, _As)}|T]) ->
find_return_value({M,F,length(A)}, T);
get_return_value(N, [{I,_}|_]) when I > N ->
not_found;
get_return_value(_, []) ->
not_found.
find_return_value(MFA, T) ->
find_return_value(MFA, T, 0).
find_return_value(MFA,[{_, ?RETURN_FROM(_Pid, MFA, Val, _As)}|_],0 = _Depth)->
{ok, MFA, Val};
find_return_value(MFA, [{_, ?RETURN_FROM_TS(_Pid, MFA, Val, _TS, _As)}|_],
0 = _Depth) ->
{ok, MFA, Val};
find_return_value(MFA, [{_, ?RETURN_FROM(_Pid, MFA, _Val, _As)}|T], Depth)
when Depth > 0 ->
find_return_value(MFA, T, Depth-1);
find_return_value(MFA,[{_, ?RETURN_FROM_TS(_Pid, MFA, _MFA, _TS, _As)}|T],Depth)
when Depth > 0 ->
find_return_value(MFA, T, Depth-1);
find_return_value(MFA, [{_, ?CALL(_Pid, MFA, _As)}|T], Depth) ->
find_return_value(MFA, T, Depth+1);
find_return_value(MFA, [{_, ?CALL_TS(_Pid, MFA, _TS, _As)}|T], Depth) ->
find_return_value(MFA, T, Depth+1);
find_return_value(MFA, [_|T], Depth) ->
find_return_value(MFA, T, Depth);
find_return_value(_MFA, [], _Depth) ->
not_found.
mlist(N, Buf) ->
try
case lists:keyfind(N, 1, Buf) of
{_, ?CALL(_Pid, MFA, _As)} ->
do_mlist(MFA);
{_, ?CALL_TS(_Pid, MFA, _TS, _As)} ->
do_mlist(MFA);
{_, ?SEND(SendPid, Msg, ToPid, _As)} ->
show_send_msg(SendPid, ToPid, Msg);
{_, ?SEND_TS(SendPid, Msg, ToPid, _TS, _As)} ->
show_send_msg(SendPid, ToPid, Msg);
{_, ?RECEIVE(RecvPid, Msg, _As)} ->
show_recv_msg(RecvPid, Msg);
{_, ?RECEIVE_TS(RecvPid, Msg, _TS, _As)} ->
show_recv_msg(RecvPid, Msg);
_ ->
?info_msg("not found~n",[])
end
catch
_:Err ->
?info_msg(?c_err("CRASH: ~p~n"), [Err])
end.
show_send_msg(SendPid, ToPid, Msg) ->
?info_msg("~nMessage sent by: ~p to: ~p~n~p~n",
[SendPid,ToPid,Msg]).
show_recv_msg(RecvPid, Msg) ->
?info_msg("~nMessage received by: ~p~n~p~n",
[RecvPid,Msg]).
do_mlist({M,F,A}) ->
Fname = edbg:find_source(M),
{ok, SrcBin, Fname} = erl_prim_loader:get_file(Fname),
file:write_file("/tmp/"++filename:basename(Fname), SrcBin),
LF = atom_to_list(F),
Src = binary_to_list(SrcBin),
%% '.*?' ::= ungreedy match!
RegExp = b2l("\\n"++LF++"\\(.*?->"),
ExRegExp = b2l("def[p]?[ ]+"++LF++"[ (].*do"), % Elixir!
%% 'dotall' ::= allow multiline function headers
case re:run(Src, RegExp, [global,dotall,report_errors]) of
{match, MatchList} ->
{FmtStr, Args} = mk_print_match(SrcBin, MatchList),
Sep = pad(35, $-),
?info_msg("~nCall: ~p:~p/~p~n~s~n"++FmtStr++"~n~s~n",
[M,F,length(A),Sep|Args]++[Sep]);
Else ->
%% Could it be Elixir?
case re:run(Src, ExRegExp, [global,report_errors]) of
{match, ExMatchList} ->
{FmtStr, Args} = mk_print_match(SrcBin, ExMatchList),
Sep = pad(35, $-),
?info_msg("~nCall: ~p:~p/~p~n~s~n"++FmtStr++"~n~s~n",
[M,F,length(A),Sep|Args]++[Sep]);
_ ->
?info_msg("nomatch: ~p~n",[Else])
end
end.
mk_print_match(SrcBin, MatchList) ->
F = fun([{Start,Length}], {FmtStrAcc, ArgsAcc}) ->
<<_:Start/binary,Match:Length/binary,_/binary>> = SrcBin,
Str = binary_to_list(Match),
{"~s~n"++FmtStrAcc, [Str|ArgsAcc]}
end,
lists:foldr(F, {"",[]}, MatchList).
pad(0) -> [];
pad(N) ->
pad(N, $\s).
pad(N,C) ->
lists:duplicate(N,C).
%% @private
send(Pid, Msg) ->
Pid ! {trace, self(), Msg},
receive
{Pid, ok} -> ok;
{Pid, stop} -> exit(stop)
end.
reply(Pid, Msg) ->
Pid ! {self(), Msg}.
record_field_index({M,_,_}, Record, Field) ->
%% we cache the Record Field info for a Module
case get({M,Record}) of
undefined ->
%% [{Field,Index}]
Fields = get_record_fields(M, Record),
put({M,Record}, Fields),
{Field,Index} = lists:keyfind(Field, 1, Fields),
Index;
Fields ->
{Field,Index} = lists:keyfind(Field, 1, Fields),
Index
end.
get_record_fields(Mod, Record) ->
%% Get the Record definitions for the Module
Fname = edbg:find_source(Mod),
{ok, Defs} = pp_record:read(Fname),
%% Get the Record Fields
{Record,{attribute,_,record,{Record,Fs}}} = lists:keyfind(Record, 1, Defs),
%% Extract the Record Field names; keep the order
F = fun({typed_record_field,{record_field,_,{atom,_,Name},_},_},Acc) ->
[Name|Acc];
({typed_record_field,{record_field,_,{atom,_,Name}},_},Acc) ->
[Name|Acc];
({record_field,_,{atom,_,Name}},Acc) ->
[Name|Acc];
({record_field,_,{atom,_,Name},_},Acc) ->
[Name|Acc]
end,
Fields = lists:foldr(F, [], Fs),
%% Create a Key-Value list of the fields and their index into the Record
lists:zip(Fields, lists:seq(2, erlang:length(Fields)+1)).