You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

733 lines
28 KiB

пре 4 година
  1. %%% @author Fred Hebert <mononcqc@ferd.ca>
  2. %%% [http://ferd.ca/]
  3. %%% @doc
  4. %%% `recon_trace' is a module that handles tracing in a safe manner for single
  5. %%% Erlang nodes, currently for function calls only. Functionality includes:
  6. %%%
  7. %%% <ul>
  8. %%% <li>Nicer to use interface (arguably) than `dbg' or trace BIFs.</li>
  9. %%% <li>Protection against dumb decisions (matching all calls on a node
  10. %%% being traced, for example)</li>
  11. %%% <li>Adding safe guards in terms of absolute trace count or
  12. %%% rate-limitting</li>
  13. %%% <li>Nicer formatting than default traces</li>
  14. %%% </ul>
  15. %%%
  16. %%% == Tracing Erlang Code ==
  17. %%%
  18. %%% The Erlang Trace BIFs allow to trace any Erlang code at all. They work in
  19. %%% two parts: pid specifications, and trace patterns.
  20. %%%
  21. %%% Pid specifications let you decide which processes to target. They can be
  22. %%% specific pids, `all' pids, `existing' pids, or `new' pids (those not
  23. %%% spawned at the time of the function call).
  24. %%%
  25. %%% The trace patterns represent functions. Functions can be specified in two
  26. %%% parts: specifying the modules, functions, and arguments, and then with
  27. %%% Erlang match specifications to add constraints to arguments (see
  28. %%% {@link calls/3} for details).
  29. %%%
  30. %%% What defines whether you get traced or not is the intersection of both:
  31. %%%
  32. %%% ```
  33. %%% _,--------,_ _,--------,_
  34. %%% ,-' `-,,-' `-,
  35. %%% ,-' ,-' '-, `-,
  36. %%% | Matching -' '- Matching |
  37. %%% | Pids | Getting | Trace |
  38. %%% | | Traced | Patterns |
  39. %%% | -, ,- |
  40. %%% '-, '-, ,-' ,-'
  41. %%% '-,_ _,-''-,_ _,-'
  42. %%% '--------' '--------'
  43. %%% '''
  44. %%%
  45. %%% If either the pid specification excludes a process or a trace pattern
  46. %%% excludes a given call, no trace will be received.
  47. %%%
  48. %%% == Example Session ==
  49. %%%
  50. %%% First let's trace the `queue:new' functions in any process:
  51. %%%
  52. %%% ```
  53. %%% 1> recon_trace:calls({queue, new, '_'}, 1).
  54. %%% 1
  55. %%% 13:14:34.086078 <0.44.0> queue:new()
  56. %%% Recon tracer rate limit tripped.
  57. %%% '''
  58. %%%
  59. %%% The limit was set to `1' trace message at most, and `recon' let us
  60. %%% know when that limit was reached.
  61. %%%
  62. %%% Let's instead look for all the `queue:in/2' calls, to see what it is
  63. %%% we're inserting in queues:
  64. %%%
  65. %%% ```
  66. %%% 2> recon_trace:calls({queue, in, 2}, 1).
  67. %%% 1
  68. %%% 13:14:55.365157 <0.44.0> queue:in(a, {[],[]})
  69. %%% Recon tracer rate limit tripped.
  70. %%% '''
  71. %%%
  72. %%% In order to see the content we want, we should change the trace patterns
  73. %%% to use a `fun' that matches on all arguments in a list (`_') and returns
  74. %%% `return_trace()'. This last part will generate a second trace for each
  75. %%% call that includes the return value:
  76. %%%
  77. %%% ```
  78. %%% 3> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3).
  79. %%% 1
  80. %%%
  81. %%% 13:15:27.655132 <0.44.0> queue:in(a, {[],[]})
  82. %%%
  83. %%% 13:15:27.655467 <0.44.0> queue:in/2 --> {[a],[]}
  84. %%%
  85. %%% 13:15:27.757921 <0.44.0> queue:in(a, {[],[]})
  86. %%% Recon tracer rate limit tripped.
  87. %%% '''
  88. %%%
  89. %%% Matching on argument lists can be done in a more complex manner:
  90. %%%
  91. %%% ```
  92. %%% 4> recon_trace:calls(
  93. %%% 4> {queue, '_', fun([A,_]) when is_list(A); is_integer(A) andalso A > 1 -> return_trace() end},
  94. %%% 4> {10,100}
  95. %%% 4> ).
  96. %%% 32
  97. %%%
  98. %%% 13:24:21.324309 <0.38.0> queue:in(3, {[],[]})
  99. %%%
  100. %%% 13:24:21.371473 <0.38.0> queue:in/2 --> {[3],[]}
  101. %%%
  102. %%% 13:25:14.694865 <0.53.0> queue:split(4, {[10,9,8,7],[1,2,3,4,5,6]})
  103. %%%
  104. %%% 13:25:14.695194 <0.53.0> queue:split/2 --> {{[4,3,2],[1]},{[10,9,8,7],[5,6]}}
  105. %%%
  106. %%% 5> recon_trace:clear().
  107. %%% ok
  108. %%% '''
  109. %%%
  110. %%% Note that in the pattern above, no specific function (<code>'_'</code>) was
  111. %%% matched against. Instead, the `fun' used restricted functions to those
  112. %%% having two arguments, the first of which is either a list or an integer
  113. %%% greater than `1'.
  114. %%%
  115. %%% The limit was also set using `{10,100}' instead of an integer, making the
  116. %%% rate-limitting at 10 messages per 100 milliseconds, instead of an absolute
  117. %%% value.
  118. %%%
  119. %%% Any tracing can be manually interrupted by calling `recon_trace:clear()',
  120. %%% or killing the shell process.
  121. %%%
  122. %%% Be aware that extremely broad patterns with lax rate-limitting (or very
  123. %%% high absolute limits) may impact your node's stability in ways
  124. %%% `recon_trace' cannot easily help you with.
  125. %%%
  126. %%% In doubt, start with the most restrictive tracing possible, with low
  127. %%% limits, and progressively increase your scope.
  128. %%%
  129. %%% See {@link calls/3} for more details and tracing possibilities.
  130. %%%
  131. %%% == Structure ==
  132. %%%
  133. %%% This library is production-safe due to taking the following structure for
  134. %%% tracing:
  135. %%%
  136. %%% ```
  137. %%% [IO/Group leader] <---------------------,
  138. %%% | |
  139. %%% [shell] ---> [tracer process] ----> [formatter]
  140. %%% '''
  141. %%%
  142. %%% The tracer process receives trace messages from the node, and enforces
  143. %%% limits in absolute terms or trace rates, before forwarding the messages
  144. %%% to the formatter. This is done so the tracer can do as little work as
  145. %%% possible and never block while building up a large mailbox.
  146. %%%
  147. %%% The tracer process is linked to the shell, and the formatter to the
  148. %%% tracer process. The formatter also traps exits to be able to handle
  149. %%% all received trace messages until the tracer termination, but will then
  150. %%% shut down as soon as possible.
  151. %%%
  152. %%% In case the operator is tracing from a remote shell which gets
  153. %%% disconnected, the links between the shell and the tracer should make it
  154. %%% so tracing is automatically turned off once you disconnect.
  155. %%%
  156. %%% If sending output to the Group Leader is not desired, you may specify
  157. %%% a different pid() via the option `io_server' in the {@link calls/3} function.
  158. %%% For instance to write the traces to a file you can do something like
  159. %%%
  160. %%% ```
  161. %%% 1> {ok, Dev} = file:open("/tmp/trace",[write]).
  162. %%% 2> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3, [{io_server, Dev}]).
  163. %%% 1
  164. %%% 3>
  165. %%% Recon tracer rate limit tripped.
  166. %%% 4> file:close(Dev).
  167. %%% '''
  168. %%%
  169. %%% The only output still sent to the Group Leader is the rate limit being
  170. %%% tripped, and any errors. The rest will be sent to the other IO
  171. %%% server (see [http://erlang.org/doc/apps/stdlib/io_protocol.html]).
  172. %%%
  173. %%% == Record Printing ==
  174. %%%
  175. %%% Thanks to code contributed by Bartek Górny, record printing can be added
  176. %%% to traces by first importing records in an active session with
  177. %%% `recon_rec:import([Module, ...])', after which the records declared in
  178. %%% the module list will be supported.
  179. %%% @end
  180. -module(recon_trace).
  181. %% API
  182. -export([clear/0, calls/2, calls/3]).
  183. -export([format/1]).
  184. %% Internal exports
  185. -export([count_tracer/1, rate_tracer/2, formatter/5, format_trace_output/1, format_trace_output/2]).
  186. -type matchspec() :: [{[term()] | '_', [term()], [term()]}].
  187. -type shellfun() :: fun((_) -> term()).
  188. -type formatterfun() :: fun((_) -> iodata()).
  189. -type millisecs() :: non_neg_integer().
  190. -type pidspec() :: all | existing | new | recon:pid_term().
  191. -type max_traces() :: non_neg_integer().
  192. -type max_rate() :: {max_traces(), millisecs()}.
  193. %% trace options
  194. -type options() :: [ {pid, pidspec() | [pidspec(),...]} % default: all
  195. | {timestamp, formatter | trace} % default: formatter
  196. | {args, args | arity} % default: args
  197. | {io_server, pid()} % default: group_leader()
  198. | {formatter, formatterfun()} % default: internal formatter
  199. | return_to | {return_to, boolean()} % default: false
  200. %% match pattern options
  201. | {scope, global | local} % default: global
  202. ].
  203. -type mod() :: '_' | module().
  204. -type fn() :: '_' | atom().
  205. -type args() :: '_' | 0..255 | return_trace | matchspec() | shellfun().
  206. -type tspec() :: {mod(), fn(), args()}.
  207. -type max() :: max_traces() | max_rate().
  208. -type num_matches() :: non_neg_integer().
  209. -export_type([mod/0, fn/0, args/0, tspec/0, num_matches/0, options/0,
  210. max_traces/0, max_rate/0]).
  211. %%%%%%%%%%%%%%
  212. %%% PUBLIC %%%
  213. %%%%%%%%%%%%%%
  214. %% @doc Stops all tracing at once.
  215. -spec clear() -> ok.
  216. clear() ->
  217. erlang:trace(all, false, [all]),
  218. erlang:trace_pattern({'_','_','_'}, false, [local,meta,call_count,call_time]),
  219. erlang:trace_pattern({'_','_','_'}, false, []), % unsets global
  220. maybe_kill(recon_trace_tracer),
  221. maybe_kill(recon_trace_formatter),
  222. ok.
  223. %% @equiv calls({Mod, Fun, Args}, Max, [])
  224. -spec calls(tspec() | [tspec(),...], max()) -> num_matches().
  225. calls({Mod, Fun, Args}, Max) ->
  226. calls([{Mod,Fun,Args}], Max, []);
  227. calls(TSpecs = [_|_], Max) ->
  228. calls(TSpecs, Max, []).
  229. %% @doc Allows to set trace patterns and pid specifications to trace
  230. %% function calls.
  231. %%
  232. %% The basic calls take the trace patterns as tuples of the form
  233. %% `{Module, Function, Args}' where:
  234. %%
  235. %% <ul>
  236. %% <li>`Module' is any atom representing a module</li>
  237. %% <li>`Function' is any atom representing a function, or the wildcard
  238. %% <code>'_'</code></li>
  239. %% <li>`Args' is either the arity of a function (`0..255'), a wildcard
  240. %% pattern (<code>'_'</code>), a
  241. %% <a href="http://learnyousomeerlang.com/ets#you-have-been-selected">match specification</a>,
  242. %% or a function from a shell session that can be transformed into
  243. %% a match specification</li>
  244. %% </ul>
  245. %%
  246. %% There is also an argument specifying either a maximal count (a number)
  247. %% of trace messages to be received, or a maximal frequency (`{Num, Millisecs}').
  248. %%
  249. %% Here are examples of things to trace:
  250. %%
  251. %% <ul>
  252. %% <li>All calls from the `queue' module, with 10 calls printed at most:
  253. %% ``recon_trace:calls({queue, '_', '_'}, 10)''</li>
  254. %% <li>All calls to `lists:seq(A,B)', with 100 calls printed at most:
  255. %% `recon_trace:calls({lists, seq, 2}, 100)'</li>
  256. %% <li>All calls to `lists:seq(A,B)', with 100 calls per second at most:
  257. %% `recon_trace:calls({lists, seq, 2}, {100, 1000})'</li>
  258. %% <li>All calls to `lists:seq(A,B,2)' (all sequences increasing by two)
  259. %% with 100 calls at most:
  260. %% `recon_trace:calls({lists, seq, fun([_,_,2]) -> ok end}, 100)'</li>
  261. %% <li>All calls to `iolist_to_binary/1' made with a binary as an argument
  262. %% already (kind of useless conversion!):
  263. %% `recon_trace:calls({erlang, iolist_to_binary, fun([X]) when is_binary(X) -> ok end}, 10)'</li>
  264. %% <li>Calls to the queue module only in a given process `Pid', at a rate
  265. %% of 50 per second at most:
  266. %% ``recon_trace:calls({queue, '_', '_'}, {50,1000}, [{pid, Pid}])''</li>
  267. %% <li>Print the traces with the function arity instead of literal arguments:
  268. %% `recon_trace:calls(TSpec, Max, [{args, arity}])'</li>
  269. %% <li>Matching the `filter/2' functions of both `dict' and `lists' modules,
  270. %% across new processes only:
  271. %% `recon_trace:calls([{dict,filter,2},{lists,filter,2}], 10, [{pid, new}])'</li>
  272. %% <li>Tracing the `handle_call/3' functions of a given module for all new processes,
  273. %% and those of an existing one registered with `gproc':
  274. %% `recon_trace:calls({Mod,handle_call,3}, {10,100}, [{pid, [{via, gproc, Name}, new]}'</li>
  275. %% <li>Show the result of a given function call:
  276. %% `recon_trace:calls({Mod,Fun,fun(_) -> return_trace() end}, Max, Opts)'
  277. %% or
  278. %% ``recon_trace:calls({Mod,Fun,[{'_', [], [{return_trace}]}]}, Max, Opts)'',
  279. %% the important bit being the `return_trace()' call or the
  280. %% `{return_trace}' match spec value.
  281. %% A short-hand version for this pattern of 'match anything, trace everything'
  282. %% for a function is `recon_trace:calls({Mod, Fun, return_trace})'. </li>
  283. %% </ul>
  284. %%
  285. %% There's a few more combination possible, with multiple trace patterns per call, and more
  286. %% options:
  287. %%
  288. %% <ul>
  289. %% <li>`{pid, PidSpec}': which processes to trace. Valid options is any of
  290. %% `all', `new', `existing', or a process descriptor (`{A,B,C}',
  291. %% `"<A.B.C>"', an atom representing a name, `{global, Name}',
  292. %% `{via, Registrar, Name}', or a pid). It's also possible to specify
  293. %% more than one by putting them in a list.</li>
  294. %% <li>`{timestamp, formatter | trace}': by default, the formatter process
  295. %% adds timestamps to messages received. If accurate timestamps are
  296. %% required, it's possible to force the usage of timestamps within
  297. %% trace messages by adding the option `{timestamp, trace}'.</li>
  298. %% <li>`{args, arity | args}': whether to print arity in function calls
  299. %% or their (by default) literal representation.</li>
  300. %% <li>`{scope, global | local}': by default, only 'global' (fully qualified
  301. %% function calls) are traced, not calls made internally. To force tracing
  302. %% of local calls, pass in `{scope, local}'. This is useful whenever
  303. %% you want to track the changes of code in a process that isn't called
  304. %% with `Module:Fun(Args)', but just `Fun(Args)'.</li>
  305. %% <li>`{formatter, fun(Term) -> io_data() end}': override the default
  306. %% formatting functionality provided by recon.</li>
  307. %% <li>`{io_server, pid() | atom()}': by default, recon logs to the current
  308. %% group leader, usually the shell. This option allows to redirect
  309. %% trace output to a different IO server (such as a file handle).</li>
  310. %% <li>`return_to': If this option is set (in conjunction with the match
  311. %% option `{scope, local}'), the function to which the value is returned
  312. %% is output in a trace. Note that this is distinct from giving the
  313. %% *caller* since exception handling or calls in tail position may
  314. %% hide the original caller.</li>
  315. %% </ul>
  316. %%
  317. %% Also note that putting extremely large `Max' values (i.e. `99999999' or
  318. %% `{10000,1}') will probably negate most of the safe-guarding this library
  319. %% does and be dangerous to your node. Similarly, tracing extremely large
  320. %% amounts of function calls (all of them, or all of `io' for example)
  321. %% can be risky if more trace messages are generated than any process on
  322. %% the node could ever handle, despite the precautions taken by this library.
  323. %% @end
  324. -spec calls(tspec() | [tspec(),...], max(), options()) -> num_matches().
  325. calls({Mod, Fun, Args}, Max, Opts) ->
  326. calls([{Mod,Fun,Args}], Max, Opts);
  327. calls(TSpecs = [_|_], {Max, Time}, Opts) ->
  328. Pid = setup(rate_tracer, [Max, Time],
  329. validate_formatter(Opts), validate_io_server(Opts)),
  330. trace_calls(TSpecs, Pid, Opts);
  331. calls(TSpecs = [_|_], Max, Opts) ->
  332. Pid = setup(count_tracer, [Max],
  333. validate_formatter(Opts), validate_io_server(Opts)),
  334. trace_calls(TSpecs, Pid, Opts).
  335. %%%%%%%%%%%%%%%%%%%%%%%
  336. %%% PRIVATE EXPORTS %%%
  337. %%%%%%%%%%%%%%%%%%%%%%%
  338. %% @private Stops when N trace messages have been received
  339. count_tracer(0) ->
  340. exit(normal);
  341. count_tracer(N) ->
  342. receive
  343. Msg ->
  344. recon_trace_formatter ! Msg,
  345. count_tracer(N-1)
  346. end.
  347. %% @private Stops whenever the trace message rates goes higher than
  348. %% `Max' messages in `Time' milliseconds. Note that if the rate
  349. %% proposed is higher than what the IO system of the formatter
  350. %% can handle, this can still put a node at risk.
  351. %%
  352. %% It is recommended to try stricter rates to begin with.
  353. rate_tracer(Max, Time) -> rate_tracer(Max, Time, 0, os:timestamp()).
  354. rate_tracer(Max, Time, Count, Start) ->
  355. receive
  356. Msg ->
  357. recon_trace_formatter ! Msg,
  358. Now = os:timestamp(),
  359. Delay = timer:now_diff(Now, Start) div 1000,
  360. if Delay > Time -> rate_tracer(Max, Time, 0, Now)
  361. ; Max > Count -> rate_tracer(Max, Time, Count+1, Start)
  362. ; Max =:= Count -> exit(normal)
  363. end
  364. end.
  365. %% @private Formats traces to be output
  366. formatter(Tracer, Parent, Ref, FormatterFun, IOServer) ->
  367. process_flag(trap_exit, true),
  368. link(Tracer),
  369. Parent ! {Ref, linked},
  370. formatter(Tracer, IOServer, FormatterFun).
  371. formatter(Tracer, IOServer, FormatterFun) ->
  372. receive
  373. {'EXIT', Tracer, normal} ->
  374. io:format("Recon tracer rate limit tripped.~n"),
  375. exit(normal);
  376. {'EXIT', Tracer, Reason} ->
  377. exit(Reason);
  378. TraceMsg ->
  379. io:format(IOServer, FormatterFun(TraceMsg), []),
  380. formatter(Tracer, IOServer, FormatterFun)
  381. end.
  382. %%%%%%%%%%%%%%%%%%%%%%%
  383. %%% SETUP FUNCTIONS %%%
  384. %%%%%%%%%%%%%%%%%%%%%%%
  385. %% starts the tracer and formatter processes, and
  386. %% cleans them up before each call.
  387. setup(TracerFun, TracerArgs, FormatterFun, IOServer) ->
  388. clear(),
  389. Ref = make_ref(),
  390. Tracer = spawn_link(?MODULE, TracerFun, TracerArgs),
  391. register(recon_trace_tracer, Tracer),
  392. Format = spawn(?MODULE, formatter, [Tracer, self(), Ref, FormatterFun, IOServer]),
  393. register(recon_trace_formatter, Format),
  394. receive
  395. {Ref, linked} -> Tracer
  396. after 5000 ->
  397. error(setup_failed)
  398. end.
  399. %% Sets the traces in action
  400. trace_calls(TSpecs, Pid, Opts) ->
  401. {PidSpecs, TraceOpts, MatchOpts} = validate_opts(Opts),
  402. Matches = [begin
  403. {Arity, Spec} = validate_tspec(Mod, Fun, Args),
  404. erlang:trace_pattern({Mod, Fun, Arity}, Spec, MatchOpts)
  405. end || {Mod, Fun, Args} <- TSpecs],
  406. [erlang:trace(PidSpec, true, [call, {tracer, Pid} | TraceOpts])
  407. || PidSpec <- PidSpecs],
  408. lists:sum(Matches).
  409. %%%%%%%%%%%%%%%%%%
  410. %%% VALIDATION %%%
  411. %%%%%%%%%%%%%%%%%%
  412. validate_opts(Opts) ->
  413. PidSpecs = validate_pid_specs(proplists:get_value(pid, Opts, all)),
  414. Scope = proplists:get_value(scope, Opts, global),
  415. TraceOpts = case proplists:get_value(timestamp, Opts, formatter) of
  416. formatter -> [];
  417. trace -> [timestamp]
  418. end ++
  419. case proplists:get_value(args, Opts, args) of
  420. args -> [];
  421. arity -> [arity]
  422. end ++
  423. case proplists:get_value(return_to, Opts, undefined) of
  424. true when Scope =:= local ->
  425. [return_to];
  426. true when Scope =:= global ->
  427. io:format("Option return_to only works with option {scope, local}~n"),
  428. %% Set it anyway
  429. [return_to];
  430. _ ->
  431. []
  432. end,
  433. MatchOpts = [Scope],
  434. {PidSpecs, TraceOpts, MatchOpts}.
  435. %% Support the regular specs, but also allow `recon:pid_term()' and lists
  436. %% of further pid specs.
  437. -spec validate_pid_specs(pidspec() | [pidspec(),...]) ->
  438. [all | new | existing | pid(), ...].
  439. validate_pid_specs(all) -> [all];
  440. validate_pid_specs(existing) -> [existing];
  441. validate_pid_specs(new) -> [new];
  442. validate_pid_specs([Spec]) -> validate_pid_specs(Spec);
  443. validate_pid_specs(PidTerm = [Spec|Rest]) ->
  444. %% can be "<a.b.c>" or [pidspec()]
  445. try
  446. [recon_lib:term_to_pid(PidTerm)]
  447. catch
  448. error:function_clause ->
  449. validate_pid_specs(Spec) ++ validate_pid_specs(Rest)
  450. end;
  451. validate_pid_specs(PidTerm) ->
  452. %% has to be `recon:pid_term()'.
  453. [recon_lib:term_to_pid(PidTerm)].
  454. validate_tspec(Mod, Fun, Args) when is_function(Args) ->
  455. validate_tspec(Mod, Fun, fun_to_ms(Args));
  456. %% helper to save typing for common actions
  457. validate_tspec(Mod, Fun, return_trace) ->
  458. validate_tspec(Mod, Fun, [{'_', [], [{return_trace}]}]);
  459. validate_tspec(Mod, Fun, Args) ->
  460. BannedMods = ['_', ?MODULE, io, lists],
  461. %% The banned mod check can be bypassed by using
  462. %% match specs if you really feel like being dumb.
  463. case {lists:member(Mod, BannedMods), Args} of
  464. {true, '_'} -> error({dangerous_combo, {Mod,Fun,Args}});
  465. {true, []} -> error({dangerous_combo, {Mod,Fun,Args}});
  466. _ -> ok
  467. end,
  468. case Args of
  469. '_' -> {'_', true};
  470. _ when is_list(Args) -> {'_', Args};
  471. _ when Args >= 0, Args =< 255 -> {Args, true}
  472. end.
  473. validate_formatter(Opts) ->
  474. case proplists:get_value(formatter, Opts) of
  475. F when is_function(F, 1) -> F;
  476. _ -> fun format/1
  477. end.
  478. validate_io_server(Opts) ->
  479. proplists:get_value(io_server, Opts, group_leader()).
  480. %%%%%%%%%%%%%%%%%%%%%%%%
  481. %%% TRACE FORMATTING %%%
  482. %%%%%%%%%%%%%%%%%%%%%%%%
  483. %% Thanks Geoff Cant for the foundations for this.
  484. format(TraceMsg) ->
  485. {Type, Pid, {Hour,Min,Sec}, TraceInfo} = extract_info(TraceMsg),
  486. {FormatStr, FormatArgs} = case {Type, TraceInfo} of
  487. %% {trace, Pid, 'receive', Msg}
  488. {'receive', [Msg]} ->
  489. {"< ~p", [Msg]};
  490. %% {trace, Pid, send, Msg, To}
  491. {send, [Msg, To]} ->
  492. {" > ~p: ~p", [To, Msg]};
  493. %% {trace, Pid, send_to_non_existing_process, Msg, To}
  494. {send_to_non_existing_process, [Msg, To]} ->
  495. {" > (non_existent) ~p: ~p", [To, Msg]};
  496. %% {trace, Pid, call, {M, F, Args}}
  497. {call, [{M,F,Args}]} ->
  498. {"~p:~p~s", [M,F,format_args(Args)]};
  499. %% {trace, Pid, call, {M, F, Args}, Msg}
  500. {call, [{M,F,Args}, Msg]} ->
  501. {"~p:~p~s ~s", [M,F,format_args(Args), format_trace_output(Msg)]};
  502. %% {trace, Pid, return_to, {M, F, Arity}}
  503. {return_to, [{M,F,Arity}]} ->
  504. {" '--> ~p:~p/~p", [M,F,Arity]};
  505. %% {trace, Pid, return_from, {M, F, Arity}, ReturnValue}
  506. {return_from, [{M,F,Arity}, Return]} ->
  507. {"~p:~p/~p --> ~s", [M,F,Arity, format_trace_output(Return)]};
  508. %% {trace, Pid, exception_from, {M, F, Arity}, {Class, Value}}
  509. {exception_from, [{M,F,Arity}, {Class,Val}]} ->
  510. {"~p:~p/~p ~p ~p", [M,F,Arity, Class, Val]};
  511. %% {trace, Pid, spawn, Spawned, {M, F, Args}}
  512. {spawn, [Spawned, {M,F,Args}]} ->
  513. {"spawned ~p as ~p:~p~s", [Spawned, M, F, format_args(Args)]};
  514. %% {trace, Pid, exit, Reason}
  515. {exit, [Reason]} ->
  516. {"EXIT ~p", [Reason]};
  517. %% {trace, Pid, link, Pid2}
  518. {link, [Linked]} ->
  519. {"link(~p)", [Linked]};
  520. %% {trace, Pid, unlink, Pid2}
  521. {unlink, [Linked]} ->
  522. {"unlink(~p)", [Linked]};
  523. %% {trace, Pid, getting_linked, Pid2}
  524. {getting_linked, [Linker]} ->
  525. {"getting linked by ~p", [Linker]};
  526. %% {trace, Pid, getting_unlinked, Pid2}
  527. {getting_unlinked, [Unlinker]} ->
  528. {"getting unlinked by ~p", [Unlinker]};
  529. %% {trace, Pid, register, RegName}
  530. {register, [Name]} ->
  531. {"registered as ~p", [Name]};
  532. %% {trace, Pid, unregister, RegName}
  533. {unregister, [Name]} ->
  534. {"no longer registered as ~p", [Name]};
  535. %% {trace, Pid, in, {M, F, Arity} | 0}
  536. {in, [{M,F,Arity}]} ->
  537. {"scheduled in for ~p:~p/~p", [M,F,Arity]};
  538. {in, [0]} ->
  539. {"scheduled in", []};
  540. %% {trace, Pid, out, {M, F, Arity} | 0}
  541. {out, [{M,F,Arity}]} ->
  542. {"scheduled out from ~p:~p/~p", [M, F, Arity]};
  543. {out, [0]} ->
  544. {"scheduled out", []};
  545. %% {trace, Pid, gc_start, Info}
  546. {gc_start, [Info]} ->
  547. HeapSize = proplists:get_value(heap_size, Info),
  548. OldHeapSize = proplists:get_value(old_heap_size, Info),
  549. MbufSize = proplists:get_value(mbuf_size, Info),
  550. {"gc beginning -- heap ~p bytes",
  551. [HeapSize + OldHeapSize + MbufSize]};
  552. %% {trace, Pid, gc_end, Info}
  553. {gc_end, [Info]} ->
  554. HeapSize = proplists:get_value(heap_size, Info),
  555. OldHeapSize = proplists:get_value(old_heap_size, Info),
  556. MbufSize = proplists:get_value(mbuf_size, Info),
  557. {"gc finished -- heap ~p bytes",
  558. [HeapSize + OldHeapSize + MbufSize]};
  559. _ ->
  560. {"unknown trace type ~p -- ~p", [Type, TraceInfo]}
  561. end,
  562. io_lib:format("~n~p:~p:~9.6.0f ~p " ++ FormatStr ++ "~n",
  563. [Hour, Min, Sec, Pid] ++ FormatArgs).
  564. extract_info(TraceMsg) ->
  565. case tuple_to_list(TraceMsg) of
  566. [trace_ts, Pid, Type | Info] ->
  567. {TraceInfo, [Timestamp]} = lists:split(length(Info)-1, Info),
  568. {Type, Pid, to_hms(Timestamp), TraceInfo};
  569. [trace, Pid, Type | TraceInfo] ->
  570. {Type, Pid, to_hms(os:timestamp()), TraceInfo}
  571. end.
  572. to_hms(Stamp = {_, _, Micro}) ->
  573. {_,{H, M, Secs}} = calendar:now_to_local_time(Stamp),
  574. Seconds = Secs rem 60 + (Micro / 1000000),
  575. {H,M,Seconds};
  576. to_hms(_) ->
  577. {0,0,0}.
  578. format_args(Arity) when is_integer(Arity) ->
  579. [$/, integer_to_list(Arity)];
  580. format_args(Args) when is_list(Args) ->
  581. [$(, join(", ", [format_trace_output(Arg) || Arg <- Args]), $)].
  582. %% @doc formats call arguments and return values - most types are just printed out, except for
  583. %% tuples recognised as records, which mimic the source code syntax
  584. %% @end
  585. format_trace_output(Args) ->
  586. format_trace_output(recon_rec:is_active(), recon_map:is_active(), Args).
  587. format_trace_output(Recs, Args) ->
  588. format_trace_output(Recs, recon_map:is_active(), Args).
  589. format_trace_output(true, _, Args) when is_tuple(Args) ->
  590. recon_rec:format_tuple(Args);
  591. format_trace_output(false, true, Args) when is_tuple(Args) ->
  592. format_tuple(false, true, Args);
  593. format_trace_output(Recs, Maps, Args) when is_list(Args), Recs orelse Maps ->
  594. case io_lib:printable_list(Args) of
  595. true ->
  596. io_lib:format("~p", [Args]);
  597. false ->
  598. format_maybe_improper_list(Recs, Maps, Args)
  599. end;
  600. format_trace_output(Recs, true, Args) when is_map(Args) ->
  601. {Label, Map} = case recon_map:process_map(Args) of
  602. {L, M} -> {atom_to_list(L), M};
  603. M -> {"", M}
  604. end,
  605. ItemList = maps:to_list(Map),
  606. [Label,
  607. "#{",
  608. join(", ", [format_kv(Recs, true, Key, Val) || {Key, Val} <- ItemList]),
  609. "}"];
  610. format_trace_output(Recs, false, Args) when is_map(Args) ->
  611. ItemList = maps:to_list(Args),
  612. ["#{",
  613. join(", ", [format_kv(Recs, false, Key, Val) || {Key, Val} <- ItemList]),
  614. "}"];
  615. format_trace_output(_, _, Args) ->
  616. io_lib:format("~p", [Args]).
  617. format_kv(Recs, Maps, Key, Val) ->
  618. [format_trace_output(Recs, Maps, Key), "=>", format_trace_output(Recs, Maps, Val)].
  619. format_tuple(Recs, Maps, Tup) ->
  620. [${ | format_tuple_(Recs, Maps, tuple_to_list(Tup))].
  621. format_tuple_(_Recs, _Maps, []) ->
  622. "}";
  623. format_tuple_(Recs, Maps, [H|T]) ->
  624. [format_trace_output(Recs, Maps, H), $,,
  625. format_tuple_(Recs, Maps, T)].
  626. format_maybe_improper_list(Recs, Maps, List) ->
  627. [$[ | format_maybe_improper_list_(Recs, Maps, List)].
  628. format_maybe_improper_list_(_, _, []) ->
  629. "]";
  630. format_maybe_improper_list_(Recs, Maps, [H|[]]) ->
  631. [format_trace_output(Recs, Maps, H), $]];
  632. format_maybe_improper_list_(Recs, Maps, [H|T]) when is_list(T) ->
  633. [format_trace_output(Recs, Maps, H), $,,
  634. format_maybe_improper_list_(Recs, Maps, T)];
  635. format_maybe_improper_list_(Recs, Maps, [H|T]) when not is_list(T) ->
  636. %% Handling improper lists
  637. [format_trace_output(Recs, Maps, H), $|,
  638. format_trace_output(Recs, Maps, T), $]].
  639. %%%%%%%%%%%%%%%
  640. %%% HELPERS %%%
  641. %%%%%%%%%%%%%%%
  642. maybe_kill(Name) ->
  643. case whereis(Name) of
  644. undefined ->
  645. ok;
  646. Pid ->
  647. unlink(Pid),
  648. exit(Pid, kill),
  649. wait_for_death(Pid, Name)
  650. end.
  651. wait_for_death(Pid, Name) ->
  652. case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
  653. true ->
  654. timer:sleep(10),
  655. wait_for_death(Pid, Name);
  656. false ->
  657. ok
  658. end.
  659. %% Borrowed from dbg
  660. fun_to_ms(ShellFun) when is_function(ShellFun) ->
  661. case erl_eval:fun_data(ShellFun) of
  662. {fun_data,ImportList,Clauses} ->
  663. case ms_transform:transform_from_shell(
  664. dbg,Clauses,ImportList) of
  665. {error,[{_,[{_,_,Code}|_]}|_],_} ->
  666. io:format("Error: ~s~n",
  667. [ms_transform:format_error(Code)]),
  668. {error,transform_error};
  669. Else ->
  670. Else
  671. end;
  672. false ->
  673. exit(shell_funs_only)
  674. end.
  675. -ifdef(OTP_RELEASE).
  676. -spec join(term(), [term()]) -> [term()].
  677. join(Sep, List) ->
  678. lists:join(Sep, List).
  679. -else.
  680. -spec join(string(), [string()]) -> string().
  681. join(Sep, List) ->
  682. string:join(List, Sep).
  683. -endif.