Merge branch 'trace-fn' of https://github.com/evanmcc/recon into evanmcc-trace-fn
diff --git a/README.md b/README.md
index 77120da..aec9c96 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,8 @@
- 2.3.2 (current Master)
- Allow the `return_to` option in `recon_trace`
+ - More efficient sorting function for procs and ports attributes
+ (thanks to @zhongwencool and @pichi)
- 2.3.1
- Updated `app_deps` script to run with rebar3 dependencies
- Minor docs update
diff --git a/src/recon.erl b/src/recon.erl
index 15ba606..1d56897 100644
--- a/src/recon.erl
+++ b/src/recon.erl
@@ -260,7 +260,7 @@
AttributeName :: atom(),
Num :: non_neg_integer().
proc_count(AttrName, Num) ->
- sublist_top_n(recon_lib:proc_attrs(AttrName), Num).
+ recon_lib:sublist_top_n_attrs(recon_lib:proc_attrs(AttrName), Num).
%% @doc Fetches a given attribute from all processes (except the
%% caller) and returns the biggest entries, over a sliding time window.
@@ -294,7 +294,7 @@
proc_window(AttrName, Num, Time) ->
Sample = fun() -> recon_lib:proc_attrs(AttrName) end,
{First,Last} = recon_lib:sample(Time, Sample),
- sublist_top_n(recon_lib:sliding_window(First, Last), Num).
+ recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num).
%% @doc Refc binaries can be leaking when barely-busy processes route them
%% around and do little else, or when extremely busy processes reach a stable
@@ -311,15 +311,17 @@
%% for more details on refc binaries
-spec bin_leak(pos_integer()) -> [proc_attrs()].
bin_leak(N) ->
- sublist_top_n([try
- {ok, {_,Pre,Id}} = recon_lib:proc_attrs(binary, Pid),
- erlang:garbage_collect(Pid),
- {ok, {_,Post,_}} = recon_lib:proc_attrs(binary, Pid),
- {Pid, length(Post)-length(Pre), Id}
- catch
- _:_ -> {Pid, 0, []}
- end || Pid <- processes()],
- N).
+ Procs = recon_lib:sublist_top_n_attrs([
+ try
+ {ok, {_,Pre,Id}} = recon_lib:proc_attrs(binary, Pid),
+ erlang:garbage_collect(Pid),
+ {ok, {_,Post,_}} = recon_lib:proc_attrs(binary, Pid),
+ {Pid, length(Pre) - length(Post), Id}
+ catch
+ _:_ -> {Pid, {0, 0}, []}
+ end || Pid <- processes()
+ ], N),
+ [{Pid, -Val, Id} ||{Pid, Val, Id} <-Procs].
%% @doc Shorthand for `node_stats(N, Interval, fun(X,_) -> io:format("~p~n",[X]) end, nostate)'.
-spec node_stats_print(Repeat, Interval) -> term() when
@@ -542,7 +544,7 @@
| 'cnt' | 'oct',
Num :: non_neg_integer().
inet_count(Attr, Num) ->
- sublist_top_n(recon_lib:inet_attrs(Attr), Num).
+ recon_lib:sublist_top_n_attrs(recon_lib:inet_attrs(Attr), Num).
%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP)
%% and returns the biggest entries, over a sliding time window.
@@ -564,7 +566,7 @@
inet_window(Attr, Num, Time) when is_atom(Attr) ->
Sample = fun() -> recon_lib:inet_attrs(Attr) end,
{First,Last} = recon_lib:sample(Time, Sample),
- sublist_top_n(recon_lib:sliding_window(First, Last), Num).
+ recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num).
%% @doc Allows to be similar to `erlang:port_info/1', but allows
%% more flexible port usage: usual ports, ports that were registered
@@ -700,27 +702,3 @@
named_rpc(Node, Fun, Timeout) when is_atom(Node) ->
named_rpc([Node], Fun, Timeout).
-%% @private Returns the top n element of List. n = Len
-sublist_top_n(List, Len) ->
- sublist_top_n(List, Len, 0, []).
-
-sublist_top_n([], _, _, Acc) ->
- lists:reverse(Acc);
-sublist_top_n([NewKey = {New1, New2, _}|List], MaxLen, Len, Acc) ->
- {NewLen, NewAcc} =
- if Len < MaxLen -> {Len + 1, insert(NewKey, Acc)};
- true ->
- [{Small1, Small2, _}|Acc2] = Acc,
- if {Small2, Small1} < {New2, New1} -> {Len, insert(NewKey, Acc2)};
- true -> {Len, Acc}
- end
- end,
- sublist_top_n(List, MaxLen, NewLen, NewAcc).
-
-%% @private insert Key in an ordered list
-insert(K, []) -> [K];
-insert({K1, K2, _} = K, [{H1, H2, _} = H|T]) ->
- if {H2, H1} =< {K2, K1} -> [H|insert(K, T)];
- true -> [K, H|T]
- end.
-
diff --git a/src/recon_lib.erl b/src/recon_lib.erl
index 5a49cf3..8bae1db 100644
--- a/src/recon_lib.erl
+++ b/src/recon_lib.erl
@@ -12,7 +12,8 @@
triple_to_pid/3, term_to_pid/1,
term_to_port/1,
time_map/5, time_fold/6,
- scheduler_usage_diff/2]).
+ scheduler_usage_diff/2,
+ sublist_top_n_attrs/2]).
%% private exports
-export([binary_memory/1]).
@@ -224,7 +225,53 @@
lists:zip(lists:sort(First), lists:sort(Last))
).
+%% @doc Returns the top n element of a list of process or inet attributes
+-spec sublist_top_n_attrs([Attrs], pos_integer()) -> [Attrs]
+ when Attrs :: recon:proc_attrs() | recon:inet_attrs().
+sublist_top_n_attrs(_, 0) ->
+ %% matching lists:sublist/2 behaviour
+ [];
+sublist_top_n_attrs(List, Len) ->
+ pheap_fill(List, Len, []).
+
%% @private crush binaries from process_info into their amount of place
%% taken in memory.
binary_memory(Bins) ->
lists:foldl(fun({_,Mem,_}, Tot) -> Mem+Tot end, 0, Bins).
+
+%%%%%%%%%%%%%%%
+%%% PRIVATE %%%
+%%%%%%%%%%%%%%%
+pheap_fill(List, 0, Heap) ->
+ pheap_full(List, Heap);
+pheap_fill([], _, Heap) ->
+ pheap_to_list(Heap, []);
+pheap_fill([{Y, X, _} = H|T], N, Heap) ->
+ pheap_fill(T, N-1, insert({{X, Y}, H}, Heap)).
+
+pheap_full([], Heap) ->
+ pheap_to_list(Heap, []);
+pheap_full([{Y, X, _} = H|T], [{K, _}|HeapT] = Heap) ->
+ case {X, Y} of
+ N when N > K ->
+ pheap_full(T, insert({N, H}, merge_pairs(HeapT)));
+ _ ->
+ pheap_full(T, Heap)
+ end.
+
+pheap_to_list([], Acc) -> Acc;
+pheap_to_list([{_, H}|T], Acc) ->
+ pheap_to_list(merge_pairs(T), [H|Acc]).
+
+-compile({inline, [insert/2, merge/2]}).
+insert(E, []) -> [E]; %% merge([E], H)
+insert(E, [E2|_] = H) when E =< E2 -> [E, H];
+insert(E, [E2|H]) -> [E2, [E]|H].
+
+merge(H1, []) -> H1;
+merge([E1|H1], [E2|_]=H2) when E1 =< E2 -> [E1, H2|H1];
+merge(H1, [E2|H2]) -> [E2, H1|H2].
+
+merge_pairs([]) -> [];
+merge_pairs([H]) -> H;
+merge_pairs([A, B|T]) -> merge(merge(A, B), merge_pairs(T)).
diff --git a/test/recon_SUITE.erl b/test/recon_SUITE.erl
index 63060bc..3122157 100644
--- a/test/recon_SUITE.erl
+++ b/test/recon_SUITE.erl
@@ -127,6 +127,7 @@
Res = recon:bin_leak(5),
5 = length(Res),
true = proc_attrs(Res),
+ true = lists:any(fun({_,Val,_})-> Val =/= 0 end, Res),
%% all results are =< 0, and ordered from smallest to biggest
lists:foldl(fun({_,Current,_}, Next) when Current =< Next -> Current end,
0,
diff --git a/test/recon_lib_SUITE.erl b/test/recon_lib_SUITE.erl
index 4a69df3..5c022c8 100644
--- a/test/recon_lib_SUITE.erl
+++ b/test/recon_lib_SUITE.erl
@@ -2,7 +2,7 @@
-include_lib("common_test/include/ct.hrl").
-compile(export_all).
-all() -> [scheduler_usage_diff].
+all() -> [scheduler_usage_diff, sublist_top_n].
scheduler_usage_diff(_Config) ->
{Active0, Total0} = {1000, 2000},
@@ -15,3 +15,15 @@
% Check for 100% usage
SchedStat2 = {1, Active0 + 1000, Total0 + 1000},
[{1, 1.0}] = recon_lib:scheduler_usage_diff([SchedStat0], [SchedStat2]).
+
+sublist_top_n(_Config) ->
+ L0 = [1,1,2,4,5,6,0,8,7,4,5,2,1,8,agbg,{t},3,[bah],"te",<<"bin">>,23.0, 23],
+ L = [{make_ref(), Val, [{meta,data}]} || Val <- L0],
+ %% former sort function used prior to integraton of sublist_top_n
+ Sorted = lists:usort(fun({_,A,_},{_,B,_}) -> A > B end, L),
+ [begin
+ Sub = (catch lists:sublist(Sorted, N)),
+ ct:pal("Sub ~p: ~p", [N, Sub]),
+ Sub = (catch recon_lib:sublist_top_n_attrs(L, N))
+ end || N <- lists:seq(0, length(L)+1)],
+ ok.