aboutsummaryrefslogtreecommitdiffstats
path: root/lib/pman/src/pman_shell.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/pman/src/pman_shell.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/pman/src/pman_shell.erl')
-rw-r--r--lib/pman/src/pman_shell.erl823
1 files changed, 823 insertions, 0 deletions
diff --git a/lib/pman/src/pman_shell.erl b/lib/pman/src/pman_shell.erl
new file mode 100644
index 0000000000..0b13890460
--- /dev/null
+++ b/lib/pman/src/pman_shell.erl
@@ -0,0 +1,823 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-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%
+%%
+%%
+%% ---------------------------------------------------------------
+%% Purpose: Create a trace window with process
+%% information or a help window with information
+%% about pman.
+%%
+%% ---------------------------------------------------------------
+
+-module(pman_shell).
+
+%% ---------------------------------------------------------------
+%% The user interface exports
+%% ---------------------------------------------------------------
+
+-export([start_list/3,
+ start/2,
+ start/1,
+ find_shell/0]).
+
+%% ---------------------------------------------------------------
+%% Includes
+%% ---------------------------------------------------------------
+-include("assert.hrl").
+-include("pman_options.hrl").
+-include("pman_buf.hrl").
+
+
+%% ---------------------------------------------------------------
+%% Internal record declarations
+%% ---------------------------------------------------------------
+-record(pman_shell,{win,
+ editor,
+ pid,
+ buffer,
+ father,
+ shell_flag, % boolean, true for shell
+ trace_options, % Keeps trace options
+ db}). % DB for trace windows
+
+
+%%
+%% Constants
+%%
+
+-define (PMAN_DB, pman_db). % The pman db for trace windows
+
+
+
+%% ---------------------------------------------------------------
+%% start/1, start/2
+%%
+%% Starts a new trace shell process.
+%%
+%% start(Pid, DefaultOptions)
+%% Pid The Pid of the process to trace
+%% DefaultOptions The default trace options passed along from
+%% the calling process.
+%%
+%%
+%% start(Pid)
+%% Pid The Pid of the process to trace
+%%
+%% start(Pid) starts without using any default options except for those
+%% hardwired into the application. (See pman_options.hrl).
+%%
+%%
+%% Return: Both functions return a process id
+%% ---------------------------------------------------------------
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% start_list/3 - Starts a trace window for each of the processes
+%% in the list
+
+start_list(LIPid, Father, Options) ->
+ StartFun = fun(Pid) ->
+ start({Pid,Father}, Options)
+ end,
+ lists:foreach(StartFun, LIPid).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% start/1 - Starts a trace window for the specified Pid.
+%%
+
+start(Pid) ->
+ start(Pid, #trace_options{}).
+
+%%
+%% start/2
+%%
+
+start(Pid,DefaultOptions) when is_pid(Pid) ->
+ start({Pid,self()}, DefaultOptions);
+
+start(Var,DefaultOptions) ->
+ Db = db_start(),
+ spawn_link(fun() -> internal(Var, DefaultOptions, Db) end).
+
+%% ---------------------------------------------------------------
+%% Initialize the enviroment for tracing/viewing Object
+%%
+%% Object can either be {shell,Shell} or a Pid.
+%% The main loop is then called, which handles trace and event
+%% requests. The window dies whenever Supervisor dies, while
+%% message windows die whenever their parent dies.
+%% ---------------------------------------------------------------
+
+internal({Object,Supervisor}, DefaultOptions, Db) ->
+
+ %% (???) This call will cause minor problems when the window has been
+ %% invoked with proc/1 from for instance the shell. The shell
+ %% does not handle the exit-signals, so it will exit
+ %% when the window is exited.
+
+
+ %% First check that no other process is tracing the process we want
+ %% to trace. There is no well defined way of doing this, so the
+ %% code below is used instead. (???)
+
+ pman_relay:start(Object), %(???) Uses proc. dict.
+
+ Pid = pman_process:get_pid(Object),
+
+ case pman_relay:ok_to_trace(Pid) of
+
+ %% Tracing cannot be performed on the specified process
+
+ false ->
+ T = lists:flatten(io_lib:format("ERROR: Process ~p is already being~ntraced by some other process.~nOr there may be a problem communicating with it.",[Pid])),
+ tool_utils:notify(gs:start(),T),
+ exit(quit);
+
+ %% Tracing can be performed, go ahead!
+
+ true ->
+
+ case db_insert_key (Db, Pid) of
+ true ->
+
+ link(Supervisor),
+ process_flag(trap_exit, true),
+
+ case catch pman_win:window(Object) of
+ {'EXIT', badrpc} ->
+ T = "ERROR: Could not access node",
+ pman_win:dialog_window(gs:start(),T);
+ {'EXIT', dead} ->
+ T = "ERROR: The process is dead",
+ pman_win:dialog_window(gs:start(),T);
+ {'EXIT',_W} ->
+ T = "ERROR: Untracable process \n(unexpected EXIT reason)",
+ pman_win:dialog_window(gs:start(),T);
+ {Win, Ed} ->
+ init_monitor_loop(Win,
+ Ed,
+ Object,
+ Supervisor,
+ DefaultOptions,
+ Db)
+ end;
+
+ false ->
+ T = lists:flatten(io_lib:format("ERROR: Process ~p is already being~ntraced by some other process.",[Pid])),
+ tool_utils:notify(gs:start(),T),
+ exit(quit);
+
+ Error ->
+ Error
+ end
+
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% init_monitor_loop/5
+
+init_monitor_loop(Win,Ed,Object,Supervisor, DefaultOptions, Db) ->
+
+ process_flag(priority, max),
+
+ %% Most default options come from the main window. Now we must set
+ %% the default file name to something that is shows what process
+ %% is being traced.
+
+ %% Find out an appropriate file name to write the trace output
+ %% to if the output should go to a file.
+
+ FileName = case pman_process:is_pid_or_shell(Object) of
+ true ->
+ default_file_name(pman_process:get_pid(Object));
+ false ->
+ "NoName"
+ end,
+
+ Buff = pman_buf:start(Ed, FileName),
+
+ case pman_process:is_running(Object) of
+
+ %% We are tracing a shell process.
+ {true,{shell,Pid}} ->
+ safe_link(Pid),
+ NewDefaultOptions =
+ DefaultOptions#trace_options{file=FileName},
+ perform_option_changes(Pid, NewDefaultOptions, Buff),
+ monitor_loop(#pman_shell{win=Win, editor=Ed, pid=Pid, buffer=Buff,
+ father = Supervisor,
+ shell_flag = true,
+ trace_options = NewDefaultOptions,
+ db = Db});
+
+ %% We are tracing an ordinary process.
+ {true,Pid} ->
+ safe_link(Pid),
+ NewDefaultOptions =
+ DefaultOptions#trace_options{file=FileName},
+ perform_option_changes(Pid, NewDefaultOptions, Buff),
+ monitor_loop(#pman_shell{win=Win, editor=Ed, pid=Pid, buffer=Buff,
+ father = Supervisor,
+ shell_flag = false,
+ trace_options = NewDefaultOptions,
+ db = Db});
+
+ %% The process being traced is dead.
+ false ->
+ monitor_loop(#pman_shell{win=Win, editor=Ed, pid=nopid,
+ buffer=Buff,
+ father = Supervisor,
+ shell_flag = false,
+ trace_options= DefaultOptions,
+ db = Db})
+ end.
+
+%% ----------------------------------------------------------------
+%% What is the Pid of the shell on our node?
+%% ----------------------------------------------------------------
+
+find_shell() ->
+ case shell:whereis_evaluator() of
+ undefined -> % noshell
+ noshell;
+ Pid ->
+ Pid
+ end.
+
+%% ---------------------------------------------------------------
+%% Functions called in case of an exit message
+%% ---------------------------------------------------------------
+
+clean_up(Win, Buff,Pid) ->
+
+ %% (???) Unlinks the traced process, but since we are using a safe link
+ %% it is probably unnecessary.
+
+ safe_unlink(Pid),
+
+ %% Kill helper processes
+
+ exit(Buff#buffer.converter, topquit),
+ exit(Buff#buffer.buffer, topquit),
+
+ gs:destroy(Win).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% exit_cmd/3 - Takes care of the necessary details when
+%% a linked process terminates.
+
+
+exit_cmd(Pid,_Reason, State) ->
+ case State#pman_shell.shell_flag of
+
+ %% This clause handles the case when a shell process dies.
+ %% Since it is restarted and the intention is to continue tracing
+ %% the restarted shell process, we need to handle it separately by
+ %% finding the new shell process.
+ true ->
+
+ NewShell = find_shell(),
+ safe_link(NewShell),
+ pman_relay:start(NewShell),
+
+ %% Update the window title with the new PID
+ Title = pman_win:title({shell, NewShell}),
+ Win = State#pman_shell.win,
+ gse:config(Win,[{title,Title}]),
+
+ pman_relay:trac(NewShell, true, flags()),
+
+ B = State#pman_shell.buffer,
+ B#buffer.converter!{raw,[{shell_died, Pid, NewShell}]},
+
+
+
+ State#pman_shell{pid=NewShell};
+
+ %% This clause handles the case when a traced process that is
+ %% not a shell process dies.
+ false ->
+
+ B = State#pman_shell.buffer,
+ B#buffer.converter!{raw,[{died, Pid}]},
+
+ lists:foreach(fun(X) -> gse:disable(X) end,
+ ['Options',
+ 'Kill',
+ 'LinksMenu']),
+ State#pman_shell{pid=undefined}
+ end.
+
+flags() ->
+ [send, 'receive', call, procs,
+ set_on_spawn, set_on_first_spawn, set_on_link, set_on_first_link].
+
+options_to_flaglists(Options) ->
+ AssocList =
+ [{Options#trace_options.send, send},
+ {Options#trace_options.treceive, 'receive'},
+ {Options#trace_options.inherit_on_1st_spawn, set_on_first_spawn},
+ {Options#trace_options.inherit_on_all_spawn, set_on_spawn},
+ {Options#trace_options.inherit_on_1st_link, set_on_first_link},
+ {Options#trace_options.inherit_on_all_link, set_on_link},
+ {Options#trace_options.events, procs},
+ {Options#trace_options.functions,call}],
+
+ TrueFun = fun ({Option,Flag}) ->
+ case Option of
+ true -> Flag;
+ _Otherwise -> false
+ end
+ end,
+ TrueFlags = mapfilter(TrueFun, AssocList),
+
+ FalseFun = fun ({Option,Flag}) ->
+ case Option of
+ false -> Flag;
+ _Otherwise -> false
+ end
+ end,
+ FalseFlags = mapfilter(FalseFun, AssocList),
+ {TrueFlags,FalseFlags}.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% mapfilter/2 - Combines the functionality of lists:map and
+%% lists:filter. mapfilter applies the function argument to
+%% each element in the list. All returned values that are
+%% not false will occur in the resulting list.
+%%
+%% Arguments:
+%% Fun A fun that takes one argument
+%% List A list. Each element will become an argument to Fun.
+%%
+%% Returns:
+%% A list of all results from the map operation that are not false.
+%%
+
+mapfilter(Fun,[E|Es]) ->
+ case apply(Fun,[E]) of
+ false ->
+ mapfilter(Fun,Es);
+ Value -> [Value | mapfilter(Fun,Es)]
+ end;
+mapfilter(_Fun, []) -> [].
+
+
+
+perform_option_changes(Pid,Options,Buffer) ->
+
+ %% Notify the trace output functionality
+ %% if the destination is supposed to go to a file...
+
+ case Options#trace_options.to_file of
+ true ->
+ FName = Options#trace_options.file,
+ Buffer#buffer.converter!{file,FName};
+ false ->
+ done
+ end,
+
+ %%...then set the trace flags of the traced process
+
+ {OnFlags, OffFlags} = options_to_flaglists(Options),
+ case catch begin
+
+ %% (???) Note that the following calls cannot actually fail
+ %% This may be a problem. And the catch appears unnecessary
+ %% However, it may become necessary to let the
+ %% pman_relay:trac/3 function retrun appropriate values.
+ pman_relay:trac(Pid,true, OnFlags),
+ pman_relay:trac(Pid,false, OffFlags)
+ end of
+ true ->
+ ok;
+ _ -> pman_win:format("** Illegal trace request ** \n", [])
+ end.
+
+
+
+
+
+
+%% ---------------------------------------------------------------
+%% Take care of the command executed by the user.
+
+execute_cmd(Cmd,Shell_data) ->
+ Window = Shell_data#pman_shell.win,
+ Editor = Shell_data#pman_shell.editor,
+ Shell = Shell_data#pman_shell.pid,
+ Buffer = Shell_data#pman_shell.buffer,
+ TraceOptions = Shell_data#pman_shell.trace_options,
+
+ case Cmd of
+ 'Close' ->
+ db_delete_key (Shell_data#pman_shell.db, Shell_data#pman_shell.pid),
+ clean_up(Window, Buffer, Shell),
+ exit(quit);
+ 'Destroy' ->
+ db_delete_key (Shell_data#pman_shell.db, Shell_data#pman_shell.pid),
+ exit(Buffer#buffer.buffer,topquit),
+ safe_unlink(Shell),
+ exit(Buffer#buffer.converter,topquit),
+ exit(Buffer#buffer.buffer,topquit),
+ exit(quit);
+
+ 'Clear' when is_pid(Shell) ->
+ New_buffer = pman_buf:clear(Buffer,pman_win:display(Shell),
+ TraceOptions#trace_options.file),
+ Shell_data#pman_shell{buffer = New_buffer};
+ 'Save buffer' ->
+ DefaultFile = "Pman_buffer." ++ default_file_name(Shell),
+ Result = tool_utils:file_dialog([{type,save},
+ {file,DefaultFile}]),
+ case Result of
+ {ok, UserFile, _State} ->
+ Buffer#buffer.buffer!{save_buffer,UserFile};
+ {error,_Reason} ->
+ true
+ end,
+ Shell_data;
+ 'Help' ->
+ HelpFile = filename:join([code:lib_dir(pman), "doc", "html", "index.html"]),
+ tool_utils:open_help(gs:start([{kernel, true}]), HelpFile),
+ Shell_data;
+ 'Kill' when is_pid(Shell) ->
+ exit(Buffer#buffer.converter,killed),
+ exit(Buffer#buffer.buffer,killed),
+ lists:foreach(fun(X) -> gse:disable(X) end,
+ ['TraceMenu',
+ 'Clear']),
+ catch exit(Shell, kill),
+ Shell_data#pman_shell{pid = undefined};
+ 'All Links' when is_pid(Shell) ->
+ LIPid = pman_process:pinfo(Shell, links),
+ ?ALWAYS_ASSERT("Just a brutal test"),
+ start_list(LIPid,
+ Shell_data#pman_shell.father,
+ Shell_data#pman_shell.trace_options),
+ Shell_data;
+ 'Module' when is_pid(Shell) ->
+ {ModuleName,_,_} = pman_process:function_info(Shell),
+ pman_module_info:start(ModuleName),
+ Shell_data;
+ 'Options' when is_pid(Shell) ->
+ case pman_options:dialog(Window,
+ "Trace Options for Process",
+ TraceOptions) of
+ {error, _Reason} ->
+ Shell_data;
+ Options ->
+ perform_option_changes(Shell, Options, Buffer),
+ Shell_data#pman_shell{trace_options=Options}
+ end;
+
+ {trac,Choice,Bool} when is_pid(Shell) ->
+ pman_relay:trac(Shell, Bool, [Choice]),
+ Shell_data;
+
+
+ {configure,{X,Y}} ->
+ configure (Editor, X, Y),
+ Shell_data;
+
+ Pid when is_pid(Pid) ->
+ pman_shell:start({Pid, Shell_data#pman_shell.father},
+ Shell_data#pman_shell.trace_options),
+ Shell_data;
+ _Other ->
+ ?ALWAYS_ASSERT("Received unexpected event"),
+ Shell_data
+ end.
+
+
+default_file_name(Shell) when is_pid(Shell) ->
+ [A,B,C] = string:tokens(pid_to_list(Shell),[$.,$<,$>]),
+ "pman_trace." ++ A ++ "_" ++ B ++ "_" ++ C;
+default_file_name(_OTHER) ->
+ "shell".
+
+
+
+
+
+%% Key accellerators
+
+key(e) -> 'Clear';
+key(s) -> 'Save buffer';
+key(c) -> 'Close';
+key(a) -> 'All';
+key(r) -> 'Reset';
+key(m) -> 'Module';
+key(l) -> 'All Links';
+key(k) -> 'Kill';
+key(h) -> 'Help';
+key(z) -> 'Close';
+key(O) -> O.
+
+
+
+%% ---------------------------------------------------------------
+%% The main loop takes care of data coming in from the traces, as
+%% well as exit signals from proceses we are monitoring. Events
+%% caused by the user or window manager are also handled here.
+%% ---------------------------------------------------------------
+
+
+monitor_loop(Shell_data) ->
+ receive
+
+ %% WM destroy
+ {gs,_Window,destroy,[],[]} -> %%Avoid links menus
+ execute_cmd('Destroy', Shell_data);
+
+
+ %% Handle EXIT signal from parent process
+ {'EXIT', _Pid, topquit} ->
+ clean_up(Shell_data#pman_shell.win,
+ Shell_data#pman_shell.buffer,
+ Shell_data#pman_shell.pid),
+ exit(topquit);
+
+ %% (???) Ignore "stray" EXIT signal from converter
+ {'EXIT', _Pid, win_killed} ->
+ monitor_loop(Shell_data);
+
+
+ %% Handle EXIT signal from safely linked Pid
+ %% This is received when a traced process dies.
+ {'SAFE_EXIT', Pid, Reason} ->
+ New_Shell_data = exit_cmd(Pid, Reason,Shell_data ),
+ monitor_loop(New_Shell_data);
+
+
+ %% Handle EXIT signal from processes where we expect
+ %% some EXIT signals, such as the file_dialog opened, and possibly
+ %% others.
+
+ {'EXIT', _Pid, _Reason} ->
+ monitor_loop(Shell_data);
+
+ %% Handle incoming trace messages
+ Message when is_tuple(Message) , element(1,Message) == trace->
+ {L, Suspended} = collect_tracs([Message]),
+ Buffer = Shell_data#pman_shell.buffer,
+ Buffer#buffer.converter!{raw,L},
+ lists:foreach(fun(P) -> erlang:resume_process(P) end, Suspended),
+ monitor_loop(Shell_data);
+
+
+ %% All other messages on the form {...,...,...}
+ Message when is_tuple(Message) ->
+ do_link_stuff(Shell_data),
+
+ New_Shell_data = process_gs_event(Message,Shell_data),
+ monitor_loop(New_Shell_data);
+
+ %% Catch all for unexpected messages
+ _Anything ->
+ ?ALWAYS_ASSERT("Received unexpected event"),
+ monitor_loop(Shell_data)
+
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% process_event/1 - Error handling wrapper for gs_cmd
+
+process_gs_event(Message, Shell_data) ->
+ case catch gs_cmd(Message,Shell_data) of
+
+ %%
+ %% Error exits from gs_cmd
+
+ {'EXIT', badrpc} ->
+ Text = "\nERROR: Could not access node",
+ pman_win:msg_win(Text),
+ Shell_data;
+ {'EXIT', dead} ->
+ Text = "\nERROR: The process is dead",
+ pman_win:msg_win(Text),
+ Shell_data;
+
+ %% A controlled application initiated termination
+ {'EXIT', quit} ->
+ db_delete_key (Shell_data#pman_shell.db, Shell_data#pman_shell.pid),
+ exit(quit);
+
+
+ {'EXIT',Reason} ->
+ db_delete_key (Shell_data#pman_shell.db, Shell_data#pman_shell.pid),
+ io:format("Debug info, Reason: ~p~n",[Reason]),
+ ?ALWAYS_ASSERT("Unexpected EXIT reason"),
+ exit({unexpected_EXIT_reason,Reason});
+
+ %%
+ %% "Proper" exits from gs_cmd
+
+ New_Shell_data ->
+ New_Shell_data
+ end.
+
+
+
+gs_cmd(Cmd, Shell_data) ->
+ case Cmd of
+
+ %%User Command
+ {gs, Command, click, _Data, _Args} ->
+ execute_cmd(Command,Shell_data);
+
+ %%Key accellerator
+ {gs,_Window,keypress,_D,[Key,_,0,1]} ->
+ execute_cmd(key(Key),Shell_data);
+
+ %%Window Resize
+ {gs,_Window,configure,_,[X,Y|_]} ->
+ execute_cmd({configure,{X,Y}},Shell_data);
+
+
+ {gs, _Object, _Event, _Data, _Args} ->
+ ?ALWAYS_ASSERT("Unhandled gs event"),
+ Shell_data
+
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% (???) do_link_stuff/1 - I have no clue.
+%%
+
+do_link_stuff(Shell_data) ->
+
+ %% This appears to be code to execute for adding
+ %% dynamic links menus.
+
+ case Shell_data#pman_shell.pid of
+ undefined ->
+ ok;
+ Pid ->
+ case pman_process:pinfo(Pid, links) of
+ Links when is_list(Links) ->
+ pman_win:links_menus(Links);
+ undefined ->
+ ok
+ end
+ end.
+
+
+%% (???) Process dictionary used to safe Pid-Pid pairs.
+%%
+%% safe_link/1 - Spawns a process, that links to the Pid, and sends
+%% a message to the caller when the linked process dies.
+%%
+%% Since we (think we) need to link to the traced process, we want
+%% to do it in a way that has the smallest possible risk. The process
+%% that links to the Pid is small and simple, which is safer than if
+%% the calling process would link directly to the Pid.
+
+safe_link(Pid) when is_pid(Pid) ->
+ Self = self(),
+ PidSafe = spawn_link(fun() -> safe_init(Self, Pid) end),
+ put(Pid, PidSafe).
+
+
+%% safe_unlink/1 - Removes a safe link
+%%
+
+safe_unlink(Pid) when is_pid(Pid) ->
+ PidSafe = get(Pid),
+ PidSafe ! {unlink, self(), Pid},
+ erase(Pid);
+
+safe_unlink(_Anything)->
+ true.
+
+%% safe_init/2 - Initialize a simple receive loop that controls safe linking
+%% to application processes.
+%%
+safe_init(Caller, Pid) ->
+
+ process_flag(trap_exit, true),
+ link(Pid),
+
+ safe_loop(Caller, Pid).
+
+
+%% safe_loop/2 - Simply waits for an exit signal from the linked Pid,
+%% all other messages are disregarded.
+%%
+
+
+safe_loop(Caller, Pid) ->
+ receive
+ %% Linked process dies
+ {'EXIT' , Pid, Reason} ->
+ Caller ! {'SAFE_EXIT', Pid, Reason};
+
+ %% Caller dies
+ {'EXIT', Caller, _Reason} ->
+ unlink(Pid);
+
+
+ %% Unlink request
+ {unlink, Caller, Pid} ->
+ unlink(Pid);
+
+ %% Ignore everything else
+ _Anything ->
+ safe_loop(Caller, Pid)
+ end.
+
+
+
+configure (Editor, W, H) ->
+ gs:config (Editor, [{width, W - 3},
+ {height, H - 40}]).
+
+
+
+
+%%% The DB is used to avoid multiple trace windows
+%%% of the same process.
+
+%%% db_start /0
+%%%
+
+db_start() ->
+ case ets:info(?PMAN_DB) of
+ undefined -> ets:new(?PMAN_DB, [public, named_table]);
+ _ -> ?PMAN_DB
+ end.
+
+
+
+%%% db_insert_key /2
+%%%
+
+db_insert_key (Db, Pid) ->
+ case ets:lookup (Db, Pid) of
+ [] ->
+ case catch ets:insert (Db, {Pid}) of
+ true ->
+ true;
+
+ _Error ->
+ error_insert_db
+ end;
+
+ _already_exists ->
+ false
+ end.
+
+
+
+%%% db_delete_key /2
+%%%
+
+db_delete_key (Db, Pid) ->
+ ets:delete (Db, Pid).
+
+
+%% Function to collect all trace messages in the receive queue.
+%% Returns: {Messages,SuspendedProcesses}
+
+collect_tracs(Ack) -> collect_tracs(Ack, ordsets:new()).
+
+collect_tracs(Ack, Procs) ->
+ receive
+ Trac when is_tuple(Trac), element(1, Trac) == trace ->
+ P = suspend(Trac, Procs),
+ collect_tracs([Trac | Ack], P)
+ after 0 ->
+ {lists:reverse(Ack), ordsets:to_list(Procs)}
+ end.
+
+suspend({trace,From,call,_Func}, Suspended) when node(From) == node() ->
+ case ordsets:is_element(From, Suspended) of
+ true -> Suspended;
+ false ->
+ case (catch erlang:suspend_process(From)) of
+ true ->
+ ordsets:add_element(From, Suspended);
+ _ ->
+ Suspended
+ end
+ end;
+suspend(_Other, Suspended) -> Suspended.