|
|
- %%% @author Fred Hebert <mononcqc@ferd.ca>
- %%% [http://ferd.ca/]
- %%% @doc
- %%% `recon_trace' is a module that handles tracing in a safe manner for single
- %%% Erlang nodes, currently for function calls only. Functionality includes:
- %%%
- %%% <ul>
- %%% <li>Nicer to use interface (arguably) than `dbg' or trace BIFs.</li>
- %%% <li>Protection against dumb decisions (matching all calls on a node
- %%% being traced, for example)</li>
- %%% <li>Adding safe guards in terms of absolute trace count or
- %%% rate-limitting</li>
- %%% <li>Nicer formatting than default traces</li>
- %%% </ul>
- %%%
- %%% == Tracing Erlang Code ==
- %%%
- %%% The Erlang Trace BIFs allow to trace any Erlang code at all. They work in
- %%% two parts: pid specifications, and trace patterns.
- %%%
- %%% Pid specifications let you decide which processes to target. They can be
- %%% specific pids, `all' pids, `existing' pids, or `new' pids (those not
- %%% spawned at the time of the function call).
- %%%
- %%% The trace patterns represent functions. Functions can be specified in two
- %%% parts: specifying the modules, functions, and arguments, and then with
- %%% Erlang match specifications to add constraints to arguments (see
- %%% {@link calls/3} for details).
- %%%
- %%% What defines whether you get traced or not is the intersection of both:
- %%%
- %%% ```
- %%% _,--------,_ _,--------,_
- %%% ,-' `-,,-' `-,
- %%% ,-' ,-' '-, `-,
- %%% | Matching -' '- Matching |
- %%% | Pids | Getting | Trace |
- %%% | | Traced | Patterns |
- %%% | -, ,- |
- %%% '-, '-, ,-' ,-'
- %%% '-,_ _,-''-,_ _,-'
- %%% '--------' '--------'
- %%% '''
- %%%
- %%% If either the pid specification excludes a process or a trace pattern
- %%% excludes a given call, no trace will be received.
- %%%
- %%% == Example Session ==
- %%%
- %%% First let's trace the `queue:new' functions in any process:
- %%%
- %%% ```
- %%% 1> recon_trace:calls({queue, new, '_'}, 1).
- %%% 1
- %%% 13:14:34.086078 <0.44.0> queue:new()
- %%% Recon tracer rate limit tripped.
- %%% '''
- %%%
- %%% The limit was set to `1' trace message at most, and `recon' let us
- %%% know when that limit was reached.
- %%%
- %%% Let's instead look for all the `queue:in/2' calls, to see what it is
- %%% we're inserting in queues:
- %%%
- %%% ```
- %%% 2> recon_trace:calls({queue, in, 2}, 1).
- %%% 1
- %%% 13:14:55.365157 <0.44.0> queue:in(a, {[],[]})
- %%% Recon tracer rate limit tripped.
- %%% '''
- %%%
- %%% In order to see the content we want, we should change the trace patterns
- %%% to use a `fun' that matches on all arguments in a list (`_') and returns
- %%% `return_trace()'. This last part will generate a second trace for each
- %%% call that includes the return value:
- %%%
- %%% ```
- %%% 3> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3).
- %%% 1
- %%%
- %%% 13:15:27.655132 <0.44.0> queue:in(a, {[],[]})
- %%%
- %%% 13:15:27.655467 <0.44.0> queue:in/2 --> {[a],[]}
- %%%
- %%% 13:15:27.757921 <0.44.0> queue:in(a, {[],[]})
- %%% Recon tracer rate limit tripped.
- %%% '''
- %%%
- %%% Matching on argument lists can be done in a more complex manner:
- %%%
- %%% ```
- %%% 4> recon_trace:calls(
- %%% 4> {queue, '_', fun([A,_]) when is_list(A); is_integer(A) andalso A > 1 -> return_trace() end},
- %%% 4> {10,100}
- %%% 4> ).
- %%% 32
- %%%
- %%% 13:24:21.324309 <0.38.0> queue:in(3, {[],[]})
- %%%
- %%% 13:24:21.371473 <0.38.0> queue:in/2 --> {[3],[]}
- %%%
- %%% 13:25:14.694865 <0.53.0> queue:split(4, {[10,9,8,7],[1,2,3,4,5,6]})
- %%%
- %%% 13:25:14.695194 <0.53.0> queue:split/2 --> {{[4,3,2],[1]},{[10,9,8,7],[5,6]}}
- %%%
- %%% 5> recon_trace:clear().
- %%% ok
- %%% '''
- %%%
- %%% Note that in the pattern above, no specific function (<code>'_'</code>) was
- %%% matched against. Instead, the `fun' used restricted functions to those
- %%% having two arguments, the first of which is either a list or an integer
- %%% greater than `1'.
- %%%
- %%% The limit was also set using `{10,100}' instead of an integer, making the
- %%% rate-limitting at 10 messages per 100 milliseconds, instead of an absolute
- %%% value.
- %%%
- %%% Any tracing can be manually interrupted by calling `recon_trace:clear()',
- %%% or killing the shell process.
- %%%
- %%% Be aware that extremely broad patterns with lax rate-limitting (or very
- %%% high absolute limits) may impact your node's stability in ways
- %%% `recon_trace' cannot easily help you with.
- %%%
- %%% In doubt, start with the most restrictive tracing possible, with low
- %%% limits, and progressively increase your scope.
- %%%
- %%% See {@link calls/3} for more details and tracing possibilities.
- %%%
- %%% == Structure ==
- %%%
- %%% This library is production-safe due to taking the following structure for
- %%% tracing:
- %%%
- %%% ```
- %%% [IO/Group leader] <---------------------,
- %%% | |
- %%% [shell] ---> [tracer process] ----> [formatter]
- %%% '''
- %%%
- %%% The tracer process receives trace messages from the node, and enforces
- %%% limits in absolute terms or trace rates, before forwarding the messages
- %%% to the formatter. This is done so the tracer can do as little work as
- %%% possible and never block while building up a large mailbox.
- %%%
- %%% The tracer process is linked to the shell, and the formatter to the
- %%% tracer process. The formatter also traps exits to be able to handle
- %%% all received trace messages until the tracer termination, but will then
- %%% shut down as soon as possible.
- %%%
- %%% In case the operator is tracing from a remote shell which gets
- %%% disconnected, the links between the shell and the tracer should make it
- %%% so tracing is automatically turned off once you disconnect.
- %%%
- %%% If sending output to the Group Leader is not desired, you may specify
- %%% a different pid() via the option `io_server' in the {@link calls/3} function.
- %%% For instance to write the traces to a file you can do something like
- %%%
- %%% ```
- %%% 1> {ok, Dev} = file:open("/tmp/trace",[write]).
- %%% 2> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3, [{io_server, Dev}]).
- %%% 1
- %%% 3>
- %%% Recon tracer rate limit tripped.
- %%% 4> file:close(Dev).
- %%% '''
- %%%
- %%% The only output still sent to the Group Leader is the rate limit being
- %%% tripped, and any errors. The rest will be sent to the other IO
- %%% server (see [http://erlang.org/doc/apps/stdlib/io_protocol.html]).
- %%%
- %%% == Record Printing ==
- %%%
- %%% Thanks to code contributed by Bartek Górny, record printing can be added
- %%% to traces by first importing records in an active session with
- %%% `recon_rec:import([Module, ...])', after which the records declared in
- %%% the module list will be supported.
- %%% @end
- -module(recon_trace).
-
- %% API
- -export([clear/0, calls/2, calls/3]).
-
- -export([format/1]).
-
- %% Internal exports
- -export([count_tracer/1, rate_tracer/2, formatter/5, format_trace_output/1, format_trace_output/2]).
-
- -type matchspec() :: [{[term()] | '_', [term()], [term()]}].
- -type shellfun() :: fun((_) -> term()).
- -type formatterfun() :: fun((_) -> iodata()).
- -type millisecs() :: non_neg_integer().
- -type pidspec() :: all | existing | new | recon:pid_term().
- -type max_traces() :: non_neg_integer().
- -type max_rate() :: {max_traces(), millisecs()}.
-
- %% trace options
- -type options() :: [ {pid, pidspec() | [pidspec(),...]} % default: all
- | {timestamp, formatter | trace} % default: formatter
- | {args, args | arity} % default: args
- | {io_server, pid()} % default: group_leader()
- | {formatter, formatterfun()} % default: internal formatter
- | return_to | {return_to, boolean()} % default: false
- %% match pattern options
- | {scope, global | local} % default: global
- ].
-
- -type mod() :: '_' | module().
- -type fn() :: '_' | atom().
- -type args() :: '_' | 0..255 | return_trace | matchspec() | shellfun().
- -type tspec() :: {mod(), fn(), args()}.
- -type max() :: max_traces() | max_rate().
- -type num_matches() :: non_neg_integer().
-
- -export_type([mod/0, fn/0, args/0, tspec/0, num_matches/0, options/0,
- max_traces/0, max_rate/0]).
-
- %%%%%%%%%%%%%%
- %%% PUBLIC %%%
- %%%%%%%%%%%%%%
-
- %% @doc Stops all tracing at once.
- -spec clear() -> ok.
- clear() ->
- erlang:trace(all, false, [all]),
- erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_count,call_time]),
- erlang:trace_pattern({'_','_','_'}, false, []), % unsets global
- maybe_kill(recon_trace_tracer),
- maybe_kill(recon_trace_formatter),
- ok.
-
- %% @equiv calls({Mod, Fun, Args}, Max, [])
- -spec calls(tspec() | [tspec(),...], max()) -> num_matches().
- calls({Mod, Fun, Args}, Max) ->
- calls([{Mod,Fun,Args}], Max, []);
- calls(TSpecs = [_|_], Max) ->
- calls(TSpecs, Max, []).
-
- %% @doc Allows to set trace patterns and pid specifications to trace
- %% function calls.
- %%
- %% The basic calls take the trace patterns as tuples of the form
- %% `{Module, Function, Args}' where:
- %%
- %% <ul>
- %% <li>`Module' is any atom representing a module</li>
- %% <li>`Function' is any atom representing a function, or the wildcard
- %% <code>'_'</code></li>
- %% <li>`Args' is either the arity of a function (`0..255'), a wildcard
- %% pattern (<code>'_'</code>), a
- %% <a href="http://learnyousomeerlang.com/ets#you-have-been-selected">match specification</a>,
- %% or a function from a shell session that can be transformed into
- %% a match specification</li>
- %% </ul>
- %%
- %% There is also an argument specifying either a maximal count (a number)
- %% of trace messages to be received, or a maximal frequency (`{Num, Millisecs}').
- %%
- %% Here are examples of things to trace:
- %%
- %% <ul>
- %% <li>All calls from the `queue' module, with 10 calls printed at most:
- %% ``recon_trace:calls({queue, '_', '_'}, 10)''</li>
- %% <li>All calls to `lists:seq(A,B)', with 100 calls printed at most:
- %% `recon_trace:calls({lists, seq, 2}, 100)'</li>
- %% <li>All calls to `lists:seq(A,B)', with 100 calls per second at most:
- %% `recon_trace:calls({lists, seq, 2}, {100, 1000})'</li>
- %% <li>All calls to `lists:seq(A,B,2)' (all sequences increasing by two)
- %% with 100 calls at most:
- %% `recon_trace:calls({lists, seq, fun([_,_,2]) -> ok end}, 100)'</li>
- %% <li>All calls to `iolist_to_binary/1' made with a binary as an argument
- %% already (kind of useless conversion!):
- %% `recon_trace:calls({erlang, iolist_to_binary, fun([X]) when is_binary(X) -> ok end}, 10)'</li>
- %% <li>Calls to the queue module only in a given process `Pid', at a rate
- %% of 50 per second at most:
- %% ``recon_trace:calls({queue, '_', '_'}, {50,1000}, [{pid, Pid}])''</li>
- %% <li>Print the traces with the function arity instead of literal arguments:
- %% `recon_trace:calls(TSpec, Max, [{args, arity}])'</li>
- %% <li>Matching the `filter/2' functions of both `dict' and `lists' modules,
- %% across new processes only:
- %% `recon_trace:calls([{dict,filter,2},{lists,filter,2}], 10, [{pid, new}])'</li>
- %% <li>Tracing the `handle_call/3' functions of a given module for all new processes,
- %% and those of an existing one registered with `gproc':
- %% `recon_trace:calls({Mod,handle_call,3}, {10,100}, [{pid, [{via, gproc, Name}, new]}'</li>
- %% <li>Show the result of a given function call:
- %% `recon_trace:calls({Mod,Fun,fun(_) -> return_trace() end}, Max, Opts)'
- %% or
- %% ``recon_trace:calls({Mod,Fun,[{'_', [], [{return_trace}]}]}, Max, Opts)'',
- %% the important bit being the `return_trace()' call or the
- %% `{return_trace}' match spec value.
- %% A short-hand version for this pattern of 'match anything, trace everything'
- %% for a function is `recon_trace:calls({Mod, Fun, return_trace})'. </li>
- %% </ul>
- %%
- %% There's a few more combination possible, with multiple trace patterns per call, and more
- %% options:
- %%
- %% <ul>
- %% <li>`{pid, PidSpec}': which processes to trace. Valid options is any of
- %% `all', `new', `existing', or a process descriptor (`{A,B,C}',
- %% `"<A.B.C>"', an atom representing a name, `{global, Name}',
- %% `{via, Registrar, Name}', or a pid). It's also possible to specify
- %% more than one by putting them in a list.</li>
- %% <li>`{timestamp, formatter | trace}': by default, the formatter process
- %% adds timestamps to messages received. If accurate timestamps are
- %% required, it's possible to force the usage of timestamps within
- %% trace messages by adding the option `{timestamp, trace}'.</li>
- %% <li>`{args, arity | args}': whether to print arity in function calls
- %% or their (by default) literal representation.</li>
- %% <li>`{scope, global | local}': by default, only 'global' (fully qualified
- %% function calls) are traced, not calls made internally. To force tracing
- %% of local calls, pass in `{scope, local}'. This is useful whenever
- %% you want to track the changes of code in a process that isn't called
- %% with `Module:Fun(Args)', but just `Fun(Args)'.</li>
- %% <li>`{formatter, fun(Term) -> io_data() end}': override the default
- %% formatting functionality provided by recon.</li>
- %% <li>`{io_server, pid() | atom()}': by default, recon logs to the current
- %% group leader, usually the shell. This option allows to redirect
- %% trace output to a different IO server (such as a file handle).</li>
- %% <li>`return_to': If this option is set (in conjunction with the match
- %% option `{scope, local}'), the function to which the value is returned
- %% is output in a trace. Note that this is distinct from giving the
- %% *caller* since exception handling or calls in tail position may
- %% hide the original caller.</li>
- %% </ul>
- %%
- %% Also note that putting extremely large `Max' values (i.e. `99999999' or
- %% `{10000,1}') will probably negate most of the safe-guarding this library
- %% does and be dangerous to your node. Similarly, tracing extremely large
- %% amounts of function calls (all of them, or all of `io' for example)
- %% can be risky if more trace messages are generated than any process on
- %% the node could ever handle, despite the precautions taken by this library.
- %% @end
- -spec calls(tspec() | [tspec(),...], max(), options()) -> num_matches().
-
- calls({Mod, Fun, Args}, Max, Opts) ->
- calls([{Mod,Fun,Args}], Max, Opts);
- calls(TSpecs = [_|_], {Max, Time}, Opts) ->
- Pid = setup(rate_tracer, [Max, Time],
- validate_formatter(Opts), validate_io_server(Opts)),
- trace_calls(TSpecs, Pid, Opts);
- calls(TSpecs = [_|_], Max, Opts) ->
- Pid = setup(count_tracer, [Max],
- validate_formatter(Opts), validate_io_server(Opts)),
- trace_calls(TSpecs, Pid, Opts).
-
- %%%%%%%%%%%%%%%%%%%%%%%
- %%% PRIVATE EXPORTS %%%
- %%%%%%%%%%%%%%%%%%%%%%%
- %% @private Stops when N trace messages have been received
- count_tracer(0) ->
- exit(normal);
- count_tracer(N) ->
- receive
- Msg ->
- recon_trace_formatter ! Msg,
- count_tracer(N-1)
- end.
-
- %% @private Stops whenever the trace message rates goes higher than
- %% `Max' messages in `Time' milliseconds. Note that if the rate
- %% proposed is higher than what the IO system of the formatter
- %% can handle, this can still put a node at risk.
- %%
- %% It is recommended to try stricter rates to begin with.
- rate_tracer(Max, Time) -> rate_tracer(Max, Time, 0, os:timestamp()).
-
- rate_tracer(Max, Time, Count, Start) ->
- receive
- Msg ->
- recon_trace_formatter ! Msg,
- Now = os:timestamp(),
- Delay = timer:now_diff(Now, Start) div 1000,
- if Delay > Time -> rate_tracer(Max, Time, 0, Now)
- ; Max > Count -> rate_tracer(Max, Time, Count+1, Start)
- ; Max =:= Count -> exit(normal)
- end
- end.
-
- %% @private Formats traces to be output
- formatter(Tracer, Parent, Ref, FormatterFun, IOServer) ->
- process_flag(trap_exit, true),
- link(Tracer),
- Parent ! {Ref, linked},
- formatter(Tracer, IOServer, FormatterFun).
-
- formatter(Tracer, IOServer, FormatterFun) ->
- receive
- {'EXIT', Tracer, normal} ->
- io:format("Recon tracer rate limit tripped.~n"),
- exit(normal);
- {'EXIT', Tracer, Reason} ->
- exit(Reason);
- TraceMsg ->
- io:format(IOServer, FormatterFun(TraceMsg), []),
- formatter(Tracer, IOServer, FormatterFun)
- end.
-
-
- %%%%%%%%%%%%%%%%%%%%%%%
- %%% SETUP FUNCTIONS %%%
- %%%%%%%%%%%%%%%%%%%%%%%
-
- %% starts the tracer and formatter processes, and
- %% cleans them up before each call.
- setup(TracerFun, TracerArgs, FormatterFun, IOServer) ->
- clear(),
- Ref = make_ref(),
- Tracer = spawn_link(?MODULE, TracerFun, TracerArgs),
- register(recon_trace_tracer, Tracer),
- Format = spawn(?MODULE, formatter, [Tracer, self(), Ref, FormatterFun, IOServer]),
- register(recon_trace_formatter, Format),
- receive
- {Ref, linked} -> Tracer
- after 5000 ->
- error(setup_failed)
- end.
-
- %% Sets the traces in action
- trace_calls(TSpecs, Pid, Opts) ->
- {PidSpecs, TraceOpts, MatchOpts} = validate_opts(Opts),
- Matches = [begin
- {Arity, Spec} = validate_tspec(Mod, Fun, Args),
- erlang:trace_pattern({Mod, Fun, Arity}, Spec, MatchOpts)
- end || {Mod, Fun, Args} <- TSpecs],
- [erlang:trace(PidSpec, true, [call, {tracer, Pid} | TraceOpts])
- || PidSpec <- PidSpecs],
- lists:sum(Matches).
-
-
- %%%%%%%%%%%%%%%%%%
- %%% VALIDATION %%%
- %%%%%%%%%%%%%%%%%%
-
- validate_opts(Opts) ->
- PidSpecs = validate_pid_specs(proplists:get_value(pid, Opts, all)),
- Scope = proplists:get_value(scope, Opts, global),
- TraceOpts = case proplists:get_value(timestamp, Opts, formatter) of
- formatter -> [];
- trace -> [timestamp]
- end ++
- case proplists:get_value(args, Opts, args) of
- args -> [];
- arity -> [arity]
- end ++
- case proplists:get_value(return_to, Opts, undefined) of
- true when Scope =:= local ->
- [return_to];
- true when Scope =:= global ->
- io:format("Option return_to only works with option {scope, local}~n"),
- %% Set it anyway
- [return_to];
- _ ->
- []
- end,
- MatchOpts = [Scope],
- {PidSpecs, TraceOpts, MatchOpts}.
-
- %% Support the regular specs, but also allow `recon:pid_term()' and lists
- %% of further pid specs.
- -spec validate_pid_specs(pidspec() | [pidspec(),...]) ->
- [all | new | existing | pid(), ...].
- validate_pid_specs(all) -> [all];
- validate_pid_specs(existing) -> [existing];
- validate_pid_specs(new) -> [new];
- validate_pid_specs([Spec]) -> validate_pid_specs(Spec);
- validate_pid_specs(PidTerm = [Spec|Rest]) ->
- %% can be "<a.b.c>" or [pidspec()]
- try
- [recon_lib:term_to_pid(PidTerm)]
- catch
- error:function_clause ->
- validate_pid_specs(Spec) ++ validate_pid_specs(Rest)
- end;
- validate_pid_specs(PidTerm) ->
- %% has to be `recon:pid_term()'.
- [recon_lib:term_to_pid(PidTerm)].
-
- validate_tspec(Mod, Fun, Args) when is_function(Args) ->
- validate_tspec(Mod, Fun, fun_to_ms(Args));
- %% helper to save typing for common actions
- validate_tspec(Mod, Fun, return_trace) ->
- validate_tspec(Mod, Fun, [{'_', [], [{return_trace}]}]);
- validate_tspec(Mod, Fun, Args) ->
- BannedMods = ['_', ?MODULE, io, lists],
- %% The banned mod check can be bypassed by using
- %% match specs if you really feel like being dumb.
- case {lists:member(Mod, BannedMods), Args} of
- {true, '_'} -> error({dangerous_combo, {Mod,Fun,Args}});
- {true, []} -> error({dangerous_combo, {Mod,Fun,Args}});
- _ -> ok
- end,
- case Args of
- '_' -> {'_', true};
- _ when is_list(Args) -> {'_', Args};
- _ when Args >= 0, Args =< 255 -> {Args, true}
- end.
-
- validate_formatter(Opts) ->
- case proplists:get_value(formatter, Opts) of
- F when is_function(F, 1) -> F;
- _ -> fun format/1
- end.
-
- validate_io_server(Opts) ->
- proplists:get_value(io_server, Opts, group_leader()).
-
- %%%%%%%%%%%%%%%%%%%%%%%%
- %%% TRACE FORMATTING %%%
- %%%%%%%%%%%%%%%%%%%%%%%%
- %% Thanks Geoff Cant for the foundations for this.
- format(TraceMsg) ->
- {Type, Pid, {Hour,Min,Sec}, TraceInfo} = extract_info(TraceMsg),
- {FormatStr, FormatArgs} = case {Type, TraceInfo} of
- %% {trace, Pid, 'receive', Msg}
- {'receive', [Msg]} ->
- {"< ~p", [Msg]};
- %% {trace, Pid, send, Msg, To}
- {send, [Msg, To]} ->
- {" > ~p: ~p", [To, Msg]};
- %% {trace, Pid, send_to_non_existing_process, Msg, To}
- {send_to_non_existing_process, [Msg, To]} ->
- {" > (non_existent) ~p: ~p", [To, Msg]};
- %% {trace, Pid, call, {M, F, Args}}
- {call, [{M,F,Args}]} ->
- {"~p:~p~s", [M,F,format_args(Args)]};
- %% {trace, Pid, call, {M, F, Args}, Msg}
- {call, [{M,F,Args}, Msg]} ->
- {"~p:~p~s ~s", [M,F,format_args(Args), format_trace_output(Msg)]};
- %% {trace, Pid, return_to, {M, F, Arity}}
- {return_to, [{M,F,Arity}]} ->
- {" '--> ~p:~p/~p", [M,F,Arity]};
- %% {trace, Pid, return_from, {M, F, Arity}, ReturnValue}
- {return_from, [{M,F,Arity}, Return]} ->
- {"~p:~p/~p --> ~s", [M,F,Arity, format_trace_output(Return)]};
- %% {trace, Pid, exception_from, {M, F, Arity}, {Class, Value}}
- {exception_from, [{M,F,Arity}, {Class,Val}]} ->
- {"~p:~p/~p ~p ~p", [M,F,Arity, Class, Val]};
- %% {trace, Pid, spawn, Spawned, {M, F, Args}}
- {spawn, [Spawned, {M,F,Args}]} ->
- {"spawned ~p as ~p:~p~s", [Spawned, M, F, format_args(Args)]};
- %% {trace, Pid, exit, Reason}
- {exit, [Reason]} ->
- {"EXIT ~p", [Reason]};
- %% {trace, Pid, link, Pid2}
- {link, [Linked]} ->
- {"link(~p)", [Linked]};
- %% {trace, Pid, unlink, Pid2}
- {unlink, [Linked]} ->
- {"unlink(~p)", [Linked]};
- %% {trace, Pid, getting_linked, Pid2}
- {getting_linked, [Linker]} ->
- {"getting linked by ~p", [Linker]};
- %% {trace, Pid, getting_unlinked, Pid2}
- {getting_unlinked, [Unlinker]} ->
- {"getting unlinked by ~p", [Unlinker]};
- %% {trace, Pid, register, RegName}
- {register, [Name]} ->
- {"registered as ~p", [Name]};
- %% {trace, Pid, unregister, RegName}
- {unregister, [Name]} ->
- {"no longer registered as ~p", [Name]};
- %% {trace, Pid, in, {M, F, Arity} | 0}
- {in, [{M,F,Arity}]} ->
- {"scheduled in for ~p:~p/~p", [M,F,Arity]};
- {in, [0]} ->
- {"scheduled in", []};
- %% {trace, Pid, out, {M, F, Arity} | 0}
- {out, [{M,F,Arity}]} ->
- {"scheduled out from ~p:~p/~p", [M, F, Arity]};
- {out, [0]} ->
- {"scheduled out", []};
- %% {trace, Pid, gc_start, Info}
- {gc_start, [Info]} ->
- HeapSize = proplists:get_value(heap_size, Info),
- OldHeapSize = proplists:get_value(old_heap_size, Info),
- MbufSize = proplists:get_value(mbuf_size, Info),
- {"gc beginning -- heap ~p bytes",
- [HeapSize + OldHeapSize + MbufSize]};
- %% {trace, Pid, gc_end, Info}
- {gc_end, [Info]} ->
- HeapSize = proplists:get_value(heap_size, Info),
- OldHeapSize = proplists:get_value(old_heap_size, Info),
- MbufSize = proplists:get_value(mbuf_size, Info),
- {"gc finished -- heap ~p bytes",
- [HeapSize + OldHeapSize + MbufSize]};
- _ ->
- {"unknown trace type ~p -- ~p", [Type, TraceInfo]}
- end,
- io_lib:format("~n~p:~p:~9.6.0f ~p " ++ FormatStr ++ "~n",
- [Hour, Min, Sec, Pid] ++ FormatArgs).
-
- extract_info(TraceMsg) ->
- case tuple_to_list(TraceMsg) of
- [trace_ts, Pid, Type | Info] ->
- {TraceInfo, [Timestamp]} = lists:split(length(Info)-1, Info),
- {Type, Pid, to_hms(Timestamp), TraceInfo};
- [trace, Pid, Type | TraceInfo] ->
- {Type, Pid, to_hms(os:timestamp()), TraceInfo}
- end.
-
- to_hms(Stamp = {_, _, Micro}) ->
- {_,{H, M, Secs}} = calendar:now_to_local_time(Stamp),
- Seconds = Secs rem 60 + (Micro / 1000000),
- {H,M,Seconds};
- to_hms(_) ->
- {0,0,0}.
-
- format_args(Arity) when is_integer(Arity) ->
- [$/, integer_to_list(Arity)];
- format_args(Args) when is_list(Args) ->
- [$(, join(", ", [format_trace_output(Arg) || Arg <- Args]), $)].
-
-
- %% @doc formats call arguments and return values - most types are just printed out, except for
- %% tuples recognised as records, which mimic the source code syntax
- %% @end
- format_trace_output(Args) ->
- format_trace_output(recon_rec:is_active(), recon_map:is_active(), Args).
-
- format_trace_output(Recs, Args) ->
- format_trace_output(Recs, recon_map:is_active(), Args).
-
- format_trace_output(true, _, Args) when is_tuple(Args) ->
- recon_rec:format_tuple(Args);
- format_trace_output(false, true, Args) when is_tuple(Args) ->
- format_tuple(false, true, Args);
- format_trace_output(Recs, Maps, Args) when is_list(Args), Recs orelse Maps ->
- case io_lib:printable_list(Args) of
- true ->
- io_lib:format("~p", [Args]);
- false ->
- format_maybe_improper_list(Recs, Maps, Args)
- end;
- format_trace_output(Recs, true, Args) when is_map(Args) ->
- {Label, Map} = case recon_map:process_map(Args) of
- {L, M} -> {atom_to_list(L), M};
- M -> {"", M}
- end,
- ItemList = maps:to_list(Map),
- [Label,
- "#{",
- join(", ", [format_kv(Recs, true, Key, Val) || {Key, Val} <- ItemList]),
- "}"];
- format_trace_output(Recs, false, Args) when is_map(Args) ->
- ItemList = maps:to_list(Args),
- ["#{",
- join(", ", [format_kv(Recs, false, Key, Val) || {Key, Val} <- ItemList]),
- "}"];
- format_trace_output(_, _, Args) ->
- io_lib:format("~p", [Args]).
-
- format_kv(Recs, Maps, Key, Val) ->
- [format_trace_output(Recs, Maps, Key), "=>", format_trace_output(Recs, Maps, Val)].
-
-
- format_tuple(Recs, Maps, Tup) ->
- [${ | format_tuple_(Recs, Maps, tuple_to_list(Tup))].
-
- format_tuple_(_Recs, _Maps, []) ->
- "}";
- format_tuple_(Recs, Maps, [H|T]) ->
- [format_trace_output(Recs, Maps, H), $,,
- format_tuple_(Recs, Maps, T)].
-
-
- format_maybe_improper_list(Recs, Maps, List) ->
- [$[ | format_maybe_improper_list_(Recs, Maps, List)].
-
- format_maybe_improper_list_(_, _, []) ->
- "]";
- format_maybe_improper_list_(Recs, Maps, [H|[]]) ->
- [format_trace_output(Recs, Maps, H), $]];
- format_maybe_improper_list_(Recs, Maps, [H|T]) when is_list(T) ->
- [format_trace_output(Recs, Maps, H), $,,
- format_maybe_improper_list_(Recs, Maps, T)];
- format_maybe_improper_list_(Recs, Maps, [H|T]) when not is_list(T) ->
- %% Handling improper lists
- [format_trace_output(Recs, Maps, H), $|,
- format_trace_output(Recs, Maps, T), $]].
-
-
- %%%%%%%%%%%%%%%
- %%% HELPERS %%%
- %%%%%%%%%%%%%%%
-
- maybe_kill(Name) ->
- case whereis(Name) of
- undefined ->
- ok;
- Pid ->
- unlink(Pid),
- exit(Pid, kill),
- wait_for_death(Pid, Name)
- end.
-
- wait_for_death(Pid, Name) ->
- case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
- true ->
- timer:sleep(10),
- wait_for_death(Pid, Name);
- false ->
- ok
- end.
-
- %% Borrowed from dbg
- fun_to_ms(ShellFun) when is_function(ShellFun) ->
- case erl_eval:fun_data(ShellFun) of
- {fun_data,ImportList,Clauses} ->
- case ms_transform:transform_from_shell(
- dbg,Clauses,ImportList) of
- {error,[{_,[{_,_,Code}|_]}|_],_} ->
- io:format("Error: ~s~n",
- [ms_transform:format_error(Code)]),
- {error,transform_error};
- Else ->
- Else
- end;
- false ->
- exit(shell_funs_only)
- end.
-
-
- -ifdef(OTP_RELEASE).
- -spec join(term(), [term()]) -> [term()].
- join(Sep, List) ->
- lists:join(Sep, List).
- -else.
- -spec join(string(), [string()]) -> string().
- join(Sep, List) ->
- string:join(List, Sep).
- -endif.
|