%%
%% %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.