changed
README.md
|
|
@@ -27,6 +27,9 @@ all stable changes of the first version of Recon.
|
|
27
27
|
|
|
28
28
|
*2.x*
|
|
29
29
|
|
|
30
|
+ - 2.5.0
|
|
31
|
+ - Optional formatting of records in traces (thanks to @bartekgorny)
|
|
32
|
+ - Basic support for OTP-22 in `recon_alloc` (base handling of `foreign_blocks` type)
|
|
30
33
|
- 2.4.0
|
|
31
34
|
- Optional formatting of records in traces (thanks to @bartekgorny)
|
|
32
35
|
- 2.3.6
|
changed
hex_metadata.config
|
|
@@ -1,17 +1,16 @@
|
|
1
|
- {<<"name">>,<<"recon">>}.
|
|
2
|
- {<<"version">>,<<"2.4.0">>}.
|
|
3
|
- {<<"requirements">>,#{}}.
|
|
4
1
|
{<<"app">>,<<"recon">>}.
|
|
5
|
- {<<"maintainers">>,[<<"Fred Hebert">>]}.
|
|
6
|
- {<<"precompiled">>,false}.
|
|
2
|
+ {<<"build_tools">>,[<<"mix">>,<<"rebar3">>]}.
|
|
7
3
|
{<<"description">>,<<"Diagnostic tools for production use">>}.
|
|
8
4
|
{<<"files">>,
|
|
9
|
- [<<"src/recon.app.src">>,<<"LICENSE">>,<<"README.md">>,<<"rebar.lock">>,
|
|
10
|
- <<"script/app_deps.erl">>,<<"script/erl_crashdump_analyzer.sh">>,
|
|
11
|
- <<"script/queue_fun.awk">>,<<"src/recon.erl">>,<<"src/recon_alloc.erl">>,
|
|
12
|
- <<"src/recon_lib.erl">>,<<"src/recon_rec.erl">>,<<"src/recon_trace.erl">>]}.
|
|
5
|
+ [<<"src/recon.app.src">>,<<"LICENSE">>,<<"README.md">>,<<"mix.exs">>,
|
|
6
|
+ <<"rebar.lock">>,<<"script/app_deps.erl">>,
|
|
7
|
+ <<"script/erl_crashdump_analyzer.sh">>,<<"script/queue_fun.awk">>,
|
|
8
|
+ <<"src/recon.erl">>,<<"src/recon_alloc.erl">>,<<"src/recon_lib.erl">>,
|
|
9
|
+ <<"src/recon_map.erl">>,<<"src/recon_rec.erl">>,<<"src/recon_trace.erl">>]}.
|
|
13
10
|
{<<"licenses">>,[<<"BSD">>]}.
|
|
14
11
|
{<<"links">>,
|
|
15
|
- [{<<"Github">>,<<"https://github.com/ferd/recon/">>},
|
|
16
|
- {<<"Documentation">>,<<"http://ferd.github.io/recon/">>}]}.
|
|
17
|
- {<<"build_tools">>,[<<"rebar3">>]}.
|
|
12
|
+ [{<<"Documentation">>,<<"http://ferd.github.io/recon/">>},
|
|
13
|
+ {<<"Github">>,<<"https://github.com/ferd/recon/">>}]}.
|
|
14
|
+ {<<"name">>,<<"recon">>}.
|
|
15
|
+ {<<"requirements">>,[]}.
|
|
16
|
+ {<<"version">>,<<"2.5.0">>}.
|
added
mix.exs
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+ defmodule Recon.MixProject do
|
|
2
|
+ use Mix.Project
|
|
3
|
+
|
|
4
|
+ def project do
|
|
5
|
+ [
|
|
6
|
+ app: :recon,
|
|
7
|
+ description: "Diagnostic tools for production use",
|
|
8
|
+ version: "2.5.0",
|
|
9
|
+ language: :erlang,
|
|
10
|
+ deps: []
|
|
11
|
+ ]
|
|
12
|
+ end
|
|
13
|
+ end
|
changed
src/recon.app.src
|
|
@@ -1,11 +1,12 @@
|
|
1
1
|
{application,recon,
|
|
2
2
|
[{description,"Diagnostic tools for production use"},
|
|
3
|
- {vsn,"2.4.0"},
|
|
3
|
+ {vsn,"2.5.0"},
|
|
4
4
|
{modules,[recon,recon_alloc,recon_lib,recon_trace,recon_rec]},
|
|
5
5
|
{registered,[]},
|
|
6
6
|
{applications,[kernel,stdlib]},
|
|
7
|
- {maintainers,["Fred Hebert"]},
|
|
8
7
|
{licenses,["BSD"]},
|
|
9
8
|
{links,[{"Github","https://github.com/ferd/recon/"},
|
|
10
9
|
{"Documentation","http://ferd.github.io/recon/"}]},
|
|
11
|
- {files,["src/","script/","rebar.lock","README.md","LICENSE"]}]}.
|
|
10
|
+ {build_tools,["mix","rebar3"]},
|
|
11
|
+ {files,["src/","script/","rebar.lock","mix.exs","README.md",
|
|
12
|
+ "LICENSE"]}]}.
|
changed
src/recon_alloc.erl
|
|
@@ -420,6 +420,9 @@ merge_values([{Key,Vs}|T1], [{Key,OVs}|T2]) when Key =:= calls;
|
|
420
420
|
%% value is very rarely important so leave it
|
|
421
421
|
%% like this for now.
|
|
422
422
|
{K, lists:max([V1,V2])};
|
|
423
|
+ ({{K,V1}, {K,V2}}) when K =:= foreign_blocks ->
|
|
424
|
+ %% foreign blocks are just merged as a bigger list.
|
|
425
|
+ {K, V1++V2};
|
|
423
426
|
({{K,V1}, {K,V2}}) ->
|
|
424
427
|
{K, V1 + V2};
|
|
425
428
|
({{K,C1,L1,M1}, {K,C2,L2,M2}}) ->
|
added
src/recon_map.erl
|
|
@@ -0,0 +1,208 @@
|
|
1
|
+ %%%-------------------------------------------------------------------
|
|
2
|
+ %%% @author bartlomiej.gorny@erlang-solutions.com
|
|
3
|
+ %%% @doc
|
|
4
|
+ %%% This module handles formatting maps.
|
|
5
|
+ %% It allows for trimming output to selected fields, or to nothing at all. It also adds a label
|
|
6
|
+ %% to a printout.
|
|
7
|
+ %% To set up a limit for a map, you need to give recon a way to tell the map you want to
|
|
8
|
+ %% trim from all the other maps, so you have to provide something like a 'type definition'.
|
|
9
|
+ %% It can be either another map which is compared to the arg, or a fun.
|
|
10
|
+ %%% @end
|
|
11
|
+ %%%-------------------------------------------------------------------
|
|
12
|
+ -module(recon_map).
|
|
13
|
+ -author("bartlomiej.gorny@erlang-solutions.com").
|
|
14
|
+ %% API
|
|
15
|
+
|
|
16
|
+ -export([limit/3, list/0, is_active/0, clear/0, remove/1, rename/2]).
|
|
17
|
+ -export([process_map/1]).
|
|
18
|
+
|
|
19
|
+ -type map_label() :: atom().
|
|
20
|
+ -type pattern() :: map() | function().
|
|
21
|
+ -type limit() :: all | none | atom() | binary() | [any()].
|
|
22
|
+
|
|
23
|
+ %% @doc quickly check if we want to do any record formatting
|
|
24
|
+ -spec is_active() -> boolean().
|
|
25
|
+ is_active() ->
|
|
26
|
+ case whereis(recon_ets_maps) of
|
|
27
|
+ undefined -> false;
|
|
28
|
+ _ -> true
|
|
29
|
+ end.
|
|
30
|
+
|
|
31
|
+ %% @doc remove all imported definitions, destroy the table, clean up
|
|
32
|
+ clear() ->
|
|
33
|
+ maybe_kill(recon_ets_maps),
|
|
34
|
+ ok.
|
|
35
|
+
|
|
36
|
+ %% @doc Limit output to selected keys of a map (can be 'none', 'all', a key or a list of keys).
|
|
37
|
+ %% Pattern selects maps to process: a "pattern" is just a map, and if all key/value pairs of a pattern
|
|
38
|
+ %% are present in a map (in other words, the pattern is a subset), then we say the map matches
|
|
39
|
+ %% and we process it accordingly (apply the limit).
|
|
40
|
+ %%
|
|
41
|
+ %% Patterns are applied in alphabetical order, until a match is found.
|
|
42
|
+ %%
|
|
43
|
+ %% Instead of a pattern you can also provide a function which will take a map and return a boolean.
|
|
44
|
+ %% @end
|
|
45
|
+ -spec limit(map_label(), pattern(), limit()) -> ok | {error, any()}.
|
|
46
|
+ limit(Label, #{} = Pattern, Limit) when is_atom(Label) ->
|
|
47
|
+ store_pattern(Label, Pattern, Limit);
|
|
48
|
+ limit(Label, Pattern, Limit) when is_atom(Label), is_function(Pattern) ->
|
|
49
|
+ store_pattern(Label, Pattern, Limit).
|
|
50
|
+
|
|
51
|
+ %% @doc prints out all "known" map definitions and their limit settings.
|
|
52
|
+ %% Printout tells a map's name, the matching fields required, and the limit options.
|
|
53
|
+ %% @end
|
|
54
|
+ list() ->
|
|
55
|
+ ensure_table_exists(),
|
|
56
|
+ io:format("~nmap definitions and limits:~n"),
|
|
57
|
+ list(ets:tab2list(patterns_table_name())).
|
|
58
|
+
|
|
59
|
+ %% @doc remove a given map entry
|
|
60
|
+ -spec remove(map_label()) -> true.
|
|
61
|
+ remove(Label) ->
|
|
62
|
+ ensure_table_exists(),
|
|
63
|
+ ets:delete(patterns_table_name(), Label).
|
|
64
|
+
|
|
65
|
+ %% @doc rename a given map entry, which allows to to change priorities for
|
|
66
|
+ %% matching. The first argument is the current name, and the second
|
|
67
|
+ %% argument is the new name.
|
|
68
|
+ -spec rename(map_label(), map_label()) -> renamed | missing.
|
|
69
|
+ rename(Name, NewName) ->
|
|
70
|
+ ensure_table_exists(),
|
|
71
|
+ case ets:lookup(patterns_table_name(), Name) of
|
|
72
|
+ [{Name, Pattern, Limit}] ->
|
|
73
|
+ ets:insert(patterns_table_name(), {NewName, Pattern, Limit}),
|
|
74
|
+ ets:delete(patterns_table_name(), Name),
|
|
75
|
+ renamed;
|
|
76
|
+ [] ->
|
|
77
|
+ missing
|
|
78
|
+ end.
|
|
79
|
+
|
|
80
|
+ %% @doc prints out all "known" map filter definitions and their settings.
|
|
81
|
+ %% Printout tells the map's label, the matching patterns, and the limit options
|
|
82
|
+ %% @end
|
|
83
|
+ list([]) ->
|
|
84
|
+ io:format("~n"),
|
|
85
|
+ ok;
|
|
86
|
+ list([{Label, Pattern, Limit} | Rest]) ->
|
|
87
|
+ io:format("~p: ~p -> ~p~n", [Label, Pattern, Limit]),
|
|
88
|
+ list(Rest).
|
|
89
|
+
|
|
90
|
+ %% @private given a map, scans saved patterns for one that matches; if found, returns a label
|
|
91
|
+ %% and a map with limits applied; otherwise returns 'none' and original map.
|
|
92
|
+ %% Pattern can be:
|
|
93
|
+ %% <ul>
|
|
94
|
+ %% <li> a map - then each key in pattern is checked for equality with the map in question</li>
|
|
95
|
+ %% <li> a fun(map()) -> boolean()</li>
|
|
96
|
+ %% </ul>
|
|
97
|
+ -spec process_map(map()) -> map() | {atom(), map()}.
|
|
98
|
+ process_map(M) ->
|
|
99
|
+ process_map(M, ets:tab2list(patterns_table_name())).
|
|
100
|
+
|
|
101
|
+ process_map(M, []) ->
|
|
102
|
+ M;
|
|
103
|
+ process_map(M, [{Label, Pattern, Limit} | Rest]) ->
|
|
104
|
+ case map_matches(M, Pattern) of
|
|
105
|
+ true ->
|
|
106
|
+ {Label, apply_map_limits(Limit, M)};
|
|
107
|
+ false ->
|
|
108
|
+ process_map(M, Rest)
|
|
109
|
+ end.
|
|
110
|
+
|
|
111
|
+ map_matches(#{} = M, Pattern) when is_function(Pattern) ->
|
|
112
|
+ Pattern(M);
|
|
113
|
+ map_matches(_, []) ->
|
|
114
|
+ true;
|
|
115
|
+ map_matches(M, [{K, V} | Rest]) ->
|
|
116
|
+ case maps:is_key(K, M) of
|
|
117
|
+ true ->
|
|
118
|
+ case maps:get(K, M) of
|
|
119
|
+ V ->
|
|
120
|
+ map_matches(M, Rest);
|
|
121
|
+ _ ->
|
|
122
|
+ false
|
|
123
|
+ end;
|
|
124
|
+ false ->
|
|
125
|
+ false
|
|
126
|
+ end.
|
|
127
|
+
|
|
128
|
+ apply_map_limits(none, M) ->
|
|
129
|
+ M;
|
|
130
|
+ apply_map_limits(all, _) ->
|
|
131
|
+ #{};
|
|
132
|
+ apply_map_limits(Fields, M) ->
|
|
133
|
+ maps:with(Fields, M).
|
|
134
|
+
|
|
135
|
+ patterns_table_name() -> recon_map_patterns.
|
|
136
|
+
|
|
137
|
+ store_pattern(Label, Pattern, Limit) ->
|
|
138
|
+ ensure_table_exists(),
|
|
139
|
+ ets:insert(patterns_table_name(), {Label, prepare_pattern(Pattern), prepare_limit(Limit)}),
|
|
140
|
+ ok.
|
|
141
|
+
|
|
142
|
+ prepare_limit(all) -> all;
|
|
143
|
+ prepare_limit(none) -> none;
|
|
144
|
+ prepare_limit(Limit) when is_binary(Limit) -> [Limit];
|
|
145
|
+ prepare_limit(Limit) when is_atom(Limit) -> [Limit];
|
|
146
|
+ prepare_limit(Limit) when is_list(Limit) -> Limit.
|
|
147
|
+
|
|
148
|
+ prepare_pattern(Pattern) when is_function(Pattern) -> Pattern;
|
|
149
|
+ prepare_pattern(Pattern) when is_map(Pattern) -> maps:to_list(Pattern).
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+ ensure_table_exists() ->
|
|
153
|
+ case ets:info(patterns_table_name()) of
|
|
154
|
+ undefined ->
|
|
155
|
+ case whereis(recon_ets_maps) of
|
|
156
|
+ undefined ->
|
|
157
|
+ Parent = self(),
|
|
158
|
+ Ref = make_ref(),
|
|
159
|
+ %% attach to the currently running session
|
|
160
|
+ {Pid, MonRef} = spawn_monitor(fun() ->
|
|
161
|
+ register(recon_ets_maps, self()),
|
|
162
|
+ ets:new(patterns_table_name(), [ordered_set, public, named_table]),
|
|
163
|
+ Parent ! Ref,
|
|
164
|
+ ets_keeper()
|
|
165
|
+ end),
|
|
166
|
+ receive
|
|
167
|
+ Ref ->
|
|
168
|
+ erlang:demonitor(MonRef, [flush]),
|
|
169
|
+ Pid;
|
|
170
|
+ {'DOWN', MonRef, _, _, Reason} ->
|
|
171
|
+ error(Reason)
|
|
172
|
+ end;
|
|
173
|
+ Pid ->
|
|
174
|
+ Pid
|
|
175
|
+ end;
|
|
176
|
+ Pid ->
|
|
177
|
+ Pid
|
|
178
|
+ end.
|
|
179
|
+
|
|
180
|
+ ets_keeper() ->
|
|
181
|
+ receive
|
|
182
|
+ stop -> ok;
|
|
183
|
+ _ -> ets_keeper()
|
|
184
|
+ end.
|
|
185
|
+
|
|
186
|
+ %%%%%%%%%%%%%%%
|
|
187
|
+ %%% HELPERS %%%
|
|
188
|
+ %%%%%%%%%%%%%%%
|
|
189
|
+
|
|
190
|
+ maybe_kill(Name) ->
|
|
191
|
+ case whereis(Name) of
|
|
192
|
+ undefined ->
|
|
193
|
+ ok;
|
|
194
|
+ Pid ->
|
|
195
|
+ unlink(Pid),
|
|
196
|
+ exit(Pid, kill),
|
|
197
|
+ wait_for_death(Pid, Name)
|
|
198
|
+ end.
|
|
199
|
+
|
|
200
|
+ wait_for_death(Pid, Name) ->
|
|
201
|
+ case is_process_alive(Pid) orelse whereis(Name) =:= Pid of
|
|
202
|
+ true ->
|
|
203
|
+ timer:sleep(10),
|
|
204
|
+ wait_for_death(Pid, Name);
|
|
205
|
+ false ->
|
|
206
|
+ ok
|
|
207
|
+ end.
|
|
208
|
+
|
changed
src/recon_rec.erl
|
|
@@ -15,7 +15,8 @@
|
|
15
15
|
%% API
|
|
16
16
|
|
|
17
17
|
-export([is_active/0]).
|
|
18
|
- -export([import/1, format_tuple/1, clear/1, clear/0, list/0, get_list/0, limit/3]).
|
|
18
|
+ -export([import/1, clear/1, clear/0, list/0, get_list/0, limit/3]).
|
|
19
|
+ -export([format_tuple/1]).
|
|
19
20
|
|
|
20
21
|
-ifdef(TEST).
|
|
21
22
|
-export([lookup_record/2]).
|
|
|
@@ -54,7 +55,7 @@ is_active() ->
|
|
54
55
|
|
|
55
56
|
%% @doc remove definitions imported from a module.
|
|
56
57
|
clear(Module) ->
|
|
57
|
- lists:map(fun(R) -> rem_for_module(R, Module) end, ets:tab2list(ets_table_name())).
|
|
58
|
+ lists:map(fun(R) -> rem_for_module(R, Module) end, ets:tab2list(records_table_name())).
|
|
58
59
|
|
|
59
60
|
%% @doc remove all imported definitions, destroy the table, clean up
|
|
60
61
|
clear() ->
|
|
|
@@ -62,7 +63,7 @@ clear() ->
|
|
62
63
|
ok.
|
|
63
64
|
|
|
64
65
|
%% @doc prints out all "known" (imported) record definitions and their limit settings.
|
|
65
|
- %% Print out tells module a record originates from, its name and a list of field names,
|
|
66
|
+ %% Printout tells module a record originates from, its name and a list of field names,
|
|
66
67
|
%% plus the record's arity (may be handy if handling big records) and a list of field it
|
|
67
68
|
%% limits its output to, if set.
|
|
68
69
|
%% @end
|
|
|
@@ -80,20 +81,20 @@ list() ->
|
|
80
81
|
-spec get_list() -> [listentry()].
|
|
81
82
|
get_list() ->
|
|
82
83
|
ensure_table_exists(),
|
|
83
|
- Lst = lists:map(fun make_list_entry/1, ets:tab2list(ets_table_name())),
|
|
84
|
+ Lst = lists:map(fun make_list_entry/1, ets:tab2list(records_table_name())),
|
|
84
85
|
lists:sort(Lst).
|
|
85
86
|
|
|
86
87
|
%% @doc Limit output to selected fields of a record (can be 'none', 'all', a field or a list of fields).
|
|
87
88
|
%% Limit set to 'none' means there is no limit, and all fields are displayed; limit 'all' means that
|
|
88
89
|
%% all fields are squashed and only record name will be shown.
|
|
89
90
|
%% @end
|
|
90
|
- -spec limit(record_name(), arity(), limit()) -> ok | {error, record_unknown}.
|
|
91
|
- limit(Name, Arity, Limit) ->
|
|
91
|
+ -spec limit(record_name(), arity(), limit()) -> ok | {error, any()}.
|
|
92
|
+ limit(Name, Arity, Limit) when is_atom(Name), is_integer(Arity) ->
|
|
92
93
|
case lookup_record(Name, Arity) of
|
|
93
94
|
[] ->
|
|
94
95
|
{error, record_unknown};
|
|
95
96
|
[{Key, Fields, Mod, _}] ->
|
|
96
|
- ets:insert(ets_table_name(), {Key, Fields, Mod, Limit}),
|
|
97
|
+ ets:insert(records_table_name(), {Key, Fields, Mod, Limit}),
|
|
97
98
|
ok
|
|
98
99
|
end.
|
|
99
100
|
|
|
|
@@ -127,10 +128,10 @@ store_record(Rec, Module, ResultList) ->
|
|
127
128
|
Arity = length(Fields),
|
|
128
129
|
Result = case lookup_record(Name, Arity) of
|
|
129
130
|
[] ->
|
|
130
|
- ets:insert(ets_table_name(), rec_info(Rec, Module)),
|
|
131
|
+ ets:insert(records_table_name(), rec_info(Rec, Module)),
|
|
131
132
|
{imported, Module, Name, Arity};
|
|
132
133
|
[{_, _, Module, _}] ->
|
|
133
|
- ets:insert(ets_table_name(), rec_info(Rec, Module)),
|
|
134
|
+ ets:insert(records_table_name(), rec_info(Rec, Module)),
|
|
134
135
|
{overwritten, Module, Name, Arity};
|
|
135
136
|
[{_, _, Mod, _}] ->
|
|
136
137
|
{ignored, Module, Name, Arity, Mod}
|
|
|
@@ -148,10 +149,11 @@ get_record(_, Acc) -> Acc.
|
|
148
149
|
%% @private
|
|
149
150
|
lookup_record(RecName, FieldCount) ->
|
|
150
151
|
ensure_table_exists(),
|
|
151
|
- ets:lookup(ets_table_name(), {RecName, FieldCount}).
|
|
152
|
+ ets:lookup(records_table_name(), {RecName, FieldCount}).
|
|
152
153
|
|
|
154
|
+ %% @private
|
|
153
155
|
ensure_table_exists() ->
|
|
154
|
- case ets:info(ets_table_name()) of
|
|
156
|
+ case ets:info(records_table_name()) of
|
|
155
157
|
undefined ->
|
|
156
158
|
case whereis(recon_ets) of
|
|
157
159
|
undefined ->
|
|
|
@@ -160,7 +162,7 @@ ensure_table_exists() ->
|
|
160
162
|
%% attach to the currently running session
|
|
161
163
|
{Pid, MonRef} = spawn_monitor(fun() ->
|
|
162
164
|
register(recon_ets, self()),
|
|
163
|
- ets:new(ets_table_name(), [set, public, named_table]),
|
|
165
|
+ ets:new(records_table_name(), [set, public, named_table]),
|
|
164
166
|
Parent ! Ref,
|
|
165
167
|
ets_keeper()
|
|
166
168
|
end),
|
|
|
@@ -178,13 +180,13 @@ ensure_table_exists() ->
|
|
178
180
|
Pid
|
|
179
181
|
end.
|
|
180
182
|
|
|
181
|
- ets_table_name() -> recon_record_definitions.
|
|
183
|
+ records_table_name() -> recon_record_definitions.
|
|
182
184
|
|
|
183
185
|
rec_info({Name, Fields}, Module) ->
|
|
184
186
|
{{Name, length(Fields)}, field_names(Fields), Module, none}.
|
|
185
187
|
|
|
186
188
|
rem_for_module({_, _, Module, _} = Rec, Module) ->
|
|
187
|
- ets:delete_object(ets_table_name(), Rec);
|
|
189
|
+ ets:delete_object(records_table_name(), Rec);
|
|
188
190
|
rem_for_module(_, _) ->
|
|
189
191
|
ok.
|
|
190
192
|
|
|
|
@@ -233,6 +235,7 @@ format_record(Rec, {{Name, Arity}, Fields, _, Limits}) ->
|
|
233
235
|
end.
|
|
234
236
|
|
|
235
237
|
format_kv(Key, Val) ->
|
|
238
|
+ %% Some messy mutually recursive calls we can't avoid
|
|
236
239
|
[recon_trace:format_trace_output(true, Key), "=", recon_trace:format_trace_output(true, Val)].
|
|
237
240
|
|
|
238
241
|
apply_limits(List, none) -> List;
|
changed
src/recon_trace.erl
|
|
@@ -185,7 +185,7 @@
|
|
185
185
|
-export([format/1]).
|
|
186
186
|
|
|
187
187
|
%% Internal exports
|
|
188
|
- -export([count_tracer/1, rate_tracer/2, formatter/5, format_trace_output/2]).
|
|
188
|
+ -export([count_tracer/1, rate_tracer/2, formatter/5, format_trace_output/1, format_trace_output/2]).
|
|
189
189
|
|
|
190
190
|
-type matchspec() :: [{[term()], [term()], [term()]}].
|
|
191
191
|
-type shellfun() :: fun((_) -> term()).
|
|
|
@@ -607,36 +607,76 @@ to_hms(_) ->
|
|
607
607
|
format_args(Arity) when is_integer(Arity) ->
|
|
608
608
|
[$/, integer_to_list(Arity)];
|
|
609
609
|
format_args(Args) when is_list(Args) ->
|
|
610
|
- Active = recon_rec:is_active(),
|
|
611
|
- [$(, join(", ", [format_trace_output(Active, Arg) || Arg <- Args]), $)].
|
|
610
|
+ [$(, join(", ", [format_trace_output(Arg) || Arg <- Args]), $)].
|
|
612
611
|
|
|
613
612
|
|
|
614
613
|
%% @doc formats call arguments and return values - most types are just printed out, except for
|
|
615
614
|
%% tuples recognised as records, which mimic the source code syntax
|
|
616
615
|
%% @end
|
|
617
616
|
format_trace_output(Args) ->
|
|
618
|
- format_trace_output(recon_rec:is_active(), Args).
|
|
617
|
+ format_trace_output(recon_rec:is_active(), recon_map:is_active(), Args).
|
|
619
618
|
|
|
620
|
- format_trace_output(true, Args) when is_tuple(Args) ->
|
|
619
|
+ format_trace_output(Recs, Args) ->
|
|
620
|
+ format_trace_output(Recs, recon_map:is_active(), Args).
|
|
621
|
+
|
|
622
|
+ format_trace_output(true, _, Args) when is_tuple(Args) ->
|
|
621
623
|
recon_rec:format_tuple(Args);
|
|
622
|
- format_trace_output(true, Args) when is_list(Args) ->
|
|
624
|
+ format_trace_output(false, true, Args) when is_tuple(Args) ->
|
|
625
|
+ format_tuple(false, true, Args);
|
|
626
|
+ format_trace_output(Recs, Maps, Args) when is_list(Args), Recs orelse Maps ->
|
|
623
627
|
case io_lib:printable_list(Args) of
|
|
624
628
|
true ->
|
|
625
629
|
io_lib:format("~p", [Args]);
|
|
626
630
|
false ->
|
|
627
|
- L = lists:map(fun(A) -> format_trace_output(true, A) end, Args),
|
|
628
|
- [$[, join(", ", L), $]]
|
|
631
|
+ format_maybe_improper_list(Recs, Maps, Args)
|
|
629
632
|
end;
|
|
630
|
- format_trace_output(true, Args) when is_map(Args) ->
|
|
633
|
+ format_trace_output(Recs, true, Args) when is_map(Args) ->
|
|
634
|
+ {Label, Map} = case recon_map:process_map(Args) of
|
|
635
|
+ {L, M} -> {atom_to_list(L), M};
|
|
636
|
+ M -> {"", M}
|
|
637
|
+ end,
|
|
638
|
+ ItemList = maps:to_list(Map),
|
|
639
|
+ [Label,
|
|
640
|
+ "#{",
|
|
641
|
+ join(", ", [format_kv(Recs, true, Key, Val) || {Key, Val} <- ItemList]),
|
|
642
|
+ "}"];
|
|
643
|
+ format_trace_output(Recs, false, Args) when is_map(Args) ->
|
|
631
644
|
ItemList = maps:to_list(Args),
|
|
632
645
|
["#{",
|
|
633
|
- join(", ", [format_kv(Key, Val) || {Key, Val} <- ItemList]),
|
|
646
|
+ join(", ", [format_kv(Recs, false, Key, Val) || {Key, Val} <- ItemList]),
|
|
634
647
|
"}"];
|
|
635
|
- format_trace_output(_, Args) ->
|
|
648
|
+ format_trace_output(_, _, Args) ->
|
|
636
649
|
io_lib:format("~p", [Args]).
|
|
637
650
|
|
|
638
|
- format_kv(Key, Val) ->
|
|
639
|
- [format_trace_output(true, Key), "=", format_trace_output(true, Val)].
|
|
651
|
+ format_kv(Recs, Maps, Key, Val) ->
|
|
652
|
+ [format_trace_output(Recs, Maps, Key), "=>", format_trace_output(Recs, Maps, Val)].
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+ format_tuple(Recs, Maps, Tup) ->
|
|
656
|
+ [${ | format_tuple_(Recs, Maps, tuple_to_list(Tup))].
|
|
657
|
+
|
|
658
|
+ format_tuple_(_Recs, _Maps, []) ->
|
|
659
|
+ "}";
|
|
660
|
+ format_tuple_(Recs, Maps, [H|T]) ->
|
|
661
|
+ [format_trace_output(Recs, Maps, H), $,,
|
|
662
|
+ format_tuple_(Recs, Maps, T)].
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+ format_maybe_improper_list(Recs, Maps, List) ->
|
|
666
|
+ [$[ | format_maybe_improper_list_(Recs, Maps, List)].
|
|
667
|
+
|
|
668
|
+ format_maybe_improper_list_(_, _, []) ->
|
|
669
|
+ "]";
|
|
670
|
+ format_maybe_improper_list_(Recs, Maps, [H|[]]) ->
|
|
671
|
+ [format_trace_output(Recs, Maps, H), $]];
|
|
672
|
+ format_maybe_improper_list_(Recs, Maps, [H|T]) when is_list(T) ->
|
|
673
|
+ [format_trace_output(Recs, Maps, H), $,,
|
|
674
|
+ format_maybe_improper_list_(Recs, Maps, T)];
|
|
675
|
+ format_maybe_improper_list_(Recs, Maps, [H|T]) when not is_list(T) ->
|
|
676
|
+ %% Handling improper lists
|
|
677
|
+ [format_trace_output(Recs, Maps, H), $|,
|
|
678
|
+ format_trace_output(Recs, Maps, T), $]].
|
|
679
|
+
|
|
640
680
|
|
|
641
681
|
%%%%%%%%%%%%%%%
|
|
642
682
|
%%% HELPERS %%%
|
|
|
@@ -678,6 +718,7 @@ fun_to_ms(ShellFun) when is_function(ShellFun) ->
|
|
678
718
|
exit(shell_funs_only)
|
|
679
719
|
end.
|
|
680
720
|
|
|
721
|
+
|
|
681
722
|
-ifdef(OTP_RELEASE).
|
|
682
723
|
-spec join(term(), [term()]) -> [term()].
|
|
683
724
|
join(Sep, List) ->
|