|
@ -79,35 +79,35 @@ |
|
|
%%% @end |
|
|
%%% @end |
|
|
-module(recon). |
|
|
-module(recon). |
|
|
-export([info/1, info/2, info/3, info/4, |
|
|
-export([info/1, info/2, info/3, info/4, |
|
|
proc_count/2, proc_window/3, |
|
|
|
|
|
bin_leak/1, |
|
|
|
|
|
node_stats_print/2, node_stats_list/2, node_stats/4, |
|
|
|
|
|
scheduler_usage/1]). |
|
|
|
|
|
|
|
|
proc_count/2, proc_window/3, |
|
|
|
|
|
bin_leak/1, |
|
|
|
|
|
node_stats_print/2, node_stats_list/2, node_stats/4, |
|
|
|
|
|
scheduler_usage/1]). |
|
|
-export([get_state/1, get_state/2]). |
|
|
-export([get_state/1, get_state/2]). |
|
|
-export([remote_load/1, remote_load/2, |
|
|
-export([remote_load/1, remote_load/2, |
|
|
source/1]). |
|
|
|
|
|
|
|
|
source/1]). |
|
|
-export([tcp/0, udp/0, sctp/0, files/0, port_types/0, |
|
|
-export([tcp/0, udp/0, sctp/0, files/0, port_types/0, |
|
|
inet_count/2, inet_window/3, |
|
|
|
|
|
port_info/1, port_info/2]). |
|
|
|
|
|
|
|
|
inet_count/2, inet_window/3, |
|
|
|
|
|
port_info/1, port_info/2]). |
|
|
-export([rpc/1, rpc/2, rpc/3, |
|
|
-export([rpc/1, rpc/2, rpc/3, |
|
|
named_rpc/1, named_rpc/2, named_rpc/3]). |
|
|
|
|
|
|
|
|
named_rpc/1, named_rpc/2, named_rpc/3]). |
|
|
|
|
|
|
|
|
%%%%%%%%%%%%% |
|
|
%%%%%%%%%%%%% |
|
|
%%% TYPES %%% |
|
|
%%% TYPES %%% |
|
|
%%%%%%%%%%%%% |
|
|
%%%%%%%%%%%%% |
|
|
-type proc_attrs() :: {pid(), |
|
|
-type proc_attrs() :: {pid(), |
|
|
Attr::_, |
|
|
|
|
|
[Name::
atom() |
|
|
|
|
|
|{current_function, mfa()} |
|
|
|
|
|
|{initial_call, mfa()}, ...]}. |
|
|
|
|
|
|
|
|
Attr :: _, |
|
|
|
|
|
[Name :: atom() |
|
|
|
|
|
|{current_function, mfa()} |
|
|
|
|
|
|{initial_call, mfa()}, ...]}. |
|
|
|
|
|
|
|
|
-type inet_attrs() :: {port(), |
|
|
-type inet_attrs() :: {port(), |
|
|
Attr::_, |
|
|
|
|
|
[{atom(), term()}]}. |
|
|
|
|
|
|
|
|
Attr :: _, |
|
|
|
|
|
[{atom(), term()}]}. |
|
|
|
|
|
|
|
|
-type pid_term() :: pid() | atom() | string() |
|
|
-type pid_term() :: pid() | atom() | string() |
|
|
| {global, term()} | {via, module(), term()} |
|
|
|
|
|
| {non_neg_integer(), non_neg_integer(), non_neg_integer()}. |
|
|
|
|
|
|
|
|
| {global, term()} | {via, module(), term()} |
|
|
|
|
|
| {non_neg_integer(), non_neg_integer(), non_neg_integer()}. |
|
|
|
|
|
|
|
|
-type info_type() :: meta | signals | location | memory_used | work. |
|
|
-type info_type() :: meta | signals | location | memory_used | work. |
|
|
|
|
|
|
|
@ -115,11 +115,11 @@ |
|
|
-type info_signals_key() :: links | monitors | monitored_by | trap_exit. |
|
|
-type info_signals_key() :: links | monitors | monitored_by | trap_exit. |
|
|
-type info_location_key() :: initial_call | current_stacktrace. |
|
|
-type info_location_key() :: initial_call | current_stacktrace. |
|
|
-type info_memory_key() :: memory | message_queue_len | heap_size |
|
|
-type info_memory_key() :: memory | message_queue_len | heap_size |
|
|
| total_heap_size | garbage_collection. |
|
|
|
|
|
|
|
|
| total_heap_size | garbage_collection. |
|
|
-type info_work_key() :: reductions. |
|
|
-type info_work_key() :: reductions. |
|
|
|
|
|
|
|
|
-type info_key() :: info_meta_key() | info_signals_key() | info_location_key() |
|
|
-type info_key() :: info_meta_key() | info_signals_key() | info_location_key() |
|
|
| info_memory_key() | info_work_key(). |
|
|
|
|
|
|
|
|
| info_memory_key() | info_work_key(). |
|
|
|
|
|
|
|
|
-type port_term() :: port() | string() | atom() | pos_integer(). |
|
|
-type port_term() :: port() | string() | atom() | pos_integer(). |
|
|
|
|
|
|
|
@ -132,16 +132,16 @@ |
|
|
-type port_info_specific_key() :: atom(). |
|
|
-type port_info_specific_key() :: atom(). |
|
|
|
|
|
|
|
|
-type port_info_key() :: port_info_meta_key() | port_info_signals_key() |
|
|
-type port_info_key() :: port_info_meta_key() | port_info_signals_key() |
|
|
| port_info_io_key() | port_info_memory_key() |
|
|
|
|
|
| port_info_specific_key(). |
|
|
|
|
|
|
|
|
| port_info_io_key() | port_info_memory_key() |
|
|
|
|
|
| port_info_specific_key(). |
|
|
|
|
|
|
|
|
-export_type([proc_attrs/0, inet_attrs/0, pid_term/0, port_term/0]). |
|
|
-export_type([proc_attrs/0, inet_attrs/0, pid_term/0, port_term/0]). |
|
|
-export_type([info_type/0, info_key/0, |
|
|
-export_type([info_type/0, info_key/0, |
|
|
info_meta_key/0, info_signals_key/0, info_location_key/0, |
|
|
|
|
|
info_memory_key/0, info_work_key/0]). |
|
|
|
|
|
|
|
|
info_meta_key/0, info_signals_key/0, info_location_key/0, |
|
|
|
|
|
info_memory_key/0, info_work_key/0]). |
|
|
-export_type([port_info_type/0, port_info_key/0, |
|
|
-export_type([port_info_type/0, port_info_key/0, |
|
|
port_info_meta_key/0, port_info_signals_key/0, port_info_io_key/0, |
|
|
|
|
|
port_info_memory_key/0, port_info_specific_key/0]). |
|
|
|
|
|
|
|
|
port_info_meta_key/0, port_info_signals_key/0, port_info_io_key/0, |
|
|
|
|
|
port_info_memory_key/0, port_info_specific_key/0]). |
|
|
|
|
|
|
|
|
%%%%%%%%%%%%%%%%%% |
|
|
%%%%%%%%%%%%%%%%%% |
|
|
%%% PUBLIC API %%% |
|
|
%%% PUBLIC API %%% |
|
@ -151,16 +151,16 @@ |
|
|
|
|
|
|
|
|
%% @doc Equivalent to `info(<A.B.C>)' where `A', `B', and `C' are integers part |
|
|
%% @doc Equivalent to `info(<A.B.C>)' where `A', `B', and `C' are integers part |
|
|
%% of a pid |
|
|
%% of a pid |
|
|
-spec info(N,N,N) -> [{info_type(), [{info_key(),term()}]},...] when |
|
|
|
|
|
N :: non_neg_integer(). |
|
|
|
|
|
info(A,B,C) -> info(recon_lib:triple_to_pid(A,B,C)). |
|
|
|
|
|
|
|
|
-spec info(N, N, N) -> [{info_type(), [{info_key(), term()}]}, ...] when |
|
|
|
|
|
N :: non_neg_integer(). |
|
|
|
|
|
info(A, B, C) -> info(recon_lib:triple_to_pid(A, B, C)). |
|
|
|
|
|
|
|
|
%% @doc Equivalent to `info(<A.B.C>, Key)' where `A', `B', and `C' are integers part |
|
|
%% @doc Equivalent to `info(<A.B.C>, Key)' where `A', `B', and `C' are integers part |
|
|
%% of a pid |
|
|
%% of a pid |
|
|
-spec info(N,N,N, Key) -> term() when |
|
|
|
|
|
N :: non_neg_integer(), |
|
|
|
|
|
Key :: info_type() | [atom()] | atom(). |
|
|
|
|
|
info(A,B,C, Key) -> info(recon_lib:triple_to_pid(A,B,C), Key). |
|
|
|
|
|
|
|
|
-spec info(N, N, N, Key) -> term() when |
|
|
|
|
|
N :: non_neg_integer(), |
|
|
|
|
|
Key :: info_type() | [atom()] | atom(). |
|
|
|
|
|
info(A, B, C, Key) -> info(recon_lib:triple_to_pid(A, B, C), Key). |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
%% @doc Allows to be similar to `erlang:process_info/1', but excludes fields |
|
|
%% @doc Allows to be similar to `erlang:process_info/1', but excludes fields |
|
@ -174,11 +174,11 @@ info(A,B,C, Key) -> info(recon_lib:triple_to_pid(A,B,C), Key). |
|
|
%% another registry supported in the `{via, Module, Name}' syntax (must have a |
|
|
%% another registry supported in the `{via, Module, Name}' syntax (must have a |
|
|
%% `Module:whereis_name/1' function). Pids can also be passed in as a string |
|
|
%% `Module:whereis_name/1' function). Pids can also be passed in as a string |
|
|
%% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used. |
|
|
%% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used. |
|
|
-spec info(pid_term()) -> [{info_type(), [{info_key(), Value}]},...] when |
|
|
|
|
|
Value :: term(). |
|
|
|
|
|
|
|
|
-spec info(pid_term()) -> [{info_type(), [{info_key(), Value}]}, ...] when |
|
|
|
|
|
Value :: term(). |
|
|
info(PidTerm) -> |
|
|
info(PidTerm) -> |
|
|
Pid = recon_lib:term_to_pid(PidTerm), |
|
|
|
|
|
[info(Pid, Type) || Type <- [meta, signals, location, memory_used, work]]. |
|
|
|
|
|
|
|
|
Pid = recon_lib:term_to_pid(PidTerm), |
|
|
|
|
|
[info(Pid, Type) || Type <- [meta, signals, location, memory_used, work]]. |
|
|
|
|
|
|
|
|
%% @doc Allows to be similar to `erlang:process_info/2', but allows to |
|
|
%% @doc Allows to be similar to `erlang:process_info/2', but allows to |
|
|
%% sort fields by safe categories and pre-selections, avoiding items such |
|
|
%% sort fields by safe categories and pre-selections, avoiding items such |
|
@ -198,66 +198,66 @@ info(PidTerm) -> |
|
|
%% A fake attribute `binary_memory' is also available to return the |
|
|
%% A fake attribute `binary_memory' is also available to return the |
|
|
%% amount of memory used by refc binaries for a process. |
|
|
%% amount of memory used by refc binaries for a process. |
|
|
-spec info(pid_term(), info_type()) -> {info_type(), [{info_key(), term()}]} |
|
|
-spec info(pid_term(), info_type()) -> {info_type(), [{info_key(), term()}]} |
|
|
; (pid_term(), [atom()]) -> [{atom(), term()}] |
|
|
|
|
|
; (pid_term(), atom()) -> {atom(), term()}. |
|
|
|
|
|
|
|
|
; (pid_term(), [atom()]) -> [{atom(), term()}] |
|
|
|
|
|
; (pid_term(), atom()) -> {atom(), term()}. |
|
|
info(PidTerm, meta) -> |
|
|
info(PidTerm, meta) -> |
|
|
info_type(PidTerm, meta, [registered_name, dictionary, group_leader, |
|
|
|
|
|
status]); |
|
|
|
|
|
|
|
|
info_type(PidTerm, meta, [registered_name, dictionary, group_leader, |
|
|
|
|
|
status]); |
|
|
info(PidTerm, signals) -> |
|
|
info(PidTerm, signals) -> |
|
|
info_type(PidTerm, signals, [links, monitors, monitored_by, trap_exit]); |
|
|
|
|
|
|
|
|
info_type(PidTerm, signals, [links, monitors, monitored_by, trap_exit]); |
|
|
info(PidTerm, location) -> |
|
|
info(PidTerm, location) -> |
|
|
info_type(PidTerm, location, [initial_call, current_stacktrace]); |
|
|
|
|
|
|
|
|
info_type(PidTerm, location, [initial_call, current_stacktrace]); |
|
|
info(PidTerm, memory_used) -> |
|
|
info(PidTerm, memory_used) -> |
|
|
info_type(PidTerm, memory_used, [memory, message_queue_len, heap_size, |
|
|
|
|
|
total_heap_size, garbage_collection]); |
|
|
|
|
|
|
|
|
info_type(PidTerm, memory_used, [memory, message_queue_len, heap_size, |
|
|
|
|
|
total_heap_size, garbage_collection]); |
|
|
info(PidTerm, work) -> |
|
|
info(PidTerm, work) -> |
|
|
info_type(PidTerm, work, [reductions]); |
|
|
|
|
|
|
|
|
info_type(PidTerm, work, [reductions]); |
|
|
info(PidTerm, Keys) -> |
|
|
info(PidTerm, Keys) -> |
|
|
proc_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. |
|
|
%% @private makes access to `info_type()' calls simpler. |
|
|
-spec info_type(pid_term(), info_type(), [info_key()]) -> |
|
|
-spec info_type(pid_term(), info_type(), [info_key()]) -> |
|
|
{info_type(), [{info_key(), term()}]}. |
|
|
|
|
|
|
|
|
{info_type(), [{info_key(), term()}]}. |
|
|
info_type(PidTerm, Type, Keys) -> |
|
|
info_type(PidTerm, Type, Keys) -> |
|
|
Pid = recon_lib:term_to_pid(PidTerm), |
|
|
|
|
|
{Type, proc_info(Pid, Keys)}. |
|
|
|
|
|
|
|
|
Pid = recon_lib:term_to_pid(PidTerm), |
|
|
|
|
|
{Type, proc_info(Pid, Keys)}. |
|
|
|
|
|
|
|
|
%% @private wrapper around `erlang:process_info/2' that allows special |
|
|
%% @private wrapper around `erlang:process_info/2' that allows special |
|
|
%% attribute handling for items like `binary_memory'. |
|
|
%% attribute handling for items like `binary_memory'. |
|
|
proc_info(Pid, binary_memory) -> |
|
|
proc_info(Pid, binary_memory) -> |
|
|
{binary, Bins} = erlang:process_info(Pid, binary), |
|
|
|
|
|
{binary_memory, recon_lib:binary_memory(Bins)}; |
|
|
|
|
|
|
|
|
{binary, Bins} = erlang:process_info(Pid, binary), |
|
|
|
|
|
{binary_memory, recon_lib:binary_memory(Bins)}; |
|
|
proc_info(Pid, Term) when is_atom(Term) -> |
|
|
proc_info(Pid, Term) when is_atom(Term) -> |
|
|
erlang:process_info(Pid, Term); |
|
|
|
|
|
|
|
|
erlang:process_info(Pid, Term); |
|
|
proc_info(Pid, List) when is_list(List) -> |
|
|
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. |
|
|
|
|
|
|
|
|
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 |
|
|
%% @private Replace keys around |
|
|
replace(_, _, []) -> []; |
|
|
replace(_, _, []) -> []; |
|
|
replace(H, Val, [H|T]) -> [Val | replace(H, Val, T)]; |
|
|
|
|
|
replace(R, Val, [H|T]) -> [H | replace(R, Val, T)]. |
|
|
|
|
|
|
|
|
replace(H, Val, [H | T]) -> [Val | replace(H, Val, T)]; |
|
|
|
|
|
replace(R, Val, [H | T]) -> [H | replace(R, Val, T)]. |
|
|
|
|
|
|
|
|
proc_fake([], []) -> |
|
|
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)]. |
|
|
|
|
|
|
|
|
[]; |
|
|
|
|
|
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 (except the |
|
|
%% @doc Fetches a given attribute from all processes (except the |
|
|
%% caller) and returns the biggest `Num' consumers. |
|
|
%% caller) and returns the biggest `Num' consumers. |
|
|
-spec proc_count(AttributeName, Num) -> [proc_attrs()] when |
|
|
-spec proc_count(AttributeName, Num) -> [proc_attrs()] when |
|
|
AttributeName :: atom(), |
|
|
|
|
|
Num :: non_neg_integer(). |
|
|
|
|
|
|
|
|
AttributeName :: atom(), |
|
|
|
|
|
Num :: non_neg_integer(). |
|
|
proc_count(AttrName, Num) -> |
|
|
proc_count(AttrName, Num) -> |
|
|
recon_lib:sublist_top_n_attrs(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 |
|
|
%% @doc Fetches a given attribute from all processes (except the |
|
|
%% caller) and returns the biggest entries, over a sliding time window. |
|
|
%% caller) and returns the biggest entries, over a sliding time window. |
|
@ -285,13 +285,13 @@ proc_count(AttrName, Num) -> |
|
|
%% building a dictionary with entries to differentiate them. This can take a |
|
|
%% building a dictionary with entries to differentiate them. This can take a |
|
|
%% heavy toll on memory when you have many dozens of thousands of processes. |
|
|
%% heavy toll on memory when you have many dozens of thousands of processes. |
|
|
-spec proc_window(AttributeName, Num, Milliseconds) -> [proc_attrs()] when |
|
|
-spec proc_window(AttributeName, Num, Milliseconds) -> [proc_attrs()] when |
|
|
AttributeName :: atom(), |
|
|
|
|
|
Num :: non_neg_integer(), |
|
|
|
|
|
Milliseconds :: pos_integer(). |
|
|
|
|
|
|
|
|
AttributeName :: atom(), |
|
|
|
|
|
Num :: non_neg_integer(), |
|
|
|
|
|
Milliseconds :: pos_integer(). |
|
|
proc_window(AttrName, Num, Time) -> |
|
|
proc_window(AttrName, Num, Time) -> |
|
|
Sample = fun() -> recon_lib:proc_attrs(AttrName) end, |
|
|
|
|
|
{First,Last} = recon_lib:sample(Time, Sample), |
|
|
|
|
|
recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num). |
|
|
|
|
|
|
|
|
Sample = fun() -> recon_lib:proc_attrs(AttrName) end, |
|
|
|
|
|
{First, Last} = recon_lib:sample(Time, Sample), |
|
|
|
|
|
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 |
|
|
%% @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 |
|
|
%% around and do little else, or when extremely busy processes reach a stable |
|
@ -308,24 +308,24 @@ proc_window(AttrName, Num, Time) -> |
|
|
%% for more details on refc binaries |
|
|
%% for more details on refc binaries |
|
|
-spec bin_leak(pos_integer()) -> [proc_attrs()]. |
|
|
-spec bin_leak(pos_integer()) -> [proc_attrs()]. |
|
|
bin_leak(N) -> |
|
|
bin_leak(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, []} |
|
|
|
|
|
end || Pid <- processes() |
|
|
|
|
|
], N), |
|
|
|
|
|
[{Pid, -Val, Id} ||{Pid, Val, Id} <-Procs]. |
|
|
|
|
|
|
|
|
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, []} |
|
|
|
|
|
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)'. |
|
|
%% @doc Shorthand for `node_stats(N, Interval, fun(X,_) -> io:format("~p~n",[X]) end, nostate)'. |
|
|
-spec node_stats_print(Repeat, Interval) -> term() when |
|
|
-spec node_stats_print(Repeat, Interval) -> term() when |
|
|
Repeat :: non_neg_integer(), |
|
|
|
|
|
Interval :: pos_integer(). |
|
|
|
|
|
|
|
|
Repeat :: non_neg_integer(), |
|
|
|
|
|
Interval :: pos_integer(). |
|
|
node_stats_print(N, Interval) -> |
|
|
node_stats_print(N, Interval) -> |
|
|
node_stats(N, Interval, fun(X, _) -> io:format("~p~n", [X]) end, ok). |
|
|
|
|
|
|
|
|
node_stats(N, Interval, fun(X, _) -> io:format("~p~n", [X]) end, ok). |
|
|
|
|
|
|
|
|
%% @doc Because Erlang CPU usage as reported from `top' isn't the most |
|
|
%% @doc Because Erlang CPU usage as reported from `top' isn't the most |
|
|
%% reliable value (due to schedulers doing idle spinning to avoid going |
|
|
%% reliable value (due to schedulers doing idle spinning to avoid going |
|
@ -346,29 +346,29 @@ node_stats_print(N, Interval) -> |
|
|
%% |
|
|
%% |
|
|
%% A scheduler isn't busy when doing anything else. |
|
|
%% A scheduler isn't busy when doing anything else. |
|
|
-spec scheduler_usage(Millisecs) -> undefined | [{SchedulerId, Usage}] when |
|
|
-spec scheduler_usage(Millisecs) -> undefined | [{SchedulerId, Usage}] when |
|
|
Millisecs :: non_neg_integer(), |
|
|
|
|
|
SchedulerId :: pos_integer(), |
|
|
|
|
|
Usage :: number(). |
|
|
|
|
|
|
|
|
Millisecs :: non_neg_integer(), |
|
|
|
|
|
SchedulerId :: pos_integer(), |
|
|
|
|
|
Usage :: number(). |
|
|
scheduler_usage(Interval) when is_integer(Interval) -> |
|
|
scheduler_usage(Interval) when is_integer(Interval) -> |
|
|
%% We start and stop the scheduler_wall_time system flag if |
|
|
|
|
|
%% it wasn't in place already. Usually setting the flag should |
|
|
|
|
|
%% have a CPU impact (making it higher) only when under low usage. |
|
|
|
|
|
FormerFlag = erlang:system_flag(scheduler_wall_time, true), |
|
|
|
|
|
First = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
timer:sleep(Interval), |
|
|
|
|
|
Last = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
erlang:system_flag(scheduler_wall_time, FormerFlag), |
|
|
|
|
|
recon_lib:scheduler_usage_diff(First, Last). |
|
|
|
|
|
|
|
|
%% We start and stop the scheduler_wall_time system flag if |
|
|
|
|
|
%% it wasn't in place already. Usually setting the flag should |
|
|
|
|
|
%% have a CPU impact (making it higher) only when under low usage. |
|
|
|
|
|
FormerFlag = erlang:system_flag(scheduler_wall_time, true), |
|
|
|
|
|
First = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
timer:sleep(Interval), |
|
|
|
|
|
Last = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
erlang:system_flag(scheduler_wall_time, FormerFlag), |
|
|
|
|
|
recon_lib:scheduler_usage_diff(First, Last). |
|
|
|
|
|
|
|
|
%% @doc Shorthand for `node_stats(N, Interval, fun(X,Acc) -> [X|Acc] end, [])' |
|
|
%% @doc Shorthand for `node_stats(N, Interval, fun(X,Acc) -> [X|Acc] end, [])' |
|
|
%% with the results reversed to be in the right temporal order. |
|
|
%% with the results reversed to be in the right temporal order. |
|
|
-spec node_stats_list(Repeat, Interval) -> [Stats] when |
|
|
-spec node_stats_list(Repeat, Interval) -> [Stats] when |
|
|
Repeat :: non_neg_integer(), |
|
|
|
|
|
Interval :: pos_integer(), |
|
|
|
|
|
Stats :: {[Absolutes::{atom(),term()}], |
|
|
|
|
|
[Increments::{atom(),term()}]}. |
|
|
|
|
|
|
|
|
Repeat :: non_neg_integer(), |
|
|
|
|
|
Interval :: pos_integer(), |
|
|
|
|
|
Stats :: {[Absolutes :: {atom(), term()}], |
|
|
|
|
|
[Increments :: {atom(), term()}]}. |
|
|
node_stats_list(N, Interval) -> |
|
|
node_stats_list(N, Interval) -> |
|
|
lists:reverse(node_stats(N, Interval, fun(X, Acc) -> [X|Acc] end, [])). |
|
|
|
|
|
|
|
|
lists:reverse(node_stats(N, Interval, fun(X, Acc) -> [X | Acc] end, [])). |
|
|
|
|
|
|
|
|
%% @doc Gathers statistics `N' time, waiting `Interval' milliseconds between |
|
|
%% @doc Gathers statistics `N' time, waiting `Interval' milliseconds between |
|
|
%% each run, and accumulates results using a folding function `FoldFun'. |
|
|
%% each run, and accumulates results using a folding function `FoldFun'. |
|
@ -386,71 +386,71 @@ node_stats_list(N, Interval) -> |
|
|
%% runs, words of memory that were garbage collected, and the global reductions |
|
|
%% runs, words of memory that were garbage collected, and the global reductions |
|
|
%% count for the node. |
|
|
%% count for the node. |
|
|
-spec node_stats(N, Interval, FoldFun, Acc) -> Acc when |
|
|
-spec node_stats(N, Interval, FoldFun, Acc) -> Acc when |
|
|
N :: non_neg_integer(), |
|
|
|
|
|
Interval :: pos_integer(), |
|
|
|
|
|
FoldFun :: fun((Stats, Acc) -> Acc), |
|
|
|
|
|
Acc :: term(), |
|
|
|
|
|
Stats :: {[Absolutes::{atom(),term()}], |
|
|
|
|
|
[Increments::{atom(),term()}]}. |
|
|
|
|
|
|
|
|
N :: non_neg_integer(), |
|
|
|
|
|
Interval :: pos_integer(), |
|
|
|
|
|
FoldFun :: fun((Stats, Acc) -> Acc), |
|
|
|
|
|
Acc :: term(), |
|
|
|
|
|
Stats :: {[Absolutes :: {atom(), term()}], |
|
|
|
|
|
[Increments :: {atom(), term()}]}. |
|
|
node_stats(N, Interval, FoldFun, Init) -> |
|
|
node_stats(N, Interval, FoldFun, Init) -> |
|
|
Logger = case whereis(error_logger) of |
|
|
|
|
|
undefined -> logger; |
|
|
|
|
|
_ -> error_logger |
|
|
|
|
|
end, |
|
|
|
|
|
%% Turn on scheduler wall time if it wasn't there already |
|
|
|
|
|
FormerFlag = erlang:system_flag(scheduler_wall_time, true), |
|
|
|
|
|
%% Stats is an ugly fun, but it does its thing. |
|
|
|
|
|
Stats = fun({{OldIn,OldOut},{OldGCs,OldWords,_}, SchedWall}) -> |
|
|
|
|
|
%% Absolutes |
|
|
|
|
|
ProcC = erlang:system_info(process_count), |
|
|
|
|
|
RunQ = erlang:statistics(run_queue), |
|
|
|
|
|
LogQ = case Logger of |
|
|
|
|
|
error_logger -> |
|
|
|
|
|
{_,LogQLen} = process_info(whereis(error_logger), |
|
|
|
|
|
message_queue_len), |
|
|
|
|
|
LogQLen; |
|
|
|
|
|
_ -> |
|
|
|
|
|
undefined |
|
|
|
|
|
end, |
|
|
|
|
|
%% Mem (Absolutes) |
|
|
|
|
|
Mem = erlang:memory(), |
|
|
|
|
|
Tot = proplists:get_value(total, Mem), |
|
|
|
|
|
ProcM = proplists:get_value(processes_used,Mem), |
|
|
|
|
|
Atom = proplists:get_value(atom_used,Mem), |
|
|
|
|
|
Bin = proplists:get_value(binary, Mem), |
|
|
|
|
|
Ets = proplists:get_value(ets, Mem), |
|
|
|
|
|
%% Incremental |
|
|
|
|
|
{{input,In},{output,Out}} = erlang:statistics(io), |
|
|
|
|
|
GC={GCs,Words,_} = erlang:statistics(garbage_collection), |
|
|
|
|
|
BytesIn = In-OldIn, |
|
|
|
|
|
BytesOut = Out-OldOut, |
|
|
|
|
|
GCCount = GCs-OldGCs, |
|
|
|
|
|
GCWords = Words-OldWords, |
|
|
|
|
|
{_, Reds} = erlang:statistics(reductions), |
|
|
|
|
|
SchedWallNew = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
SchedUsage = recon_lib:scheduler_usage_diff(SchedWall, SchedWallNew), |
|
|
|
|
|
%% Stats Results |
|
|
|
|
|
{{[{process_count,ProcC}, {run_queue,RunQ}] ++ |
|
|
|
|
|
[{error_logger_queue_len,LogQ} || LogQ =/= undefined] ++ |
|
|
|
|
|
[{memory_total,Tot}, |
|
|
|
|
|
{memory_procs,ProcM}, {memory_atoms,Atom}, |
|
|
|
|
|
{memory_bin,Bin}, {memory_ets,Ets}], |
|
|
|
|
|
[{bytes_in,BytesIn}, {bytes_out,BytesOut}, |
|
|
|
|
|
{gc_count,GCCount}, {gc_words_reclaimed,GCWords}, |
|
|
|
|
|
{reductions,Reds}, {scheduler_usage, SchedUsage}]}, |
|
|
|
|
|
|
|
|
Logger = case whereis(error_logger) of |
|
|
|
|
|
undefined -> logger; |
|
|
|
|
|
_ -> error_logger |
|
|
|
|
|
end, |
|
|
|
|
|
%% Turn on scheduler wall time if it wasn't there already |
|
|
|
|
|
FormerFlag = erlang:system_flag(scheduler_wall_time, true), |
|
|
|
|
|
%% Stats is an ugly fun, but it does its thing. |
|
|
|
|
|
Stats = fun({{OldIn, OldOut}, {OldGCs, OldWords, _}, SchedWall}) -> |
|
|
|
|
|
%% Absolutes |
|
|
|
|
|
ProcC = erlang:system_info(process_count), |
|
|
|
|
|
RunQ = erlang:statistics(run_queue), |
|
|
|
|
|
LogQ = case Logger of |
|
|
|
|
|
error_logger -> |
|
|
|
|
|
{_, LogQLen} = process_info(whereis(error_logger), |
|
|
|
|
|
message_queue_len), |
|
|
|
|
|
LogQLen; |
|
|
|
|
|
_ -> |
|
|
|
|
|
undefined |
|
|
|
|
|
end, |
|
|
|
|
|
%% Mem (Absolutes) |
|
|
|
|
|
Mem = erlang:memory(), |
|
|
|
|
|
Tot = proplists:get_value(total, Mem), |
|
|
|
|
|
ProcM = proplists:get_value(processes_used, Mem), |
|
|
|
|
|
Atom = proplists:get_value(atom_used, Mem), |
|
|
|
|
|
Bin = proplists:get_value(binary, Mem), |
|
|
|
|
|
Ets = proplists:get_value(ets, Mem), |
|
|
|
|
|
%% Incremental |
|
|
|
|
|
{{input, In}, {output, Out}} = erlang:statistics(io), |
|
|
|
|
|
GC = {GCs, Words, _} = erlang:statistics(garbage_collection), |
|
|
|
|
|
BytesIn = In - OldIn, |
|
|
|
|
|
BytesOut = Out - OldOut, |
|
|
|
|
|
GCCount = GCs - OldGCs, |
|
|
|
|
|
GCWords = Words - OldWords, |
|
|
|
|
|
{_, Reds} = erlang:statistics(reductions), |
|
|
|
|
|
SchedWallNew = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
SchedUsage = recon_lib:scheduler_usage_diff(SchedWall, SchedWallNew), |
|
|
|
|
|
%% Stats Results |
|
|
|
|
|
{{[{process_count, ProcC}, {run_queue, RunQ}] ++ |
|
|
|
|
|
[{error_logger_queue_len, LogQ} || LogQ =/= undefined] ++ |
|
|
|
|
|
[{memory_total, Tot}, |
|
|
|
|
|
{memory_procs, ProcM}, {memory_atoms, Atom}, |
|
|
|
|
|
{memory_bin, Bin}, {memory_ets, Ets}], |
|
|
|
|
|
[{bytes_in, BytesIn}, {bytes_out, BytesOut}, |
|
|
|
|
|
{gc_count, GCCount}, {gc_words_reclaimed, GCWords}, |
|
|
|
|
|
{reductions, Reds}, {scheduler_usage, SchedUsage}]}, |
|
|
%% New State |
|
|
%% New State |
|
|
{{In,Out}, GC, SchedWallNew}} |
|
|
|
|
|
end, |
|
|
|
|
|
{{input,In},{output,Out}} = erlang:statistics(io), |
|
|
|
|
|
Gc = erlang:statistics(garbage_collection), |
|
|
|
|
|
SchedWall = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
Result = recon_lib:time_fold( |
|
|
|
|
|
N, Interval, Stats, |
|
|
|
|
|
{{In,Out}, Gc, SchedWall}, |
|
|
|
|
|
FoldFun, Init), |
|
|
|
|
|
%% Set scheduler wall time back to what it was |
|
|
|
|
|
erlang:system_flag(scheduler_wall_time, FormerFlag), |
|
|
|
|
|
Result. |
|
|
|
|
|
|
|
|
{{In, Out}, GC, SchedWallNew}} |
|
|
|
|
|
end, |
|
|
|
|
|
{{input, In}, {output, Out}} = erlang:statistics(io), |
|
|
|
|
|
Gc = erlang:statistics(garbage_collection), |
|
|
|
|
|
SchedWall = erlang:statistics(scheduler_wall_time), |
|
|
|
|
|
Result = recon_lib:time_fold( |
|
|
|
|
|
N, Interval, Stats, |
|
|
|
|
|
{{In, Out}, Gc, SchedWall}, |
|
|
|
|
|
FoldFun, Init), |
|
|
|
|
|
%% Set scheduler wall time back to what it was |
|
|
|
|
|
erlang:system_flag(scheduler_wall_time, FormerFlag), |
|
|
|
|
|
Result. |
|
|
|
|
|
|
|
|
%%% OTP & Manipulations %%% |
|
|
%%% OTP & Manipulations %%% |
|
|
|
|
|
|
|
@ -462,22 +462,22 @@ get_state(PidTerm) -> get_state(PidTerm, 5000). |
|
|
%% @doc Fetch the internal state of an OTP process. |
|
|
%% @doc Fetch the internal state of an OTP process. |
|
|
%% Calls `sys:get_state/2' directly in R16B01+, and fetches |
|
|
%% Calls `sys:get_state/2' directly in R16B01+, and fetches |
|
|
%% it dynamically on older versions of OTP. |
|
|
%% it dynamically on older versions of OTP. |
|
|
-spec get_state(pid_term(), Ms::
non_neg_integer() | 'infinity') -> term(). |
|
|
|
|
|
|
|
|
-spec get_state(pid_term(), Ms :: non_neg_integer() | 'infinity') -> term(). |
|
|
get_state(PidTerm, Timeout) -> |
|
|
get_state(PidTerm, Timeout) -> |
|
|
Proc = recon_lib:term_to_pid(PidTerm), |
|
|
|
|
|
try |
|
|
|
|
|
sys:get_state(Proc, Timeout) |
|
|
|
|
|
catch |
|
|
|
|
|
error:undef -> |
|
|
|
|
|
case sys:get_status(Proc, Timeout) of |
|
|
|
|
|
{status,_Pid,{module,gen_server},Data} -> |
|
|
|
|
|
{data, Props} = lists:last(lists:nth(5, Data)), |
|
|
|
|
|
proplists:get_value("State", Props); |
|
|
|
|
|
{status,_Pod,{module,gen_fsm},Data} -> |
|
|
|
|
|
{data, Props} = lists:last(lists:nth(5, Data)), |
|
|
|
|
|
proplists:get_value("StateData", Props) |
|
|
|
|
|
end |
|
|
|
|
|
end. |
|
|
|
|
|
|
|
|
Proc = recon_lib:term_to_pid(PidTerm), |
|
|
|
|
|
try |
|
|
|
|
|
sys:get_state(Proc, Timeout) |
|
|
|
|
|
catch |
|
|
|
|
|
error:undef -> |
|
|
|
|
|
case sys:get_status(Proc, Timeout) of |
|
|
|
|
|
{status, _Pid, {module, gen_server}, Data} -> |
|
|
|
|
|
{data, Props} = lists:last(lists:nth(5, Data)), |
|
|
|
|
|
proplists:get_value("State", Props); |
|
|
|
|
|
{status, _Pod, {module, gen_fsm}, Data} -> |
|
|
|
|
|
{data, Props} = lists:last(lists:nth(5, Data)), |
|
|
|
|
|
proplists:get_value("StateData", Props) |
|
|
|
|
|
end |
|
|
|
|
|
end. |
|
|
|
|
|
|
|
|
%%% Code & Stuff %%% |
|
|
%%% Code & Stuff %%% |
|
|
|
|
|
|
|
@ -488,14 +488,14 @@ remote_load(Mod) -> remote_load(nodes(), Mod). |
|
|
%% @doc Loads one or more modules remotely, in a diskless manner. Allows to |
|
|
%% @doc Loads one or more modules remotely, in a diskless manner. Allows to |
|
|
%% share code loaded locally with a remote node that doesn't have it |
|
|
%% share code loaded locally with a remote node that doesn't have it |
|
|
-spec remote_load(Nodes, module()) -> term() when |
|
|
-spec remote_load(Nodes, module()) -> term() when |
|
|
Nodes :: [node(),...] | node(). |
|
|
|
|
|
remote_load(Nodes=[_|_], Mod) when is_atom(Mod) -> |
|
|
|
|
|
{Mod, Bin, File} = code:get_object_code(Mod), |
|
|
|
|
|
rpc:multicall(Nodes, code, load_binary, [Mod, File, Bin]); |
|
|
|
|
|
remote_load(Nodes=[_|_], Modules) when is_list(Modules) -> |
|
|
|
|
|
[remote_load(Nodes, Mod) || Mod <- Modules]; |
|
|
|
|
|
|
|
|
Nodes :: [node(), ...] | node(). |
|
|
|
|
|
remote_load(Nodes = [_ | _], Mod) when is_atom(Mod) -> |
|
|
|
|
|
{Mod, Bin, File} = code:get_object_code(Mod), |
|
|
|
|
|
rpc:multicall(Nodes, code, load_binary, [Mod, File, Bin]); |
|
|
|
|
|
remote_load(Nodes = [_ | _], Modules) when is_list(Modules) -> |
|
|
|
|
|
[remote_load(Nodes, Mod) || Mod <- Modules]; |
|
|
remote_load(Node, Mod) -> |
|
|
remote_load(Node, Mod) -> |
|
|
remote_load([Node], Mod). |
|
|
|
|
|
|
|
|
remote_load([Node], Mod). |
|
|
|
|
|
|
|
|
%% @doc Obtain the source code of a module compiled with `debug_info'. |
|
|
%% @doc Obtain the source code of a module compiled with `debug_info'. |
|
|
%% The returned list sadly does not allow to format the types and typed |
|
|
%% The returned list sadly does not allow to format the types and typed |
|
@ -505,9 +505,9 @@ remote_load(Node, Mod) -> |
|
|
%% @todo Figure out a way to pretty-print typespecs and records. |
|
|
%% @todo Figure out a way to pretty-print typespecs and records. |
|
|
-spec source(module()) -> iolist(). |
|
|
-spec source(module()) -> iolist(). |
|
|
source(Module) -> |
|
|
source(Module) -> |
|
|
Path = code:which(Module), |
|
|
|
|
|
{ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Path, [abstract_code]), |
|
|
|
|
|
erl_prettypr:format(erl_syntax:form_list(AC)). |
|
|
|
|
|
|
|
|
Path = code:which(Module), |
|
|
|
|
|
{ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Path, [abstract_code]), |
|
|
|
|
|
erl_prettypr:format(erl_syntax:form_list(AC)). |
|
|
|
|
|
|
|
|
%%% Ports Info %%% |
|
|
%%% Ports Info %%% |
|
|
|
|
|
|
|
@ -532,13 +532,13 @@ files() -> recon_lib:port_list(name, "efile"). |
|
|
|
|
|
|
|
|
%% @doc Shows a list of all different ports on the node with their respective |
|
|
%% @doc Shows a list of all different ports on the node with their respective |
|
|
%% types. |
|
|
%% types. |
|
|
-spec port_types() -> [{Type::
string(), Count::
pos_integer()}]. |
|
|
|
|
|
|
|
|
-spec port_types() -> [{Type :: string(), Count :: pos_integer()}]. |
|
|
port_types() -> |
|
|
port_types() -> |
|
|
lists:usort( |
|
|
|
|
|
%% sorts by biggest count, smallest type |
|
|
|
|
|
fun({KA,VA}, {KB,VB}) -> {VA,KB} > {VB,KA} end, |
|
|
|
|
|
recon_lib:count([Name || {_, Name} <- recon_lib:port_list(name)]) |
|
|
|
|
|
). |
|
|
|
|
|
|
|
|
lists:usort( |
|
|
|
|
|
%% sorts by biggest count, smallest type |
|
|
|
|
|
fun({KA, VA}, {KB, VB}) -> {VA, KB} > {VB, KA} end, |
|
|
|
|
|
recon_lib:count([Name || {_, Name} <- recon_lib:port_list(name)]) |
|
|
|
|
|
). |
|
|
|
|
|
|
|
|
%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP) |
|
|
%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP) |
|
|
%% and returns the biggest `Num' consumers. |
|
|
%% and returns the biggest `Num' consumers. |
|
@ -549,11 +549,11 @@ port_types() -> |
|
|
%% respectively). Individual absolute values for each metric will be returned |
|
|
%% respectively). Individual absolute values for each metric will be returned |
|
|
%% in the 3rd position of the resulting tuple. |
|
|
%% in the 3rd position of the resulting tuple. |
|
|
-spec inet_count(AttributeName, Num) -> [inet_attrs()] when |
|
|
-spec inet_count(AttributeName, Num) -> [inet_attrs()] when |
|
|
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' |
|
|
|
|
|
| 'cnt' | 'oct', |
|
|
|
|
|
Num :: non_neg_integer(). |
|
|
|
|
|
|
|
|
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' |
|
|
|
|
|
| 'cnt' | 'oct', |
|
|
|
|
|
Num :: non_neg_integer(). |
|
|
inet_count(Attr, Num) -> |
|
|
inet_count(Attr, Num) -> |
|
|
recon_lib:sublist_top_n_attrs(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) |
|
|
%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP) |
|
|
%% and returns the biggest entries, over a sliding time window. |
|
|
%% and returns the biggest entries, over a sliding time window. |
|
@ -568,14 +568,14 @@ inet_count(Attr, Num) -> |
|
|
%% respectively). Individual absolute values for each metric will be returned |
|
|
%% respectively). Individual absolute values for each metric will be returned |
|
|
%% in the 3rd position of the resulting tuple. |
|
|
%% in the 3rd position of the resulting tuple. |
|
|
-spec inet_window(AttributeName, Num, Milliseconds) -> [inet_attrs()] when |
|
|
-spec inet_window(AttributeName, Num, Milliseconds) -> [inet_attrs()] when |
|
|
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' |
|
|
|
|
|
| 'cnt' | 'oct', |
|
|
|
|
|
Num :: non_neg_integer(), |
|
|
|
|
|
Milliseconds :: pos_integer(). |
|
|
|
|
|
|
|
|
AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' |
|
|
|
|
|
| 'cnt' | 'oct', |
|
|
|
|
|
Num :: non_neg_integer(), |
|
|
|
|
|
Milliseconds :: pos_integer(). |
|
|
inet_window(Attr, Num, Time) when is_atom(Attr) -> |
|
|
inet_window(Attr, Num, Time) when is_atom(Attr) -> |
|
|
Sample = fun() -> recon_lib:inet_attrs(Attr) end, |
|
|
|
|
|
{First,Last} = recon_lib:sample(Time, Sample), |
|
|
|
|
|
recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num). |
|
|
|
|
|
|
|
|
Sample = fun() -> recon_lib:inet_attrs(Attr) end, |
|
|
|
|
|
{First, Last} = recon_lib:sample(Time, Sample), |
|
|
|
|
|
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 |
|
|
%% @doc Allows to be similar to `erlang:port_info/1', but allows |
|
|
%% more flexible port usage: usual ports, ports that were registered |
|
|
%% more flexible port usage: usual ports, ports that were registered |
|
@ -591,11 +591,11 @@ inet_window(Attr, Num, Time) when is_atom(Attr) -> |
|
|
%% The information-specific and the basic port info are sorted and |
|
|
%% The information-specific and the basic port info are sorted and |
|
|
%% categorized in broader categories ({@link port_info_type()}). |
|
|
%% categorized in broader categories ({@link port_info_type()}). |
|
|
-spec port_info(port_term()) -> [{port_info_type(), |
|
|
-spec port_info(port_term()) -> [{port_info_type(), |
|
|
[{port_info_key(), term()}]},...]. |
|
|
|
|
|
|
|
|
[{port_info_key(), term()}]}, ...]. |
|
|
port_info(PortTerm) -> |
|
|
port_info(PortTerm) -> |
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
[port_info(Port, Type) || Type <- [meta, signals, io, memory_used, |
|
|
|
|
|
specific]]. |
|
|
|
|
|
|
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
[port_info(Port, Type) || Type <- [meta, signals, io, memory_used, |
|
|
|
|
|
specific]]. |
|
|
|
|
|
|
|
|
%% @doc Allows to be similar to `erlang:port_info/2', but allows |
|
|
%% @doc Allows to be similar to `erlang:port_info/2', but allows |
|
|
%% more flexible port usage: usual ports, ports that were registered |
|
|
%% more flexible port usage: usual ports, ports that were registered |
|
@ -608,106 +608,106 @@ port_info(PortTerm) -> |
|
|
%% doesn't show it in the generated documentation, individual items |
|
|
%% doesn't show it in the generated documentation, individual items |
|
|
%% accepted by `erlang:port_info/2' are accepted, and lists of them too. |
|
|
%% accepted by `erlang:port_info/2' are accepted, and lists of them too. |
|
|
-spec port_info(port_term(), port_info_type()) -> {port_info_type(), |
|
|
-spec port_info(port_term(), port_info_type()) -> {port_info_type(), |
|
|
[{port_info_key(), _}]} |
|
|
|
|
|
; (port_term(), [atom()]) -> [{atom(), term()}] |
|
|
|
|
|
; (port_term(), atom()) -> {atom(), term()}. |
|
|
|
|
|
|
|
|
[{port_info_key(), _}]} |
|
|
|
|
|
; (port_term(), [atom()]) -> [{atom(), term()}] |
|
|
|
|
|
; (port_term(), atom()) -> {atom(), term()}. |
|
|
port_info(PortTerm, meta) -> |
|
|
port_info(PortTerm, meta) -> |
|
|
{meta, List} = port_info_type(PortTerm, meta, [id, name, os_pid]), |
|
|
|
|
|
case port_info(PortTerm, registered_name) of |
|
|
|
|
|
[] -> {meta, List}; |
|
|
|
|
|
Name -> {meta, [Name | List]} |
|
|
|
|
|
end; |
|
|
|
|
|
|
|
|
{meta, List} = port_info_type(PortTerm, meta, [id, name, os_pid]), |
|
|
|
|
|
case port_info(PortTerm, registered_name) of |
|
|
|
|
|
[] -> {meta, List}; |
|
|
|
|
|
Name -> {meta, [Name | List]} |
|
|
|
|
|
end; |
|
|
port_info(PortTerm, signals) -> |
|
|
port_info(PortTerm, signals) -> |
|
|
port_info_type(PortTerm, signals, [connected, links, monitors]); |
|
|
|
|
|
|
|
|
port_info_type(PortTerm, signals, [connected, links, monitors]); |
|
|
port_info(PortTerm, io) -> |
|
|
port_info(PortTerm, io) -> |
|
|
port_info_type(PortTerm, io, [input, output]); |
|
|
|
|
|
|
|
|
port_info_type(PortTerm, io, [input, output]); |
|
|
port_info(PortTerm, memory_used) -> |
|
|
port_info(PortTerm, memory_used) -> |
|
|
port_info_type(PortTerm, memory_used, [memory, queue_size]); |
|
|
|
|
|
|
|
|
port_info_type(PortTerm, memory_used, [memory, queue_size]); |
|
|
port_info(PortTerm, specific) -> |
|
|
port_info(PortTerm, specific) -> |
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
Props = case erlang:port_info(Port, name) of |
|
|
|
|
|
{_,Type} when Type =:= "udp_inet"; |
|
|
|
|
|
Type =:= "tcp_inet"; |
|
|
|
|
|
Type =:= "sctp_inet" -> |
|
|
|
|
|
case inet:getstat(Port) of |
|
|
|
|
|
{ok, Stats} -> [{statistics, Stats}]; |
|
|
|
|
|
_ -> [] |
|
|
|
|
|
end ++ |
|
|
|
|
|
case inet:peername(Port) of |
|
|
|
|
|
{ok, Peer} -> [{peername, Peer}]; |
|
|
|
|
|
{error, _} -> [] |
|
|
|
|
|
end ++ |
|
|
|
|
|
case inet:sockname(Port) of |
|
|
|
|
|
{ok, Local} -> [{sockname, Local}]; |
|
|
|
|
|
{error, _} -> [] |
|
|
|
|
|
end ++ |
|
|
|
|
|
case inet:getopts(Port, [active, broadcast, buffer, delay_send, |
|
|
|
|
|
dontroute, exit_on_close, header, |
|
|
|
|
|
high_watermark, ipv6_v6only, keepalive, |
|
|
|
|
|
linger, low_watermark, mode, nodelay, |
|
|
|
|
|
packet, packet_size, priority, |
|
|
|
|
|
read_packets, recbuf, reuseaddr, |
|
|
|
|
|
send_timeout, sndbuf]) of |
|
|
|
|
|
{ok, Opts} -> [{options, Opts}]; |
|
|
|
|
|
{error, _} -> [] |
|
|
|
|
|
end; |
|
|
|
|
|
{_,"efile"} -> |
|
|
|
|
|
%% would be nice to support file-specific info, but things |
|
|
|
|
|
%% are too vague with the file_server and how it works in |
|
|
|
|
|
%% order to make this work efficiently |
|
|
|
|
|
[]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
{type, Props}; |
|
|
|
|
|
|
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
Props = case erlang:port_info(Port, name) of |
|
|
|
|
|
{_, Type} when Type =:= "udp_inet"; |
|
|
|
|
|
Type =:= "tcp_inet"; |
|
|
|
|
|
Type =:= "sctp_inet" -> |
|
|
|
|
|
case inet:getstat(Port) of |
|
|
|
|
|
{ok, Stats} -> [{statistics, Stats}]; |
|
|
|
|
|
_ -> [] |
|
|
|
|
|
end ++ |
|
|
|
|
|
case inet:peername(Port) of |
|
|
|
|
|
{ok, Peer} -> [{peername, Peer}]; |
|
|
|
|
|
{error, _} -> [] |
|
|
|
|
|
end ++ |
|
|
|
|
|
case inet:sockname(Port) of |
|
|
|
|
|
{ok, Local} -> [{sockname, Local}]; |
|
|
|
|
|
{error, _} -> [] |
|
|
|
|
|
end ++ |
|
|
|
|
|
case inet:getopts(Port, [active, broadcast, buffer, delay_send, |
|
|
|
|
|
dontroute, exit_on_close, header, |
|
|
|
|
|
high_watermark, ipv6_v6only, keepalive, |
|
|
|
|
|
linger, low_watermark, mode, nodelay, |
|
|
|
|
|
packet, packet_size, priority, |
|
|
|
|
|
read_packets, recbuf, reuseaddr, |
|
|
|
|
|
send_timeout, sndbuf]) of |
|
|
|
|
|
{ok, Opts} -> [{options, Opts}]; |
|
|
|
|
|
{error, _} -> [] |
|
|
|
|
|
end; |
|
|
|
|
|
{_, "efile"} -> |
|
|
|
|
|
%% would be nice to support file-specific info, but things |
|
|
|
|
|
%% are too vague with the file_server and how it works in |
|
|
|
|
|
%% order to make this work efficiently |
|
|
|
|
|
[]; |
|
|
|
|
|
_ -> |
|
|
|
|
|
[] |
|
|
|
|
|
end, |
|
|
|
|
|
{type, Props}; |
|
|
port_info(PortTerm, Keys) when is_list(Keys) -> |
|
|
port_info(PortTerm, Keys) when is_list(Keys) -> |
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
[erlang:port_info(Port,Key) || Key <- Keys]; |
|
|
|
|
|
|
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
[erlang:port_info(Port, Key) || Key <- Keys]; |
|
|
port_info(PortTerm, Key) when is_atom(Key) -> |
|
|
port_info(PortTerm, Key) when is_atom(Key) -> |
|
|
erlang:port_info(recon_lib:term_to_port(PortTerm), Key). |
|
|
|
|
|
|
|
|
erlang:port_info(recon_lib:term_to_port(PortTerm), Key). |
|
|
|
|
|
|
|
|
%% @private makes access to `port_info_type()' calls simpler. |
|
|
%% @private makes access to `port_info_type()' calls simpler. |
|
|
%-spec port_info_type(pid_term(), port_info_type(), [port_info_key()]) -> |
|
|
%-spec port_info_type(pid_term(), port_info_type(), [port_info_key()]) -> |
|
|
% {port_info_type(), [{port_info_key(), term()}]}. |
|
|
% {port_info_type(), [{port_info_key(), term()}]}. |
|
|
port_info_type(PortTerm, Type, Keys) -> |
|
|
port_info_type(PortTerm, Type, Keys) -> |
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
{Type, [erlang:port_info(Port,Key) || Key <- Keys]}. |
|
|
|
|
|
|
|
|
Port = recon_lib:term_to_port(PortTerm), |
|
|
|
|
|
{Type, [erlang:port_info(Port, Key) || Key <- Keys]}. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
%%% RPC Utils %%% |
|
|
%%% RPC Utils %%% |
|
|
|
|
|
|
|
|
%% @doc Shorthand for `rpc([node()|nodes()], Fun)'. |
|
|
%% @doc Shorthand for `rpc([node()|nodes()], Fun)'. |
|
|
-spec rpc(fun(() -> term())) -> {[Success::_],[Fail::_]}. |
|
|
|
|
|
|
|
|
-spec rpc(fun(() -> term())) -> {[Success :: _], [Fail :: _]}. |
|
|
rpc(Fun) -> |
|
|
rpc(Fun) -> |
|
|
rpc([node()|nodes()], Fun). |
|
|
|
|
|
|
|
|
rpc([node() | nodes()], Fun). |
|
|
|
|
|
|
|
|
%% @doc Shorthand for `rpc(Nodes, Fun, infinity)'. |
|
|
%% @doc Shorthand for `rpc(Nodes, Fun, infinity)'. |
|
|
-spec rpc(node()|[node(),...], fun(() -> term())) -> {[Success::_],[Fail::_]}. |
|
|
|
|
|
|
|
|
-spec rpc(node()|[node(), ...], fun(() -> term())) -> {[Success :: _], [Fail :: _]}. |
|
|
rpc(Nodes, Fun) -> |
|
|
rpc(Nodes, Fun) -> |
|
|
rpc(Nodes, Fun, infinity). |
|
|
|
|
|
|
|
|
rpc(Nodes, Fun, infinity). |
|
|
|
|
|
|
|
|
%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes. |
|
|
%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes. |
|
|
-spec rpc(node()|[node(),...], fun(() -> term()), timeout()) -> {[Success::_],[Fail::_]}. |
|
|
|
|
|
rpc(Nodes=[_|_], Fun, Timeout) when is_function(Fun,0) -> |
|
|
|
|
|
rpc:multicall(Nodes, erlang, apply, [Fun,[]], Timeout); |
|
|
|
|
|
|
|
|
-spec rpc(node()|[node(), ...], fun(() -> term()), timeout()) -> {[Success :: _], [Fail :: _]}. |
|
|
|
|
|
rpc(Nodes = [_ | _], Fun, Timeout) when is_function(Fun, 0) -> |
|
|
|
|
|
rpc:multicall(Nodes, erlang, apply, [Fun, []], Timeout); |
|
|
rpc(Node, Fun, Timeout) when is_atom(Node) -> |
|
|
rpc(Node, Fun, Timeout) when is_atom(Node) -> |
|
|
rpc([Node], Fun, Timeout). |
|
|
|
|
|
|
|
|
rpc([Node], Fun, Timeout). |
|
|
|
|
|
|
|
|
%% @doc Shorthand for `named_rpc([node()|nodes()], Fun)'. |
|
|
%% @doc Shorthand for `named_rpc([node()|nodes()], Fun)'. |
|
|
-spec named_rpc(fun(() -> term())) -> {[Success::_],[Fail::_]}. |
|
|
|
|
|
|
|
|
-spec named_rpc(fun(() -> term())) -> {[Success :: _], [Fail :: _]}. |
|
|
named_rpc(Fun) -> |
|
|
named_rpc(Fun) -> |
|
|
named_rpc([node()|nodes()], Fun). |
|
|
|
|
|
|
|
|
named_rpc([node() | nodes()], Fun). |
|
|
|
|
|
|
|
|
%% @doc Shorthand for `named_rpc(Nodes, Fun, infinity)'. |
|
|
%% @doc Shorthand for `named_rpc(Nodes, Fun, infinity)'. |
|
|
-spec named_rpc(node()|[node(),...], fun(() -> term())) -> {[Success::_],[Fail::_]}. |
|
|
|
|
|
|
|
|
-spec named_rpc(node()|[node(), ...], fun(() -> term())) -> {[Success :: _], [Fail :: _]}. |
|
|
named_rpc(Nodes, Fun) -> |
|
|
named_rpc(Nodes, Fun) -> |
|
|
named_rpc(Nodes, Fun, infinity). |
|
|
|
|
|
|
|
|
named_rpc(Nodes, Fun, infinity). |
|
|
|
|
|
|
|
|
%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes, and returns the |
|
|
%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes, and returns the |
|
|
%% name of the node that computed a given result along with it, in a tuple. |
|
|
%% name of the node that computed a given result along with it, in a tuple. |
|
|
-spec named_rpc(node()|[node(),...], fun(() -> term()), timeout()) -> {[Success::_],[Fail::_]}. |
|
|
|
|
|
named_rpc(Nodes=[_|_], Fun, Timeout) when is_function(Fun,0) -> |
|
|
|
|
|
rpc:multicall(Nodes, erlang, apply, [fun() -> {node(),Fun()} end,[]], Timeout); |
|
|
|
|
|
|
|
|
-spec named_rpc(node()|[node(), ...], fun(() -> term()), timeout()) -> {[Success :: _], [Fail :: _]}. |
|
|
|
|
|
named_rpc(Nodes = [_ | _], Fun, Timeout) when is_function(Fun, 0) -> |
|
|
|
|
|
rpc:multicall(Nodes, erlang, apply, [fun() -> {node(), Fun()} end, []], Timeout); |
|
|
named_rpc(Node, Fun, Timeout) when is_atom(Node) -> |
|
|
named_rpc(Node, Fun, Timeout) when is_atom(Node) -> |
|
|
named_rpc([Node], Fun, Timeout). |
|
|
|
|
|
|
|
|
named_rpc([Node], Fun, Timeout). |
|
|
|
|
|
|