blob: 80feb615cebbb9d5a8f6a6f7bf7818e2554c1aa9 [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(couch_debug).
-export([
help/0,
help/1
]).
-export([
opened_files/0,
opened_files_by_regexp/1,
opened_files_contains/1
]).
-export([
process_name/1,
link_tree/1,
link_tree/2,
mapfold_tree/3,
map_tree/2,
fold_tree/3,
linked_processes_info/2,
print_linked_processes/1
]).
help() ->
[
opened_files,
opened_files_by_regexp,
opened_files_contains,
process_name,
link_tree,
mapfold,
map,
fold,
linked_processes_info,
print_linked_processes
].
-spec help(Function :: atom()) -> ok.
help(opened_files) ->
io:format("
opened_files()
--------------
Returns list of currently opened files
It iterates through `erlang:ports` and filters out all ports which are not efile.
It uses `process_info(Pid, dictionary)` to get info about couch_file properties.
---
", []);
help(opened_files_by_regexp) ->
io:format("
opened_files_by_regexp(FileRegExp)
----------------------------------
Returns list of currently opened files which name match the provided regular expression.
It iterates through `erlang:ports()` and filter out all ports which are not efile.
It uses `process_info(Pid, dictionary)` to get info about couch_file properties.
---
", []);
help(opened_files_contains) ->
io:format("
opened_files_contains(SubString)
--------------------------------
Returns list of currently opened files whose names contain the provided SubString.
It iterates through `erlang:ports()` and filters out all ports which are not efile.
It uses `process_info(Pid, dictionary)` to get info about couch_file properties.
---
", []);
help(process_name) ->
io:format("
process_name(Pid)
-----------------
Uses heuristics to figure out the process name.
The heuristic is based on the following information about the process:
- process_info(Pid, registered_name)
- '$initial_call' key in process dictionary
- process_info(Pid, initial_call)
---
", []);
help(link_tree) ->
io:format("
link_tree(Pid)
--------------
Returns a tree which represents a cluster of linked processes.
This function receives the initial Pid to start from.
The function doesn't recurse to pids older than initial one.
The Pids which are lesser than initial Pid are still shown in the output.
This is analogue of `link_tree(RootPid, []).`
link_tree(Pid, Info)
--------------------
Returns a tree which represents a cluster of linked processes.
This function receives the initial Pid to start from.
The function doesn't recurse to pids older than initial one.
The Pids which are lesser than initial Pid are still shown in the output.
The info argument is a list of process_info_item() as documented in
erlang:process_info/2. We don't do any attempts to prevent dangerous items.
Be warn that passing some of them such as `messages` for example
can be dangerous in a very busy system.
---
", []);
help(mapfold_tree) ->
io:format("
mapfold_tree(Tree, Acc, Fun)
-----------------------
Traverses all nodes of the tree. It is a combination of a map and fold.
It calls a user provided callback for every node of the tree.
`Fun(Key, Value, Pos, Acc) -> {NewValue, NewAcc}`.
Where:
- Key of the node (usualy Pid of a process)
- Value of the node (usualy information collected by link_tree)
- Pos - depth from the root of the tree
- Acc - user's accumulator
---
", []);
help(map_tree) ->
io:format("
map_tree(Tree, Fun)
-----------------------
Traverses all nodes of the tree in order to modify them.
It calls a user provided callback
`Fun(Key, Value, Pos) -> NewValue`
Where:
- Key of the node (usualy Pid of a process)
- Value of the node (usualy information collected by link_tree)
- Pos - depth from the root of the tree
---
", []);
help(fold_tree) ->
io:format("
fold_tree(Tree, Fun)
Traverses all nodes of the tree in order to collect some aggregated information
about the tree. It calls a user provided callback
`Fun(Key, Value, Pos) -> NewValue`
Where:
- Key of the node (usualy Pid of a process)
- Value of the node (usualy information collected by link_tree)
- Pos - depth from the root of the tree
---
", []);
help(linked_processes_info) ->
io:format("
linked_processes_info(Pid, Info)
--------------------------------
Convenience function which reduces the amount of typing compared to direct
use of link_tree.
- Pid: initial Pid to start from
- Info: a list of process_info_item() as documented
in erlang:process_info/2.
---
", []);
help(print_linked_processes) ->
io:format("
- print_linked_processes(Pid)
- print_linked_processes(RegisteredName)
- print_linked_processes(couch_index_server)
---------------------------
Print cluster of linked processes. This function receives the
initial Pid to start from. The function doesn't recurse to pids
older than initial one. The output would look like similar to:
```
couch_debug:print_linked_processes(whereis(couch_index_server)).
name | reductions | message_queue_len | memory
couch_index_server[<0.288.0>] | 478240 | 0 | 109696
couch_index:init/1[<0.3520.22>] | 4899 | 0 | 109456
couch_file:init/1[<0.886.22>] | 11973 | 0 | 67984
couch_index:init/1[<0.3520.22>] | 4899 | 0 | 109456
```
---
", []);
help(Unknown) ->
io:format("Unknown function: `~p`. Please try one of the following:~n", [Unknown]),
[io:format(" - ~s~n", [Function]) || Function <- help()],
io:format(" ---~n", []),
ok.
-spec opened_files() ->
[{port(), CouchFilePid :: pid(), Fd :: pid() | tuple(), FilePath :: string()}].
opened_files() ->
Info = [couch_file_port_info(Port)
|| Port <- erlang:ports(),
{name, "efile"} =:= erlang:port_info(Port, name)],
[I || I <- Info, is_tuple(I)].
couch_file_port_info(Port) ->
{connected, Pid} = erlang:port_info(Port, connected),
case couch_file:process_info(Pid) of
{Fd, FilePath} ->
{Port, Pid, Fd, FilePath};
undefined ->
undefined
end.
-spec opened_files_by_regexp(FileRegExp :: iodata()) ->
[{port(), CouchFilePid :: pid(), Fd :: pid() | tuple(), FilePath :: string()}].
opened_files_by_regexp(FileRegExp) ->
{ok, RegExp} = re:compile(FileRegExp),
lists:filter(fun({_Port, _Pid, _Fd, Path}) ->
re:run(Path, RegExp) =/= nomatch
end, couch_debug:opened_files()).
-spec opened_files_contains(FileNameFragment :: iodata()) ->
[{port(), CouchFilePid :: pid(), Fd :: pid() | tuple(), FilePath :: string()}].
opened_files_contains(FileNameFragment) ->
lists:filter(fun({_Port, _Pid, _Fd, Path}) ->
string:str(Path, FileNameFragment) > 0
end, couch_debug:opened_files()).
process_name(Pid) when is_pid(Pid) ->
Info = process_info(Pid, [registered_name, dictionary, initial_call]),
case Info of
undefined ->
iolist_to_list(io_lib:format("[~p]", [Pid]));
[{registered_name, Name} | _] when Name =/= [] ->
iolist_to_list(io_lib:format("~s[~p]", [Name, Pid]));
[_, {dictionary, Dict}, {initial_call, MFA}] ->
{M, F, A} = proplists:get_value('$initial_call', Dict, MFA),
iolist_to_list(io_lib:format("~p:~p/~p[~p]", [M, F, A, Pid]))
end;
process_name(Else) ->
iolist_to_list(io_lib:format("~p", [Else])).
iolist_to_list(List) ->
binary_to_list(iolist_to_binary(List)).
link_tree(RootPid) ->
link_tree(RootPid, []).
link_tree(RootPid, Info) ->
link_tree(RootPid, Info, fun(_, Props) -> Props end).
link_tree(RootPid, Info, Fun) ->
{_, Result} = link_tree(
RootPid, [links | Info], gb_trees:empty(), 0, [RootPid], Fun),
Result.
link_tree(RootPid, Info, Visited0, Pos, [Pid | Rest], Fun) ->
case gb_trees:lookup(Pid, Visited0) of
{value, Props} ->
{Visited0, [{Pos, {Pid, Fun(Pid, Props), []}}]};
none when RootPid =< Pid ->
Props = info(Pid, Info),
Visited1 = gb_trees:insert(Pid, Props, Visited0),
{links, Children} = lists:keyfind(links, 1, Props),
{Visited2, NewTree} = link_tree(
RootPid, Info, Visited1, Pos + 1, Children, Fun),
{Visited3, Result} = link_tree(
RootPid, Info, Visited2, Pos, Rest, Fun),
{Visited3, [{Pos, {Pid, Fun(Pid, Props), NewTree}}] ++ Result};
none ->
Props = info(Pid, Info),
Visited1 = gb_trees:insert(Pid, Props, Visited0),
{Visited2, Result} = link_tree(
RootPid, Info, Visited1, Pos, Rest, Fun),
{Visited2, [{Pos, {Pid, Fun(Pid, Props), []}}] ++ Result}
end;
link_tree(_RootPid, _Info, Visited, _Pos, [], _Fun) ->
{Visited, []}.
info(Pid, Info) when is_pid(Pid) ->
ValidProps = [
backtrace,
binary,
catchlevel,
current_function,
current_location,
current_stacktrace,
dictionary,
error_handler,
garbage_collection,
garbage_collection_info,
group_leader,
heap_size,
initial_call,
links,
last_calls,
memory,
message_queue_len,
messages,
min_heap_size,
min_bin_vheap_size,
monitored_by,
monitors,
message_queue_data,
priority,
reductions,
registered_name,
sequential_trace_token,
stack_size,
status,
suspending,
total_heap_size,
trace,
trap_exit
],
Validated = lists:filter(fun(P) -> lists:member(P, ValidProps) end, Info),
process_info(Pid, lists:usort(Validated));
info(Port, Info) when is_port(Port) ->
ValidProps = [
registered_name,
id,
connected,
links,
name,
input,
output,
os_pid
],
Validated = lists:filter(fun(P) -> lists:member(P, ValidProps) end, Info),
port_info(Port, lists:usort(Validated)).
port_info(Port, Items) ->
lists:foldl(fun(Item, Acc) ->
case (catch erlang:port_info(Port, Item)) of
{Item, _Value} = Info -> [Info | Acc];
_Else -> Acc
end
end, [], Items).
mapfold_tree([], Acc, _Fun) ->
{[], Acc};
mapfold_tree([{Pos, {Key, Value0, SubTree0}} | Rest0], Acc0, Fun) ->
{Value1, Acc1} = Fun(Key, Value0, Pos, Acc0),
{SubTree1, Acc2} = mapfold_tree(SubTree0, Acc1, Fun),
{Rest1, Acc3} = mapfold_tree(Rest0, Acc2, Fun),
{[{Pos, {Key, Value1, SubTree1}} | Rest1], Acc3}.
map_tree(Tree, Fun) ->
{Result, _} = mapfold_tree(Tree, nil, fun(Key, Value, Pos, Acc) ->
{Fun(Key, Value, Pos), Acc}
end),
Result.
fold_tree(Tree, Acc, Fun) ->
{_, Result} = mapfold_tree(Tree, Acc, fun(Key, Value, Pos, AccIn) ->
{Value, Fun(Key, Value, Pos, AccIn)}
end),
Result.
linked_processes_info(Pid, Info) ->
link_tree(Pid, Info, fun(P, Props) -> {process_name(P), Props} end).
print_linked_processes(Name) when is_atom(Name) ->
case whereis(Name) of
undefined -> {error, {unknown, Name}};
Pid -> print_linked_processes(Pid)
end;
print_linked_processes(Pid) when is_pid(Pid) ->
Info = [reductions, message_queue_len, memory],
TableSpec = [
{50, left, name}, {12, centre, reductions},
{19, centre, message_queue_len}, {10, centre, memory}
],
Tree = linked_processes_info(Pid, Info),
print_tree(Tree, TableSpec).
%% Pretty print functions
%% Limmitations:
%% - The first column has to be specified as {Width, left, Something}
%% The TableSpec is a list of either:
%% - {Value}
%% - {Width, Align, Value}
%% Align is one of the following:
%% - left
%% - centre
%% - right
print_tree(Tree, TableSpec) ->
io:format("~s~n", [format(TableSpec)]),
map_tree(Tree, fun(_, {Id, Props}, Pos) ->
io:format("~s~n", [table_row(Id, Pos * 2, Props, TableSpec)])
end),
ok.
format(Spec) ->
Fields = [format_value(Format) || Format <- Spec],
string:join(Fields, "|").
format_value({Value}) -> term2str(Value);
format_value({Width, Align, Value}) -> string:Align(term2str(Value), Width).
bind_value({K}, Props) when is_list(Props) ->
{element(2, lists:keyfind(K, 1, Props))};
bind_value({Width, Align, K}, Props) when is_list(Props) ->
{Width, Align, element(2, lists:keyfind(K, 1, Props))}.
term2str(Atom) when is_atom(Atom) -> atom_to_list(Atom);
term2str(Binary) when is_binary(Binary) -> binary_to_list(Binary);
term2str(Integer) when is_integer(Integer) -> integer_to_list(Integer);
term2str(Float) when is_float(Float) -> float_to_list(Float);
term2str(String) when is_list(String) -> lists:flatten(String);
term2str(Term) -> iolist_to_list(io_lib:format("~p", [Term])).
table_row(Key, Indent, Props, [{KeyWidth, Align, _} | Spec]) ->
Values = [bind_value(Format, Props) || Format <- Spec],
KeyStr = string:Align(term2str(Key), KeyWidth - Indent),
[string:copies(" ", Indent), KeyStr, "|" | format(Values)].
-ifdef(TEST).
-include_lib("couch/include/couch_eunit.hrl").
random_processes(Depth) ->
random_processes([], Depth).
random_processes(Pids, 0) ->
lists:usort(Pids);
random_processes(Acc, Depth) ->
Caller = self(),
Ref = make_ref(),
Pid = case oneof([spawn_link, open_port]) of
spawn_monitor ->
{P, _} = spawn_monitor(fun() ->
Caller ! {Ref, random_processes(Depth - 1)},
receive looper -> ok end
end),
P;
spawn ->
spawn(fun() ->
Caller ! {Ref, random_processes(Depth - 1)},
receive looper -> ok end
end);
spawn_link ->
spawn_link(fun() ->
Caller ! {Ref, random_processes(Depth - 1)},
receive looper -> ok end
end);
open_port ->
spawn_link(fun() ->
Port = erlang:open_port({spawn, "sleep 10"}, []),
true = erlang:link(Port),
Caller ! {Ref, random_processes(Depth - 1)},
receive looper -> ok end
end)
end,
receive
{Ref, Pids} -> random_processes([Pid | Pids] ++ Acc, Depth - 1)
end.
oneof(Options) ->
lists:nth(couch_rand:uniform(length(Options)), Options).
tree() ->
[InitialPid | _] = Processes = random_processes(5),
{InitialPid, Processes, link_tree(InitialPid)}.
setup() ->
tree().
teardown({_InitialPid, Processes, _Tree}) ->
[exit(Pid, normal) || Pid <- Processes].
link_tree_test_() ->
{
"link_tree tests",
{
foreach,
fun setup/0, fun teardown/1,
[
fun should_have_same_shape/1,
fun should_include_extra_info/1
]
}
}.
should_have_same_shape({InitialPid, _Processes, Tree}) ->
?_test(begin
InfoTree = linked_processes_info(InitialPid, []),
?assert(is_equal(InfoTree, Tree)),
ok
end).
should_include_extra_info({InitialPid, _Processes, _Tree}) ->
Info = [reductions, message_queue_len, memory],
?_test(begin
InfoTree = linked_processes_info(InitialPid, Info),
map_tree(InfoTree, fun(Key, {_Id, Props}, _Pos) ->
case Key of
Pid when is_pid(Pid) ->
?assert(lists:keymember(reductions, 1, Props)),
?assert(lists:keymember(message_queue_len, 1, Props)),
?assert(lists:keymember(memory, 1, Props));
_Port ->
ok
end,
Props
end),
ok
end).
is_equal([], []) -> true;
is_equal([{Pos, {Pid, _, A}} | RestA], [{Pos, {Pid, _, B}} | RestB]) ->
case is_equal(RestA, RestB) of
false -> false;
true -> is_equal(A, B)
end.
-endif.