1%%% @author Fred Hebert <mononcqc@ferd.ca> 2%%% [http://ferd.ca/] 3%%% @doc Recon, as a module, provides access to the high-level functionality 4%%% contained in the Recon application. 5%%% 6%%% It has functions in five main categories: 7%%% 8%%% <dl> 9%%% <dt>1. State information</dt> 10%%% <dd>Process information is everything that has to do with the 11%%% general state of the node. Functions such as {@link info/1} 12%%% and {@link info/3} are wrappers to provide more details than 13%%% `erlang:process_info/1', while providing it in a production-safe 14%%% manner. They have equivalents to `erlang:process_info/2' in 15%%% the functions {@link info/2} and {@link info/4}, respectively.</dd> 16%%% <dd>{@link proc_count/2} and {@link proc_window/3} are to be used 17%%% when you require information about processes in a larger sense: 18%%% biggest consumers of given process information (say memory or 19%%% reductions), either absolutely or over a sliding time window, 20%%% respectively.</dd> 21%%% <dd>{@link bin_leak/1} is a function that can be used to try and 22%%% see if your Erlang node is leaking refc binaries. See the function 23%%% itself for more details.</dd> 24%%% <dd>Functions to access node statistics, in a manner somewhat similar 25%%% to what <a href="https://github.com/ferd/vmstats">vmstats</a> 26%%% provides as a library. There are 3 of them: 27%%% {@link node_stats_print/2}, which displays them, 28%%% {@link node_stats_list/2}, which returns them in a list, and 29%%% {@link node_stats/4}, which provides a fold-like interface 30%%% for stats gathering. For CPU usage specifically, see 31%%% {@link scheduler_usage/1}.</dd> 32%%% 33%%% <dt>2. OTP tools</dt> 34%%% <dd>This category provides tools to interact with pieces of OTP 35%%% more easily. At this point, the only function included is 36%%% {@link get_state/1}, which works as a wrapper around 37%%% {@link get_state/2}, which works as a wrapper around 38%%% `sys:get_state/1' in R16B01, and provides the required 39%%% functionality for older versions of Erlang.</dd> 40%%% 41%%% <dt>3. Code Handling</dt> 42%%% <dd>Specific functions are in `recon' for the sole purpose 43%%% of interacting with source and compiled code. 44%%% {@link remote_load/1} and {@link remote_load/2} will allow 45%%% to take a local module, and load it remotely (in a diskless 46%%% manner) on another Erlang node you're connected to.</dd> 47%%% <dd>{@link source/1} allows to print the source of a loaded module, 48%%% in case it's not available in the currently running node.</dd> 49%%% 50%%% <dt>4. Ports and Sockets</dt> 51%%% <dd>To make it simpler to debug some network-related issues, 52%%% recon contains functions to deal with Erlang ports (raw, file 53%%% handles, or inet). Functions {@link tcp/0}, {@link udp/0}, 54%%% {@link sctp/0}, {@link files/0}, and {@link port_types/0} will 55%%% list all the Erlang ports of a given type. The latter function 56%%% prints counts of all individual types.</dd> 57%%% <dd>Port state information can be useful to figure out why certain 58%%% parts of the system misbehave. Functions such as 59%%% {@link port_info/1} and {@link port_info/2} are wrappers to provide 60%%% more similar or more details than `erlang:port_info/1-2', and, for 61%%% inet ports, statistics and options for each socket.</dd> 62%%% <dd>Finally, the functions {@link inet_count/2} and {@link inet_window/3} 63%%% provide the absolute or sliding window functionality of 64%%% {@link proc_count/2} and {@link proc_count/3} to inet ports 65%%% and connections currently on the node.</dd> 66%%% 67%%% <dt>5. RPC</dt> 68%%% <dd>These are wrappers to make RPC work simpler with clusters of 69%%% Erlang nodes. Default RPC mechanisms (from the `rpc' module) 70%%% make it somewhat painful to call shell-defined funs over node 71%%% boundaries. The functions {@link rpc/1}, {@link rpc/2}, and 72%%% {@link rpc/3} will do it with a simpler interface.</dd> 73%%% <dd>Additionally, when you're running diagnostic code on remote 74%%% nodes and want to know which node evaluated what result, using 75%%% {@link named_rpc/1}, {@link named_rpc/2}, and {@link named_rpc/3} 76%%% will wrap the results in a tuple that tells you which node it's 77%%% coming from, making it easier to identify bad nodes.</dd> 78%%% </dl> 79%%% @end 80-module(recon). 81-export([info/1, info/2, info/3, info/4, 82 proc_count/2, proc_window/3, 83 bin_leak/1, 84 node_stats_print/2, node_stats_list/2, node_stats/4, 85 scheduler_usage/1]). 86-export([get_state/1, get_state/2]). 87-export([remote_load/1, remote_load/2, 88 source/1]). 89-export([tcp/0, udp/0, sctp/0, files/0, port_types/0, 90 inet_count/2, inet_window/3, 91 port_info/1, port_info/2]). 92-export([rpc/1, rpc/2, rpc/3, 93 named_rpc/1, named_rpc/2, named_rpc/3]). 94 95%%%%%%%%%%%%% 96%%% TYPES %%% 97%%%%%%%%%%%%% 98-type proc_attrs() :: {pid(), 99 Attr::_, 100 [Name::atom() 101 |{current_function, mfa()} 102 |{initial_call, mfa()}, ...]}. 103 104-type inet_attrs() :: {port(), 105 Attr::_, 106 [{atom(), term()}]}. 107 108-type pid_term() :: pid() | atom() | string() 109 | {global, term()} | {via, module(), term()} 110 | {non_neg_integer(), non_neg_integer(), non_neg_integer()}. 111 112-type info_type() :: meta | signals | location | memory_used | work. 113 114-type info_meta_key() :: registered_name | dictionary | group_leader | status. 115-type info_signals_key() :: links | monitors | monitored_by | trap_exit. 116-type info_location_key() :: initial_call | current_stacktrace. 117-type info_memory_key() :: memory | message_queue_len | heap_size 118 | total_heap_size | garbage_collection. 119-type info_work_key() :: reductions. 120 121-type info_key() :: info_meta_key() | info_signals_key() | info_location_key() 122 | info_memory_key() | info_work_key(). 123 124-type port_term() :: port() | string() | atom() | pos_integer(). 125 126-type port_info_type() :: meta | signals | io | memory_used | specific. 127 128-type port_info_meta_key() :: registered_name | id | name | os_pid. 129-type port_info_signals_key() :: connected | links | monitors. 130-type port_info_io_key() :: input | output. 131-type port_info_memory_key() :: memory | queue_size. 132-type port_info_specific_key() :: atom(). 133 134-type port_info_key() :: port_info_meta_key() | port_info_signals_key() 135 | port_info_io_key() | port_info_memory_key() 136 | port_info_specific_key(). 137 138-export_type([proc_attrs/0, inet_attrs/0, pid_term/0, port_term/0]). 139-export_type([info_type/0, info_key/0, 140 info_meta_key/0, info_signals_key/0, info_location_key/0, 141 info_memory_key/0, info_work_key/0]). 142-export_type([port_info_type/0, port_info_key/0, 143 port_info_meta_key/0, port_info_signals_key/0, port_info_io_key/0, 144 port_info_memory_key/0, port_info_specific_key/0]). 145 146%%%%%%%%%%%%%%%%%% 147%%% PUBLIC API %%% 148%%%%%%%%%%%%%%%%%% 149 150%%% Process Info %%% 151 152%% @doc Equivalent to `info(<A.B.C>)' where `A', `B', and `C' are integers part 153%% of a pid 154-spec info(N,N,N) -> [{info_type(), [{info_key(),term()}]},...] when 155 N :: non_neg_integer(). 156info(A,B,C) -> info(recon_lib:triple_to_pid(A,B,C)). 157 158%% @doc Equivalent to `info(<A.B.C>, Key)' where `A', `B', and `C' are integers part 159%% of a pid 160-spec info(N,N,N, Key) -> term() when 161 N :: non_neg_integer(), 162 Key :: info_type() | [atom()] | atom(). 163info(A,B,C, Key) -> info(recon_lib:triple_to_pid(A,B,C), Key). 164 165 166%% @doc Allows to be similar to `erlang:process_info/1', but excludes fields 167%% such as the mailbox, which have a tendency to grow and be unsafe when called 168%% in production systems. Also includes a few more fields than what is usually 169%% given (`monitors', `monitored_by', etc.), and separates the fields in a more 170%% readable format based on the type of information contained. 171%% 172%% Moreover, it will fetch and read information on local processes that were 173%% registered locally (an atom), globally (`{global, Name}'), or through 174%% another registry supported in the `{via, Module, Name}' syntax (must have a 175%% `Module:whereis_name/1' function). Pids can also be passed in as a string 176%% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used. 177-spec info(pid_term()) -> [{info_type(), [{info_key(), Value}]},...] when 178 Value :: term(). 179info(PidTerm) -> 180 Pid = recon_lib:term_to_pid(PidTerm), 181 [info(Pid, Type) || Type <- [meta, signals, location, memory_used, work]]. 182 183%% @doc Allows to be similar to `erlang:process_info/2', but allows to 184%% sort fields by safe categories and pre-selections, avoiding items such 185%% as the mailbox, which may have a tendency to grow and be unsafe when 186%% called in production systems. 187%% 188%% Moreover, it will fetch and read information on local processes that were 189%% registered locally (an atom), globally (`{global, Name}'), or through 190%% another registry supported in the `{via, Module, Name}' syntax (must have a 191%% `Module:whereis_name/1' function). Pids can also be passed in as a string 192%% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used. 193%% 194%% Although the type signature doesn't show it in generated documentation, 195%% a list of arguments or individual arguments accepted by 196%% `erlang:process_info/2' and return them as that function would. 197%% 198%% A fake attribute `binary_memory' is also available to return the 199%% amount of memory used by refc binaries for a process. 200-spec info(pid_term(), info_type()) -> {info_type(), [{info_key(), term()}]} 201 ; (pid_term(), [atom()]) -> [{atom(), term()}] 202 ; (pid_term(), atom()) -> {atom(), term()}. 203info(PidTerm, meta) -> 204 info_type(PidTerm, meta, [registered_name, dictionary, group_leader, 205 status]); 206info(PidTerm, signals) -> 207 info_type(PidTerm, signals, [links, monitors, monitored_by, trap_exit]); 208info(PidTerm, location) -> 209 info_type(PidTerm, location, [initial_call, current_stacktrace]); 210info(PidTerm, memory_used) -> 211 info_type(PidTerm, memory_used, [memory, message_queue_len, heap_size, 212 total_heap_size, garbage_collection]); 213info(PidTerm, work) -> 214 info_type(PidTerm, work, [reductions]); 215info(PidTerm, Keys) -> 216 proc_info(recon_lib:term_to_pid(PidTerm), Keys). 217 218%% @private makes access to `info_type()' calls simpler. 219-spec info_type(pid_term(), info_type(), [info_key()]) -> 220 {info_type(), [{info_key(), term()}]}. 221info_type(PidTerm, Type, Keys) -> 222 Pid = recon_lib:term_to_pid(PidTerm), 223 {Type, proc_info(Pid, Keys)}. 224 225%% @private wrapper around `erlang:process_info/2' that allows special 226%% attribute handling for items like `binary_memory'. 227proc_info(Pid, binary_memory) -> 228 {binary, Bins} = erlang:process_info(Pid, binary), 229 {binary_memory, recon_lib:binary_memory(Bins)}; 230proc_info(Pid, Term) when is_atom(Term) -> 231 erlang:process_info(Pid, Term); 232proc_info(Pid, List) when is_list(List) -> 233 case lists:member(binary_memory, List) of 234 false -> 235 erlang:process_info(Pid, List); 236 true -> 237 Res = erlang:process_info(Pid, replace(binary_memory, binary, List)), 238 proc_fake(List, Res) 239 end. 240 241%% @private Replace keys around 242replace(_, _, []) -> []; 243replace(H, Val, [H|T]) -> [Val | replace(H, Val, T)]; 244replace(R, Val, [H|T]) -> [H | replace(R, Val, T)]. 245 246proc_fake([], []) -> 247 []; 248proc_fake([binary_memory|T1], [{binary,Bins}|T2]) -> 249 [{binary_memory, recon_lib:binary_memory(Bins)} 250 | proc_fake(T1,T2)]; 251proc_fake([_|T1], [H|T2]) -> 252 [H | proc_fake(T1,T2)]. 253 254%% @doc Fetches a given attribute from all processes (except the 255%% caller) and returns the biggest `Num' consumers. 256-spec proc_count(AttributeName, Num) -> [proc_attrs()] when 257 AttributeName :: atom(), 258 Num :: non_neg_integer(). 259proc_count(AttrName, Num) -> 260 recon_lib:sublist_top_n_attrs(recon_lib:proc_attrs(AttrName), Num). 261 262%% @doc Fetches a given attribute from all processes (except the 263%% caller) and returns the biggest entries, over a sliding time window. 264%% 265%% This function is particularly useful when processes on the node 266%% are mostly short-lived, usually too short to inspect through other 267%% tools, in order to figure out what kind of processes are eating 268%% through a lot resources on a given node. 269%% 270%% It is important to see this function as a snapshot over a sliding 271%% window. A program's timeline during sampling might look like this: 272%% 273%% `--w---- [Sample1] ---x-------------y----- [Sample2] ---z--->' 274%% 275%% Some processes will live between `w' and die at `x', some between `y' and 276%% `z', and some between `x' and `y'. These samples will not be too significant 277%% as they're incomplete. If the majority of your processes run between a time 278%% interval `x'...`y' (in absolute terms), you should make sure that your 279%% sampling time is smaller than this so that for many processes, their 280%% lifetime spans the equivalent of `w' and `z'. Not doing this can skew the 281%% results: long-lived processes, that have 10 times the time to accumulate 282%% data (say reductions) will look like bottlenecks when they're not one. 283%% 284%% Warning: this function depends on data gathered at two snapshots, and then 285%% building a dictionary with entries to differentiate them. This can take a 286%% heavy toll on memory when you have many dozens of thousands of processes. 287-spec proc_window(AttributeName, Num, Milliseconds) -> [proc_attrs()] when 288 AttributeName :: atom(), 289 Num :: non_neg_integer(), 290 Milliseconds :: pos_integer(). 291proc_window(AttrName, Num, Time) -> 292 Sample = fun() -> recon_lib:proc_attrs(AttrName) end, 293 {First,Last} = recon_lib:sample(Time, Sample), 294 recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num). 295 296%% @doc Refc binaries can be leaking when barely-busy processes route them 297%% around and do little else, or when extremely busy processes reach a stable 298%% amount of memory allocated and do the vast majority of their work with refc 299%% binaries. When this happens, it may take a very long while before references 300%% get deallocated and refc binaries get to be garbage collected, leading to 301%% Out Of Memory crashes. 302%% This function fetches the number of refc binary references in each process 303%% of the node, garbage collects them, and compares the resulting number of 304%% references in each of them. The function then returns the `N' processes 305%% that freed the biggest amount of binaries, potentially highlighting leaks. 306%% 307%% See <a href="http://www.erlang.org/doc/efficiency_guide/binaryhandling.html#id65722">The efficiency guide</a> 308%% for more details on refc binaries 309-spec bin_leak(pos_integer()) -> [proc_attrs()]. 310bin_leak(N) -> 311 Procs = recon_lib:sublist_top_n_attrs([ 312 try 313 {ok, {_,Pre,Id}} = recon_lib:proc_attrs(binary, Pid), 314 erlang:garbage_collect(Pid), 315 {ok, {_,Post,_}} = recon_lib:proc_attrs(binary, Pid), 316 {Pid, length(Pre) - length(Post), Id} 317 catch 318 _:_ -> {Pid, 0, []} 319 end || Pid <- processes() 320 ], N), 321 [{Pid, -Val, Id} ||{Pid, Val, Id} <-Procs]. 322 323%% @doc Shorthand for `node_stats(N, Interval, fun(X,_) -> io:format("~p~n",[X]) end, nostate)'. 324-spec node_stats_print(Repeat, Interval) -> term() when 325 Repeat :: non_neg_integer(), 326 Interval :: pos_integer(). 327node_stats_print(N, Interval) -> 328 node_stats(N, Interval, fun(X, _) -> io:format("~p~n", [X]) end, ok). 329 330%% @doc Because Erlang CPU usage as reported from `top' isn't the most 331%% reliable value (due to schedulers doing idle spinning to avoid going 332%% to sleep and impacting latency), a metric exists that is based on 333%% scheduler wall time. 334%% 335%% For any time interval, Scheduler wall time can be used as a measure 336%% of how 'busy' a scheduler is. A scheduler is busy when: 337%% 338%% <ul> 339%% <li>executing process code</li> 340%% <li>executing driver code</li> 341%% <li>executing NIF code</li> 342%% <li>executing BIFs</li> 343%% <li>garbage collecting</li> 344%% <li>doing memory management</li> 345%% </ul> 346%% 347%% A scheduler isn't busy when doing anything else. 348-spec scheduler_usage(Millisecs) -> undefined | [{SchedulerId, Usage}] when 349 Millisecs :: non_neg_integer(), 350 SchedulerId :: pos_integer(), 351 Usage :: number(). 352scheduler_usage(Interval) when is_integer(Interval) -> 353 %% We start and stop the scheduler_wall_time system flag if 354 %% it wasn't in place already. Usually setting the flag should 355 %% have a CPU impact (making it higher) only when under low usage. 356 FormerFlag = erlang:system_flag(scheduler_wall_time, true), 357 First = erlang:statistics(scheduler_wall_time), 358 timer:sleep(Interval), 359 Last = erlang:statistics(scheduler_wall_time), 360 erlang:system_flag(scheduler_wall_time, FormerFlag), 361 recon_lib:scheduler_usage_diff(First, Last). 362 363%% @doc Shorthand for `node_stats(N, Interval, fun(X,Acc) -> [X|Acc] end, [])' 364%% with the results reversed to be in the right temporal order. 365-spec node_stats_list(Repeat, Interval) -> [Stats] when 366 Repeat :: non_neg_integer(), 367 Interval :: pos_integer(), 368 Stats :: {[Absolutes::{atom(),term()}], 369 [Increments::{atom(),term()}]}. 370node_stats_list(N, Interval) -> 371 lists:reverse(node_stats(N, Interval, fun(X, Acc) -> [X|Acc] end, [])). 372 373%% @doc Gathers statistics `N' time, waiting `Interval' milliseconds between 374%% each run, and accumulates results using a folding function `FoldFun'. 375%% The function will gather statistics in two forms: Absolutes and Increments. 376%% 377%% Absolutes are values that keep changing with time, and are useful to know 378%% about as a datapoint: process count, size of the run queue, error_logger 379%% queue length in versions before OTP-21 or those thar run it explicitely, 380%% and the memory of the node (total, processes, atoms, binaries, 381%% and ets tables). 382%% 383%% Increments are values that are mostly useful when compared to a previous 384%% one to have an idea what they're doing, because otherwise they'd never 385%% stop increasing: bytes in and out of the node, number of garbage colelctor 386%% runs, words of memory that were garbage collected, and the global reductions 387%% count for the node. 388-spec node_stats(N, Interval, FoldFun, Acc) -> Acc when 389 N :: non_neg_integer(), 390 Interval :: pos_integer(), 391 FoldFun :: fun((Stats, Acc) -> Acc), 392 Acc :: term(), 393 Stats :: {[Absolutes::{atom(),term()}], 394 [Increments::{atom(),term()}]}. 395node_stats(N, Interval, FoldFun, Init) -> 396 Logger = case whereis(error_logger) of 397 undefined -> logger; 398 _ -> error_logger 399 end, 400 %% Turn on scheduler wall time if it wasn't there already 401 FormerFlag = erlang:system_flag(scheduler_wall_time, true), 402 %% Stats is an ugly fun, but it does its thing. 403 Stats = fun({{OldIn,OldOut},{OldGCs,OldWords,_}, SchedWall}) -> 404 %% Absolutes 405 ProcC = erlang:system_info(process_count), 406 RunQ = erlang:statistics(run_queue), 407 LogQ = case Logger of 408 error_logger -> 409 {_,LogQLen} = process_info(whereis(error_logger), 410 message_queue_len), 411 LogQLen; 412 _ -> 413 undefined 414 end, 415 %% Mem (Absolutes) 416 Mem = erlang:memory(), 417 Tot = proplists:get_value(total, Mem), 418 ProcM = proplists:get_value(processes_used,Mem), 419 Atom = proplists:get_value(atom_used,Mem), 420 Bin = proplists:get_value(binary, Mem), 421 Ets = proplists:get_value(ets, Mem), 422 %% Incremental 423 {{input,In},{output,Out}} = erlang:statistics(io), 424 GC={GCs,Words,_} = erlang:statistics(garbage_collection), 425 BytesIn = In-OldIn, 426 BytesOut = Out-OldOut, 427 GCCount = GCs-OldGCs, 428 GCWords = Words-OldWords, 429 {_, Reds} = erlang:statistics(reductions), 430 SchedWallNew = erlang:statistics(scheduler_wall_time), 431 SchedUsage = recon_lib:scheduler_usage_diff(SchedWall, SchedWallNew), 432 %% Stats Results 433 {{[{process_count,ProcC}, {run_queue,RunQ}] ++ 434 [{error_logger_queue_len,LogQ} || LogQ =/= undefined] ++ 435 [{memory_total,Tot}, 436 {memory_procs,ProcM}, {memory_atoms,Atom}, 437 {memory_bin,Bin}, {memory_ets,Ets}], 438 [{bytes_in,BytesIn}, {bytes_out,BytesOut}, 439 {gc_count,GCCount}, {gc_words_reclaimed,GCWords}, 440 {reductions,Reds}, {scheduler_usage, SchedUsage}]}, 441 %% New State 442 {{In,Out}, GC, SchedWallNew}} 443 end, 444 {{input,In},{output,Out}} = erlang:statistics(io), 445 Gc = erlang:statistics(garbage_collection), 446 SchedWall = erlang:statistics(scheduler_wall_time), 447 Result = recon_lib:time_fold( 448 N, Interval, Stats, 449 {{In,Out}, Gc, SchedWall}, 450 FoldFun, Init), 451 %% Set scheduler wall time back to what it was 452 erlang:system_flag(scheduler_wall_time, FormerFlag), 453 Result. 454 455%%% OTP & Manipulations %%% 456 457 458%% @doc Shorthand call to `recon:get_state(PidTerm, 5000)' 459-spec get_state(pid_term()) -> term(). 460get_state(PidTerm) -> get_state(PidTerm, 5000). 461 462%% @doc Fetch the internal state of an OTP process. 463%% Calls `sys:get_state/2' directly in R16B01+, and fetches 464%% it dynamically on older versions of OTP. 465-spec get_state(pid_term(), Ms::non_neg_integer() | 'infinity') -> term(). 466get_state(PidTerm, Timeout) -> 467 Proc = recon_lib:term_to_pid(PidTerm), 468 try 469 sys:get_state(Proc, Timeout) 470 catch 471 error:undef -> 472 case sys:get_status(Proc, Timeout) of 473 {status,_Pid,{module,gen_server},Data} -> 474 {data, Props} = lists:last(lists:nth(5, Data)), 475 proplists:get_value("State", Props); 476 {status,_Pod,{module,gen_fsm},Data} -> 477 {data, Props} = lists:last(lists:nth(5, Data)), 478 proplists:get_value("StateData", Props) 479 end 480 end. 481 482%%% Code & Stuff %%% 483 484%% @equiv remote_load(nodes(), Mod) 485-spec remote_load(module()) -> term(). 486remote_load(Mod) -> remote_load(nodes(), Mod). 487 488%% @doc Loads one or more modules remotely, in a diskless manner. Allows to 489%% share code loaded locally with a remote node that doesn't have it 490-spec remote_load(Nodes, module()) -> term() when 491 Nodes :: [node(),...] | node(). 492remote_load(Nodes=[_|_], Mod) when is_atom(Mod) -> 493 {Mod, Bin, File} = code:get_object_code(Mod), 494 rpc:multicall(Nodes, code, load_binary, [Mod, File, Bin]); 495remote_load(Nodes=[_|_], Modules) when is_list(Modules) -> 496 [remote_load(Nodes, Mod) || Mod <- Modules]; 497remote_load(Node, Mod) -> 498 remote_load([Node], Mod). 499 500%% @doc Obtain the source code of a module compiled with `debug_info'. 501%% The returned list sadly does not allow to format the types and typed 502%% records the way they look in the original module, but instead goes to 503%% an intermediary form used in the AST. They will still be placed 504%% in the right module attributes, however. 505%% @todo Figure out a way to pretty-print typespecs and records. 506-spec source(module()) -> iolist(). 507source(Module) -> 508 Path = code:which(Module), 509 {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Path, [abstract_code]), 510 erl_prettypr:format(erl_syntax:form_list(AC)). 511 512%%% Ports Info %%% 513 514%% @doc returns a list of all TCP ports (the data type) open on the node. 515-spec tcp() -> [port()]. 516tcp() -> recon_lib:port_list(name, "tcp_inet"). 517 518%% @doc returns a list of all UDP ports (the data type) open on the node. 519-spec udp() -> [port()]. 520udp() -> recon_lib:port_list(name, "udp_inet"). 521 522%% @doc returns a list of all SCTP ports (the data type) open on the node. 523-spec sctp() -> [port()]. 524sctp() -> recon_lib:port_list(name, "sctp_inet"). 525 526%% @doc returns a list of all file handles open on the node. 527%% @deprecated Starting with OTP-21, files are implemented as NIFs 528%% and can no longer be listed. This function returns an empty list 529%% in such a case. 530-spec files() -> [port()]. 531files() -> recon_lib:port_list(name, "efile"). 532 533%% @doc Shows a list of all different ports on the node with their respective 534%% types. 535-spec port_types() -> [{Type::string(), Count::pos_integer()}]. 536port_types() -> 537 lists:usort( 538 %% sorts by biggest count, smallest type 539 fun({KA,VA}, {KB,VB}) -> {VA,KB} > {VB,KA} end, 540 recon_lib:count([Name || {_, Name} <- recon_lib:port_list(name)]) 541 ). 542 543%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP) 544%% and returns the biggest `Num' consumers. 545%% 546%% The values to be used can be the number of octets (bytes) sent, received, 547%% or both (`send_oct', `recv_oct', `oct', respectively), or the number 548%% of packets sent, received, or both (`send_cnt', `recv_cnt', `cnt', 549%% respectively). Individual absolute values for each metric will be returned 550%% in the 3rd position of the resulting tuple. 551-spec inet_count(AttributeName, Num) -> [inet_attrs()] when 552 AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' 553 | 'cnt' | 'oct', 554 Num :: non_neg_integer(). 555inet_count(Attr, Num) -> 556 recon_lib:sublist_top_n_attrs(recon_lib:inet_attrs(Attr), Num). 557 558%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP) 559%% and returns the biggest entries, over a sliding time window. 560%% 561%% Warning: this function depends on data gathered at two snapshots, and then 562%% building a dictionary with entries to differentiate them. This can take a 563%% heavy toll on memory when you have many dozens of thousands of ports open. 564%% 565%% The values to be used can be the number of octets (bytes) sent, received, 566%% or both (`send_oct', `recv_oct', `oct', respectively), or the number 567%% of packets sent, received, or both (`send_cnt', `recv_cnt', `cnt', 568%% respectively). Individual absolute values for each metric will be returned 569%% in the 3rd position of the resulting tuple. 570-spec inet_window(AttributeName, Num, Milliseconds) -> [inet_attrs()] when 571 AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' 572 | 'cnt' | 'oct', 573 Num :: non_neg_integer(), 574 Milliseconds :: pos_integer(). 575inet_window(Attr, Num, Time) when is_atom(Attr) -> 576 Sample = fun() -> recon_lib:inet_attrs(Attr) end, 577 {First,Last} = recon_lib:sample(Time, Sample), 578 recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num). 579 580%% @doc Allows to be similar to `erlang:port_info/1', but allows 581%% more flexible port usage: usual ports, ports that were registered 582%% locally (an atom), ports represented as strings (`"#Port<0.2013>"'), 583%% or through an index lookup (`2013', for the same result as 584%% `"#Port<0.2013>"'). 585%% 586%% Moreover, the function will try to fetch implementation-specific 587%% details based on the port type (only inet ports have this feature 588%% so far). For example, TCP ports will include information about the 589%% remote peer, transfer statistics, and socket options being used. 590%% 591%% The information-specific and the basic port info are sorted and 592%% categorized in broader categories ({@link port_info_type()}). 593-spec port_info(port_term()) -> [{port_info_type(), 594 [{port_info_key(), term()}]},...]. 595port_info(PortTerm) -> 596 Port = recon_lib:term_to_port(PortTerm), 597 [port_info(Port, Type) || Type <- [meta, signals, io, memory_used, 598 specific]]. 599 600%% @doc Allows to be similar to `erlang:port_info/2', but allows 601%% more flexible port usage: usual ports, ports that were registered 602%% locally (an atom), ports represented as strings (`"#Port<0.2013>"'), 603%% or through an index lookup (`2013', for the same result as 604%% `"#Port<0.2013>"'). 605%% 606%% Moreover, the function allows to to fetch information by category 607%% as defined in {@link port_info_type()}, and although the type signature 608%% doesn't show it in the generated documentation, individual items 609%% accepted by `erlang:port_info/2' are accepted, and lists of them too. 610-spec port_info(port_term(), port_info_type()) -> {port_info_type(), 611 [{port_info_key(), _}]} 612 ; (port_term(), [atom()]) -> [{atom(), term()}] 613 ; (port_term(), atom()) -> {atom(), term()}. 614port_info(PortTerm, meta) -> 615 {meta, List} = port_info_type(PortTerm, meta, [id, name, os_pid]), 616 case port_info(PortTerm, registered_name) of 617 [] -> {meta, List}; 618 Name -> {meta, [Name | List]} 619 end; 620port_info(PortTerm, signals) -> 621 port_info_type(PortTerm, signals, [connected, links, monitors]); 622port_info(PortTerm, io) -> 623 port_info_type(PortTerm, io, [input, output]); 624port_info(PortTerm, memory_used) -> 625 port_info_type(PortTerm, memory_used, [memory, queue_size]); 626port_info(PortTerm, specific) -> 627 Port = recon_lib:term_to_port(PortTerm), 628 Props = case erlang:port_info(Port, name) of 629 {_,Type} when Type =:= "udp_inet"; 630 Type =:= "tcp_inet"; 631 Type =:= "sctp_inet" -> 632 case inet:getstat(Port) of 633 {ok, Stats} -> [{statistics, Stats}]; 634 _ -> [] 635 end ++ 636 case inet:peername(Port) of 637 {ok, Peer} -> [{peername, Peer}]; 638 {error, _} -> [] 639 end ++ 640 case inet:sockname(Port) of 641 {ok, Local} -> [{sockname, Local}]; 642 {error, _} -> [] 643 end ++ 644 case inet:getopts(Port, [active, broadcast, buffer, delay_send, 645 dontroute, exit_on_close, header, 646 high_watermark, ipv6_v6only, keepalive, 647 linger, low_watermark, mode, nodelay, 648 packet, packet_size, priority, 649 read_packets, recbuf, reuseaddr, 650 send_timeout, sndbuf]) of 651 {ok, Opts} -> [{options, Opts}]; 652 {error, _} -> [] 653 end; 654 {_,"efile"} -> 655 %% would be nice to support file-specific info, but things 656 %% are too vague with the file_server and how it works in 657 %% order to make this work efficiently 658 []; 659 _ -> 660 [] 661 end, 662 {type, Props}; 663port_info(PortTerm, Keys) when is_list(Keys) -> 664 Port = recon_lib:term_to_port(PortTerm), 665 [erlang:port_info(Port,Key) || Key <- Keys]; 666port_info(PortTerm, Key) when is_atom(Key) -> 667 erlang:port_info(recon_lib:term_to_port(PortTerm), Key). 668 669%% @private makes access to `port_info_type()' calls simpler. 670%-spec port_info_type(pid_term(), port_info_type(), [port_info_key()]) -> 671% {port_info_type(), [{port_info_key(), term()}]}. 672port_info_type(PortTerm, Type, Keys) -> 673 Port = recon_lib:term_to_port(PortTerm), 674 {Type, [erlang:port_info(Port,Key) || Key <- Keys]}. 675 676 677%%% RPC Utils %%% 678 679%% @doc Shorthand for `rpc([node()|nodes()], Fun)'. 680-spec rpc(fun(() -> term())) -> {[Success::_],[Fail::_]}. 681rpc(Fun) -> 682 rpc([node()|nodes()], Fun). 683 684%% @doc Shorthand for `rpc(Nodes, Fun, infinity)'. 685-spec rpc(node()|[node(),...], fun(() -> term())) -> {[Success::_],[Fail::_]}. 686rpc(Nodes, Fun) -> 687 rpc(Nodes, Fun, infinity). 688 689%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes. 690-spec rpc(node()|[node(),...], fun(() -> term()), timeout()) -> {[Success::_],[Fail::_]}. 691rpc(Nodes=[_|_], Fun, Timeout) when is_function(Fun,0) -> 692 rpc:multicall(Nodes, erlang, apply, [Fun,[]], Timeout); 693rpc(Node, Fun, Timeout) when is_atom(Node) -> 694 rpc([Node], Fun, Timeout). 695 696%% @doc Shorthand for `named_rpc([node()|nodes()], Fun)'. 697-spec named_rpc(fun(() -> term())) -> {[Success::_],[Fail::_]}. 698named_rpc(Fun) -> 699 named_rpc([node()|nodes()], Fun). 700 701%% @doc Shorthand for `named_rpc(Nodes, Fun, infinity)'. 702-spec named_rpc(node()|[node(),...], fun(() -> term())) -> {[Success::_],[Fail::_]}. 703named_rpc(Nodes, Fun) -> 704 named_rpc(Nodes, Fun, infinity). 705 706%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes, and returns the 707%% name of the node that computed a given result along with it, in a tuple. 708-spec named_rpc(node()|[node(),...], fun(() -> term()), timeout()) -> {[Success::_],[Fail::_]}. 709named_rpc(Nodes=[_|_], Fun, Timeout) when is_function(Fun,0) -> 710 rpc:multicall(Nodes, erlang, apply, [fun() -> {node(),Fun()} end,[]], Timeout); 711named_rpc(Node, Fun, Timeout) when is_atom(Node) -> 712 named_rpc([Node], Fun, Timeout). 713 714