Skip to content
Permalink
Browse files
Adding binary_memory handling for recon:info/2,4
This was requested in issue #5 and allows the user to get information on
the total amount of refc binaries used by a process. The code also puts
some scaffolding in place in order to possibly add similar exceptions
later on if need arises.

Tests included.
  • Loading branch information
ferd committed Nov 21, 2013
1 parent eb5ed42 commit 4cf52082d4bc60799ea69533547203e557b43fc6
Showing 3 changed files with 66 additions and 5 deletions.
@@ -209,15 +209,43 @@ info(PidTerm, memory_used) ->
info(PidTerm, work) ->
info_type(PidTerm, work, [reductions]);
info(PidTerm, Keys) ->
process_info(recon_lib:term_to_pid(PidTerm), Keys).
proc_info(recon_lib:term_to_pid(PidTerm), Keys).

%% @private makes access to `info_type()' calls simpler.
-spec info_type(pid_term(), info_type(), [info_key()]) ->
{info_type(), [{info_key(), term()}]}.
info_type(PidTerm, Type, Keys) ->
Pid = recon_lib:term_to_pid(PidTerm),
{Type, erlang:process_info(Pid, Keys)}.
{Type, proc_info(Pid, Keys)}.

%% @private wrapper around `erlang:process_info/2' that allows special
%% attribute handling for items like `binary_memory'.
proc_info(Pid, binary_memory) ->
{binary, Bins} = erlang:process_info(Pid, binary),
{binary_memory, recon_lib:binary_memory(Bins)};
proc_info(Pid, Term) when is_atom(Term) ->
erlang:process_info(Pid, Term);
proc_info(Pid, List) when is_list(List) ->
case lists:member(binary_memory, List) of
false ->
erlang:process_info(Pid, List);
true ->
Res = erlang:process_info(Pid, replace(binary_memory, binary, List)),
proc_fake(List, Res)
end.

%% @private Replace keys around
replace(_, _, []) -> [];
replace(H, Val, [H|T]) -> [Val | replace(H, Val, T)];
replace(R, Val, [H|T]) -> [H | replace(R, Val, T)].

proc_fake([], []) ->
[];
proc_fake([binary_memory|T1], [{binary,Bins}|T2]) ->
[{binary_memory, recon_lib:binary_memory(Bins)}
| proc_fake(T1,T2)];
proc_fake([_|T1], [H|T2]) ->
[H | proc_fake(T1,T2)].

%% @doc Fetches a given attribute from all processes and returns
%% the biggest `Num' consumers.
@@ -12,6 +12,8 @@
triple_to_pid/3, term_to_pid/1,
term_to_port/1,
time_map/5, time_fold/6]).
%% private exports
-export([binary_memory/1]).

-type diff() :: [recon:proc_attrs() | recon:inet_attrs()].

@@ -86,8 +88,7 @@ proc_attrs(binary_memory, Pid) ->
case process_info(Pid, [binary, registered_name,
current_function, initial_call]) of
[{_, Bins}, {registered_name,Name}, Init, Cur] ->
MemTot = lists:foldl(fun({_,Mem,_}, Tot) -> Mem+Tot end, 0, Bins),
{ok, {Pid, MemTot, [Name || is_atom(Name)]++[Init, Cur]}};
{ok, {Pid, binary_memory(Bins), [Name || is_atom(Name)]++[Init, Cur]}};
undefined ->
{error, undefined}
end;
@@ -206,3 +207,7 @@ time_fold(N, Interval, Fun, State, FoldFun, Init) ->
Acc = FoldFun(Res,Init),
time_fold(N-1,Interval,Fun,NewState,FoldFun,Acc).

%% @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).
@@ -4,11 +4,12 @@
%%% conformance and obvious changes more than anything.
-module(recon_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).

all() -> [{group,info}, proc_count, proc_window, bin_leak,
node_stats_list, get_state, source, tcp, udp, files, port_types,
inet_count, inet_window].
inet_count, inet_window, binary_memory].

groups() -> [{info, [], [info3, info4, info1, info2,
port_info1, port_info2]}].
@@ -224,6 +225,33 @@ port_info2(Config) ->
{io, [{input,_},{output,_}]} = recon:port_info(TCP, io),
{io, [{input,_},{output,_}]} = recon:port_info(UDP, io).

%% binary_memory is a created attribute that counts the amount
%% of memory held by refc binaries, usable in info/2-4 and
%% in proc_count/proc_window.
binary_memory(_Config) ->
%% we just don't want them to crash like it happens with
%% non-existing attributes.
?assertError(_, recon:proc_count(fake_attribute, 10)),
?assertError(_, recon:proc_window(fake_attribute, 10, 100)),
recon:proc_count(binary_memory, 10),
recon:proc_window(binary_memory, 10, 100),
%% And now for info, it should work in lists, which can contain
%% duplicates, or in single element calls.
%% Note: we allocate the binary before spawning the process but
%% use it in a closure to avoid race conditions on allocation for
%% the test.
Bin = <<1:999999>>,
Pid1 = spawn_link(fun() -> timer:sleep(100000) end),
Pid2 = spawn_link(fun() -> timer:sleep(100000), Bin end),
{binary_memory, 0} = recon:info(Pid1, binary_memory),
{binary_memory, N} = recon:info(Pid2, binary_memory),
true = N > 0,
Res1 = recon:info(Pid1, [binary, binary_memory, binary]),
Res2 = recon:info(Pid2, [binary_memory, binary, binary_memory]),
%% we expect everything to look as a single call to process_info/2
[{binary,X}, {binary_memory,_}, {binary,X}] = Res1,
[{binary_memory,Y}, {binary,_}, {binary_memory,Y}] = Res2.

%%%%%%%%%%%%%%%
%%% HELPERS %%%
%%%%%%%%%%%%%%%

0 comments on commit 4cf5208

Please sign in to comment.