From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/pman/src/Makefile | 112 +++++ lib/pman/src/assert.hrl | 81 ++++ lib/pman/src/pman.app.src | 40 ++ lib/pman/src/pman.appup.src | 19 + lib/pman/src/pman.erl | 132 ++++++ lib/pman/src/pman_buf.erl | 117 +++++ lib/pman/src/pman_buf.hrl | 29 ++ lib/pman/src/pman_buf_buffer.erl | 102 +++++ lib/pman/src/pman_buf_converter.erl | 189 +++++++++ lib/pman/src/pman_buf_printer.erl | 89 ++++ lib/pman/src/pman_buf_utils.erl | 106 +++++ lib/pman/src/pman_main.erl | 787 ++++++++++++++++++++++++++++++++++ lib/pman/src/pman_module_info.erl | 131 ++++++ lib/pman/src/pman_options.erl | 395 +++++++++++++++++ lib/pman/src/pman_options.hrl | 34 ++ lib/pman/src/pman_process.erl | 317 ++++++++++++++ lib/pman/src/pman_relay.erl | 127 ++++++ lib/pman/src/pman_relay_server.erl | 57 +++ lib/pman/src/pman_shell.erl | 823 ++++++++++++++++++++++++++++++++++++ lib/pman/src/pman_tool.erl | 145 +++++++ lib/pman/src/pman_win.erl | 667 +++++++++++++++++++++++++++++ lib/pman/src/pman_win.hrl | 39 ++ 22 files changed, 4538 insertions(+) create mode 100644 lib/pman/src/Makefile create mode 100644 lib/pman/src/assert.hrl create mode 100644 lib/pman/src/pman.app.src create mode 100644 lib/pman/src/pman.appup.src create mode 100644 lib/pman/src/pman.erl create mode 100644 lib/pman/src/pman_buf.erl create mode 100644 lib/pman/src/pman_buf.hrl create mode 100644 lib/pman/src/pman_buf_buffer.erl create mode 100644 lib/pman/src/pman_buf_converter.erl create mode 100644 lib/pman/src/pman_buf_printer.erl create mode 100644 lib/pman/src/pman_buf_utils.erl create mode 100644 lib/pman/src/pman_main.erl create mode 100644 lib/pman/src/pman_module_info.erl create mode 100644 lib/pman/src/pman_options.erl create mode 100644 lib/pman/src/pman_options.hrl create mode 100644 lib/pman/src/pman_process.erl create mode 100644 lib/pman/src/pman_relay.erl create mode 100644 lib/pman/src/pman_relay_server.erl create mode 100644 lib/pman/src/pman_shell.erl create mode 100644 lib/pman/src/pman_tool.erl create mode 100644 lib/pman/src/pman_win.erl create mode 100644 lib/pman/src/pman_win.hrl (limited to 'lib/pman/src') 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 +%% 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). + + + + + + + + + -- cgit v1.2.3