aboutsummaryrefslogtreecommitdiffstats
path: root/lib/pman/src
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
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/pman/src')
-rw-r--r--lib/pman/src/Makefile112
-rw-r--r--lib/pman/src/assert.hrl81
-rw-r--r--lib/pman/src/pman.app.src40
-rw-r--r--lib/pman/src/pman.appup.src19
-rw-r--r--lib/pman/src/pman.erl132
-rw-r--r--lib/pman/src/pman_buf.erl117
-rw-r--r--lib/pman/src/pman_buf.hrl29
-rw-r--r--lib/pman/src/pman_buf_buffer.erl102
-rw-r--r--lib/pman/src/pman_buf_converter.erl189
-rw-r--r--lib/pman/src/pman_buf_printer.erl89
-rw-r--r--lib/pman/src/pman_buf_utils.erl106
-rw-r--r--lib/pman/src/pman_main.erl787
-rw-r--r--lib/pman/src/pman_module_info.erl131
-rw-r--r--lib/pman/src/pman_options.erl395
-rw-r--r--lib/pman/src/pman_options.hrl34
-rw-r--r--lib/pman/src/pman_process.erl317
-rw-r--r--lib/pman/src/pman_relay.erl127
-rw-r--r--lib/pman/src/pman_relay_server.erl57
-rw-r--r--lib/pman/src/pman_shell.erl823
-rw-r--r--lib/pman/src/pman_tool.erl145
-rw-r--r--lib/pman/src/pman_win.erl667
-rw-r--r--lib/pman/src/pman_win.hrl39
22 files changed, 4538 insertions, 0 deletions
diff --git a/lib/pman/src/Makefile b/lib/pman/src/Makefile
new file mode 100644
index 0000000000..e573e57220
--- /dev/null
+++ b/lib/pman/src/Makefile
@@ -0,0 +1,112 @@
+#
+# %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%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(PMAN_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/pman-$(VSN)
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+
+MODULES= \
+ pman \
+ pman_main \
+ pman_shell \
+ pman_relay \
+ pman_relay_server \
+ pman_module_info \
+ pman_win \
+ pman_buf \
+ pman_buf_utils \
+ pman_buf_buffer \
+ pman_buf_converter \
+ pman_buf_printer \
+ pman_options \
+ pman_process \
+ pman_tool
+
+HRL_FILES= \
+ assert.hrl \
+ pman_buf.hrl \
+ pman_options.hrl \
+ pman_win.hrl
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
+
+APP_FILE = pman.app
+APP_SRC = $(APP_FILE).src
+APP_TARGET = $(EBIN)/$(APP_FILE)
+
+APPUP_FILE = pman.appup
+APPUP_SRC = $(APPUP_FILE).src
+APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += +warn_obsolete_guard
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f errs core *~
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+docs:
+
+# ----------------------------------------------------
+# Special Targets
+# ----------------------------------------------------
+
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(TOOLBOX_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(TARGET_TOOLBOX_FILES) $(RELSYSDIR)/ebin
+
+release_docs_spec:
+
diff --git a/lib/pman/src/assert.hrl b/lib/pman/src/assert.hrl
new file mode 100644
index 0000000000..ea3b68cd7c
--- /dev/null
+++ b/lib/pman/src/assert.hrl
@@ -0,0 +1,81 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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 : Assert macro
+
+
+%% ?ASSERT/2 - will simply return true if the first argument evaluates to true
+%% otherwise it will exit and output (via the error logger) the
+%% second string
+%%
+%% Arguments:
+%% Flag Expression that should evalueate to true or false
+%% String String to return as a part of the exit reason as well
+%% be to be sent to the error logger.
+%%
+%% Returns:
+%% true If the Flag expression evaluates to true
+%%
+%% Exits:
+%% {'EXIT', {assertion_failed, String}}
+%% If the Flag expression evaluates to something other than
+%% true.
+%%
+%% Usage notes:
+%% Please note that the Flag argument must be a valid expression that
+%% evaluates to true.
+%%
+%% Also, avoid any side effects in the Flag, as everything performed
+%% within the scope of the ?ASSERT macro will not be present when
+%% the code is not compiled with the debug_on flag.
+%%
+%% Side effects include the binding of a variable, sending of a
+%% message, etc.
+%%
+
+-ifdef(debug_on).
+-define(ASSERT(Flag, String),
+ case Flag of
+ true ->
+ true;
+ _ ->
+ S2 =
+ lists:flatten(
+ io_lib:format(
+ "=ASSERT====~nPid:~p, Module:~p, Line:~p~nTermination because assertion failed:~n~p",
+ [self(),?MODULE, ?LINE,String])),
+ error_logger:error_report(S2),
+ exit({assertion_failed, String})
+ end
+ ).
+
+-define(ALWAYS_ASSERT(String),
+ S2 = lists:flatten(
+ io_lib:format(
+ "=ASSERT====~nPid:~p, Module:~p, Line:~p~nTermination because of unconditional assert:~n~p",
+ [self(),?MODULE, ?LINE, String])),
+ error_logger:error_report(S2),
+ exit({always_assert, String})
+ ).
+-else.
+-define(ASSERT(_Flag,_String), true).
+-define(ALWAYS_ASSERT(_String), true).
+-endif.
+
+
+
diff --git a/lib/pman/src/pman.app.src b/lib/pman/src/pman.app.src
new file mode 100644
index 0000000000..cc32a17296
--- /dev/null
+++ b/lib/pman/src/pman.app.src
@@ -0,0 +1,40 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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%
+%%
+{application, pman,
+ [{description, "pman The Process Manager"},
+ {vsn, "%VSN%"},
+ {modules, [
+ pman,
+ pman_buf,
+ pman_buf_buffer,
+ pman_buf_converter,
+ pman_buf_printer,
+ pman_buf_utils,
+ pman_main,
+ pman_module_info,
+ pman_options,
+ pman_process,
+ pman_relay,
+ pman_relay_server,
+ pman_shell,
+ pman_tool,
+ pman_win
+ ]},
+ {registered, []},
+ {applications, [kernel, stdlib]}]}.
diff --git a/lib/pman/src/pman.appup.src b/lib/pman/src/pman.appup.src
new file mode 100644
index 0000000000..7a435e9b22
--- /dev/null
+++ b/lib/pman/src/pman.appup.src
@@ -0,0 +1,19 @@
+%%
+%% %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%
+%%
+{"%VSN%",[],[]}.
diff --git a/lib/pman/src/pman.erl b/lib/pman/src/pman.erl
new file mode 100644
index 0000000000..c8ea34b6b7
--- /dev/null
+++ b/lib/pman/src/pman.erl
@@ -0,0 +1,132 @@
+%%
+%% %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 : Exported API to the Pman graphical tool
+%%----------------------------------------------------------------------
+
+-module(pman).
+
+
+%% ---------------------------------------------------------------
+%% The user interface exports
+%% ---------------------------------------------------------------
+-export([start/0,
+ start_notimeout/0,
+ start/1,
+ start_notimeout/1,
+ proc/1,
+ proc/3]).
+
+%% ---------------------------------------------------------------
+
+%% Timeout for the startup function.
+%% If no {initialization_complete, Pid} message has been received
+%% from the spawned init-function within ?STARTUP_TIMEOUT ms
+%% the start-function will call exit(Reason).
+-define(STARTUP_TIMEOUT, 20000).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% start/0
+
+start() ->
+ start([], ?STARTUP_TIMEOUT). %Start w/o excluded modules
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% start_notimeout/0
+
+start_notimeout() ->
+ start([],infinity). %Start w/o excluded modules
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% start/1
+
+start(LIModuleExcluded) ->
+ start(LIModuleExcluded, ?STARTUP_TIMEOUT).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% start_notimeout/1
+
+start_notimeout(LIModuleExcluded) ->
+ start(LIModuleExcluded, infinity).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% start/2 - Spawns the main Pman process, that will supervise
+%% all processes except those running code from the modules
+%% specified in LIModuleExcluded
+%%
+
+start(LIModuleExcluded, Timeout) ->
+
+ OSModuleExcluded = ordsets:from_list(LIModuleExcluded),
+
+ PidInit = spawn(pman_main, init, [self(), OSModuleExcluded]),
+
+ %% Wait for a initialization completion message from
+ %% the spawned process before returning its Pid.
+ %%
+
+ receive
+ {initialization_complete, PidInit} ->
+ PidInit
+
+ %% (Conditional) Failure to start within the time limit will
+ %% result in termination
+
+ after
+ Timeout ->
+ exit(PidInit, kill),
+ exit({startup_timeout, ?MODULE})
+ end.
+
+
+
+%% ---------------------------------------------------------------
+%% If we want to trace just one process, we can call proc, giving it
+%% either the Pid, or the registered name, (Global or local).
+%%
+%% (???)
+%% Note that this function must not be used internally to create a
+%% trace window, since it is assumed that it is started from any
+%% process (esp. the shell) it will not have any supervisor process
+%% that shall be notified about it's exit/death.
+%%
+%% Returns: Trace loop Pid|udefined
+
+%% ---------------------------------------------------------------
+
+
+proc(undefined) ->
+ exit(undefined);
+
+proc({shell,P}) when is_pid(P) ->
+ pman_shell:start({{shell,P},self()});
+
+proc(P) when is_atom(P) ->
+ proc(whereis(P));
+
+proc({global, N}) ->
+ proc(global:whereis_name(N));
+
+proc(P) when is_pid(P) ->
+ pman_shell:start({P,self()}).
+
+proc(X,Y,Z) ->
+ proc(c:pid(X,Y,Z)).
+
diff --git a/lib/pman/src/pman_buf.erl b/lib/pman/src/pman_buf.erl
new file mode 100644
index 0000000000..d56ce184fa
--- /dev/null
+++ b/lib/pman/src/pman_buf.erl
@@ -0,0 +1,117 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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 : This module is the exported interface to the buffering mechanism
+%%% used in PMAN to prevent the trace output to congest
+%%% the system.
+%%%
+%%% This module depends on the modules (direct or indirectly):
+%%% pman_buf.hrl
+%%% pman_buf_utils.erl
+%%% pman_buf_buffer.erl
+%%% pman_buf_converter.erl
+%%% pman_buf_printer.erl
+%%%
+%%%----------------------------------------------------------------------
+
+-module(pman_buf).
+
+%%-compile(export_all).
+-export([start/2,clear/3]).
+
+
+-include("pman_buf.hrl").
+
+
+%% The buffering mechanism consists of three processes that
+%% work in a chain to prevent the process monitor from congesting
+%% output mechanism.
+%%
+%% Messages are buffered in the CONVERTER process before they are sent to
+%% to the BUFFER process where they are formatted before they are finally
+%% sent to either a file or the PRINTER process. The printer process
+%% outputs the messages in the graphical user interface.
+%%
+%%
+%%
+%% --> CONVERTER --> BUFFER --> PRINTER --> gui
+%% |
+%% |
+%% |
+%% V
+%%
+%% file
+%%
+
+
+
+
+
+%% ----------------------------------------------------------------
+%% The amount of data produced by a trace message may be large, and
+%% cause the run time system to run out of memory. To avoid this,
+%% the task of storing, cutting buffers, formating data and printing
+%% it is performed by three processes: The buffer, the converter and
+%% the printer.
+%%
+%% The converter accepts the raw data, a list
+%% of {trace,Msg} tuples. Having max priority, it assures that the
+%% amount of raw data stored never excedes ?BUFF_SIZE messages.
+%% (With the exception of the last batch received, which assures that
+%% the last trace message printed is never a buffer cut message.)
+%% Whenever there is space available in the buffer process, (The
+%% Buffer process stores max. ?BUFF_SIZE converted messages),
+%% the buffer asks for more unconverted messages, and ?PRINT_LEN messages
+%% are sent. They are converted by the buffer, and added to the list
+%% of messages to be sent.
+
+%% The printer process requests formatted messages from the buffer,
+%% and in chuncs of ?MAX_OUTPUT sends them to the buffer. If traces
+%% are to be dumped on file, due to the max priority, such is handled
+%% in the converter, and buffers are not cut.
+%%
+
+
+%% ---------------------------------------------------------------
+%% Initializes the buffering mechanism, which consist of three
+%% processes, each involved with a phase of the formattation and
+%% output of data to the process windows.
+
+start(Editor, FileName) ->
+ Buffer_Pid = spawn_link(pman_buf_buffer,init,[Editor]),
+ Converter_Pid =
+ spawn_link(pman_buf_converter,init,[Buffer_Pid, FileName]),
+ Buffer_Pid!{converter_pid, Converter_Pid},
+ #buffer{converter=Converter_Pid,buffer=Buffer_Pid}.
+
+
+
+%% ---------------------------------------------------------------
+%% Kills the converter and the clears the buffer with formated data
+%% starting a new converter.
+
+clear(Buff,String, FileName) ->
+ exit(Buff#buffer.converter,win_killed),
+ Converter_Pid=spawn_link(pman_buf_converter,init,[Buff#buffer.buffer,
+ FileName]),
+ Buff#buffer.buffer!{clear,String,Converter_Pid },
+ Buff#buffer{converter = Converter_Pid}.
+
+
+
diff --git a/lib/pman/src/pman_buf.hrl b/lib/pman/src/pman_buf.hrl
new file mode 100644
index 0000000000..3f25dcc5f0
--- /dev/null
+++ b/lib/pman/src/pman_buf.hrl
@@ -0,0 +1,29 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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%
+%%
+
+%%-compile(export_all).
+%%-export([Function/Arity, ...]).
+
+-define(BUFF_SIZE,1000).
+-define(EDITOR_MAX,10000).
+-define(PRINT_LEN,50).
+-define(MAX_OUTPUT,5000).
+
+
+-record(buffer,{buffer,converter}).
diff --git a/lib/pman/src/pman_buf_buffer.erl b/lib/pman/src/pman_buf_buffer.erl
new file mode 100644
index 0000000000..ad92eb1f3e
--- /dev/null
+++ b/lib/pman/src/pman_buf_buffer.erl
@@ -0,0 +1,102 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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 : The purpouse of the buffer process is to take
+%%% care of the data that is received by the converter
+%%% process and pass it on to the printer process in chunks
+%%% that can be handled.
+%%%
+%%% This module is a part of the buffering system, and
+%%% should not be used except through the API defined
+%%% in the pman_buf module.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(pman_buf_buffer).
+
+%%-compile(export_all).
+-export([init/1]).
+
+-include("pman_buf.hrl").
+
+
+
+%%
+%% Initialization function for the buffer process.
+%% To be started with spawn from the calling process.
+%%
+
+init(Editor) ->
+ Printer_pid = spawn_link(pman_buf_printer,init,[Editor,self()]),
+ receive
+ {converter_pid,Pid} ->
+ Pid!{buffer,accept},
+ buffer_loop([],0,0,Printer_pid,Pid)
+ end.
+
+
+
+%%
+%% Receive loop for the buffer process.
+%%
+
+buffer_loop(Buffer,Size,Acc,Printer,Converter) ->
+ receive
+ {save_buffer,Name} ->
+ Printer!{save_buffer,Name},
+ buffer_loop(Buffer,Size,Acc,Printer,Converter);
+ {raw,Raw,Length} -> %%output to editor
+ New_Size = Size + Length,
+ if New_Size < ?BUFF_SIZE ->
+ Converter!{buffer,accept};
+ true -> ok
+ end,
+ Print = lists:map(fun(X) -> pman_buf_utils:textformat(X) end, Raw),
+ New_Buff = lists:append(Buffer,Print),
+ buffer_loop(New_Buff,New_Size,Acc,Printer,Converter);
+ {clear,Text,N_Converter} ->
+ Converter!{buffer,accept},
+ Printer!clear,
+ buffer_loop([Text],1,1,Printer,N_Converter);
+ {printer,send} when Buffer /= [] ->
+ if
+ Acc > ?EDITOR_MAX ->
+ Printer!clear,
+ Printer !{buffer,"Cleared Buffer due to Size\n\n"},
+ buffer_loop(Buffer,Size,1,Printer,Converter);
+ true ->
+ {Length,Rest,Print} = pman_buf_utils:split(Buffer,
+ ?PRINT_LEN,
+ 0,
+ []),
+ Printer ! {buffer,Print},
+ New_Size = Size - Length,
+ if New_Size < ?BUFF_SIZE ->
+ Converter!{buffer,accept};
+ true -> ok
+ end,
+ buffer_loop(Rest,New_Size,Acc+Length,Printer,Converter)
+ end;
+ {converter,file} ->
+ Converter!{buffer,Buffer},
+ self()!{raw,[to_file],1},
+ buffer_loop([],0,Acc,Printer,Converter)
+ end.
+
+
diff --git a/lib/pman/src/pman_buf_converter.erl b/lib/pman/src/pman_buf_converter.erl
new file mode 100644
index 0000000000..b6f560411c
--- /dev/null
+++ b/lib/pman/src/pman_buf_converter.erl
@@ -0,0 +1,189 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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 : The purpouse of the converter process is to take
+%% care of the raw data that is received by the tracing
+%% process (a pman_shell process) and pass it on to
+%% the buffer process in chunks that can be handled.
+%%
+%% This module is a part of the buffering system, and
+%% should not be used except through the API defined
+%% in the pman_buf module.
+%%
+%%----------------------------------------------------------------------
+
+-module(pman_buf_converter).
+
+%%-compile(export_all).
+-export([init/2]).
+
+-include("pman_buf.hrl").
+
+
+%% ---------------------------------------------------------------
+%% Starts the process which received the raw data from the debugger,
+%% cuts and forwards it to the buffer in smaller chunks. High priority
+%% to avoid large message queues waiting to be processed.
+
+init(Buffer_Pid, FileName) ->
+ process_flag(priority, max),
+ converter_loop(Buffer_Pid,[],0,true,[], FileName).
+
+converter_loop(Buffer_Pid,Raw,Size,State,Last, FileName) ->
+ receive
+ {file,Shell} ->
+ case init_file(lists:append(Raw,Last),
+ FileName,
+ Shell,
+ Buffer_Pid) of
+ true -> converter_loop(Buffer_Pid,
+ [to_buffer],
+ 1,
+ State,
+ [],
+ FileName);
+ false -> converter_loop(Buffer_Pid,
+ Raw,
+ Size,
+ State,
+ Last,
+ FileName)
+ end;
+ {raw,Trace} ->
+ {New_Raw,New_Size,New_State,New_Last} =
+ converter_data(Trace, Buffer_Pid, Raw, Size, State, Last),
+ converter_loop(Buffer_Pid,
+ New_Raw,
+ New_Size,
+ New_State,
+ New_Last,
+ FileName);
+ {buffer,accept} when Raw /= [] ->
+ {Length,Rest,Print} = pman_buf_utils:split(Raw,?PRINT_LEN,0,[]),
+ Buffer_Pid!{raw,Print,Length},
+ converter_loop(Buffer_Pid,Rest,Size-Length,false,Last,FileName);
+ {buffer,accept} when Last /= [] ->
+ {New_Raw,New_Size,New_State,New_Last} =
+ converter_data(Last,Buffer_Pid,Raw,Size,true,[]),
+ converter_loop(Buffer_Pid,
+ New_Raw,
+ New_Size,
+ New_State,
+ New_Last,
+ FileName);
+ {buffer,accept} ->
+ converter_loop(Buffer_Pid,Raw,Size,true,Last, FileName);
+ {clear,Str} ->
+ Buffer_Pid!{clear,Str},
+ converter_loop(Buffer_Pid,[],0,State,Last,FileName)
+ end.
+
+converter_data(Trace,Buffer_Pid,Raw,Size,State,Last) ->
+ if
+ ?BUFF_SIZE - Size > 0 ->
+ {Len,Rest,New_Trace} = pman_buf_utils:split(Trace,
+ ?BUFF_SIZE-Size,
+ 0,[]),
+ {New_Raw,New_Last} =
+ case Rest of
+ [] ->
+ {lists:append(Raw,New_Trace),Last};
+ [_|_] ->
+ case Last of
+ [] ->
+ {lists:append(Raw,New_Trace),Rest};
+ _ ->{lists:concat([Raw,New_Trace,[cut_buffer]]),
+ Rest}
+ end
+ end,
+ case State of true ->
+ {Length,Cut_Raw,Print} = pman_buf_utils:split(New_Raw,
+ ?PRINT_LEN,
+ 0,[]),
+ Buffer_Pid!{raw,Print,Length},
+ {Cut_Raw,Size-Length,false,New_Last};
+ _ ->
+ {New_Raw,Size+Len,false,New_Last}
+ end;
+ true ->
+ {Raw,Size,State,Trace}
+ end.
+
+
+%% ---------------------------------------------------------------
+%% Initializes the environment for saving the trace to file. The
+%% actual saving is taken care of by the buffer process.
+
+init_file(Raw,FileName, Name,Buffer_Pid) ->
+ case open_file(FileName, Name) of
+ {false,T} ->
+ pman_win:msg_win(T),
+ false;
+ {File,T} ->
+ Buffer_Pid!{converter,file},
+ pman_win:dialog_window(gs:start(),T),
+ save_loop_init(File,Raw)
+ end.
+
+open_file(FileName, _Shell) ->
+%% L = "pman_trace." ++ Shell,
+ case file:open(FileName, [read,write]) of
+ {error, _} ->
+ Str = "ERROR: Could not create_file\n" ++ FileName,
+ {false,Str};
+ {ok,File} ->
+ file:position(File, {eof, 0}),
+ Str1 = " Appending trace log to file\n" ++ FileName,
+ {File,Str1}
+ end.
+
+
+save_loop_init(Fd,Raw) ->
+ {Date, Time} = calendar:local_time(),
+ {Year, Month, Day} = Date,
+ {Hour, Minute, Second} = Time,
+ io:format(Fd,"%%% ~n",[]),
+ io:format(Fd,"%%% Trace output~n",[]),
+ io:format(Fd,"%%% Started at ~4p-~2p-~2p ~2p:~2p:~2p~n",
+ [Year, Month, Day,
+ Hour, Minute, Second
+ ]),
+ io:format(Fd,"%%% ~n~n",[]),
+
+ Print = lists:map(fun(X) -> pman_buf_utils:textformat(X) end, Raw),
+ receive
+ {buffer,Text} when is_list(Text) ->
+ io:format(Fd,Text,[]),
+ io:format(Fd,Print,[]),
+ save_loop(Fd)
+ end.
+
+save_loop(Fd) ->
+ receive
+ {raw,Raw} ->
+ Print = lists:map(fun(X) -> pman_buf_utils:textformat(X) end, Raw),
+ io:format(Fd,Print,[]),
+ save_loop(Fd);
+ buffer -> true
+ end.
+
+
+
+
+
diff --git a/lib/pman/src/pman_buf_printer.erl b/lib/pman/src/pman_buf_printer.erl
new file mode 100644
index 0000000000..74e935171a
--- /dev/null
+++ b/lib/pman/src/pman_buf_printer.erl
@@ -0,0 +1,89 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(pman_buf_printer).
+
+%%-compile(export_all).
+-export([init/2]).
+
+
+-include("pman_buf.hrl").
+
+%% ---------------------------------------------------------------
+%% Starts the part of the buffer which regulates the flow of data to
+%% be printed in the pid editors
+
+
+init(Editor,Buffer_pid) ->
+ Buffer_pid!{printer,send},
+ printer_loop(Editor,Buffer_pid).
+
+printer_loop(Editor,Buffer_pid)->
+ receive
+ {save_buffer,Name} ->
+ gs:config(Editor,{save,Name}),
+ TT = "Buffer saved in file\n" ++ Name,
+ pman_win:dialog_window(gs:start(),TT),
+ printer_loop(Editor,Buffer_pid);
+ {buffer,Trace} ->
+ case lists:flatlength(Trace) of
+ Len when Len > ?MAX_OUTPUT ->
+ printer_long(lists:flatten(Trace),Editor),
+ Buffer_pid!{printer,send},
+ printer_loop(Editor,Buffer_pid);
+ _ ->
+ Buffer_pid!{printer,send},
+ print_trace(Editor,Trace),
+ printer_loop(Editor,Buffer_pid)
+ end;
+ clear ->
+ pman_win:configeditor(Editor, [{enable, true}]),
+ pman_win:configeditor(Editor,clear),
+ pman_win:configeditor(Editor, [{enable, false}]),
+ printer_loop(Editor,Buffer_pid);
+ _Other ->
+ printer_loop(Editor,Buffer_pid)
+ end.
+
+printer_long([],_) -> ok;
+printer_long(Trace,Editor) ->
+ receive
+ clear ->
+ pman_win:configeditor(Editor, [{enable, true}]),
+ pman_win:configeditor(Editor,clear),
+ pman_win:configeditor(Editor, [{enable, false}])
+ after 0 ->
+ {_Length,Rest,Print} = pman_buf_utils:split(Trace,
+ ?MAX_OUTPUT,
+ 0,
+ []),
+ print_trace(Editor,Print),
+ printer_long(Rest,Editor)
+ end.
+
+
+
+%% ---------------------------------------------------------------
+%% Function which print trace messages on the window
+%% ---------------------------------------------------------------
+
+print_trace(Editor,Elements) ->
+ pman_win:configeditor(Editor, [{enable, true}]),
+ pman_win:configeditor(Editor, [{insert, {'end',Elements}}]),
+ pman_win:configeditor(Editor, [{enable, false}]).
diff --git a/lib/pman/src/pman_buf_utils.erl b/lib/pman/src/pman_buf_utils.erl
new file mode 100644
index 0000000000..af3982665e
--- /dev/null
+++ b/lib/pman/src/pman_buf_utils.erl
@@ -0,0 +1,106 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(pman_buf_utils).
+
+%%-compile(export_all).
+-export([textformat/1, split/4]).
+
+
+%% ---------------------------------------------------------------
+%% Funtion which format the trace message
+%% ---------------------------------------------------------------
+
+textformat(died) ->
+ "\n\nProcess died\n";
+textformat({died, Pid}) ->
+ io_lib:format("~w Process died.~n",[Pid]);
+textformat({shell_died, Old, New}) ->
+ io_lib:format("~w Shell Process died. Restarted as ~w~n~n",[Old,New]);
+
+
+textformat(to_buffer) ->
+ "\nAppending trace log to Buffer\n\n";
+textformat(to_file) ->
+ "\nAppending trace log to File\n\n";
+textformat(cut_buffer) ->
+ "\nCUT BUFFER\n\n";
+textformat({trace, From, 'receive', Msg}) ->
+ io_lib:format("~w: rec ~s~n", [From,
+ tuple_space(Msg)]);
+textformat({trace, From, send, Msg, To}) ->
+ io_lib:format("~w: ! To: ~w Msg: ~s~n", [From,
+ To,
+ tuple_space(Msg)]);
+textformat({trace, From, call, Func}) ->
+ io_lib:format("~w: call ~s~n",[From, ffunc(Func)]);
+textformat({trace, From, spawn, Data}) ->
+ io_lib:format("~w: spawn ~p~n", [From, Data]);
+textformat({trace, From, link, Data}) ->
+ io_lib:format("~w: link ~p~n", [From, Data]);
+textformat({trace, From, unlink, Data}) ->
+ io_lib:format("~w: U-lnk ~p~n", [From, Data]);
+
+textformat({trace, From, Op, Data}) ->
+ io_lib:format("~w: ~w ~p~n", [From, Op, Data]);
+
+textformat({print, Format, Args}) ->
+ io_lib:format(Format, Args);
+textformat(Other) ->
+ io_lib:format("~p~n",[Other]).
+
+
+
+
+
+ffunc({M,F, Argl}) ->
+ io_lib:format("~w:~w(~s)", [M, F, fargs(Argl)]);
+ffunc(X) -> tuple_space(X).
+fargs([]) -> [];
+fargs([A]) -> tuple_space(A); %% last arg
+fargs([A|Args]) -> [tuple_space(A),", "|fargs(Args)].
+
+
+tuple_space(X) when is_tuple(X) -> print(size(X), X, "}");
+tuple_space(X) -> io_lib:format("~p",[X]).
+
+print(0 , _X, Buff) -> ["{"|Buff];
+print(1 , X, Buff) ->
+ Str = tuple_space(element(1, X)),
+ ["{",Str|Buff];
+print(Num, X, Buff) ->
+ Str = tuple_space(element(Num, X)),
+ print(Num-1, X, [", ",Str|Buff]).
+
+
+
+%% ----------------------------------------------------------------
+%% splits the list at element Size, returns Size, and the 2 lists
+%% If the list is not long enough, it returns {size(List),[],List}
+
+
+split([],_,Length,Buff) ->
+ {Length,[],lists:reverse(Buff)};
+split(Rest,0,Length,Buff) ->
+ {Length,Rest,lists:reverse(Buff)};
+split([L|List],Size,Length,Buff) ->
+ split(List,Size-1,Length+1,[L|Buff]).
+
+
+
diff --git a/lib/pman/src/pman_main.erl b/lib/pman/src/pman_main.erl
new file mode 100644
index 0000000000..b68da1d2c3
--- /dev/null
+++ b/lib/pman/src/pman_main.erl
@@ -0,0 +1,787 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(pman_main).
+
+%% Main process and window
+
+-export([init/2]).
+
+-record(state, {win, % GS top window
+ frame, % GS top frame
+ grid, % GS process info grid
+
+ size, % int() No. of displayed procs
+ w, % int() Window width
+ h, % int() Window height
+
+ hide_system=false, % bool() Auto-hide system procs
+ hide_new=false, % bool() Auto-hide new processes
+
+ hide_modules, % ordset() Excluded modules
+
+ hide_all=[], % [{node(), bool()}] Hide all
+ hide_pids=[], % [{node(), Ordset}] Processes
+ % explicitly to hide, per node
+ show_pids=[], % [{node(), Ordset}] Processes
+ % explicitly to show, per node
+
+ shown_pids=[], % [{node(), Ordset}] Processes
+ % actually shown, per node
+
+ node, % node() Current node
+ nodes=[], % [node()] All known nodes
+
+ focus=1, % int() Grid line with focus
+ focus_pid=undefined, % pid() | undefined Proc in focus
+
+ noshell, % bool() Noshell mode on
+
+ options}). % term() Trace options settings
+
+
+-include("pman_win.hrl").
+
+-define(REFRESH_TIME,5000).
+
+-define(REQUIRES_FOCUS, % List of menus that should
+ ['Trace Process', % be disabled if no process
+ 'Kill', % is in focus
+ 'Hide Selected Process',
+ 'Module']).
+
+%%--Process init and loop-----------------------------------------------
+
+init(PidCaller, OSModuleExcluded) ->
+ process_flag(trap_exit, true),
+
+ %% Monitor all nodes in a distributed system
+ case is_alive() of
+
+ %% We have a distributed system
+ true -> net_kernel:monitor_nodes(true);
+
+ %% No distribution
+ false -> ignore
+ end,
+ Nodes = [node()|nodes()],
+
+ %% Create the main window
+ %% For some extremely strange reason, the frame must be resized
+ %% or the grid won't be visible...
+ GridSize = length(processes()) + 61,
+ {Window, Grid, Frame, Visible, W, H} =
+ pman_win:pman_window(GridSize, OSModuleExcluded, Nodes),
+ gse:resize(Frame, ?WIN_WIDTH, ?WIN_HEIGHT-?MENU_HEIGHT),
+
+ Noshell = case pman_shell:find_shell() of
+ noshell -> true;
+ _ -> false
+ end,
+
+ State1 = #state{win=Window, frame=Frame, grid=Grid,
+ size=Visible,
+ w=W, h=H,
+ hide_modules=OSModuleExcluded,
+ node=node(),
+ noshell=Noshell},
+
+ State2 = lists:foldl(fun(Node, State) -> add_node(Node, State) end,
+ State1,
+ Nodes),
+
+ State3 = refresh(State2),
+
+ %% Notify caller that the process appears
+ %% to have been started.
+ PidCaller ! {initialization_complete, self()},
+
+ %% Initiate a 'catch all' trace pattern so call tracing works
+ erlang:trace_pattern({'_', '_', '_'}, true, [local]),
+
+ %% Read default options file
+ Options = restore_options(State3),
+
+ loop(State3#state{options=Options}).
+
+add_node(Node, State) ->
+ pman_win:add_menu(node, [Node], "Show"),
+ State#state{hide_all=nl_update(Node, false, State#state.hide_all),
+ hide_pids=nl_update(Node, [], State#state.hide_pids),
+ show_pids=nl_update(Node, [], State#state.show_pids),
+ shown_pids=nl_update(Node, [], State#state.shown_pids),
+ nodes=[Node|State#state.nodes]}.
+
+%% Restore saved options from default file
+restore_options(State)->
+ File = options_file(),
+ case pman_options:read_from_file(File) of
+ {ok, Options} ->
+ Options;
+ {error, ReasonStr, DefOptions} ->
+ Parent = State#state.win,
+ Msg = io_lib:format(
+ "Problems reading default option file~n~s:~n~s",
+ [File, ReasonStr]),
+ tool_utils:notify(Parent, Msg),
+ DefOptions
+ end.
+
+options_file() ->
+ {ok, [[Home]]} = init:get_argument(home),
+ filename:join([Home, ".erlang_tools", "pman.opts"]).
+
+loop(State) ->
+ receive
+ {nodeup, Node} ->
+ case nl_exists(Node, State#state.hide_all) of
+ true ->
+ pman_win:add_menu(node, [Node], "Show"),
+ loop(State#state{nodes=[Node|State#state.nodes]});
+ false ->
+ loop(add_node(Node, State))
+ end;
+
+ {nodedown, Node} ->
+ pman_win:remove_menu([Node]),
+
+ Msg = io_lib:format("Node~n~p~ndown.", [Node]),
+ spawn_link(tool_utils, notify, [State#state.win, Msg]),
+
+ %% We remove Node from the list of nodes but not from
+ %% the other lists of State, in case Node reappears later
+ Nodes = lists:delete(Node, State#state.nodes),
+ State2 = State#state{nodes=Nodes},
+
+ %% If it was the shown node that went down,
+ %% change overview to this node
+ if
+ Node==State#state.node ->
+ State3 = execute_cmd({node,node()}, State2, [], []),
+ loop(State3);
+ true ->
+ loop(State2)
+ end;
+
+ %% Ignore EXIT signals from help processes
+ {'EXIT', _Pid, _Reason} ->
+ loop(State);
+
+ %% GS events
+ {gs, _Obj, _Event, _Data, _Args} = Cmd ->
+ case gs_cmd(Cmd, State) of
+ stop ->
+ exit(topquit);
+ State2 ->
+ loop(State2)
+ end
+
+ after ?REFRESH_TIME ->
+ State2 = refresh(State),
+ loop(State2)
+ end.
+
+%% gs_cmd(Event, State) -> stop | State'
+gs_cmd(Event, State) ->
+ case Event of
+
+ %% --- Window manager commands ---
+
+ %% Window is moved or resized
+ {gs, _, configure, _Data, Args} ->
+ configure(Args, State);
+
+ %% Window closed, stop Pman
+ {gs, _, destroy, _, _} ->
+ stop;
+
+ %% --- Dynamic commands ---
+
+ %% Click in any object where the GS Data field is a 2-tuple
+ {gs, _, click, Data, Args} when is_tuple(Data), size(Data)==2 ->
+ execute_cmd(Data, State, [], Args);
+
+ %% Single click in the grid sets focus to selected process
+ {gs, _, click, {pidfunc,_,_}, [_,Row|_]} when is_integer(Row) ->
+ focus(Row, State);
+
+ %% Double click in the grid starts tracing of selected process
+ {gs, _, doubleclick, {pidfunc,_,_}, [_Col,Row| _]} when is_integer(Row) ->
+ execute_cmd('Trace Process', State, [], []);
+
+ %% Click in named GS objects
+ {gs, Cmd, click, Data, Args} when is_atom(Cmd);
+ is_atom(element(1, Cmd)) ->
+ execute_cmd(Cmd, State, Data, Args);
+
+ %% --- Keyboard accelerator commands ---
+
+ %% Move focus up and down
+ {gs, _, keypress, [], ['Up',_,0,0]} ->
+ execute_cmd(focus_previous, State, [], []);
+ {gs, _, keypress, [], ['Down',_,0,0]} ->
+ execute_cmd(focus_next, State, [], []);
+
+ %% Other keyboard shortcuts
+ {gs, _, keypress, [], ['Return',_,0,0]} ->
+ execute_cmd('Trace Process', State, [], []);
+ {gs, _, keypress, [], [Key,_,0,1]} ->
+ execute_cmd(shortcut(Key), State, [], []);
+
+ %% Ignore all other GS events
+ _Other ->
+ State
+ end.
+
+%% Keyboard shortcuts
+
+%% File menu
+shortcut(o) -> 'Default Options';
+shortcut(e) -> 'Exit';
+shortcut(z) -> 'Exit';
+
+%% View menu
+shortcut(i) -> 'Hide All';
+shortcut(u) -> 'Hide Modules';
+shortcut(d) -> 'Hide Selected Process';
+shortcut(m) -> 'Module';
+shortcut(r) -> 'Refresh';
+
+%% Trace menu
+shortcut(k) -> 'Kill';
+shortcut(t) -> 'Trace Process';
+shortcut(s) -> 'Trace Shell';
+
+%% Help menu
+shortcut(h) -> 'Help';
+
+%% Keyboard command only
+shortcut(l) -> 'All Links';
+
+%% Process grid traversal
+shortcut(p) -> focus_previous;
+shortcut(n) -> focus_next;
+shortcut(_) -> dummy.
+
+%% configure([W,H,X,Y|_], State) -> State'
+%% Window has been moved or resized
+configure([W,H|_], State) ->
+ if
+ W==State#state.w, H==State#state.h ->
+ ignore;
+
+ true ->
+ gse:resize(State#state.frame, W, H-?MENU_HEIGHT),
+
+ Grid = State#state.grid,
+ case abs(W - gs:read(Grid,width) - 6) of
+ 0 ->
+ ok; %% Avoid refreshing width if possible
+ _Anything ->
+ Cols = pman_win:calc_columnwidths(W-6),
+ gs:config(Grid, Cols)
+ end,
+ pman_win:configwin(Grid, W, H)
+ end,
+ State.
+
+%% focus(Row, State) -> State'
+%% Row = int() Grid row
+%% User has selected a row in the grid.
+%% Row==1 means header row.
+focus(Row, State) ->
+
+ Pid = case get_pid_in_focus(Row, State#state.grid) of
+ {true, {pidfunc,Pid0,_}} ->
+ pman_win:change_colour(State#state.grid,
+ State#state.focus, Row),
+ enable_pid_actions(),
+ Pid0;
+ false ->
+ disable_pid_actions(),
+ undefined
+ end,
+
+ State#state{focus=Row, focus_pid=Pid}.
+
+%% get_pid_in_focus(Row, Grid) -> {true, Data} | false
+%% Data = {pidfunc, Pid, Func}
+%% Func = {Mod,Name,Arity} | term()
+%% Return the data associated with the process in focus if there is one,
+get_pid_in_focus(1, _Grid) ->
+ false;
+get_pid_in_focus(Row, Grid) ->
+ case gs:read(Grid, {obj_at_row,Row}) of
+ undefined -> false;
+ GridLine ->
+ Data = gs:read(GridLine, data),
+ {true, Data}
+ end.
+
+%% execute_cmd(Cmd, State, Data, Args) -> stop | State'
+
+%% Checkbutton "Hide System Processes"
+execute_cmd('Hide System', State, _Data, Args) ->
+ [_Text, _Group, Bool|_Rest] = Args,
+ State2 = State#state{hide_system=Bool},
+ refresh(State2);
+
+%% Checkbutton "Auto-Hide New"
+execute_cmd('Auto Hide New', State, _Data, Args ) ->
+ [_Text, _Group, Bool|_Rest] = Args,
+ refresh(State#state{hide_new=Bool});
+
+%% File->Options...
+execute_cmd('Default Options', State, _Data, _Args) ->
+ OldOptions = State#state.options,
+ NewOptions = pman_options:dialog(State#state.win,
+ "Default Trace Options",
+ OldOptions),
+ case NewOptions of
+ {error, _Reason} ->
+ State;
+ Options ->
+ State#state{options=Options}
+ end;
+
+%% File->Save Options
+%% Save the set default options to the user's option file
+execute_cmd('Save Options', State, _Data, _Args)->
+ Options = State#state.options,
+ File = options_file(),
+ Parent = State#state.win,
+
+ case pman_options:save_to_file(Options, File) of
+ ok ->
+ tool_utils:notify(Parent, "Options saved to\n" ++ File);
+ {error, ReasonStr} ->
+ Msg = io_lib:format("Could not save options to~n~s:~n~s",
+ [File, ReasonStr]),
+ tool_utils:notify(Parent, Msg)
+ end,
+ State;
+
+%% File->Exit
+%% Exit the application
+execute_cmd('Exit', _State, _Data, _Args) ->
+ stop;
+
+%% View->Hide All Processes
+execute_cmd('Hide All', State, _Data, _Args) ->
+ Node = State#state.node,
+ HideAll = nl_update(Node, true, State#state.hide_all),
+ ShowPids = nl_del_all(State#state.node, State#state.show_pids),
+ State2 = State#state{hide_all=HideAll, show_pids=ShowPids},
+ refresh(State2, true);
+
+%% View->Hide modules...
+%% Opens a dialog where the user can select from a list of
+%% the loaded modules.
+%% The selected module is added to the list of hidden modules.
+execute_cmd('Hide Modules', State, _Data, _Args) ->
+
+ %% Get all loaded modules that are not already hidden
+ AllModules = lists:map(fun({Module, _File}) -> Module end,
+ code:all_loaded()),
+ ModulesSet = ordsets:subtract(ordsets:from_list(AllModules),
+ State#state.hide_modules),
+
+ %% Let the user select which of the loaded modules to exclude from
+ %% the process overview
+ Title = "Module selection",
+ case pman_tool:select(State#state.win, Title, ModulesSet) of
+ Modules when is_list(Modules) ->
+ HideModules = ordsets:union(State#state.hide_modules,
+ ordsets:from_list(Modules)),
+ refresh(State#state{hide_modules=HideModules});
+ cancelled -> State
+ end;
+
+%% View->Hide Selected Process
+%% The process in focus should explicitly be hidden
+execute_cmd('Hide Selected Process', State, _Data, _Args) ->
+ case State#state.focus_pid of
+ undefined -> State;
+ Pid ->
+ Node = State#state.node,
+ HidePids = nl_add(Node, Pid, State#state.hide_pids),
+ ShowPids = nl_del(Node, Pid, State#state.show_pids),
+ refresh(State#state{hide_pids=HidePids, show_pids=ShowPids})
+ end;
+
+%% View->Module Info...
+%% Open window with module information.
+execute_cmd('Module', State, _Data, _Args) ->
+ case get_pid_in_focus(State#state.focus, State#state.grid) of
+ {true, {pidfunc, _Pid, {Module,_Name,_Arity}}} ->
+ pman_module_info:start(Module);
+ _ -> % false | {true, {pidfunc, Pid, Other}}
+ ignore
+ end,
+ State;
+
+%% View->Refresh
+%% Refresh the main window.
+%% (Called automatically every ?REFRESH_TIME millisecond)
+execute_cmd('Refresh', State, _Data, _Args) ->
+ refresh(State);
+
+%% View->Show All Processes
+%% Makes all processes visible except system processes and new
+%% processes, if those buttons are checked.
+%% Note: Also un-hides all hidden modules!
+execute_cmd('Show All', State, _Data, _Args) ->
+ Node = State#state.node,
+ HideAll = nl_update(Node, false, State#state.hide_all),
+ HidePids = nl_del_all(State#state.node, State#state.hide_pids),
+ ShowPids = nl_del_all(State#state.node, State#state.show_pids),
+ State2 = State#state{hide_modules=ordsets:new(), hide_all=HideAll,
+ hide_pids=HidePids, show_pids=ShowPids},
+ refresh(State2, true);
+
+%% View->Show Processes...
+%% Open a list of all hidden processes, if the user selects one this
+%% process should explicitly be shown
+execute_cmd('Show Selected', State, _Data, _Args) ->
+ Node = State#state.node,
+
+ All = pman_process:r_processes(Node),
+ Hidden = case nl_lookup(Node, State#state.hide_all) of
+ true ->
+ All;
+ false ->
+ Shown = nl_lookup(Node, State#state.shown_pids),
+ ordsets:subtract(All, Shown)
+ end,
+
+ %% Selection window
+ Title = "Select Processes to Show",
+ Tuples =
+ lists:map(fun(Pid) ->
+ {M,F,A} = pman_process:function_info(Pid),
+ Str = case pman_process:get_name(Pid) of
+ " " ->
+ io_lib:format("~p:~p/~p",
+ [M, F, A]);
+ Name ->
+ io_lib:format("[~p] ~p:~p/~p",
+ [Name, M, F, A])
+ end,
+ {Pid, Str}
+ end,
+ Hidden),
+ case pman_tool:select(State#state.win, Title, Tuples) of
+ Pids when is_list(Pids) ->
+ HidePids = nl_del(Node, Pids, State#state.hide_pids),
+ ShowPids = nl_add(Node, Pids, State#state.show_pids),
+ refresh(State#state{hide_pids=HidePids,show_pids=ShowPids});
+ cancelled -> State
+ end;
+
+%% Trace->Kill
+execute_cmd('Kill', State, _Data, _Args) ->
+ case State#state.focus_pid of
+ Pid when is_pid(Pid) ->
+ exit(Pid, kill);
+ undefined ->
+ ignore
+ end,
+ State;
+
+%% Trace->Selected Process
+execute_cmd('Trace Process', State, _Data, _Args) ->
+ case State#state.focus_pid of
+ Pid when is_pid(Pid) ->
+ pman_shell:start({Pid,self()}, State#state.options);
+ undefined ->
+ ignore
+ end,
+ State;
+
+%% Trace->Shell Process
+execute_cmd('Trace Shell', State, _Data, _Args) ->
+ case pman_shell:find_shell() of
+ noshell ->
+ State;
+ Shell ->
+ pman_shell:start({{shell,Shell},self()},
+ State#state.options),
+ State#state{noshell=false}
+ end;
+
+%% Nodes->Show <Node>
+%% Change shown node
+execute_cmd({node,Node}, State, _Data, _Args) ->
+ gse:config(State#state.win,
+ [{title,lists:concat(["Pman: Overview on ", Node])}]),
+ gse:disable(Node),
+ catch gse:enable(State#state.node), % Menu may not exist any more
+ refresh(State#state{node=Node}, true);
+
+%% Help->Help
+execute_cmd('Help', State, _Data, _Args) ->
+ Win = State#state.win,
+ HelpFile =
+ filename:join([code:lib_dir(pman),"doc","html","index.html"]),
+ tool_utils:open_help(Win, HelpFile),
+ State;
+
+%% Keyboard shortcut Ctrl-l
+execute_cmd('All Links', State, _Data, _Args) ->
+ case State#state.focus_pid of
+ Pid when is_pid(Pid) ->
+ case process_info(Pid, links) of
+ {links, Pids} ->
+ pman_shell:start_list(Pids, self(),
+ State#state.options);
+ undefined ->
+ ignore
+ end;
+ undefined -> ignore
+ end,
+ State;
+
+%% Keyboard shortcuts for process grid traversal
+execute_cmd(focus_previous, State, _Data, _Args) ->
+ focus(previous_row(State), State);
+execute_cmd(focus_next, State, _Data, _Args) ->
+ focus(next_row(State), State);
+
+%% Keyboard combinations that are not shortcuts
+execute_cmd(dummy, State, _Data, _Args) ->
+ State.
+
+%% Convenience functions for disabling/enabling menu items that require
+%% that a process is selected.
+disable_pid_actions() ->
+ lists:foreach(fun(X) -> gse:disable(X) end, ?REQUIRES_FOCUS).
+
+enable_pid_actions() ->
+ lists:foreach(fun(X) -> gse:enable(X) end, ?REQUIRES_FOCUS).
+
+%% refresh(State) -> State'
+%% refresh(State, ForceP) -> State'
+%% Refreshes the main window.
+refresh(State) ->
+ refresh(State, false).
+refresh(#state{node=Node} = State, ForceP) ->
+
+ %% Update shown processes
+
+ %% First, get an ordset of all processes running at the current node
+ All = pman_process:r_processes(Node),
+
+ Shown = nl_lookup(Node, State#state.shown_pids),
+ ExpShown = nl_lookup(Node, State#state.show_pids),
+
+ {Show, State2} =
+ case nl_lookup(Node, State#state.hide_all) of
+
+ %% If the user has selected "Hide All Processes", only
+ %% explicitly selected processes which still exist should
+ %% be shown
+ true ->
+ {ordsets:intersection(ExpShown, All), State};
+
+ false ->
+ %% Compute which processes should be hidden according
+ %% to the flags/menu items selected
+ Hidden = hidden_pids(All, State),
+
+ NotHidden = ordsets:subtract(All, Hidden),
+
+ Show0 = case State#state.hide_new of
+ %% If the user has selected "Auto-Hide New",
+ %% then only those processes in NotHidden
+ %% which are already shown, should be shown,
+ %% together with explicitly selected
+ %% processes which still exist
+ true ->
+ ordsets:union(
+ ordsets:intersection(NotHidden,Shown),
+ ordsets:intersection(ExpShown, All));
+
+ %% Otherwise, show all processes in
+ %% NotHidden, together with explicitly
+ %% selected processes which still exist
+ false ->
+ ordsets:union(
+ NotHidden,
+ ordsets:intersection(ExpShown, All))
+ end,
+
+ ShownPids = nl_update(Node, Show0,
+ State#state.shown_pids),
+ {Show0, State#state{shown_pids=ShownPids}}
+ end,
+
+ NoOfHidden = length(All) - length(Show),
+
+ if
+ Show==Shown, not ForceP ->
+ pman_win:update(NoOfHidden),
+ State;
+
+ true ->
+ ShowInfo = display_info(Show),
+ pman_win:update(State#state.grid, ShowInfo, NoOfHidden),
+
+ %% Set the focus appropriately
+ State3 = case State2#state.focus_pid of
+ undefined ->
+ disable_pid_actions(),
+ State2;
+ Pid ->
+ Row = get_row(Pid, Show),
+ focus(Row, State2)
+ end,
+
+ trace_shell_possible(State3),
+
+ Size = length(Show),
+ case Size of
+ 1 -> gse:disable('Hide All');
+ _ -> gse:enable('Hide All')
+ end,
+
+ State3#state{size=Size}
+ end.
+
+%% hidden_pids(All, State) -> Hidden
+hidden_pids(All, State) ->
+
+ %% Processes hidden because they are system processes
+ HideSys = case State#state.hide_system of
+ true ->
+ lists:filter(
+ fun(Pid) ->
+ pman_process:is_system_process(Pid)
+ end,
+ All);
+ false ->
+ []
+ end,
+
+ %% Process hidden because they are executing code in a hidden module
+ Mods = State#state.hide_modules,
+ HideMod =
+ lists:filter(fun(Pid) ->
+ pman_process:is_hidden_by_module(Pid, Mods)
+ end,
+ All),
+
+ %% Explicitly hidden processes
+ HideExp = nl_lookup(State#state.node, State#state.hide_pids),
+
+ %% All hidden processes
+ ordsets:union([HideSys, HideMod, HideExp]).
+
+display_info(Pids) ->
+ lists:map(fun(Pid) ->
+ Func = pman_process:function_info(Pid),
+ Name = pman_process:get_name(Pid),
+ Msgs = pman_process:msg(Pid),
+ Reds = pman_process:reds(Pid),
+ Size = pman_process:psize(Pid),
+ {Pid, Func, Name, Msgs, Reds, Size}
+ end,
+ Pids).
+
+get_row(Pid, List) ->
+ get_row(Pid, List, length(List)+1).
+
+get_row(Pid, [Pid | _], Row) ->
+ Row;
+get_row(Pid, [_ | T], Row) ->
+ get_row(Pid, T, Row-1);
+get_row(_Pid, [], _Row) ->
+ 1.
+
+next_row(#state{size=Size, focus=Row}) ->
+ check_row(Row+1, Size).
+
+previous_row(#state{size=Size, focus=Row}) ->
+ check_row(Row-1, Size).
+
+check_row(1, Size) ->
+ Size+1;
+check_row(Row, Size) when Row==Size+2 ->
+ 2;
+check_row(Row, _Size) ->
+ Row.
+
+%% Check if node is running in noshell mode and if so disable the
+%% 'Trace Shell' menu option.
+trace_shell_possible(#state{noshell=true}) ->
+ gse:disable('Trace Shell');
+trace_shell_possible(_) ->
+ ok.
+
+%% -- Functions for manipulating {Node, Data} lists --
+
+%% nl_add(Node, Elem|Elems, NList) -> NList'
+nl_add(Node, Elems, [{Node, Ordset} | T]) when is_list(Elems) ->
+ [{Node, ordsets:union(Elems, Ordset)} | T];
+nl_add(Node, Elem, [{Node, Ordset} | T]) ->
+ [{Node, ordsets:add_element(Elem, Ordset)} | T];
+nl_add(Node, Elem, [H | T]) ->
+ [H | nl_add(Node, Elem, T)];
+nl_add(Node, Elems, []) when is_list(Elems) ->
+ [{Node, Elems}];
+nl_add(Node, Elem, []) ->
+ [{Node, ordsets:add_element(Elem, ordsets:new())}].
+
+%% nl_del(Node, Elem|Elems, NList) -> NList'
+nl_del(Node, Elems, [{Node, Ordset} | T]) when is_list(Elems) ->
+ [{Node, ordsets:subtract(Ordset, Elems)} | T];
+nl_del(Node, Elem, [{Node, Ordset} | T]) ->
+ [{Node, ordsets:del_element(Elem, Ordset)} | T];
+nl_del(Node, Elem, [H | T]) ->
+ [H | nl_del(Node, Elem, T)];
+nl_del(_Node, _Elem, []) ->
+ [].
+
+%% nl_del_all(Node, NList) -> NList'
+nl_del_all(Node, [{Node, _Ordset} | T]) ->
+ [{Node, ordsets:new()} | T];
+nl_del_all(Node, [H | T]) ->
+ [H | nl_del_all(Node, T)];
+nl_del_all(_Node, []) ->
+ [].
+
+%% nl_update(Node, Val, NList) -> NList'
+nl_update(Node, Val, [{Node, _OldVal} | T]) ->
+ [{Node, Val} | T];
+nl_update(Node, Val, [H | T]) ->
+ [H | nl_update(Node, Val, T)];
+nl_update(Node, Val, []) ->
+ [{Node, Val}].
+
+%% nl_lookup(Node, NList) -> Val
+nl_lookup(Node, NList) ->
+ {value, {_Node,Val}} = lists:keysearch(Node, 1, NList),
+ Val.
+
+%% nl_exists(Node, NList) -> bool()
+nl_exists(Node, NList) ->
+ case lists:keysearch(Node, 1, NList) of
+ {value, _Val} ->
+ true;
+ false ->
+ false
+ end.
diff --git a/lib/pman/src/pman_module_info.erl b/lib/pman/src/pman_module_info.erl
new file mode 100644
index 0000000000..cfd711a6e1
--- /dev/null
+++ b/lib/pman/src/pman_module_info.erl
@@ -0,0 +1,131 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(pman_module_info).
+
+%% Window with module information (View->Module Info...)
+
+%% External exports
+-export([start/1]).
+
+%% Record for keeping the loop state for the
+%% module info process.
+-record(state, {topwin, % GS identifier for top window
+ editor, % GS identifier for editor
+ module, % Name of the viewed module
+ parent}). % Pid of the parent
+
+start(Module) ->
+ Self = self(),
+ spawn_link(fun() -> init(Module, Self) end).
+
+init(Module, Parent) ->
+ process_flag(trap_exit, true),
+
+ GS = gs:start([{kernel,true}]),
+ Font = pman_win:font(GS),
+
+ WinTitle = lists:flatten(io_lib:format("Pman - Module Info: ~p",
+ [Module])),
+ WinOptions = [{title,WinTitle}, {width,550}, {height, 400},
+ {configure,true}, {keypress,true}, {destroy,true}],
+ TopWindow = gse:window(GS, WinOptions),
+
+ %% File menu
+ MenuBar = gse:menubar(TopWindow, []),
+ MBFile = gse:menubutton(MenuBar, [{label,{text," File "}},
+ {font,Font}, {underline, 1}]),
+ MenuFile = gse:menu(MBFile, []),
+
+ gse:named_menuitem('Save buffer', MenuFile,
+ [{label,{text,"Save buffer..."}},
+ {font,Font}, {underline,0}]),
+ gse:named_menuitem('Close', MenuFile,
+ [{label,{text,"Close"}},
+ {font,Font}, {underline,0}]),
+
+ %% Output part of window
+ Editor = gse:editor(TopWindow,
+ [{font,Font},
+ {x,3}, {y,40}, {width,546}, {height,348}]),
+ gse:config(Editor, [{keypress,true},
+ {insert,{'end',pman_win:module_data(Module)}}]),
+ gse:config(Editor, [{enable,false},
+ {vscroll,right}, {hscroll,bottom},
+ {wrap,none}]),
+ gse:map(TopWindow),
+
+ State = #state{topwin=TopWindow, editor=Editor, module=Module,
+ parent=Parent},
+ loop(State).
+
+loop(State) ->
+
+ receive
+ %% Die if the parent dies
+ {'EXIT', Pid, _Reason} when Pid==State#state.parent ->
+ gse:destroy(State#state.topwin);
+
+ %% Ignore other exit signals (from file dialog window)
+ {'EXIT', _Pid, _Reason} ->
+ loop(State);
+
+ %% Window closed
+ {gs, _TopWindow, destroy, [], []} ->
+ ok;
+
+ %% Window resized or moved
+ {gs, _TopWindow, configure ,_Data, [W,H,_X,_Y|_]} ->
+ gs:config(State#state.editor, [{width,W-3}, {height,H-40}]),
+ loop(State);
+
+ %% Close - destroy window and exit process
+ {gs, 'Close', click, _Data, _Args} ->
+ gse:destroy(State#state.topwin),
+ ok;
+
+ %% Save Buffer - make filename and save buffer to file
+ {gs, 'Save buffer', click, _Data, _Args} ->
+ save_buffer(State),
+ loop(State);
+
+ %% Keyboard accelerator commands
+ {gs, _, keypress, [], [c,_,0,1]} -> % 'Close'
+ gse:destroy(State#state.topwin),
+ ok;
+ {gs, _, keypress, [], [s,_,0,1]} -> % 'Save buffer'
+ save_buffer(State),
+ loop(State);
+ {gs, _, keypress, _Data, _Args} ->
+ loop(State)
+ end.
+
+save_buffer(State) ->
+ DefaultFile = atom_to_list(State#state.module) ++ ".module_info",
+ Result = tool_utils:file_dialog([{type,save}, {file,DefaultFile}]),
+ case Result of
+ %% User selected a file, now save the result
+ {ok, File, _Dir} ->
+ gs:config(State#state.editor, {save,File}),
+ Msg = "Module information saved in file\n" ++ File,
+ tool_utils:notify(State#state.topwin, Msg);
+
+ %% File dialog was cancelled in some way.
+ {error, _Reason} ->
+ ignore
+ end.
diff --git a/lib/pman/src/pman_options.erl b/lib/pman/src/pman_options.erl
new file mode 100644
index 0000000000..0765458fdc
--- /dev/null
+++ b/lib/pman/src/pman_options.erl
@@ -0,0 +1,395 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(pman_options).
+
+%% Window with trace options settings (File->Options...)
+
+-export([dialog/3,
+ read_from_file/1, save_to_file/2]).
+
+-include("pman_options.hrl").
+
+-define(WIN_WIDTH, 350).
+-define(WIN_HEIGHT, 350).
+
+-define(TOP_WINDOW, xx_pman_option_window_xx).
+-define(TOP_FRAME, xx_pman_top_frame_xx).
+
+-record(state, {resize_frame, % GS identifier for the main frame
+ parent}). % Pid of parent
+
+%%--dialog/3------------------------------------------------------------
+%% Create a window, or return a value indicating that is is already
+%% created.
+
+dialog(ParentWin, Title, Options) ->
+ Self = self(),
+ Pid = spawn(fun() -> dialog(Self, ParentWin, Title, Options) end),
+ receive
+ {Pid, Value} ->
+ Value % Options2 | {error,destroyed} | {error,cancelled}
+ end.
+
+dialog(Parent, ParentWin, Title, Options) ->
+
+ %% Check if the dialog has already been created, in that
+ %% case, we can reuse it. Otherwise a new dialog is created.
+ case gse:name_occupied(?TOP_WINDOW) of
+ false -> make_window(ParentWin, Title);
+ true -> ok
+ end,
+
+ %% Window has now been created or may be re-used
+ update_window_from_options(Options),
+
+ gse:resize(?TOP_FRAME, ?WIN_WIDTH, ?WIN_HEIGHT),
+ gse:map(?TOP_WINDOW),
+
+ loop(#state{resize_frame=?TOP_FRAME, parent=Parent}).
+
+loop(State) ->
+ receive
+ {gs, _Id, destroy, _Data, _Arg} ->
+ State#state.parent ! {self(), {error,destroyed}};
+
+ {gs, ?TOP_WINDOW, configure, _Data, [W, H |_]} ->
+ gse:config(State#state.resize_frame,
+ [{width,W},{height,H}]), % repack
+ loop(State);
+
+ {gs, ok_button, click, _Data, _Arg} ->
+ Options = get_options_from_window(),
+ gse:unmap(?TOP_WINDOW),
+ State#state.parent ! {self(), Options};
+
+ {gs, cancel_button, click, _Data, _Arg} ->
+ gse:unmap(?TOP_WINDOW),
+ State#state.parent ! {self(), {error,cancelled}};
+
+ {gs, trace_spawn, click, _Data, [_Text,_,Value]} ->
+ group_radio(Value, trace_spawn_all, [trace_spawn_all,
+ trace_spawn_first]),
+ loop(State);
+
+ {gs, trace_link, click, _Data, [_Text,_,Value]} ->
+ group_radio(Value, trace_link_all, [trace_link_all,
+ trace_link_first]),
+ loop(State);
+
+ {gs, trace_in_window, click, _Data, _Arg} ->
+ lists:foreach(fun(X) -> gse:disable(X) end,
+ [trace_file, trace_file_browse]),
+ loop(State);
+
+ {gs, trace_to_file, click, _Data, [_Text,_,_Value]} ->
+ lists:foreach(fun(X) -> gse:enable(X) end,
+ [trace_file, trace_file_browse]),
+ loop(State);
+
+ {gs, trace_file_browse, click, _Data, _Arg} ->
+ Result = tool_utils:file_dialog([{type,save},
+ {file, "Untitled.log"}]),
+ case Result of
+ {error, _Reason} ->
+ loop(State);
+ {ok, Name,_State} ->
+ gse:config(trace_file, [{text, Name}]),
+ loop(State)
+ end
+ end.
+
+-define(LBLOPTS, [{justify,left}, {align,w}]).
+-define(BTNOPTS, [{justify,left}, {align,w}]).
+
+make_window(ParentWin, Title) ->
+
+ Font = pman_win:font(),
+
+ gse:named_window(?TOP_WINDOW, ParentWin, [{title,Title},
+ {configure,true},
+ {width, ?WIN_WIDTH},
+ {height, ?WIN_HEIGHT}]),
+
+ gse:named_frame(?TOP_FRAME, ?TOP_WINDOW,
+ [{bw,3},
+ {packer_x,[{stretch,1,175}, {stretch,1,175}]},
+ {packer_y,[{stretch,3},{stretch,2},{stretch,1}]}]),
+
+ F11 = gse:frame(?TOP_FRAME, [{bw,3},
+ {pack_xy,{1,1}},
+ {packer_x,[{stretch,1},
+ {stretch,20},
+ {stretch,2}]},
+ {packer_y,[{stretch,2},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1}]}]),
+
+ gse:label(F11,[{pack_xy,{2,1}},
+ {label,{text,"Trace output options:"}},
+ {font,Font} | ?LBLOPTS]),
+
+ gse:named_checkbutton(trace_send, F11,
+ [{pack_xy,{2,2}},
+ {label,{text,"Trace send"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_checkbutton(trace_receive, F11,
+ [{pack_xy,{2,3}},
+ {label,{text, "Trace receive"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_checkbutton(trace_functions,F11,
+ [{pack_xy,{2,4}},
+ {label,{text, "Trace functions"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_checkbutton(trace_events,F11,
+ [{pack_xy,{2,5}},
+ {label,{text, "Trace events"}},
+ {font,Font} | ?BTNOPTS]),
+
+ F21 = gse:frame(?TOP_FRAME, [{bw,3},
+ {pack_xy,{2,1}},
+ {packer_x,[{stretch,1},
+ {stretch,2},
+ {stretch,2},
+ {stretch,20},
+ {stretch,1}]},
+ {packer_y,[{stretch,2},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1}]}]),
+
+ gse:label(F21, [{pack_xy,{{2,4},1}},
+ {label,{text,"Inheritance options:"}},
+ {font,Font} | ?LBLOPTS]),
+
+ gse:named_checkbutton(trace_spawn, F21,
+ [{pack_xy,{{2,4},2}},
+ {data,trace_send},
+ {label,{text,"Inherit on spawn"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_radiobutton(trace_spawn_all, F21,
+ [{pack_xy,{{3,4},3}},
+ {group,spawn},
+ {data,trace_receive},
+ {label,{text, "All spawns"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_radiobutton(trace_spawn_first, F21,
+ [{pack_xy,{{3,4},4}},
+ {group,spawn},
+ {data,trace_receive},
+ {label,{text,"First spawn only"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_checkbutton(trace_link, F21,
+ [{pack_xy,{{2,4},6}},
+ {data,trace_send},
+ {label,{text,"Inherit on link"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_radiobutton(trace_link_all, F21,
+ [{pack_xy,{{3,4},7}},
+ {group,link},
+ {data,trace_receive},
+ {label,{text,"All links"}},
+ {font,Font} | ?BTNOPTS]),
+
+ gse:named_radiobutton(trace_link_first, F21,
+ [{pack_xy,{{3,4},8}},
+ {group,link},
+ {data,trace_receive},
+ {label,{text,"First link only"}},
+ {font,Font} | ?BTNOPTS]),
+
+ F12 = gse:frame(?TOP_FRAME, [{bw,3},
+ {pack_xy,{{1,2},2}},
+ {packer_x,[{stretch,1},
+ {stretch,5}, % Label
+ {stretch,1},
+ {stretch,10}, % Field
+ {stretch,1},
+ {stretch,5}, % Button
+ {stretch,1}]},
+ {packer_y,[{stretch,2},
+ {stretch,1},
+ {stretch,1},
+ {stretch,1}]}]),
+
+ gse:label(F12, [{pack_xy,{{2,6},1}},
+ {label,{text,"Trace output options:"}},
+ {font,Font} | ?LBLOPTS]),
+ gse:named_radiobutton(trace_in_window, F12,
+ [{pack_xy,{{2,6},2}},
+ {group, trace_dest},
+ {label,{text,"In window"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_radiobutton(trace_to_file, F12,
+ [{pack_xy,{2,3}},
+ {group, trace_dest},
+ {label,{text,"To file"}},
+ {font,Font} | ?BTNOPTS]),
+ gse:named_entry(trace_file, F12, [{pack_xy,{4,3}}, {font,Font}]),
+ gse:named_button(trace_file_browse, F12,
+ [{pack_xy,{6,3}},
+ {label,{text," Browse..."}},
+ {font,Font} | ?BTNOPTS]),
+
+ F13 = gse:frame(?TOP_FRAME, [{bw,3},
+ {pack_xy,{{1,2},3}},
+ {packer_x,[{stretch, 1},
+ {fixed, 60},
+ {stretch, 1},
+ {fixed, 60},
+ {stretch, 1}]},
+ {packer_y,[{stretch,1},
+ {fixed, 30},
+ {stretch,1}]}]),
+
+ gse:named_button(ok_button, F13, [{pack_xy,{2,2}},
+ {label,{text,"OK"}},
+ {font,Font}]),
+ gse:named_button(cancel_button, F13, [{pack_xy,{4,2}},
+ {label,{text,"Cancel"}},
+ {font,Font}]).
+
+update_window_from_options(Options) ->
+
+ %% Trace output
+ gse:config(trace_send, [{select,Options#trace_options.send}]),
+ gse:config(trace_receive,
+ [{select,Options#trace_options.treceive}]),
+ gse:config(trace_functions,
+ [{select,Options#trace_options.functions}]),
+ gse:config(trace_events, [{select,Options#trace_options.events}]),
+
+ %% Trace inheritance
+ case (Options#trace_options.inherit_on_all_spawn or
+ Options#trace_options.inherit_on_1st_spawn) of
+ true ->
+ gse:select(trace_spawn),
+ gse:config(trace_spawn_all,
+ [{select,Options#trace_options.inherit_on_all_spawn}]),
+ gse:config(trace_spawn_first,
+ [{select,Options#trace_options.inherit_on_1st_spawn}]);
+ false ->
+ lists:foreach(fun(X) -> gse:disable(X) end,
+ [trace_spawn_all,trace_spawn_first])
+ end,
+
+ case (Options#trace_options.inherit_on_all_link or
+ Options#trace_options.inherit_on_1st_link) of
+ true -> gse:select(trace_link),
+ gse:config(trace_link_all,
+ [{select,Options#trace_options.inherit_on_all_link}]),
+ gse:config(trace_link_first,
+ [{select, Options#trace_options.inherit_on_1st_link}]);
+ false ->
+ lists:foreach(fun(X) -> gse:disable(X) end,
+ [trace_link_all,trace_link_first])
+ end,
+
+ %% Trace ouput destinations
+ gse:config(trace_in_window,
+ [{select,(not Options#trace_options.to_file)}]),
+
+ gse:config(trace_to_file, [{select,Options#trace_options.to_file}]),
+ gse:config(trace_file, [{text,Options#trace_options.file}]),
+ case Options#trace_options.to_file of
+ true ->
+ ok;
+ false ->
+ lists:foreach(fun(X) -> gse:disable(X) end,
+ [trace_file, trace_file_browse])
+ end.
+
+get_options_from_window() ->
+ #trace_options{send = gse:read(trace_send,select),
+ treceive = gse:read(trace_receive,select),
+ functions = gse:read(trace_functions,select),
+ events = gse:read(trace_events,select),
+ inherit_on_1st_spawn = gse:read(trace_spawn_first,select),
+ inherit_on_all_spawn = gse:read(trace_spawn_all,select),
+ inherit_on_1st_link = gse:read(trace_link_first,select),
+ inherit_on_all_link = gse:read(trace_link_all,select),
+ to_file = gse:read(trace_to_file,select),
+ file = gse:read(trace_file,text)}.
+
+group_radio(Value, Default, GroupList) ->
+ case Value of
+ true ->
+ gse:select(Default),
+ lists:foreach(fun(X) -> gse:enable(X) end, GroupList);
+ false ->
+ lists:foreach(fun(X) -> gse:deselect(X) end, GroupList),
+ lists:foreach(fun(X) -> gse:disable(X) end, GroupList)
+ end.
+
+%%--read_from_file/(File)-----------------------------------------------
+%% Returns the options saved in File.
+%% If no options can be found, then the default options are
+%% returned.
+
+read_from_file(File) ->
+ case file:consult(File) of
+ {ok, [Term]} ->
+ if
+ is_record(Term, trace_options) ->
+ {ok, Term};
+ true ->
+ {error, "unexpected contents", #trace_options{}}
+ end;
+ {ok, _Terms} ->
+ {error, "unexpected contents", #trace_options{}};
+ {error, Tuple} when is_tuple(Tuple) -> % {Line,Mod,Term}
+ {error, "erroneous contents", #trace_options{}};
+ {error, _Posix} ->
+ %% The most probable reason is that the file does not
+ %% exist, this is not an error so we simply return
+ %% the default trace options instead
+ {ok, #trace_options{}}
+ end.
+
+%%--save_to_file(Options, File)-----------------------------------------
+
+save_to_file(Options, File) ->
+ case file:open(File, [write]) of
+ {ok, Fd} ->
+ {{Year,Month,Day},{H,M,S}} = calendar:local_time(),
+ io:format(Fd, "%%%~n", []),
+ io:format(Fd, "%%% File: ~s~n", [File]),
+ io:format(Fd, "%%% Date: ~w-~2..0w-~2..0w, ~2..0w:~2..0w:~2..0w~n",
+ [Year,Month,Day,H,M,S]),
+ io:format(Fd, "%%%~n", []),
+ io:format(Fd, "%%% This file was created by Pman. ~n", []),
+ io:format(Fd, "%%%~n", []),
+ io:format(Fd, "%%% DO NOT EDIT! ~n", []),
+ io:format(Fd, "%%%~n", []),
+ io:format(Fd, "%%%~n", []),
+ io:format(Fd, "~p.~n", [Options]),
+ file:close(Fd),
+ ok;
+ {error, Posix} ->
+ {error, file:format_error(Posix)}
+ end.
diff --git a/lib/pman/src/pman_options.hrl b/lib/pman/src/pman_options.hrl
new file mode 100644
index 0000000000..047b9866c3
--- /dev/null
+++ b/lib/pman/src/pman_options.hrl
@@ -0,0 +1,34 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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%
+%%
+
+%%
+%% An options-record contains the return data from the option dialog.
+%%
+
+-record(trace_options, {send=true,
+ treceive=true,
+ functions=true,
+ events=true,
+ to_file=false,
+ file="",
+ inherit_on_1st_spawn=false,
+ inherit_on_all_spawn=true,
+ inherit_on_1st_link=false,
+ inherit_on_all_link=true}).
+
diff --git a/lib/pman/src/pman_process.erl b/lib/pman/src/pman_process.erl
new file mode 100644
index 0000000000..276407a0f1
--- /dev/null
+++ b/lib/pman/src/pman_process.erl
@@ -0,0 +1,317 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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 : A front-end to the erlang:process_info() functions, that
+%% can handle processes on different nodes in a transparent
+%% way.
+%% Also some convenience functions for process info, as well
+%% as some application specific functions for process
+%% classification.
+%%----------------------------------------------------------------------
+
+-module(pman_process).
+
+-export([pinfo/1, pinfo/2,
+ r_processes/1,
+ function_info/1,
+ get_name/1, msg/1, reds/1, psize/1,
+ is_running/1,
+ is_pid_or_shell/1,
+ get_pid/1,
+ is_system_process/1,
+ is_hidden_by_module/2
+ ]).
+
+%% List of registered name that will make a prodcess a "SYSTEM"-process
+-define(SYSTEM_REG_NAMES,
+ [
+ %% kernel
+ application_controller,
+ erl_reply,
+ auth,
+ boot_server,
+ code_server,
+ disk_log_server,
+ disk_log_sup,
+ erl_prim_loader,
+ error_logger,
+ file_server_2,
+ fixtable_server,
+ global_group,
+ global_name_server,
+ heart,
+ inet_gethost_native,
+ inet_gethost_native_sup,
+ init,
+ kernel_config,
+ kernel_safe_sup,
+ kernel_sup,
+ net_kernel,
+ net_sup,
+ rex,
+ user,
+ os_server,
+ ddll_server,
+ erl_epmd,
+ inet_db,
+ pg2,
+
+ %% stdlib
+ timer_server,
+ rsh_starter,
+ take_over_monitor,
+ pool_master,
+ dets,
+
+ %% sasl
+ sasl_safe_sup, sasl_sup, alarm_handler, overload,
+ release_handler,
+
+ %% gs
+ gs_frontend
+ ]).
+
+%% List of module:function/arity calls that will make the caller a
+%% "SYSTEM"-process.
+%%
+-define(SYSTEM_INIT_CALLS,
+ [{application_master,init,4},
+ {application_master,start_it,4},
+ {inet_tcp_dist,accept_loop,2},
+ {net_kernel,ticker,2},
+ {supervisor_bridge,user_sup,1},
+ {user_drv,server,2},
+ {group,server,3},
+ {kernel_config,init,1},
+ {inet_tcp_dist,do_accept,6},
+ {inet_tcp_dist,do_setup,6},
+ {pman_main,init,2},
+ {pman_buf_printer,init,2},
+ {pman_buf_converter,init,2},
+ {pman_buf_buffer,init,1},
+ {gstk,init,1},
+ {gstk_port_handler,init,2},
+ {gstk,worker_init,1}
+ ]).
+
+%% List of module:function/arity calls that will make the executing
+%% process a "SYSTEM"-process.
+-define(SYSTEM_RUNNING_CALLS,
+ [{file_io_server,server_loop,1},
+ {global,loop_the_locker,1},
+ {global,collect_deletions,2},
+ {global,loop_the_registrar,0},
+ {gs_frontend,request,2},
+ {shell,get_command1,5},
+ {shell,eval_loop,3},
+ {io,wait_io_mon_reply,2},
+ {pman_module_info,loop,1},
+ {pman_options,dialog,3},
+ {pman_options,loop,1},
+ {pman_relay_server,loop,1},
+ {pman_shell,monitor_loop,1},
+ {pman_shell,safe_loop,2}
+ ]).
+
+%% pinfo(Pid) -> [{Item, Info}] | undefined
+%% pinfo(Pid, Item) -> Info | undefined
+%% A version of process_info/1 that handles pid on remote nodes as well.
+pinfo({_, Pid}) -> % Handle internal process format
+ pinfo(Pid);
+pinfo(Pid) when node(Pid)==node() ->
+ process_info(Pid);
+pinfo(Pid) ->
+ case rpc:call(node(Pid), erlang, process_info, [Pid]) of
+ {badrpc, _} -> undefined;
+ Res -> Res
+ end.
+
+pinfo({_, Pid}, Item) -> % Handle internal process format
+ pinfo(Pid, Item);
+pinfo(Pid, Item) when node(Pid)==node() ->
+ case process_info(Pid, Item) of
+ {Item, Info} -> Info;
+ "" -> ""; % Item == registered_name
+ undefined -> undefined
+ end;
+pinfo(Pid, Item) ->
+ case rpc:call(node(Pid), erlang, process_info, [Pid, Item]) of
+ {badrpc, _} -> undefined;
+ {Item, Info} -> Info;
+ "" -> ""; % Item == registered_name
+ undefined -> undefined
+ end.
+
+%% function_info(Pid) -> {M, F, A}
+%% Returns the initial function for the specified process.
+function_info(Pid) ->
+ case pinfo(Pid, current_function) of
+ {Module, Function, Arity} ->
+ {Module, Function, Arity};
+ undefined ->
+ {unknown, unknown, 0}
+ end.
+
+%% r_processes(Node) -> Pids
+%% Return a list of all processes at Node.
+%%
+%% If there is a problem with getting information from a remote
+%% node, an empty list is returned.
+r_processes(Node) ->
+ ordsets:from_list(r_processes1(Node)).
+
+r_processes1(Node) ->
+ if
+ Node==node() ->
+ processes();
+ true ->
+ case rpc:block_call(Node, erlang, processes, []) of
+ {badrpc, _} ->
+ [];
+ Pids -> Pids
+ end
+ end.
+
+%% is_running(Object) -> {true, {shell,Pid}} | {true, Pid} | false
+%% Object = {shell, Pid} | {link, Pid, ?} | Pid
+is_running({shell,Pid}) ->
+ case is_running(Pid) of
+ {true,Pid} ->
+ {true,{shell,Pid}};
+ false ->
+ false
+ end;
+is_running({link,Pid,_}) ->
+ is_running(Pid);
+is_running(Pid) ->
+ case is_pid_or_shell(Pid) of
+ true ->
+ case pinfo(Pid) of
+ undefined -> false;
+ _PInfo -> {true, Pid}
+ end;
+ false ->
+ false
+ end.
+
+%% is_pid_or_shell(Object) -> bool()
+%% Checks if the argument is an pid or a tuple {shell, Pid}.
+is_pid_or_shell({shell,Pid}) when is_pid(Pid) ->
+ true;
+is_pid_or_shell(Pid) when is_pid(Pid) ->
+ true;
+is_pid_or_shell(_) ->
+ false.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% get_pid/1 - returns the Pid of the object provided that
+%% it is a proper process specifier.
+%%
+%% Arguments:
+%% Object A process specifier
+%%
+%% Returns:
+%% The Pid.
+
+get_pid({shell,Pid}) ->
+ Pid;
+get_pid(Pid) when is_pid(Pid) ->
+ Pid.
+
+%% is_system_process(Pid) -> bool()
+%% Returns true if Pid is a "system process".
+%% This is a prototype version, use file configuration later.
+is_system_process(Pid) ->
+ catch is_system_process2(Pid).
+
+is_system_process2(Pid) ->
+
+ %% Test if the registered name is a system registered name
+ case pinfo(Pid, registered_name) of
+ undefined -> ignore;
+ "" -> ignore;
+ Name ->
+ case lists:member(Name, ?SYSTEM_REG_NAMES) of
+ true -> throw(true);
+ false -> ignore
+ end
+ end,
+
+ %% Test if the start specification is a "system start function"
+ MFAi = case pinfo(Pid, initial_call) of
+ {proc_lib, init_p, 5} ->
+ proc_lib:translate_initial_call(Pid); % {M,F,A} | Fun
+ Res -> Res % {M,F,A} | undefined
+ end,
+ case lists:member(MFAi, ?SYSTEM_INIT_CALLS) of
+ true -> throw(true);
+ false -> ignore
+ end,
+
+ %% Test if the running specification is a "system running function"
+ case pinfo(Pid, current_function) of
+ undefined -> false;
+ MFAc ->
+ lists:member(MFAc, ?SYSTEM_RUNNING_CALLS)
+ end.
+
+%% is_hidden_by_module(Pid, Modules) -> bool()
+%% Checks if Pid is to be hidden because it executes code from one
+%% of Modules
+is_hidden_by_module(Pid, Modules) ->
+ case pinfo(Pid, current_function) of
+ {Module, _Function, _Arity} ->
+ lists:member(Module, Modules);
+ undefined -> false
+ end.
+
+%% get_name(Pid) -> Name | " "
+%% Returns the registered name of a process, if any, or " " otherwise.
+get_name(Pid) ->
+ case pinfo(Pid, registered_name) of
+ undefined -> " ";
+ "" -> " ";
+ Name -> Name
+ end.
+
+%% msg(Pid) -> int()
+msg(Pid) ->
+ case pinfo(Pid, messages) of
+ undefined -> 0;
+ Msgs -> length(Msgs)
+ end.
+
+%% reds(Pid) -> int()
+reds(Pid) ->
+ case pinfo(Pid, reductions) of
+ undefined -> 0;
+ Reds -> Reds
+ end.
+
+%% psize(Pid) -> int()
+%% Returns the total process size (stack + heap).
+psize(Pid) ->
+ Stack = pinfo(Pid, stack_size),
+ Heap = pinfo(Pid, heap_size),
+ case {Heap, Stack} of
+ {undefined, undefined} -> 0;
+ {undefined, Sz} -> Sz;
+ {Sz, undefined} -> Sz;
+ {Sz0, Sz1} -> Sz0 + Sz1
+ end.
diff --git a/lib/pman/src/pman_relay.erl b/lib/pman/src/pman_relay.erl
new file mode 100644
index 0000000000..289765492f
--- /dev/null
+++ b/lib/pman/src/pman_relay.erl
@@ -0,0 +1,127 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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 : Interface function to relay calls (esp. trace calls)
+%% to processes on other nodes. Some of the calls
+%% are conditionally relayed.
+%%----------------------------------------------------------------------
+
+-module(pman_relay).
+
+%%-compile(export_all).
+-export([start/1,
+ ok_to_trace/1,
+ trac/3]).
+
+
+-include("assert.hrl").
+
+%% --------------------------------------------------------------
+%% DISTRIBUTION
+%% --------------------------------------------------------------
+%% (???) Process dictionary alert!!!
+%%
+%% Since we are not allowed to do erlang:trace/3 on remote
+%% processe we create a help process at the remote node to
+%% do the job for us
+%% ---------------------------------------------------------------
+
+start(P) when is_pid(P), node(P)/=node() ->
+
+ %% Remote supervision, relaying necessary
+
+ put(relay, spawn_link(node(P), pman_relay_server, init, [self()]));
+
+
+start(_) ->
+
+ %% Local supervision, no relaying
+
+ ignore.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% ok_to_trace/1 - Tests wheter we can actually start tracing
+%% a process.
+%%
+%% Arguments:
+%% Pid Pid of the process to trace (on local or remote node)
+%%
+%% Returns
+%% true If it is OK to trace the process
+%% false If the process is already traced, or some other
+%% condition prevents it from being traced.
+
+ok_to_trace(Pid) when node(Pid) == node()->
+
+ %% Local trace, no relaying
+
+ case catch erlang:trace(Pid, false, [send]) of
+ 1 ->
+ true;
+ _Otherwise ->
+ false
+ end;
+ok_to_trace(Pid) ->
+
+ %% Remote trace, relaying necessary
+
+ PidRelay = get(relay),
+ PidRelay ! {ok_to_trace, self(), Pid},
+ receive
+ {ok_to_trace, PidRelay} ->
+ true;
+ {not_ok_to_trace, PidRelay} ->
+ false;
+ _Otherwise ->
+ ?ALWAYS_ASSERT("Unexpected message from relay process")
+ after
+ 5000 ->
+ false
+ end.
+
+
+
+
+
+
+%% ---------------------------------------------------------------
+%% Possibly send a request to do tracing to a remote node.
+%% ---------------------------------------------------------------
+
+trac(Pid, How, Flag) when node(Pid) == node() ->
+
+ %% Local trace, no relaying necessary
+
+
+ case catch erlang:trace(Pid, How, Flag) of
+ 1 -> ok;
+ _ -> pman_win:format("** Illegal trace request ** \n", [])
+ end;
+
+trac(Pid, How, Flag) ->
+
+
+
+ %% Remote trace, relaying necessary
+
+ get(relay) ! {self(), erlang, trace, [Pid, How, Flag]}.
+
diff --git a/lib/pman/src/pman_relay_server.erl b/lib/pman/src/pman_relay_server.erl
new file mode 100644
index 0000000000..2fcbb663bc
--- /dev/null
+++ b/lib/pman/src/pman_relay_server.erl
@@ -0,0 +1,57 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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 : Relay server code.
+%%%----------------------------------------------------------------------
+
+-module(pman_relay_server).
+
+%%-compile(export_all).
+-export([init/1]).
+
+
+
+init(P) ->
+ process_flag(trap_exit, true),
+
+ loop(P).
+
+loop(P) ->
+ receive
+ {ok_to_trace, PidSender, PidToTrace} ->
+ case catch erlang:trace(PidToTrace, false, [send]) of
+ 1 ->
+ PidSender ! {ok_to_trace, self()},
+ loop(P);
+ _Otherwise ->
+ PidSender ! {not_ok_to_trace, self()}
+ end;
+
+ {P, M,F,A} ->
+ case catch apply(M, F, A) of
+ 1 -> ok;
+ _Other -> P ! {print, "** Illegal trace request **\n", []}
+ end,
+ loop(P);
+ {'EXIT', P, _Reason} ->
+ exit(normal);
+ Other -> %% Here is the normal case for trace i/o
+ P ! Other,
+ loop(P)
+ end.
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.
diff --git a/lib/pman/src/pman_tool.erl b/lib/pman/src/pman_tool.erl
new file mode 100644
index 0000000000..1d33fb9764
--- /dev/null
+++ b/lib/pman/src/pman_tool.erl
@@ -0,0 +1,145 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(pman_tool).
+
+%% Listbox selection window
+
+-export([select/3]).
+
+-record(state, {topwin,
+ frame,
+ listbox}).
+
+%% Constants
+-define(WIN_WIDTH, 350).
+-define(WIN_HEIGHT, 350).
+
+select(Win, Title, Choices) ->
+ Self = self(),
+ Pid = spawn_link(fun() -> init(Self, Win, Title, Choices) end),
+ receive
+ {Pid, Result} ->
+ Result
+ end.
+
+init(Pid, Win, Title, Choices) ->
+
+ %% Create window
+ State = create_window(Win, Title, Choices),
+
+ gse:map(State#state.topwin),
+
+ %% enter event loop
+ loop(Pid, Choices, State).
+
+loop(Pid, Choices, State) ->
+ receive
+ {gs, _, destroy, _Data, _Args} ->
+ Pid ! {self(), cancelled};
+ {gs, _, configure, _Data, [W, H|_]} ->
+ gse:resize(State#state.frame, W, H),
+ loop(Pid, Choices, State);
+ {gs, _, click, ok, _Args} ->
+ case gs:read(State#state.listbox, selection) of
+ [] ->
+ Pid ! {self(), cancelled};
+ Indices ->
+ Selection = selection(Indices, Choices),
+ Pid ! {self(), Selection}
+ end;
+ {gs, _, click, cancel, _Args} ->
+ Pid ! {self(), cancelled};
+ {gs, Obj, doubleclick, _Data, _Args} ->
+ self() ! {gs, Obj, click, ok, []},
+ loop(Pid, Choices, State);
+ _GSEvent ->
+ loop(Pid, Choices, State)
+ end.
+
+selection(Indices, Choices) ->
+ selection(0, Indices, Choices).
+
+selection(I, [I|Is], [{Val,_Str}|Vals]) ->
+ [Val | selection(I+1, Is, Vals)];
+selection(I, [I|Is], [Val|Vals]) ->
+ [Val | selection(I+1, Is, Vals)];
+selection(_I, [], _Vals) ->
+ [];
+selection(I, Is, [_Val|Vals]) ->
+ selection(I+1, Is, Vals).
+
+create_window(Win, Title, Choices) ->
+ Font = pman_win:font(Win),
+
+ %% Top window and a frame that covers it entirely, to allow
+ %% usage of the packer for geometry management.
+ Topwin = gse:window(Win, [{width, ?WIN_WIDTH},
+ {height,?WIN_HEIGHT},
+ {configure, true},
+ {title, Title}]),
+ Frame = gse:frame(Topwin, [{packer_x,[{stretch,1},
+ {stretch,1}]},
+ {packer_y,[{stretch,1},
+ {stretch,5},
+ {stretch,1}]}]),
+
+ %% Caption above the list of items
+ CaptionTxt = "Select one or more of the following:",
+ gse:label(Frame, [{pack_x,{1,2}},
+ {pack_y,{1,1}},
+ {label,{text,CaptionTxt}}, {font,Font}]),
+
+ %% List of selectable items
+ Listbox = gse:listbox(Frame, [{pack_x,{1,2}},
+ {pack_y,{2,2}},
+ {selectmode,multiple},
+ {doubleclick, true},
+ {font,Font},
+ {items, str_choices(Choices)}]),
+
+ %% OK and Cancel buttons in a separate frame.
+ F13 = gse:frame(Frame, [{bw,1},
+ {pack_xy,{{1,2},3}},
+ {packer_x,[{stretch,1},
+ {fixed, 60},
+ {stretch,1},
+ {fixed, 60},
+ {stretch,1}]},
+ {packer_y,[{stretch,1},
+ {fixed, 30},
+ {stretch,1}]}]),
+
+ gse:button(F13, [{pack_xy,{2,2}},
+ {label,{text,"OK"}}, {font,Font},
+ {data,ok}]),
+ gse:button(F13, [{pack_xy,{4,2}},
+ {label,{text,"Cancel"}}, {font,Font},
+ {data,cancel}]),
+
+ gse:resize(Frame, ?WIN_WIDTH, ?WIN_HEIGHT),
+ #state{topwin=Topwin, frame=Frame, listbox=Listbox}.
+
+str_choices(Choices) ->
+ lists:map(
+ fun({Val, Str}) ->
+ lists:flatten(io_lib:format("~p: ~s", [Val, Str]));
+ (Term) ->
+ lists:flatten(io_lib:format("~p", [Term]))
+ end,
+ Choices).
diff --git a/lib/pman/src/pman_win.erl b/lib/pman/src/pman_win.erl
new file mode 100644
index 0000000000..52d5a237cf
--- /dev/null
+++ b/lib/pman/src/pman_win.erl
@@ -0,0 +1,667 @@
+%%
+%% %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: window management and the gs interface
+%% ------------------------------------------------------------
+
+-module(pman_win).
+
+%% ---------------------------------------------------------------
+%% The user interface exports
+%% ---------------------------------------------------------------
+
+-export([pman_window/3, window/1, module_data/1, display/1, format/2,
+ dialog_window/2, configeditor/2, configwin/3,
+ update/1, update/3,
+ msg_win/1, title/1,
+ remove_menu/1, add_menu/3,
+ change_colour/3, links_menus/1, calc_columnwidths/1]).
+-export([font/0, font/1]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Constants
+%%
+-include("pman_win.hrl").
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% pman_window/3 - Create a GS window and components for the
+%% Pman overview window, the main window.
+%%
+%% Arguments:
+%% Size number of processes
+%% HiddenModules list of modules
+%% Nodes list of supervised nodes
+%%
+%% Return:
+%% {Win, Grid, Frame, Procnum, W, H} where
+%% Win The GS top window
+%% Grid The GS grid
+%% Procnum Number of displayed processes
+%%
+
+pman_window(Size, _HiddenModules, Nodes) ->
+ GS = gs:start([{kernel,true}]),
+ Font = font(GS),
+ Win_Options = [{title, lists:concat(["Pman: Overview on ",node()])},
+ {width, ?WIN_WIDTH}, {height, ?WIN_HEIGHT},
+ {destroy, true},
+ {keypress,true}],
+ Win = gs:create(window, GS, Win_Options),
+
+ %% Menu bar
+ MenuBar = gs:create(menubar, Win, []),
+ MBFile = gs:create(menubutton, MenuBar, [{label,{text," File "}},
+ {font,Font},
+ {underline,1}]),
+ MBView = gs:create(menubutton, MenuBar, [{label,{text, " View "}},
+ {font,Font},
+ {underline,1}]),
+ MBTrace = gs:create(menubutton, MenuBar, [{label,{text, " Trace "}},
+ {font,Font},
+ {underline,1}]),
+ MBHelp = gs:create(menubutton, MenuBar, [{label, {text, " Help "}},
+ {font,Font},
+ {side,right},
+ {underline,1}]),
+
+ %% Addition of a menu for distribution
+ add_node_menu(MenuBar, Nodes, Font),
+
+ %% All menu buttons
+ MenuFile = gs:create(menu, MBFile, []),
+ MenuView = gs:create(menu, MBView, []),
+ MenuTrace = gs:create(menu, MBTrace, []),
+ MenuHelp = gs:create(menu, MBHelp, []),
+
+ %% File menu
+ gse:named_menuitem('Default Options', MenuFile,
+ [{label,{text,"Options..."}}, {font,Font},
+ {underline,0}]),
+ gse:named_menuitem('Save Options',MenuFile,
+ [{label,{text,"Save Options"}}, {font,Font}]),
+ gse:named_menuitem('Exit', MenuFile,
+ [{label,{text,"Exit"}}, {font,Font},
+ {underline,0}]),
+
+ %% View menu
+ gse:named_menuitem('Hide All',MenuView,
+ [{label, {text, "Hide All Processes"}},
+ {font,Font},
+ {underline,1}]),
+
+ gse:named_menuitem('Hide Modules', MenuView,
+ [{label, {text, "Hide Modules..."}},
+ {font,Font},
+ {underline,8}]),
+
+ gse:named_menuitem('Hide Selected Process', MenuView,
+ [{label, {text, "Hide Selected Process"}},
+ {font,Font},
+ {underline,2}]),
+
+ gse:named_menuitem('Module',MenuView,
+ [{label, {text, "Module Info..."}}, {font,Font},
+ {underline,7}]),
+
+ gse:named_menuitem('Refresh', MenuView,
+ [{label, {text, "Refresh"}}, {font,Font},
+ {underline,0}]),
+
+ gse:named_menuitem('Show All',MenuView,
+ [{label, {text, "Show All Processes"}},
+ {font,Font}]),
+
+ gse:named_menuitem('Show Selected',MenuView,
+ [{label, {text, "Show Processes..."}},
+ {font,Font}]),
+
+ %% Trace menu
+ gs:create(menuitem, 'Kill', MenuTrace, [{label,{text, "Kill"}},
+ {font,Font},
+ {underline,0}]),
+
+ gs:create(menuitem, 'Trace Process', MenuTrace,
+ [{label, {text, "Trace Selected Process"}}, {font,Font},
+ {underline,0}]),
+
+ gs:create(menuitem,'Trace Shell', MenuTrace,
+ [{label, {text,"Shell Process"}}, {font,Font},
+ {underline,0}]),
+
+ %% Help menu
+ gs:create(menuitem,'Help', MenuHelp, [{label, {text, "Help" }},
+ {font,Font},
+ {underline,0}]),
+
+ %% Window contents
+
+ %% Geometry managing frame
+ Frame = gse:frame(Win, [{y,?MENU_HEIGHT},
+ {packer_x,[{stretch, 1}]},
+ {packer_y,[{stretch,10},
+ {fixed,?CHECKBAR_HEIGHT}]}]),
+
+
+
+ %% Grid
+ Grid_Options = [
+ {pack_x,1}, {pack_y,1},
+ {fg,black},
+ {vscroll,right},{hscroll,bottom},
+ calc_columnwidths(739),
+ {rows, {1,Size}}],
+ Grid = gse:grid(Frame,Grid_Options),
+
+
+ %% Checkbutton bar at the bottom of the window
+
+ CheckBar = gse:frame(Frame, [{pack_x,1},
+ {pack_y,2},
+ {packer_x,[{stretch, 2, 100,300},
+ {stretch, 2, 100,300},
+ {stretch,1},
+ {stretch, 2,100,300}]},
+ {packer_y,[{stretch,1}]}]),
+ gse:named_checkbutton('Hide System',CheckBar,
+ [{pack_xy,{1,1}},
+ {justify, left},
+ {align,w},
+ {width, 200},
+ {font, Font},
+ {label, {text, "Hide System Processes" }}]),
+
+ gse:named_checkbutton('Auto Hide New',CheckBar,
+ [{pack_xy,{2,1}},
+ {width, 200},
+ {justify, left},
+ {align,w},
+ {font, Font},
+ {label, {text, "Auto-Hide New" }}]),
+
+ gse:named_label('Number Hidden',CheckBar,
+ [{pack_xy,{4,1}},
+ {justify, left},
+ {align,w},
+ {width, 200},
+ {font, Font},
+ {label, {text, ?CPIDHIDDENTEXT }}]),
+
+ %% Finalize it!
+ gse:map(Win),
+ gse:config(Win,[raise]),
+ gse:config(Win,[{configure,true}]),
+
+
+ {Win, Grid, Frame, length(processes())+1, ?WIN_WIDTH, ?WIN_HEIGHT}.
+
+
+%% Calculate columnwidths in respect to the size of the window.
+
+calc_columnwidths(Width) ->
+ if
+ Width =< 739 ->
+ {columnwidths,[75,215,146,90,105,105]};
+ true ->
+ S = (Width - 75)/(215+146+90+105+105),
+ {columnwidths,[75,round(215*S),round(146*S),round(90*S),
+ round(105*S),round(105*S)]}
+ end.
+
+%% ---------------------------------------------------------------
+%% Create a trace window
+%%
+%% Process, a process id or an atom
+%%
+%% Return: A window and a editor
+%% ---------------------------------------------------------------
+
+
+window(Process) ->
+ GS = gs:start([{kernel,true}]),
+ Font = font(GS),
+ Win_Options = [{title,title(Process)}, {width,550}, {keypress,true},
+ {configure,true},
+ {destroy,true},{height, 400}],
+ Win = gs:create(window,GS,Win_Options),
+
+ MenuBar = gs:create(menubar, Win, []),
+
+ %% File menu
+ MBFile = gs:create(menubutton,MenuBar,[{label,{text," File "}},
+ {font,Font},
+ {underline, 1}]),
+ MenuFile = gs:create(menu, MBFile, []),
+ make_menus(pman_process:is_running(Process), MenuBar, MenuFile,
+ Font),
+
+ gse:named_menuitem('Save buffer',MenuFile,
+ [{label,{text, "Save buffer..."}},
+ {font,Font},
+ {underline,0}]),
+ gse:named_menuitem('Close',MenuFile,
+ [{label, {text, "Close"}},
+ {font,Font},
+ {underline,0}]),
+
+
+ Editor = gs:create(editor,Win,[{x,3}, {y,40},
+ {width,546}, {height,348},
+ {font,Font}]),
+ gs:config(Editor, [{keypress, true},{insert, {'end', display(Process)}}]),
+ gs:config(Editor, [{enable, false},{vscroll, right}, {hscroll, bottom},
+ {wrap,none}]),
+ gs:config(Win, [{map, true}]),
+ {Win, Editor}.
+
+%% ---------------------------------------------------------------------
+%% Menu Help Fuctions
+%% ---------------------------------------------------------------------
+
+
+links_menus(Links) ->
+ gs:destroy('Links'),
+ gs:create(menu,'Links','LinksMenu',[]),
+ Flag = case links_menus(Links,[]) of
+ [] -> false;
+ Pids ->
+ add_menu('Links', Pids, "Trace"),
+ true
+ end,
+ gse:config('LinksMenu',[{enable,Flag}]).
+
+links_menus([],Pids) -> Pids;
+links_menus([Pid|Links],Pids) when is_pid(Pid) ->
+ links_menus(Links,[Pid|Pids]);
+links_menus([_Port|Links],Pids) ->
+ links_menus(Links,Pids).
+
+
+%% Create the node menu.
+
+add_node_menu(MenuBar, Nodes, Font) ->
+ MBNode = gs:create(menubutton, MenuBar, [{label,{text, " Nodes "}},
+ {font,Font},
+ {underline, 1}]),
+ gs:create(menu, node, MBNode, []),
+ add_menu(node, Nodes, "Show", Font),
+ gse:disable(node()).
+
+
+%% ---------------------------------------------------------------------
+%% Add Menus in the list under Menu menuitem.
+
+add_menu(Menu, Names, Tag) ->
+ add_menu(Menu, Names, Tag, font()).
+
+add_menu(_Menu, [], _Tag, _Font) -> ok;
+add_menu(Menu, [Name|Names], Tag, Font) ->
+ Title = io_lib:format("~s ~p",[Tag, Name]),
+ gs:create(menuitem,Name,Menu,[{label,{text,Title}},
+ {font,Font},
+ {data,{Menu,Name}}]),
+ add_menu(Menu, Names, Tag, Font).
+
+%% ---------------------------------------------------------------------
+%% Remove a specific menu item, or a whole menu, or a list of both.
+%%
+
+remove_menu(List) when is_list(List)->
+ lists:foreach(fun(X) -> gs:destroy(X) end, List);
+
+remove_menu(Object) ->
+ gse:destroy(Object).
+
+
+%% ---------------------------------------------------------------------
+%% If the trace window opened is supposed to trace a real pid, let us
+%% add the trace menu, and other items specific to tracing. If not,
+%% the only menus available are the ones in the default defined in
+%% window(Pid).
+
+make_menus(false, _, _, _) -> ok;
+make_menus({true,Pid}, MenuBar, MenuFile, Font) ->
+ MBView = gs:create(menubutton,'ViewMenu',MenuBar,
+ [{underline,1},
+ {label,{text," View "}}, {font,Font},
+ {side,left}]),
+ MenuView = gs:create(menu, MBView, []),
+
+ MBTrace = gs:create(menubutton,'TraceMenu',MenuBar,
+ [{underline,1},
+ {label,{text," Trace "}}, {font,Font},
+ {side,left}]),
+ MenuTrace = gs:create(menu, MBTrace, []),
+
+
+ MBHelp = gs:create(menubutton,'HelpMenu',MenuBar,
+ [{underline,1},
+ {label,{text," Help "}}, {font,Font},
+ {side,right}]),
+ MenuHelp = gs:create(menu, MBHelp, []),
+
+ %% File menu
+ gse:named_menuitem('Options', MenuFile,
+ [{label, {text, "Options..."}}, {font,Font},
+ {underline,0}]),
+
+ %% Trace menu
+ gse:named_menuitem('All Links', MenuTrace,
+ [{label, {text, "All Linked Processes"}},
+ {font,Font},
+ {underline,0}]),
+ gse:named_menuitem('LinksMenu', MenuTrace,
+ [{underline,0},
+ {label, {text, "Linked Process..."}},
+ {font,Font},
+ {itemtype, cascade},
+ {enable,false}]),
+ gs:create(menu,'Links','LinksMenu',[]),
+ case pman_process:pinfo(Pid, links) of
+ Links when is_list(Links) ->
+ links_menus(Links);
+ undefined ->
+ lists:foreach(fun(X) -> gse:disable(X) end,['LinksMenu'])
+ end,
+ gse:named_menuitem('Kill', MenuTrace,
+ [{label, {text, "Kill"}}, {font,Font},
+ {underline,0}]),
+
+ %% View menu
+ gse:named_menuitem('Clear', MenuView,
+ [{label, {text, "Clear buffer"}}, {font,Font},
+ {underline,0}]),
+
+ gse:named_menuitem('Module', MenuView,
+ [{label, {text, "Module Info"}}, {font,Font},
+ {underline,0}]),
+
+ %% Help menu
+ gse:named_menuitem('Help', MenuHelp,
+ [{label, {text, "Help"}}, {font,Font},
+ {underline,0}]).
+
+%% ---------------------------------------------------------------------
+%% Configurate the actual editor
+%%
+%% Editor, actual editor
+%% Options, actual options for the editor
+%%
+%% Return: A configurated editor with the actual options
+%% ---------------------------------------------------------------------
+
+configeditor(Editor, Options) ->
+ gs:config(Editor, Options).
+
+%% ---------------------------------------------------------------------
+%% Configure the actual window after it has been resized.
+%% ---------------------------------------------------------------------
+
+configwin(Object, W, H) ->
+ Dx = abs(W - gs:read(Object,width) - 4),
+ Dy = abs(H - gs:read(Object,height) - 42),
+ if
+ Dx + Dy =/= 0 ->
+ gs:config(Object,[{width,W - 4}]);
+ true -> ok
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% update/1, 3
+update(NoOfHidden) ->
+ Str = lists:flatten(io_lib:format(?CPIDHIDDENTEXT++"~w",
+ [NoOfHidden])),
+ gse:config('Number Hidden', [{label, {text,Str}}]).
+
+update(Grid, ShowInfoR, NoOfHidden) ->
+
+ %% We reverse the list because we want the processes to appear with
+ %% the newest (=highest) pid first in the list.
+ ShowInfo = lists:reverse(ShowInfoR),
+
+ %% Set the length of the grid
+ CGridline = length(ShowInfo) + 1,
+ gs:config(Grid, [{rows, {1,CGridline}}]),
+
+ %% Add the header line
+ add_gridline(Grid,
+ 1,
+ {'Pid','Current Function','Name','Msgs','Reds','Size'},
+ []),
+
+ update(NoOfHidden),
+
+ %% Recurse through the ordset of pids
+ update_r(Grid, ShowInfo, 2).
+
+update_r(Grid, [], Row) ->
+ delete_gridlines(Grid, Row);
+update_r(Grid, [{Pid,Func,Name,Msgs,Reds,Psize}|ShowInfo], Row) ->
+ {M, F, A} = Func,
+ FuncText = lists:flatten(io_lib:format("~w:~w/~w", [M, F, A])),
+ add_gridline(Grid,
+ Row,
+ {Pid, FuncText, Name, Msgs, Reds, Psize},
+ [{data,{pidfunc,Pid,Func}}]),
+ update_r(Grid, ShowInfo, Row+1).
+
+add_gridline(Grid, Row, Tuple, LIOptSpec) ->
+ {Pid, FuncText, Name, Msgs, Reds, Psize} = Tuple,
+ LIOpt = [{click,true},
+ {doubleclick,true},
+ {fg, colour(Row)},
+ {text,{1,Pid}},
+ {text,{2,FuncText}},
+ {text,{3,Name}},
+ {text,{4,Msgs}},
+ {text,{5,Reds}},
+ {text,{6,Psize}} |LIOptSpec],
+ case gs:read(Grid, {obj_at_row, Row}) of
+ undefined ->
+ gse:gridline(Grid,[{row, Row}|LIOpt]);
+ GridLine ->
+ gs:config(GridLine,LIOpt)
+ end.
+
+delete_gridlines(Grid, Row) ->
+ case gs:read(Grid, {obj_at_row, Row}) of
+ undefined ->
+ ok;
+ GridLine ->
+ gs:destroy(GridLine),
+ delete_gridlines(Grid, Row+1)
+ end.
+
+colour(1) ->
+ ?HEADER_COLOUR;
+colour(_Row) ->
+ ?UNSELECTED_COLOUR.
+
+%% Interchange colours between two rows
+change_colour(Grid, Row, Row) ->
+ Gitem = gs:read(Grid, {obj_at_row,Row}),
+ gs:config(Gitem, {fg,?SELECTED_COLOUR});
+change_colour(Grid, From, To) ->
+ Gitem_to = gs:read(Grid, {obj_at_row,To}),
+ Gitem_fr = gs:read(Grid, {obj_at_row,From}),
+ gs:config(Gitem_to, {fg,?SELECTED_COLOUR}),
+ gs:config(Gitem_fr, {fg,colour(From)}).
+
+%% --------------------------------------------------------------
+%% Create a title for the window
+%% Return: the title
+%% --------------------------------------------------------------
+
+title({module, Mod}) ->
+ lists:flatten([io_lib:format("Pman: Module info ~p", [Mod])]);
+
+title({shell, Sh} ) ->
+ lists:flatten([io_lib:format("Pman: Shell process ~p on ~p",
+ [Sh,node(Sh)])]);
+
+title(Sh) ->
+ lists:flatten([io_lib:format("Pman: Process ~p on ~p",
+ [Sh, node(Sh)]),name(Sh)]).
+name(Pid) ->
+ case pman_process:pinfo(Pid, registered_name) of
+ undefined -> "";
+ Name ->
+ lists:flatten([io_lib:format("[~p]", [Name])])
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% module_data/1 - %% Returns the module information for a
+%% module, on a format suitable to insert into a GS editor.
+%%
+%% Arguments:
+%% ModuleName The module
+%%
+%% Returns:
+%% A string with module information.
+%%
+
+module_data(ModuleName) ->
+ vformat("", catch apply({ModuleName, module_info},[])).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% display/1 -
+%%
+
+display({_,Pid,_}) -> display(Pid);
+display({_,Pid}) -> display(Pid);
+display(Pid) when is_pid(Pid) ->
+ case pman_process:pinfo(Pid) of
+ undefined ->
+ format('Process is dead~n',[]);
+ Other ->
+ proc_format(Other)
+ end.
+
+%% --------------------------------------------------------------
+%% Format functions for the shell and help window.
+%% --------------------------------------------------------------
+
+vformat(Pad, {M,F,A}) when is_atom(F) ->
+ Pad2 = lists:append(Pad,mkpad(io_lib:format("~w:~w",[M,F]))),
+ lists:flatten([format("~p:~p", [M,F]),argformat(Pad2, A)]);
+
+vformat(Pad, [H|T]) ->
+ kvformat(Pad, [H|T],"[");
+
+vformat(_Pad, X) -> format("~p~n", [X]).
+
+format(Format) -> format(Format, []).
+
+format(Format, Args) ->
+ io_lib:format(Format, Args).
+
+
+kvformat(S, [Item],Buff) ->
+ lists:reverse([format("\n~s~p]\n",[S,Item])|Buff]);
+
+kvformat(S,[H|T],Buff) ->
+ kvformat(S,T,[format("\n~s~p, ",[S,H])|Buff]);
+
+kvformat(_,[],Buff) ->
+ lists:reverse(["]\n"|Buff]).
+
+argformat(_Pad,A) when is_integer(A) ->
+ format("/~p\n", [A]);
+argformat(_,A) ->
+ lists:flatten([format("/~p\n", [length(A)]),
+ format("args: \n"),
+ argformat2(" ", A)]).
+
+argformat2(Pad, Arglist) ->
+ Chars = lists:flatten(io_lib:format("~p",[Arglist])),
+ if
+ length(Chars) < (70 - length(Pad)) ->
+ format("~s~s\n", [Pad, Chars]);
+ true ->
+ argformat3(Pad, Arglist)
+ end.
+
+argformat3(_,[]) -> format("\n");
+argformat3(Pad, [H|T]) ->
+ Chars = truncate(65,io_lib:format("~s~p",[Pad, H])),
+ format("~s,\n", [Chars]),
+ argformat3(Pad, T).
+
+pformat(false) -> [];
+pformat({value,{_, 0}}) -> [];
+pformat({value,{_, []}}) -> [];
+pformat({value, {Key, Vals}}) ->
+ Pad = mkpad(io_lib:format("~p ",[Key])),
+ format(lists:flatten(["~p: " ,vformat(Pad, Vals), "~n"]), [Key]).
+
+truncate(0, _Chars) -> ".....";
+truncate(I, [H|T]) -> [H|truncate(I-1, T)];
+truncate(_I, []) -> [].
+
+mkpad([_|T]) -> [32|mkpad(T)];
+mkpad([]) -> [].
+
+proc_format(Pi) -> %% process_info struct
+ X1 = pformat(lists:keysearch(initial_call, 1, Pi)),
+ X2 = pformat(lists:keysearch(current_function, 1,Pi)),
+ X3 = pformat(lists:keysearch(messages, 1,Pi)),
+ X4 = pformat(lists:keysearch(dictionary,1, Pi)),
+ X5 = pformat(lists:keysearch(heap_size, 1,Pi)),
+ X6 = pformat(lists:keysearch(stack_size, 1,Pi)),
+ X7 = pformat(lists:keysearch(reductions, 1,Pi)),
+ X8 = pformat(lists:keysearch(links, 1,Pi)),
+ X9 = pformat(lists:keysearch(trap_exit, 1,Pi)),
+ lists:flatten([X1, X2, X3, X4, X5,X6,X7,X8,X9]).
+
+
+%% Using the tool_utils function for presenting messages.
+dialog_window(GSParent, Text) ->
+ spawn_link(tool_utils, notify, [GSParent, Text]).
+
+%% Create a window with a dismiss button.
+msg_win(Text) ->
+ spawn_link(fun() -> display_msg_win(Text) end).
+
+display_msg_win(Text) ->
+ GS = gs:start([{kernel,true}]),
+ Font = font(GS),
+ Win = gs:window(GS, [{width,200}, {height,75}, {destroy,true},
+ {title,"Pman Message"}]),
+ Can = gs:canvas(Win, [{width,200}, {height, 75},{x,0},{y,0}]),
+ gs:text(Can, [{text,Text}, {coords,[{10,0}]}, {justify,center}]),
+ Btn = gs:button(Win, [{label,{text,"Dismiss"}}, {font,Font},
+ {width,100}, {x,50}, {y,40}]),
+ gs:config(Win, {map,true}),
+ receive
+ {gs, Btn, click, _, _} ->
+ ok
+ end.
+
+%% Choose default font
+font() ->
+ font(gs:start([{kernel,true}])).
+
+font(GS) ->
+ case gs:read(GS, {choose_font, {screen,[],12}}) of
+ Font when element(1, Font)==screen ->
+ Font;
+ _ ->
+ gs:read(GS, {choose_font, {courier,[],12}})
+ end.
diff --git a/lib/pman/src/pman_win.hrl b/lib/pman/src/pman_win.hrl
new file mode 100644
index 0000000000..8a2778d5b7
--- /dev/null
+++ b/lib/pman/src/pman_win.hrl
@@ -0,0 +1,39 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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%
+%%
+
+-define(WIN_HEIGHT, 390).
+-define(WIN_WIDTH, 745).
+
+-define(MENU_HEIGHT, 40).
+-define(CHECKBAR_HEIGHT, 40).
+
+-define(CPIDHIDDENTEXT, "# Hidden: ").
+
+-define(HEADER_COLOUR, blue).
+-define(UNSELECTED_COLOUR, black).
+-define(SELECTED_COLOUR, white).
+
+
+
+
+
+
+
+
+