%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2001-2009. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% -module(process_info). -behavior(gen_server). -export([start/0, start_link/0, stop/0]). -export([is_node/1, get_nodes/0, get_applications/1, get_application_keys/2, get_processes/3, get_process_data/2, send_trace/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(data, {que=undef, procs=undef, links=undef, links2=undef}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% %% Functions to retrieve information about which application %% %% at the node %% %% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start() -> gen_server:start({local, proc_info}, process_info, [], []). start_link() -> gen_server:start_link({local, proc_info}, process_info, [], []). stop() -> gen_server:call(proc_info, stop, 1000). %% is_node(NodeS) -> {bool(), Node, NodeS2} %% NodeS = NodeS2 = string() %% Node = node() is_node(NodeS) -> Node = list_to_atom(NodeS), case lists:member(Node, [node()|nodes()]) of true-> {true, Node, NodeS}; false -> {false, node(), atom_to_list(node())} end. %% get_nodes() -> [node()] get_nodes() -> [node()|nodes()]. %% get_applications(Node) -> [App] %% Node = node() %% App = atom() %% Returns the list of all applications with a supervision tree (that %% is, not library applications such as stdlib) at Node. get_applications(Node) -> Info = rpc:call(Node, application, info, []), {value, {running, Apps}} = lists:keysearch(running, 1, Info), [App || {App, Pid} <- Apps, is_pid(Pid)]. %% get_application_keys(App, Node) -> {ok, Keys} | {error, Reason} %% Node = node() %% App = atom() %% Keys = [{Key, Val}] %% Key = atom() %% Val = term() %% Reason = badapp | badrpc get_application_keys(App, Node) -> case rpc:call(Node, application, get_all_key, [App]) of {ok, Keys} -> {ok, Keys}; undefined -> {error, badapp}; {badrpc, _} -> {error, badrpc} end. %% get_processes(App, Mode, Node) -> {Tree, Dict} | unknown %% App = atom() %% Mode = sup | sup_child | all %% Node = node() get_processes(App, Mode, Node) -> gen_server:call(proc_info, {get_processes, App, Mode, Node}). %% get_process_data(Pid, Node) -> ProcData %% Pid = pid() %% Node = node() %% ProcData -- see erlang:process_info/1 get_process_data(Pid, Node) -> case rpc:call(Node, erlang, process_info, [Pid]) of {badrpc, _} -> [{error,"Please try again"}]; Res -> Res end. %% send_trace(PidL) -> void() send_trace(PidL) -> gen_server:call(proc_info, {send_trace, PidL}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% %% gen_server callbacks %% %% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([]) -> {ok, ets:new(procs, [])}. handle_call({get_processes, App, Mode, Node}, _From, State) -> case do_get_processes(App, Mode, Node) of unknown -> {reply, unknown, State}; Tree -> {reply, {Tree, State}, State} end; handle_call({send_trace, PidL}, _From, State) -> do_send_trace(PidL, State), {reply, ok, State}; handle_call(stop, _From, State) -> {stop, normal, ok, State}. handle_cast(_, State) -> {noreply, State}. handle_info(_, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_, State, _) -> {ok, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% %% Internal functions %% %% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% do_get_processes(App, Mode, Node) -> Tree | unknown %% App = atom() %% Mode = all | sup | sup_childs %% Node = node() %% Tree = term() do_get_processes(App, Mode, Node) -> case rpc:call(Node, application_controller, get_master, [App]) of Pid when is_pid(Pid) -> start_collecting_data(Pid, Mode, Node); undefined -> unknown end. %% Initiate the database, get the processes and the links. %% Then build lists and return them. start_collecting_data(Pid, Mode, Node) -> Db = get_database(), {Db2, Tree} = build_graph({master,Pid}, Db, Pid, Mode, Node), delete_database(Db2), Tree. get_database() -> P = ets:new(procs,[]), L = ets:new(link,[bag]), L2 = ets:new(link2,[bag]), Q = queue:new(), ets:insert(P, {whereis(application_controller), crap}), ets:insert(P, {whereis(gs), crap}), #data{que=Q, procs=P, links=L, links2=L2}. delete_database(Db) -> ets:delete(Db#data.procs), ets:delete(Db#data.links), ets:delete(Db#data.links2). %% The thought is %% 1. Get the processes that links to Pid. %% Pid is the application master the first time. %% 2. Add the processes to the database and clear the list of children %% from processes which for some resason not should be there. %% 3. Queue the children, so we later can se if they have any links. %% 4. Add links to the childrens. %% 5. When the whole tree is retreived remove the unnecessary processes %% depending on the mode. %% 6. Take all links that point to the same pid and sort out %% the primary and secondary relations. %% If more than one process links to the same process, the relation %% between a supervisor and a process is primary. The rest is %% secondary, there is no different in real world just in logic %% between a secondary and a primary relation. %% When all processes in the application is collected, %% fix secondary links and return the tree. build_graph(finish, Db, Grp, Mode, Node) -> Db = fix_links(Db, Grp, Node), delete_unwanted(Db, Mode, Grp), Tree = start_tree(Db, Node), {Db, Tree}; build_graph(Pid, Db, Grp, Mode, Node) -> Children = get_children(Pid, Mode, Node), Children2 = add_and_remove(Children, Pid, Db, Grp, Node), Q2 = queue_children(Db#data.que, Children2), add_children(Pid, Db, Children2, 1), case queue:out(Q2) of {empty, _}-> build_graph(finish, Db, Grp, Mode, Node); {{value,NPid}, Q3}-> Db2 = Db#data{que=Q3}, build_graph(NPid,Db2,Grp,Mode,Node) end. %% Collect the processes which the current process has a link to %% Pid is now the application_master and the application master's %% child is the application supervisor but in reality there are two %% application master processes. %% Fix this by reordering the processes a little. get_children({master,Pid}, _Mode, Node) when is_pid(Pid) -> %% Get the master pid MPid = case application_master:get_child(Pid) of {Pid1, _App} -> Pid1; Pid1 -> Pid1 end, %% Get the second appplication master process and order them %% correctly case rpc:call(Node, erlang, process_info, [MPid,links]) of {links, [H|T]} -> [H,MPid|T]; {links, []} -> MPid end; get_children({Pid, _Name}, _Mode, Node) when is_pid(Pid), Node==node(Pid) -> {links,Links} = rpc:call(Node, erlang, process_info, [Pid,links]), Links; get_children(Pid, _Mode, Node) when is_pid(Pid), Node==node(Pid) -> {links,Links} = rpc:call(Node, erlang, process_info, [Pid,links]), Links; get_children(Pid, _Mode, Node) when is_pid(Pid), Node/=node(Pid) -> []; get_children(Port, _Mode, _Node) when is_port(Port) -> []. %% Add the links to the database. %% The first case -- when it is the application master process -- there %% is only one real child even though there are more links. add_children({master,Pid}, Db, [Child|_Rest], N) -> add_child(Pid, Db, Child, N); add_children(_Pid, _Db, [], _N) -> ok; add_children(Pid, Db, [Child|Rest], N) -> add_child(Pid, Db, Child, N), add_children(Pid, Db, Rest, N+1). add_child(Pid, Db, Child, N) -> case ets:match_object(Db#data.links, {Pid,Child,'_'}) of [] -> ets:insert(Db#data.links, {Pid,Child,N}); _ -> ok end. %% Add the list of processes to the queue. queue_children(Queue, []) -> Queue; queue_children(Queue, [H|T]) -> Q = queue:in(H, Queue), queue_children(Q, T). %% The processess that we already has added to the database are %% not children to the current process, so we don't need to add them a %% second time. remove_used_children([], _Db, New_list) -> lists:reverse(New_list); remove_used_children([Child|Rest], Db, New) -> case ets:lookup(Db#data.procs, Child) of [] -> remove_used_children(Rest, Db, [Child|New]); _ -> remove_used_children(Rest, Db, New) end. %% Take the list of links and separate it into a list with ports and a %% list with pids. separate_ports([], Pids, Ports) -> {Pids, Ports}; separate_ports([Child|Rest], Pids, Ports) -> if is_port(Child) -> separate_ports(Rest, Pids, [Child|Ports]); is_pid(Child) -> separate_ports(Rest, [Child|Pids], Ports) end. %% Add the current pid to the ets table with processes and clear %% the list of children from processes that should not be there. %% In the first case, no children are used so it's not necessary. add_and_remove(Children, {master,Pid}, Db, _Grp, Node) when is_pid(Pid), Node==node(Pid) -> ets:insert(Db#data.procs, {Pid, {master,master}, controller}), {_Pids,Ports} = separate_ports(Children, [], []), Ports++Children; %% This clause is removable when using only link as retrieving mode . add_and_remove(Children, {Pid,_Name}, Db, Grp, Node) when is_pid(Pid), Node==node(Pid) -> ets:insert(Db#data.procs, {Pid, rpc:call(Node,erlang,process_info, [Pid,registered_name])}), {Pids, Ports} = separate_ports(Children, [], []), Children1 = remove_used_children(Pids, Db, []), Children2 = remove_others_children(Children1, Grp, Node), Ports++Children2; add_and_remove(Children, Pid, Db, Grp, Node) when is_pid(Pid), Node==node(Pid) -> ets:insert(Db#data.procs, {Pid, rpc:call(Node,erlang,process_info, [Pid,registered_name])}), {Pids, Ports} = separate_ports(Children, [], []), Children1 = remove_used_children(Pids, Db, []), Children2 =remove_others_children(Children1, Grp, Node), Ports++Children2; add_and_remove(_Children, Pid, _Db, _Grp, Node) when is_pid(Pid), Node/=node(Pid) -> []; %% Take care of the ports, don't add them to the table with processes. add_and_remove(_Children, Pid, _Db, _Grp, _Node) when is_port(Pid) -> []. %% Control that the application's group leader is the group leader of %% Pid group_leader_check({Pid,_Name}, Grp, Node) -> group_leader_check(Pid, Grp, Node); group_leader_check(Pid, Grp, Node) -> case rpc:call(Node, erlang, process_info, [Pid,group_leader]) of {_Item, Grp} -> yes; _ -> no end. %% Take the list of children and remove the ones with anoother group %% leader. remove_others_children(Children, Grp, Node) -> lists:filter(fun(Child) -> case group_leader_check(Child, Grp, Node) of yes -> true; no -> false end end, Children). %% Mark the processes in the procs table as either supervisor or worker. fix_links(Db, Leader, Node) -> {Sup,_Work} = mark_supervisors_workers(Db, Leader, Node), ets:match_delete(Db#data.procs, {'_',crap}), [_Pid|Procs] = ets:tab2list(Db#data.procs), N_links = get_n_links(Procs, Db#data.links, []), N_links2 = take_sup_links(Sup, Db#data.links, N_links), add_shared_links(N_links2, Db#data.links2), Db. %% Add the links that point to the same child to the shared links table add_shared_links(N_links, Links2) -> Insert_fun = fun(Link) -> ets:insert(Links2, Link) end, lists:map(fun(List) -> lists:map(Insert_fun, List) end, N_links). %% Take the list of links that point to the same children and remove %% the ones that are children to supervisors. %% The first argument is a list of the supervisors. %% N_links contains a list of list of links that points to the same %% child. take_sup_links([], _Db, N_links) -> N_links; take_sup_links([H|Supervised], Links_table, N_links) -> N_list_fun = fun(Link) -> insert_sup_links(Link, H, Links_table) end, N_links2 = lists:map(fun(Link_list) -> lists:filter(N_list_fun,Link_list) end, N_links), take_sup_links(Supervised, Links_table, N_links2). %% Insert the supervised links in the primary links list. %% This function should be used as a fun to the filter function in %% take_sup_links/3. insert_sup_links({From,To,N}, Sup, Links_table) -> case From of Sup -> ets:insert(Links_table, {From,To,N}), false; _ -> true end. %% Get the links which points to the same children. get_n_links([], _Links, N_link) -> N_link; get_n_links([{Pid,_,_}|Procs], Links, N_link) -> case ets:match_object(Links, {'_',Pid,'_'}) of L when length(L)>1 -> ets:match_delete(Links, {'_',Pid,'_'}), get_n_links(Procs, Links, [L|N_link]); _L -> get_n_links(Procs, Links, N_link) end; get_n_links([{Pid,_}|Procs], Links, N_link) -> case ets:match_object(Links, {'_',Pid,'_'}) of L when length(L)>1 -> ets:match_delete(Links, {'_',Pid,'_'}), get_n_links(Procs, Links, [L|N_link]); _L -> get_n_links(Procs, Links, N_link) end. %% Mark the processes that are in the supervisor tree as either worker %% or supervisor. mark_supervisors_workers(Db, Leader, Node) -> %% Get the supervisors and workers. {Sup_list, Worker_list} = get_by_supervisors1(Leader), %% Update the supervisor pids. lists:map(fun(Pid) -> ets:insert(Db#data.procs, {Pid, rpc:call(Node, erlang,process_info, [Pid,registered_name]), supervisor}) end, Sup_list), %% Update the worker pids. lists:map(fun(Pid) -> ets:insert(Db#data.procs, {Pid, rpc:call(Node, erlang,process_info, [Pid,registered_name]), worker}) end, Worker_list), {lists:reverse(Sup_list), Worker_list}. %% The second way to retrieve the applications processes is to go by %% the supervision tree. get_by_supervisors1(Leader) -> case application_master:get_child(Leader) of {Pid, _Name}-> get_by_supervisors([{namn,Pid,supervisor,list_of_mods}], [], []); Pid -> get_by_supervisors([{namn,Pid,supervisor,list_of_mods}], [], []) end. get_by_supervisors([], Sup, Work) -> {Sup, Work}; get_by_supervisors([{_,Pid,supervisor,_}|Rest], Sup, Work) when is_pid(Pid) -> Children = supervisor:which_children(Pid), Children2 = lists:append(Children, Rest), get_by_supervisors(Children2, [Pid|Sup], Work); get_by_supervisors([{_,Pid,_,_}|Rest], Sup, Work) when is_pid(Pid) -> get_by_supervisors(Rest, Sup, [Pid|Work]); get_by_supervisors([_Whatever|Rest], Sup, Work) -> get_by_supervisors(Rest, Sup, Work). %% Use pattern matching to select mode and delete the unneccesary pids delete_unwanted(Db, sup_child, App_pid) -> delete_not_in_supervisor_tree(Db), add_main_link(Db, App_pid), Db; delete_unwanted(Db, all, _App_pid) -> Db; delete_unwanted(Db, sup, App_pid) -> delete_workers(Db), delete_not_in_supervisor_tree(Db), add_main_link(Db, App_pid), Db. add_main_link(Db, App_pid) -> case application_master:get_child(App_pid) of {Pid, _Name} when is_pid(Pid) -> ets:insert(Db#data.links, {App_pid,Pid,1}); Pid when is_pid(Pid) -> ets:insert(Db#data.links, {App_pid,Pid,1}); _ -> false end. %% Delete the processes that are in the supervision tree but are %% workers, and their links. delete_workers(Db) -> Pids = ets:match_object(Db#data.procs, {'_','_',worker}), Pids2 = lists:map( fun({Pid,_,_}) -> %% Remove the unwanted pids from the process table. ets:match_delete(Db#data.procs, {Pid,'_','_'}), %% Remove the links to and from the pid. ets:match_delete(Db#data.links, {Pid,'_','_'}), ets:match_delete(Db#data.links, {'_',Pid,'_'}), ets:match_delete(Db#data.links2, {Pid,'_','_'}), ets:match_delete(Db#data.links2, {'_',Pid,'_'}) end, Pids), Pids2. %% Delete the processes that are not in the supervision tree. delete_not_in_supervisor_tree(Db) -> Pids = ets:match_object(Db#data.procs,{'_','_'}), Pids2 = lists:map( fun({Pid,_}) -> %% Remove the unwanted from the process table. ets:match_delete(Db#data.procs, {Pid,'_'}), %% Remove the links to and from the pid. ets:match_delete(Db#data.links, {Pid,'_','_'}), ets:match_delete(Db#data.links, {'_',Pid,'_'}), ets:match_delete(Db#data.links2, {Pid,'_','_'}), ets:match_delete(Db#data.links2, {'_',Pid,'_'}) end, Pids), Pids2. %% Start generating the tree. start_tree(Db, Node) -> case get_master(Db) of no -> false; Pid -> build_node(Pid, Db, Node) end. %% Build a node and then it runs itself on every child to the current %% pid. build_node(Pid, Db, Node) when is_pid(Pid), Node==node(Pid) -> Sort_fun = fun sort_order/2, Fix_sec_name_fun = fun(Pid2) -> get_link_name(Pid2, Db) end, Build_tree_fun = fun({_,Pid1,_}) -> build_node(Pid1,Db,Node) end, Children = ets:match_object(Db#data.links, {Pid,'_','_'}), Children1 = lists:sort(Sort_fun, Children), Sec_children = ets:match_object(Db#data.links2, {Pid,'_','_'}), {get_name(Pid,Db), lists:map(Build_tree_fun,Children1), lists:map(Fix_sec_name_fun,Sec_children)}; build_node(Pid, _Db, Node) when is_pid(Pid), Node/=node(Pid) -> {"Runs on another node:"++erlang:pid_to_list(Pid), [], []}; build_node(Pid, _Db, _Node) when is_port(Pid) -> {"Port :"++erlang:port_to_list(Pid), [], []}. %% Select the name of the pid from the database where we previosly %% added it. get_name(Pid, Db) -> case ets:lookup(Db#data.procs, Pid) of [{_,{_,master},_}] -> pid_to_list(Pid); [{_,{_,Name}}] -> atom_to_list(Name)++" : "++pid_to_list(Pid); [{_,{_,Name},_}] -> atom_to_list(Name)++" : "++pid_to_list(Pid); _ -> pid_to_list(Pid) end. %% Select the name of the process which we have a link to. get_link_name({_,Pid,_}, Db) when is_pid(Pid) -> case ets:lookup(Db#data.procs, Pid) of [{_,{_,Name}}] -> atom_to_list(Name)++" : "++pid_to_list(Pid); [{_,{_,Name},_}] -> atom_to_list(Name)++" : "++pid_to_list(Pid); _ -> pid_to_list(Pid) end; get_link_name({_,Port,_}, _Db) when is_port(Port) -> "Port :"++" : "; get_link_name(_, _) -> "". %% Sort the links in the order they where added, in ascending order. sort_order({_,_,N1}, {_,_,N2}) when N1>N2 -> true; sort_order(_N1, _N2) -> false. %% Select the pid of the application master. get_master(Db) -> case ets:match_object(Db#data.procs, {'_',{master,master},controller}) of [{Pid,_,_}|_Rest] -> Pid; _ -> no end. %% The main function to handle tracing. %% Checks if the process is in the table with traced processes. If so, %% it stops the trace, otherwise it starts the trace. do_send_trace(PidL, Traced_tab) -> Pid = list_to_pid(PidL), Key = get_key(Pid), case catch ets:lookup(Traced_tab, Key) of [] -> trace_process(Pid, Key, true, Traced_tab); [_Object]-> trace_process(Pid, Key, false, Traced_tab) end, filter_procs(Traced_tab, ets:tab2list(Traced_tab)). get_key(Pid) -> Node = node(Pid), case rpc:call(Node, erlang, process_info, [Pid,registered_name]) of [] -> pid_to_list(Pid); {registered_name, Name} -> atom_to_list(Name)++" : "++pid_to_list(Pid) end. %% Tries to toggle the trace flag for the process. trace_process(Pid, Key, On_or_off, Procs_tab) -> case rpc:call(node(Pid), sys, trace, [Pid,On_or_off,1000]) of timeout -> Node = node(Pid), io:fwrite("timeout node= ~w, Pid= ~w mode= ~w ~n", [Node, Pid, On_or_off]); {badrpc, _} -> Node = node(Pid), io:fwrite("badrpc node= ~w, Pid= ~w mode= ~w ~n", [Node, Pid, On_or_off]); Res -> Node = node(Pid), io:fwrite("anymode ~w node= ~w, Pid= ~w mode= ~w ~n", [Res, Node, Pid,On_or_off]), case On_or_off of true -> ets:insert(Procs_tab, {Key,On_or_off}); false -> ets:delete(Procs_tab, Key) end end. %% Check if the processes in the ets table with traced processes %% are alive. If not, remove them. filter_procs(Tab, Tab_list) -> lists:foreach(fun({Key,_Val}) -> is_alive(Key, Tab) end, Tab_list). is_alive(Key, Tab) -> case get_pid(Key) of nopid -> false; Pid -> is_alive2(Pid, Key, Tab) end. %% Key is either a pid in list form or Pidname:Pid in list form. get_pid(Key) -> case catch list_to_pid(string:substr(Key,string:rchr(Key,$<))) of Pid when is_pid(Pid) -> Pid; _ -> nopid end. is_alive2(Pid, Key, Tab) -> case catch rpc:call(node(Pid), erlang, is_process_alive, [Pid]) of true -> true; false -> catch ets:delete(Tab, Key), ok end.