diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/inviso/src | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/inviso/src')
-rw-r--r-- | lib/inviso/src/Makefile | 104 | ||||
-rw-r--r-- | lib/inviso/src/inviso.app.src | 13 | ||||
-rw-r--r-- | lib/inviso/src/inviso.appup.src | 1 | ||||
-rw-r--r-- | lib/inviso/src/inviso.erl | 1055 | ||||
-rw-r--r-- | lib/inviso/src/inviso_c.erl | 1335 | ||||
-rw-r--r-- | lib/inviso/src/inviso_lfm.erl | 431 | ||||
-rw-r--r-- | lib/inviso/src/inviso_lfm_tpfreader.erl | 388 | ||||
-rw-r--r-- | lib/inviso/src/inviso_tool.erl | 3324 | ||||
-rw-r--r-- | lib/inviso/src/inviso_tool_lib.erl | 379 | ||||
-rw-r--r-- | lib/inviso/src/inviso_tool_sh.erl | 1731 |
10 files changed, 8761 insertions, 0 deletions
diff --git a/lib/inviso/src/Makefile b/lib/inviso/src/Makefile new file mode 100644 index 0000000000..1f2f8b1aff --- /dev/null +++ b/lib/inviso/src/Makefile @@ -0,0 +1,104 @@ +# ``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 via the world wide web 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. +# +# The Initial Developer of the Original Code is Ericsson Utvecklings AB. +# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +# AB. All Rights Reserved.'' +# +# $Id$ +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(INVISO_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/inviso-$(VSN) + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- + +MODULES= \ + inviso \ + inviso_c \ + inviso_lfm \ + inviso_lfm_tpfreader \ + inviso_tool \ + inviso_tool_lib + + +#HRL_FILES= ../include/ + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) + +APP_FILE= inviso.app + +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBIN)/$(APP_FILE) + +APPUP_FILE= inviso.appup + +APPUP_SRC= $(APPUP_FILE).src +APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += +warn_unused_vars -I../include + +# ---------------------------------------------------- +# 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: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src +# $(INSTALL_DIR) $(RELSYSDIR)/include +# $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + + + + + + + diff --git a/lib/inviso/src/inviso.app.src b/lib/inviso/src/inviso.app.src new file mode 100644 index 0000000000..91eaa1b9b2 --- /dev/null +++ b/lib/inviso/src/inviso.app.src @@ -0,0 +1,13 @@ +{application,inviso, + [{description, "INVISO trace tool"}, + {vsn, "%VSN%"}, + {modules, [inviso_c,inviso, + inviso_lfm,inviso_lfm_tpfreader + ]}, + {registered, [inviso_c]}, + {applications, [kernel, stdlib, runtime_tools]}, + {env, []} + ]}. + + + diff --git a/lib/inviso/src/inviso.appup.src b/lib/inviso/src/inviso.appup.src new file mode 100644 index 0000000000..54a63833e6 --- /dev/null +++ b/lib/inviso/src/inviso.appup.src @@ -0,0 +1 @@ +{"%VSN%",[],[]}. diff --git a/lib/inviso/src/inviso.erl b/lib/inviso/src/inviso.erl new file mode 100644 index 0000000000..de42926ffe --- /dev/null +++ b/lib/inviso/src/inviso.erl @@ -0,0 +1,1055 @@ +%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+%% Author: Ann-Marie L�f, [email protected]
+%% Lennart �hman, [email protected]
+%%
+%% Description: API module for the inviso system.
+%% Inviso consists of a control component and possibly one or more runtime
+%% components. This module is simply the API to the inviso system. All normal
+%% calls goes through the control component.
+%% ------------------------------------------------------------------------------
+
+-module(inviso).
+
+%% ------------------------------------------------------------------------------
+%% Exported API functions.
+%% ------------------------------------------------------------------------------
+
+-export([start/0, start/1,
+ add_node/1, add_node/2, add_node_if_ref/1, add_node_if_ref/2,
+ add_nodes/2, add_nodes/3, add_nodes_if_ref/2, add_nodes_if_ref/3,
+ change_options/1, change_options/2,
+ init_tracing/1, init_tracing/2,
+ stop_tracing/0, stop_tracing/1,
+ clear/0, clear/1, clear/2,
+ flush/0,flush/1,
+ stop/0, stop_nodes/0, stop_nodes/1, stop_all/0,
+ tp/1,tp/2,tp/4,tp/5,tp/6,
+ tpl/1,tpl/2,tpl/4,tpl/5,tpl/6,
+ tpm_localnames/0,tpm_localnames/1,tpm_globalnames/0,tpm_globalnames/1,
+ init_tpm/4,init_tpm/5,init_tpm/7,init_tpm/8,
+ tpm/4,tpm/5,tpm/6,tpm/8,tpm/9,
+ tpm_tracer/4,tpm_tracer/5,tpm_tracer/6,tpm_tracer/8,tpm_tracer/9,
+ tpm_ms/5,tpm_ms/6,
+ tpm_ms_tracer/5,tpm_ms_tracer/6,
+ ctpm_ms/4,ctpm_ms/5,ctpm/3,ctpm/4,
+ ctpm_localnames/0,ctpm_localnames/1,ctpm_globalnames/0,ctpm_globalnames/1,
+ ctp/1,ctp/2,ctp/3,ctp/4,
+ ctpl/1,ctpl/2,ctpl/3,ctpl/4,
+ tf/1, tf/2, tf/3,
+ ctf/1, ctf/2, ctf/3,
+ ctp_all/0, ctp_all/1, ctf_all/0, ctf_all/1,
+ suspend/1, suspend/2,
+ cancel_suspension/0, cancel_suspension/1,
+ get_status/0, get_status/1,
+ get_tracerdata/0, get_tracerdata/1,
+ list_logs/0, list_logs/1,
+ fetch_log/2, fetch_log/3, fetch_log/4,
+ delete_log/0, delete_log/1, delete_log/2,
+ subscribe/0, subscribe/1,
+ unsubscribe/0, unsubscribe/1]).
+
+%% debuging inviso
+-export([state/0, state/1]).
+
+%% ------------------------------------------------------------------------------
+%% Macros used in this module.
+%% ------------------------------------------------------------------------------
+
+-define(CONTROLLER,inviso_c).
+
+%% Some function calls to runtime components may take long time, we must wait
+%% longer than the standard timeout for a reply from the control component.
+-define(CALL_TIMEOUT,60000).
+%% ------------------------------------------------------------------------------
+
+
+
+%% =============================================================================
+%% CONTROL COMPONENT API FUNCTIONS.
+%% =============================================================================
+
+%% start()={ok,pid()}|{error,Reason}
+%% start(Options)={ok,pid()}|{error,Reason}
+%% Options=[Option,...], the options will be default options to runtime components
+%% later started. See add_node about available options.
+%% There are also options consumed by the control component:
+%% Option={subscribe,pid()}
+%%
+%% Starts a control component process on the local node. A control component must
+%% be started before runtime components can be started manually.
+start() ->
+ gen_server:start({local,?CONTROLLER},?CONTROLLER,{self(),[]},[]).
+
+start(Options) ->
+ gen_server:start({local,?CONTROLLER},?CONTROLLER,{self(),Options},[]).
+%% -----------------------------------------------------------------------------
+
+%% add_node(Reference)=NodeResult|{error, Reason}
+%% add_node(Reference,Options)=NodeResult|{error, Reason}
+%% Reference=PreviousReference = term(),
+%% Options=[Option,...],
+%% Option={dependency,Dep}
+%% Dep=integer()|'infinity'; The timeout before the runtime component will
+%% terminate if abandoned by this control component.
+%% Option={overload,Overload}; controls how and how often overload checks shall
+%% be performed. Instead of specifying a tuple, the atom 'overload' can be
+%% specified to state no loadcheck. The result will actually be the same
+%% if 'infinity' is used as intervall. It is sometimes necessary to
+%% initialize the overlaod check. This can be done with InitMFA. The
+%% loadchecker must then also be removed by using a RemoveMFA.
+%% Overload=Iterval (int() in milliseconds) |
+%% {LoadMF,Interval}|{LoadMF,Interval,InitMFA,RemoveMFA}
+%% LoadMF={Mod,Func}
+%% InitMFA,RemoveMFA={Mod,Func,ArgList}|void
+%% If just Interval is used, it means using a default overload check.
+%% NodeResult={ok,NAns}|{error,Reason}
+%% NAns=new|{adopted,State,Status,PreviousReference}|already_added
+%% Status = running | {suspended, SReason}
+%%
+%% Starts or tries to connect to an existing runtime component at the local
+%% node, regardless if the system is distributed or not.
+%% Options will override any default options specified at start-up of the
+%% control component.
+add_node(Reference) ->
+ gen_server:call(?CONTROLLER,{add_nodes,[node()],[],Reference,any_ref},?CALL_TIMEOUT).
+
+add_node(Reference,Options) when list(Options) ->
+ gen_server:call(?CONTROLLER,{add_nodes,[node()],Options,Reference,any_ref},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% add_node(Reference)=NodeResult|{error,{wrong_reference,OtherRef}}|{error,Reason}
+%% add_node(Reference,Options)=NodeResult|{error,{wrong_reference,OtherRef}}|
+%% {error,Reason}
+%%
+%% As add_node/1,/2 but will only connect to an already existing runtime component
+%% if its reference is the same as the one given as argument.
+add_node_if_ref(Reference) ->
+ gen_server:call(?CONTROLLER,{add_nodes,[node()],[],Reference,if_ref},?CALL_TIMEOUT).
+
+add_node_if_ref(Reference,Options) when list(Options) ->
+ gen_server:call(?CONTROLLER,{add_nodes,[node()],Options,Reference,if_ref},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% add_nodes(Nodes,Reference)={ok,NodeResults}|{error,Reason}
+%% add_nodes(Nodes,Reference,Options)={ok,NodeResults}|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeResults=[{Node,NodeResult},...]
+%%
+%% As add_node/1,/2 but for the nodes specified in Nodes.
+%% It is possible but not intended to use this function in a non-distributed
+%% system. By speicifying node() as the node where the runtime component shall
+%% be started. The return value will then follow the rules of non distributed
+%% returnvalues and not have a node indicator.
+add_nodes(Nodes,Reference) when list(Nodes) ->
+ gen_server:call(?CONTROLLER,{add_nodes,Nodes,[],Reference,any_ref},?CALL_TIMEOUT).
+
+add_nodes(Nodes,Reference,Options) when list(Nodes),list(Options) ->
+ gen_server:call(?CONTROLLER,{add_nodes,Nodes,Options,Reference,any_ref},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% add_nodes_if_ref(Nodes,Reference)={ok,NodeResults}|{error,Reason}
+%% add_nodes_if_ref(Nodes,Reference,Options)={ok,NodeResults}|{error,Reason}
+%%
+%% As add_nodes/2,/3 but will only connect to an already existing runtime component
+%% if its reference is the same as the one given as argument.
+add_nodes_if_ref(Nodes,Reference) when list(Nodes) ->
+ gen_server:call(?CONTROLLER,{add_nodes,Nodes,[],Reference,if_ref},?CALL_TIMEOUT).
+
+add_nodes_if_ref(Nodes,Reference,Options) when list(Nodes),list(Options) ->
+ gen_server:call(?CONTROLLER,{add_nodes,Nodes,Options,Reference,if_ref},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% change_options(Options)={ok,NodeResults}|NodeResult|{error,Reason}
+%% change_options(Nodes,Options)={ok,NodeResults}|{error,Reason}
+%% Nodes=[Node,...],
+%% Options= see add_node and add_nodes on available options.
+%%
+%% Change options on all or specified Nodes. This may result in for instance
+%% reinitialization of overloadcheck.
+change_options(Options) when list(Options) ->
+ gen_server:call(?CONTROLLER,{change_options,all,Options},?CALL_TIMEOUT).
+change_options(Nodes,Options) when list(Nodes),list(Options) ->
+ gen_server:call(?CONTROLLER,{change_options,Nodes,Options},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% init_tracing(TracerData)={ok,[{Node,NodeResult}]} | NodeResult | {error,Reason}
+%% init_tracing(TracerList)={ok,[{Node,NodeResult}]} | {error,Reason}
+%% init_tracing(Nodes,TracerData)={ok,[{Node,NodeResult}]}|{error,Reason}
+%% TracerData = [{trace,LogTD} [,{ti,TiTD}]}] | LogTD
+%% LogTD = {HandlerFun,Data} | collector | {relayer,pid()} |
+%% {relayer,CollectingNode} | {ip,IPPortParameters} |
+%% {file,FilePortParameters}
+%% TiTD = {file,FileName} | {file,FileName,TiMFA}
+%% TiMFA = {Module,Function,ArgumentList} initiating a private loopdata
+%% inside the meta-tracer.
+%% TracerList = [{Node,TracerData}],
+%% IPPortParameters = Portno | {Portno, Qsiz}
+%% Qsiz =
+%% FilePortParameters = {Filename, wrap, Tail, {time, WrapTime}, WrapCnt} |
+%% {FileName, wrap, Tail, WrapSize, WrapCnt} |
+%% {FileName, wrap, Tail, WrapSize} |
+%% {FileName, wrap, Tail} | FileName
+%% Tail =/= ""
+%% HandlerFun is a function taking 2 arguments.
+%% Nodes = [node()],
+%% CollectingNode = pid() | node(),
+%% NodeResult = {ok,LogResults} | {error, NReason}
+%% LogResults=[LogResult,...]
+%% LogResult={trace_log,LogRes} | {ti_log,LogRes}
+%% LogRes=ok|{error,Reason}
+%%
+%% Starts tracing on the nodes specified. If just providing a TracerData tracing
+%% will be initiated on all our nodes. If it is the non distributed case, that
+%% means only on the local non distributed node.
+%%
+%% {HandlerFun,Data}
+%% Will use the runtime components own process as tracer and handle all
+%% incomming trace message using HandlerFun.
+%% {relayer,CollectingNode}
+%% The runtime component addressed will act tracer and relay all incomming trace
+%% messages to Node or Pid, if CollectingNode is not a traced node connected
+%% to the controll component, the init_tracing call will return an error.
+%% Note that {relayer, Node} only is syntactical sugar for
+%% {relayer, rpc:call(Node,erlang,whereis,[inviso_rt])}
+%% collector
+%% The runtime component is used as tracer or collector of relayed
+%% trace messages using the default handler writing them to io.
+%% ip | file - will open a trace-port on Node using PortParameters
+init_tracing(TracerDataList) ->
+ gen_server:call(?CONTROLLER,{init_tracing,TracerDataList},?CALL_TIMEOUT).
+
+init_tracing(Nodes,TracerData) when list(Nodes) ->
+ gen_server:call(?CONTROLLER,{init_tracing,Nodes,TracerData},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% stop_tracing(Nodes)={ok,NodeResults}|{error,Reason}
+%% stop_tracing()={ok,NodeResults}|NodeResult
+%% Nodes=[Node,...],
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,State}|{error,Reason}
+%% State=new|idle
+%% Stops tracing on all or specified Nodes. Flushes trace buffert,
+%% closes trace port and removes all trace flags and meta-patterns.
+%% The nodes are called in parallel.
+stop_tracing() ->
+ gen_server:call(?CONTROLLER,{stop_tracing,all},?CALL_TIMEOUT).
+
+stop_tracing(Nodes) when is_list(Nodes) ->
+ gen_server:call(?CONTROLLER,{stop_tracing,Nodes},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% clear()={ok,NodeResults}|NodeResult
+%% clear(Nodes,Options)={ok,NodeResults}|{error,Reason}
+%% clear(Options)={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...],
+%% Options=[Option,...],
+%% Option=keep_trace_patterns|keep_log_files
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,{new,Status}}|{error,Reason}
+%% Status=running|{suspended,SReason}
+%%
+%% Stops all tracing including removing meta-trace patterns. If the node is tracing
+%% or idle, logs belonging to the current tracerdata are removed. Hence the node
+%% is returned to state 'new'. Note that node can still be suspended.
+clear() ->
+ gen_server:call(?CONTROLLER,{clear,all,[]},?CALL_TIMEOUT).
+
+clear(Nodes) when list(Nodes) ->
+ gen_server:call(?CONTROLLER,{clear,Nodes,[]},?CALL_TIMEOUT).
+
+clear(Nodes,Options) when list(Nodes),list(Options) ->
+ gen_server:call(?CONTROLLER,{clear,Nodes,Options},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% flush()={ok,NodeResults} | NodeResult
+%% flush(Nodes)={ok,NodeResults}
+%% Nodes=[Node,...]
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok | {error,Reason}
+%% Sends a flush request to the trace-port driver on the nodes in Nodes.
+%% There will be an error for nodes that are not tracing. It is not an error to
+%% try to flush runtime components not using a trace-port.
+flush() ->
+ gen_server:call(?CONTROLLER,{flush,all},?CALL_TIMEOUT).
+flush(Nodes) when is_list(Nodes) ->
+ gen_server:call(?CONTROLLER,{flush,Nodes},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% stop()=shutdown
+%%
+%% Stops the controll component. Runtime components are left as is. They will
+%% behave according to their dependency values.
+stop() ->
+ case catch gen_server:call(?CONTROLLER,stop,?CALL_TIMEOUT) of
+ shutdown ->
+ shutdown;
+ {'EXIT',{noproc,_}} ->
+ shutdown;
+ {'EXIT',Reason} ->
+ exit(Reason)
+ end.
+%% -----------------------------------------------------------------------------
+
+%% stop_nodes()={ok,NodeResults}|NodeResult
+%% stop_nodes(Nodes)={ok,NodeResults}|{error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason}
+%%
+%% Stops runtime component on Nodes. stop_nodes/0 will if the control component
+%% is running on a distributed node stop all runtime components. And if running
+%% on a non distributed node, stop the local and only runtime component.
+stop_nodes() ->
+ gen_server:call(?CONTROLLER,{stop_nodes,all},?CALL_TIMEOUT).
+stop_nodes(Nodes) when is_list(Nodes) ->
+ gen_server:call(?CONTROLLER,{stop_nodes,Nodes},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% stop_all()={ok,NodeResults}|NodeResult
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason}
+%%
+%% A combination of stop/0 and stop_nodes/0.
+stop_all() ->
+ gen_server:call(?CONTROLLER, stop_all,?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% tp(Nodes,Module,Function,Arity,MatchSpec,Opts)={ok,NodeResults}|{error,Reason}
+%% tp(Nodes,Module,Function,Arity,MatchSpec)={ok,NodeResults}|{error,Reason}
+%% tp(Module,Function,Arity,MatchSpec)={ok,NodeResults}|NodeResult|{error,Reason}
+%% tp(Nodes,PatternList)={ok,NodeResults}|{error,Reason}
+%% tp(PatternList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...]
+%% Module,Function=atom() | '_'
+%% Arity=integer() | '_'
+%% MatchSpec=true|false|[]| matchspec()
+%% PatternList=[Pattern,...],
+%% Pattern={Module,Function,Arity,MatchSpec,Opts},
+%% Opts=[Opt,...]
+%% Opt='only_loaded'; means that the runtime component shall not try to load
+%% a module should it not already be present in the runtime system.
+%% NodeResults=[NodeResult,...]
+%% NodeResult={ok,[Ans]}|{error,Reason},
+%% Ans=integer()|{error,Reason}
+%%
+%% Set trace pattern (global) on specified or all Nodes. The integer replied
+%% if the call was successfully describes the matched number of functions.
+%% The functions without a Nodes argument means all nodes, in a non-distributed
+%% environment it means the local node.
+%% When calling several nodes, the nodes are called in parallel.
+tp(Nodes,Module,Function,Arity,MatchSpec,Opts) ->
+ trace_pattern(Nodes,[{Module,Function,Arity,MatchSpec,Opts}],[global]).
+
+tp(Nodes,Module,Function,Arity,MatchSpec) when list(Nodes) ->
+ trace_pattern(Nodes,[{Module,Function,Arity,MatchSpec,[]}],[global]);
+tp(Module,Function,Arity,MatchSpec,Opts) when atom(Module) ->
+ trace_pattern(all,[{Module,Function,Arity,MatchSpec,Opts}],[global]).
+
+tp(Module,Function,Arity,MatchSpec) ->
+ trace_pattern(all,[{Module,Function,Arity,MatchSpec,[]}],[global]).
+
+tp(Nodes,PatternList) ->
+ trace_pattern(Nodes,PatternList,[global]).
+
+tp(PatternList) ->
+ trace_pattern(all,PatternList,[global]).
+%% -----------------------------------------------------------------------------
+
+%% tpl(Nodes,Module,Function,Arity,MatchSpec)={ok,NodeResults}|{error,Reason}
+%% tpl(Module,Function,Arity,MatchSpec)={ok,NodeResults}|NodeResult|{error,Reason}
+%% tpl(Nodes,PatternList)={ok,NodeResults}|{error,Reason}
+%% tpl(PatternList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% see tp/X for description.
+%%
+%% Set trace pattern (local) on specified or all Nodes. The integer replied
+%% if the command was successfully describes the matched number of functions.
+%% The functions without a Nodes argument means all nodes, in a non-distributed
+%% environment it means the local node.
+%% When calling several nodes, the nodes are called in parallel.
+tpl(Nodes,Module,Function,Arity,MatchSpec,Opts) ->
+ trace_pattern(Nodes,[{Module,Function,Arity,MatchSpec,Opts}],[local]).
+
+tpl(Nodes,Module,Function,Arity,MatchSpec) when list(Nodes) ->
+ trace_pattern(Nodes,[{Module,Function,Arity,MatchSpec,[]}],[local]);
+tpl(Module,Function,Arity,MatchSpec,Opts) when atom(Module) ->
+ trace_pattern(all,[{Module,Function,Arity,MatchSpec,Opts}],[local]).
+
+tpl(Module,Function,Arity,MatchSpec) ->
+ trace_pattern(all,[{Module,Function,Arity,MatchSpec,[]}],[local]).
+
+tpl(Nodes, PatternList) ->
+ trace_pattern(Nodes,PatternList,[local]).
+
+tpl(PatternList) ->
+ trace_pattern(all,PatternList,[local]).
+%% -----------------------------------------------------------------------------
+
+%% ctp(Nodes,Module,Function,Arity)={ok,NodeResults}|{error,Reason}
+%% ctp(Module,Function,Arity)={ok,NodeResults}|NodeResult|{error,Reason}
+%% ctp(Nodes,PatternList)={ok,NodeResults}|{error,Reason}
+%% ctp(PatternList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% PatternList=[{Mod,Func,Arity},...]
+%% see tp/X for other argument descriptions.
+%%
+%% Clear trace pattern (global) on specified or all Nodes. The integer replied
+%% if the call was successfully describes the matched number of functions.
+%% The functions without a Nodes argument means all nodes, in a non-distributed
+%% environment it means the local node.
+%% When calling several nodes, the nodes are called in parallel.
+ctp(Nodes,Module,Function,Arity) ->
+ trace_pattern(Nodes,[{Module,Function,Arity,false,[only_loaded]}],[global]).
+
+ctp(Module,Function,Arity) ->
+ trace_pattern(all,[{Module,Function,Arity,false,[only_loaded]}],[global]).
+
+ctp(Nodes,PatternList) when list(PatternList) ->
+ trace_pattern(Nodes,
+ lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList),
+ [global]).
+
+ctp(PatternList) when list(PatternList) ->
+ trace_pattern(all,
+ lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList),
+ [global]).
+%% -----------------------------------------------------------------------------
+
+%% ctpl(Nodes,Module,Function,Arity)={ok,NodeResults}|{error,Reason}
+%% ctpl(Module,Function,Arity)={ok,NodeResults}|NodeResult|{error,Reason}
+%% ctpl(Nodes,PatternList)={ok,NodeResults}|{error,Reason}
+%% ctpl(PatternList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% see ctp/X for argument description.
+%%
+%% Clear trace pattern (local) on specified or all Nodes. The integer replied
+%% if the call was successfully describes the matched number of functions.
+%% The functions without a Nodes argument means all nodes, in a non-distributed
+%% environment it means the local node.
+%% When calling several nodes, the nodes are called in parallel.
+ctpl(Nodes,Module,Function,Arity) ->
+ trace_pattern(Nodes,[{Module,Function,Arity,false,[only_loaded]}],[local]).
+
+ctpl(Module,Function,Arity) ->
+ trace_pattern(all,[{Module,Function,Arity,false,[only_loaded]}],[local]).
+
+ctpl(Nodes,PatternList) when list(PatternList) ->
+ trace_pattern(Nodes,
+ lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList),
+ [local]).
+
+ctpl(PatternList) when list(PatternList) ->
+ trace_pattern(all,
+ lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList),
+ [local]).
+%% -----------------------------------------------------------------------------
+
+%% Help function doing the control component calling for all tp/X, tpl/X, ctp/X
+%% and ctpl/X functions.
+trace_pattern(Nodes,Patterns,FlagList) ->
+ gen_server:call(?CONTROLLER, {trace_pattern, Nodes, Patterns, FlagList},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+%% -----------------------------------------------------------------------------
+
+%% tf(Nodes,PidSpec,FlagList)={ok,NodeResults}|{error,Reason}
+%% tf(PidSpec,FlagList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% tf(Nodes,TraceConfList)={ok,NodeResults}|{error,Reason}
+%% tf(NodeTraceConfList)={ok,NodeResults}|{error,Reason}
+%% tf(TraceConfList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeTraceConfList=[{Node,TraceConfList}]
+%% TraceConfList=[{PidSpec,FlagList},...],
+%% FlagList=[Flags],
+%% PidSpec=all|new|existing|pid()|locally_registered_name()
+%% Flags= all process trace flags allowed.
+%% NodeResult={ok,[Ans]}|{error,Reason},
+%% Ans=integer() | {error,Reason}
+%%
+%% Set process trace flags on processes on all or specified Nodes. The integer
+%% return if the call was successfully describes the matched number of processes.
+%% The functions without a Nodes argument means all nodes, in a non-distributed
+%% environment it means the local node.
+%% There are many combinations which does not make musch scense. For instance
+%% specifying a certain pid at all nodes. Or an empty TraceConfList for all
+%% nodes.
+%% When calling several nodes, the nodes are called in parallel.
+tf(Nodes,PidSpec,FlagList) when list(Nodes),list(FlagList) ->
+ trace_flags(Nodes,[{PidSpec, FlagList}],true).
+
+tf(Nodes,TraceConfList) when list(Nodes),list(TraceConfList) ->
+ trace_flags(Nodes,TraceConfList,true);
+tf(PidSpec,FlagList) when list(FlagList) ->
+ trace_flags(all,[{PidSpec,FlagList}],true).
+
+tf(ArgList) when list(ArgList) -> % This one has triple functionality!
+ case ArgList of
+ [{_Process,Flags}|_] when list(Flags),atom(hd(Flags))-> % A call to all nodes.
+ trace_flags(all,ArgList,true);
+ [{_Node,TraceConfList}|_] when list(TraceConfList),tuple(hd(TraceConfList)) ->
+ trace_flags(ArgList,true);
+ [{_Node,_TraceConfList,_How}|_] ->
+ trace_flags(ArgList);
+ [] -> % Stupid but allowed.
+ trace_flags(all,ArgList,true) % Actually doesn't matter which we choose.
+ end.
+%% -----------------------------------------------------------------------------
+
+%% ctf(Nodes,PidSpec,FlagList)={ok,NodeResults}|{error,Reason}
+%% ctf(PidSpec,FlagList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% ctf(Nodes,TraceConfList)={ok,NodeResults}|{error,Reason}
+%% ctf(TraceConfList)={ok,NodeResults}|NodeResult|{error,Reason}
+%% see tf/X for arguments.
+%%
+%% Clear process trace flags on all or specified Nodes. The integer replied
+%% if the command was successfully describes the matched number of processes.
+%% The functions without a Nodes argument means all nodes, in a non-distributed
+%% environment it means the local node.
+%% When calling several nodes, the nodes are called in parallel.
+ctf(Nodes,PidSpec,FlagList) when list(Nodes),list(FlagList) ->
+ trace_flags(Nodes,[{PidSpec,FlagList}],false).
+
+ctf(Nodes,TraceConfList) when list(Nodes),list(TraceConfList) ->
+ trace_flags(Nodes,TraceConfList,false);
+ctf(PidSpec,FlagList) when list(FlagList) ->
+ trace_flags(all,[{PidSpec,FlagList}],false).
+
+ctf(TraceConfList) when list(TraceConfList) ->
+ trace_flags(all,TraceConfList,false).
+%% -----------------------------------------------------------------------------
+
+%% ctf_all(Nodes)={ok,NodeResults}|{error,Reason}
+%% ctf_all()={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason},
+%%
+%% Clears all trace flags on all or specified nodes. Just for convenience.
+ctf_all() ->
+ gen_server:call(?CONTROLLER,{trace_flags,all,[{all,[all]}],false},?CALL_TIMEOUT).
+
+ctf_all(Nodes) ->
+ gen_server:call(?CONTROLLER,{trace_flags,Nodes,[{all,[all]}],false},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% Help function to tf/X and ctf/X making the call to the control component.
+trace_flags(Nodes, TraceConfList, How) ->
+ gen_server:call(?CONTROLLER, {trace_flags, Nodes, TraceConfList, How},?CALL_TIMEOUT).
+
+trace_flags(NodeTraceConfList,How) ->
+ gen_server:call(?CONTROLLER,{trace_flags,NodeTraceConfList,How},?CALL_TIMEOUT).
+
+trace_flags(NodeTraceConfListHow) ->
+ gen_server:call(?CONTROLLER,{trace_flags,NodeTraceConfListHow},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+%% -----------------------------------------------------------------------------
+
+%% tpm_localnames()={ok,NodeResults}|NodeResult|{error,Reason}
+%% tpm_localnames(Nodes)={ok,NodeResults}|{error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,N}|{error,Reason}, Note that N can only be 0 or 1.
+%%
+%% Quick version for setting meta-trace patterns on erlang:register/2. It uses
+%% a default CallFunc and ReturnFunc in the meta-tracer server.
+%% The main purpose of this function is to create ti-log entries for printing
+%% the aliases for process instead of their process identities.
+tpm_localnames() ->
+ tpm_localnames(all).
+
+tpm_localnames(Nodes) ->
+ gen_server:call(?CONTROLLER,{meta_pattern,Nodes,{local_register,[]}},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% tpm_globalnames()={ok,NodeResults}|NodeResult|{error,Reason}
+%% tpm_globalnames(Nodes)={ok,NodeResults}|{error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={SubResult,SubResult}
+%% SubResult={ok,N}|{error,Reason}, Note that N can only be 0 or 1.
+%% As tpm_locanames/0,/1 but for registering names with global. Note that this
+%% actually involves setting meta trace patterns on two functions in global.
+tpm_globalnames() ->
+ tpm_globalnames(all).
+
+tpm_globalnames(Nodes) ->
+ gen_server:call(?CONTROLLER,{meta_pattern,Nodes,{global_register,[]}},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% init_tpm(Mod,Func,Arity,CallFunc)={ok,NodeResults}|NodeResult|{error,Reason}
+%% init_tpm(Nodes,Mod,Func,Arity,CallFunc)={ok,NodeResults}|{error,Reason}
+%% init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc)=
+%% {ok,NodeResults}|NodeResult|{error,Reason}
+%% init_tpm(Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc)=
+%% {ok,NodeResults}|{error,Reason}
+%%
+%% Mod,Func=Pointing out the function which shall be meta traced, atom().
+%% Arity=As above, integer().
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason}
+%%
+%% InitFunc,RemoveFunc={Module,Function}|fun(), functions being called when
+%% to initialize the public loopdata structure, and to reset it.
+%% InitFunc(Mod,Func,Arity,PublLD)->{ok,NewPublLD,Output}
+%% Supposed to initialize whatever needs to be done before
+%% handling any incoming meta-trace message for the Mod:Func/Arity.
+%% RemoveFunc(Mod,Func,Arity,PublLD)->{ok,NewPublLD}
+%% Called when meta tracing of Mod:Func/Arity is stopped. It is supposed
+%% to clear datastructures away from the PublLD.
+%% Initializes the public loopdata for this function. Note that we can not use wildcards
+%% here (even if it is perfectly legal in Erlang). It also sets the CallFunc and
+%% ReturnFunc for the meta traced function. The function is hence ready to be
+%% meta traced with either tpm/5 or tpm_ms/5.
+%% When calling several nodes, the nodes are called in parallel.
+init_tpm(Mod,Func,Arity,CallFunc) ->
+ init_tpm(all,Mod,Func,Arity,CallFunc).
+
+init_tpm(Nodes,Mod,Func,Arity,CallFunc) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,
+ Nodes,
+ {init_tpm,
+ [Mod,Func,Arity,CallFunc]}},
+ ?CALL_TIMEOUT).
+
+init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ init_tpm(all,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc).
+
+init_tpm(Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,
+ Nodes,
+ {init_tpm,
+ [Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}},
+ ?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% tpm(Mod,Func,Arity,MS)={ok,NodeResults}|NodeResult|{error,Reason}
+%% tpm(Nodes,Mod,Func,Arity,MS)={ok,NodeResults}|{error,Reason}
+%% tpm(Mod,Func,Arity,MS,CallFunc)={ok,NodeResults}|NodeResults|{error,Reason}
+%% tpm(Nodes,Mod,Func,Arity,MS,CallFunc)={ok,NodeResults}|{error,Reason}
+%% tpm(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc)=
+%% {ok,NodeResults}|NodeResults|{error,Reason}
+%% tpm(Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc)=
+%% {ok,NodeResults}|{error,Reason}
+%%
+%% Mod,Func=atom() and not '_'.
+%% Arity=integer()
+%% MS=list(), matchspecification.
+%% Nodes=List of nodenames.
+%% InitFunc,CallFunc,ReturnFunc,RemoveFunc={Module,Function}|fun(),
+%% functions being called when these functions are called by the meta trace
+%% server at certain events.
+%% CallFunc(CallingPid,ActualArgList,PublLD)->{ok,NewPrivLD,Output}
+%% ReturnFunc(CallingPid,ReturnValue,PublLD)->{ok,NewPrivLD,Output}
+%% When a call respectively return_from trace message arrives for the meta
+%% traced function, the corresponding function is called.
+%% The ReturnFunc must handle the fact that a return_from message arrives
+%% for a call which was never noticed. This because the message queue of the
+%% meta tracer may have been emptied.
+%%
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,N}|{error,Reason}, Note that N can only be 0 or 1.
+%%
+%% Activates meta-tracing in the inviso_rt_meta tracer. Except when using tpm/6,/8
+%% and /9 the function must first have been initiated using init_tpm. If running
+%% a non distributed system the variants without Node shall be used. If running
+%% in a distributed environment, without Node means all our nodes.
+%% When calling several nodes, the nodes are called in parallel.
+tpm(Mod,Func,Arity,MS) ->
+ tpm(all,Mod,Func,Arity,MS).
+
+tpm(Nodes,Mod,Func,Arity,MS) when integer(Arity) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{tpm,[Mod,Func,Arity,MS]}});
+tpm(Mod,Func,Arity,MS,CallFunc) when integer(Arity) ->
+ tpm(all,Mod,Func,Arity,MS,CallFunc).
+
+tpm(Nodes,Mod,Func,Arity,MS,CallFunc) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{tpm,[Mod,Func,Arity,MS,CallFunc]}}).
+
+tpm(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ tpm(all,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc).
+
+tpm(Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,
+ Nodes,
+ {tpm,
+ [Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}},
+ ?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% Same as tpm/X but the meta tracer will append {tracer,Tracer} to any enable
+%% list in a trace body action term.
+tpm_tracer(Mod,Func,Arity,MS) ->
+ tpm_tracer(all,Mod,Func,Arity,MS).
+
+tpm_tracer(Nodes,Mod,Func,Arity,MS) when integer(Arity) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{tpm_tracer,[Mod,Func,Arity,MS]}},
+ ?CALL_TIMEOUT);
+tpm_tracer(Mod,Func,Arity,MS,CallFunc) when integer(Arity) ->
+ tpm_tracer(all,Mod,Func,Arity,MS,CallFunc).
+
+tpm_tracer(Nodes,Mod,Func,Arity,MS,CallFunc) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{tpm_tracer,[Mod,Func,Arity,MS,CallFunc]}}).
+
+tpm_tracer(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ tpm_tracer(all,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc).
+
+tpm_tracer(Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,
+ Nodes,
+ {tpm_tracer,
+ [Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}},
+ ?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% tpm_ms(Mod,Func,Arity,MSname,MS)={ok,NodeResults}|NodeResult|{error,Reason}
+%% tpm_ms(Nodes,Mod,Func,Arity,MSname,MS)={ok,NodeResults}|{error,Reason}
+%% Nodes= List of all nodes where the function shall be carried out.
+%% Mod,Func=Pointing out the function to which we shall add a match-spec., atom().
+%% Arity=As above, integer().
+%% MSname=A name to be used if this MS shall be removed later. term().
+%% MatchSpec=List of match specification, Remember {return_trace}
+%% if expecting return_from messages.
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,1}|{ok,0}|{error,Reason} where {ok,1} indicates that
+%% setting the matchspecification for the function succeeded.
+%%
+%% This function adds a list of match-specs to the already existing ones. It
+%% uses an internal database to keep track of existing match-specs. If the
+%% match-spec does not result in any meta traced functions (for whatever reason),
+%% the MS is not saved in the database. The previously known match-specs are
+%% not removed.
+%% The function must previously have been initiated in order for this function
+%% to add a match-spec.
+%% When calling several nodes, the nodes are called in parallel.
+tpm_ms(Mod,Func,Arity,MSname,MS) ->
+ tpm_ms(all,Mod,Func,Arity,MSname,MS).
+
+tpm_ms(Nodes,Mod,Func,Arity,MSname,MS) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{tpm_ms,[Mod,Func,Arity,MSname,MS]}},
+ ?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% Same as tpm_ms/5, /6 but the meta tracer will append {tracer,Tracer} to any enable
+%% list in a trace body action term.
+tpm_ms_tracer(Mod,Func,Arity,MSname,MS) ->
+ tpm_ms_tracer(all,Mod,Func,Arity,MSname,MS).
+
+tpm_ms_tracer(Nodes,Mod,Func,Arity,MSname,MS) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{tpm_ms_tracer,[Mod,Func,Arity,MSname,MS]}},
+ ?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% ctpm_ms(Mod,Func,Arity,MSname)={ok,NodeResults}|NodeResult|{error,Reason}
+%% ctpm_ms(Nodes,Mod,Func,Arity,MSname)={ok,NodeResults}|{error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason}
+%%
+%% Removes a named match-spec from the meta traced function. Note that it never
+%% is a fault to remove an MS. Not even from a function which is non existant.
+%% When calling several nodes, the nodes are called in parallel.
+ctpm_ms(Mod,Func,Arity,MSname) ->
+ ctpm_ms(all,Mod,Func,Arity,MSname).
+
+ctpm_ms(Nodes,Mod,Func,Arity,MSname) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{ctpm_ms,[Mod,Func,Arity,MSname]}},
+ ?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% ctpm(Mod,Func,Arity)={ok,NodeResults}|NodeResult|{error,Reason}
+%% ctpm(Node,Mod,Func,Arity)={ok,NodeResults}|{error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason}
+%%
+%% Removes the meta trace pattern for the function, means stops generating output
+%% for this function. The public LD may be cleared by the previously entered
+%% RemoveFunc.
+%% When calling several nodes, the nodes are called in parallel.
+ctpm(Mod,Func,Arity) ->
+ ctpm(all,Mod,Func,Arity).
+
+ctpm(Nodes,Mod,Func,Arity) ->
+ gen_server:call(?CONTROLLER,
+ {meta_pattern,Nodes,{ctpm,[Mod,Func,Arity]}},
+ ?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% ctpm_localnames()={ok,NodeResults}|NodeResult|{error,Reason}
+%% ctpm_localnames(Nodes)={ok,NodeResults}|{error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason}
+%%
+%% Removes meta-trace pattern for erlang:register/2, previously set by tpm_localnames.
+%% When calling several nodes, the nodes are called in parallel.
+ctpm_localnames() ->
+ ctpm_localnames(all).
+
+ctpm_localnames(Nodes) ->
+ gen_server:call(?CONTROLLER,{meta_pattern,Nodes,{remove_local_register,[]}},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% ctpm_localnames()={ok,NodeResults}|NodeResult|{error,Reason}
+%% ctpm_localnames(Nodes)={ok,NodeResults}|{error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={SubResult,Subresult}
+%% SubResult=ok|{error,Reason}
+%%
+%% Removes meta-trace pattern for the register functions in global. Note that there
+%% are two of them.
+%% When calling several nodes, the nodes are called in parallel.
+ctpm_globalnames() ->
+ ctpm_globalnames(all).
+
+ctpm_globalnames(Nodes) ->
+ gen_server:call(?CONTROLLER,{meta_pattern,Nodes,{remove_global_register,[]}},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% ctp_all(Nodes)={ok,NodeResults}|{error,Reason}
+%% ctp_all()={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason},
+%%
+%% Clears all both global and local trace patterns on all or specified nodes.
+%% Does not effect meta patterns.
+ctp_all() ->
+ gen_server:call(?CONTROLLER,{ctp_all,all},?CALL_TIMEOUT).
+
+ctp_all(Nodes) ->
+ gen_server:call(?CONTROLLER,{ctp_all,Nodes},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% suspend(Nodes,Reason)={ok,NodeResults}|{error,Reason}
+%% suspend(Reason)={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node],
+%% Reason=term(); supposed to describe the reason why suspended.
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason},
+%%
+%% Suspend all or specified Nodes with reason Reason. Suspend means that all
+%% process trace flags are removed and all meta-patterns.
+suspend(Nodes, Reason) ->
+ gen_server:call(?CONTROLLER,{suspend,Reason,Nodes},?CALL_TIMEOUT).
+
+suspend(Reason) ->
+ gen_server:call(?CONTROLLER,{suspend,Reason,all},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% cancel_suspension(Nodes)={ok,NodeResults}|{error,Reason}
+%% cancel_suspension()={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok|{error,Reason},
+%% Cancel suspension on all or specified Nodes. Note that this does not imply
+%% that "business" is resumed as before. You must reactivate flags and meta-patter
+%% your self.
+cancel_suspension(Nodes) ->
+ gen_server:call(?CONTROLLER,{cancel_suspension,Nodes},?CALL_TIMEOUT).
+
+cancel_suspension() ->
+ gen_server:call(?CONTROLLER,{cancel_suspension,all},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% get_status(Nodes)={ok,NodeResults}|{error,Reason}
+%% get_status()={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,{State,Status}}|{error,Reason},
+%% State=new|idle|tracing
+%% Status=running|{suspended,SReason}
+%%
+%% Get Status form all or specified runtime components.
+get_status(Nodes) when list(Nodes) ->
+ gen_server:call(?CONTROLLER,{get_status,Nodes},?CALL_TIMEOUT).
+
+get_status() ->
+ gen_server:call(?CONTROLLER,{get_status,all},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% get_tracerdata()={ok,NodeResults}|NodeResult|{error,Reason}
+%% get_tracerdata(Nodes)={ok,NodeResults}|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeResult={ok,NResult}|{error,Reason},
+%% NResult=TracerData|no_tracerdata
+%% TracerData will be exactly as it was specified when doing init_tracing.
+%%
+%% Get TracerData form all or specified runtime components.
+get_tracerdata() ->
+ gen_server:call(?CONTROLLER,{get_tracerdata,all},?CALL_TIMEOUT).
+
+get_tracerdata(Nodes) when is_list(Nodes) ->
+ gen_server:call(?CONTROLLER,{get_tracerdata,Nodes},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% list_logs(TracerData)={ok,NodeResults}|NodeResult|{error,Reason}
+%% list_logs(NodeList)={ok,NodeResults}|{error,Reason}
+%% list_logs()={ok,NodeResults}|NodeResult|{error,Reason}
+%% TracerData see init_tracing/1/2
+%% NodeList=[NodeSpec,...]
+%% NodeSpec=Node|{Node,TracerData}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,FileList}|{ok,no_log}|{error,Reason}
+%% FileList=[FileType,...], one or more of different types.
+%% FileType={trace_log,Dir,Files}|{ti_log,Dir,Files}
+%% Files=[FileNameWithOutPath,...]
+%%
+%% Ask local or specified runtime components for now existing logs given
+%% TracerData. If TracerData is left out, the runtime components TracerData,
+%% if existing, will be used instead.
+list_logs() ->
+ gen_server:call(?CONTROLLER,list_logs,?CALL_TIMEOUT).
+
+list_logs(TracerDataOrNodesList) when list(TracerDataOrNodesList) ->
+ gen_server:call(?CONTROLLER,{list_logs,TracerDataOrNodesList},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+%% fetch_log(LogSpecList,DestDir,Prefix)={ok,NodeResults}|{error,not_distributed}|
+%% {error,Reason}
+%% fetch_log(DestDir,Prefix)={ok,NodeResults}|{error,not_distributed}|
+%% {error,Reason}
+%% fetch_log(ToNode,DestDir,Prefix)=
+%% fetch_log(ToNode,LogSpecList,DestDir,Prefix)=
+%% DestDir=string(), to where the fetched files shall be placed.
+%% Prefix=string(), prefix on locally saved fetched files.
+%% LogSpecList=[LogSpec,...],
+%% LogSpec={Node,FileSpecList}|Node|{Node,TracerData}
+%% TracerData=see init_tracing/1/2
+%% FileSpecList=[{trace_log,Dir,FileList},{ti_log,Dir,FileList}]
+%% where each tuple-item is optional.
+%% FileList=[RemoteFileName,...]
+%% ToNode=atom()
+%% NodeResult={Conclusion,ResultFileSpec}|no_log|{error,NReason}
+%% Conclusion=complete|incomplete
+%% ResultFileSpec=[{trace_log,FileResults},{ti_log,FileResults}]
+%% FileResults=[FileResult,...]
+%% FileResult={ok,FileName}|{error,FReason}
+%% NReason=own_node|Reason
+%% FReason = {file_open,{posix(),FileName}} |
+%% {file_open,{posix(),RemoteFileName}}
+%% {file_open,{posix(),[DestDir,Prefix,RemoteFileName]}}
+%% {file_write,{posix(),FileName}} |
+%% {truncated,FileName}
+%% {truncated,{Reason,FileName}}
+%% posix() - an atom which is named from the Posix error codes used in
+%% Unix, and in the runtime libraries of most C compilers.
+%% See module file in Kernel Reference manual.
+%%
+%% Copies logfiles over distributed erlang to ToNode. This
+%% function can only be used in a distributed system.
+%% The resulting transfered files will have the prefix Prefix and will be
+%% located in DestDir.
+%% Note that the client process using this function will wait until all files
+%% are moved. The job can be cancelled, causing any already copied files to be
+%% removed, by simply terminating the waiting client process.
+fetch_log(DestDir,Prefix) when list(DestDir),list(Prefix) ->
+ gen_server:call(?CONTROLLER,{fetch_log,node(),all,DestDir,Prefix},infinity).
+
+fetch_log(ToNode,DestDir,Prefix) when atom(ToNode),list(DestDir),list(Prefix) ->
+ gen_server:call(?CONTROLLER,{fetch_log,ToNode,all,DestDir,Prefix},infinity);
+
+fetch_log(LogSpecList,DestDir,Prefix) when list(LogSpecList),list(DestDir),list(Prefix) ->
+ gen_server:call(?CONTROLLER,{fetch_log,node(),LogSpecList,DestDir,Prefix},infinity).
+
+fetch_log(ToNode,LogSpecList,DestDir,Prefix)
+ when atom(ToNode),list(LogSpecList),list(DestDir),list(Prefix) ->
+ gen_server:call(?CONTROLLER,{fetch_log,ToNode,LogSpecList,DestDir,Prefix},infinity).
+%% ------------------------------------------------------------------------------
+
+%% delete_log(Nodes,TracerData)={ok,NodeResults}|{error,Reason}
+%% delete_log(NodeSpecList)={ok,NodeResults}|{error,Reason}
+%% delete_log(Spec)={ok,NodeResults}|NodeResult|{error,Reason}
+%% delete_log(TracerData)={ok,NodeResults}|NodeResult|{error,Reason}
+%% delete_log()={ok,NodeResults}|NodeResult|{error,Reason}
+%% Nodes=[Node,...],
+%% NodeSpecList=[{Node,Spec},...]
+%% Spec=[AbsPathFileName,...]|LogSpecs
+%% LogSpecs=[LogSpec,...]
+%% LogSpec={trace_log,Dir,[FileNameWithoutPath,...]}|
+%% {ti_log,Dir,[FileNameWithoutPath,...]}
+%% TracerData = see init_tracing/1/2
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,no_log}|{ok,LogInfos}|{ok,FileInfos}
+%% LogInfos=[LogInfo,...]
+%% LogInfo={trace_log,FileInfos}|{ti_log,FileInfos}
+%% FileInfos=[FileInfo,...]
+%% FileInfo={ok,FileName}|{error,Reason} whether FileName contains
+%% full path or not depends on if AbsPathFileName or LogSpec was
+%% used when specifying the files.
+%%
+%% Deletes listed files or files corresponding to TracerData from specified
+%% or all Nodes. If no TracerData or list of files is specified in the call the
+%% TracerData at Node will be used to identify log files to delete.
+delete_log() ->
+ gen_server:call(?CONTROLLER,{delete_log,all},?CALL_TIMEOUT).
+delete_log(What) ->
+ gen_server:call(?CONTROLLER,{delete_log,What},?CALL_TIMEOUT).
+delete_log(Nodes,Spec) ->
+ gen_server:call(?CONTROLLER,{delete_log,Nodes,Spec},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% subscribe()= same as subscribe(self())
+%% subscribe(Pid)=ok|{error,Reason}
+%% Pid=pid(),
+%% Reason =
+%% Add Pid or self() to event sending list. Note that it is possible to add a
+%% pid several times and that the Pid then will receive several event messages.
+%% All events will be sent to all subscribers in the event sending list.
+%% Event={inviso_event,ControllerPid,erlang:localtime(),Msg},
+%% Msg=
+%% {connected, Node, {Tag, {State, Status}}}
+%% {disconnected, Node, not_applicable}
+%% {state_change, Node, {State, Status}}
+%% {port_down, Node, Reason}
+%% Node = node() | local_runtime (when running in a non-distributed
+%% environment)
+subscribe() ->
+ gen_server:call(?CONTROLLER,{subscribe,self()},?CALL_TIMEOUT).
+
+subscribe(Pid) ->
+ gen_server:call(?CONTROLLER,{subscribe,Pid},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% unsubscribe()= same as unsubscribe(self())
+%% unsubscribe(Pid)=ok
+%% Pid=pid(),
+%%
+%% Remove, if present, first occurrence of Pid or self() from event sending
+%% list. Note that it is not an error to remove a non existing subscription.
+unsubscribe() ->
+ gen_server:call(?CONTROLLER,{unsubscribe,self()},?CALL_TIMEOUT).
+
+unsubscribe(Pid) ->
+ gen_server:call(?CONTROLLER,{unsubscribe,Pid},?CALL_TIMEOUT).
+%% ------------------------------------------------------------------------------
+
+
+
+%% debuging the controller
+%% ----------------------------------------------------------------------------
+state() ->
+ ?CONTROLLER ! state.
+
+%% debuging the runtime component
+state(Node) ->
+ ?CONTROLLER ! {state, Node}.
+
+%%% end of file
diff --git a/lib/inviso/src/inviso_c.erl b/lib/inviso/src/inviso_c.erl new file mode 100644 index 0000000000..ecbd5b0778 --- /dev/null +++ b/lib/inviso/src/inviso_c.erl @@ -0,0 +1,1335 @@ +%% ``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 via the world wide web 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. +%% +%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. +%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +%% AB. All Rights Reserved.'' +%% +%% $Id$ +%% +%% Author: Ann-Marie L�f, [email protected] +%% Lennart �hman, [email protected] +%% +%% Description: The controlling part of the trace tool Inviso +%% +%% This code implements the inviso control component meant to be run on an Erlang +%% node doing any tracing. It can also be used in a non distributed system where +%% the control component and the runtime component will run on the same virtual +%% machine. +%% The control component is not meant to be started by a supervisor but rather +%% directly by the user when needed. +%% This module does not provide any APIs to users. Those are found in inviso.erl. +%% This module merly has the gen_server call-backs. +%% ------------------------------------------------------------------------------ + +-module(inviso_c). +-behavior(gen_server). + + +%% ------------------------------------------------------------------------------ +%% gen_server callbacks. +%% ------------------------------------------------------------------------------ +-export([init/1, + handle_call/3,handle_cast/2,handle_info/2, + terminate/2, + code_change/3]). +%% ------------------------------------------------------------------------------ + +%% ------------------------------------------------------------------------------ +%% Exported internal functions (used in spawn of help process). +%% ------------------------------------------------------------------------------ +-export([log_rec_init/4]). + +%% ------------------------------------------------------------------------------ + +%% ------------------------------------------------------------------------------ +%% Records. +%% ------------------------------------------------------------------------------ + +%% #state +%% Record used in the loopdata. +-record(state,{ + nodes=[], % [#node,...] + distributed, % false | true + subscribers=[], % [{pid(),monitor_ref()},...] + rt_options=[{dependency,{infinity,node()}},{overload, default}] + }). +%% ------------------------------------------------------------------------------ + +%% #node +%% Record storing information about a runtime component connected to this control +%% component. +-record(node,{ + node, % [atom(),...] + pid, % pid() + vsn, + ref, % monitor_ref() + tag % term() + }). +%% ------------------------------------------------------------------------------ + +%% ------------------------------------------------------------------------------ +%% Macros used in this module. +%% ------------------------------------------------------------------------------ + +-define(RUNTIME,inviso_rt). % The module API name of the runtime. +%% ------------------------------------------------------------------------------ + + + +%% ============================================================================== +%% Controller component implmentation. +%% ============================================================================== + +init({_Parent,Options}) -> + process_flag(trap_exit,true), + case check_options(Options,start) of + {ok,Options2} -> + LoopData=initiate_state(Options2), + {ok,LoopData}; + Error -> + {stop,Error} + end. +%% ------------------------------------------------------------------------------ + +handle_call({subscribe,Pid},_From,LD) when pid(Pid) -> + MRef=erlang:monitor(process,Pid), + {reply,ok,LD#state{subscribers=[{Pid,MRef}|LD#state.subscribers]}}; +handle_call({subscribe,Faulty},_From,LD) -> + {reply,{error,{badarg,Faulty}},LD}; +handle_call({unsubscribe,Pid},_From,LD) -> + case lists:keysearch(Pid,1,LD#state.subscribers) of + {value,{_,MRef}} -> + erlang:demonitor(MRef), + {reply,ok,LD#state{subscribers=lists:keydelete(Pid,1,LD#state.subscribers)}}; + false -> + {reply,ok,LD} + end; +handle_call({add_nodes,Nodes,Opts,Tag,Condition},_From,LD) -> + case check_options(Opts,add_node) of + {ok,Opts2} -> + Opts3=merge_options(LD#state.rt_options,Opts2), + {NewLD,Reply}=do_add_nodes(Nodes,LD,Opts3,Tag,Condition), + {reply,adapt_reply(NewLD,Reply),NewLD}; + Error -> + {reply,Error,LD} + end; +handle_call({change_options,Nodes,Opts},_From,LD) -> + case check_options(Opts,add_node) of + {ok,Opts2} -> + {reply,adapt_reply(LD,do_change_option(Nodes,Opts2,LD)),LD}; + Error -> + {reply,Error,LD} + end; +handle_call({init_tracing,TracerDataList},_From,LD) -> + {reply,adapt_reply(LD,do_init_tracing(TracerDataList,LD)),LD}; +handle_call({init_tracing,Nodes,TracerData},_From,LD) when list(Nodes) -> + TracerDataList= + lists:map(fun(N)->{N,TracerData} end,started_trace_nodes(Nodes,LD)), + {reply,adapt_reply(LD,do_init_tracing(TracerDataList,LD)),LD}; +handle_call({init_tracing,Nodes,_TracerData},_From,LD) -> + {reply,{error,{badarg,Nodes}},LD}; +handle_call({trace_pattern,Nodes,Patterns,FlagList},_From,LD) -> + {reply,adapt_reply(LD,distribute_tp(Nodes,Patterns,FlagList,LD)),LD}; +handle_call({trace_flags,Nodes,Args,How},_From,LD) -> + {reply,adapt_reply(LD,distribute_tf(Nodes,Args,How,LD)),LD}; +handle_call({trace_flags,NodeArgs,How},_From,LD) -> + {reply,distribute_tf(NodeArgs,How,LD),LD}; % Always distributed here. +handle_call({trace_flags,NodeArgHows},_From,LD) -> + {reply,distribute_tf(NodeArgHows,LD),LD}; % Always distributed here. +handle_call({meta_pattern,Nodes,Args},_From,LD) -> + {reply,adapt_reply(LD,distribute_metapattern(Nodes,Args,LD)),LD}; +handle_call({ctp_all,Nodes},_From,LD) -> + {reply,adapt_reply(LD,do_ctp_all(Nodes,LD)),LD}; +handle_call({suspend,Reason,Nodes},_From,LD) -> + {reply,adapt_reply(LD,do_suspend(Nodes,Reason,LD)),LD}; +handle_call({cancel_suspension,Nodes},_From,LD) -> + {reply,adapt_reply(LD,do_cancel_suspension(Nodes,LD)),LD}; +handle_call({stop_tracing,Nodes},_From,LD) -> + {reply,adapt_reply(LD,do_stop_tracing(Nodes,LD)),LD}; +handle_call({get_status,Nodes},_From,LD) -> + {reply,adapt_reply(LD,do_get_status(Nodes,LD)),LD}; +handle_call({get_tracerdata,Nodes},_From,LD) -> + {reply,adapt_reply(LD,do_get_tracerdata(Nodes,LD)),LD}; +handle_call(list_logs,_From,LD) -> + {reply,adapt_reply(LD,do_list_logs(all,LD)),LD}; +handle_call({list_logs,TracerDataOrNodesList},_From,LD) -> + {reply,adapt_reply(LD,do_list_logs(TracerDataOrNodesList,LD)),LD}; +handle_call({fetch_log,ToNode,Spec,Dest,Prefix},From,LD) -> + case LD#state.distributed of + true -> % It is a distributed system. + do_fetch_log(ToNode,Spec,Dest,Prefix,From,LD), + {noreply,LD}; % Reply will come from collector pid. + false -> % Stupidity! you dont want this! + {reply,{error,not_distributed},LD} + end; +handle_call({delete_log,NodesOrNodeSpecs},_From,LD) -> + {reply,adapt_reply(LD,do_delete_log(NodesOrNodeSpecs,LD)),LD}; +handle_call({delete_log,Nodes,Specs},_From,LD) when list(Nodes) -> + Reply=do_delete_log(lists:map(fun(N)->{N,Specs} end,Nodes),LD), + {reply,adapt_reply(LD,Reply),LD}; +handle_call({delete_log,FaultyNodes,_Specs},_From,LD) -> + {reply,{error,{badarg,FaultyNodes}},LD}; +handle_call({clear,Nodes,Options},_From,LD) -> + {reply,adapt_reply(LD,do_clear(Nodes,LD,Options)),LD}; +handle_call({flush,Nodes},_From,LD) -> + {reply,adapt_reply(LD,do_flush(Nodes,LD)),LD}; +handle_call({stop_nodes,Nodes},_From,LD) -> + {NewLD,Reply}=do_stop_nodes(Nodes,LD), + {reply,adapt_reply(NewLD,Reply),NewLD}; +handle_call(stop,_From,LD) -> + {stop,normal,shutdown,LD}; +handle_call(stop_all,_From,LD) -> + {NewLD,Reply}=do_stop_nodes(started_trace_nodes(all,LD),LD), + {stop,normal,adapt_reply(NewLD,Reply),NewLD}; +handle_call(Request,_From,LD) -> %% for debug purpose only + {reply,{error,{invalid_request,Request}},LD}. + +handle_cast(_Request,LD) -> % There are no casts. + {noreply,LD}. + +handle_info({connect,_Node,Pid,_VSN,Tag},LD) -> % From connecting runtime. + {noreply,do_confirm_connection(Pid,Tag,LD)}; +handle_info({trace_event,Event},LD) -> % A runtime component issues an event. + send_to_subscribers(Event,LD), % Relay to our subscribers. + {noreply,LD}; +handle_info({'DOWN',Ref,process,_From,Info},LD) -> % A runtime component died? + {noreply,do_down_msg(Ref,Info,LD)}; +handle_info(state,LD) -> % For debug purposes. + io:format("trace_c state: ~p~n",[LD]), + {noreply,LD}; +handle_info(_Msg,LD) -> + {noreply,LD}. + +terminate(_Reason, _LD) -> + ok. + +code_change(_OldVsn, LoopData, _Extra) -> + {ok, LoopData}. +%% ----------------------------------------------------------------------------- + + +%% ----------------------------------------------------------------------------- +%% Handle call help functions. +%% ----------------------------------------------------------------------------- + +%% Help function which adapts a reply based on if this is a distributed system +%% or not. The first argument indicates distribution (='true'). +%% If we are not running a distributed system, all node references are removed. +adapt_reply(#state{distributed=Distributed},Reply) -> + adapt_reply(Distributed,Reply); +adapt_reply(true,Reply) -> % We are distributed. + Reply; +adapt_reply(false,{ok,[{_Node,LocalReply}]}) -> + LocalReply; +adapt_reply(false,{ok,[]}) -> + {error,not_an_added_node}; % �R DET H�R VERKLIGEN RIKTIGT? +adapt_reply(false,Reply) -> + Reply. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% First level help functions to handler functions. +%% ============================================================================== + +%% Function starting a runtime component at the nodes in Nodes. If doing non +%% distributed, Nodes will be a list of nonode@nohost. +%% Returns {NewLD,{ok,Replies}} or {LD,{error,Reason}}. +do_add_nodes(Nodes,LD,Options,Tag,Condition) -> + do_add_nodes_2(Nodes,LD,Options,Tag,Condition,[]). + +do_add_nodes_2([Node|Tail],LD,Options,Tag,Condition,Replies) -> + case find(fun is_started/2,LD#state.nodes,Node) of + {value,true} -> % Already started by us. + do_add_nodes_2(Tail,LD,Options,Tag,Condition,[{Node,{ok,already_added}}|Replies]); + no_match -> + case ?RUNTIME:start(Node,Options,Tag,Condition) of + {node_info,_Node,Pid,VSN,State,Status,new} -> + NewLD=do_add_nodes_3(Node,LD,Tag,Pid,VSN,State,Status), + do_add_nodes_2(Tail,NewLD,Options,Tag,Condition,[{Node,{ok,new}}|Replies]); + {node_info,_Node,Pid,VSN,State,Status,{tag,Tag2}} -> + NewLD=do_add_nodes_3(Node,LD,Tag,Pid,VSN,State,Status), + do_add_nodes_2(Tail,NewLD,Options,Tag,Condition, + [{Node,{ok,{adopted,State,Status,Tag2}}}|Replies]); + Error -> + do_add_nodes_2(Tail,LD,Options,Tag,Condition,[{Node,Error}|Replies]) + end + end; +do_add_nodes_2([],LD,_,_,_,Replies) -> + {LD,{ok,lists:reverse(Replies)}}; +do_add_nodes_2(Faulty,LD,_,_,_,_) -> % Not a list of nodes. + {LD,{error,{badarg,Faulty}}}. + +do_add_nodes_3(Node,LD,Tag,Pid,VSN,State,Status) -> + MRef=erlang:monitor(process,Pid), + NodeRec=#node{node=Node,pid=Pid,vsn=VSN,ref=MRef,tag=Tag}, + send_to_subscribers({connected,Node,{Tag,{State,Status}}},LD), + _NewLD=set_node_rec(NodeRec,LD). +%% ------------------------------------------------------------------------------ + +%% Function calling change_options sequensially on all nodes in Nodes. +%% Returns {ok,Replies} or {error,Reason}. +do_change_option(Nodes,Options,LD) -> + do_change_option_2(started_trace_nodes(Nodes,LD#state.nodes),Options,LD,[]). + +do_change_option_2([Node|Tail],Options,LD,Replies) -> + case get_node_rec(Node,LD) of + Rec when record(Rec,node) -> + Answer=?RUNTIME:change_options(Rec#node.pid,Options), + do_change_option_2(Tail,Options,LD,[{Node,Answer}|Replies]); + Error -> + do_change_option_2(Tail,Options,LD,[{Node,Error}|Replies]) + end; +do_change_option_2([],_Options,_LD,Replies) -> + {ok,Replies}; +do_change_option_2(Faulty,_,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function which calls the runtime components in TracerDataList and initiates +%% the tracing. TracerDataList may either be just one tracer data which shall be +%% applied to all runtime components, or a list of nodes and tracerdata. +do_init_tracing(TracerDataList,LD) -> + case inviso_rt_lib:is_tracerdata(TracerDataList) of + true -> % Then we must add all our nodes. + List=lists:map(fun(N)->{N,TracerDataList} end,started_trace_nodes(all,LD)), + do_init_tracing_2(List,LD,[]); + false -> % Assume it is a list of {Node,Tracerdata} + do_init_tracing_2(TracerDataList,LD,[]) + end. + +do_init_tracing_2([{Node,TracerData}|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + Rec when is_record(Rec,node) -> + case check_modify_tracerdata(TracerData,LD) of + {ok,NewTracerData} -> % Tracerdata ok and node->pid. + Reply=?RUNTIME:init_tracing(Rec#node.pid,NewTracerData), + do_init_tracing_2(Tail,LD,[{Node,Reply}|Replies]); + {error,Reason} -> % Unknown tracerdata. + do_init_tracing_2(Tail,LD,[{Node,{error,Reason}}|Replies]) + end; + {error,Reason} -> + do_init_tracing_2(Tail,LD,[{Node,{error,Reason}}|Replies]) + end; +do_init_tracing_2([_|Tail],LD,Replies) -> % Just ignore item we don't understand. + do_init_tracing_2(Tail,LD,Replies); % Will not end up in Replies either. +do_init_tracing_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_init_tracing_2(What,_LD,_) -> + {error,{badarg,What}}. +%% ----------------------------------------------------------------------------- + +%% Function setting trace patterns on all nodes mentioned in Nodes. Uses a +%% parallel mechanism in the runtime component. +%% Returns {ok,Reply} or {error,Reason}. +distribute_tp(all,Patterns,FlagList,LD) -> + distribute_tp(started_trace_nodes(all,LD),Patterns,FlagList,LD); +distribute_tp(Nodes,Patterns,FlagList,LD) when list(Nodes) -> + RTpids=lists:map(fun(N)->case get_node_rec(N,LD) of + #node{pid=Pid} -> + {Pid,N}; + Error -> + {Error,N} + end + end, + Nodes), + {ok,?RUNTIME:trace_patterns_parallel(RTpids,Patterns,FlagList)}; +distribute_tp(Faulty,_,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function which in parallel sets trace flags on all nodes in Nodes. Can be +%% either a list of node names or 'all'. Note that in the reply list there will be an +%% error indicating nodes not reachable. Either because such a node disappeared or +%% because it is not one of "our" nodes. +%% Returns {ok,Reply} or {error,Reason}. +distribute_tf(all,Args,How,LD) -> + distribute_tf(started_trace_nodes(all,LD),Args,How,LD); +distribute_tf(Nodes,Args,How,LD) when list(Nodes) -> + RTpids=lists:map(fun(Node)-> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + {Pid,Node}; + Error -> % Not an added node then! + {Error,Node} + end + end, + Nodes), + {ok,?RUNTIME:trace_flags_parallel(RTpids,Args,How)}; +distribute_tf(Faulty,_,_,_) -> + {error,{badarg,Faulty}}. + +%% As above but specific args for each node. +distribute_tf(NodeArgs,How,LD) when list(NodeArgs) -> + RTpidArgs=lists:map(fun({Node,Args})-> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + {Pid,Node,Args}; + Error -> % Not an added node then! + {Error,Node} + end + end, + NodeArgs), + {ok,?RUNTIME:trace_flags_parallel(RTpidArgs,How)}; +distribute_tf(Faulty,_,_) -> + {error,{badarg,Faulty}}. + +%% As above but both specific args for each node and How (set or remove flag). +distribute_tf(NodeArgHows,LD) when list(NodeArgHows) -> + RTpidArgHows= + lists:map(fun({Node,Args,How}) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + {Pid,Node,Args,How}; + Error -> % Not an added node then! + {Error,Node} + end + end, + NodeArgHows), + {ok,?RUNTIME:trace_flags_parallel(RTpidArgHows)}; +distribute_tf(Faulty,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function making a parallel call to all nodes in Nodes, calling the generic +%% meta-tracer function stated in Args. +%% Returns {ok,Reply} or {error,Reason}. +distribute_metapattern(all,Args,LD) -> + distribute_metapattern(started_trace_nodes(all,LD),Args,LD); +distribute_metapattern(Nodes,Args,LD) when list(Nodes) -> + RTpids=lists:map(fun(N)->case get_node_rec(N,LD) of + #node{pid=Pid} -> + {Pid,N}; + Error -> + {Error,N} + end + end, + Nodes), + {ok,?RUNTIME:meta_tracer_call_parallel(RTpids,Args)}; +distribute_metapattern(Faulty,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function clearing all trace patterns on all node mentioned in Nodes. +%% Returns {ok,Reply} or {error,Reason}. +do_ctp_all(Nodes,LD) -> + do_ctp_all_2(started_trace_nodes(Nodes,LD),LD,[]). + +do_ctp_all_2([Node|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + do_ctp_all_2(Tail,LD,[{Node,?RUNTIME:clear_all_tp(Pid)}|Replies]); + Error -> + do_ctp_all_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_ctp_all_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_ctp_all_2(Faulty,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function suspending all runtime components mentioned in Nodes. +%% Returns {ok,Reply} or {error,Reason}. +do_suspend(Nodes,Reason,LD) -> + do_suspend_2(started_trace_nodes(Nodes,LD),Reason,LD,[]). + +do_suspend_2([Node|Tail],Reason,LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + Answer=?RUNTIME:call_suspend(Pid,Reason), + do_suspend_2(Tail,Reason,LD,[{Node,Answer}|Replies]); + Error -> + do_suspend_2(Tail,Reason,LD,[{Node,Error}|Replies]) + end; +do_suspend_2([],_Reason,_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_suspend_2(Faulty,_,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function cancelling a suspension at the runtime components mentioned in Nodes. +%% Returns {ok,Reply} or {error,Reason}. +do_cancel_suspension(Nodes,LD) -> + do_cancel_suspension_2(started_trace_nodes(Nodes,LD),LD,[]). + +do_cancel_suspension_2([Node|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + Answer=?RUNTIME:cancel_suspension(Pid), + do_cancel_suspension_2(Tail,LD,[{Node,Answer}|Replies]); + Error -> + do_cancel_suspension_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_cancel_suspension_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_cancel_suspension_2(Faulty,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function which stops tracing on all nodes in Nodes. The function is performed +%% in parallel over the nodes to get a more precise stop-time. +%% Return {ok,Reply} or {error,Reason}. +do_stop_tracing(all,LD) -> + do_stop_tracing(started_trace_nodes(all,LD),LD); +do_stop_tracing(Nodes,LD) when list(Nodes) -> + RTpids=lists:map(fun(N)->case get_node_rec(N,LD) of + #node{pid=Pid} -> + {Pid,N}; + Error -> + {Error,N} + end + end, + Nodes), + {ok,?RUNTIME:stop_tracing_parallel(RTpids)}; +do_stop_tracing(Faulty,_) -> + {error,{badarg,Faulty}}. +%% ----------------------------------------------------------------------------- + +%% Function fetching the current status of the runtime components in Nodes. +%% Returns {ok,Reply} or {error,Reason}. +do_get_status(Nodes,LD) -> + do_get_status_2(started_trace_nodes(Nodes,LD),LD,[]). + +do_get_status_2([Node|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + do_get_status_2(Tail,LD,[{Node,?RUNTIME:get_status(Pid)}|Replies]); + Error -> + do_get_status_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_get_status_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_get_status_2(Faulty,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function retrieving the tracerdata for the nodes in Nodes. +%% Returns {ok,Reply} or {error,Reason}. +do_get_tracerdata(Nodes,LD) -> + do_get_tracerdata_2(started_trace_nodes(Nodes,LD),LD,[]). + +do_get_tracerdata_2([Node|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + do_get_tracerdata_2(Tail,LD,[{Node,?RUNTIME:get_tracerdata(Pid)}|Replies]); + Error -> + do_get_tracerdata_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_get_tracerdata_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_get_tracerdata_2(Faulty,_,_) -> + {error,{badarg,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Function that lists all logfiles associated with a certain tracerdata +%% or the current tracerdata should no tracerdata be mentioned. +%% Returns {ok,Replies} or {error,Reason}. +do_list_logs(all,LD) -> % When doing all known nodes. + do_list_logs_2(started_trace_nodes(all,LD),LD,[]); +do_list_logs(TracerDataOrNodesList,LD) -> + case inviso_rt_lib:is_tracerdata(TracerDataOrNodesList) of + true -> % It is tracerdata for this node. + do_list_logs_2([{node(),TracerDataOrNodesList}],LD,[]); + false -> + do_list_logs_2(TracerDataOrNodesList,LD,[]) + end. + +do_list_logs_2([{Node,TD}|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + case check_modify_tracerdata(TD,LD) of + {ok,TracerData} -> + Answer=?RUNTIME:list_logs(Pid,TracerData), + do_list_logs_2(Tail,LD,[{Node,Answer}|Replies]); + Error -> + do_list_logs_2(Tail,LD,[{Node,Error}|Replies]) + end; + Error -> + do_list_logs_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_list_logs_2([Node|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + Answer=?RUNTIME:list_logs(Pid), + do_list_logs_2(Tail,LD,[{Node,Answer}|Replies]); + Error -> + do_list_logs_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_list_logs_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_list_logs_2(Other,_LD,_Replies) -> + {error,{badarg,Other}}. +%% ----------------------------------------------------------------------------- + +%% Function fetching logfiles using distributed erlang. This function does not +%% return anything significant. This since the reply to the client is always +%% sent by the CollectPid unless there is badarg fault detected before the +%% CollectPid is spawned. Note that this function sends a list of fetchers from +%% which the CollectPid shall expect replies. +%% We try to catch some bad arguments like Destination and Prefix not being +%% strings. However the fact that they are lists does not guarantee they are +%% proper strings. +do_fetch_log(ToNode,all,Dest,Prefix,From,LD) -> + do_fetch_log(ToNode,started_trace_nodes(all,LD),Dest,Prefix,From,LD); +do_fetch_log(ToNode,Specs,Dest,Prefix,From,LD) when list(Dest),list(Prefix) -> + CollectPid=spawn_link(ToNode,?MODULE,log_rec_init,[self(),Dest,Prefix,From]), + do_fetch_log_2(Specs,LD,CollectPid,[],[]); +do_fetch_log(_ToNode,_Specs,Dest,Prefix,From,_LD) -> + gen_server:reply(From,{error,{badarg,[Dest,Prefix]}}). + +do_fetch_log_2([{Node,Spec}|Rest],LD,CollectPid,Fetchers,Replies) -> + if + Node==node() -> % This is plain stupid! + do_fetch_log_2(Rest,LD,CollectPid,Fetchers,[{Node,{error,own_node}}|Replies]); + true -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + {NewFetchers,NewReplies}= + do_fetch_log_3(Fetchers, + Replies, + Node, + ?RUNTIME:fetch_log(Pid,CollectPid,Spec)), + do_fetch_log_2(Rest,LD,CollectPid,NewFetchers,NewReplies); + Error -> % Most likely the node does not exist. + do_fetch_log_2(Rest,LD,CollectPid,Fetchers,[{Node,Error}|Replies]) + end + end; +do_fetch_log_2([Node|Rest],LD,CollectPid,Fetchers,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + {NewFetchers,NewReplies}= + do_fetch_log_3(Fetchers,Replies,Node,?RUNTIME:fetch_log(Pid,CollectPid)), + do_fetch_log_2(Rest,LD,CollectPid,NewFetchers,NewReplies); + Error -> % Most likely the node does not exist. + do_fetch_log_2(Rest,LD,CollectPid,Fetchers,[{Node,Error}|Replies]) + end; +do_fetch_log_2([],_,CollectPid,Fetchers,Replies) -> + CollectPid ! {?MODULE,self(),Fetchers,Replies}; +do_fetch_log_2(FaultySpec,_,CollectPid,_Fetchers,_Replies) -> + CollectPid ! {?MODULE,self(),[],{error,{badarg,FaultySpec}}}. + +do_fetch_log_3(Fetchers,Replies,Node,{ok,Fetcher}) -> + {[{Node,Fetcher}|Fetchers],Replies}; +do_fetch_log_3(Fetchers,Replies,Node,{complete,no_log}) -> + {Fetchers,[{Node,{complete,no_log}}|Replies]}; +do_fetch_log_3(Fetchers,Replies,Node,{error,Reason}) -> + {Fetchers,[{Node,{error,Reason}}|Replies]}. +%% ----------------------------------------------------------------------------- + +%% Function removing files from the runtime components. We can either ask for +%% all files associated with the current tracerdata to be removed, or provide +%% tracerdata or a list of files to be removed. +%% Returns the client reply. +do_delete_log(all,LD) -> + do_delete_log_2(started_trace_nodes(all,LD),LD,[]); +do_delete_log(NodeSpecs,LD) -> + case inviso_rt_lib:is_tracerdata(NodeSpecs) of + true -> % It is tracerdata for all nodes. + do_delete_log_2(lists:map(fun(N)->{N,NodeSpecs} end, + started_trace_nodes(all,LD)), + LD,[]); + false -> + if + list(NodeSpecs),list(hd(NodeSpecs)) -> % A list of files. + do_delete_log_2(lists:map(fun(N)->{N,NodeSpecs} end, + started_trace_nodes(all,LD)), + LD,[]); + true -> % Then use it as is. + do_delete_log_2(NodeSpecs,LD,[]) + end + end. + +do_delete_log_2([{Node,Spec}|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + do_delete_log_2(Tail,LD,[{Node,?RUNTIME:delete_log(Pid,Spec)}|Replies]); + Error -> + do_delete_log_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_delete_log_2([Node|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + do_delete_log_2(Tail,LD,[{Node,?RUNTIME:delete_log(Pid)}|Replies]); + Error -> + do_delete_log_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_delete_log_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_delete_log_2(Faulty,_,_) -> + {error,{badarg,Faulty}}. +%% ----------------------------------------------------------------------------- + +%% Function removing files from runtime components. +%% Returns {ok,Replies} or {error,Reason}. +do_clear(Nodes,LD,Options) -> + do_clear_2(started_trace_nodes(Nodes,LD),LD,Options,[]). + +do_clear_2([Node|Tail],LD,Options,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + do_clear_2(Tail,LD,Options,[{Node,?RUNTIME:clear(Pid,Options)}|Replies]); + Error -> + do_clear_2(Tail,LD,Options,[{Node,Error}|Replies]) + end; +do_clear_2([],_LD,_Options,Replies) -> + {ok,lists:reverse(Replies)}; +do_clear_2(FaultyNodes,_LD,_Options,_Replies) -> + {error,{badarg,FaultyNodes}}. +%% ----------------------------------------------------------------------------- + +%% Function doing a flush trace-port. +%% Returns {ok,Replies} or {error,Reason}. +do_flush(Nodes,LD) -> + do_flush_2(started_trace_nodes(Nodes,LD),LD,[]). + +do_flush_2([Node|Rest],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{pid=Pid} -> + do_flush_2(Rest,LD,[{Node,?RUNTIME:flush(Pid)}|Replies]); + Error -> + do_flush_2(Rest,LD,[{Node,Error}|Replies]) + end; +do_flush_2([],_LD,Replies) -> + {ok,lists:reverse(Replies)}; +do_flush_2(FaultyNodes,_LD,_Replies) -> + {error,{badarg,FaultyNodes}}. +%% ----------------------------------------------------------------------------- + +%% Function stopping runtime components. We can only stop runtime components +%% belonging to this control component. +%% Returns {NewLoopdata,Reply}. +do_stop_nodes(Nodes,LD) -> + do_stop_nodes_2(started_trace_nodes(Nodes,LD),LD,[]). + +do_stop_nodes_2([Node|Tail],LD,Replies) -> + case get_node_rec(Node,LD) of + #node{ref=MRef} -> + erlang:demonitor(MRef), + case ?RUNTIME:stop(Node) of + ok -> + NewLD=delete_node_rec(Node,LD), + send_to_subscribers({disconnected,Node,void},LD), + do_stop_nodes_2(Tail,NewLD,[{Node,ok}|Replies]); + Error -> + do_stop_nodes_2(Tail,LD,[{Node,Error}|Replies]) + end; + Error -> + do_stop_nodes_2(Tail,LD,[{Node,Error}|Replies]) + end; +do_stop_nodes_2([],LD,Replies) -> + {LD,{ok,Replies}}; +do_stop_nodes_2(Faulty,LD,_) -> + {LD,{error,{badarg,Faulty}}}. +%% ----------------------------------------------------------------------------- + +%% Function being called when a runtime component sends a connect message to +%% the controlcomponent. The control component then confirms that is has indeed +%% taken on that runtime component. +%% Returns a new loopdata structure. +do_confirm_connection(Pid,Tag,LD) -> + case ?RUNTIME:confirm_connection(Pid,Tag) of + {node_info,Node,_Pid,VSN,State,Status,_Tag} -> + MRef=erlang:monitor(process,Pid), % We must monitor it from now on. + Rec=#node{node=Node,vsn=VSN,tag=Tag,ref=MRef,pid=Pid}, + send_to_subscribers({connected,Node,{Tag,{State,Status}}},LD), + set_node_rec(Rec,LD); % Makes new loopdata. + _Error -> % Strange, it wanted us as control!? + LD + end. +%% ------------------------------------------------------------------------------ + +%% Function handling an incomming DOWN message. This can be one of our runtime +%% components terminating, or a process subscribing to events. Send a trace-event +%% to subscribers if it was a runtime terminating and remove it from +%% our list of runtime components. +%% Note that if a subscriber has subscribed multiple times to events, we will get +%% multiple DOWN messages too, since we have monitored that process multiple +%% times. It is therefore sufficient to remove just one subscription entry here +%% each time (remove on the monitor reference!). +%% Returns a new LoopData structure. +do_down_msg(Ref,Info,LD) -> + case find(fun ref/2,LD#state.nodes,Ref) of + {value,Node} -> % Yes it was one of our nodes. + send_to_subscribers({disconnected,Node,Info},LD), + delete_node_rec(Node,LD); + no_match -> % Not one of our nodes. + case lists:keysearch(Ref,2,LD#state.subscribers) of + {value,{_Pid,_}} -> % It was a subscriber terminating. + LD#state{subscribers=lists:keydelete(Ref,2,LD#state.subscribers)}; + false -> % Not one of our subscribers either. + LD % Do nothing then. + end + end. +%% ------------------------------------------------------------------------------ + + + +%% ============================================================================== +%% Help functions. +%% ============================================================================== + + +%% Help function which inspects options to start and add_node. Returns a new +%% list of options in {ok,Options} or {error,Reason}. +check_options(Options, Context) -> + check_options_2(Options, Context, []). + +check_options_2([],_Context,Result) -> + {ok,Result}; +check_options_2([{subscribe,Pid}|OptionsTail],start,Result) when pid(Pid) -> + check_options_2(OptionsTail,start,[{subscribe,Pid}|Result]); +check_options_2([{unsubscribe,Pid}|OptionsTail],start,Result) when pid(Pid) -> + check_options_2(OptionsTail,start,[{unsubscribe,Pid}|Result]); +check_options_2([{dependency,How}|OptionsTail],Context,Result) -> + check_options_2(OptionsTail,Context,[{dependency,How}|Result]); +check_options_2([{overload,How}|OptionsTail],Context,Result) -> + check_options_2(OptionsTail,Context,[{overload,How}|Result]); +check_options_2([overload|OptionsTail],Context,Result) -> + check_options_2(OptionsTail,Context,[overload|Result]); +check_options_2([UnKnown|_],_Context,_Result) -> + {error,{unknown_option,UnKnown}}; +check_options_2(UnKnown,_Context,_Result) -> + {error,{unknown_option,UnKnown}}. +%% ------------------------------------------------------------------------------ + +%% Help function initiating the #state structure, i.e the loopdata. Since there +%% are some initial values from the record defaults when creating a new #state, +%% those must be compared with possibly specified Options. Specified Options shall +%% of course override defaults. +%% Note that it is not the control component's responsibility to understand all +%% options later given to a runtime component. It mearly stores them in rt_options +%% so they can be passed to the runtime component at start-up. +%% Returns a loopdata structure. +initiate_state(Options) -> + ResultingOptions=merge_options((#state{})#state.rt_options,Options), + LD1=initiate_state_2(ResultingOptions,#state{rt_options=[]}), + case node() of % Finally set the distribution flag. + nonode@nohost -> + LD1#state{distributed=false}; + _ -> + LD1#state{distributed=true} + end. + +initiate_state_2([{subscribe,Proc}|Tail],LD) when pid(Proc);atom(Proc)-> + MRef=erlang:monitor(process,Proc), + initiate_state_2(Tail,LD#state{subscribers=[{Proc,MRef}|LD#state.subscribers]}); +initiate_state_2([Opt|Tail],LD) when tuple(Opt),size(Opt)>=1 -> + initiate_state_2(Tail,initiate_state_3(element(1,Opt),Opt,LD)); +initiate_state_2([Opt|Tail],LD) when atom(Opt) -> + initiate_state_2(Tail,initiate_state_3(Opt,Opt,LD)); +initiate_state_2([_|Tail],LD) -> + initiate_state_2(Tail,LD); +initiate_state_2([],LD) -> + LD. + +initiate_state_3(OptName,Opt,LD) -> + case initiate_state_is_rt_option(OptName) of + true -> % Yes, it shall be part of the rt_options. + LD#state{rt_options=[Opt|LD#state.rt_options]}; + false -> % Ignore the option then. + LD + end. + +%% This is the only place you have to change should there be more rt_options +%% introduced. +initiate_state_is_rt_option(overload) -> true; +initiate_state_is_rt_option(dependency) -> true; +initiate_state_is_rt_option(_) -> false. +%% ------------------------------------------------------------------------------ + +%% Help function which takes a list of default options and a list of overriding +%% options. The function builds a return value consisting of all default options +%% unless they are overridden by a overriding option. +%% An option can be either {Param,.....} or Param. I.e either a tuple with zero +%% or more values associated with the Parameter, or just an atom. +merge_options([], Options) -> + Options; +merge_options([T|DefaultTail],Options) when tuple(T),size(T)>=1 -> + merge_options(DefaultTail,merge_options_2(element(1,T),T,Options)); +merge_options([Param|DefaultTail],Options) when atom(Param) -> + merge_options(DefaultTail,merge_options_2(Param,Param,Options)); +merge_options([_|DefaultTail],Options) -> % Strange, bad default option! + merge_options(DefaultTail,Options). + +merge_options_2(Param,Opt,Options) -> + case merge_options_find(Param,Options) of + true -> + Options; + false -> + [Opt|Options] + end. + +merge_options_find(Param,[T|_]) when tuple(T),element(1,T)==Param -> + true; +merge_options_find(Param,[Param|_]) -> + true; +merge_options_find(Param,[_|Rest]) -> + merge_options_find(Param,Rest); +merge_options_find(_,[]) -> + false. +%% ------------------------------------------------------------------------------ + +%% Help function which transforms certain parts of a tracer data. Those are +%% parts which must be transformed at the controlnode like node to pid mappings. +%% It also checks the formatting of the tracerdata since runtime components +%% does not accept too badly formatted tracerdata. +%% Returns {ok,NewTraceData} or {error,Reason}. +check_modify_tracerdata(TracerData,LoopData) when list(TracerData) -> + case lists:keysearch(trace,1,TracerData) of + {value,{_,TraceTD}} -> % Examine the trace part. + case check_modify_tracerdata(TraceTD,LoopData) of + {ok,NewTraceTD} -> + {ok,lists:keyreplace(trace,1,TracerData,{trace,NewTraceTD})}; + {error,Reason} -> % The trace part was faulty. + {error,Reason} % Ruins everything :-) + end; + false -> % Unusual, but no trace part. + {ok,TracerData} % No modifications necessary. + end; +check_modify_tracerdata(collector,_LoopData) -> + {ok, collector}; +check_modify_tracerdata({relayer,Collector},_LoopData) when is_pid(Collector) -> + {ok,{relayer,Collector}}; +check_modify_tracerdata({relayer,Collector},LoopData) when is_atom(Collector) -> + case get_node_rec(Collector,LoopData) of + Rec when is_record(Rec,node) -> % Collector is a known node. + {ok,{relayer,Rec#node.pid}}; + {error,not_an_added_node} -> + {error,{not_an_added_node,Collector}} + end; +check_modify_tracerdata({Type,Data},_LoopData) when Type==ip;Type==file -> + {ok,{Type,Data}}; +check_modify_tracerdata({Handler,Data},_LoopData) when function(Handler) -> + {ok,{Handler,Data}}; +check_modify_tracerdata(Data,_LoopData) -> + {error,{bad_tracerdata,Data}}. +%% ----------------------------------------------------------------------------- + +%% Help function sending an event to all processes subscribing to events. +%% Note that the function manipulates the from Node indicator incase we are not +%% running in a distributed system. +%% Returns nothing significant. +send_to_subscribers(Msg={Event,_Node,Data},LD) -> + AdaptedMsg= + case LD#state.distributed of + true -> + Msg; + false -> + {Event,local_runtime,Data} + end, + TraceEvent={inviso_event,self(),erlang:localtime(),AdaptedMsg}, + send_to_subscribers_2(LD#state.subscribers,TraceEvent). + +send_to_subscribers_2([],_) -> + ok; +send_to_subscribers_2([{Subscriber,_}|Tail],TraceEvent) -> + Subscriber ! TraceEvent, + send_to_subscribers_2(Tail,TraceEvent). +%% ----------------------------------------------------------------------------- + +%% Help function converting the Nodes parameter to known nodes. Actually today +%% it only converts the all atom to all known nodes. +%% Returns a list of nodes. +started_trace_nodes(all,LoopData) -> + lists:map(fun(N)->N#node.node end,LoopData#state.nodes); +started_trace_nodes(Nodes,_) -> + Nodes. +%% ------------------------------------------------------------------------------ + +%% Help function searching through a list of elements looking for an element +%% containing Key. How the element shall be interpreted is done by the Fun. +%% Returns {value,Element} or 'no_match'. +%% Fun=fun(Element,Key)={return,Value} | false +find(_,[],_Key) -> + no_match; +find(Fun,[H|T],Key) -> + case Fun(H,Key) of + {return,Value}-> + {value,Value}; + _ -> + find(Fun,T,Key) + end. +%% ----------------------------------------------------------------------------- + + +%% ----------------------------------------------------------------------------- +%% Functions handling the nodes datastructure part of the #state. +%% #state.nodes is a list of #node. +%% ----------------------------------------------------------------------------- + +%% Function used to build find fun, looking for a certain #node with its monitoring +%% reference set to Ref. Useful when finding out if a DOWN message comes from one +%% of our runtime components. +ref(#node{ref=Ref,node=Node},Ref) -> + {return,Node}; +ref(_,_) -> + false. +%% ----------------------------------------------------------------------------- + +%% use in find/3 +%% Function used to build find fun, finding out if we have a node with the node +%% name Node. +is_started(#node{node=Node},Node) -> + {return,true}; +is_started(_,_) -> + false. +%% ----------------------------------------------------------------------------- + +%% Help function replacing or adding an entry for a node. Works on either a list +%% of #node or a loopdata structure. Returns a new list of #node or a loopdata struct. +set_node_rec(Rec,LD=#state{nodes=NodeList}) -> + LD#state{nodes=set_node_rec_2(Rec,NodeList)}. + +set_node_rec_2(Rec,[]) -> + [Rec]; +set_node_rec_2(Rec,[NodeRec|Tail]) when NodeRec#node.node==Rec#node.node -> + [Rec|Tail]; +set_node_rec_2(Rec,[NodeRec|Tail]) -> + [NodeRec|set_node_rec_2(Rec,Tail)]. +%% ------------------------------------------------------------------------------ + +%% Help function finding a node record for Node in a list of #node or in loopdata. +%% Returns the #node in question or {error,not_an_added_node}. +get_node_rec(Node,NodeList) when list(NodeList) -> + get_node_rec_2(Node,NodeList); +get_node_rec(Node,#state{nodes=NodeList}) -> + get_node_rec_2(Node,NodeList). + +get_node_rec_2(_Node,[]) -> + {error,not_an_added_node}; +get_node_rec_2(Node,[NodeRec|_]) when NodeRec#node.node==Node -> + NodeRec; +get_node_rec_2(Node,[_NodeRec|Tail]) -> + get_node_rec_2(Node,Tail). +%% ------------------------------------------------------------------------------ + +%% Help function removing a #node from either a list of #node or from a loopdata +%% structure. Returns a new list of #node or a new loopdata structure. +delete_node_rec(Node,LD=#state{nodes=NodeList}) -> + LD#state{nodes=delete_node_rec_2(Node,NodeList)}; +delete_node_rec(Node,NodeList) when list(NodeList) -> + delete_node_rec_2(Node,NodeList). + +delete_node_rec_2(_,[]) -> + []; +delete_node_rec_2(#node{node=Node},[#node{node=Node}|Tail]) -> + Tail; +delete_node_rec_2(Node,[#node{node=Node}|Tail]) -> + Tail; +delete_node_rec_2(Node,[NRec|Tail]) -> + [NRec|delete_node_rec_2(Node,Tail)]. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================= +%% Implementation of the help process receiving all logs from the runtime +%% components. This process is referred to as the CollectPid. +%% It is responsible for sending the reply back to the control component +%% client. If a runtime component becomes suspended, the CollectPid is +%% alerted by the DOWN message. +%% Note that it may take some time before this process responds back to the client. +%% Therefore the client must wait for 'infinity'. The job of transferring the +%% files can be costly. Therefore it is a good idea to stop if no one is really +%% interested in the result. This collector process monitors the From client in +%% order to learn if the job can be cancelled. That will also be a possibility +%% for a client to willfully cancel a fetch job. +%% ============================================================================= + +%% Intitial function on which the control component spawns. Note that the start +%% must be done in two steps since the runtime components must be informed of +%% the CollectPid. But the CollectPid must also know from which runtime components +%% it can expect files from. +%% InitialReplies: contains {Node,Result} for nodes from where there will be no +%% files, but which must be part of the final reply. +log_rec_init(Parent,Dest,Prefix,From={ClientPid,_}) -> + receive + {?MODULE,Parent,Fetchers,InitialReplies} -> + RTs=lists:map(fun({N,F})-> + {N,erlang:monitor(process,F),void,void,void} + end, + Fetchers), + CMRef=erlang:monitor(process,ClientPid), % Monitor the client. + case log_rec_loop(Dest,Prefix,RTs,InitialReplies,CMRef) of + Reply when list(Reply) -> % It is an ok value. + gen_server:reply(From,{ok,Reply}); + {error,Reason} -> + gen_server:reply(From,{error,Reason}); + false -> % The client terminated, no response. + true % Simply terminate, fetchers will notice. + end + end. + +log_rec_loop(_Dest,_Prefix,[],Replies,_CMRef) -> % All nodes done! + Replies; % This is the final reply. +log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) -> + receive + {Node,open,{FType,RemoteFName}} -> + case lists:keysearch(Node,1,RTs) of + {value,{_,MRef,_,_,_}} -> + {NewRTs,NewReplies}= + log_rec_open(Dest,Prefix,Node,MRef,FType,RemoteFName,RTs,Replies), + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef); + false -> + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + {Node,open_failure,{FType,RemoteFName}} -> + case lists:keysearch(Node,1,RTs) of + {value,{_,MRef,_,_,_}} -> + {NewRTs,NewReplies}= + log_rec_open_failure(Node,MRef,FType,RemoteFName,RTs,Replies), + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef); + false -> + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + {Node,payload,Bin,FPid} -> % A chunk of data from a fetcher. + case lists:keysearch(Node,1,RTs) of + {value,{_,_,_,_,void}} -> % Node has no file open here. + FPid ! {self(),cancel_transmission}, % No use sending more to me. + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef); % Simply ignore payload. + {value,{_Node,MRef,FType,FName,FD}} -> + case log_rec_payload(Node,MRef,FType,FName,FD,Bin,RTs,Replies) of + {ok,{NewRTs,NewReplies}} -> + FPid ! {self(),chunk_ack}, % For flow control. + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef); + {error,{NewRTs,NewReplies}} -> + FPid ! {self(),cancel_transmission}, % No use sending more to me. + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef) + end; + false -> % Node is not part of transfere. + FPid ! {self(),cancel_transmission}, % No use sending more to me. + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + {Node,end_of_file} -> + case lists:keysearch(Node,1,RTs) of + {value,{_,_,_,_,void}} -> % Node has no file open here. + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef); + {value,{_,MRef,FType,FName,FD}} -> + {NewRTs,NewReplies}= + log_rec_eof(Node,MRef,FType,FName,FD,RTs,Replies), + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef); + false -> + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + {Node,end_of_transmission} -> % This runtime is done! + case lists:keysearch(Node,1,RTs) of + {value,{_Node,MRef,_,_,_}} -> + erlang:demonitor(MRef), + log_rec_loop(Dest,Prefix, + lists:keydelete(Node,1,RTs), + log_rec_mkreply(Node,complete,Replies), + CMRef); + false -> % Strange, not one of our nodes. + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + {Node,incomplete} -> % This runtime is done (with errors). + case lists:keysearch(Node,1,RTs) of + {value,{_,MRef,FType,FName,FD}} -> + erlang:demonitor(MRef), + {NewRTs,NewReplies}= + log_rec_incomplete(Node,FType,FName,FD,RTs,Replies), + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef); + false -> % Not our, or not anylonger. + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + {Node,{error,Reason}} -> % Remote file read_error. + case lists:keysearch(Node,1,RTs) of + {value,{_,MRef,FType,FName,FD}} -> + {NewRTs,NewReplies}= + log_rec_error(Node,MRef,FType,FName,FD,RTs,Reason,Replies), + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef); + false -> + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + {'DOWN',CMRef,process,_,_} -> % The client got tired waiting. + log_rec_cancel(Dest,RTs,Replies), % Close and remove all files. + false; % Indicate no response message. + {'DOWN',Ref,process,_P,_Info} -> + case lists:keysearch(Ref,2,RTs) of + {value,{Node,_,FType,FName,FD}} -> + {NewRTs,NewReplies}= + log_rec_incomplete(Node,FType,FName,FD,RTs,Replies), + log_rec_loop(Dest,Prefix,NewRTs,NewReplies,CMRef); + false -> % Not our, or not anylonger. + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end; + _ -> + log_rec_loop(Dest,Prefix,RTs,Replies,CMRef) + end. + +%% Help function opening a new target file on the receiver. It returns +%% {NewRTs,NewReplies}. +%% Note that we must protect us against that some of the strings are not proper +%% strings, but contains garbage. +log_rec_open(Dest,Prefix,Node,MRef,FType,RemoteFName,RTs,Replies) -> + case catch log_rec_open_2(Dest,Prefix,Node,MRef,FType,RemoteFName,RTs,Replies) of + {'EXIT',Reason} -> + NewRTs=lists:keyreplace(Node,1,RTs,{Node,MRef,void,void,void}), + NewReplies= + log_rec_addreply(Node, + FType, + {error,{file_open,{Reason,[Dest,Prefix,RemoteFName]}}},Replies), + {NewRTs,NewReplies}; + Result -> + Result + end. + +log_rec_open_2(Dest,Prefix,Node,MRef,FType,RemoteFName,RTs,Replies) -> + FName=Prefix++RemoteFName, % Our file name. + case file:open(filename:join([Dest,FName]),[write]) of + {ok,FD} -> + NewRTs=lists:keyreplace(Node,1,RTs,{Node,MRef,FType,FName,FD}), + {NewRTs,Replies}; + {error,Reason} -> + NewRTs=lists:keyreplace(Node,1,RTs,{Node,MRef,void,void,void}), + NewReplies= + log_rec_addreply(Node,FType,{error,{file_open,{Reason,FName}}},Replies), + {NewRTs,NewReplies} + end. + +%% Help function adding a file that was unsuccessfully opened as failed. +log_rec_open_failure(Node,MRef,FType,RemoteFName,RTs,Replies) -> + NewRTs=lists:keyreplace(Node,1,RTs,{Node,MRef,void,void,void}), + NewReplies= + log_rec_addreply(Node, + FType, + {error,{remote_open,RemoteFName}},Replies), + {NewRTs,NewReplies}. + +%% Help function whih writes the Bin to the FD file. If writing was unsuccessful, +%% close the file and modify RTs and add a reply to Replies. Note that we can not +%% stop the runtime from sending us more data belonging to this file. But we will +%% simply just inore it from now on. +%% Returns {SuccessCode,{NewRTs,NewReplies}}. +log_rec_payload(Node,MRef,FType,FName,FD,Bin,RTs,Replies) -> + case file:write(FD,Bin) of + ok -> + {ok,{RTs,Replies}}; + {error,Reason} -> + file:close(FD), + NewRTs=lists:keyreplace(Node,1,RTs,{Node,MRef,void,void,void}), + NewReplies= + log_rec_addreply(Node,FType,{error,{file_write,{Reason,FName}}},Replies), + {error,{NewRTs,NewReplies}} + end. + +%% Help function whih shall be used when a file has been successfully transfered. +%% This function closes the output file and updates RTs and the Replies. +%% Returns {NewRTs,NewReplies}. +log_rec_eof(Node,MRef,FType,FName,FD,RTs,Replies) -> + file:close(FD), + NewRTs=lists:keyreplace(Node,1,RTs,{Node,MRef,void,void,void}), + {NewRTs,log_rec_addreply(Node,FType,{ok,FName},Replies)}. + +%% Help function which, if there is an open file, indicates it as truncated in the +%% replies. And finalize the reply for Node assuming that the node is in total incomplete +%% Returns {NewRTs,NewReplies}. +log_rec_incomplete(Node,_FType,_FName,void,RTs,Replies) -> + NewRTs=lists:keydelete(Node,1,RTs), % The node is done. + {NewRTs,log_rec_mkreply(Node,incomplete,Replies)}; +log_rec_incomplete(Node,FType,FName,FD,RTs,Replies) -> + file:close(FD), % Not going to write anymore in this file. + NewRTs=lists:keydelete(Node,1,RTs), % The node is done. + NewReplies=log_rec_addreply(Node,FType,{error,{truncated,FName}},Replies), + {NewRTs,log_rec_mkreply(Node,incomplete,NewReplies)}. + +%% Help function handling the case when runtime component experiences an error +%% transferering the file. That means that there will be no more chunks of this +%% file. Hence it works a bit like EOF. +%% Returns {NewRTs,NewReplies}. +log_rec_error(Node,MRef,FType,FName,FD,RTs,Reason,Replies) -> + file:close(FD), + NewRTs=lists:keyreplace(Node,1,RTs,{Node,MRef,void,void,void}), + {NewRTs,log_rec_addreply(Node,FType,{error,{truncated,{Reason,FName}}},Replies)}. +%% ----------------------------------------------------------------------------- + +%% Help function adding a reply to the list of replies. +%% Replies is a list {Node,FType,Reply} for each file handled, sucessfully or not. +%% The list may also contain finalized nodes, which will be on the format: +%% {Node,{Conclusion,[{trace_log,TraceLogReplies},{ti_log,TiLogReplies}]}}. +log_rec_addreply(Node,FType,Reply,Replies) -> + [{Node,FType,Reply}|Replies]. + +%% Help function which converts the {Node,FType,Reply} tuples in Replies to +%% a finalized reply. +log_rec_mkreply(Node,Conclusion,Replies) -> + {RemainingReplies,TiReplies,TraceReplies}= + log_rec_mkreply_node_ftype(Node,Replies,[],[],[]), + [{Node,{Conclusion,[{trace_log,TraceReplies},{ti_log,TiReplies}]}}| + RemainingReplies]. + +%% Help function taking out the ti_log and trace_log file-types replies for +%% Node. Returns {RemainingReplies,Ti,Trace}. +log_rec_mkreply_node_ftype(Node,[{Node,ti_log,Result}|Rest],Replies,Ti,Trace) -> + log_rec_mkreply_node_ftype(Node,Rest,Replies,[Result|Ti],Trace); +log_rec_mkreply_node_ftype(Node,[{Node,trace_log,Result}|Rest],Replies,Ti,Trace) -> + log_rec_mkreply_node_ftype(Node,Rest,Replies,Ti,[Result|Trace]); +log_rec_mkreply_node_ftype(Node,[Reply|Rest],Replies,Ti,Trace) -> + log_rec_mkreply_node_ftype(Node,Rest,[Reply|Replies],Ti,Trace); +log_rec_mkreply_node_ftype(_,[],Replies,Ti,Trace) -> + {Replies,Ti,Trace}. +%% ----------------------------------------------------------------------------- + +%% If the fetching job shall be cancelled, we must close all open files and +%% remove them including all already closed files. Returns nothing significant. +log_rec_cancel(Dest,RTs,Replies) -> + log_rec_cancel_open(Dest,RTs), % First close and remove all open files. + log_rec_cancel_finished(Dest,Replies). % Remove all already closed files. + +log_rec_cancel_open(Dest,[{_Node,_MRef,_FType,_FName,void}|Rest]) -> + log_rec_cancel_open(Dest,Rest); % There is no open file to close. +log_rec_cancel_open(Dest,[{_Node,_MRef,_FType,FName,FD}|Rest]) -> + file:close(FD), + catch file:delete(filename:join(Dest,FName)), % Will just try to do my best. + log_rec_cancel_open(Dest,Rest); +log_rec_cancel_open(_Dest,[]) -> + true. + +log_rec_cancel_finished(Dest,[{_N,_FT,Reply}|Rest]) -> + [FName]=log_rec_cancel_finished_get_fname([Reply]), + catch file:delete(filename:join(Dest,FName)), + log_rec_cancel_finished(Dest,Rest); +log_rec_cancel_finished(Dest,[{_N,{_Conclusion,[{_,Replies1},{_,Replies2}]}}|Rest]) -> + FNames1=log_rec_cancel_finished_get_fname(Replies1), + lists:foreach(fun(FName)-> + catch file:delete(filename:join(Dest,FName)) + end, + FNames1), + FNames2=log_rec_cancel_finished_get_fname(Replies2), + lists:foreach(fun(FName)-> + catch file:delete(filename:join(Dest,FName)) + end, + FNames2), + log_rec_cancel_finished(Dest,Rest); +log_rec_cancel_finished(_Dest,[]) -> + true. + +%% Help function going through all possible reply values for a file. So +%% consequently there must be a clause here for every possible log_rec_addreply +%% call above. Returns a list of filenames that shall be removed in order to +%% restore the disk since the fetch job is cancelled. +log_rec_cancel_finished_get_fname([{error,{file_open,{_,FName}}}|Rest]) -> + [FName|log_rec_cancel_finished_get_fname(Rest)]; +log_rec_cancel_finished_get_fname([{error,{file_write,{_,FName}}}|Rest]) -> + [FName|log_rec_cancel_finished_get_fname(Rest)]; +log_rec_cancel_finished_get_fname([{ok,FName}|Rest]) -> + [FName|log_rec_cancel_finished_get_fname(Rest)]; +log_rec_cancel_finished_get_fname([{error,{truncated,{_,FName}}}|Rest]) -> + [FName|log_rec_cancel_finished_get_fname(Rest)]; +log_rec_cancel_finished_get_fname([{error,{truncated,FName}}|Rest]) -> + [FName|log_rec_cancel_finished_get_fname(Rest)]; +log_rec_cancel_finished_get_fname([_|Rest]) -> % This shall not happend. + log_rec_cancel_finished_get_fname(Rest); +log_rec_cancel_finished_get_fname([]) -> + []. +%% ----------------------------------------------------------------------------- + +%% EOF diff --git a/lib/inviso/src/inviso_lfm.erl b/lib/inviso/src/inviso_lfm.erl new file mode 100644 index 0000000000..362176c776 --- /dev/null +++ b/lib/inviso/src/inviso_lfm.erl @@ -0,0 +1,431 @@ +%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+%% Author: Lennart �hman, [email protected]
+%%
+%% INVISO LogFile Merger.
+%%
+%% Merges all log-entries in all files in Files in chronological order
+%% into what ever is handled by WorkHandlerFun. Note that Files can contain
+%% several files. Both in the sence that it can be a wrapset. But also because
+%% the log is spread over more than one LogFiles (i.e trace_log + ti_log).
+%% It is further possible to use another reader-process (for the logfiles)
+%% than the default one. This is useful if the logfiles are formatted in
+%% another way than as done by a trace-port.
+
+-module(inviso_lfm).
+
+%% -----------------------------------------------------------------------------
+%% API exports.
+%% -----------------------------------------------------------------------------
+
+-export([merge/2,merge/3,merge/4,merge/5,merge/6]).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Default handler exports.
+%% -----------------------------------------------------------------------------
+
+-export([outfile_opener/1,outfile_writer/4,outfile_closer/1]).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Formatting functions.
+%% -----------------------------------------------------------------------------
+
+-export([format_arguments/3,format_argument_string/2]).
+%% -----------------------------------------------------------------------------
+%% Internal exports.
+%% -----------------------------------------------------------------------------
+
+-export([init_receiver/7]).
+%% -----------------------------------------------------------------------------
+
+%% merge(Files,BeginHandlerFun,WorkHandlerFun,EndHandlerFun,HandlerData)=
+%% {ok,Count} | {error,Reason}
+%% merge(Files,OutputFile) =
+%%
+%% Files=[FileDescription,...]
+%% FileDescription=FileSet | {reader,Mod,Func,FileSet}
+%% FileSet={Node,LogFiles} | {Node,[LogFiles,...]}
+%% in the latter case the LogFiles must be sorted, beginning with the oldest.
+%% LogFiles=[{trace_log,Files} [,{ti_log,[FileName]}] ]
+%% either just trace_log or trace_log and ti_log.
+%% Files=[FileName] | [FileName,...]
+%% in the latter case it is a wrapset.
+%% BeginHandlerFun= ( fun(HandlerData)->{ok,NewHandleData} | {error,Reason} )
+%% WorkHandlerFun= ( fun(Node,Term,PidMappings,HandlerData)->
+%% {ok,NewHandlerData} | {error,Reason}
+%% EndHandlerFun= ( fun(HandlerData)->ok | {error,Reason} )
+%% Count=integer(), the total number of handled log entries.
+%%
+%% Merges all logfiles in Files together into one common log file, in chronological
+%% order according to the time-stamps in each log. Each entry is also marked with
+%% the node name in the merged log.
+%% Configuration:
+%% If a non-default reader shall be used, Mod:Func(ReceiverPid,LogFiles) shall
+%% spawn a reader process complying to the receiver/reader message protocoll.
+%% The default reader reads logs generated by a trace-port.
+%% BeginHandler is called before any logentries are processed, typically to open
+%% the out-file, if any.
+%% WorkHandlerFun is called for every log-entry. It typically writes the output.
+%% EndHandlerFun is called when the last reader has finished, typically to
+%% close the outfile.
+%%
+%% Using merge/2 assumes you want to use default handlers writing to a file.
+merge(Files,OutputFile) when list(OutputFile) ->
+ merge(Files,fun outfile_opener/1,fun outfile_writer/4,fun outfile_closer/1,OutputFile,off).
+merge(Files,WorkHandlerFun,HandlerData) when function(WorkHandlerFun) ->
+ merge(Files,void,WorkHandlerFun,void,HandlerData,off);
+merge(Files,OutputFile,Dbg) when list(OutputFile) ->
+ merge(Files,fun outfile_opener/1,fun outfile_writer/4,fun outfile_closer/1,OutputFile,Dbg).
+merge(Files,WorkHandlerFun,HandlerData,Dbg) when function(WorkHandlerFun) ->
+ merge(Files,void,WorkHandlerFun,void,HandlerData,Dbg).
+merge(Files,BeginHandlerFun,WorkHandlerFun,EndHandlerFun,HandlerData) ->
+ merge(Files,BeginHandlerFun,WorkHandlerFun,EndHandlerFun,HandlerData,off).
+merge(Files,BeginHandlerFun,WorkHandlerFun,EndHandlerFun,HandlerData,Dbg) ->
+ ReceiverPid=spawn_link(?MODULE,
+ init_receiver,
+ [self(),Files,BeginHandlerFun,WorkHandlerFun,
+ EndHandlerFun,HandlerData,Dbg]),
+ wait_for_response(ReceiverPid).
+
+wait_for_response(ReceiverPid) ->
+ receive
+ {reply,ReceiverPid,Reply} ->
+ Reply;
+ {'EXIT',ReceiverPid,Reason} ->
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% =============================================================================
+%% Code for the receiver process.
+%% =============================================================================
+
+%% Initial function for the receiver process. This function must be exported.
+init_receiver(From,Files,BeginHandlerFun,WorkHandlerFun,EndHandlerFun,HandlerData,Dbg) ->
+ case setup_readers(Files) of % Create the reader processes.
+ {ok,Readers} ->
+ process_flag(trap_exit,true),
+ if
+ function(BeginHandlerFun) ->
+ case catch BeginHandlerFun(HandlerData) of
+ {ok,NewHandlerData} ->
+ init_receiver_2(From,WorkHandlerFun,EndHandlerFun,
+ NewHandlerData,Dbg,Readers);
+ {error,Reason} -> % Faulty begin-function.
+ From ! {reply,self(),{error,{begin_handler,Reason}}};
+ {'EXIT',Reason} ->
+ From ! {reply,self(),{error,{begin_handler,Reason}}}
+ end;
+ true -> % There is no begin-handler.
+ init_receiver_2(From,WorkHandlerFun,EndHandlerFun,HandlerData,Dbg,Readers)
+ end;
+ {error,Reason} ->
+ From ! {reply,self(),{error,{files,Reason}}}
+ end.
+
+init_receiver_2(From,WorkHandlerFun,EndHandlerFun,HandlerData,Dbg,Readers) ->
+ {NewReaders,EntryStruct}=mk_entrystruct(Readers,Dbg),
+ {Reply,NewHandlerData}=
+ loop(From,WorkHandlerFun,HandlerData,NewReaders,EntryStruct,Dbg,0),
+ if
+ function(EndHandlerFun) ->
+ case EndHandlerFun(NewHandlerData) of
+ ok ->
+ From ! {reply,self(),Reply};
+ {error,_Reason} ->
+ From ! {reply,self(),Reply}
+ end;
+ true -> % Reply directly then, no finish fun.
+ From ! {reply,self(),Reply}
+ end.
+
+%% Function that spawns a help process for each group of files in the list.
+%% The help process will read entries from the input files in the correct order
+%% and deliver them to the receiver process.
+%% Note that there is a possibility to design your own readers. The default
+%% reader understands trace-port generated logfiles.
+%% Returns a list of {Node,Pid}.
+setup_readers(Files) ->
+ setup_readers_2(Files,[]).
+
+setup_readers_2([{reader,Mod,Func,{Node,FileStruct}}|Rest],Acc) ->
+ Pid=spawn_link(Mod,Func,[self(),FileStruct]),
+ setup_readers_2(Rest,[{Node,Pid}|Acc]);
+setup_readers_2([{Node,FileStruct}|Rest],Acc) ->
+ Pid=spawn_link(inviso_lfm_tpfreader,init,[self(),FileStruct]),
+ setup_readers_2(Rest,[{Node,Pid}|Acc]);
+setup_readers_2([],Acc) ->
+ {ok,Acc};
+setup_readers_2([Faulty|_],_Acc) ->
+ {error,{bad_reader_spec,Faulty}}.
+%% -----------------------------------------------------------------------------
+
+%% This is the workloop that polls each reader for messages and writes them
+%% in the correct order.
+loop(From,WorkHFun,HData,Readers,EntryStruct,Dbg,Count) ->
+ case find_oldest_entry(EntryStruct) of
+ {Pid,Node,PidMappings,Term} ->
+ case get_and_insert_new_entry(From,Node,Pid,Readers,EntryStruct,Dbg) of
+ {ok,{NewReaders,NewEntryStruct}} ->
+ case WorkHFun(Node,Term,PidMappings,HData) of
+ {ok,NewHData} ->
+ loop(From,WorkHFun,NewHData,NewReaders,NewEntryStruct,Dbg,Count+1);
+ {error,Reason} -> % Serious, we cant go on then.
+ stop_readers(NewReaders),
+ {{error,{writing_output_file,Reason}},HData}
+ end;
+ {stop,_Reason} -> % The original caller is no longer there!
+ stop_readers(Readers),
+ {error,HData}
+ end;
+ done -> % No more readers.
+ {{ok,Count},HData}
+ end.
+
+%% Help function which finds the oldest entry in the EntryStruct. Note that the
+%% timestamp can actually be the atom 'false'. This happens for instance if it is
+%% a dropped-messages term. But since 'false' is smaller than any tuple, that
+%% term will be consumed immediately as soon as it turns up in EntryList.
+find_oldest_entry(EntryStruct) ->
+ case list_all_entries(EntryStruct) of
+ [] -> % The we are done!
+ done;
+ EntryList when list(EntryList) -> % Find smallest timestamp in here then.
+ {Pid,Node,PidMappings,_TS,Term}=
+ lists:foldl(fun({P,N,PMap,TS1,T},{_P,_N,_PMap,TS0,_T}) when TS1<TS0 ->
+ {P,N,PMap,TS1,T};
+ (_,Acc) ->
+ Acc
+ end,
+ hd(EntryList),
+ EntryList),
+ {Pid,Node,PidMappings,Term}
+ end.
+
+%% Help function which signals all reader process to clean-up and terminate.
+%% Returns nothing significant.
+stop_readers([Pid|Rest]) ->
+ Pid ! {stop,self()},
+ stop_readers(Rest);
+stop_readers([]) ->
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% Help function which tries to replace the entry by Pid in EntryStruct with
+%% a new one from that process. If one is returned on request, it replaces
+%% the old one in EntryStruct. If Pid is done or otherwise dissapears, Pid
+%% is simply removed from Readers and the EntryStruct.
+get_and_insert_new_entry(Node,Pid,Readers,EntryStruct,Dbg) ->
+ get_and_insert_new_entry(void,Node,Pid,Readers,EntryStruct,Dbg).
+
+get_and_insert_new_entry(From,Node,Pid,Readers,EntryStruct,Dbg) ->
+ Pid ! {get_next_entry,self()},
+ receive
+ {'EXIT',From,Reason} -> % No one is waiting for our reply!
+ {stop,Reason}; % No use continuing then.
+ {next_entry,Pid,PidMappings,TS,Term} -> % We got a next entry from Pid!
+ ets:insert(EntryStruct,{Pid,Node,PidMappings,TS,Term}),
+ {ok,{Readers,EntryStruct}};
+ {next_entry,Pid,{error,_Reason}} -> % Reading an entry went wrong.
+ get_and_insert_new_entry(From,Node,Pid,Readers,EntryStruct,Dbg);
+ {'EXIT',Pid,_Reason} -> % The process has terminated.
+ ets:delete(EntryStruct,Pid),
+ NewReaders=lists:delete(Pid,Readers),
+ {ok,{NewReaders,EntryStruct}}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function which from a list of reader processes creates the private
+%% storage where the oldest entry from each reader is always kept.
+%% Returns {Readers,EntryStruct}.
+mk_entrystruct(Pids,Dbg) ->
+ TId=ets:new(list_to_atom("inviso_lfm_tab_"++pid_to_list(self())),[set]),
+ mk_entrystruct_2(Pids,lists:map(fun({_,P})->P end,Pids),Dbg,TId).
+
+mk_entrystruct_2([{Node,Pid}|Rest],Readers,Dbg,EntryStruct) ->
+ {ok,{NewReaders,NewEntryStruct}}=
+ get_and_insert_new_entry(Node,Pid,Readers,EntryStruct,Dbg),
+ mk_entrystruct_2(Rest,NewReaders,Dbg,NewEntryStruct);
+mk_entrystruct_2([],Readers,_Dbg,EntryStruct) ->
+ {Readers,EntryStruct}.
+%% -----------------------------------------------------------------------------
+
+%% Help function that returns a list of our oldest entry structure.
+%% [{Pid,Node,PidMappings,TimeStamp,Term},...]
+list_all_entries(EntryStruct) ->
+ ets:tab2list(EntryStruct).
+%% -----------------------------------------------------------------------------
+
+%% =============================================================================
+%% Default handlers for the receiver
+%% =============================================================================
+
+%% These functions are also exported in order to make them available when creating
+%% other funs in other modules.
+
+%% Default begin-handler.
+outfile_opener(FileName) ->
+ case file:open(FileName,[write]) of
+ {ok,FD} ->
+ {ok,FD}; % Let the descriptor be handlerdata.
+ {error,Reason} ->
+ {error,{open,Reason}}
+ end.
+
+%% Default work-handler.
+%% DEN H�R �R L�NGT IFR�N F�RDIG!!!
+outfile_writer(Node,Term,PidMappings,FD) ->
+ io:format(FD,"~w ~w ~w~n",[Node,PidMappings,Term]),
+ {ok,FD}.
+
+%% Default end-handler.
+outfile_closer(FD) ->
+ file:close(FD),
+ ok.
+%% -----------------------------------------------------------------------------
+
+%% =============================================================================
+%% Formatting functions.
+%% =============================================================================
+
+%% This section contains a useful formatting function formatting an (function)
+%% argument list. It also offers a working example of how to write
+%% own datatype translators (which will be used by the formatting function to
+%% further enhance the output).
+
+%% format_arguments(Args,FOpts,Transaltors)=Args2 | <failure>
+%% Args=list(), list of the argument as usually given in a trace message,
+%% a stack trace or similar.
+%% FOpts=term(), formatting options understood by the translation functions.
+%% Translations=[Translator,...]
+%% Translator=fun(Term,FOpts)=TResult | {M,F}, where M:F(Term,FOpts)=TResult
+%% TResult={ok,TranslationString} | false
+%% Arg2=list(), list of Args where terms may be replaced by own representations.
+%% Note that terms not effected will remain as is, but if an own representation
+%% is choosen, that must be a string in order for any io format function to
+%% print it exactly as formatted here.
+format_arguments([Arg|Rest],FOpts,Translators) -> % More than one argument.
+ [format_argument(Arg,FOpts,Translators)|format_arguments(Rest,FOpts,Translators)];
+format_arguments([],_FOpts,_Translators) ->
+ []. % The empty list.
+
+%% Help function handling the various Erlang datatypes. There must hence be one
+%% clause here for every existing datatype.
+format_argument(List,FOpts,Translators) when is_list(List) ->
+ case format_argument_own_datatype(List,FOpts,Translators) of
+ {true,TranslationStr} ->
+ TranslationStr;
+ false ->
+ format_argument_list(List,FOpts,Translators)
+ end;
+format_argument(Tuple,FOpts,Translators) when is_tuple(Tuple) ->
+ case format_argument_own_datatype(Tuple,FOpts,Translators) of
+ {true,TranslationStr} -> % It was one of our special datatypes.
+ TranslationStr;
+ false -> % Regular tuple.
+ format_argument_tuple(Tuple,FOpts,Translators)
+ end;
+format_argument(Binary,FOpts,Translators) when is_binary(Binary) ->
+ case format_argument_own_datatype(Binary,FOpts,Translators) of
+ {true,TranslationStr} -> % It was one of our special datatypes..
+ TranslationStr;
+ false -> % Regular binary.
+ format_argument_binary(Binary,FOpts,Translators)
+ end;
+format_argument(Atom,_FOpts,_Translators) when is_atom(Atom) ->
+ Atom;
+format_argument(Integer,_FOpts,_Translators) when is_integer(Integer) ->
+ Integer;
+format_argument(Float,_FOpts,_Translators) when is_float(Float) ->
+ Float;
+format_argument(Pid,_FOpts,_Translators) when is_pid(Pid) ->
+ Pid;
+format_argument(Port,_FOpts,_Translators) when is_port(Port) ->
+ Port;
+format_argument(Ref,_FOpts,_Translators) when is_reference(Ref) ->
+ Ref;
+format_argument(Fun,_FOpts,_Translators) when is_function(Fun) ->
+ Fun.
+
+%% Help function handling the case when an element is a list.
+format_argument_list([Element|Rest],FOpts,Translators) ->
+ [format_argument(Element,FOpts,Translators)|
+ format_argument_list(Rest,FOpts,Translators)];
+format_argument_list([],_FOpts,_Translators) ->
+ [].
+
+%% Help function handling the case when an element is a tuple.
+format_argument_tuple(Tuple,FOpts,Translators) ->
+ list_to_tuple(format_argument_tuple(Tuple,FOpts,Translators,size(Tuple),[])).
+
+format_argument_tuple(_,_,_,0,List) ->
+ List;
+format_argument_tuple(Tuple,FOpts,Translators,Index,List) ->
+ E=format_argument(element(Index,Tuple),FOpts,Translators),
+ format_argument_tuple(Tuple,FOpts,Translators,Index-1,[E|List]).
+
+%% Help function handling the case when an element is a binary.
+format_argument_binary(Binary,_FOpts,_Translators) ->
+ Binary.
+
+%% Help function trying to use the translations.
+format_argument_own_datatype(Term,FOpts,[Fun|Rest]) when is_function(Fun) ->
+ case catch Fun(Term,FOpts) of
+ {ok,TranslationStr} ->
+ {true,TranslationStr};
+ _ ->
+ format_argument_own_datatype(Term,FOpts,Rest)
+ end;
+format_argument_own_datatype(Term,FOpts,[{M,F}|Rest]) ->
+ case catch M:F(Term,FOpts) of
+ {ok,TranslationStr} ->
+ {true,TranslationStr};
+ _ ->
+ format_argument_own_datatype(Term,FOpts,Rest)
+ end;
+format_argument_own_datatype(Term,FOpts,[_|Rest]) ->
+ format_argument_own_datatype(Term,FOpts,Rest);
+format_argument_own_datatype(_Term,_FOpts,[]) -> % There is no applicable format.
+ false.
+%% -----------------------------------------------------------------------------
+
+%% format_argument_string(String,_FOpts)={ok,QuotedString} | false
+%% String=string() | term()
+%% QuotedString="String"
+%% Example of datatype checker that checks, in this case, that its argument is
+%% a string. If it is, it returns a deep list of the characters to print in order
+%% to make it a quoted string.
+format_argument_string(List=[_|_],_FOpts) -> % Must be at least one element.
+ case format_argument_string_2(List) of
+ true ->
+ {ok,[$",List,$"]};
+ false ->
+ false
+ end;
+format_argument_string(_,_FOpts) ->
+ false.
+
+format_argument_string_2([C|Rest]) when (((C<127) and (C>=32)) or ((C>=8) and (C=<13))) ->
+ format_argument_string_2(Rest);
+format_argument_string_2([_|_]) ->
+ false;
+format_argument_string_2([]) ->
+ true.
+%% -----------------------------------------------------------------------------
diff --git a/lib/inviso/src/inviso_lfm_tpfreader.erl b/lib/inviso/src/inviso_lfm_tpfreader.erl new file mode 100644 index 0000000000..d0db4b6d02 --- /dev/null +++ b/lib/inviso/src/inviso_lfm_tpfreader.erl @@ -0,0 +1,388 @@ +%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+%% Author: Lennart �hman, [email protected]
+
+%%
+%% INVISO LogFileMerger TracePort File READER.
+%%
+%% This module implements a reader process capable of reading traceport files
+%% and feeding them according to the logfile merger process message protocoll
+%% to the logfile merger process.
+%% This module can also serve as example for writing file readers for other
+%% file formats.
+%%
+%% A reader process must:
+%% Support the reader-receiver protocoll.
+%% receive next_entry message: {get_next_entry,ReceiverPid}
+%% recieve stop message should the receiver wish to quit: {stop,ReceiverPid}.
+%% send next_entry message, either with entry or fault-code.
+%% next_entry message contains:{next_entry,self(),PidMappings,Timestamp,Term}
+%% {next_entry,self(),Error}
+%% recognize receiver termination (EXIT-signal).
+%% Understand logfile structure, both filename structure and content.
+%% Understand content (log-entry) details to extract the entry and entry
+%% components as timestamp and originating pid (to make pid-mappings).
+%% Understand any trace information files (ti).
+%%
+%% The logfile structure written by inviso_rt_meta is:
+%% {Pid,Alias,Op,TimeStamp} where:
+%% Pid=pid(), if Alias==unalias: pid()|other_than_pid()
+%% Op=alias|unalias,
+%% TimeStamp=now()
+%% -----------------------------------------------------------------------------
+-module(inviso_lfm_tpfreader).
+
+-export([init/2]).
+%% -----------------------------------------------------------------------------
+
+-export([handle_logfile_sort_wrapset/1]). % Exported as a service to other readers.
+%% -----------------------------------------------------------------------------
+
+%% init(RecPid,FileStruct)=N/A
+%% RecPid=pid(), the process id of the log file merger.
+%% FileStruct=LogFiles | [LogFiles,...]
+%% LogFiles=[{trace_log,[File,...]} [,{ti_log,[File]}] ]
+%% File=string()
+%% Spawn on this function to start a reader process for trace-port generated
+%% logfiles, possibly with inviso-generated ti-files.
+init(RecPid,LogFiles=[Tuple|_]) when tuple(Tuple) -> % Only one LogFiles.
+ init(RecPid,[LogFiles]);
+init(RecPid,FileStruct) when list(FileStruct) ->
+ logfiles_loop(RecPid,FileStruct).
+%% -----------------------------------------------------------------------------
+
+logfiles_loop(RecPid,[LogFiles|Rest]) ->
+ {TIalias,TIunalias}=handle_ti_file(LogFiles),% If there is a ti-file, read it.
+ Files=handle_logfiles(LogFiles), % Returns a sorted list of logfiles.
+ case open_next_file(Files) of
+ {ok,FileName,FD,NewFiles} ->
+ case loop(RecPid,FileName,NewFiles,TIalias,TIunalias,FD) of
+ next ->
+ logfiles_loop(RecPid,Rest);
+ stop ->
+ true % Terminate normally.
+ end;
+ done -> % Hmm, already out of files.
+ true; % Then lets terminate normally.
+ {error,Reason} -> % Couldn't even open the first file.
+ exit(Reason)
+ end;
+logfiles_loop(_RecPid,[]) -> % No more files in LogFiles.
+ true. % Terminate normally.
+
+%% This workloop reads an entry from the input file upon request from the merger
+%% process and sends it back to the merger process (Parent). If the file ends
+%% there are more files to open and read in Files, the next file will be opened.
+loop(RecPid,FileName,Files,TIalias,TIunalias,FD) ->
+ receive
+ {get_next_entry,RecPid} -> % The receiver request the next entry.
+ case fetch_next(FileName,FD,Files) of
+ {ok,Term,NewCurrFile,NewFiles,NewFD} ->
+ TS=find_timestamp_in_term(Term),
+ PidMappings=make_pid_mappings(Term,TIalias,TIunalias,TS),
+ RecPid ! {next_entry,self(),PidMappings,TS,Term},
+ loop(RecPid,NewCurrFile,NewFiles,TIalias,TIunalias,NewFD);
+ {error,Reason} -> % Not a properly formatted entry.
+ RecPid ! {next_entry,self(),{error,Reason}},
+ loop(RecPid,FileName,Files,TIalias,TIunalias,FD);
+ done -> % No more files to read in this LogFiles.
+ next % Are there more Files in FileStruct?
+ end;
+ {stop,RecPid} -> % The receiver process is done.
+ file:close(FD), % Close file and terminate normally.
+ stop
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Function which reads the next trace-entry from the file handled by FD, or if
+%% that file reaches EOF opens the next file in Files. Files must be sorted in
+%% the correct order.
+%% Returns {ok,Term,NewFileName,NewFiles,NewFD}, {error,Reason} or 'done'.
+fetch_next(FileName,FD,Files) ->
+ case read_traceport_file(FileName,FD) of
+ {ok,Term} -> % There were more terms in the file.
+ {ok,Term,FileName,Files,FD}; % No changes necessary then.
+ eof -> % This file is empty, try next file!
+ file:close(FD),
+ case open_next_file(Files) of
+ {ok,NewFileName,NewFD,NewFiles} -> % A new file has been opened.
+ fetch_next(NewFileName,NewFD,NewFiles); % Try again.
+ done -> % No more files.
+ done;
+ {error,Reason} -> % Problems opening files.
+ {error,Reason}
+ end;
+ {error,Reason} -> % Problems reading the file.
+ {error,Reason}
+ end.
+
+read_traceport_file(FileName,FD) ->
+ case file:read(FD,5) of % Trace-port file entries start with 5 bytes.
+ {ok,<<0,Size:32>>} -> % Each entry in a traceport file begins.
+ case file:read(FD,Size) of
+ {ok,Bin} when binary(Bin),size(Bin)=:=Size ->
+ try binary_to_term(Bin) of
+ Term -> % Bin was a properly formatted term!
+ {ok,Term}
+ catch
+ error:_Reason -> % Not a properly formatted term!
+ {error,{binary_to_term,[FileName,Bin]}}
+ end;
+ {ok,Bin} -> % Incorrect length.
+ {error,{faulty_length,[FileName,Size,Bin]}};
+ eof -> % This is premature end of file!
+ {error,{premature_eof,FileName}}
+ end;
+ {ok,<<1,DroppedMsgs:32>>} ->
+ {ok,{drop,DroppedMsgs}};
+ {ok,JunkBin} -> % Don't understand, report it as error.
+ {error,{junk,[FileName,JunkBin]}};
+ eof -> % A correct end of file!
+ eof
+ end.
+
+%% Help function which opens a file in raw binary mode and returns
+%% {ok,FileName,FD,Rest} or {error,Reason}.
+open_next_file([]) -> % There are no more files to open.
+ done;
+open_next_file([FileName|Rest]) ->
+ case file:open(FileName,[read,raw,binary]) of
+ {ok,FD} ->
+ {ok,FileName,FD,Rest};
+ {error,Reason} ->
+ {error,{open,[FileName,Reason]}}
+ end.
+%% ------------------------------------------------------------------------------
+
+%% ==============================================================================
+%% Help functions.
+%% ==============================================================================
+
+
+%% Help function which extract the originating process id from the log entry
+%% term and returns a list of all associations to the PID found in TIalias.
+make_pid_mappings(_,void,_,_) -> % Trace Information is not used.
+ []; % Simply no pid mappings then!
+make_pid_mappings(Term,TIalias,TIunalias,TS)
+ when element(1,Term)==trace;element(1,Term)==trace_ts ->
+ Pid=element(2,Term), % The pid.
+ TempAliases=find_aliases(ets:lookup(TIalias,Pid),TS),
+ remove_expired_aliases(TempAliases,TIalias,TIunalias,TS),
+ lists:map(fun({_,_,Alias})->Alias end,
+ find_aliases(ets:lookup(TIalias,Pid),TS));
+make_pid_mappings(_Term,_TIalias,_TIunalias,_TS) -> % Don't understand Term.
+ []. % Simply no translations then!
+
+%% Help function traversing a list of ets-alias-table entries and returning a
+%% list of those old enough to have happend before TS.
+%% Note that it is possible to have an Offset in microseconds. This because an
+%% association may end up in the ti-file a short time after logentries starts
+%% to appear in the log file for the process in question. We therefore like to
+%% allow some slack,
+find_aliases(List,TS) ->
+ lists:filter(fun({_,Now,_}) when Now<TS -> true;
+ (_) -> false
+ end,
+ List).
+%% ------------------------------------------------------------------------------
+
+%% Help function which removes aliases that are no longer valid from the
+%% ETS table. It uses unalias entries which are older than TS but younger than
+%% the alias association.
+%% Returns nothing significant.
+remove_expired_aliases([{Pid,Now1,Alias}|Rest],TIalias,TIunalias,TS) ->
+ Candidates=ets:lookup(TIunalias,Alias),
+ lists:foreach(fun({_,Now2,P})
+ when (Now2>Now1) and
+ (Now2<TS) and
+ ((P==Pid) or (not(is_pid(P)))) ->
+ ets:delete_object(TIalias,{Pid,Now1,Alias}),
+ true; % This alias is infact no longer.
+ (_) ->
+ false
+ end,
+ Candidates),
+ remove_expired_aliases(Rest,TIalias,TIunalias,TS);
+remove_expired_aliases([],_,_,_) ->
+ true.
+%% ------------------------------------------------------------------------------
+
+find_timestamp_in_term({trace_ts,_,_,_,TS}) ->
+ TS;
+find_timestamp_in_term({trace_ts,_,_,_,_,TS}) ->
+ TS;
+find_timestamp_in_term(_) -> % Don't know if there is a timestamp.
+ false.
+%% -----------------------------------------------------------------------------
+
+
+%% -----------------------------------------------------------------------------
+%% Help function handling a trace-information file and building the TIstruct storage.
+%% -----------------------------------------------------------------------------
+
+%% Help function which opens a standard ti-file, reads its content and
+%% builds two ETS-table where PID is primary index in the one for aliases, and
+%% the alias is primary index in the one for unalias.
+%% Returns a handle to the two ETS tables.
+%%
+%% This function currently handles:
+%% (1) plain straight raw binary files.
+handle_ti_file(FileStruct) ->
+ case lists:keysearch(ti_log,1,FileStruct) of
+ {value,{_,[FileName]}} when list(FileName) -> % There is one ti-file in this set.
+ case file:open(FileName,[read,raw,binary]) of
+ {ok,FD} ->
+ TIdAlias=ets:new(list_to_atom("inviso_ti_atab_"++pid_to_list(self())),
+ [bag]),
+ TIdUnalias=ets:new(list_to_atom("inviso_ti_utab_"++pid_to_list(self())),
+ [bag]),
+ handle_ti_file_2(FD,TIdAlias,TIdUnalias), % Fill the table.
+ file:close(FD),
+ {TIdAlias,TIdUnalias};
+ {error,_Reason} -> % Hmm, unable to open the file.
+ {void,void} % Treat it as no ti-file.
+ end;
+ {value,_} -> % Some other file-set.
+ {void,void}; % Pretend we don't understand.
+ false -> % No ti-file in this set.
+ {void,void}
+ end.
+
+handle_ti_file_2(FD,TIdAlias,TIdUnalias) ->
+ case file:read(FD,5) of % First read the header.
+ {ok,<<_,Size:32>>} ->
+ case file:read(FD,Size) of % Read the actual term.
+ {ok,Bin} when size(Bin)=:=Size ->
+ try binary_to_term(Bin) of
+ {Pid,Alias,alias,NowStamp} -> % Save this association.
+ ets:insert(TIdAlias,{Pid,NowStamp,Alias}),
+ handle_ti_file_2(FD,TIdAlias,TIdUnalias);
+ {Pid,Alias,unalias,NowStamp} ->
+ ets:insert(TIdUnalias,{Alias,NowStamp,Pid}),
+ handle_ti_file_2(FD,TIdAlias,TIdUnalias);
+ _Term -> % Don't understand!
+ handle_ti_file_2(FD,TIdAlias,TIdUnalias)
+ catch
+ error:_Reason -> % Badly formatted term
+ handle_ti_file_2(FD,TIdAlias,TIdUnalias)
+ end;
+ {ok,_JunkBin} -> % To short probably.
+ handle_ti_file_2(FD,TIdAlias,TIdUnalias); % Just drop it.
+ eof -> % Should not come here, but
+ {TIdAlias,TIdUnalias} % not much we can do, drop it and stop.
+ end;
+ {ok,_} -> % Also an error.
+ handle_ti_file_2(FD,TIdAlias,TIdUnalias);
+ eof -> % This is the normal eof point.
+ {TIdAlias,TIdUnalias}
+ end.
+%% -----------------------------------------------------------------------------
+
+
+%% -----------------------------------------------------------------------------
+%% Help functions sorting out what kind of logfiles we have to deal with.
+%% -----------------------------------------------------------------------------
+
+%% Help function which takes the filestruct argument and retrieves the names
+%% of all log-files mentioned there. If there are several logfiles, this function
+%% sorts them beginning with the oldest. That means that this function must
+%% have knowledge of how wrap-sets and so on works.
+%% Today known set-types:
+%% (1) file: One plain file.
+%% (2) wrap_set: List of files belonging to a wrap-set. Must be sorted.
+handle_logfiles(FileStruct) ->
+ handle_logfiles_2(lists:keysearch(trace_log,1,FileStruct)).
+
+handle_logfiles_2({value,{_,[FileName]}}) when list(FileName)-> % One single plain file.
+ [FileName];
+handle_logfiles_2({value,{_,Files}}) when list(Files) -> % A wrap-set.
+ handle_logfile_sort_wrapset(Files);
+handle_logfiles_2(_) ->
+ []. % Pretend there were no files otherwise.
+
+%% Help function which sorts the files in WrapSet beginning with the oldest.
+%% It assumes that a logfile is Name++SeqNo++Suffix.
+%% First the Name and Suffix must be established. We look at all files to find
+%% that out.
+%% Returns a list of sorted filenames.
+%% This function is exported since it might turn useful in own implemented
+%% readers.
+handle_logfile_sort_wrapset(Set=[_FileName]) -> % Only one file! Done then :-)
+ Set;
+handle_logfile_sort_wrapset([]) -> % Also pretty simple :-)
+ [];
+handle_logfile_sort_wrapset(FileSet) ->
+ Prefix=find_common_prefix(FileSet),
+ Suffix=find_common_prefix(lists:map(fun(Str)->lists:reverse(Str) end,FileSet)),
+ find_hole_in_wrapset(FileSet,length(Prefix),length(Suffix)).
+
+%% Help function which finds the longest common prefix of all strings in the
+%% argument-list. Returns that string.
+find_common_prefix(Files=[[FirstChar|_]|_]) ->
+ find_common_prefix_2(Files,FirstChar,[],[]);
+find_common_prefix([_|_]) -> % Means that prefix is "".
+ "".
+
+find_common_prefix_2([[CurrChar|RestString]|Rest],CurrChar,Files,RevPrefix) ->
+ find_common_prefix_2(Rest,CurrChar,[RestString|Files],RevPrefix);
+find_common_prefix_2([_String|_],_CurrChar,_Files,RevPrefix) ->
+ lists:reverse(RevPrefix); % Found a difference.
+find_common_prefix_2([],CurrChar,Files=[[FirstChar|_]|_],RevPrefix) ->
+ find_common_prefix_2(Files,FirstChar,[],[CurrChar|RevPrefix]);
+find_common_prefix_2([],CurrChar,_,RevPrefix) ->
+ lists:reverse([CurrChar|RevPrefix]). % Actually, prefix was entire string!
+
+%% Help function which returns a sorted list of FileSet with the oldest first.
+find_hole_in_wrapset(FileSet,PreLen,SufLen) ->
+ NumberedFiles=find_hole_in_wrapset_2(FileSet,PreLen,SufLen),
+ find_hole_in_wrapset_3(lists:sort(NumberedFiles),0,[]). % Wrap-sets start at 0.
+
+find_hole_in_wrapset_2([FileName|Rest],PreLen,SufLen) ->
+ [{list_to_integer(lists:sublist(FileName,PreLen+1,length(FileName)-PreLen-SufLen)),
+ FileName}|
+ find_hole_in_wrapset_2(Rest,PreLen,SufLen)];
+find_hole_in_wrapset_2([],_,_) ->
+ [].
+
+find_hole_in_wrapset_3([{N,FileName}|Rest],N,Acc) ->
+ find_hole_in_wrapset_3(Rest,N+1,[FileName|Acc]);
+find_hole_in_wrapset_3([{_,FileName}|Rest],_N,Acc) -> % FileName is the oldest one.
+ [FileName|lists:map(fun({_,FN})->FN end,Rest)]++lists:reverse(Acc);
+find_hole_in_wrapset_3([],_,Acc) -> % Means all were in order.
+ lists:reverse(Acc).
+%% -----------------------------------------------------------------------------
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inviso/src/inviso_tool.erl b/lib/inviso/src/inviso_tool.erl new file mode 100644 index 0000000000..7126ba4387 --- /dev/null +++ b/lib/inviso/src/inviso_tool.erl @@ -0,0 +1,3324 @@ +% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+%% Description:
+%% The inviso_tool implementation. A tool that uses inviso.
+%%
+%% Authors:
+%% Lennart �hman, [email protected]
+%% -----------------------------------------------------------------------------
+
+-module(inviso_tool).
+
+
+%% This is the inviso tool, which is a tool using the inviso trace application.
+%% It is developed to make tracing using trace cases possible in an environment
+%% of distributed Erlang nodes.
+%% A current restriction is that the Erlang nodes are supposed to have the same
+%% code. This since inviso tool can at this point not handle subsets of nodes.
+%% Instead all participating Erlang nodes are treated the same.
+%%
+%% The main functionality of the inviso tool are:
+%%
+%% (1) Handles start and stop of tracing at participating nodes.
+%% (2) Interprets trace-case files at a distributed network level.
+%% (The inviso runtime component is responsible for interpreting
+%% trace cases at a local level, if run in an autostart).
+%% (3) Keeps a command history log from which:
+%% (a) Sequences easily can be repeated.
+%% (b) Autostart configuration files can be created (understood by the
+%% default inviso autostart mechanism).
+%% (4) Performs reactivation in case tracing is suspended (manually or by
+%% an overload mechanism).
+%% (5) Can reconnect crashed nodes and by using the history bringing them
+%% up to speed.
+
+%% Distributed Erlang
+%% ------------------
+%% Inviso is built to run in a distributed environment.
+%% The inviso tool can also be used in a non distributed environment.
+
+%% Short description
+%% -----------------
+%% Start-up of the inviso tool
+%% During the start-up of the tool, the tool starts runtime components at
+%% all participating nodes. A runtime component can already be running at
+%% a particular node and will then simply be adopted.
+%%
+%% Session
+%% A session is said to start when tracing is initiated, and ends when
+%% made to stop by the user. When a session is stopped, tracing is stopped
+%% at all participating nodes. Note that participating nodes may come and
+%% go though the time-frame of a session. That means that if a node is
+%% reconnected it may resume its tracing in the current session through
+%% a 'restart_session'. A runtime component that is already tracing at the
+%% time start-session will simply be part of the session without its
+%% ingoing tracing being changed.
+%%
+%% Reactivation
+%% A node that is suspended can be reactivated to resume tracing. Note that
+%% tracing has in this situation never been stopped at the node in question.
+%% The inviso tool resumes the node and applies the history to it.
+%%
+%% Reconnect
+%% A node that is "down" from the inviso tool's perspective can be
+%% reconnected. During reconnection the tool restarts the runtime component
+%% at that node but does not (re)initiate tracing. The latter is called
+%% restart_session and must be done explicitly, unless the node in question
+%% is in fact already tracing. If the node is already tracing (due to an autostart
+%% for instance), it automatically becomes part of the ongoing session (if
+%% there is an ongoing session).
+%%
+%% Restart Session
+%% A node that has been down and has been reconnected can be made to
+%% initialize and resume its tracing. This is done by starting the session
+%% at the node in question and redoing the current history.
+
+%% Trace files within a session
+%% Since it is possible to init-tracing (from an inviso perspective) several
+%% times within the same session, a session may leave several trace log files
+%% behind. This must be resolved by the tracer data generator function
+%% (user supplied) by marking filenames in a chronological order but still
+%% making them possible to identify as part of the same session
+
+
+
+%% -----------------------------------------------------------------------------
+%% API exports.
+%% -----------------------------------------------------------------------------
+
+-export([start/0,start/1,stop/0,stop/1]).
+-export([reconnect_nodes/0,reconnect_nodes/1,
+ start_session/0,start_session/1,
+ reinitiate_session/0,reinitiate_session/1,
+ restore_session/0,restore_session/1,restore_session/2,
+ stop_session/0,
+ reset_nodes/0,reset_nodes/1,
+ atc/3,sync_atc/3,sync_atc/4,
+ sync_rtc/2,sync_rtc/3,
+ dtc/2,sync_dtc/2,sync_dtc/3,
+ inviso/2]).
+-export([reactivate/0,reactivate/1,
+ save_history/1,
+ get_autostart_data/1,get_autostart_data/2,
+ get_activities/0,get_node_status/0,get_node_status/1,get_session_data/0]).
+-export([flush/0,flush/1]).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Debug exports.
+%% -----------------------------------------------------------------------------
+
+-export([get_loopdata/0]).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% OTP exports and call backs.
+%% -----------------------------------------------------------------------------
+
+-export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2]).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Internal exports.
+%% -----------------------------------------------------------------------------
+
+-export([tc_executer/4,reactivator_executer/6]).
+-export([std_options_generator/1]).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Constants.
+%% -----------------------------------------------------------------------------
+
+%% Defines the inviso function calls that shall be possible to do through the
+%% inviso API in this tool.
+-define(INVISO_CMDS,
+ [{tp,5},{tp,4},{tp,1},{tpl,5},{tpl,4},{tpl,1},
+ {ctp,1},{ctp,2},{ctp,3},{ctpl,1},{ctpl,2},{ctpl,3},
+ {tf,2},{tf,1},{ctf,2},{ctf,1},{ctf_all,0},
+ {init_tpm,4},{init_tpm,7},
+ {tpm,4},{tpm,5},{tpm,8},
+ {tpm_tracer,4},{tpm_tracer,5},{init_tpm,8},
+ {tpm_ms,5},{tpm_ms_tracer,5},
+ {ctpm_ms,4},{ctpm,3},
+ {tpm_localnames,0},{ctpm_localnames,0},
+ {tpm_globalnames,0},{ctpm_globalnames,0},
+ {ctp_all,0},
+ {suspend,1},{cancel_suspension,0}]).
+%% -----------------------------------------------------------------------------
+
+%% These inviso functions shall be included in the command history log. Others
+%% are not relevant to be redone during a recactivation, a restart session or
+%% exported to an autostart file.
+-define(INVISO_CMD_HISTORY,
+ [{tp,5},{tp,4},{tp,1},{tpl,5},{tpl,4},{tpl,1},
+ {ctp,1},{ctp,2},{ctp,3},{ctpl,1},{ctpl,2},{ctpl,3},
+ {tf,2},{tf,1},{ctf,2},{ctf,1},{ctf_all,0},
+ {init_tpm,4},{init_tpm,7},
+ {tpm,4},{tpm,5},{tpm,8},
+ {tpm_tracer,4},{tpm_tracer,5},{init_tpm,8},
+ {tpm_ms,5},{tpm_ms_tracer,5},
+ {ctpm_ms,4},{ctpm,3},
+ {tpm_localnames,0},{ctpm_localnames,0},
+ {tpm_globalnames,0},{ctpm_globalnames,0},
+ {ctp_all,0}]).
+%% -----------------------------------------------------------------------------
+
+%% Since many function calls to inviso may take long time, especially if they
+%% involve difficult and many trace patterns to set, the default gen_server:call
+%% time out can not be used. We just do not want to get stuck for ever if some
+%% error occurs.
+-define(CALL_TIMEOUT,60000).
+
+%% Default max time to wait for a trace case called synchronously to return.
+-define(SYNC_TC_TIMEOUT,10000).
+
+%% Runtime components shall terminate when the tool terminates.
+-define(DEFAULT_DEPENDENCY,{dependency,0}).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Record definitions.
+%% -----------------------------------------------------------------------------
+
+%% The loopdata record.
+-record(ld,{
+ dir=".", % Working dir of the tool.
+ nodes=down, % The nodesD database, defaults to non-distr.
+ c_node, % Location of inviso_c.
+ c_pid, % The inviso control component.
+ regexp_node, % Node for regexp expansions.
+ tc_dict, % Trace case definition db.
+ chl, % Command history log.
+ session_state=passive, % passive | tracing
+ tdg={inviso_tool_lib,std_tdg,[]}, % Tracer data generator func.
+ tracer_data, % Current session nr and TDGargs.
+ reactivators=[], % Pids of now running reactivators.
+ tc_def_file, % Trace case definition file.
+ optg={?MODULE,std_options_generator,[]}, % Generates options to add_nodes/3.
+ initial_tcs=[], % Initial trace cases.
+ started_initial_tcs=[], % Cases that must be stopped when stop_tracing.
+ history_dir, % File path for history file.
+ keep_nodes=[], % Nodes that shall not be cleared when stopping.
+ debug=false % Internal debug mode
+ }).
+%% -----------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% API
+%% =============================================================================
+
+%% start()={ok,Pid} | {error,{already_started,pid()}}
+%% start(Config)
+%% Config=[{Opt,Value},...], list of tuple options.
+%% Opt=dir|nodes|c_node|regexp_node|tdg|tc_def_file|optg|initial_tcs|
+%% history_dir|keep_nodes
+%% Starts the inviso_tool process. Options in Config are the same as those
+%% which are kept in the #ld structure.
+start() ->
+ start([]).
+start(Config) ->
+ gen_server:start({local,?MODULE},?MODULE,Config,[]).
+%% -----------------------------------------------------------------------------
+
+%% stop(UntouchedNodes)=
+%% stop()={ok,NodeResults} | NodeResult | {error,Reason}
+%% UntouchedNodes=list(), nodes where any trace patterns shall not be removed.
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok | {error,Reason} | patterns_untouched
+%% Stops the inviso tool and the inviso control component. Runtime components are
+%% stopped by them selves depending on their dependcy of the control component.
+%% All runtime components that are not marked as to be kept will have their
+%% trace patterns cleared before the inviso control component is shutdown.
+%% The NodeResults indicates which nodes were successfullt handled.
+stop() ->
+ stop([]).
+stop(UntouchedNodes) ->
+ gen_server:call(?MODULE,{stop,UntouchedNodes},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% reconnect_nodes()=NodeResult; function for the nod-distributed case.
+%% reconnect_nodes(Nodes)={ok,NodesResults}
+%% NodesResults=[{Node,NodeResult},...]
+%% NodeResult={ok,{State,Status}} | {error,NReason}
+%% State=tracing | inactive
+%% Status=running | suspended
+%% NReason=unknown_node | already_connected | down
+%% (Re)starts the inviso runtime components at Nodes. Depending on its state
+%% (new,idle or tracing) and if the tool is running a session or not, it becomes
+%% part of the tool's ongoing session. If the newly reconnected node is not
+%% tracing but the tool runs a session, the node must be reinitiated to become
+%% tracing.
+reconnect_nodes() ->
+ gen_server:call(?MODULE,{reconnect_nodes,local_runtime},?CALL_TIMEOUT).
+reconnect_nodes(Node) when atom(Node) ->
+ reconnect_nodes([Node]);
+reconnect_nodes(Nodes) when list(Nodes) ->
+ gen_server:call(?MODULE,{reconnect_nodes,Nodes},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% start_session()={ok,{SessionNr,InvisoReturn}} | {error,Reason}
+%% start_session(MoreTDGargs)=
+%% MoreTDGargs=list(), prepended to the fixed list of args used when calling the
+%% tracer data generator function.
+%% SessionNr=integer(), trace sessions are numbered by the tool.
+%% InvisoReturn=If successful inviso call, the returnvalue from inviso.
+%% Note that individual nodes may be unsuccessful. See inviso:init_tracing/1
+%% Initiates tracing at all participating nodes.
+start_session() ->
+ start_session([]).
+start_session(MoreTDGargs) ->
+ gen_server:call(?MODULE,{start_session,MoreTDGargs},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% reinitiate_session(Nodes)={ok,InvisoReturn} | {error,Reason}
+%% InvisoReturn=If successful inviso call, the returnvalue from inviso:init_tracing/1.
+%% Note that individual nodes may be unsuccessful. Mentioned nodes not part
+%% of the tool or not in state inactive will be marked as failing by the
+%% tool in the InvisoReturn.
+%% To reinitate a node means to (inviso) init tracing at it according to saved
+%% tracer data generator arguments for the current session and then redo the current
+%% history to bring it up to speed. Note that the tool must be running a session
+%% for reinitiate to work.
+reinitiate_session() ->
+ gen_server:call(?MODULE,{reinitiate_session,local_runtime},?CALL_TIMEOUT).
+reinitiate_session(Nodes) ->
+ gen_server:call(?MODULE,{reinitiate_session,Nodes},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% restore_session()=
+%% restore_session(MoreTDGargs)=
+%% restore_session(FileName)=
+%% restore_session(FileName,MoreTDGargs)={ok,{SessionNr,InvisoReturn}} | {error,Reason}
+%% The two first clauses will start a new session using the last history. This
+%% implies that there must have been a session running prior.
+%% The two last clauses starts a session and reads a history file and executes the
+%% tracecases in it at all inactive nodes.
+%% In both cases the reused or read history becomes the current histoy, just if the
+%% session had been initiated manually. The tool may not
+%% have a session ongoing, and nodes already tracing (nodes which were adopted)
+%% are not effected. Just like when starting a session manually.
+restore_session() ->
+ restore_session([]).
+restore_session([]) -> % This cant be a filename.
+ gen_server:call(?MODULE,{restore_session,[]},?CALL_TIMEOUT);
+restore_session(FileNameOrMoreTDGargs) ->
+ case is_string(FileNameOrMoreTDGargs) of
+ true -> % Interpret it as a filename.
+ restore_session(FileNameOrMoreTDGargs,[]);
+ false -> % The we want to use last session history!
+ gen_server:call(?MODULE,{restore_session,FileNameOrMoreTDGargs},?CALL_TIMEOUT)
+ end.
+restore_session(FileName,MoreTDGargs) ->
+ gen_server:call(?MODULE,{restore_session,{FileName,MoreTDGargs}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% stop_session()={ok,{SessionNr,Result}} | {error,Reason}
+%% SessionNr=integer()
+%% Result=[{Node,NodeResult},...] | NonDistributedNodeResult
+%% NodeResult=ok | {error,Reason}
+%% NonDistributedNodeResult=[ok] | []
+%% Stops inviso tracing at all participating nodes. The inviso runtime components
+%% will go to state idle. It is now time to fetch the logfiles. Will most often
+%% succeed. Will only return an error if the entire inviso call returned an
+%% error. Not if an individual node failed stop tracing successfully.
+%% Any running trace case, including reactivator processes will be terminated.
+stop_session() ->
+ gen_server:call(?MODULE,stop_session,?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% reset_nodes()=NodeResult | {error,Reason}
+%% reset_nodes(Nodes)={ok,NodeResults} | {error,Reason}
+%% NodeResults and NodeResult as returned by inviso:clear/1 and /0.
+%% Clear nodes from trace flags, trace patterns and meta trace patterns. The tool
+%% must not be having a running session.
+reset_nodes() ->
+ gen_server:call(?MODULE,{reset_nodes,local_runtime},?CALL_TIMEOUT).
+reset_nodes(Nodes) ->
+ gen_server:call(?MODULE,{reset_nodes,Nodes},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% atc(TC,Id,Vars)=ok | {error,Reason}
+%% TC=atom(), name of the trace case.
+%% Id=term(), given name of this usage of TC.
+%% Vars=list(), list of variable bindings [{Var,Value},...], Var=atom(),Value=term().
+%% Function activating a trace case. The trace case must be defined in the
+%% trace case dictionary. The 'ok' return value is only a signal that the
+%% trace case has started successfully. It may then run for as long as it is
+%% programmed to run. An erroneous return value does not necessarily mean that
+%% the trace case has not been executed. It rather means that is undetermined
+%% what happend.
+atc(TC,Id,Vars) ->
+ gen_server:call(?MODULE,{atc,{TC,Id,Vars}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% sync_atc(TC,Id,Vars)=Result | {error,Reason}
+%% sync_atc(TC,Id,Vars,TimeOut)=
+%% Result=term(), what ever is returned be the last expression in the trace case.
+%% TimeOut=interger() | infinity, the max wait time for the trace case to finnish.
+%% As atc/3 but waits for the trace case to finish.
+sync_atc(TC,Id,Vars) ->
+ gen_server:call(?MODULE,{sync_atc,{TC,Id,Vars,?SYNC_TC_TIMEOUT}},?CALL_TIMEOUT).
+sync_atc(TC,Id,Vars,TimeOut) ->
+ gen_server:call(?MODULE,{sync_atc,{TC,Id,Vars,TimeOut}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% sync_rtc(TC,Vars)=Result | {error,Reason}
+%% sync_rtc(TC,Vars,TimeOut)=
+%% Result=term(), what ever is returned be the last expression in the trace case.
+%% TimeOut=interger() | infinity, the max wait time for the trace case to finnish.
+%% As sync_atc/3 but the trace case is not marked as activated. It is mearly placed
+%% in the history. Hence with sync_rtc a trace case can be "activated" multiple time.
+sync_rtc(TC,Vars) ->
+ gen_server:call(?MODULE,{sync_rtc,{TC,Vars,?SYNC_TC_TIMEOUT}},?CALL_TIMEOUT).
+sync_rtc(TC,Vars,TimeOut) ->
+ gen_server:call(?MODULE,{sync_rtc,{TC,Vars,TimeOut}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% dtc(TC,Id)=ok | {error,Reason}
+%% Deactivates a previosly activated trace case. This function can only be used
+%% on trace cases that has a deactivation defined in the trace case dictionary.
+%% There is of course really no difference between a file containing an activation
+%% compared to a deactivation. But to be able cancelling activations out from the
+%% history log, a defined deactivation is essential.
+%% As with activation, the returned 'ok' simply indicates the start of the trace
+%% case.
+dtc(TC,Id) ->
+ gen_server:call(?MODULE,{dtc,{TC,Id}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% sync_dtc(TC,Id)=Result | {error,Reason}
+%% sync_dtc(TC,Id,TimeOut)=
+%% Synchronous deactivation of trace case. See dtc/2 and sync_atc/3 for
+%% parameters.
+sync_dtc(TC,Id) ->
+ gen_server:call(?MODULE,{sync_dtc,{TC,Id,?SYNC_TC_TIMEOUT}},?CALL_TIMEOUT).
+sync_dtc(TC,Id,TimeOut) ->
+ gen_server:call(?MODULE,{sync_dtc,{TC,Id,TimeOut}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% inviso(Cmd,Args)=Result
+%% Cmd=atom(), the (inviso) function name that shall be called.
+%% Args=list(), the arguments to Cmd.
+%% Result=term(), the result from the inviso function call.
+%% This function executes a Cmd in the inviso tool context. The inviso call will
+%% be logged in history log and thereby repeated in case of a reactivation.
+%% Note that this function is intended for use with inviso function API without
+%% specifying any nodes, since the function call is supposed to be carried out on
+%% all nodes.
+%% When these functions are written to an autostart config file by the tool there
+%% is supposed to be a translation to inviso_rt functions.
+inviso(Cmd,Args) ->
+ gen_server:call(?MODULE,{inviso,{Cmd,Args}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% reactivate()=ok | {error,Reason}
+%% reactivate(Node)=ok | {error,Reason}
+%% Moves a runtime component from suspended to the state running. This can be
+%% done for both tracing and inactive nodes. The later is necessary since you
+%% may have stopped tracing with a node suspended.
+%% In case the node is tracing, commands in the command history log are redone at
+%% the node in questions.
+%% Note that this function returns 'ok' before the node is running. This because the
+%% the reactivated history is done by a separate process and there is no guarantee
+%% when it will be ready. The reactivated node will not be marked as running in
+%% the tool until done reactivating.
+%% Further it is important to understand that if there are "ongoing" tracecases
+%% (i.e tracecase scripts that are currently executing) and this node was running
+%% at the time that tracecase script started to execute, the list of nodes bound
+%% to the Nodes variable in that script executer includes this node. Making it
+%% no longer suspended makes it start executing inviso commands from where ever
+%% such are called. Hence the reactivation may be interferred by that tracecase.
+reactivate() -> % Non-distributed API.
+ reactivate(node()).
+reactivate(Node) ->
+ gen_server:call(?MODULE,{reactivate,Node},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% save_history(FileName)={ok,AbsFileName} | {error,Reason}
+%% Saves the currently collected command history log to a file. The file will
+%% be a binary-file. If FileName is an absolute path, it will be saved to that
+%% file. Otherwise the history dir will be used. If no history dir was specified
+%% the tool dir will be used, prepended to FileName.
+save_history(FileName) ->
+ gen_server:call(?MODULE,{save_history,FileName},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% get_autostart_data(Nodes,Dependency)={ok,{AutoStartData,NodeResults} |
+%% {ok,{AutoStartData,NodeResult}} | {error,Reason}
+%% Dependency=inviso dependency parameter which will be used for every
+%% autostarted runtime component (included in Options).
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult={ok,{Options,{tdg,{M,F,CompleteTDGargs}}}} | {error,Reason}
+%% Options=add_nodes options to the inviso runtime component.
+%% M,F=atom(), the module and function for tracerdata generation.
+%% CompleteTDGargs=list(), all arguments as they are given to the tracer
+%% data generator function.
+%% AutostartData=[CaseSpec,...]
+%% CaseSpec={file,{FileName,Bindings}} | {mfa,{M,F,Args}}
+%% FileName=string(), pointing out the trace case file. Note that this
+%% is the same as the path used by the tool.
+%% Bindings=Var bindings used according to the history for the
+%% invocation.
+%% M,F=atom(), the function that shall be called (normally some inviso).
+%% Args=list(), the actual arguments. Note that this may contain things
+%% which can not be written to file (ports, pids,...).
+%% Function returning information on how to autostart a node to make it trace
+%% according to the current history. The inviso_tool does not know how to write
+%% the necessary files at the nodes in question. That must be done by the user
+%% of the tool, guided by the return value from this function.
+%% Note that there will be two types of trace case files. Regular trace case
+%% files and binaries returned from this function. The latter contains the
+%% inviso commands which have been executed. Note that the order amongst the
+%% trace cases and binaries is of importance (otherwise they will be redone in
+%% an incorrect order).
+get_autostart_data(Dependency) ->
+ gen_server:call(?MODULE,{get_autostart_data,Dependency},?CALL_TIMEOUT).
+get_autostart_data(Nodes,Dependency) ->
+ gen_server:call(?MODULE,{get_autostart_data,{Nodes,Dependency}},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% get_activities()={ok,Ongoing} | {error,Reason}
+%% Ongoing=list(); [ [TraceCases] [,Reactivators] ]
+%% TraceCases={tracecases,TraceCaseList}
+%% TraceCaseList=[{{TCname,Id},Phase},...]
+%% Phase=activating | deactivating
+%% Reactivators={reactivating_nodes,ReactivatingNodes}
+%% ReactivatingNodes=[Node,...]
+%% Returns a list of assynchronous tracecases and nodes doing reactivation at
+%% this momement. This can be useful to implement "home brewn" synchronization,
+%% waiting for the runtime components to reach a certain state.
+get_activities() ->
+ gen_server:call(?MODULE,get_activities,?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% get_status(Node)={ok,StateStatus} | {error,Reason}
+%% StateStatus={State,Status} | reactivating | down
+%% State=tracing | inactive | trace_failure
+%% Status=running | suspended
+get_node_status() ->
+ get_node_status(local_runtime).
+get_node_status(Node) ->
+ gen_server:call(?MODULE,{get_node_status,Node},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% get_session_data()={ok,{Status,SessionNr,TDGargs}} | {error,Reason}
+%% Status=tracing | not_tracing, info about current/last session.
+%% SessionNr=integer()
+%% TDGargs=list(), list of the arguments that will be given to the tracer data
+%% generator function (not including the leading Nodes list).
+%% Returns data about the current or last session.
+get_session_data() ->
+ gen_server:call(?MODULE,get_session_data,?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% flush()={ok,NodeResults} | NodeResult | {error,Reason}
+%% flush(Nodes)={ok,NodesResults} | {error,Reason}
+%% NodeResults=[{Node,NodeResult},...]
+%% NodeResult=ok | {error,Reason}
+%% Makes runtime components flush their trace ports.
+flush() ->
+ gen_server:call(?MODULE,flush,?CALL_TIMEOUT).
+flush(Nodes) ->
+ gen_server:call(?MODULE,{flush,Nodes},?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% get_loopdata()=#ld
+%% Debug API returning the internal loopdata structure. See #ld above for details.
+get_loopdata() ->
+ gen_server:call(?MODULE,get_loopdata,?CALL_TIMEOUT).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Internal APIs.
+%% -----------------------------------------------------------------------------
+
+%% tc_executer_reply(To,Reply)=nothing significant
+%% To=pid()
+%% Reply=term()
+%% Internal API used by a trace case executer process to signal its completion.
+tc_executer_reply(To,Reply) ->
+ gen_server:cast(To,{tc_executer_reply,Reply}).
+%% -----------------------------------------------------------------------------
+
+%% Internal API used by a reactivator process indicating it is done with the
+%% history log it has got so far.
+%% Timeout set to infinity since the tool may be busy, then the reactivator just
+%% have to wait. If the tool crashes the reactivator will be go down too automatically.
+reactivator_reply(TPid,Counter) ->
+ gen_server:call(TPid,{reactivator_reply,{Counter,self()}},infinity).
+%% -----------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% gen_server implementation.
+%% =============================================================================
+
+init(Config) ->
+ case fetch_configuration(Config) of % From conf-file and Config.
+ {ok,LD} when record(LD,ld) ->
+ case start_inviso_at_c_node(LD) of
+ {ok,CPid} ->
+ LD2=start_runtime_components(LD),
+ LD3=read_trace_case_definitions(LD2),
+ process_flag(trap_exit,true),
+ start_subscribe_inviso_events(LD3#ld.c_node),
+ {ok,LD3#ld{c_pid=CPid}};
+ {error,Reason} -> % Most likely already running.
+ {stop,{error,Reason}}
+ end;
+ {error,Reason} ->
+ {stop,{error,{start_up,Reason}}}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function starting the inviso control component at node c_node, or "here"
+%% if it is not a distributed network.
+start_inviso_at_c_node(#ld{c_node=undefined}) -> % Non distributed case.
+ case inviso:start() of
+ {ok,Pid} ->
+ {ok,Pid};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+start_inviso_at_c_node(#ld{c_node=CNode}) ->
+ case rpc:call(CNode,inviso,start,[]) of
+ {ok,Pid} ->
+ {ok,Pid};
+ {error,{already_started,_}} -> % A control component already started.
+ {error,{inviso_control_already_running,CNode}};
+ {error,Reason} ->
+ {error,Reason};
+ {badrpc,Reason} ->
+ {error,{inviso_control_node_error,Reason}}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function starting the runtime components at all particapting nodes.
+%% It also updates the nodes structure in the #ld to indicate which nodes where
+%% successfully started. Returns a new #ld.
+%% Note that a runtime component may actually be running at one or several nodes.
+%% This is supposed to be the result of an (wanted) autostart. Meaning that the
+%% inviso tool can not handle the situation if a runtime component is not doing
+%% what it is supposed to do. In case a runtime component is already running it
+%% will be adopted and therefore marked as running.
+start_runtime_components(LD=#ld{c_node=undefined}) ->
+ start_runtime_components_2(local_runtime,undefined,LD);
+start_runtime_components(LD=#ld{c_node=CNode,nodes=NodesD}) ->
+ start_runtime_components_2(get_all_nodenames_nodes(NodesD),CNode,LD).
+start_runtime_components(Nodes,LD=#ld{c_node=CNode}) ->
+ start_runtime_components_2(Nodes,CNode,LD).
+
+start_runtime_components_2(local_runtime,CNode,LD=#ld{optg=OptG}) ->
+ Opts=start_runtime_components_mk_opts(local_runtime,OptG),
+ case inviso:add_node(mk_rt_tag(),Opts) of
+ {ok,NAnsw} -> % Should be more clever really!
+ NewNodesD=update_added_nodes(CNode,{ok,NAnsw},LD#ld.nodes),
+ LD#ld{nodes=NewNodesD};
+ {error,_Reason} ->
+ LD
+ end;
+start_runtime_components_2([Node|Rest],CNode,LD=#ld{optg=OptG}) ->
+ Opts=start_runtime_components_mk_opts(Node,OptG),
+ case rpc:call(CNode,inviso,add_nodes,[[Node],mk_rt_tag(),Opts]) of
+ {ok,NodeResults} ->
+ NewNodesD=update_added_nodes(CNode,NodeResults,LD#ld.nodes),
+ start_runtime_components_2(Rest,CNode,LD#ld{nodes=NewNodesD});
+ {error,_Reason} ->
+ start_runtime_components_2(Rest,CNode,LD);
+ {badrpc,_Reason} ->
+ start_runtime_components_2(Rest,CNode,LD)
+ end;
+start_runtime_components_2([],_,LD) ->
+ LD.
+
+start_runtime_components_mk_opts(Node,{M,F,Args}) ->
+ case catch apply(M,F,[Node|Args]) of
+ {ok,Opts} when list(Opts) ->
+ start_runtime_component_mk_opts_add_dependency(Opts);
+ _ ->
+ [?DEFAULT_DEPENDENCY]
+ end.
+
+%% The options generator is not supposed to generate the dependency. Hence this
+%% function adds and if necessary removes an incorrectly added dependency tag.
+start_runtime_component_mk_opts_add_dependency(Opts) ->
+ case lists:keysearch(dependency,1,Opts) of
+ {value,_} -> % Not allowed!!!
+ [?DEFAULT_DEPENDENCY|lists:keydelete(dependecy,1,Opts)];
+ false ->
+ [?DEFAULT_DEPENDENCY|Opts]
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function subscribing to inviso events from the inviso controller. This
+%% will make it possible to follow runtime components going down.
+start_subscribe_inviso_events(undefined) ->
+ inviso:subscribe();
+start_subscribe_inviso_events(CNode) ->
+ rpc:call(CNode,inviso,subscribe,[self()]). % Don't want the rpc-proc to subscribe!
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% gen_server handle call back functions.
+%% -----------------------------------------------------------------------------
+
+handle_call({stop,UntouchedNodes},_From,LD=#ld{nodes=NodesD,c_node=CNode,keep_nodes=KeepNodes})
+ when is_list(UntouchedNodes) ->
+ {stop,
+ normal,
+ remove_all_trace_patterns(CNode,
+ UntouchedNodes++KeepNodes,
+ get_available_nodes(NodesD)),
+ LD};
+handle_call({stop,BadArg},_From,LD) ->
+ {reply,{error,{badarg,BadArg}},LD};
+
+handle_call({reconnect_nodes,Nodes},_From,LD) ->
+ case h_reconnect_nodes(Nodes,LD) of
+ {ok,{Nodes2,NodesErr,NewLD}} ->
+ if
+ Nodes==local_runtime ->
+ {reply,
+ build_reconnect_nodes_reply(Nodes,Nodes2,NodesErr,NewLD#ld.nodes),
+ NewLD};
+ list(Nodes) ->
+ {reply,
+ {ok,build_reconnect_nodes_reply(Nodes,Nodes2,NodesErr,NewLD#ld.nodes)},
+ NewLD}
+ end;
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+
+handle_call({start_session,MoreTDGargs},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of
+ false -> % No session running.
+ if
+ list(MoreTDGargs) ->
+ DateTime=calendar:universal_time(),
+ {M,F,Args}=LD#ld.tdg,
+ TDGargs=inviso_tool_lib:mk_tdg_args(DateTime,MoreTDGargs++Args),
+ case h_start_session(M,F,TDGargs,LD) of
+ {ok,{SessionNr,ReturnVal,NewLD}} -> % No nodes to initiate.
+ NewLD2=add_initial_tcs_to_history(NewLD#ld.initial_tcs,
+ NewLD#ld{chl=mk_chl(LD#ld.chl)}),
+ {reply,
+ {ok,{SessionNr,ReturnVal}},
+ NewLD2#ld{session_state=tracing_sessionstate()}};
+ {ok,{SessionNr,ReturnVal,Nodes2,NewLD}} ->
+ NewLD2=do_initial_tcs(NewLD#ld.initial_tcs,
+ Nodes2,
+ NewLD#ld{chl=mk_chl(LD#ld.chl)}),
+ {reply,
+ {ok,{SessionNr,ReturnVal}},
+ NewLD2#ld{session_state=tracing_sessionstate()}};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ true -> % Faulty TDGargs.
+ {reply,{error,{badarg,MoreTDGargs}},LD}
+ end;
+ true ->
+ {reply,{error,session_already_started},LD}
+ end;
+
+handle_call({reinitiate_session,Nodes},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of
+ true -> % The tool must be tracing.
+ {M,F,_Args}=LD#ld.tdg,
+ TDGargs=get_latest_tdgargs_tracer_data(LD#ld.tracer_data),
+ case h_reinitiate_session(Nodes,M,F,TDGargs,LD) of
+ {ok,{NodesErr,ReturnVal,NewLD}} ->
+ {reply,
+ {ok,build_reinitiate_session_reply(Nodes,NodesErr,ReturnVal)},
+ NewLD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ false -> % Must have a running session!
+ {reply,{error,no_session},LD}
+ end;
+
+handle_call({restore_session,{FileName,MoreTDGargs}},_From,LD=#ld{chl=OldCHL})
+ when list(MoreTDGargs) ->
+ case is_tracing(LD#ld.session_state) of
+ false ->
+ case catch make_absolute_path(FileName,LD#ld.dir) of
+ AbsFileName when list(AbsFileName) ->
+ case file:read_file(AbsFileName) of
+ {ok,Bin} ->
+ if
+ list(MoreTDGargs) ->
+ case catch replace_history_chl(OldCHL,
+ binary_to_term(Bin)) of
+ {ok,CHL} -> % The file was well formatted.
+ case h_restore_session(MoreTDGargs,
+ LD#ld{chl=CHL}) of
+ {ok,{SessionNr,ReturnVal,NewLD}} ->
+ {reply,
+ {ok,{SessionNr,ReturnVal}},
+ NewLD#ld{session_state=
+ tracing_sessionstate()}};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ Error -> % Badly formatted file.
+ {reply,
+ {error,{bad_file,{AbsFileName,Error}}},
+ LD}
+ end;
+ true ->
+ {reply,{error,{badarg,MoreTDGargs}},LD}
+ end;
+ {error,Reason} ->
+ {reply,{error,{read_file,Reason}},LD}
+ end;
+ Error ->
+ {reply,{error,{bad_filename,{FileName,Error}}},LD}
+ end;
+ true ->
+ {reply,{error,session_already_started},LD}
+ end;
+%% This is doing restore session on the current history.
+handle_call({restore_session,MoreTDGargs},_From,LD=#ld{chl=CHL}) ->
+ case is_tracing(LD#ld.session_state) of
+ false ->
+ case history_exists_chl(CHL) of
+ true -> % There is a history to redo.
+ if
+ list(MoreTDGargs) ->
+ case h_restore_session(MoreTDGargs,LD) of
+ {ok,{SessionNr,ReturnVal,NewLD}} ->
+ {reply,
+ {ok,{SessionNr,ReturnVal}},
+ NewLD#ld{session_state=tracing_sessionstate()}};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ true ->
+ {reply,{error,{badarg,MoreTDGargs}},LD}
+ end;
+ false ->
+ {reply,{error,no_history},LD}
+ end;
+ true ->
+ {reply,{error,session_already_started},LD}
+ end;
+
+%% To stop tracing means stop_tracing through the inviso API. But we must also
+%% remove any help processes executing inviso commands (trace case executers
+%% and reactivators).
+%% Note that to be really sure we should actually wait for EXIT-signals from those
+%% processes before returning a successful returnvalue to the caller. In theory
+%% those processes could issue an inviso call effecting a new trace session started
+%% with init_tracing shortly after the call to stop_tracing. But too complicated! :-)
+%% Further, stop-tracing is done on all nodes in our nodes structure. Regardless
+%% if the node is tracing or not
+handle_call(stop_session,_From,LD=#ld{session_state=SState,chl=CHL,reactivators=ReAct}) ->
+ case is_tracing(SState) of
+ true ->
+ NewCHL=stop_all_tc_executer_chl(CHL), % Stop any running trace case proc.
+ NewReAct=stop_all_reactivators(ReAct), % Stop any running reactivators.
+ case h_stop_session(LD) of
+ {ok,{SessionNr,Result}} ->
+ NewNodesD=set_inactive_nodes(Result,LD#ld.nodes),
+ {reply,
+ {ok,{SessionNr,Result}},
+ LD#ld{session_state=passive_sessionstate(),
+ nodes=NewNodesD,
+ chl=NewCHL,
+ reactivators=NewReAct,
+ started_initial_tcs=[]}};
+ {error,Reason} -> % Now we're really in deep shit :-)
+ {reply,{error,{unrecoverable,Reason}},LD}
+ end;
+ false ->
+ {reply,{error,no_session},LD}
+ end;
+
+handle_call({reset_nodes,Nodes},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of
+ false -> % We can not be in a session.
+ {reply,h_reset_nodes(Nodes,LD#ld.c_node),LD};
+ true ->
+ {reply,{error,session_active},LD}
+ end;
+
+%% Calling a trace-case, or "turning it on".
+handle_call({atc,{TC,Id,Vars}},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of % Check that we are tracing now.
+ true ->
+ case h_atc(TC,Id,Vars,LD) of
+ {ok,NewLD} -> % Trace case executed.
+ {reply,ok,NewLD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ false -> % Can't activate if not tracing.
+ {reply,{error,no_session},LD}
+ end;
+
+handle_call({sync_atc,{TC,Id,Vars,TimeOut}},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of
+ true ->
+ if
+ integer(TimeOut);TimeOut==infinity ->
+ case h_sync_atc(TC,Id,Vars,TimeOut,LD) of
+ {ok,NewLD,Result} ->
+ {reply,Result,NewLD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ true ->
+ {reply,{error,{badarg,TimeOut}},LD}
+ end;
+ false ->
+ {reply,{error,no_session},LD}
+ end;
+
+handle_call({sync_rtc,{TC,Vars,TimeOut}},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of
+ true ->
+ if
+ integer(TimeOut);TimeOut==infinity ->
+ case h_sync_rtc(TC,Vars,TimeOut,LD) of
+ {ok,NewLD,Result} ->
+ {reply,Result,NewLD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ true ->
+ {reply,{error,{badarg,TimeOut}},LD}
+ end;
+ false ->
+ {reply,{error,no_session},LD}
+ end;
+
+
+handle_call({dtc,{TC,Id}},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of % Check that we are tracing now.
+ true ->
+ case h_dtc(TC,Id,LD) of
+ {ok,NewLD} ->
+ {reply,ok,NewLD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ false -> % Can't activate if not tracing.
+ {reply,{error,no_session},LD}
+ end;
+
+handle_call({sync_dtc,{TC,Id,TimeOut}},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of % Check that we are tracing now.
+ true ->
+ if
+ integer(TimeOut);TimeOut==infinity ->
+ case h_sync_dtc(TC,Id,TimeOut,LD) of
+ {ok,NewLD,Result} ->
+ {reply,Result,NewLD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ true ->
+ {reply,{error,{badarg,TimeOut}},LD}
+ end;
+ false -> % Can't activate if not tracing.
+ {reply,{error,no_session},LD}
+ end;
+
+handle_call({inviso,{Cmd,Args}},_From,LD=#ld{session_state=SState}) ->
+ case is_tracing(SState) of
+ true ->
+ if
+ list(Args) ->
+ case h_inviso(Cmd,Args,LD) of
+ {ok,{Reply,NewLD}} ->
+ {reply,Reply,NewLD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ true ->
+ {reply,{error,{badarg,Args}},LD}
+ end;
+ false -> % Can't do if not tracing.
+ {reply,{error,no_session},LD}
+ end;
+
+handle_call({reactivate,Node},_From,LD=#ld{nodes=NodesD,c_node=CNode}) ->
+ case get_state_nodes(Node,NodesD) of
+ {trace_failure,_} ->
+ {reply,{error,trace_failure},LD};
+ {State,suspended} -> % The node is infact suspended.
+ case h_reactivate(Node,CNode) of
+ ok ->
+ case {State,is_tracing(LD#ld.session_state)} of
+ {tracing,true} -> % Only then shall we redo cmds.
+ {reply,ok,redo_cmd_history(Node,LD)};
+ _ -> % All other just no longer suspended.
+ {reply,ok,LD#ld{nodes=set_running_nodes(Node,NodesD)}}
+ end;
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end;
+ reactivating ->
+ {reply,{error,reactivating},LD};
+ {_,running} ->
+ {reply,{error,already_running},LD};
+ down ->
+ {reply,{error,not_available},LD};
+ false ->
+ {reply,{error,unknown_node},LD}
+ end;
+
+handle_call({save_history,FileName},_From,LD=#ld{chl=CHL,dir=Dir,history_dir=HDir}) ->
+ case lists:keysort(2,get_loglist_chl(CHL)) of
+ [] -> % Empty history or no history.
+ {reply,{error,no_history},LD};
+ Log ->
+ case h_save_history(HDir,Dir,FileName,Log) of
+ {ok,AbsFileName} ->
+ {reply,{ok,AbsFileName},LD};
+ {error,Reason} ->
+ {reply,{error,Reason},LD}
+ end
+ end;
+
+
+handle_call({get_autostart_data,{Nodes,Dependency}},_From,LD=#ld{chl=CHL}) ->
+ case build_autostart_data(lists:keysort(2,get_loglist_chl(CHL)),LD#ld.tc_dict) of
+ {ok,ASD} ->
+ TDGargs=get_latest_tdgargs_tracer_data(LD#ld.tracer_data),
+ {M,F,_}=LD#ld.tdg,
+ OptsG=LD#ld.optg, % Addnodes options generator.
+ {reply,
+ h_get_autostart_data(Nodes,LD#ld.c_node,Dependency,ASD,M,F,TDGargs,OptsG),
+ LD};
+ {error,Reason} -> % Bad datatypes in command args.
+ {reply,{error,Reason},LD}
+ end;
+
+handle_call({get_autostart_data,Dependency},From,LD=#ld{c_node=undefined}) ->
+ handle_call({get_autostart_data,{local_runtime,Dependency}},From,LD);
+handle_call({get_autostart_data,Dependency},From,LD=#ld{nodes=NodesD}) ->
+ Nodes=get_all_nodenames_nodes(NodesD),
+ handle_call({get_autostart_data,{local_runtime,{Nodes,Dependency}}},From,LD);
+
+handle_call(get_activities,_From,LD=#ld{chl=CHL,reactivators=Reactivators}) ->
+ TraceCases=get_ongoing_chl(CHL),
+ RNodes=get_all_nodes_reactivators(Reactivators),
+ ReturnList1=
+ if
+ TraceCases==[] ->
+ [];
+ true ->
+ [{tracecases,TraceCases}]
+ end,
+ ReturnList2=
+ if
+ RNodes==[] ->
+ ReturnList1;
+ true ->
+ [{reactivating_nodes,RNodes}|ReturnList1]
+ end,
+ {reply,{ok,ReturnList2},LD};
+
+handle_call({get_node_status,Node},_Node,LD) ->
+ case get_state_nodes(Node,LD#ld.nodes) of
+ false ->
+ {reply,{error,unknown_node},LD};
+ StateStatus ->
+ {reply,{ok,StateStatus},LD}
+ end;
+
+handle_call(get_session_data,_From,LD=#ld{session_state=SState,tracer_data=TD}) ->
+ case get_latest_session_nr_tracer_data(TD) of
+ undefined ->
+ {reply,{error,no_session},LD};
+ SessionNr ->
+ TDGargs=get_latest_tdgargs_tracer_data(TD),
+ case is_tracing(SState) of
+ true ->
+ {reply,{ok,{tracing,SessionNr,TDGargs}},LD};
+ false ->
+ {reply,{ok,{not_tracing,SessionNr,TDGargs}},LD}
+ end
+ end;
+
+handle_call(flush,_From,LD=#ld{c_node=CNode,nodes=NodesD}) ->
+ Nodes=get_tracing_nodes(NodesD),
+ {reply,h_flush(CNode,Nodes),LD};
+handle_call({flush,Nodes},_From,LD=#ld{c_node=CNode}) ->
+ {reply,h_flush(CNode,Nodes),LD};
+
+handle_call(get_loopdata,_From,LD) ->
+ {reply,LD,LD};
+
+%% Internal handle_call callbacks.
+
+handle_call({reactivator_reply,{Counter,RPid}},_From,LD=#ld{chl=CHL}) ->
+ HighestUsedCounter=get_highest_used_counter_chl(CHL),
+ if
+ HighestUsedCounter>Counter -> % There are now more log entries.
+ NewUnsortedLog=get_loglist_chl(CHL),
+ {reply,{more,NewUnsortedLog},LD};
+ true -> % No Counter is youngest log entry.
+ NodesD=LD#ld.nodes,
+ Node=get_node_reactivators(RPid,LD#ld.reactivators),
+ {reply,
+ done,
+ LD#ld{nodes=set_running_nodes(Node,NodesD),
+ reactivators=del_reactivators(RPid,LD#ld.reactivators)}}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Handling a notification from a trace case execution process. Receiving this
+%% indicated that this phase of the trace case is finnished.
+handle_cast({tc_executer_reply,{Phase,ProcH,Result}},LD) ->
+ case Phase of
+ activating -> % The trace case is running now.
+ {ok,NewLD}=h_tc_activation_done(ProcH,Result,LD),
+ {noreply,NewLD};
+ stopping ->
+ {ok,NewLD}=h_tc_stopping_done(ProcH,Result,LD),
+ {noreply,NewLD};
+ _ ->
+ {noreply,LD}
+ end;
+handle_cast(_,LD) ->
+ {noreply,LD}.
+%% -----------------------------------------------------------------------------
+
+%% This is the case when a runtime component goes down. We stop all running
+%% reactivators for this node. Note that there can also be tracecases ongoing
+%% where this node is part of the Nodes variable. But there is not much we can
+%% do about that. Other then informing the user that it is unwise to reconnect
+%% this node before those tracecases have stopped being ongoing.
+handle_info({inviso_event,_CNode,_Time,{disconnected,Node,_}},LD) ->
+ {noreply,LD#ld{nodes=set_down_nodes(Node,LD#ld.nodes),
+ reactivators=stop_node_reactivators(Node,LD#ld.reactivators)}};
+
+%% This is the case when a runtime component gets suspended. Much of the same
+%% problem as described above applies.
+handle_info({inviso_event,_CNode,_Time,{state_change,Node,{_,{suspended,_}}}},LD) ->
+ {noreply,LD#ld{nodes=set_suspended_nodes(Node,LD#ld.nodes),
+ reactivators=stop_node_reactivators(Node,LD#ld.reactivators)}};
+
+handle_info(_,LD) ->
+ {noreply,LD}.
+%% -----------------------------------------------------------------------------
+
+%% Called when the tool server stops. First clause, termination is initiated by
+%% our self and therefore controlled another way. In the second case we are
+%% stopping for some external reason, and we must then do more here in terminate/2.
+terminate(normal,#ld{c_node=CNode}) -> % This is when we are stopping our self.
+ stop_inviso_at_c_node(CNode);
+terminate(_,#ld{c_node=CNode,nodes=NodesD,keep_nodes=KeepNodes}) ->
+ remove_all_trace_patterns(CNode,KeepNodes,get_all_nodenames_nodes(NodesD)),
+ stop_inviso_at_c_node(CNode).
+%% -----------------------------------------------------------------------------
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% =============================================================================
+%% Handler first level help functions.
+%% =============================================================================
+
+%% -----------------------------------------------------------------------------
+%% reconnect_nodes
+%% -----------------------------------------------------------------------------
+
+%% Help function reconnecting the nodes in Nodes. Listed nodes must be part of
+%% the set of nodes handled by the tool. It is not possible to reconnect a node
+%% that is not marked as down. This partly because we otherwise risk losing the
+%% trace_failure state (which can not be rediscovered).
+h_reconnect_nodes(local_runtime,LD=#ld{nodes=NodesD}) -> % Non-distributed.
+ case get_state_nodes(local_runtime,NodesD) of
+ down ->
+ {ok,{local_runtime,[],start_runtime_components(local_runtime,LD)}};
+ _ -> % Allready connected!
+ {ok,{[],{error,already_connected},LD}}
+ end;
+h_reconnect_nodes(Nodes,LD=#ld{nodes=NodesD}) when list(Nodes) ->
+ {Nodes2,NodesErr}=
+ lists:foldl(fun(N,{Nodes2,NodesErr})->
+ case get_state_nodes(N,NodesD) of
+ down -> % Yes this node can be reconnected.
+ {[N|Nodes2],NodesErr};
+ false -> % Not part of the node-set!
+ {Nodes2,[{N,{error,unknown_node}}|NodesErr]};
+ _ -> % Allready connected!
+ {Nodes2,[{N,{error,already_connected}}|NodesErr]}
+ end
+ end,
+ {[],[]},
+ Nodes),
+ LD2=start_runtime_components(Nodes2,LD), % Inpect the #ld.nodes for result.
+ {ok,{Nodes2,NodesErr,LD2}};
+h_reconnect_nodes(Nodes,_LD) ->
+ {error,{badarg,Nodes}}.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% start_session
+%% -----------------------------------------------------------------------------
+
+%% Help function starting the tracing at all nodes. Note that the tracer data
+%% is calculated using a user defined function. This is how for instance the
+%% file names (of the log files) are determined.
+%% Before the nodes are initiated their (possibly remaining) trace patterns are
+%% cleared, both local and global.
+h_start_session(M,F,TDGargs,LD=#ld{c_node=CNode,nodes=NodesD,tracer_data=TDs}) ->
+ case get_inactive_running_nodes(NodesD) of
+ [] -> % There are no nodes to initiate!
+ h_start_session_nonodes(TDGargs,LD,[]);
+ Nodes -> % List of nodes or 'local_runtime'.
+ case h_start_session_ctp_all(CNode,Nodes) of
+ {ok,Errors,[]} -> % Now no nodes to initiate!
+ h_start_session_nonodes(TDGargs,LD,Errors);
+ {ok,Errors,Nodes2} -> % Now these nodes are fresh.
+ case call_tracer_data_generator(CNode,M,F,TDGargs,Nodes2) of
+ {ok,TracerList} -> % Generated our tracerdata.
+ case h_start_session_2(CNode,TracerList,Errors) of
+ {ok,ReturnValue} -> % Some nodes are initialized now.
+ {NewNodesD,Nodes3}=
+ set_tracing_running_nodes(CNode,ReturnValue,NodesD),
+ {SessionNr,NewTDs}=insert_td_tracer_data(TDGargs,TDs),
+ {ok,{SessionNr,
+ ReturnValue,
+ Nodes3, % The nodes that shall get initial tracases.
+ LD#ld{nodes=NewNodesD,tracer_data=NewTDs}}};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ {error,Reason} -> % Faulty tracer data generator func.
+ {error,{bad_tdg,Reason}}
+ end;
+ {error,Reason} -> % Error clearing patterns.
+ {error,Reason}
+ end
+ end.
+
+h_start_session_nonodes(TDGargs,LD=#ld{c_node=CNode,tracer_data=TDs},Errors) ->
+ {SessionNr,NewTDs}=insert_td_tracer_data(TDGargs,TDs),
+ if
+ CNode==undefined ->
+ {ok,{SessionNr,[],LD#ld{tracer_data=NewTDs}}};
+ true ->
+ {ok,{SessionNr,{ok,Errors},LD#ld{tracer_data=NewTDs}}}
+ end.
+
+%% Help function clearing all trace patterns on all nodes.
+h_start_session_ctp_all(CNode,Nodes) ->
+ case remove_all_trace_patterns(CNode,[],Nodes) of
+ ok -> % Non-distributed case1.
+ {ok,[],local_runtime};
+ {error,Reason} -> % Non-distributed case2 and general failure.
+ {error,Reason};
+ {ok,NodeResults} ->
+ h_start_session_ctp_all_2(NodeResults,[],[])
+ end.
+
+h_start_session_ctp_all_2([{Node,{error,Reason}}|Rest],Errors,Nodes) ->
+ h_start_session_ctp_all_2(Rest,[{Node,{error,Reason}}|Errors],Nodes);
+h_start_session_ctp_all_2([{Node,_OkOrPatternsUntouched}|Rest],Errors,Nodes) ->
+ h_start_session_ctp_all_2(Rest,Errors,[Node|Nodes]);
+h_start_session_ctp_all_2([],Errors,Nodes) ->
+ {ok,Errors,Nodes}.
+
+%% Help function doing the actual init_tracing.
+h_start_session_2(undefined,TracerData,_Errors) -> % Non distributed case.
+ case inviso:init_tracing(TracerData) of
+ {ok,LogResult} when list(LogResult) ->
+ {ok,{ok,LogResult}};
+ {error,already_initated} -> % Perhaps adopted!?
+ {ok,{error,already_initiated}}; % Not necessarily wrong.
+ {error,Reason} ->
+ {error,Reason}
+ end;
+h_start_session_2(CNode,TracerList,Errors) ->
+ case rpc:call(CNode,inviso,init_tracing,[TracerList]) of
+ {ok,NodeResults} ->
+ {ok,{ok,Errors++NodeResults}};
+ {error,Reason} ->
+ {error,Reason};
+ {badrpc,Reason} ->
+ {error,{inviso_control_node_error,Reason}}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function starting all initial trace cases. They are actually handled
+%% the same way as user started trace cases. We actually only start initial
+%% tracecases at Nodes (if Nodes is a list of nodes). This because we may have
+%% adopted some nodes some already tracing nodes, and such are supposed to have
+%% the correct patterns and flags set.
+do_initial_tcs([{TC,Vars}|Rest],Nodes,LD) ->
+ Id=make_ref(), % Trace case ID.
+ case h_atc(TC,Id,Vars,LD,Nodes) of % Start using regular start methods.
+ {ok,NewLD} -> % Trace case was successfully started.
+ NewInitialTcs=add_initial_tcs(TC,Id,NewLD#ld.started_initial_tcs),
+ do_initial_tcs(Rest,Nodes,NewLD#ld{started_initial_tcs=NewInitialTcs});
+ {error,_Reason} ->
+ do_initial_tcs(Rest,Nodes,LD)
+ end;
+do_initial_tcs([_|Rest],Nodes,LD) ->
+ do_initial_tcs(Rest,Nodes,LD);
+do_initial_tcs([],_Nodes,LD) ->
+ LD.
+%% -----------------------------------------------------------------------------
+
+%% This help functio is used instead of do_initial_tcs/3 if there actually are no
+%% nodes to do the trace cases on. The reason we must have this function is that
+%% the tracecases must still be entered into the history with bindings and all.
+%% But we let them be marked as 'running' immediately (no need for the activator
+%% process).
+add_initial_tcs_to_history([{TC,Vars}|Rest],LD=#ld{tc_dict=TCdict,chl=CHL}) ->
+ case get_tracecase_tc_dict(TC,TCdict) of
+ {ok,TraceCase} ->
+ case check_bindings(Vars,TraceCase) of
+ {ok,Bindings} ->
+ Id=make_ref(), % Trace case ID.
+ FakeProcH=make_ref(), % Need something to enter as activator.
+ NewCHL=set_activating_chl(TC,Id,CHL,Bindings,FakeProcH),
+ NewCHL2=set_running_chl(FakeProcH,TC,Id,void,NewCHL), % Result=void.
+ NewInitialTcs=add_initial_tcs(TC,Id,LD#ld.started_initial_tcs),
+ add_initial_tcs_to_history(Rest,LD#ld{chl=NewCHL2,
+ started_initial_tcs=NewInitialTcs});
+ {error,_Reason} -> % Not much we can do about that.
+ add_initial_tcs_to_history(Rest,LD)
+ end;
+ false ->
+ add_initial_tcs_to_history(Rest,LD)
+ end;
+add_initial_tcs_to_history([],LD) ->
+ LD.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% reinitiate_session
+%% -----------------------------------------------------------------------------
+
+%% Function doing the reinitiation. That means first do init_tracing at the nodes
+%% in question. Then redo the command history to bring them up to speed.
+%% But first the runtime component is cleared of all trace patterns.
+h_reinitiate_session(Nodes,M,F,TDGargs,LD=#ld{c_node=CNode,nodes=NodesD}) ->
+ case h_reinitiate_session_2(Nodes,NodesD,CNode) of
+ {ok,{[],NodesErr}} -> % No nodes to reinitiate.
+ {ok,{NodesErr,{ok,[]},LD}};
+ {ok,{Nodes2,NodesErr}} -> % List of nodes or local_runtime.
+ case call_tracer_data_generator(CNode,M,F,TDGargs,Nodes2) of
+ {ok,TracerList} ->
+ case h_start_session_2(CNode,TracerList,[]) of % Borrow from start_session.
+ {ok,ReturnValue} -> % Ok, now we must redo cmd history.
+ {NewNodesD,_Nodes}=
+ set_tracing_running_nodes(CNode,ReturnValue,NodesD),
+ NewLD=h_reinitiate_session_chl(Nodes2,LD#ld{nodes=NewNodesD}),
+ {ok,{NodesErr,ReturnValue,NewLD}};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ {error,Reason} ->
+ {error,{bad_tdg,Reason}}
+ end;
+ {error,Reason} ->
+ {error,Reason}
+ end.
+
+%% Help function finding out which nodes in Nodes actually can be reinitiated.
+%% A node must be up, inactive and not suspended in order for this to work. All the
+%% rest is just a matter of how detailed error return values we want to generate.
+h_reinitiate_session_2(local_runtime,NodesD,undefined) -> % Non distributed case.
+ case get_state_nodes(local_runtime,NodesD) of
+ {inactive,running} -> % Only ok case.
+ case inviso:ctp_all() of
+ ok ->
+ {ok,{local_runtime,[]}};
+ {error,Reason} -> % This is strange.
+ {error,Reason}
+ end;
+ {_,suspended} ->
+ {ok,{[],{error,suspended}}};
+ down ->
+ {ok,{[],{error,down}}};
+ _ ->
+ {ok,{[],{error,already_in_session}}}
+ end;
+h_reinitiate_session_2(Nodes,NodesD,CNode) when list(Nodes) ->
+ {ok,lists:foldl(fun(N,{Nodes2,NodesErr})->
+ case get_state_nodes(N,NodesD) of
+ {inactive,running} -> % Only ok case.
+ case rpc:call(CNode,inviso,ctp_all,[[N]]) of
+ {ok,[{N,ok}]} ->
+ {[N|Nodes2],NodesErr};
+ {ok,[{N,{error,Reason}}]} ->
+ {Nodes2,[{N,{error,Reason}}|NodesErr]};
+ {error,Reason} ->
+ {Nodes2,[{N,{error,Reason}}|NodesErr]};
+ {badrpc,Reason} ->
+ {Nodes2,[{N,{error,{badrpc,Reason}}}|NodesErr]}
+ end;
+ {_,suspended} ->
+ {Nodes2,[{N,{error,suspended}}|NodesErr]};
+ down ->
+ {Nodes2,[{N,{error,down}}|NodesErr]};
+ false ->
+ {Nodes2,[{N,{error,unknown_node}}|NodesErr]};
+ _ ->
+ {Nodes2,[{N,{error,already_in_session}}|NodesErr]}
+ end
+ end,
+ {[],[]},
+ Nodes)};
+h_reinitiate_session_2(Nodes,_NodesD,_CNode) ->
+ {error,{badarg7,Nodes}}.
+
+%% Help function redoing the command history log at all nodes that actually
+%% started to trace. Note that we do not modify the return value which will be
+%% given to the caller just because we decide not to redo commands. The user
+%% must conclude him self from the inviso return value that commands were not
+%% redone at a particular node.
+h_reinitiate_session_chl(local_runtime,LD) ->
+ h_reinitiate_session_chl([local_runtime],LD);
+h_reinitiate_session_chl([Node|Rest],LD=#ld{nodes=NodesD}) ->
+ case get_state_nodes(Node,NodesD) of
+ {tracing,running} -> % Only case when we shall redo!
+ h_reinitiate_session_chl(Rest,redo_cmd_history(Node,LD));
+ _ -> % No redo of chl in other cases.
+ h_reinitiate_session_chl(Rest,LD)
+ end;
+h_reinitiate_session_chl([],LD) ->
+ LD.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% restore_session
+%% -----------------------------------------------------------------------------
+
+%% Help function starting a session (init tracing) and redoes the history
+%% found in CHL.
+h_restore_session(MoreTDGargs,LD) ->
+ DateTime=calendar:universal_time(),
+ {M,F,Args}=LD#ld.tdg,
+ TDGargs=inviso_tool_lib:mk_tdg_args(DateTime,MoreTDGargs++Args),
+ case h_start_session(M,F,TDGargs,LD) of
+ {ok,{SessionNr,ReturnVal,NewLD}} -> % There were no available nodes.
+ {ok,{SessionNr,ReturnVal,NewLD}};
+ {ok,{SessionNr,ReturnVal,Nodes2,NewLD}} ->
+ NewLD2=h_reinitiate_session_chl(Nodes2,NewLD),
+ {ok,{SessionNr,ReturnVal,NewLD2}};
+ {error,Reason} -> % Risk of out of control.
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% stop_session
+%% -----------------------------------------------------------------------------
+
+%% Help function stopping tracing at tracing nodes.
+h_stop_session(#ld{c_node=CNode,nodes=NodesD,tracer_data=TDs}) ->
+ case h_stop_session_2(CNode,NodesD) of
+ {ok,Result} ->
+ {ok,{get_latest_session_nr_tracer_data(TDs),Result}};
+ {error,Reason} ->
+ {error,Reason}
+ end.
+
+h_stop_session_2(undefined,NodesD) -> % The non distributed case.
+ case get_tracing_nodes(NodesD) of
+ {up,{inactive,_}} -> % Already not tracing!
+ {ok,[]};
+ {up,_} ->
+ case inviso:stop_tracing() of
+ {ok,_State} ->
+ {ok,[ok]};
+ {error,no_response} ->
+ {ok,[]};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ down ->
+ {ok,[]}
+ end;
+h_stop_session_2(CNode,NodesD) ->
+ Nodes=get_tracing_nodes(NodesD),
+ case rpc:call(CNode,inviso,stop_tracing,[Nodes]) of
+ {ok,NodeResults} ->
+ {ok,lists:map(fun({N,{ok,_}})->{N,ok};
+ (NodeError)->NodeError
+ end,
+ NodeResults)};
+ {error,Reason} ->
+ {error,Reason};
+ {badrpc,Reason} ->
+ {error,{inviso_control_node_error,Reason}}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function removing any trace flags, trace patterns and meta trace patterns
+%% at Nodes. This will cause the nodes to become "fresh".
+h_reset_nodes(local_runtime,_CNode) ->
+ inviso:clear([keep_log_files]);
+h_reset_nodes(Nodes,CNode) ->
+ case inviso_tool_lib:inviso_cmd(CNode,clear,[Nodes,[keep_log_files]]) of
+ {ok,NodeResults} ->
+ {ok,NodeResults};
+ {error,Reason} ->
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+
+%% -----------------------------------------------------------------------------
+%% atc
+%% -----------------------------------------------------------------------------
+
+%% Function handling ativating a trace case. Trace cases that do not have a
+%% particular on/off handling (but just on in some scense) are handled here too.
+%% The trace case is entered into the Command History Log.
+%% Note that the trace case can not be executed at this node but must be
+%% executed where the inviso control component is.
+%% Further it is possible to either activated the tracecase for all running and
+%% tracing nodes, or just for a specified list of nodes.
+%% TC=tracecase_name(),
+%% Id=term(), identifiying this usage so we can turn it off later.
+%% Vars=list(), list of variable-value bindnings.
+h_atc(TC,Id,Vars,LD) ->
+ h_atc(TC,Id,Vars,LD,void). % For all running-tracing nodes.
+
+h_atc(TC,Id,Vars,LD=#ld{c_node=CNode,tc_dict=TCdict,chl=CHL},Nodes) ->
+ case find_id_chl(TC,Id,CHL) of
+ activating -> % Already started.
+ {error,activating};
+ stopping -> % Not yet stopped.
+ {error,deactivating};
+ false ->
+ case get_tracecase_tc_dict(TC,TCdict) of
+ {ok,TraceCase} -> % Such a trace case exists.
+ case check_bindings(Vars,TraceCase) of
+ {ok,Bindings} -> % Necessary vars exists in Vars.
+ if
+ list(Nodes) -> % Nodes predefined.
+ h_atc_2(TC,Id,CNode,CHL,LD,TraceCase,Bindings,Nodes);
+ true -> % Use all tracing and running nodes.
+ Nodes1=get_nodenames_running_nodes(LD#ld.nodes),
+ h_atc_2(TC,Id,CNode,CHL,LD,TraceCase,Bindings,Nodes1)
+ end;
+ {error,Reason} -> % Variable def missing.
+ {error,Reason}
+ end;
+ false ->
+ {error,unknown_tracecase}
+ end;
+ {ok,_Bindings} -> % Already activated and running.
+ {error,already_started}
+ end.
+
+h_atc_2(TC,Id,CNode,CHL,LD,TraceCase,Bindings,Nodes) ->
+ case exec_trace_case_on(CNode,TraceCase,Bindings,Nodes) of
+ {ok,ProcH} -> % Trace cases have no return values.
+ NewCHL=set_activating_chl(TC,Id,CHL,Bindings,ProcH),
+ {ok,LD#ld{chl=NewCHL}};
+ {error,Reason} ->
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% sync_atc
+%% -----------------------------------------------------------------------------
+
+h_sync_atc(TC,Id,Vars,TimeOut,LD=#ld{c_node=CNode,tc_dict=TCdict,chl=CHL}) ->
+ case find_id_chl(TC,Id,CHL) of
+ activating -> % Already started.
+ {error,activating};
+ stopping -> % Not yet stopped.
+ {error,deactivating};
+ false ->
+ case get_tracecase_tc_dict(TC,TCdict) of
+ {ok,TraceCase} -> % Such a trace case exists.
+ case check_bindings(Vars,TraceCase) of
+ {ok,Bindings} -> % Necessary vars exists in Vars.
+ {ok,TcFName}=get_tc_activate_fname(TraceCase),
+ Nodes=get_nodenames_running_nodes(LD#ld.nodes),
+ Bindings2=erl_eval:add_binding('Nodes',Nodes,Bindings),
+ RpcNode=get_rpc_nodename(CNode),
+ case rpc:call(RpcNode,file,script,[TcFName,Bindings2],TimeOut) of
+ {ok,Value} ->
+ FakeProcH=make_ref(),
+ NewCHL1=set_activating_chl(TC,Id,CHL,Bindings,FakeProcH),
+ NewCHL2=set_running_chl(FakeProcH,TC,Id,Value,NewCHL1),
+ {ok,LD#ld{chl=NewCHL2},Value};
+ {error,Reason} ->
+ {error,{faulty_tracecase,{TcFName,Reason}}};
+ {badrpc,Reason} ->
+ {error,{badrpc,Reason}}
+ end;
+ {error,Reason} -> % Variable def missing.
+ {error,Reason}
+ end;
+ false ->
+ {error,unknown_tracecase}
+ end;
+ {ok,_Bindings} -> % Already activated and running.
+ {error,already_started}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% rtc
+%% -----------------------------------------------------------------------------
+
+%% Function handling running a trace case without marking it as activated. It
+%% is in the history mearly indicated as activated
+h_sync_rtc(TC,Vars,TimeOut,LD=#ld{c_node=CNode,tc_dict=TCdict,chl=CHL}) ->
+ case get_tracecase_tc_dict(TC,TCdict) of
+ {ok,TraceCase} -> % Such a trace case exists.
+ case check_bindings(Vars,TraceCase) of
+ {ok,Bindings} -> % Necessary vars exists in Vars.
+ {ok,TcFName}=get_tc_activate_fname(TraceCase),
+ Nodes=get_nodenames_running_nodes(LD#ld.nodes),
+ Bindings2=erl_eval:add_binding('Nodes',Nodes,Bindings),
+ RpcNode=get_rpc_nodename(CNode),
+ case rpc:call(RpcNode,file,script,[TcFName,Bindings2],TimeOut) of
+ {ok,Value} ->
+ {ok,LD#ld{chl=add_rtc_chl(TC,Bindings2,CHL)},Value};
+ {error,Reason} ->
+ {error,{faulty_tracecase,{TcFName,Reason}}};
+ {badrpc,Reason} ->
+ {error,{badrpc,Reason}}
+ end;
+ {error,Reason} -> % Variable def missing.
+ {error,Reason}
+ end;
+ false ->
+ {error,unknown_tracecase}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% dtc
+%% -----------------------------------------------------------------------------
+
+%% Function handling turning a trace case off. The trace case must be registered
+%% as having an off mechanism. If it has an off mechanism and was previously entered
+%% into the Command History Log and is done with its activation phase, it will be
+%% executed and removed from the CHL.
+h_dtc(TC,Id,LD=#ld{c_node=CNode,tc_dict=TCdict,chl=CHL}) ->
+ case find_id_chl(TC,Id,CHL) of
+ {ok,Bindings} -> % Yes, we have turned it on before.
+ case get_tracecase_tc_dict(TC,TCdict) of
+ {ok,TraceCase} ->
+ Nodes=get_nodenames_running_nodes(LD#ld.nodes),
+ case exec_trace_case_off(CNode,TraceCase,Bindings,Nodes) of
+ {ok,ProcH} ->
+ NewCHL=set_stopping_chl(TC,Id,CHL,ProcH),
+ {ok,LD#ld{chl=NewCHL}};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ false -> % Strange, Id ok but no such trace case.
+ {error,unknown_tracecase}
+ end;
+ false -> % Not previously turned on.
+ {error,unknown_id};
+ activating ->
+ {error,activating};
+ stopping ->
+ {error,already_deactivating}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% sync_dtc
+%% -----------------------------------------------------------------------------
+
+h_sync_dtc(TC,Id,TimeOut,LD=#ld{c_node=CNode,tc_dict=TCdict,chl=CHL}) ->
+ case find_id_chl(TC,Id,CHL) of
+ {ok,Bindings} -> % Yes, we have turned it on before.
+ case get_tracecase_tc_dict(TC,TCdict) of
+ {ok,TraceCase} ->
+ case get_tc_deactivate_fname(TraceCase) of
+ {ok,TcFName} ->
+ Nodes=get_nodenames_running_nodes(LD#ld.nodes),
+ Bindings2=erl_eval:add_binding('Nodes',Nodes,Bindings),
+ RpcNode=get_rpc_nodename(CNode),
+ case rpc:call(RpcNode,file,script,[TcFName,Bindings2],TimeOut) of
+ {ok,Value} ->
+ FakeProcH=make_ref(),
+ NewCHL1=set_stopping_chl(TC,Id,CHL,FakeProcH),
+ NewCHL2=nullify_chl(FakeProcH,TC,Id,NewCHL1),
+ {ok,LD#ld{chl=NewCHL2},Value};
+ {error,Reason} -> % Script fault.
+ {error,{faulty_tracecase,{TcFName,Reason}}};
+ {badrpc,Reason} ->
+ {error,{badrpc,Reason}}
+ end;
+ false ->
+ {error,no_deactivation}
+ end;
+ false -> % Strange, Id ok but no such trace case.
+ {error,unknown_tracecase}
+ end;
+ false -> % Not previously turned on.
+ {error,unknown_id};
+ activating ->
+ {error,activating};
+ stopping ->
+ {error,already_deactivating}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% inviso
+%% -----------------------------------------------------------------------------
+
+%% Function executing one inviso command. The returnvalue from the inviso
+%% function call will be the return value to the client. The command is
+%% entered into the history command log.
+%% Note that the inviso call may have to be done at another node, dictated
+%% by the c_node field. Further, if the module name is not an atom it is
+%% most likely a regexp, which must be expanded at the regexp_node. Note
+%% this is only relevant for tp and tpl.
+h_inviso(Cmd,Args,LD=#ld{c_node=CNode,regexp_node=RegExpNode,chl=CHL}) ->
+ Arity=length(Args),
+ case check_proper_inviso_call(Cmd,Arity) of
+ {true,RegExpFlag} -> % Yes it is an inviso call.
+ Nodes=get_nodenames_running_nodes(LD#ld.nodes),
+ case h_inviso_2(Cmd,Args,CNode,RegExpNode,RegExpFlag,Nodes) of
+ {ok,Result} ->
+ case check_inviso_call_to_history(Cmd,Arity) of
+ true -> % This function shall be added to chl.
+ {ok,{Result,LD#ld{chl=add_inviso_call_chl(Cmd,Args,CHL)}}};
+ false -> % Do not add it.
+ {ok,{Result,LD}}
+ end;
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ false -> % Not an inviso function.
+ {error,invalid_function_name}
+ end.
+
+h_inviso_2(Cmd,Args,undefined,_,_,_) -> % A non distributed system.
+ case catch apply(inviso,Cmd,Args) of % Regexp expansion only relevant when
+ {'EXIT',Reason} -> % distributed, here let inviso_rt expand.
+ {error,{'EXIT',Reason}};
+ Result ->
+ {ok,Result}
+ end;
+h_inviso_2(Cmd,Args,CNode,RegExpNode,RegExpFlag,Nodes) ->
+ case expand_module_regexps(Args,RegExpNode,Nodes,RegExpFlag) of
+ {ok,NewArgs} ->
+ case catch inviso_tool_lib:inviso_cmd(CNode,Cmd,[Nodes|NewArgs]) of
+ {'EXIT',Reason} ->
+ {error,{'EXIT',Reason}};
+ {error,{badrpc,Reason}} -> % Includes runtime failure.
+ {error,{badrpc,Reason}};
+ Result ->
+ {ok,Result}
+ end;
+ {error,Reason} ->
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% reactivate
+%% -----------------------------------------------------------------------------
+
+h_reactivate(_Node,undefined) -> % The non-distributed case.
+ case inviso:cancel_suspension() of
+ ok ->
+ ok;
+ {error,Reason} ->
+ {error,Reason}
+ end;
+h_reactivate(Node,CNode) ->
+ case inviso_tool_lib:inviso_cmd(CNode,cancel_suspension,[[Node]]) of
+ {ok,[{Node,ok}]} ->
+ ok;
+ {ok,[{Node,{error,Reason}}]} ->
+ {error,Reason};
+ {error,Reason} ->
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% save_history
+%% -----------------------------------------------------------------------------
+
+h_save_history(HDir,Dir,FileName,SortedLog) ->
+ Dir0=
+ if
+ list(HDir) -> % There is a history dir specified.
+ HDir; % Use it then.
+ true ->
+ Dir % Else use the tool dir.
+ end,
+ case catch make_absolute_path(FileName,Dir0) of
+ AbsFileName when list(AbsFileName) ->
+ Log2=build_saved_history_data(SortedLog), % Remove stopped tracecases.
+ case file:write_file(AbsFileName,term_to_binary(Log2)) of
+ ok ->
+ {ok,AbsFileName};
+ {error,Reason} ->
+ {error,{write_file,Reason}}
+ end;
+ {'EXIT',_Reason} ->
+ {error,{bad_filename,FileName}}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% get_autostart_data
+%% -----------------------------------------------------------------------------
+
+%% Help function building the structures used when exporting autostart information
+%% from the tool. Note that we remove the tool-dependency and insert the one
+%% specify in the get_autostart_data call.
+h_get_autostart_data(local_runtime,_,Dependency,ASD,M,F,TDGargs,OptsG) ->
+ CompleteTDGargs=call_tracer_data_generator_mkargs(local_runtime,TDGargs),
+ Opts0=start_runtime_components_mk_opts(local_runtime,OptsG),
+ Opts=[Dependency|lists:keydelete(dependency,1,Opts0)],
+ {ok,{ASD,{ok,{Opts,{tdg,{M,F,CompleteTDGargs}}}}}};
+
+h_get_autostart_data(Nodes,CNode,Dependency,ASD,M,F,TDGargs,OptsG) when list(Nodes) ->
+ {ok,{ASD,h_get_autostart_data_2(Nodes,CNode,Dependency,M,F,TDGargs,OptsG)}};
+h_get_autostart_data(Nodes,_CNode,_Dependency,_ASD,_M,_F,_TDGargs,_OptsG) ->
+ {error,{badarg,Nodes}}.
+
+h_get_autostart_data_2([Node|Rest],CNode,Dependency,M,F,TDGargs,OptsG) ->
+ CompleteTDGargs=call_tracer_data_generator_mkargs(Node,TDGargs),
+ Opts0=start_runtime_components_mk_opts(Node,OptsG),
+ Opts=[Dependency|lists:keydelete(dependency,1,Opts0)],
+ [{Node,{ok,{Opts,{tdg,{M,F,CompleteTDGargs}}}}}|
+ h_get_autostart_data_2(Rest,CNode,Dependency,M,F,TDGargs,OptsG)];
+h_get_autostart_data_2([],_CNode,_Dependency,_M,_F,_TDGargs,_OptsG) ->
+ [].
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% flush
+%% -----------------------------------------------------------------------------
+
+h_flush(undefined,_Nodes) ->
+ inviso:flush();
+h_flush(CNode,Nodes) ->
+ inviso_tool_lib:inviso_cmd(CNode,flush,[Nodes]).
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% tc_executer_reply
+%% -----------------------------------------------------------------------------
+
+%% Function handling that a trace case has completed its activation phase and
+%% shall now be marked in the Command History Log as running.
+h_tc_activation_done(ProcH,Result,LD=#ld{chl=CHL}) ->
+ case find_tc_executer_chl(ProcH,CHL) of
+ {activating,{TC,Id}} ->
+ case Result of
+ {ok,Value} -> % The trace case is successful activated.
+ {ok,LD#ld{chl=set_running_chl(ProcH,TC,Id,Value,CHL)}};
+ {error,_} -> % Then pretend it never happend :-)
+ {ok,LD#ld{chl=del_tc_chl(ProcH,TC,Id,CHL)}} % Remove it.
+ end;
+ _ -> % Where did this come from?
+ {ok,LD} % Well just ignore it then.
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Function handling that a trace case has completed its stopping phase and
+%% shall now be nulled in the Command History Log (meaning that it will not
+%% be repeated in the event of a reactivation).
+h_tc_stopping_done(ProcH,Result,LD=#ld{chl=CHL}) ->
+ case find_tc_executer_chl(ProcH,CHL) of
+ {stopping,{TC,Id}} ->
+ case Result of
+ {ok,_Result} -> % _Result is returned from the tracecase.
+ {ok,LD#ld{chl=nullify_chl(ProcH,TC,Id,CHL)}};
+ {error,_} -> % This is difficult, is it still active?
+ {ok,LD#ld{chl=nullify_chl(ProcH,TC,Id,CHL)}}
+ end;
+ _ -> % Strange.
+ {ok,LD}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% Terminate.
+%% -----------------------------------------------------------------------------
+
+%% Help function stopping the inviso control component. Does not return
+%% anything significant.
+stop_inviso_at_c_node(undefined) -> % Non distributed case.
+ inviso:stop();
+stop_inviso_at_c_node(CNode) ->
+ rpc:call(CNode,inviso,stop,[]).
+%% -----------------------------------------------------------------------------
+
+%% Help function that removes all trace patterns from the nodes that are not
+%% marked as such were patterns shall be left after stopping of inviso.
+%% Returns {ok,NodeResult} or {error,Reason}. In the non-distributed case
+%% 'ok' is returned incase of success, ot 'patterns_untouched'.
+remove_all_trace_patterns(undefined,KeepNodes,_Nodes) ->
+ case KeepNodes of
+ undefined -> % No, remove patterns from localruntime.
+ inviso:ctp_all();
+ _ ->
+ patterns_untouched
+ end;
+remove_all_trace_patterns(CNode,KeepNodes,Nodes) ->
+ Nodes2=lists:filter(fun(N)->not(lists:member(N,KeepNodes)) end,Nodes),
+ case inviso_tool_lib:inviso_cmd(CNode,ctp_all,[Nodes2]) of
+ {ok,NodeResults} ->
+ F=fun(N) ->
+ case lists:member(N,KeepNodes) of
+ true ->
+ {N,patterns_untouched};
+ false ->
+ case lists:keysearch(N,1,NodeResults) of
+ {value,Result} ->
+ Result; % {Node,ok}
+ false -> % Extremely strange.
+ {N,{error,general_error}}
+ end
+ end
+ end,
+ {ok,lists:map(F,Nodes)};
+ {error,{badrpc,Reason}} ->
+ {error,{inviso_control_node_error,Reason}};
+ {error,Reason} ->
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+%% =============================================================================
+%% Second level help functions.
+%% =============================================================================
+
+%% Help function building a reply to a reconnection call based on which nodes
+%% where asked to be reconnected and which of those are actually now working.
+%% We actually make an effort to serve the return value in the same order as the
+%% nodes were mentioned in the original call (Nodes).
+build_reconnect_nodes_reply(local_runtime,local_runtime,_NodesErr,NodesD) ->
+ case get_state_nodes(local_runtime,NodesD) of
+ down ->
+ {error,down};
+ {State,Status} ->
+ {ok,{State,Status}}
+ end;
+build_reconnect_nodes_reply(local_runtime,_,NodesErr,_NodesD) ->
+ NodesErr;
+build_reconnect_nodes_reply([Node|Rest],Nodes2,NodesErr,NodesD) ->
+ case lists:member(Node,Nodes2) of
+ true -> % Ok, look in the #ld.nodes.
+ case get_state_nodes(Node,NodesD) of
+ down -> % Somekind of failure, still down.
+ [{Node,{error,down}}|
+ build_reconnect_nodes_reply(Rest,Nodes2,NodesErr,NodesD)];
+ {State,Status} -> % {State,Status}
+ [{Node,{ok,{State,Status}}}|
+ build_reconnect_nodes_reply(Rest,Nodes2,NodesErr,NodesD)]
+ end;
+ false -> % Error already from the beginning.
+ {value,{_,Error}}=lists:keysearch(Node,1,NodesErr),
+ [{Node,Error}|build_reconnect_nodes_reply(Rest,Nodes2,NodesErr,NodesD)]
+ end;
+build_reconnect_nodes_reply([],_,_,_) ->
+ [].
+%% -----------------------------------------------------------------------------
+
+%% Help function building a return value to reinitiate_session. Nodes contains
+%% all involved nodes. If the node occurrs in NodesErr, we choose the error in
+%% NodesErr. Otherwise the returnvalue in ReturnVal is used.
+build_reinitiate_session_reply(Nodes,NodesErr,{ok,NodesResults}) ->
+ {ok,build_reinitiate_session_reply_2(Nodes,NodesErr,NodesResults)};
+build_reinitiate_session_reply(local_runtime,[],NodeResult) ->
+ NodeResult;
+build_reinitiate_session_reply(local_runtime,NodesErr,_NodeResult) ->
+ NodesErr.
+build_reinitiate_session_reply_2([Node|Rest],NodesErr,NodeResults) ->
+ case lists:keysearch(Node,1,NodesErr) of
+ {value,{_,Error}} ->
+ [{Node,Error}|build_reinitiate_session_reply_2(Rest,NodesErr,NodeResults)];
+ false ->
+ case lists:keysearch(Node,1,NodeResults) of
+ {value,Value} ->
+ [Value|build_reinitiate_session_reply_2(Rest,NodesErr,NodeResults)]
+ end
+ end;
+build_reinitiate_session_reply_2([],_NodesErr,_NodeResults) ->
+ [].
+%% -----------------------------------------------------------------------------
+
+%% Help function returning a history log where stop and stopping entries have
+%% been removed. Further all tracecase log entries must be set to running since
+%% there can not be such a thing as an activating tracecase stored away in a
+%% saved historyfile!
+%% We must also take away any #Ref.
+build_saved_history_data(SortedLog) ->
+ CleanedLog=
+ lists:filter(fun({_,_,Stop,_}) when Stop==stop;Stop==stopping -> false;
+ (_) -> true
+ end,
+ SortedLog),
+ lists:map(fun({{TC,Id},C,activating,B}) -> {{TC,Id},C,running,B};
+ ({{TC,Id},C,S,B}) -> {{TC,Id},C,S,B};
+ ({{M,F,Args,_Ref},C}) -> {{M,F,Args},C};
+ ({{TC,_Ref},C,B}) -> {TC,C,B} % An rtc.
+ end,
+ CleanedLog).
+%% -----------------------------------------------------------------------------
+
+%% This help function builds the AutoStartData structure which is returned from
+%% get_austostart_data. An AutoStartData structure is a list of trace-files and
+%% inviso commands. The order is significant since it is the idea that doing
+%% the trace case files and inviso commands in that order will bring a node to
+%% a certain state in a trace perspective.
+%% Returns {ok,AutoStartData} or {error,Reason}
+build_autostart_data(SortedLog,TCdict) ->
+ build_autostart_data_2(SortedLog,TCdict,[]).
+
+build_autostart_data_2([{_,_C,Stop,_B}|Rest],TCdict,Accum) when Stop==stop;Stop==stopping->
+ build_autostart_data_2(Rest,TCdict,Accum); % Simply skip deactivated/deativating.
+build_autostart_data_2([{{TCname,_},_C,activating,Bindings}|Rest],TCdict,Accum) ->
+ build_autostart_data_tc(TCname,Bindings,TCdict,Rest,Accum);
+build_autostart_data_2([{{TCname,_},_C,running,Bindings}|Rest],TCdict,Accum) ->
+ build_autostart_data_tc(TCname,Bindings,TCdict,Rest,Accum);
+build_autostart_data_2([{{TCname,_Ref},_C,Bindings}|Rest],TCdict,Accum) ->
+ build_autostart_data_tc(TCname,Bindings,TCdict,Rest,Accum);
+build_autostart_data_2([{{M,F,Args,_Ref},_C}|Rest],TCdict,Accum) ->
+ build_autostart_data_2(Rest,TCdict,[{mfa,{M,F,Args}}|Accum]);
+build_autostart_data_2([],_TCdict,Accum) ->
+ {ok,lists:reverse(Accum)}.
+
+%% Help function placing the filename in the AutoStartData structure.
+build_autostart_data_tc(TCname,Bindings,TCdict,Rest,Accum) ->
+ {ok,TC}=get_tracecase_tc_dict(TCname,TCdict),
+ {ok,FName}=get_tc_activate_fname(TC),
+ build_autostart_data_2(Rest,TCdict,[{file,{FName,Bindings}}|Accum]).
+%% -----------------------------------------------------------------------------
+
+%% Help function generating tracerdata to init inviso tracing. The generation
+%% is done by the TracerDataGenerator, TDG, function.
+%% Individual tracerdata is generated for each node in Nodes.
+%% Returns {ok,TracerData} or {error,Reason}.
+call_tracer_data_generator(undefined,M,F,TDGargs,_Nodes) -> % Non distributed.
+ case catch call_tracer_data_generator_3(M,F,TDGargs,local_runtime) of
+ {'EXIT',Reason} ->
+ {error,{'EXIT',Reason}};
+ TracerData ->
+ {ok,TracerData}
+ end;
+call_tracer_data_generator(_CNode,M,F,TDGargs,Nodes) ->
+ case catch call_tracer_data_generator_2(M,F,TDGargs,Nodes) of
+ {'EXIT',Reason} ->
+ {error,{'EXIT',Reason}};
+ TracerList ->
+ {ok,TracerList}
+ end.
+
+call_tracer_data_generator_2(M,F,TDGargs,[Node|Rest]) ->
+ [{Node,call_tracer_data_generator_3(M,F,TDGargs,Node)}|
+ call_tracer_data_generator_2(M,F,TDGargs,Rest)];
+call_tracer_data_generator_2(_,_,_,[]) ->
+ [].
+
+call_tracer_data_generator_3(M,F,TDGargs,Node) ->
+ apply(M,F,call_tracer_data_generator_mkargs(Node,TDGargs)).
+
+%% This function creates the arguments that the tracer data generator function
+%% accepts (in an apply call). The reason for making it a sepparate function is
+%% that the arguments are constructed in more situations than just when actually
+%% doing the apply. By having a function it will become obvious where to change
+%% should the arguments change.
+call_tracer_data_generator_mkargs(Node,TDGargs) ->
+ inviso_tool_lib:mk_complete_tdg_args(Node,TDGargs).
+%% -----------------------------------------------------------------------------
+
+%% This function acts as standard options generator function. That is returning
+%% the options argument to inviso:add_node/3. Note that this function must not
+%% return the dependency part of that option.
+std_options_generator(_Node) ->
+ []. % No particular options(!)
+%% -----------------------------------------------------------------------------
+
+
+%% Help function checking that Vars contains a binding for every variable
+%% listed in the VarNames field in TraceCase. Note that the special variable 'Nodes'
+%% is disregarded, since it is always added by the inviso_tool.
+%% Returns {ok,Bindings} or {error,Reason}. Where Bindings is a bindngs structure
+%% according to file:eval functionality.
+check_bindings(Vars,TraceCase) ->
+ case catch check_bindings_2(Vars,
+ get_tc_varnames(TraceCase),
+ erl_eval:new_bindings()) of
+ {'EXIT',_Reason} ->
+ {error,variable_error};
+ {error,Reason} -> % Missing a bindning.
+ {error,Reason};
+ {ok,Bindings} ->
+ {ok,Bindings}
+ end.
+
+check_bindings_2(Vars,['Nodes'|Rest],Bindings) ->
+ check_bindings_2(Vars,Rest,Bindings); % Disregard Nodes since it is automatic.
+check_bindings_2(Vars,[VarName|Rest],Bindings) ->
+ case lists:keysearch(VarName,1,Vars) of
+ {value,{_,Val}} ->
+ check_bindings_2(Vars,Rest,erl_eval:add_binding(VarName,Val,Bindings));
+ false -> % Mandatory variable missing.
+ {error,{missing_variable,VarName}} % Quite here then.
+ end;
+check_bindings_2(_,[],Bindings) ->
+ {ok,Bindings}.
+%% -----------------------------------------------------------------------------
+
+%% This help function checks that the command the user tries to do is amongst
+%% the inviso API. It at the same time returns what kind of command it is.
+%% {true,RegExpFlag} or 'false' where RegExpFlag indicates if this command
+%% needs to have its argument modified by module regexp expansion or not.
+check_proper_inviso_call(Cmd,Arity) ->
+ case lists:member({Cmd,Arity},?INVISO_CMDS) of
+ true -> % It is part of inviso API.
+ {true,check_proper_inviso_call_regexp(Cmd,Arity)};
+ false ->
+ false
+ end.
+
+%% Returns {Type,Arity,PlaceOfModuleSpec} or 'false'.
+check_proper_inviso_call_regexp(tp,5) -> {tp,5,1};
+check_proper_inviso_call_regexp(tp,4) -> {tp,4,1};
+check_proper_inviso_call_regexp(tp,1) -> {tp,1,1};
+check_proper_inviso_call_regexp(tpl,5) -> {tp,5,1};
+check_proper_inviso_call_regexp(tpl,4) -> {tp,4,1};
+check_proper_inviso_call_regexp(tpl,1) -> {tp,1,1};
+check_proper_inviso_call_regexp(ctp,3) -> {ctp,3,1};
+check_proper_inviso_call_regexp(ctp,1) -> {ctp,1,1};
+check_proper_inviso_call_regexp(ctpl,3) -> {ctp,3,1};
+check_proper_inviso_call_regexp(ctpl,1) -> {ctp,1,1};
+check_proper_inviso_call_regexp(_,_) -> % No regexp expansion.
+ false.
+%% -----------------------------------------------------------------------------
+
+%% Help function checking if this inviso command shall be added to the command
+%% history log. Returns true or false.
+check_inviso_call_to_history(Cmd,Arity) ->
+ case lists:member({Cmd,Arity},?INVISO_CMD_HISTORY) of
+ true ->
+ true;
+ false ->
+ false
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function traversing the arguments and expanding module names stated
+%% as regular expressions. This means that the resulting arguments may be longer
+%% than the orginal ones.
+%% When we run this function it has been determined that we are a distributed
+%% system.
+%% Also note that if there are no regexps in Args, no regexpansion will be
+%% made and RegExpNode may be 'undefined' (as it is if not set at start-up).
+%% If RegExpNode is unavailable the nodes found in Nodes will be used until
+%% one that works is found.
+expand_module_regexps(Args,_RegExpNode,_Nodes,false) ->
+ {ok,Args};
+expand_module_regexps([PatternList],RegExpNode,Nodes,{tp,1,1}) ->
+ case catch expand_module_regexps_tp(PatternList,RegExpNode,Nodes) of
+ NewPatternList when list(NewPatternList) ->
+ {ok,[NewPatternList]};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+expand_module_regexps([PatternList],RegExpNode,Nodes,{ctp,1,1}) ->
+ case catch expand_module_regexps_ctp(PatternList,RegExpNode,Nodes) of
+ NewPatternList when list(NewPatternList) ->
+ {ok,[NewPatternList]};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+expand_module_regexps([M,F,Arity,MS,Opts],RegExpNode,Nodes,{tp,5,1}) ->
+ expand_module_regexps([[{M,F,Arity,MS,Opts}]],RegExpNode,Nodes,{tp,1,1});
+expand_module_regexps([M,F,Arity,MS],RegExpNode,Nodes,{tp,4,1}) ->
+ expand_module_regexps([[{M,F,Arity,MS,[]}]],RegExpNode,Nodes,{tp,1,1});
+expand_module_regexps([M,F,Arity],RegExpNode,Nodes,{ctp,3,1}) ->
+ expand_module_regexps([[{M,F,Arity}]],RegExpNode,Nodes,{ctp,1,1}).
+
+
+expand_module_regexps_tp([E={M,_,_,_,_}|Rest],RegExpNode,Nodes) when atom(M) ->
+ [E|expand_module_regexps_tp(Rest,RegExpNode,Nodes)];
+expand_module_regexps_tp([{M,F,Arity,MS,Opts}|Rest],RegExpNode,Nodes) when list(M);tuple(M) ->
+ case inviso_tool_lib:expand_module_names([RegExpNode],
+ M,
+ [{expand_only_at,RegExpNode}]) of
+ {singlenode_expansion,Modules} ->
+ expand_module_regexps_tp_2(Modules,F,Arity,MS,Opts,Rest,RegExpNode,Nodes);
+ {error,{faulty_node,RegExpNode}} -> % RegExpNode probably down.
+ case Nodes of
+ [NewRegExpNode|RestNodes] -> % Ok, just choose a node.
+ expand_module_regexps_tp([{M,F,Arity,MS,Opts}|Rest],NewRegExpNode,RestNodes);
+ [] -> % No more nodes to choose from.
+ throw({error,no_available_regexpnode})
+ end;
+ {error,_Reason} ->
+ expand_module_regexps_tp(Rest,RegExpNode,Nodes)
+ end;
+expand_module_regexps_tp([_|Rest],RegExpNode,Nodes) ->
+ expand_module_regexps_tp(Rest,RegExpNode,Nodes); % Skip faulty module specification.
+expand_module_regexps_tp([],_RegExpNodes,_Nodes) ->
+ [].
+
+expand_module_regexps_tp_2([M|MRest],F,Arity,MS,Opts,Rest,RegExpNode,Nodes) ->
+ [{M,F,Arity,MS,Opts}|
+ expand_module_regexps_tp_2(MRest,F,Arity,MS,Opts,Rest,RegExpNode,Nodes)];
+expand_module_regexps_tp_2([],_,_,_,_,Rest,RegExpNode,Nodes) ->
+ expand_module_regexps_tp(Rest,RegExpNode,Nodes).
+
+expand_module_regexps_ctp([E={M,_,_}|Rest],RegExpNode,Nodes) when atom(M) ->
+ [E|expand_module_regexps_ctp(Rest,RegExpNode,Nodes)];
+expand_module_regexps_ctp([{M,F,Arity}|Rest],RegExpNode,Nodes) when list(M);tuple(M) ->
+ case inviso_tool_lib:expand_module_names([RegExpNode],
+ M,
+ [{expand_only_at,RegExpNode}]) of
+ {singlenode_expansion,badrpc} -> % RegExpNode probably down.
+ case Nodes of
+ [NewRegExpNode|RestNodes] -> % Ok, just choose a node.
+ expand_module_regexps_ctp([{M,F,Arity}|Rest],NewRegExpNode,RestNodes);
+ [] -> % No more nodes to choose from.
+ throw({error,no_available_regexpnode})
+ end;
+ {singlenode_expansion,Modules} ->
+ expand_module_regexps_ctp_2(Modules,F,Arity,Rest,RegExpNode,Nodes);
+ {error,_Reason} ->
+ expand_module_regexps_ctp(Rest,RegExpNode,Nodes)
+ end;
+expand_module_regexps_ctp([_|Rest],RegExpNode,Nodes) ->
+ expand_module_regexps_tp(Rest,RegExpNode,Nodes); % Skip faulty module specification.
+expand_module_regexps_ctp([],_RegExpNodes,_Nodes) ->
+ [].
+
+expand_module_regexps_ctp_2([M|MRest],F,Arity,Rest,RegExpNode,Nodes) ->
+ [{M,F,Arity}|expand_module_regexps_ctp_2(MRest,F,Arity,Rest,RegExpNode,Nodes)];
+expand_module_regexps_ctp_2([],_,_,Rest,RegExpNode,Nodes) ->
+ expand_module_regexps_ctp(Rest,RegExpNode,Nodes).
+%% -----------------------------------------------------------------------------
+
+
+
+%% Help function running the activation of a trace case. Note that this must
+%% be done at the inviso control component's Erlang node *and* that it must be
+%% done in its own process since there is no telling for how long a trace case
+%% may run.
+%% Returns {ok,ActivationHandler}.
+exec_trace_case_on(CNode,TraceCase,Bindings,Nodes) ->
+ {ok,TcFName}=get_tc_activate_fname(TraceCase),
+ {ok,exec_trace_case_2(CNode,
+ TcFName,
+ erl_eval:add_binding('Nodes',Nodes,Bindings),
+ activating)}.
+
+%% Help function running the deactivation of a trace case.
+exec_trace_case_off(CNode,TraceCase,Bindings,Nodes) ->
+ case get_tc_deactivate_fname(TraceCase) of
+ {ok,TcFName} -> % There is a deactivation.
+ {ok,exec_trace_case_2(CNode,
+ TcFName,
+ erl_eval:add_binding('Nodes',Nodes,Bindings),
+ stopping)};
+ false ->
+ {error,no_deactivation}
+ end.
+
+exec_trace_case_2(CNode,TcFName,Bindings,Phase) ->
+ if
+ CNode==undefined -> % The non distributed case.
+ spawn_link(?MODULE,tc_executer,[TcFName,Bindings,Phase,self()]);
+ true ->
+ spawn_link(CNode,?MODULE,tc_executer,[TcFName,Bindings,Phase,self()])
+ end.
+
+%% This function is run in its own process and is responsible for executing
+%% the trace case.
+tc_executer(TcFName,Bindings,Phase,Parent) ->
+ case catch file:script(TcFName,Bindings) of
+ {ok,Value} ->
+ tc_executer_reply(Parent,{Phase,self(),{ok,Value}});
+ {'EXIT',Reason} ->
+ tc_executer_reply(Parent,{Phase,self(),{error,{'EXIT',Reason}}});
+ Error ->
+ tc_executer_reply(Parent,{Phase,self(),Error})
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Help function which starts a reactivator process redoing command history at
+%% Node. It also updates the loopdata to indicate that Node is now in state
+%% reactivating. It is a good idea to only handle one node per reactivator process.
+%% This because if the node terminates and comes back up, the reactivator must be
+%% stopped.
+redo_cmd_history(Node,LD=#ld{c_node=CNode,tc_dict=TCdict,chl=CHL,nodes=NodesD}) ->
+ P=start_reactivator(Node,CNode,TCdict,CHL),
+ LD#ld{nodes=set_reactivating_nodes(Node,NodesD),
+ reactivators=add_reactivators(Node,P,LD#ld.reactivators)}.
+
+%% Help function starting a reactivator process replaying the command history log.
+%% Returns a pid of the reactivator process.
+start_reactivator(Node,CNode,TCdict,CHL) ->
+ UnsortedLog=get_loglist_chl(CHL), % Must fetch here, later on wrong node.
+ if
+ CNode==undefined -> % The non-distributed case.
+ spawn_link(?MODULE,
+ reactivator_executer,
+ [Node,TCdict,UnsortedLog,self(),0,[]]);
+ true ->
+ spawn_link(CNode,
+ ?MODULE,
+ reactivator_executer,
+ [Node,TCdict,UnsortedLog,self(),0,[]])
+ end.
+
+%% The strategy is to traverse the CHL ETS table in Counter order, redoing the
+%% commands one by one. We wait until one command is finished until we do the
+%% next. Commands marked as nullified are not performed. In fact when a command
+%% is nullified only the stop will be found in the CHL. Its activation will be
+%% removed.
+reactivator_executer(Node,TCdict,UnsortedLog,TPid,StartCounter,DoneCases) ->
+ SortedLog=lists:keysort(2,UnsortedLog), % Sort on Counter, oldest first.
+ Log=reactivator_skip_log_entries(SortedLog,StartCounter),
+ case reactivator_executer_2(Node,TCdict,TPid,StartCounter,DoneCases,Log) of
+ done ->
+ true; % Simply terminate the reactivator then.
+ {more,{NewStartCounter,NewDoneCases,NewUnsortedLog}} ->
+ reactivator_executer(Node,TCdict,NewUnsortedLog,TPid,NewStartCounter,NewDoneCases)
+ end.
+
+reactivator_executer_2(Node,TCdict,TPid,_Counter,DoneCases,
+ [{{TCname,Id},NextC,running,Bindings}|Rest]) ->
+ reactivator_executer_3(Node,TCdict,TPid,DoneCases,Rest,TCname,Id,NextC,Bindings,Rest);
+reactivator_executer_2(Node,TCdict,TPid,_Counter,DoneCases,
+ [{{TCname,_Ref},NextC,Bindings}|Rest]) ->
+ reactivator_executer_rtc(Node,TCdict,TPid,DoneCases,Rest,TCname,NextC,Bindings,Rest);
+reactivator_executer_2(Node,TCdict,TPid,_Counter,DoneCases,
+ [{{TCname,Id},NextC,activating,Bindings}|Rest]) ->
+ reactivator_executer_3(Node,TCdict,TPid,DoneCases,Rest,TCname,Id,NextC,Bindings,Rest);
+reactivator_executer_2(Node,TCdict,TPid,_Counter,DoneCases,
+ [{{M,F,Args,_Ref},NextC}|Rest]) ->
+ reactivator_executer_cmd(Node,M,F,Args),
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest);
+reactivator_executer_2(Node,TCdict,TPid,_Counter,DoneCases,
+ [{{_TCname,_Id},NextC,stopping,_Bindings}|Rest]) ->
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest);
+reactivator_executer_2(Node,TCdict,TPid,_Counter,DoneCases,
+ [{{TCname,Id,_Ref},NextC,stop,Bindings}|Rest]) ->
+ case lists:member({TCname,Id},DoneCases) of
+ true -> % We have activated it, must stop then.
+ case get_tracecase_tc_dict(TCname,TCdict) of
+ {ok,{_,_,_,_,FNameOff}} ->
+ reactivator_executer_tc(Node,Bindings,FNameOff),
+ NewDoneCases=lists:delete({TCname,Id},DoneCases),
+ reactivator_executer_2(Node,TCdict,TPid,NextC,NewDoneCases,Rest);
+ {ok,_} -> % No stop-filename, strange!
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest);
+ false -> % Even stranger, does not exist!?
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest)
+ end;
+ false -> % Never activated in the first place.
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest)
+ end;
+%% Done all log entries found this lap. See if there are more entries by now.
+reactivator_executer_2(_Node,_TCdict,TPid,Counter,DoneCases,[]) ->
+ case reactivator_reply(TPid,Counter) of % Ask the tool process for more entries.
+ done -> % No more entries in the CHL.
+ done;
+ {more,NewUnsortedLog} -> % Repeat the procedure
+ {more,{Counter+1,DoneCases,NewUnsortedLog}} % with log entries from Counter+1.
+ end.
+
+%% This help function activates a tracecase.
+reactivator_executer_3(Node,TCdict,TPid,DoneCases,Rest,TCname,Id,NextC,Bindings,Rest) ->
+ case get_tracecase_tc_dict(TCname,TCdict) of
+ {ok,{_,_,_,FNameOn}} -> % A case with just on functionality.
+ reactivator_executer_tc(Node,Bindings,FNameOn),
+ reactivator_executer_2(Node,TCdict,TPid,NextC,[{TCname,Id}|DoneCases],Rest);
+ {ok,{_,_,_,FNameOn,_}} ->
+ reactivator_executer_tc(Node,Bindings,FNameOn),
+ reactivator_executer_2(Node,TCdict,TPid,NextC,[{TCname,Id}|DoneCases],Rest);
+ false -> % Strange, does not exist anylonger!?
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest)
+ end.
+
+%% Help function executing a trace case in the reactivators context. Does not
+%% return anything significant.
+reactivator_executer_tc(Node,Bindings,FileName) ->
+ catch file:eval(FileName,erl_eval:add_binding('Nodes',[Node],Bindings)).
+
+%% Help function handling trace case that are simply executed - rtc.
+reactivator_executer_rtc(Node,TCdict,TPid,DoneCases,Rest,TCname,NextC,Bindings,Rest) ->
+ case get_tracecase_tc_dict(TCname,TCdict) of
+ {ok,{_,_,_,FNameOn}} -> % A case with just on functionality.
+ reactivator_executer_tc(Node,Bindings,FNameOn),
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest);
+ {ok,{_,_,_,FNameOn,_}} ->
+ reactivator_executer_tc(Node,Bindings,FNameOn),
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest);
+ false -> % Strange, does not exist anylonger!?
+ reactivator_executer_2(Node,TCdict,TPid,NextC,DoneCases,Rest)
+ end.
+
+reactivator_executer_cmd(nonode@nohost,M,F,Args) ->
+ catch apply(M,F,Args); % Non-distributed.
+reactivator_executer_cmd(Node,M,F,Args) ->
+ catch apply(M,F,[[Node]|Args]).
+
+%% Help function returning a list of log entries missing the first entries
+%% having a counter less or equal to C1.
+reactivator_skip_log_entries([{_,C,_,_}|Rest],C1) when C<C1 ->
+ reactivator_skip_log_entries(Rest,C1);
+reactivator_skip_log_entries([{_,C}|Rest],C1) when C<C1 ->
+ reactivator_skip_log_entries(Rest,C1);
+reactivator_skip_log_entries(Log,_) ->
+ Log.
+%% -----------------------------------------------------------------------------
+
+%% Help function returning the node name to use in an rpc call.
+get_rpc_nodename(undefined) ->
+ node();
+get_rpc_nodename(CNode) ->
+ CNode.
+%% -----------------------------------------------------------------------------
+
+mk_rt_tag() ->
+ inviso_tool.
+%% -----------------------------------------------------------------------------
+
+is_string([C|Rest]) when C>=32, C=<255 ->
+ is_string(Rest);
+is_string([]) ->
+ true;
+is_string(_) ->
+ false.
+%% -----------------------------------------------------------------------------
+
+
+%% -----------------------------------------------------------------------------
+%% Functions for handling the configuration file.
+%% -----------------------------------------------------------------------------
+
+%% The inviso tool is configured via start arguments and/or a configuration file.
+%% Start arguments will override any definitions in a configuration file.
+%% The configuration file is pointed out by either a start argument or the
+%% inviso application parameter 'inviso_tool_config_file'.
+
+%% Help function building the internal configuration structure. Configurations
+%% in the start argument will override parameters found in a configuration file.
+fetch_configuration(Config) ->
+ case fetch_config_filename(Config) of
+ {ok,FName} -> % We are supposed to use a conf-file.
+ case read_config_file(FName) of
+ {ok,LD} -> % Managed to open a file.
+ NewLD=read_config_list(LD,Config),
+ {ok,NewLD};
+ {error,_Reason} -> % Problem finding/opening file.
+ LD=read_config_list(#ld{},Config),
+ {ok,LD}
+ end;
+ false -> % No filename specified.
+ LD=read_config_list(#ld{},Config),
+ {ok,LD}
+ end.
+
+%% Help function determining the name of the file which shall be consulted as
+%% the main configuration file.
+%% Returns {ok,FileName} or 'false'. The latter if no name could be determined.
+fetch_config_filename(Config) ->
+ case catch lists:keysearch(config_file,1,Config) of
+ {value,{_,FName}} when list(FName) ->
+ {ok,FName};
+ _ -> % No filename in the start argument.
+ fetch_config_filename_2()
+ end.
+
+fetch_config_filename_2() ->
+ case application:get_env(inviso_tool_config_file) of
+ {ok,FName} when list(FName) ->
+ {ok,FName};
+ _ -> % Application parameter not specified.
+ false % Means no config file will be used.
+ end.
+
+%% Help function reading the configuration file. Returns a #conf or {error,Reason}.
+read_config_file(FName) ->
+ case catch file:consult(FName) of
+ {ok,Terms} ->
+ {ok,read_config_list(#ld{},Terms)};
+ {error,Reason} ->
+ {error,{file_consult,Reason}};
+ {'EXIT',Reason} ->
+ {error,{failure,Reason}}
+ end.
+
+%% Help function traversing the Terms list entering known tag-values into #ld.
+read_config_list(LD,Terms) ->
+ LD1=read_config_list_2(LD,Terms,nodes),
+ LD2=read_config_list_2(LD1,Terms,c_node),
+ LD3=read_config_list_2(LD2,Terms,regexp_node),
+ LD4=read_config_list_2(LD3,Terms,tc_def_file),
+ LD6=read_config_list_2(LD4,Terms,tdg),
+ LD8=read_config_list_2(LD6,Terms,debug),
+ LD10=read_config_list_2(LD8,Terms,initial_tcs),
+ LD11=read_config_list_2(LD10,Terms,dir),
+ _LD12=read_config_list_2(LD11,Terms,optg).
+
+read_config_list_2(LD,Terms,Tag) ->
+ case catch lists:keysearch(Tag,1,Terms) of
+ {value,{_,Value}} ->
+ update_ld_record(LD,Tag,Value);
+ _ ->
+ LD % Tag not found in Terms (or error!)
+ end.
+%% -----------------------------------------------------------------------------
+
+%% Function updating a named field in a record. Returns a new record. Note that
+%% this function must be maintained due the fact that field names are removed
+%% at compile time.
+update_ld_record(LD,nodes,Value) when record(LD,ld) ->
+ case mk_nodes(Value) of
+ {ok,NodesD} ->
+ LD#ld{nodes=NodesD};
+ error ->
+ LD
+ end;
+update_ld_record(LD,Tag,Value) when record(LD,ld) ->
+ Index=
+ case Tag of
+ c_node -> % atom()
+ #ld.c_node;
+ regexp_node -> % atom()
+ #ld.regexp_node;
+ tc_def_file -> % string()
+ #ld.tc_def_file;
+ initial_tcs -> % [{TCname,VarList},...]
+ #ld.initial_tcs;
+ history_dir -> % string()
+ #ld.history_dir;
+ debug -> % true | false
+ #ld.debug;
+ dir -> % string()
+ #ld.dir;
+ optg -> % {Mod,Func,Args}
+ #ld.optg;
+ tdg -> % {Mod,Func,Args}
+ #ld.tdg;
+ keep_nodes -> % [Nodes,...]
+ #ld.keep_nodes
+ end,
+ setelement(Index,LD,Value). % Cheeting!
+%% -----------------------------------------------------------------------------
+
+
+%% Help function which, if it exists, consults the trace definition file. The
+%% idea behind the trace definition file is to point out which trace cases there
+%% are, where to find them and how to turn them on and off.
+%% Trace case definitions are:
+%% {TCname,Type,VariableNameList,ActivatioFileName} |
+%% {TCname,Type,VariableNameList,ActivationFileName,DeactivationFileName}
+%% TCname=atom()
+%% Type=on | on_off
+%% VariableNameList=[atom(),...]
+%% ActivationFileName=DeactivationFileName=string()
+read_trace_case_definitions(LD) ->
+ case LD#ld.tc_def_file of
+ TCfileName when list(TCfileName) ->
+ case catch file:consult(TCfileName) of
+ {ok,Terms} ->
+ Dir=LD#ld.dir, % The working directory of the tool.
+ TCdict=read_trace_case_definitions_2(Terms,Dir,mk_tc_dict()),
+ LD#ld{tc_dict=TCdict};
+ _ ->
+ LD
+ end;
+ _ ->
+ LD
+ end.
+
+read_trace_case_definitions_2([{TCname,on,VarNames,FName}|Rest],Dir,TCdict) ->
+ FileName=make_absolute_path(FName,Dir),
+ read_trace_case_definitions_2(Rest,
+ Dir,
+ insert_tracecase_tc_dict(TCname,
+ on,
+ VarNames,
+ FileName,
+ TCdict));
+read_trace_case_definitions_2([{TCname,on_off,VarNames,FNameOn,FNameOff}|Rest],Dir,TCdict) ->
+ FileNameOn=make_absolute_path(FNameOn,Dir),
+ FileNameOff=make_absolute_path(FNameOff,Dir),
+ read_trace_case_definitions_2(Rest,
+ Dir,
+ insert_tracecase_tc_dict(TCname,
+ on_off,
+ VarNames,
+ FileNameOn,
+ FileNameOff,
+ TCdict));
+read_trace_case_definitions_2([_|Rest],Dir,TCdict) ->
+ read_trace_case_definitions_2(Rest,Dir,TCdict);
+read_trace_case_definitions_2([],_Dir,TCdict) ->
+ TCdict.
+
+%% Help function returning an absolute path to FName if FName is not already
+%% absolute. Dir is the working dir of the tool and supposed to be absolute.
+make_absolute_path(FName,Dir) ->
+ case filename:pathtype(FName) of
+ absolute -> % Then do nothing, allready absolute.
+ FName;
+ _ ->
+ filename:join(Dir,FName)
+ end.
+%% -----------------------------------------------------------------------------
+
+get_status(undefined,_Node) ->
+ inviso:get_status();
+get_status(CNode,Nodes) ->
+ inviso_tool_lib:inviso_cmd(CNode,get_status,[Nodes]).
+%% -----------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% Internal data structure functions.
+%% =============================================================================
+
+%% -----------------------------------------------------------------------------
+%% The nodes database structure.
+%% -----------------------------------------------------------------------------
+
+%% The purpose of the nodes database structure is to keep track of what runtime
+%% nodes we have, and their current status.
+%% Implementation:
+%% [{NodeName,AvailableStatus},...] or AvailableStatus in the
+%% non-distributed case.
+%% AvailableStatus={up,Status1} | down
+%% Status1={State,Status} | reactivating
+%% State=tracing | inactive | trace_failure
+%% Status=running | suspended
+%% reactivating=the node is now being brought up to date.
+%% inactive=not tracing, can be initiated and then reactivated.
+%% The following states can occure.
+%% {inactive,running}
+%% Mainly when we start the tool, before a session has been started.
+%% {tracing,running}
+%% When a trace session is on-going.
+%% {trace_failure,running}
+%% If init_tracing failed for some reason.
+%% {tracing,suspended}
+%% reactivating
+%% The node is tracing (has always been) but was suspended. It is now
+%% no longer suspended and the tool is redong commands.
+%% {inactive,suspended}
+%% We can end up here if a session is stopped with this node suspended.
+
+%% Returns a nodes database structure filled with the nodes Nodes.
+mk_nodes(Nodes) when list(Nodes) ->
+ {ok,lists:map(fun(N) when atom(N)->{N,down} end,Nodes)};
+mk_nodes(local_runtime) -> % The non-distributed case.
+ down;
+mk_nodes(_Nodes) ->
+ error.
+%% -----------------------------------------------------------------------------
+
+%% Updates the nodes database structure for each node that has been added.
+%% This is the case when we start the tool or reactivate a node. Note that a node
+%% may have become adopted instead of started.
+%% Returns a new nodes database structure.
+update_added_nodes(CNode,[{Node,NodeResult}|Rest],NodesD) ->
+ case update_added_nodes_3(NodeResult) of
+ already_added -> % Already added to the control component.
+ case get_status(CNode,[Node]) of % Examine if it is tracing or not.
+ {ok,[{Node,NodeResult2}]} ->
+ Result=mk_nodes_state_from_status(NodeResult2),
+ update_added_nodes_2(CNode,Node,Result,NodesD,Rest);
+ {error,_Reason} -> % Strange, mark it as down now.
+ update_added_nodes_2(CNode,Node,down,NodesD,Rest)
+ end;
+ Result ->
+ update_added_nodes_2(CNode,Node,Result,NodesD,Rest)
+ end;
+update_added_nodes(_CNode,[],NodesD) ->
+ NodesD;
+update_added_nodes(_CNode,NodeResult,_NodesD) -> % Non distributed case.
+ case update_added_nodes_3(NodeResult) of
+ already_added -> % Already added, most likely autostart.
+ mk_nodes_state_from_status(inviso:get_status());
+ Result ->
+ Result % Simply replace NodesD.
+ end.
+
+update_added_nodes_2(CNode,Node,Result,NodesD,Rest) ->
+ case lists:keysearch(Node,1,NodesD) of
+ {value,_} -> % Node already exists, replace!
+ update_added_nodes(CNode,Rest,lists:keyreplace(Node,1,NodesD,{Node,Result}));
+ false -> % Strange, unknown node!
+ update_added_nodes(CNode,Rest,NodesD)
+ end.
+
+update_added_nodes_3({ok,{adopted,tracing,running,_Tag}}) ->
+ {up,{tracing,running}};
+update_added_nodes_3({ok,{adopted,tracing,{suspended,_SReason},_Tag}}) ->
+ {up,{tracing,suspended}};
+update_added_nodes_3({ok,{adopted,_,running,_Tag}}) ->
+ {up,{inactive,running}};
+update_added_nodes_3({ok,{adopted,_,{suspended,_SReason},_Tag}}) ->
+ {up,{inactive,suspended}};
+update_added_nodes_3({ok,new}) ->
+ {up,{inactive,running}};
+update_added_nodes_3({ok,already_added}) ->
+ already_added; % This is an error value!
+update_added_nodes_3({error,_Reason}) ->
+ down.
+%% -----------------------------------------------------------------------------
+
+%% Function marking all nodes that, according to the returnvalue from init_tracing,
+%% now are successfully initiated as tracing and running. Note that nodes that
+%% does not fully respond 'ok' when init_tracing are marked as 'trace_failure'.
+%% Also note that we assume that the nodes must be running to have made it this far.
+%% A node can of course have become suspended in the process, but that node will
+%% be marked as suspended later when that inviso event message arrives to the tool.
+%% Returns {NewNodesD,Nodes} where Nodes are the nodes that actually got initiated
+%% as a result of the init_tracing call (judged from the LogResults).
+set_tracing_running_nodes(undefined,{ok,LogResults},_AvailableStatus) -> % Non-distr. case.
+ case set_tracing_running_nodes_checkresult(LogResults) of
+ ok ->
+ {{up,{tracing,running}},local_runtime};
+ error ->
+ {down,[]}
+ end;
+set_tracing_running_nodes(undefined,{error,already_initiated},_) -> % Non-distributed case.
+ {mk_nodes_state_from_status(inviso:get_status()),[]}; % Ask it for its status.
+set_tracing_running_nodes(undefined,{error,_Reason},_) -> % Non-distributed case.
+ {down,[]}; % This is questionable!
+set_tracing_running_nodes(CNode,{ok,NodeResults},NodesD) ->
+ set_tracing_running_nodes_2(CNode,NodeResults,NodesD,[]).
+
+set_tracing_running_nodes_2(CNode,[{Node,{ok,LogResults}}|Rest],NodesD,Nodes) ->
+ case set_tracing_running_nodes_checkresult(LogResults) of
+ ok -> % The result is good.
+ case lists:keysearch(Node,1,NodesD) of
+ {value,_} ->
+ NewNodesD=lists:keyreplace(Node,1,NodesD,{Node,{up,{tracing,running}}}),
+ set_tracing_running_nodes_2(CNode,Rest,NewNodesD,[Node|Nodes]);
+ false -> % Strange.
+ set_tracing_running_nodes_2(CNode,Rest,NodesD,Nodes)
+ end;
+ error -> % This node is not tracing correctly.
+ NewNodesD=lists:keyreplace(Node,1,NodesD,{Node,down}),
+ set_tracing_running_nodes_2(CNode,Rest,NewNodesD,Nodes)
+ end;
+set_tracing_running_nodes_2(CNode,[{Node,{error,already_initiated}}|Rest],NodesD,Nodes) ->
+ case get_status(CNode,[Node]) of % Then we must ask what it is doing now.
+ {ok,[{Node,NodeResult}]} ->
+ Result=mk_nodes_state_from_status(NodeResult),
+ NewNodesD=lists:keyreplace(Node,1,NodesD,{Node,Result}),
+ set_tracing_running_nodes_2(CNode,Rest,NewNodesD,Nodes);
+ {error,_Reason} -> % Strange, mark it as down.
+ NewNodesD=lists:keyreplace(Node,1,NodesD,{Node,down}),
+ set_tracing_running_nodes_2(CNode,Rest,NewNodesD,Nodes)
+ end;
+set_tracing_running_nodes_2(CNode,[{Node,{error,_Reason}}|Rest],NodesD,Nodes) ->
+ NewNodesD=lists:keyreplace(Node,1,NodesD,{Node,{up,{trace_failure,running}}}),
+ set_tracing_running_nodes_2(CNode,Rest,NewNodesD,Nodes);
+set_tracing_running_nodes_2(_CNode,[],NodesD,Nodes) ->
+ {NodesD,Nodes}. % New NodesD and nodes successfully initiated.
+
+%% Help function checking if a returnvalue from inviso:init_tracing really
+%% means that tracing has started as requested.
+set_tracing_running_nodes_checkresult(_LogResults) -> ok. % Should really be better!
+%% -----------------------------------------------------------------------------
+
+%% Function updating Node in the NodesD structure and sets it to 'down'.
+%% Returns a new nodes structure.
+set_down_nodes(Node,[{Node,_}|Rest]) ->
+ [{Node,down}|Rest];
+set_down_nodes(Node,[NodeStruct|Rest]) ->
+ [NodeStruct|set_down_nodes(Node,Rest)];
+set_down_nodes(_,[]) ->
+ [];
+set_down_nodes(_,_) -> % Non-distributed case.
+ down. % One can argue if this can happend.
+%% -----------------------------------------------------------------------------
+
+%% Function updating Node in NodesD to now be suspended. Note that if the node is
+%% reactivating it must be moved to state tracing because that is what is doing.
+set_suspended_nodes(Node,[{Node,{up,reactivating}}|Rest]) ->
+ [{Node,{up,{tracing,suspended}}}|Rest];
+set_suspended_nodes(Node,[{Node,{up,{State,_}}}|Rest]) ->
+ [{Node,{up,{State,suspended}}}|Rest];
+set_suspended_nodes(Node,[NodesData|Rest]) ->
+ [NodesData|set_suspended_nodes(Node,Rest)];
+set_suspended_nodes(_Node,[]) -> % Hmm, strange why did we end up here?
+ [];
+set_suspended_nodes(_,{up,reactivating}) -> % Non-distributed case.
+ {up,{tracing,suspended}};
+set_suspended_nodes(_,{up,{State,_}}) ->
+ {up,{State,suspended}}.
+%% -----------------------------------------------------------------------------
+
+%% This function is called when reactivation is completed. Hence it moves the
+%% node to no longer suspended. Note this can mean that the node is either
+%% tracing or inactive. Reactivation is not allowed for a node have trace_failure.
+set_running_nodes(Node,NodesD) when list(NodesD) ->
+ case lists:keysearch(Node,1,NodesD) of
+ {value,{_,AvailableStatus}} ->
+ lists:keyreplace(Node,1,NodesD,{Node,set_running_nodes_2(AvailableStatus)});
+ false -> % Very strange!
+ NodesD
+ end;
+set_running_nodes(_,NodesD) -> % The non-distributed case.
+ set_running_nodes_2(NodesD).
+
+set_running_nodes_2({up,reactivating}) ->
+ {up,{tracing,running}};
+set_running_nodes_2({up,{State,suspended}}) ->
+ {up,{State,running}}.
+%% -----------------------------------------------------------------------------
+
+%% Function marking node as now reactivating. That means it is not suspended
+%% any longer (and tracing), but still not part of the set of nodes which shall
+%% get all commands. Returns a new NodesD.
+set_reactivating_nodes(Node,[{Node,_}|Rest]) ->
+ [{Node,{up,reactivating}}|Rest];
+set_reactivating_nodes(Node,[NodesData|Rest]) ->
+ [NodesData|set_reactivating_nodes(Node,Rest)];
+set_reactivating_nodes(_,[]) ->
+ [];
+set_reactivating_nodes(_,{up,_}) -> % The non-distributed case.
+ {up,reactivating}.
+%% -----------------------------------------------------------------------------
+
+%% Function called when stop-tracing is done. That is all nodes in Nodes shall
+%% be inactive now. Note that an inactive node can still be suspended.
+%% Returns a new NodesD.
+set_inactive_nodes(_,{up,reactivating}) -> % Non-distributed case.
+ {up,{inactive,running}};
+set_inactive_nodes(_,{up,{_,Status}}) -> % Tracing or trace_failure.
+ {up,{inactive,Status}};
+set_inactive_nodes(_,down) ->
+ down;
+set_inactive_nodes([{Node,ok}|Rest],NodesD) ->
+ case lists:keysearch(Node,1,NodesD) of
+ {value,{_,{up,reactivating}}} ->
+ set_inactive_nodes(Rest,lists:keyreplace(Node,1,NodesD,{Node,{up,{inactive,running}}}));
+ {value,{_,{up,{_,Status}}}} -> % Tracing or trace_failure.
+ set_inactive_nodes(Rest,lists:keyreplace(Node,1,NodesD,{Node,{up,{inactive,Status}}}));
+ _ -> % This should not happend.
+ set_inactive_nodes(Rest,NodesD)
+ end;
+set_inactive_nodes([{_Node,_Error}|Rest],NodesD) ->
+ set_inactive_nodes(Rest,NodesD);
+set_inactive_nodes([],NodesD) ->
+ NodesD.
+%% -----------------------------------------------------------------------------
+
+%% Returns a list of all node names. Note that it can only be used in the
+%% distributed case.
+get_all_nodenames_nodes(NodesD) ->
+ lists:map(fun({Node,_})->Node end,NodesD).
+%% -----------------------------------------------------------------------------
+
+%% Returns a list of all nodes that are up, tracing and running (not suspended),
+%% or 'void' in the non-distributed case. This is the list of nodes that shall get
+%% inviso commands.
+get_nodenames_running_nodes([{Node,{up,{tracing,running}}}|Rest]) ->
+ [Node|get_nodenames_running_nodes(Rest)];
+get_nodenames_running_nodes([{_Node,_}|Rest]) ->
+ get_nodenames_running_nodes(Rest);
+get_nodenames_running_nodes([]) ->
+ [];
+get_nodenames_running_nodes(_) ->
+ void. % When non distributed, N/A.
+%% -----------------------------------------------------------------------------
+
+%% Returns a list of nodes that can be made to initiate tracing.
+get_inactive_running_nodes({up,{inactive,running}}) ->
+ local_runtime;
+get_inactive_running_nodes(NonDistributed) when not(is_list(NonDistributed)) ->
+ [];
+get_inactive_running_nodes([{Node,{up,{inactive,running}}}|Rest]) ->
+ [Node|get_inactive_running_nodes(Rest)];
+get_inactive_running_nodes([{_Node,_}|Rest]) ->
+ get_inactive_running_nodes(Rest);
+get_inactive_running_nodes([]) ->
+ [].
+%% -----------------------------------------------------------------------------
+
+%% Returns a list of nodes that are currently tracing (not necessarily running).
+%% In the non-distributed case the status of the runtime component will be
+%% returned.
+%% Note that nodes showing trace_failure will be included since we like to stop
+%% tracing at those nodes too.
+get_tracing_nodes([{Node,{up,{tracing,_}}}|Rest]) ->
+ [Node|get_tracing_nodes(Rest)];
+get_tracing_nodes([{Node,{up,{trace_failure,_}}}|Rest]) ->
+ [Node|get_tracing_nodes(Rest)];
+get_tracing_nodes([{Node,{up,reactivating}}|Rest]) ->
+ [Node|get_tracing_nodes(Rest)];
+get_tracing_nodes([_|Rest]) ->
+ get_tracing_nodes(Rest);
+get_tracing_nodes([]) ->
+ [];
+get_tracing_nodes(AvailableStatus) ->
+ AvailableStatus.
+%% -----------------------------------------------------------------------------
+
+%% Returns a list of all nodes that are currently up.
+get_available_nodes(down) ->
+ undefined;
+get_available_nodes([{_Node,down}|Rest]) ->
+ get_available_nodes(Rest);
+get_available_nodes([{Node,_}|Rest]) ->
+ [Node|get_available_nodes(Rest)];
+get_available_nodes([]) ->
+ [].
+%% -----------------------------------------------------------------------------
+
+%% Function returning the "state" of Node. Mainly used to check if the node is
+%% suspended or not.
+%% Returns {State,Status} | reactivating | down
+%% where
+get_state_nodes(Node,NodesD) when list(NodesD) ->
+ case lists:keysearch(Node,1,NodesD) of
+ {value,{_,AvailableStatus}} ->
+ get_state_nodes_2(AvailableStatus);
+ false ->
+ false
+ end;
+get_state_nodes(_,NodesD) -> % Non distributed case.
+ get_state_nodes_2(NodesD).
+
+get_state_nodes_2({up,{trace_failure,Status}}) ->
+ {trace_failure,Status};
+get_state_nodes_2({up,{State,suspended}}) -> % {tracing|inactive,suspended}
+ {State,suspended};
+get_state_nodes_2({up,reactivating}) ->
+ reactivating;
+get_state_nodes_2({up,{State,running}}) ->
+ {State,running};
+get_state_nodes_2(down) ->
+ down.
+%% -----------------------------------------------------------------------------
+
+%% Help function in the case we need to consult the state/status of a runtime
+%% component. Returns a nodesD value that can be added to the nodes database.
+mk_nodes_state_from_status({ok,{tracing,running}}) ->
+ {up,{tracing,running}};
+mk_nodes_state_from_status({ok,{tracing,{suspended,_SReason}}}) ->
+ {up,{tracing,suspended}};
+mk_nodes_state_from_status({ok,{_,running}}) ->
+ {up,{inactive,running}};
+mk_nodes_state_from_status({ok,{_,{suspended,_SReason}}}) ->
+ {up,{inactive,suspended}};
+mk_nodes_state_from_status({error,_Reason}) ->
+ down.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% The session_state.
+%% -----------------------------------------------------------------------------
+
+%% The session state reflects if the inviso_tool is tracing or not.
+%% This means that if the tool is tracing a reconnected node can be made to
+%% restart_session.
+
+%% Returns the correct value indicating that we are tracing now.
+tracing_sessionstate() ->
+ tracing.
+%% -----------------------------------------------------------------------------
+
+%% Returns true or false depending on if we are tracing now or not.
+is_tracing(tracing) ->
+ true;
+is_tracing(_) ->
+ false.
+%% -----------------------------------------------------------------------------
+
+%% Returns the correct value indicating that the tool is not tracing.
+passive_sessionstate() ->
+ idle.
+%% -----------------------------------------------------------------------------
+
+%% -----------------------------------------------------------------------------
+%% The tracer_data datastructure.
+%% -----------------------------------------------------------------------------
+
+%% The tracer_data structure collects the tracer data arguments used to init tracing
+%% by this inviso tool. The args are saved per session. Each session has
+%% a number.
+%% Implementation:
+%% Sessions=[{SessionNr,TDGargs},...]
+%% SessionNr=integer()
+%% TDGargs=list(), args given to the tracer data generator
+%% minus the first argument which is the Node name.
+
+%% Function taking tracerdata args structure inserting yet another session.
+%% Returns {SessionNr,NewTDs}.
+insert_td_tracer_data(TDGargs,TDs=[{SNr,_}|_]) ->
+ {SNr+1,[{SNr+1,TDGargs}|TDs]};
+insert_td_tracer_data(TDGargs,undefined) ->
+ {1,[{1,TDGargs}]}.
+%% -----------------------------------------------------------------------------
+
+%% Returns the latest session nr.
+get_latest_session_nr_tracer_data(undefined) ->
+ undefined;
+get_latest_session_nr_tracer_data([{SessionNr,_}|_]) ->
+ SessionNr.
+%% -----------------------------------------------------------------------------
+
+%% Returns the tracer data arguments used when creating the trace data for the
+%% latest session.
+get_latest_tdgargs_tracer_data(undefined) ->
+ undefined;
+get_latest_tdgargs_tracer_data([{_,TDGargs}|_]) ->
+ TDGargs.
+%% -----------------------------------------------------------------------------
+
+
+%% -----------------------------------------------------------------------------
+%% The tc_dict or trace case dictionary datastructure.
+%% -----------------------------------------------------------------------------
+
+%% The tc_dict stores information about all available trace cases.
+%% Implementation:
+%% [{TCname,Type,VarNames,FNameOn [,FNameOff]},...]
+%% TCname=atom()
+%% Type=on | on_off
+%% VarNames=[atom(),...]
+%% FNameOn=FNameOff=string()
+
+%% Returns the empty trace case dictionary.
+mk_tc_dict() ->
+ [].
+%% -----------------------------------------------------------------------------
+
+%% Function inserting a new trace case into the trace case dictionary.
+insert_tracecase_tc_dict(TCname,on,VarNames,FNameOn,TCdict) ->
+ [{TCname,on,VarNames,FNameOn}|TCdict].
+insert_tracecase_tc_dict(TCname,on_off,VarNames,FNameOn,FNameOff,TCdict) ->
+ [{TCname,on_off,VarNames,FNameOn,FNameOff}|TCdict].
+%% -----------------------------------------------------------------------------
+
+%% Function finding a trace case definition in the tc_dict structure.
+%% Returns {ok,{TCname,Type,VarNAmes,FNameOn [,FNameOff]}} or 'false'.
+get_tracecase_tc_dict(TCname,[Tuple|_]) when element(1,Tuple)==TCname ->
+ {ok,Tuple};
+get_tracecase_tc_dict(TCname,[_|Rest]) ->
+ get_tracecase_tc_dict(TCname,Rest);
+get_tracecase_tc_dict(_,[]) ->
+ false;
+get_tracecase_tc_dict(_,_) -> % There are no trace cases!
+ false.
+%% -----------------------------------------------------------------------------
+
+%% Function working on the trace case definition returned by get_tracecase_tc_dict/2
+%% function.
+%% Returning {ok,ActivationFileName}.
+get_tc_activate_fname({_TCname,_Type,_VarNames,FNameOn}) ->
+ {ok,FNameOn};
+get_tc_activate_fname({_TCname,_Type,_VarNames,FNameOn,_FNameOff}) ->
+ {ok,FNameOn}.
+
+get_tc_deactivate_fname({_TCname,_Type,_VarNames,_FNameOn,FNameOff}) ->
+ {ok,FNameOff};
+get_tc_deactivate_fname(_) -> % Not a case with off function.
+ false.
+
+get_tc_varnames({_TCname,_Type,VarNames,_FNameOn}) ->
+ VarNames;
+get_tc_varnames({_TCname,_Type,VarNames,_FNameOn,_FNameOff}) ->
+ VarNames.
+
+%% -----------------------------------------------------------------------------
+
+
+%% The Command History Log (CHL) stores commands to make it possible to
+%% reactivate suspended nodes, reconnect restarted nodes, and to make
+%% autostart files.
+%% Each time tracing is initiated (that is started) the CHL is cleared since
+%% it would not make scense to repeat commands from an earlier tracing at
+%% reactivation for instance.
+
+%% Implementation: {NextCounter,OnGoingList,ETStable}
+%% NextCounter=integer(), next command number - to be able to sort them in order.
+%% OnGoingList=[{ProcH,{TCname,ID}},...]
+%% ID=term(), instance id for this execution of this trace case.
+%% ETStable=tid() -> {{TCname,Id},Counter,State1,Bindings}
+%% ETStable=tid() -> {{TCname,Id},Counter,running,Bindings,Result} |
+%% {{TCname,Id,#Ref},Counter,stop,Bindings} |
+%% {{TCname,#Ref},Counter,Bindings} % An rtc
+%% {{M,F,Args,#Ref},Counter}
+%% Counter=integer(), the order-counter for this logged entry.
+%% State1=activating | stopping
+%% Where:
+%% activating: the activation file for the tracecase is running.
+%% running : activation is completed.
+%% stopping : set on the previously running ETS entry when deactivation
+%% file is currently executing.
+%% stop : entered with own Counter into the ETS table when
+%% deactivation file is executing. Remains after too.
+%% Result=term(), the result returned from the tr-case or inviso call.
+
+
+%% Returning an initial empty CHL.
+mk_chl(undefined) ->
+ {1,[],ets:new(inviso_tool_chl,[set,protected])};
+mk_chl({_,_,TId}) ->
+ ets:delete(TId),
+ mk_chl(undefined).
+
+%% Help function returning 'true' if there is a current history.
+history_exists_chl(undefined) ->
+ false;
+history_exists_chl({_,_,_}) ->
+ true.
+
+%% Function looking up the state of this trace case.
+find_id_chl(TCname,Id,{_NextCounter,_OnGoingList,TId}) ->
+ case ets:lookup(TId,{TCname,Id}) of
+ [{_,_,running,Bindings,_Result}] -> % The trace case is tracing.
+ {ok,Bindings};
+ [{_,_,State,_}] -> % activating or stopping.
+ State;
+ [] ->
+ false
+ end.
+
+%% Function finding the Trace case associated with a process handle
+%% doing this trace case's activation or stopping.
+find_tc_executer_chl(ProcH,{_,OnGoingList,TId}) ->
+ case lists:keysearch(ProcH,1,OnGoingList) of
+ {value,{_,{TCname,Id}}} ->
+ [{_,_,State,_}]=ets:lookup(TId,{TCname,Id}),
+ {State,{TCname,Id}}; % Should be activating or stopping.
+ false ->
+ false
+ end.
+
+%% Adds a Trace case to the CHL. This is done when it is turned on. Or when it
+%% is called for trace cases that do not have on/off functionality.
+set_activating_chl(TCname,Id,{Counter,OnGoingList,TId},Bindings,ProcH) ->
+ ets:insert(TId,{{TCname,Id},Counter,activating,Bindings}),
+ {Counter+1,[{ProcH,{TCname,Id}}|OnGoingList],TId}.
+
+%% Function marking a trace case as now running. That is the activation
+%% phase is completed. It is normaly completed when the process executing
+%% the trace case signals that it is done.
+set_running_chl(ProcH,TCname,Id,Result,{NextCounter,OnGoingList,TId}) ->
+ [{_,Counter,_,Bindings}]=ets:lookup(TId,{TCname,Id}),
+ ets:insert(TId,{{TCname,Id},Counter,running,Bindings,Result}),
+ NewOnGoingList=lists:keydelete(ProcH,1,OnGoingList),
+ {NextCounter,NewOnGoingList,TId}.
+
+%% Function marking trace case TCname with identifier Id as now in its stopping
+%% state. Where ProcH is the handler to the process running the stopping
+%% trace case.
+set_stopping_chl(TCname,Id,{NextCounter,OnGoingList,TId},ProcH)->
+ [{_,Counter,_,Bindings,_}]=ets:lookup(TId,{TCname,Id}),
+ ets:insert(TId,{{TCname,Id},Counter,stopping,Bindings}),
+ ets:insert(TId,{{TCname,Id,make_ref()},NextCounter,stop,Bindings}),
+ {NextCounter+1,[{ProcH,{TCname,Id}}|OnGoingList],TId}.
+
+%% Function removing a TCname-Id from the CHL. This is mostly used
+%% if activating the trace case failed for some reason. We do not then
+%% expect the user to stop the trace case. Hence it must be removed now.
+%% A reactivation process may have noticed the activating-entry and started
+%% to activate it. But since the general state reached after an unsuccessful
+%% activation can not easily be determined, we don't try to do much about it.
+del_tc_chl(ProcH,TCname,Id,{NextCounter,OnGoingList,TId}) ->
+ ets:delete(TId,{TCname,Id}),
+ NewOnGoingList=lists:keydelete(ProcH,1,OnGoingList),
+ {NextCounter,NewOnGoingList,TId}.
+
+%% Function removing the entry TCname+Id from the CHL. This makes it
+%% possible to activate a tracecase with this id again. The entry was
+%% previously marked as stopping.
+nullify_chl(ProcH,TCname,Id,{NextCounter,OnGoingList,TId}) ->
+ ets:delete(TId,{TCname,Id}),
+ NewOnGoingList=lists:keydelete(ProcH,1,OnGoingList),
+ {NextCounter+1,NewOnGoingList,TId}.
+
+%% Function stopping all processes saved as being now running tc executers.
+%% This is useful as cleanup during stop tracing for instance.
+%% Returns a new CHL which is not in all parts correct. Entries in the
+%% ETS table are for instance not properly state-changed. But the CHL will
+%% from now on only be used to create command files and similar.
+stop_all_tc_executer_chl({NextCounter,[{ProcH,_}|Rest],TId}) ->
+ exit(ProcH,kill),
+ stop_all_tc_executer_chl({NextCounter,Rest,TId});
+stop_all_tc_executer_chl({NextCounter,[],TId}) ->
+ {NextCounter,[],TId}.
+
+%% Function adding a "plain" inviso call to the CHL.
+add_inviso_call_chl(Cmd,Args,{NextCounter,OnGoingList,TId}) ->
+ ets:insert(TId,{{inviso,Cmd,Args,make_ref()},NextCounter}),
+ {NextCounter+1,OnGoingList,TId}.
+
+%% Function adding a run trace case entry to the chl.
+add_rtc_chl(TCname,Bindings,{NextCounter,OnGoingList,TId}) ->
+ ets:insert(TId,{{TCname,make_ref()},NextCounter,Bindings}),
+ {NextCounter+1,OnGoingList,TId}.
+%% Returns the highest used counter number in the command history log.
+get_highest_used_counter_chl({NextCounter,_,_}) ->
+ NextCounter-1.
+
+%% Help function returning a list of {{TCname,Id},Phase} for all ongoing
+%% assynchronous tracecases.
+get_ongoing_chl(undefined) ->
+ [];
+get_ongoing_chl({_,OngoingList,TId}) ->
+ get_ongoing_chl_2(OngoingList,TId).
+
+get_ongoing_chl_2([{_ProcH,{TCname,Id}}|Rest],TId) ->
+ case ets:lookup(TId,{TCname,Id}) of
+ [{_,_C,activating,_B}] ->
+ [{{TCname,Id},activating}|get_ongoing_chl_2(Rest,TId)];
+ [{_,_C,stopping,_B}] ->
+ [{{TCname,Id},deactivating}|get_ongoing_chl_2(Rest,TId)]
+ end;
+get_ongoing_chl_2([],_) ->
+ [].
+
+%% Function returning a list of log entries. Note that the list is unsorted
+%% in respect to Counter.
+get_loglist_chl({_,_,TId}) ->
+ L=ets:tab2list(TId),
+ lists:map(fun({{TC,Id},C,S,B,_Result}) -> {{TC,Id},C,S,B}; % running
+ (Tuple={{_TC,_Id},_C,_S,_B}) -> Tuple; % activating | stopping
+ (Tuple={{_TC,_Id,_Ref},_C,_S,_B}) -> Tuple; % stop
+ (Tuple={{_M,_F,_Args,_Ref},_C}) -> Tuple;
+ (Tuple={{_TC,_Ref},_C,_B}) -> Tuple
+ end,
+ L);
+get_loglist_chl(_) -> % The history is not initiated, ever!
+ [].
+
+%% Function returning a list of log entries, but only those which are not
+%% cancelled out by deactivations.
+% get_loglist_active_chl({_,_,TId}) ->
+% L=ets:tab2list(TId),
+% lists:zf(fun({{TC,Id},C,S,B,_Result}) -> {true,{{TC,Id},C,S,B}}; % running
+% (Tuple={{_TC,_Id},_C,_S,_B}) -> Tuple; % activating | stopping
+% (Tuple={{_TC,_Id,_Ref},_C,_S,_B}) -> Tuple; % stop
+% (Tuple={{_M,_F,_Args,_Ref},_C}) -> Tuple
+% end,
+% L);
+% get_loglist_chl(_) -> % The history is not initiated, ever!
+% [].
+
+
+%% This helpfunction recreates a history from a saved history list. This function
+%% is supposed to crash if the log is not well formatted. Note that we must restore
+%% the counter in order for the counter to work if new commands are added to the
+%% history.
+replace_history_chl(OldCHL,SortedLog) ->
+ {_,Ongoing,TId}=mk_chl(OldCHL),
+ {NewTId,Counter}=replace_history_chl_2(TId,SortedLog,0),
+ {ok,{Counter+1,Ongoing,NewTId}}.
+
+replace_history_chl_2(TId,[{{TC,Id},C,running,B}|Rest],_Counter) ->
+ ets:insert(TId,{{TC,Id},C,running,B,undefined}),
+ replace_history_chl_2(TId,Rest,C);
+replace_history_chl_2(TId,[{{M,F,Args},C}|Rest],_Counter) ->
+ ets:insert(TId,{{M,F,Args,make_ref()},C}),
+ replace_history_chl_2(TId,Rest,C);
+replace_history_chl_2(TId,[{TC,C,B}|Rest],_Counter) ->
+ ets:insert(TId,{{TC,make_ref()},C,B}),
+ replace_history_chl_2(TId,Rest,C);
+replace_history_chl_2(TId,[],Counter) ->
+ {TId,Counter}.
+%% -----------------------------------------------------------------------------
+
+
+%% -----------------------------------------------------------------------------
+%% Reactivators data structure.
+%% -----------------------------------------------------------------------------
+
+%% Function adding a new node-reactivatorpid pair to the reactivators structure.
+%% In this way we know which reactivators to remove if Node terminates, or when
+%% a node is fully updated when a reactivator is done.
+add_reactivators(Node,Pid,Reactivators) ->
+ [{Node,Pid}|Reactivators].
+
+%% Function removing a reactivator entry from the reactivators structure.
+del_reactivators(RPid,[{_Node,RPid}|Rest]) ->
+ Rest;
+del_reactivators(RPid,[Element|Rest]) ->
+ [Element|del_reactivators(RPid,Rest)];
+del_reactivators(_,[]) -> % This should not happend.
+ [].
+
+get_node_reactivators(RPid,Reactivators) ->
+ case lists:keysearch(RPid,2,Reactivators) of
+ {value,{Node,_}} ->
+ Node;
+ false -> % This should not happend.
+ false
+ end.
+
+%% Returns a list of list all nodes that are currently reactivating.
+get_all_nodes_reactivators([{Nodes,_Pid}|Rest]) ->
+ [Nodes|get_all_nodes_reactivators(Rest)];
+get_all_nodes_reactivators([]) ->
+ [].
+
+%% Function stopping all running reactivator processes. Returns a new empty
+%% reactivators structure. Note that this function does not set the state of
+%% Nodes. It must most often be set to running.
+stop_all_reactivators([{_Nodes,Pid}|Rest]) ->
+ exit(Pid,kill),
+ stop_all_reactivators(Rest);
+stop_all_reactivators([]) ->
+ []. % Returns an empty reactivators.
+
+%% Help function stopping the reactivator (if any) that reactivates Node.
+%% Returns a new list of reactivators structure.
+stop_node_reactivators(Node,[{Node,Pid}|Rest]) ->
+ exit(Pid,kill),
+ Rest;
+stop_node_reactivators(Node,[NodePid|Rest]) ->
+ [NodePid|stop_node_reactivators(Node,Rest)];
+stop_node_reactivators(_,[]) ->
+ [].
+%% -----------------------------------------------------------------------------
+
+
+%% -----------------------------------------------------------------------------
+%% Started initial trace cases data structure.
+%% -----------------------------------------------------------------------------
+
+%% This datastructure keeps information about ongoing trace cases started
+%% automatically at init_tracing. These must be automatically stopped when calling
+%% stop_tracing.
+
+add_initial_tcs(TCname,Id,StartedInitialTcs) ->
+ [{TCname,Id}|StartedInitialTcs].
+%% -----------------------------------------------------------------------------
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/inviso/src/inviso_tool_lib.erl b/lib/inviso/src/inviso_tool_lib.erl new file mode 100644 index 0000000000..20a8b509ae --- /dev/null +++ b/lib/inviso/src/inviso_tool_lib.erl @@ -0,0 +1,379 @@ +% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+%% Description:
+%% Support module to the inviso tool.
+%%
+%% Authors:
+%% Lennart �hman, [email protected]
+%% -----------------------------------------------------------------------------
+
+-module(inviso_tool_lib).
+
+%% -----------------------------------------------------------------------------
+%% Exported library APIs
+%% -----------------------------------------------------------------------------
+-export([inviso_cmd/3,expand_module_names/3,make_patterns/7,std_tdg/2]).
+-export([mk_tdg_args/2,mk_complete_tdg_args/2,get_datetime_from_tdg_args/1]).
+-export([debug/3]).
+
+%% -----------------------------------------------------------------------------
+%% Constants.
+%% -----------------------------------------------------------------------------
+-define(DBG_OFF,off). % No internal debug indicator.
+
+
+%% =============================================================================
+%% Functions for inviso_cmd
+%% =============================================================================
+
+%% Help function which executes a trace control call. The reason for having a special
+%% function is that we either want to do rpc if the trace control component is
+%% located on another Erlang node than this one. Or call trace_c directly if
+%% it actually is on this node.
+%% Returns whatever the inviso function returns. In case of badrpc it is wrapped
+%% in an error-tuple.
+inviso_cmd(NodeName,Func,Args) ->
+ case node() of
+ NodeName -> % Control component on this node.
+ apply(inviso,Func,Args);
+ _ -> % On other node, must do RPC.
+ case rpc:call(NodeName,inviso,Func,Args) of
+ {badrpc,Reason} ->
+ {error,{badrpc,Reason}};
+ Result ->
+ Result
+ end
+ end.
+%% -----------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% Functions for expand_module_names
+%% =============================================================================
+
+%% Help function which expands the module name depending on how it is expressed.
+%% Setting Nodes to 'void' makes it non-distributed, expanding only here.
+%% The following special cases are handled:
+%% '_' =All modules, no expansion into individual module names. Instead
+%% it is intended to use the '_' mechanism in the trace_pattern BIF.
+%% Can therefore not be combined with a directory regexp.
+%% "*" =Is translated into ".*".
+%% "abc"=Means ".*abc.*". Can only be used for module or directory names
+%% containing upper or lowercase, digits and a slash.
+%% Returns {multinode_expansion,NodeModules},
+%% {singlenode_expansion,Modules},
+%% 'module', 'wildcard' or {error,Reason},
+%% To limit the places where expansion is done, the option {expand_only_at,Node}
+%% can be provided in the Opts list.
+%% In the non-distributed case the singlenode_expansion will be returned.
+expand_module_names(_Nodes,Mod={_,'_'},_) ->
+ {error,{faulty_regexp_combination,Mod}};
+expand_module_names(Nodes,{DirStr,ModStr},Opts) when list(DirStr), list(ModStr) ->
+ case expand_module_names_special_regexp(DirStr) of
+ {ok,NewDirStr} ->
+ case expand_module_names_special_regexp(ModStr) of
+ {ok,NewModStr} ->
+ expand_module_names_2(Nodes,NewDirStr,NewModStr,Opts);
+ {error,_Reason} ->
+ {error,{faulty_regexp,ModStr}}
+ end;
+ {error,_Reason} ->
+ {error,{faulty_regexp,DirStr}}
+ end;
+expand_module_names(_,'_',_Opts) -> % If we want to trace all modules
+ wildcard; % we shall not expand it.
+expand_module_names(_Nodes,Mod,_Opts) when atom(Mod) ->
+ module; % If it is an atom, no expansion.
+expand_module_names(Nodes,"*",Opts) -> % Treat this as a reg.exp.
+ expand_module_names(Nodes,".*",Opts);
+expand_module_names(Nodes,ModStr,Opts) when list(ModStr) ->
+ case expand_module_names_special_regexp(ModStr) of
+ {ok,NewModStr} ->
+ expand_module_names_2(Nodes,NewModStr,Opts);
+ {error,_Reason} ->
+ {error,{faulty_regexp,ModStr}}
+ end.
+
+expand_module_names_2(void,ModStr,Opts) -> % Non-distributed case.
+ {singlenode_expansion,inviso_rt_lib:expand_regexp(ModStr,Opts)};
+expand_module_names_2(Nodes,ModStr,Opts) ->
+ case get_expand_regexp_at_opts(Opts) of
+ {ok,Node} -> % Expansion only at this node.
+ case inviso_rt_lib:expand_regexp([Node],ModStr,Opts) of
+ [{Node,Modules}] when list(Modules) ->
+ {singlenode_expansion,Modules};
+ [{Node,_}] -> % Most likely badrpc.
+ {error,{faulty_node,Node}}
+ end;
+ false -> % Expand on all nodes.
+ Result=inviso_rt_lib:expand_regexp(Nodes,ModStr,Opts),
+ {multinode_expansion,Result}
+ end.
+expand_module_names_2(void,DirStr,ModStr,Opts) -> % Non-distributed case.
+ {singlenode_expansion,inviso_rt_lib:expand_regexp(DirStr,ModStr,Opts)};
+expand_module_names_2(Nodes,DirStr,ModStr,Opts) ->
+ case get_expand_regexp_at_opts(Opts) of
+ {ok,Node} -> % Expansion only at this node.
+ case inviso_rt_lib:expand_regexp([Node],DirStr,ModStr,Opts) of
+ [{Node,Modules}] when list(Modules) ->
+ {singlenode_expansion,Modules};
+ [{Node,_}] -> % Most likely badrpc.
+ {error,{faulty_node,Node}}
+ end;
+ false -> % Expand on all nodes.
+ Result=inviso_rt_lib:expand_regexp(Nodes,DirStr,ModStr,Opts),
+ {multinode_expansion,Result}
+ end.
+
+%% Help function which converts a special regexp into a proper one. With
+%% special regexps we mean e.g:"abc" which is supposed to mean ".*abc.*".
+%% Always returns a regexp or {error,Reason}.
+expand_module_names_special_regexp(Str) ->
+ StrLen=length(Str),
+ case regexp:first_match(Str,"[0-9a-zA-Z_/]*") of
+ {match,1,StrLen} -> % Ok, it is the special case.
+ {ok,".*"++Str++".*"}; % Convert it to a proper regexp.
+ {match,_,_} ->
+ {ok,Str}; % Keep it and hope it is a regexp.
+ nomatch ->
+ {ok,Str}; % Keep it and hope it is a regexp.
+ {error,Reason} -> % Can't continue with this!
+ {error,Reason}
+ end.
+%% -----------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% Functions for make_pattern.
+%% =============================================================================
+
+-define(DEPTH,3). % Max recursive safety catch depth.
+
+%% Help function that creates trace-patterns for each module in the list.
+%% It can handle both lists of modules or lists of nodes and modules.
+%% It will also in the process apply safety catches, if such are not disabled,
+%% in order to prevent certain patterns to form.
+%% The safety catch function is supposed to return either 'ok' or {new,NewTracePattern}.
+%% Where the NewTracePattern is a list of zero or more {M,F,Arity,MS}. The
+%% NewTracePatter is then the replacement for the tried trace-pattern.
+%% Note that the new trace-pattern(s) are also tried against all safety catches.
+%% This can possibly result in even replacements of the replacements. There is
+%% a depth meter to prevent the safety catch mechanism from circularly expanding
+%% trace patterns for ever.
+%% Returns a list of [{Node,PatternList},...] or [Pattern,...].
+%% The latter is used when the modules have been expanded on a single node.
+make_patterns(Catches,Opts,Dbg,NodeModsOrMods,F,A,MS) ->
+ OwnArg=get_ownarg_opts(Opts),
+ case get_disable_safety_opts(Opts) of
+ true -> % Do not use the safety catches.
+ make_patterns_2(void,OwnArg,Dbg,NodeModsOrMods,F,A,MS);
+ false ->
+ make_patterns_2(Catches,OwnArg,Dbg,NodeModsOrMods,F,A,MS)
+ end.
+
+make_patterns_2(Catches,OwnArg,Dbg,[{Node,Mods}|Rest],F,A,MS) when list(Mods) ->
+ TPs=make_patterns_3(Catches,OwnArg,Dbg,Mods,F,A,MS,[]),
+ [{Node,join_patterns(TPs)}|make_patterns_2(Catches,OwnArg,Dbg,Rest,F,A,MS)];
+make_patterns_2(Catches,OwnArg,Dbg,[{_Node,_}|Rest],F,A,MS) -> % badrpc!?
+ make_patterns_2(Catches,OwnArg,Dbg,Rest,F,A,MS);
+make_patterns_2(Catches,OwnArg,Dbg,Modules,F,A,MS) when list(Modules) ->
+ TPs=make_patterns_3(Catches,OwnArg,Dbg,Modules,F,A,MS,[]),
+ join_patterns(TPs);
+make_patterns_2(_,_,_,[],_,_,_) ->
+ [].
+
+make_patterns_3(void,OwnArg,Dbg,[M|Rest],F,A,MS,Result) -> % S-catches not used!
+ make_patterns_3(void,OwnArg,Dbg,Rest,F,A,MS,[{M,F,A,MS}|Result]);
+make_patterns_3(Catches,OwnArg,Dbg,[M|Rest],F,A,MS,Result) ->
+ NewTPs=try_safety_catches(Catches,OwnArg,[{M,F,A,MS}],Dbg,[],?DEPTH),
+ make_patterns_3(Catches,OwnArg,Dbg,Rest,F,A,MS,[NewTPs|Result]);
+make_patterns_3(_,_,_,[],_,_,_,Result) ->
+ lists:flatten(Result).
+
+try_safety_catches(_Catches,_OwnArg,TPs,Dbg,_Accum,0) -> % Max depth here!
+ debug(max_catch_depth,Dbg,[TPs]),
+ TPs; % Just return them unchanged.
+try_safety_catches(Catches,OwnArg,[TP={M,F,A,MS}|Rest],Dbg,Accum,Depth) ->
+ case try_safety_catch(Catches,OwnArg,M,F,A,MS,Dbg) of
+ ok -> % This pattern is safe!
+ try_safety_catches(Catches,OwnArg,Rest,Dbg,[TP|Accum],?DEPTH);
+ {new,NewTPs} -> % Then we must try them too!
+ NewTPs2=try_safety_catches(Catches,OwnArg,NewTPs,Dbg,[],Depth-1),
+ try_safety_catches(Catches,OwnArg,Rest,Dbg,[NewTPs2|Accum],?DEPTH)
+ end;
+try_safety_catches(_,_,[],_,Accum,_) ->
+ Accum.
+
+try_safety_catch([{SafetyMod,SafetyFunc}|Rest],OwnArg,M,F,A,MS,Dbg) ->
+ case (catch apply(SafetyMod,SafetyFunc,[M,F,A,MS,OwnArg])) of
+ ok -> % This catch has no oppinion about it.
+ try_safety_catch(Rest,OwnArg,M,F,A,MS,Dbg); % Continue with the next.
+ {new,NewTPs} -> % Replace it with this or these new.
+ debug(safety_catch,Dbg,[new,{SafetyMod,SafetyFunc},M,F,A,MS,NewTPs]),
+ {new,NewTPs}; % and stop trying safety cathes.
+ {'EXIT',Reason} -> % Something wrong with the safety catch.
+ debug(safety_catch,Dbg,['EXIT',{SafetyMod,SafetyFunc},M,F,A,MS,Reason]),
+ try_safety_catch(Rest,OwnArg,M,F,A,MS,Dbg) % Skip it and go on.
+ end;
+try_safety_catch([],_,_,_,_,_,_) ->
+ ok. % Since it passed all, it is safe!
+%% -----------------------------------------------------------------------------
+
+%% Help function that joins patterns together. This is necessary since you can
+%% only set the pattern once for a module-function-arity. This function can not
+%% remove conflicting match-spec "commands". Match-specs will simply be concatenated.
+%% Returns a list of patterns where each mod-func-arity is unique.
+join_patterns(Patterns) ->
+ join_patterns_2(Patterns,[]).
+
+join_patterns_2([{M,F,Arity,MS}|Rest],Result) ->
+ case join_patterns_is_already_done(M,F,Arity,Result) of
+ false -> % No we have not collapsed this one.
+ case join_patterns_3(M,F,Arity,Rest) of
+ [] -> % No this combination is unique.
+ join_patterns_2(Rest,[{M,F,Arity,MS}|Result]);
+ MSs -> % We got a list of all other TPs.
+ join_patterns_2(Rest,[{M,F,Arity,MS++MSs}|Result])
+ end;
+ true -> % We already joined this M-F-Arity.
+ join_patterns_2(Rest,Result) % Simply skip it, already done.
+ end;
+join_patterns_2([],Result) ->
+ Result. % Reversed but does not matter!
+
+%% Help function checking if we have already built a trace-pattern for
+%% this M-F-Arity. If so, the found M-F-Arity is already handled.
+join_patterns_is_already_done(M,F,Arity,[{M,F,Arity,_}|_]) ->
+ true;
+join_patterns_is_already_done(M,F,Arity,[_|Rest]) ->
+ join_patterns_is_already_done(M,F,Arity,Rest);
+join_patterns_is_already_done(_,_,_,[]) ->
+ false.
+
+%% Help function which simply concatenates all match-specs for this
+%% M-F-Arity.
+join_patterns_3(M,F,Arity,[{M,F,Arity,MS}|Rest]) ->
+ [MS|join_patterns_3(M,F,Arity,Rest)];
+join_patterns_3(M,F,Arity,[_|Rest]) ->
+ join_patterns_3(M,F,Arity,Rest);
+join_patterns_3(_,_,_,[]) ->
+ [].
+%% -----------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% Function for tracer data creation.
+%% =============================================================================
+
+-define(I2L(Arg),integer_to_list(Arg)).
+
+%% The inviso_tool uses a tracer-data generator function to create the tracer_data
+%% specification for each node that shall participate in tracing controlled
+%% through the inviso-tool. If no own tracer data generator function is specified,
+%% this function is used.
+std_tdg(Node,{{Y,Mo,D},{H,Mi,S}}) ->
+ NameStr=atom_to_list(Node)++"_"++?I2L(Y)++"-"++?I2L(Mo)++"-"++?I2L(D)++"_"++
+ ?I2L(H)++"-"++?I2L(Mi)++"-"++?I2L(S),
+ LogTD={file,NameStr++".log"},
+ TiTD={file,NameStr++".ti"},
+ [{trace,LogTD},{ti,TiTD}].
+%% ------------------------------------------------------------------------------
+
+%% mk_tdg_args(DateTime,Args)=TDGargs
+%% DateTime={Date,Time}
+%% Date=tuple(),
+%% Time=tuple(),
+%% Args=list()
+%% TDGargs=list(),
+%% Creates the TDGargs list used when calling functions making the CompleteTDGargs.
+mk_tdg_args(DateTime,Args) ->
+ [DateTime|Args].
+%% ------------------------------------------------------------------------------
+
+%% mk_complete_tdg_args(Node,TDGargs)=CompleteTDGargs
+%% Returns the list of all arguments a tracer data generator function must accept.
+mk_complete_tdg_args(Node,TDGargs) ->
+ [Node|TDGargs].
+%% ------------------------------------------------------------------------------
+
+%% get_datetime_from_tdg_args(TDGargs)=DateTime
+%% Function returning the DateTime tuple in a TDGargs list.
+get_datetime_from_tdg_args([DateTime|_]) ->
+ DateTime.
+%% ------------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% Various help functions.
+%% =============================================================================
+
+%% -----------------------------------------------------------------------------
+%% Functions handling set trace-pattern options.
+%% -----------------------------------------------------------------------------
+
+%% Gets additional arguments given to various configurable functions.
+%% Returns a list.
+get_ownarg_opts(Opts) ->
+ case lists:keysearch(arg,1,Opts) of
+ {value,{_,OwnArg}} when list(OwnArg) ->
+ OwnArg;
+ {value,{_,OwnArg}} ->
+ [OwnArg];
+ false ->
+ []
+ end.
+%% -----------------------------------------------------------------------------
+
+get_disable_safety_opts(Opts) ->
+ case lists:member(disable_safety,Opts) of
+ true ->
+ true;
+ false ->
+ false
+ end.
+%% -----------------------------------------------------------------------------
+
+get_expand_regexp_at_opts(Opts) ->
+ case lists:keysearch(expand_only_at,1,Opts) of
+ {value,{_,Node}} when atom(Node) ->
+ {ok,Node};
+ _ ->
+ false
+ end.
+%% -----------------------------------------------------------------------------
+
+
+%% =============================================================================
+%% Functions for the internal debugging system.
+%% =============================================================================
+
+%% The debug system is meant to provide tracing of inviso tool at different levels.
+%%
+%% debug(What,Level,Description) -> nothing significant.
+%% What : controls what kind of event. This can both be certain parts of the tool
+%% as well as certain levels (info to catastrophy).
+%% Level: Determines if What shall be printed or not.
+%% Description: this is what happend.
+debug(_What,?DBG_OFF,_Description) ->
+ true; % Debug is off, no action.
+debug(What,On,Description) ->
+ debug_2(What,On,Description).
+
+debug_2(What,_,Description) ->
+ io:format("INVISO DEBUG:~w, ~p~n",[What,Description]).
+%% -----------------------------------------------------------------------------
diff --git a/lib/inviso/src/inviso_tool_sh.erl b/lib/inviso/src/inviso_tool_sh.erl new file mode 100644 index 0000000000..fe876b955a --- /dev/null +++ b/lib/inviso/src/inviso_tool_sh.erl @@ -0,0 +1,1731 @@ +%%%------------------------------------------------------------------------------
+%%% File : inviso_tool_sh.erl
+%%% Author : Lennart �hman <[email protected]>
+%%% Description :
+%%%
+%%% Created : 24 Oct 2005 by Lennart �hman
+%%%------------------------------------------------------------------------------
+-module(inviso_tool_sh).
+
+%% Inviso Session Handler.
+%% This is the code for the session handler process. Its purpose is that we have
+%% one session handler process for each trace session started through the
+%% start_session inviso tool API. The session handler process is responsible for:
+%%
+%% -Knowing the state/status of all participating runtime components.
+%% -Keeping storage of all tracerdata all our participants have used. This means
+%% also to find out the tracerdata of runtime components connecting by them
+%% selves.
+%%
+%% STORAGE STRATEGY
+%% ----------------
+%% The local information storage can be changed by two things. Either by executing
+%% commands issued through our APIs. Or by receiving trace_event from the control
+%% component. When we execute commands, a corresponding event will also follow.
+%% Meaning that in those situations we are informed twice.
+%% A simple strategy could be to wait for the event even when doing the changes
+%% to the runtime components our self (through commands). But that may result in
+%% a small time frame where someone might do yet another command and failing
+%% because the local information storage is not uptodate as it would have been
+%% expected to be. Therefore we always update the local storage when making changes
+%% to a runtime component our selves. There will eventually be a double update
+%% through an incoming event. But the storage must coop with that, preventing
+%% inconsitancies to happend. An example of a strategy is that the tracerdata table
+%% is a bag, not allowing for double entries of the same kind. Therefore a double
+%% update is harmless there.
+
+%% ------------------------------------------------------------------------------
+%% Module wide constants.
+%% ------------------------------------------------------------------------------
+-define(LOCAL_RUNTIME,local_runtime). % Used as node name when non-disitrbuted.
+-define(TRACING,tracing). % A state defined by the control component.
+-define(RUNNING,running). % A status according to control componet.
+
+-define(COPY_LOG_FROM,copy_log_from). % Common fileystem option.
+%% ------------------------------------------------------------------------------
+
+%% ------------------------------------------------------------------------------
+%% API exports.
+%% ------------------------------------------------------------------------------
+-export([start_link/5,start_link/8]).
+-export([cancel_session/1,stop_session/3]).
+-export([reactivate/1,reactivate/2]).
+-export([tpl/5,tpl/6,tpl/7,
+ tf/2,tf/3,
+ tpm_localnames/2,init_tpm/6,init_tpm/9,tpm/6,tpm/7,tpm/10,
+ tpm_ms/7,ctpm_ms/6,ctpm/5
+ ]).
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Internal exports.
+%% ------------------------------------------------------------------------------
+-export([init/1,handle_call/3,handle_info/2,terminate/2]).
+
+-export([get_loopdata/1]).
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Includes.
+%% ------------------------------------------------------------------------------
+-include_lib("kernel/include/file.hrl"). % Necessary for file module.
+%% ------------------------------------------------------------------------------
+
+
+%% ==============================================================================
+%% Exported API functions.
+%% ==============================================================================
+
+%% start_link(From,NodeParams,CtrlNode,CtrlPid,SafetyCatches,NodesIn,NodesNotIn) =
+%% {ok,Pid} | {error,Reason}
+%% From= pid(), the initial client expecting the reply.
+%% NodeParams=[{Node,TracerData},{Node,TracerData,Opts}...]
+%% CtrlNode=atom() | 'void', the node where the trace control component is.
+%% CtrlPid=pid(), the pid of the trace control component.
+%% SafetyCatches=
+%% Dir=string(), where to place fetched logs and the merged log.
+%% Dbg=debug structure.
+%% NodesIn=[Node,...], list of nodes already in another session.
+%% NodesNotIn=[Node,...], list of nodes not in another session.
+%%
+%% Starts a session-handler. It keeps track of the the state and status of all
+%% participating runtime components. Note that there is a non-distributed case too.
+%% In the non-distributed case there is no things such as CtrlNode.
+start_link(From,TracerData,CtrlPid,SafetyCatches,Dbg) ->
+ gen_server:start_link(?MODULE,
+ {self(),From,TracerData,CtrlPid,SafetyCatches,Dbg},
+ []).
+
+start_link(From,NodeParams,CtrlNode,CtrlPid,SafetyCatches,Dbg,NodesIn,NodesNotIn) ->
+ gen_server:start_link(?MODULE,
+ {self(),From,NodeParams,CtrlNode,CtrlPid,
+ SafetyCatches,Dbg,NodesIn,NodesNotIn},
+ []).
+%% ------------------------------------------------------------------------------
+
+%% Stops tracing where it is ongoing. Fetches all logfiles.
+stop_session(SID,Dir,Prefix) ->
+ gen_server:call(SID,{stop_session,Dir,Prefix}).
+%% ------------------------------------------------------------------------------
+
+%% stop_session(SID) = ok
+%%
+%% Cancels the session brutaly. All runtime components are made to stop tracing,
+%% all local log files are removed using the tracerdata we know for them.
+cancel_session(SID) ->
+ gen_server:call(SID,cancel_session).
+%% ------------------------------------------------------------------------------
+
+%% reactivate(SID) = {ok,
+%% reactivate(SID,Nodes) = {ok,NodeResults} | {error,Reason}.
+%% SID=session id, pid().
+%% Nodes=[Node,...]
+%% NodeResult=[{Node,Result},...]
+%% Result={Good,Bad}
+%% Good,Bad=integer(), the number of redone activities.
+%%
+%% Function which reactivates runtime components being suspended. This is done
+%% replaying all trace flags (in the correct order) to the corresponding nodes.
+%% Note that this may also mean turning flags off. Like first turning them on
+%% then off a split second later.
+reactivate(SID) ->
+ gen_server:call(SID,reactivate). %% NOT IMPLEMENTED YET.
+reactivate(SID,Nodes) ->
+ gen_server:call(SID,{reactivate,Nodes}).
+%% ------------------------------------------------------------------------------
+
+
+%% tpl(SessionID,Mod,Func,Arity,MS)=
+%% tpl(SessionID,Mod,Func,Arity,MS,Opts)={ok,N}|{error,Reason}.
+%% tpl(SessionID,Nodes,Mod,Func,Arity,MS)=
+%% tpl(SessionID,Nodes,Mod,Func,Arity,MS,Opts)={ok,Result}|{error,Reason}
+%% Mod='_' | ModuleName | ModRegExp | {DirRegExp,ModRegExp}
+%% ModRegExp=DirRegExp= string()
+%% Func='_' | FunctionName
+%% Arity='_' | integer()
+%% MS=[] | false | a match specification
+%% Opts=[Opts,...]
+%% Opt={arg,Arg}, disable_safety, {expand_regexp_at,NodeName}, only_loaded
+%% Nodes=[NodeName,...]
+tpl(SID,Mod,Func,Arity,MS) ->
+ gen_server:call(SID,{tp,tpl,Mod,Func,Arity,MS,[]}).
+tpl(SID,Mod,Func,Arity,MS,Opts) when list(MS);MS==true;MS==false ->
+ gen_server:call(SID,{tp,tpl,Mod,Func,Arity,MS,Opts});
+tpl(SID,Nodes,Mod,Func,Arity,MS) when integer(Arity);Arity=='_' ->
+ gen_server:call(SID,{tp,tpl,Nodes,Mod,Func,Arity,MS,[]}).
+tpl(SID,Nodes,Mod,Func,Arity,MS,Opts) ->
+ gen_server:call(SID,{tp,tpl,Nodes,Mod,Func,Arity,MS,Opts}).
+%% ------------------------------------------------------------------------------
+
+%% ctpl(SessionID,Nodes,Mod,Func,Arity)=
+%% See tpl/X for arguments.
+%%
+%% Removes local trace-patterns from functions.
+ctpl(SID,Nodes,Mod,Func,Arity) ->
+ gen_server:call(SID,{ctp,ctpl,Nodes,Mod,Func,Arity}).
+%% ------------------------------------------------------------------------------
+
+
+tpm_localnames(SID,Nodes) ->
+ gen_server:call(SID,{tpm_localnames,Nodes}).
+tpm_globalnames(SID,Nodes) ->
+ gen_server:call(SID,{tpm_globalnames,Nodes}).
+
+init_tpm(SID,Nodes,Mod,Func,Arity,CallFunc) ->
+ gen_server:call(SID,{init_tpm,Nodes,Mod,Func,Arity,CallFunc}).
+init_tpm(SID,Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ gen_server:call(SID,
+ {init_tpm,Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc}).
+tpm(SID,Nodes,Mod,Func,Arity,MS) ->
+ gen_server:call(SID,{tpm,Nodes,Mod,Func,Arity,MS}).
+tpm(SID,Nodes,Mod,Func,Arity,MS,CallFunc) ->
+ gen_server:call(SID,{tpm,Nodes,Mod,Func,Arity,MS,CallFunc}).
+tpm(SID,Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
+ gen_server:call(SID,{tpm,Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc}).
+
+tpm_ms(SID,Nodes,Mod,Func,Arity,MSname,MS) ->
+ gen_server:call(SID,{tpm_ms,Nodes,Mod,Func,Arity,MSname,MS}).
+
+ctpm_ms(SID,Nodes,Mod,Func,Arity,MSname) ->
+ gen_server:call(SID,{tpm_ms,Nodes,Mod,Func,Arity,MSname}).
+
+ctpm(SID,Nodes,Mod,Func,Arity) ->
+ gen_server:call(SID,{ctpm,Nodes,Mod,Func,Arity}).
+%% ------------------------------------------------------------------------------
+
+
+%% tf(SessionID,Nodes,TraceConfList)=
+%% TraceConfList=[{PidSpec,Flags},...]
+%% PidSpec=pid()|atom()|all|new|existing
+%% Flags=[Flag,...]
+tf(SID,TraceConfList) ->
+ gen_server:call(SID,{tf,TraceConfList}).
+tf(SID,Nodes,TraceConfList) ->
+ gen_server:call(SID,{tf,Nodes,TraceConfList}).
+%% ------------------------------------------------------------------------------
+
+
+get_loopdata(SID) ->
+ gen_server:call(SID,get_loopdata).
+%% ------------------------------------------------------------------------------
+
+%% ==============================================================================
+%% Genserver call-backs.
+%% ==============================================================================
+
+%% Initial function for the session handler process. The nodes participating in
+%% the session must previously have been added to our control component by the tool.
+%% The session handler first finds out the state/status of the specified runtime
+%% components, then it tries to initiate tracing on those where it is applicable.
+%% Note that a reply to the initial (tool)client is done from here instead from
+%% the tool-server.
+init({Parent,From,TracerData,CtrlPid,SafetyCatches,Dbg}) -> % The non-distributed case.
+ {ok,StateStatus}=init_rtcomponent_states([],void,CtrlPid,[?LOCAL_RUNTIME]),
+ case is_tool_internal_tracerdata(TracerData) of
+ false -> % We shall initiate local runtime.
+ case inviso:init_tracing(TracerData) of
+ ok ->
+ gen_server:reply(From,{ok,{self(),ok}}),
+ {ok,mk_ld(Parent,
+ void,
+ CtrlPid,
+ to_rtstates([{?LOCAL_RUNTIME,{tracing,?RUNNING},[]}]),
+ [{?LOCAL_RUNTIME,TracerData}],
+ [],
+ SafetyCatches,
+ Dbg)};
+ {error,Reason} -> % It might have become suspended?!
+ gen_server:reply(From,{error,Reason}),
+ {ok,mk_ld(Parent,
+ void,
+ CtrlPid,
+ to_rtstates([{?LOCAL_RUNTIME,StateStatus,[]}]),
+ [{?LOCAL_RUNTIME,TracerData}],
+ [],
+ SafetyCatches,
+ Dbg)}
+ end;
+ true -> % We shall not pass this one on.
+ gen_server:reply(From,{ok,{self(),ok}}), % Then it is ok.
+ {ok,mk_ld(Parent,
+ void,
+ CtrlPid,
+ to_rtstates([{?LOCAL_RUNTIME,StateStatus,[]}]),
+ [],
+ [?LOCAL_RUNTIME],
+ SafetyCatches,
+ Dbg)}
+ end;
+init({Parent,From,NodeParams,CtrlNode,CtrlPid,SafetyCatches,Dbg,NodesIn,NodesNotIn}) ->
+ case init_rtcomponent_states(NodeParams,CtrlNode,CtrlPid,NodesNotIn) of
+ {ok,States} -> % A list of {Node,{State,Status},Opts}.
+ {NodeParams2,Nodes2}=remove_nodeparams(NodesIn,NodeParams),
+ case inviso_tool_lib:inviso_cmd(CtrlNode,init_tracing,[NodeParams2]) of
+ {ok,Result} -> % Resulted in state changes!
+ RTStates=set_tracing_rtstates(to_rtstates(States),Result),
+ ReplyValue=init_fix_resultnodes(NodesIn,Nodes2,Result),
+ gen_server:reply(From,{ok,{self(),ReplyValue}}),
+ {ok,mk_ld(Parent,CtrlNode,CtrlPid,RTStates,
+ NodeParams2,Nodes2,SafetyCatches,Dbg)};
+ {error,Reason} -> % Some general failure.
+ inviso_tool_lib:inviso_cmd(CtrlNode,unsubscribe,[]),
+ gen_server:reply(From,{error,{init_tracing,Reason}}),
+ {stop,{init_tracing,Reason}};
+ What ->
+ io:format("GOT:~n~w~n",[What]),
+ exit(foo)
+ end;
+ {error,Reason} -> % Unable to get the state/status.
+ inviso_tool_lib:inviso_cmd(CtrlNode,unsubscribe,[]),
+ gen_server:reply(From,{error,Reason}),
+ {stop,{error,Reason}};
+ What ->
+ io:format("GOT:~n~w~n",[What]),
+ exit(foo)
+ end.
+%% ------------------------------------------------------------------------------
+
+%% To stop a session means stop the tracing and remove all local files on the
+%% runtime nodes. We do have a table with all tracer data and that is how we are
+%% going to recreate what files to remove.
+%% Since runtime components may actually change state when this procedure is
+%% on-going, we do not care! It is the state in the session handling process at
+%% the time of start of this procedure which is used.
+handle_call(cancel_session,_From,LD) ->
+ CtrlNode=get_ctrlnode_ld(LD),
+ RTStates=get_rtstates_ld(LD),
+ Dbg=get_dbg_ld(LD),
+ TracingNodes=get_all_tracing_nodes_rtstates(RTStates),
+ case stop_all_tracing(CtrlNode,Dbg,TracingNodes) of
+ ok-> % Hopefully all nodes are stopped now.
+ AvailableNodes=get_all_available_nodes_rtstates(RTStates),
+ TRDstorage=get_trdstorage_ld(LD),
+ remove_all_local_logs(CtrlNode,TRDstorage,AvailableNodes,Dbg),
+ {stop,normal,ok,LD}; % LD actually not correct now!
+ {error,Reason} -> % Some serious error when stop_tracing.
+ {stop,normal,{error,Reason},LD}
+ end;
+%% ------------------------------------------------------------------------------
+
+%% *Stop all tracing on runtime components still tracing.
+%% *Copy all local log files to the collection directory.
+handle_call({stop_session,Dir,Prefix},_From,LD) ->
+ case check_directory_exists(Dir) of % Check that this directory exists here.
+ true ->
+ RTStates=get_rtstates_ld(LD),
+ CtrlNode=get_ctrlnode_ld(LD),
+ Dbg=get_dbg_ld(LD),
+ TracingNodes=get_all_tracing_nodes_rtstates(RTStates),
+ case stop_all_tracing(CtrlNode,Dbg,TracingNodes) of
+ ok -> % Hopefully no node is still tracing now.
+ TRDstorage=get_trdstorage_ld(LD),
+ AvailableNodes=get_all_available_nodes_rtstates(RTStates),
+ {FailedNodes,FetchedFiles}=
+ transfer_logfiles(RTStates,CtrlNode,Dir,Prefix,
+ TRDstorage,Dbg,AvailableNodes),
+ RemoveNodes= % We only delete local logs where fetch ok.
+ lists:filter(fun(N)->
+ case lists:keysearch(N,1,FailedNodes) of
+ {value,_} ->
+ false;
+ false ->
+ true
+ end
+ end,
+ AvailableNodes),
+ remove_all_local_logs(CtrlNode,TRDstorage,RemoveNodes,Dbg),
+ {stop,normal,{ok,{FailedNodes,FetchedFiles}},LD};
+ {error,Reason} -> % Some general failure, quit.
+ {stop,normal,{error,Reason},LD}
+ end;
+ false -> % You specified a non-existing directory!
+ {reply,{error,{faulty_dir,Dir}},LD}
+ end;
+%% ------------------------------------------------------------------------------
+
+handle_call({reactivate,Nodes},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ {OurNodes,OtherNodes}=
+ remove_nodes_not_ours(Nodes,get_all_session_nodes_rtstates(RTStates)),
+ CtrlNode=get_ctrlnode_ld(LD),
+ ACTstorage=get_actstorage_ld(LD),
+ case h_reactivate(CtrlNode,OurNodes,ACTstorage) of
+ {ok,Results} -> % A list of {Node,Result}.
+ if
+ OtherNodes==[] -> % Normal case, no non-session nodes.
+ {reply,{ok,Results},LD};
+ true -> % Add error values for non-session nodes.
+ {reply,
+ {ok,
+ lists:map(fun(N)->{N,{error,not_in_session}} end,OtherNodes)++
+ Results},
+ LD}
+ end;
+ {error,Reason} -> % Then this error takes presidence.
+ {reply,{error,Reason},LD}
+ end;
+%% ------------------------------------------------------------------------------
+
+%% Call-back for set trace-pattern for both global and local functions.
+handle_call({tp,PatternFunc,Mod,F,A,MS,Opts},_From,LD) ->
+ Reply=h_tp(all,PatternFunc,Mod,F,A,MS,Opts,LD), % For all active nodes in the session.
+ {reply,Reply,LD};
+handle_call({tp,PatternFunc,Nodes,Mod,F,A,MS,Opts},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ SNodes=get_all_session_nodes_rtstates(RTStates), % Notes belongoing to the session.
+ {Nodes2,FaultyNodes}=remove_nodes_not_ours(Nodes,SNodes),
+ Reply=h_tp(Nodes2,PatternFunc,Mod,F,A,MS,Opts,LD),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,FaultyNodes),
+ {reply,ErrorReply++Reply,LD};
+%% ------------------------------------------------------------------------------
+
+%% Call-back handling the removal of both local and global trace-patterns.
+%% NOT IMPLEMENTED YET.
+handle_call({ctp,PatternFunc,Nodes,Mod,F,A},_From,LD) ->
+ Reply=h_ctp(Nodes,PatternFunc,Mod,F,A,LD),
+ {reply,Reply,LD};
+%% ------------------------------------------------------------------------------
+
+handle_call({tpm_localnames,Nodes},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_tpm_localnames(get_ctrlnode_ld(LD),Nodes2,RTStates,ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({init_tpm,Nodes,Mod,Func,Arity,CallFunc},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),
+ Nodes2,
+ init_tpm,
+ [Mod,Func,Arity,CallFunc],
+ RTStates,
+ ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({init_tpm,Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),
+ Nodes2,
+ init_tpm,
+ [Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc],
+ RTStates,
+ ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({tpm,Nodes,Mod,Func,Arity,MS},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),Nodes2,tpm,[Mod,Func,Arity,MS],RTStates,ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({tpm,Nodes,Mod,Func,Arity,MS,CallFunc},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),
+ Nodes2,
+ tpm,
+ [Mod,Func,Arity,MS,CallFunc],
+ RTStates,
+ ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({tpm,Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),
+ Nodes2,
+ tpm,
+ [Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc],
+ RTStates,
+ ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({tpm_ms,Nodes,Mod,Func,Arity,MSname,MS},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),
+ Nodes2,
+ tpm_ms,
+ [Mod,Func,Arity,MSname,MS],
+ RTStates,
+ ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({ctpm_ms,Nodes,Mod,Func,Arity,MSname},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),
+ Nodes2,
+ ctpm_ms,
+ [Mod,Func,Arity,MSname],
+ RTStates,
+ ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+
+handle_call({ctpm,Nodes,Mod,Func,Arity},_From,LD) ->
+ RTStates=get_rtstates_ld(LD),
+ OurNodes=get_all_session_nodes_rtstates(RTStates),
+ {Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
+ ACTstorage=get_actstorage_ld(LD),
+ {Reply,NewACTstorage}=
+ h_all_tpm(get_ctrlnode_ld(LD),Nodes2,ctpm,[Mod,Func,Arity],RTStates,ACTstorage),
+ ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
+ {reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
+%% ------------------------------------------------------------------------------
+
+%% Call-back for setting process trace-flags. Handles both distributed and non-
+%% distributed case.
+handle_call({tf,TraceConfList},From,LD) ->
+ handle_call({tf,all,TraceConfList},From,LD);
+handle_call({tf,Nodes,TraceConfList},_From,LD) ->
+ {Reply,NewACTstorage}=h_tf(get_ctrlnode_ld(LD),
+ Nodes,
+ TraceConfList,
+ get_actstorage_ld(LD),
+ get_rtstates_ld(LD)),
+ {reply,Reply,put_actstorage_ld(NewACTstorage,LD)};
+%% ------------------------------------------------------------------------------
+
+
+
+handle_call(get_loopdata,_From,LD) ->
+ io:format("The loopdata:~n~p~n",[LD]),
+ {reply,ok,LD}.
+%% ------------------------------------------------------------------------------
+
+
+%% Clause handling an incomming state-change event from the control component.
+%% Note that it does not have to be one of our nodes since it is not possible
+%% to subscribe to certain node-events.
+%% We may very well get state-change events for state-changes we are the source
+%% to our selves. Those state-changes are already incorporated into the RTStates.
+%% There is however no harm in doing them again since we know that this event
+%% message will reach us before a reply to a potentially following state-change
+%% request will reach us. Hence we will do all state-changes in the correct order,
+%% even if sometimes done twice.
+handle_info({trace_event,CtrlPid,_Time,{state_change,Node,{State,Status}}},LD) ->
+ case get_ctrlpid_ld(LD) of
+ CtrlPid -> % It is from our control component.
+ case {State,Status} of
+ {?TRACING,?RUNNING} -> % This is the only case when new tracerdata!
+ NewTracerData=add_current_tracerdata_ld(get_ctrlnode_ld(LD),
+ Node,
+ get_rtstates_ld(LD),
+ get_trdstorage_ld(LD)),
+ NewRTStates=statechange_rtstates(Node,State,Status,get_rtstates_ld(LD)),
+ {noreply,put_trdstorage_ld(NewTracerData,
+ put_rtstates_ld(NewRTStates,LD))};
+ _ -> % In all other cases, just fix rtstates.
+ NewRTStates=statechange_rtstates(Node,State,Status,get_rtstates_ld(LD)),
+ {noreply,put_rtstates_ld(NewRTStates,LD)}
+ end;
+ _ ->
+ {noreply,LD}
+ end;
+%% If a new runtime component connects to our trace control component, and it is
+%% in our list of runtime components belonging to this session, we may update its
+%% state to now being present. Otherwise it does not belong to this session.
+%% Note that we avoid updating an already connected runtime component. This
+%% can happend if it connected by itself after we started the session handler,
+%% but before we managed to initiate tracing. Doing so or not will not result in
+%% any error in the long run, but during a short period of time we might be
+%% prevented from doing things with the runtime though it actually is tracing.
+handle_info({trace_event,CtrlPid,_Time,{connected,Node,{_Tag,{State,Status}}}},LD) ->
+ case get_ctrlpid_ld(LD) of
+ CtrlPid -> % It is from our control component.
+ case get_statestatus_rtstates(Node,get_rtstates_ld(LD)) of
+ {ok,unavailable} -> % This is the situation when we update!
+ NewRTStates=statechange_rtstates(Node,State,Status,get_rtstates_ld(LD)),
+ {noreply,put_rtstates_ld(NewRTStates,LD)};
+ _ -> % In all other cases, let it be.
+ {noreply,LD}
+ end;
+ _ -> % Not from our control component.
+ {noreply,LD}
+ end;
+%% If a runtime component disconnects we mark it as unavailable. We must also
+%% remove all saved trace-flags in order for them to not be accidently reactivated
+%% should the runtime component reconnect and then suspend.
+handle_info({trace_event,CtrlPid,_Time,{disconnected,Node,_}},LD) ->
+ case get_ctrlpid_ld(LD) of
+ CtrlPid -> % It is from our control component.
+ NewRTStates=set_unavailable_rtstates(Node,get_rtstates_ld(LD)),
+ NewACTstorage=del_node_actstorage(Node,get_actstorage_ld(LD)),
+ {noreply,put_actstorage_ld(NewACTstorage,put_rtstates_ld(NewRTStates,LD))};
+ _ ->
+ {noreply,LD}
+ end;
+handle_info(_,LD) ->
+ {noreply,LD}.
+%% ------------------------------------------------------------------------------
+
+%% In terminate we cancel our subscription to event from the trace control component.
+%% That should actually not be necessary, but lets do it the correct way!
+terminate(_,LD) ->
+ case get_ctrlnode_ld(LD) of
+ void -> % Non-distributed.
+ inviso:unsubscribe();
+ Node ->
+ inviso_tool_lib:inviso_cmd(Node,unsubscribe,[])
+ end.
+%% ------------------------------------------------------------------------------
+
+
+
+%% ==============================================================================
+%% First level help functions to call-backs.
+%% ==============================================================================
+
+%% ------------------------------------------------------------------------------
+%% Help functions to init.
+%% ------------------------------------------------------------------------------
+
+%% Help function which find out the state/status of the runtime components.
+%% Note that since we have just started subscribe to state changes we must
+%% check our inqueue to see that we have no waiting messages for the nodes
+%% we learned the state/status of. If there is a waiting message we don't
+%% know whether that was a state change received before or after the state
+%% check was done. We will then redo the state-check.
+%% Returns {ok,States} or {error,Reason}.
+%% Where States is [{Node,{State,Status},Opts},...].
+%% Note that {error,Reason} can not occur in the non-distributed case.
+init_rtcomponent_states(NodeParams,void,CtrlPid,Nodes) -> % The non-distributed case.
+ ok=inviso:subscribe(),
+ init_rtcomponent_states_2(NodeParams,void,CtrlPid,Nodes,[]);
+init_rtcomponent_states(NodeParams,CtrlNode,CtrlPid,Nodes) ->
+ ok=inviso_tool_lib:inviso_cmd(CtrlNode,subscribe,[]),
+ init_rtcomponent_states_2(NodeParams,CtrlNode,CtrlPid,Nodes,[]).
+
+init_rtcomponent_states_2(_,_,_,[],States) ->
+ {ok,States};
+init_rtcomponent_states_2(NodeParams,void,CtrlPid,_Nodes,States) ->
+ case inviso:get_status() of
+ {ok,StateStatus} -> % Got its state/status, now...
+ {ProblemNodes,NewStates}=
+ init_rtcomponent_states_3(NodeParams,CtrlPid,[{?LOCAL_RUNTIME,{ok,StateStatus}}],
+ [],States),
+ init_rtcomponent_states_2(NodeParams,void,CtrlPid,ProblemNodes,NewStates);
+ {error,_Reason} -> % The runtime is not available!?
+ {ok,[{?LOCAL_RUNTIME,unavailable,[]}]} % Create the return value immediately.
+ end;
+init_rtcomponent_states_2(NodeParams,CtrlNode,CtrlPid,Nodes,States) ->
+ case inviso_tool_lib:inviso_cmd(CtrlNode,get_status,[Nodes]) of
+ {ok,NodeResult} ->
+ {ProblemNodes,NewStates}=
+ init_rtcomponent_states_3(NodeParams,CtrlPid,NodeResult,[],States),
+ init_rtcomponent_states_2(NodeParams,CtrlNode,CtrlPid,ProblemNodes,NewStates);
+ {error,Reason} -> % Severe problem, abort the session.
+ {error,{get_status,Reason}}
+ end.
+
+%% Traverses the list of returnvalues and checks that we do not have an event
+%% waiting in the message queue. If we do have, it is a problem. That node will
+%% be asked about its state again.
+%% Note that it is here we construct the RTStatesList.
+init_rtcomponent_states_3(NodeParams,CtrlPid,[{Node,{ok,{State,Status}}}|Rest],Problems,States) ->
+ receive
+ {trace_event,CtrlPid,_Time,{state_change,Node,_}} ->
+ init_rtcomponent_states_3(NodeParams,CtrlPid,Rest,[Node|Problems],States)
+ after
+ 0 -> % Not in msg queue, then we're safe!
+ RTState=case lists:keysearch(Node,1,NodeParams) of
+ {value,{_Node,_TracerData,Opts}} ->
+ {Node,{State,Status},Opts};
+ _ -> % No option available, use [].
+ {Node,{State,Status},[]}
+ end,
+ init_rtcomponent_states_3(NodeParams,CtrlPid,Rest,Problems,[RTState|States])
+ end;
+init_rtcomponent_states_3(NodeParams,CtrlPid,[{Node,{error,_Reason}}|Rest],Problems,States) ->
+ RTState=case lists:keysearch(Node,1,NodeParams) of
+ {value,{_Node,_TracerData,Opts}} ->
+ {Node,unavailable,Opts};
+ _ -> % No option available, use [].
+ {Node,unavailable,[]}
+ end,
+ init_rtcomponent_states_3(NodeParams,CtrlPid,Rest,Problems,[RTState|States]);
+init_rtcomponent_states_3(_,_,[],Problems,States) ->
+ {Problems,States}.
+%% ------------------------------------------------------------------------------
+
+%% Help function removing nodes from NodeParams. The reason for this can either
+%% be that we are using a tool internal tracerdata that shall not be forwarded to
+%% the trace control component, or that the node is actually already part of
+%% another session.
+%% Returns {NewNodeParams,NodesWhichShallNotBeInitiated}.
+remove_nodeparams(Nodes,NodesParams) ->
+ remove_nodeparams_2(Nodes,NodesParams,[],[]).
+
+remove_nodeparams_2(Nodes,[NodeParam|Rest],NPAcc,NAcc) when % NPAcc=NodeParamsAcc.
+ (is_tuple(NodeParam) and ((size(NodeParam)==2) or (size(NodeParam)==3))) ->
+ Node=element(1,NodeParam),
+ Params=element(2,NodeParam), % This is tracerdata!
+ case lists:member(Node,Nodes) of
+ true -> % Remove this one, in another session.
+ remove_nodeparams_2(Nodes,Rest,NPAcc,NAcc);
+ false -> % Ok so far...
+ case is_tool_internal_tracerdata(Params) of
+ false -> % Then keep it and use it later!
+ remove_nodeparams_2(Nodes,Rest,[{Node,Params}|NPAcc],NAcc);
+ true -> % Since it is, remove it from the list.
+ remove_nodeparams_2(Nodes,Rest,NPAcc,[Node|NAcc])
+ end
+ end;
+remove_nodeparams_2(Nodes,[_|Rest],NPAcc,NAcc) -> % Faulty NodeParam, skip it!
+ remove_nodeparams_2(Nodes,Rest,NPAcc,NAcc);
+remove_nodeparams_2(_,[],NPAcc,NAcc) ->
+ {lists:reverse(NPAcc),NAcc}.
+%% ------------------------------------------------------------------------------
+
+%% Help function which adds both the nodes which were already part of another
+%% session and the nodes that we actually did not issue any init_tracing for.
+%% Returns a new Result list of [{Node,NodeResult},...].
+init_fix_resultnodes(NodesOtherSes,NodesNotInit,Result) ->
+ NewResult=init_fix_resultnodes_2(NodesOtherSes,{error,in_other_session},Result),
+ init_fix_resultnodes_2(NodesNotInit,ok,NewResult).
+
+init_fix_resultnodes_2([Node|Rest],NodeResult,Result) ->
+ [{Node,NodeResult}|init_fix_resultnodes_2(Rest,NodeResult,Result)];
+init_fix_resultnodes_2([],_,Result) ->
+ Result. % Append Result to the end of the list.
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Help functions to reactivate.
+%% ------------------------------------------------------------------------------
+
+h_reactivate(CtrlNode,Nodes,ACTstorage) -> % Distributed case.
+ case inviso_tool_lib:inviso_cmd(CtrlNode,cancel_suspension,[Nodes]) of
+ {ok,CSuspResults} ->
+ {GoodNodes,BadResults}= % Sort out nodes no longer suspended.
+ lists:foldl(fun({Node,ok},{GoodNs,BadNs})->
+ {[Node|GoodNs],BadNs};
+ ({Node,{error,Reason}},{GoodNs,BadNs})->
+ {GoodNs,[{Node,{error,{cancel_suspension,Reason}}}|BadNs]}
+ end,
+ {[],[]},
+ CSuspResults),
+ Results=h_reactivate_redo_activity(CtrlNode,GoodNodes,ACTstorage,[]),
+ {ok,BadResults++Results};
+ {error,Reason} -> % General failure cancelling suspend.
+ {error,{cancel_suspension,Reason}}
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Help function which traverses the list of nodes known to be ours and have
+%% cancelled their suspend. If we fail redoing one of the activities associated
+%% with a node, the node will be reported in the return value as failed. From
+%% that point on its state must be considered unknown since we do not know how
+%% many of the activities were successfully redone.
+h_reactivate_redo_activity(CtrlNode,[Node|Rest],ACTstorage,Acc) ->
+ case get_activities_actstorage(Node,ACTstorage) of
+ {ok,Activities} -> % The node existed in activity storage.
+ {Good,Bad}=h_reactivate_redo_activity_2(CtrlNode,Node,Activities,0,0),
+ h_reactivate_redo_activity(CtrlNode,Rest,ACTstorage,[{Node,{Good,Bad}}|Acc]);
+ false -> % Node not present in activity storage.
+ h_reactivate_redo_activity(CtrlNode,Rest,ACTstorage,[{Node,{0,0}}|Acc])
+ end;
+h_reactivate_redo_activity(_CtrlNode,[],_,Acc) ->
+ lists:reverse(Acc).
+
+%% Help function actually redoing the activity. Note that there must be one
+%% clause here for every type of activity.
+%% Returns {NrGoodCmds,NrBadCmds}.
+%% The number of good or bad commands refers to inviso commands done. If any
+%% of the subparts of such a command returned an error, the command is concidered
+%% no good.
+h_reactivate_redo_activity_2(CtrlNode,Node,[{tf,{Op,TraceConfList}}|Rest],Good,Bad) ->
+ case inviso_tool_lib:inviso_cmd(CtrlNode,Op,[[Node],TraceConfList]) of
+ {ok,[{_Node,{ok,Answers}}]} ->
+ case h_reactivate_redo_activity_check_tf(Answers) of
+ ok ->
+ h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good+1,Bad);
+ error -> % At least oneReports the first encountered error.
+ h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1)
+ end;
+ {ok,[{_Node,{error,_Reason}}]} ->
+ h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1);
+ {error,_Reason} -> % General error when doing cmd.
+ h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1)
+ end;
+h_reactivate_redo_activity_2(CtrlNode,Node,[{tpm,{Op,InvisoCmdParams}}|Rest],Good,Bad) ->
+ case inviso_tool_lib:inviso_cmd(CtrlNode,Op,[[Node]|InvisoCmdParams]) of
+ {ok,[{_Node,ok}]} ->
+ h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good+1,Bad);
+ {ok,[{_Node,{error,_Reason}}]} ->
+ h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1);
+ {error,_Reason} -> % General error when doing cmd.
+ h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1)
+ end;
+h_reactivate_redo_activity_2(_CtrlNode,_Node,[],Good,Bad) ->
+ {Good,Bad}.
+
+%% Help function traversing a list of results from inviso:tf/2 or inviso:ctf/2
+%% to see if there were any errors.
+h_reactivate_redo_activity_check_tf([N|Rest]) when integer(N) ->
+ h_reactivate_redo_activity_check_tf(Rest);
+h_reactivate_redo_activity_check_tf([{error,_Reason}|_]) ->
+ error;
+h_reactivate_redo_activity_check_tf([]) ->
+ ok.
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Help functions to tp (setting trace patterns, both local and global).
+%% ------------------------------------------------------------------------------
+
+%% Help function which handles both tpl and tp. Note that the non-distributed case
+%% handled with Nodes='all'.
+%% Returns what shall be the reply to the client.
+h_tp(all,PatternFunc,Mod,F,A,MS,Opts,LD) -> % All available runtime nodes.
+ Nodes=get_all_available_nodes_rtstates(get_rtstates_ld(LD)),
+ h_tp(Nodes,PatternFunc,Mod,F,A,MS,Opts,LD);
+h_tp(Nodes,PatternFunc,Mod,F,A,MS,Opts,LD) -> % Only certain nodes in the session.
+ CtrlNode=get_ctrlnode_ld(LD),
+ Dbg=get_dbg_ld(LD),
+ SafetyCatches=get_safetycatches_ld(LD),
+ case inviso_tool_lib:expand_module_names(Nodes,Mod,Opts) of % Take care of any reg-exps.
+ {multinode_expansion,NodeMods} ->
+ NodeTPs=inviso_tool_lib:make_patterns(SafetyCatches,Opts,Dbg,NodeMods,F,A,MS),
+ h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,NodeTPs,[]);
+ {singlenode_expansion,Modules} ->
+ TPs=inviso_tool_lib:make_patterns(SafetyCatches,Opts,Dbg,Modules,F,A,MS),
+ h_tp_do_tps(CtrlNode,Nodes,TPs,PatternFunc,Dbg);
+ module ->
+ TPs=inviso_tool_lib:make_patterns(SafetyCatches,Opts,Dbg,[Mod],F,A,MS),
+ h_tp_do_tps(CtrlNode,Nodes,TPs,PatternFunc,Dbg);
+ wildcard -> % Means do for all modules, no safety.
+ h_tp_do_tps(CtrlNode,Nodes,[{Mod,F,A,MS}],PatternFunc,Dbg);
+ {error,Reason} ->
+ {error,Reason}
+ end.
+
+%% Note that this function can never be called in the non-distributed case.
+h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,[{Node,TPs}|Rest],Accum) ->
+ case h_tp_do_tps(CtrlNode,[Node],TPs,PatternFunc,Dbg) of
+ {ok,[{Node,Result}]} ->
+ h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,Rest,[{Node,Result}|Accum]);
+ {error,Reason} -> % Failure, but don't stop.
+ h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,Rest,[{Node,{error,Reason}}|Accum])
+ end;
+h_tp_node_by_node(_,_,_,[],Accum) ->
+ {ok,lists:reverse(Accum)}.
+
+%% Help function which does the actual call to the trace control component.
+%% Note that Nodes can be a list of nodes (including a single one) or
+%% ?LOCAL_RUNTIME if we are not distributed. The non-distributed case is otherwise
+%% detected by the 'void' CtrlNode.
+%% Returns {ok,[{Node,{ok,{NrOfFunctions,NrOfErrors}}},{Node,{error,Reason}},...]} or
+%% {error,Reason}. In the non-distributed case {ok,{NrOfFunctions,NrOfErros}} or
+%% {error,Reason}.
+h_tp_do_tps(void,_Nodes,TPs,PatternFunc,Dbg) -> % Non distributed case!
+ inviso_tool_lib:debug(tp,Dbg,[TPs,PatternFunc]),
+ case inviso:PatternFunc(TPs) of
+ {ok,Result} -> % A list of [Nr1,Nr2,error,...].
+ {ok,
+ lists:foldl(fun(N,{AccNr,AccErr}) when integer(N) ->
+ {AccNr+N,AccErr};
+ (error,{AccNr,AccErr}) ->
+ {AccNr,AccErr+1}
+ end,
+ {0,0},
+ Result)};
+ {error,Reason} ->
+ {error,{PatternFunc,Reason}}
+ end;
+h_tp_do_tps(CtrlNode,Nodes,TPs,PatternFunc,Dbg) ->
+ inviso_tool_lib:debug(tp,Dbg,[Nodes,TPs,PatternFunc]),
+ case inviso_tool_lib:inviso_cmd(CtrlNode,PatternFunc,[Nodes,TPs]) of
+ {ok,Result} -> % Result is [{Node,Result},...].
+ {ok,
+ lists:map(fun({Node,{ok,Res}})->
+ {Node,lists:foldl(fun(N,{ok,{AccNr,AccErr}}) when integer(N) ->
+ {ok,{AccNr+N,AccErr}};
+ (error,{AccNr,AccErr}) ->
+ {ok,{AccNr,AccErr+1}}
+ end,
+ {ok,{0,0}},
+ Res)};
+ ({_Node,{error,Reason}})->
+ {error,Reason}
+ end,
+ Result)};
+ {error,Reason} ->
+ {error,{PatternFunc,Reason}}
+ end.
+%% ------------------------------------------------------------------------------
+
+%% ------------------------------------------------------------------------------
+%% Help functions for removing trace-patterns.
+%% ------------------------------------------------------------------------------
+
+%% NOT IMPLEMENTED YET.
+h_ctp(Node,PatternFunc,Mod,F,A,LD) ->
+ tbd.
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Help functions for calling the trace information facility.
+%% ------------------------------------------------------------------------------
+
+
+%% Function handling the meta trace pattern for capturing registration of local
+%% process names.
+h_tpm_localnames(CtrlNode,Nodes,RTStates,ACTstorage) ->
+ AvailableNodes=get_all_available_nodes_rtstates(RTStates),
+ {Nodes3,FaultyNodes}=remove_nodes_not_ours(Nodes,AvailableNodes),
+ case inviso_tool_lib:inviso_cmd(CtrlNode,tpm_localnames,[Nodes3]) of
+ {ok,Result} -> % That good we want to modify tpmstorage!
+ NewACTstorage=add_tpm_actstorage(Result,tpm_localnames,[],ACTstorage),
+ ErrorResult=lists:map(fun(N)->{N,{error,not_available}} end,FaultyNodes),
+ {{ok,ErrorResult++Result},NewACTstorage};
+ {error,Reason} -> % If general failure, do not modify storage.
+ {{error,Reason},ACTstorage}
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Functions calling meta trace functions for specified nodes. This function is
+%% intended for use with all tmp function calls, init_tpm,tpm,tpm_ms,ctpm_ms and
+%% ctpm.
+%% Note that we must store called meta trace functions and their parameters in the
+%% activity storage in order to be able to redo them in case of a reactivate.
+h_all_tpm(CtrlNode,Nodes,TpmCmd,InvisoCmdParams,RTStates,ACTstorage) ->
+ AvailableNodes=get_all_available_nodes_rtstates(RTStates),
+ {Nodes3,FaultyNodes}=remove_nodes_not_ours(Nodes,AvailableNodes),
+ case inviso_tool_lib:inviso_cmd(CtrlNode,TpmCmd,[Nodes3|InvisoCmdParams]) of
+ {ok,Result} -> % That good we want to modify tpmstorage!
+ NewACTstorage=add_tpm_actstorage(Result,TpmCmd,InvisoCmdParams,ACTstorage),
+ ErrorResult=lists:map(fun(N)->{N,{error,not_available}} end,FaultyNodes),
+ {{ok,ErrorResult++Result},NewACTstorage};
+ {error,Reason} -> % If general failure, do not modify storage.
+ {{error,Reason},ACTstorage}
+ end.
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Help functions for set trace flags.
+%% ------------------------------------------------------------------------------
+
+%% Help function which sets the tracepatterns in TraceConfList for all nodes
+%% mentioned in Nodes. Note that non-distributed case is handled with Nodes='all'.
+%% Returns {Reply,NewACTstorage} where Reply is whatever shall be returned to caller
+%% and NewACTstorage is traceflag storage modified with the flags added to the
+%% corresponding nodes.
+h_tf(void,_Nodes,TraceConfList,ACTstorage,_RTStates) -> % The non-distributed case.
+ Reply=inviso:tf(TraceConfList),
+ NewACTstorage=add_tf_actstorage([{?LOCAL_RUNTIME,Reply}],tf,TraceConfList,ACTstorage),
+ {Reply,NewACTstorage};
+h_tf(CtrlNode,all,TraceConfList,ACTstorage,RTStates) ->
+ AllNodes=get_all_session_nodes_rtstates(RTStates),
+ h_tf(CtrlNode,AllNodes,TraceConfList,ACTstorage,RTStates);
+h_tf(CtrlNode,Nodes,TraceConfList,ACTstorage,_RTStates) ->
+ case inviso_tool_lib:inviso_cmd(CtrlNode,tf,[Nodes,TraceConfList]) of
+ {ok,Result} -> % That good we want to modify actstorage!
+ NewACTstorage=add_tf_actstorage(Result,tf,TraceConfList,ACTstorage),
+ {{ok,Result},NewACTstorage};
+ {error,Reason} -> % If general failure, do not modify actstorage.
+ {{error,Reason},ACTstorage}
+ end.
+%% ------------------------------------------------------------------------------
+
+%% ------------------------------------------------------------------------------
+%% Help functions to stop_session.
+%% ------------------------------------------------------------------------------
+
+%% This function fetches all local log-files using our stored tracerdata. Note
+%% that there are two major ways of tranfering logfiles. Either via distributed
+%% Erlang or by common filesystem (like NFS). The default is distributed Erlang.
+%% But there may be info in the RTStates structure about a common file-system.
+%% Returns {FailedNodes,FetchedFileNames} where FailedNodes is a list of
+%% nodenames where problems occurred. Note that problems does not necessarily
+%% mean that no files were copied.
+%% FetchedFileNames contains one or two of the tuples {trace_log,Files} and/or
+%% {ti_log,Files}, listing all files successfully fetched. Note that the
+%% list of fetched files contains sublists of filenames. One for each node and
+%% tracerdata.
+%% In the non-distributed system we always use copy (since the files always
+%% resides locally).
+transfer_logfiles(RTStates,CtrlNode,Dir,Prefix,TRDstorage,Dbg,AvailableNodes) ->
+ if
+ CtrlNode==void -> % When non-distributed, always copy!
+ fetch_logfiles_copy(CtrlNode,Dir,Prefix,TRDstorage,Dbg,[?LOCAL_RUNTIME]);
+ true -> % The distributed case.
+ {FetchNodes,CopyNodes}=find_logfile_transfer_methods(AvailableNodes,RTStates),
+ {FailedFetchNodes,FetchedFiles}=
+ case fetch_logfiles_distributed(CtrlNode,Dir,Prefix,TRDstorage,Dbg,FetchNodes) of
+ {ok,Failed,Files} -> % So far no disasters.
+ {Failed,Files};
+ {error,Reason} -> % Means all fetch-nodes failed!
+ inviso_tool_lib:debug(transfer_logfiles,Dbg,[FetchNodes,Reason]),
+ {lists:map(fun(N)->{N,error} end,FetchNodes),[]}
+ end,
+ {FailedCopyNodes,CopiedFiles}=
+ fetch_logfiles_copy(CtrlNode,Dir,Prefix,TRDstorage,Dbg,CopyNodes),
+ {FailedFetchNodes++FailedCopyNodes,FetchedFiles++CopiedFiles}
+ end.
+
+%% Help function which finds out which node we have a common file system with
+%% and from which we must make distributed erlang tranfere.
+%% Returns {DistributedNodes,CopyNodes} where CopyNode is [{Node,CopyFromDir},...].
+find_logfile_transfer_methods(Nodes,RTStates) ->
+ find_logfile_transfer_methods_2(Nodes,RTStates,[],[]).
+
+find_logfile_transfer_methods_2([Node|Rest],RTStates,FetchAcc,CopyAcc) ->
+ {ok,Opts}=get_opts_rtstates(Node,RTStates), % Node must be in RTStates!
+ case lists:keysearch(?COPY_LOG_FROM,1,Opts) of
+ {value,{_,FromDir}} when list(FromDir) -> % Node has common filesystem.
+ find_logfile_transfer_methods_2(Rest,RTStates,FetchAcc,[{Node,FromDir}|CopyAcc]);
+ {value,_} -> % Can't understand dir option.
+ find_logfile_transfer_methods_2(Rest,RTStates,[Node|FetchAcc],CopyAcc);
+ false -> % Then we want to use fetch instead.
+ find_logfile_transfer_methods_2(Rest,RTStates,[Node|FetchAcc],CopyAcc)
+ end;
+find_logfile_transfer_methods_2([],_,FetchAcc,CopyAcc) ->
+ {FetchAcc,CopyAcc}.
+%% ------------------------------------------------------------------------------
+
+%% Help function which transferes all local logfiles according to the tracerdata
+%% stored for the nodes in Nodes.
+%% Returns {ok,FailedNodes,FileNodeSpecs} or {error,Reason}.
+%% FailedNodes is a list of nodes where fetching logs did not succeed, partially
+%% or not at all.
+%% FileNames is a list of list of actually fetched files (the name as it is here, including
+%% Dir). The sublists are files which belong together.
+fetch_logfiles_distributed(CtrlNode,Dir,Prefix,TRDstorage,Dbg,Nodes) ->
+ LogSpecList=build_logspeclist(Nodes,TRDstorage),
+ case inviso_fetch_log(inviso_tool_lib:inviso_cmd(CtrlNode,
+ fetch_log,
+ [LogSpecList,Dir,Prefix])) of
+ {ok,Result} ->
+ Files=get_all_filenames_fetchlog_result(Result,Dbg),
+ FailedNodes=get_all_failednodes_fetchlog_result(Result),
+ {ok,FailedNodes,Files};
+ {error,Reason} -> % Some general failure!
+ {error,{fetch_log,Reason}}
+ end.
+
+%% Help function which constructs a list {Node,TracerData} for all nodes in Nodes.
+%% Note that there may be more than one tracerdata for a node, resulting in multiple
+%% tuples for that node.
+build_logspeclist(Nodes,TRDstorage) ->
+ build_logspeclist_2(Nodes,TRDstorage,[]).
+
+build_logspeclist_2([Node|Rest],TRDstorage,Acc) ->
+ TRDlist=find_tracerdata_for_node_trd(Node,TRDstorage), % A list of all tracerdata.
+ build_logspeclist_2(Rest,
+ TRDstorage,
+ [lists:map(fun(TRD)->{Node,TRD} end,TRDlist)|Acc]);
+build_logspeclist_2([],_,Acc) ->
+ lists:flatten(Acc).
+
+%% Help function which translates inviso:fetch_log return values to what I
+%% want!
+inviso_fetch_log({error,Reason}) ->
+ {error,Reason};
+inviso_fetch_log({_Success,ResultList}) ->
+ {ok,ResultList}.
+
+%% Help function which collects all filenames mentioned in a noderesult structure.
+%% The files may or may not be complete.
+%% Returns a list of list of filenames. Each sublist contains files which belong
+%% together, i.e because they are a wrap-set.
+get_all_filenames_fetchlog_result(NodeResult,Dbg) ->
+ get_all_filenames_fetchlog_result_2(NodeResult,Dbg,[]).
+
+get_all_filenames_fetchlog_result_2([{Node,{Success,FileInfo}}|Rest],Dbg,Accum)
+ when Success=/=error, list(FileInfo) ->
+ SubAccum=get_all_filenames_fetchlog_result_3(FileInfo,[]),
+ get_all_filenames_fetchlog_result_2(Rest,Dbg,[{Node,SubAccum}|Accum]);
+get_all_filenames_fetchlog_result_2([{Node,{error,FReason}}|Rest],Dbg,Accum) ->
+ inviso_tool_lib:debug(fetch_files,Dbg,[Node,FReason]),
+ get_all_filenames_fetchlog_result_2(Rest,Dbg,Accum);
+get_all_filenames_fetchlog_result_2([],_Dbg,Accum) ->
+ Accum.
+
+get_all_filenames_fetchlog_result_3([{FType,Files}|Rest],SubAccum) ->
+ FilesOnly=lists:foldl(fun({ok,FName},Acc)->[FName|Acc];(_,Acc)->Acc end,[],Files),
+ get_all_filenames_fetchlog_result_3(Rest,[{FType,FilesOnly}|SubAccum]);
+get_all_filenames_fetchlog_result_3([],SubAccum) ->
+ SubAccum.
+
+%% Help function which traverses a noderesult and builds a list as return
+%% value containing the nodenames of all nodes not being complete.
+%% Note that a node may occur multiple times since may have fetched logfiles
+%% for several tracerdata from the same node. Makes sure the list contains
+%% unique node names.
+%% Returns a list nodes.
+get_all_failednodes_fetchlog_result(NodeResult) ->
+ get_all_failednodes_fetchlog_result_2(NodeResult,[]).
+
+get_all_failednodes_fetchlog_result_2([{_Node,{complete,_}}|Rest],Acc) ->
+ get_all_failednodes_fetchlog_result_2(Rest,Acc);
+get_all_failednodes_fetchlog_result_2([{Node,{_Severity,_}}|Rest],Acc) ->
+ case lists:member(Node,Acc) of
+ true -> % Already in the list.
+ get_all_failednodes_fetchlog_result_2(Rest,Acc);
+ false -> % Not in Acc, add it!
+ get_all_failednodes_fetchlog_result_2(Rest,[Node|Acc])
+ end;
+get_all_failednodes_fetchlog_result_2([],Acc) ->
+ Acc.
+%% ------------------------------------------------------------------------------
+
+%% Help function which copies files from one location to Dir and at the same time
+%% adds the Prefix to the filename. NodeSpecs contains full path to the files. The
+%% reason the node information is still part of NodeSpecs is that otherwise we can
+%% not report faulty nodes. Note that one node may occur multiple times since there
+%% may be more than one tracerdata for a node.
+%% Returns {FailedNodes,Files} where FailedNodes is a list of nodes where problems
+%% occurred. Files is a tuple list of [{Node,[{FType,FileNames},...]},...].
+fetch_logfiles_copy(CtrlNode,Dir,Prefix,TRDstorage,Dbg,NodeSpecs) ->
+ CopySpecList=build_copylist(CtrlNode,Dbg,NodeSpecs,TRDstorage),
+ fetch_logfiles_copy_2(Dir,Prefix,Dbg,CopySpecList,[],[]).
+
+fetch_logfiles_copy_2(Dir,Prefix,Dbg,[{Node,CopySpecs}|Rest],FailedNodes,Files) ->
+ case fetch_logfiles_copy_3(Dir,Prefix,Dbg,CopySpecs,[],0) of
+ {0,LocalFiles} -> % Copy went ok and zero errors.
+ fetch_logfiles_copy_2(Dir,Prefix,Dbg,Rest,FailedNodes,[{Node,LocalFiles}|Files]);
+ {_N,LocalFiles} -> % Copied files, but some went wrong.
+ case lists:member(Node,FailedNodes) of
+ true -> % Node already in FailedNodes.
+ fetch_logfiles_copy_2(Dir,Prefix,Dbg,Rest,FailedNodes,
+ [{Node,LocalFiles}|Files]);
+ false -> % Node not marked as failed, yet.
+ fetch_logfiles_copy_2(Dir,Prefix,Dbg,Rest,[Node|FailedNodes],
+ [{Node,LocalFiles}|Files])
+ end
+ end;
+fetch_logfiles_copy_2(_,_,_,[],FailedNodes,Files) ->
+ {FailedNodes,Files}. % The return value from fetch_logfiles_copy.
+
+fetch_logfiles_copy_3(Dir,Prefix,Dbg,[{FType,RemoteFiles}|Rest],Results,Errors) ->
+ {Err,LocalFiles}=fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,RemoteFiles,[],0),
+ fetch_logfiles_copy_3(Dir,Prefix,Dbg,Rest,[{FType,LocalFiles}|Results],Errors+Err);
+fetch_logfiles_copy_3(_,_,_,[],Results,Errors) ->
+ {Errors,Results}.
+
+%% For each file of one file-type (e.g. trace_log).
+fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,[File|Rest],LocalFiles,Errors) ->
+ DestName=Prefix++filename:basename(File),
+ Destination=filename:join(Dir,DestName),
+ case do_copy_file(File,Destination) of
+ ok ->
+ fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,Rest,[DestName|LocalFiles],Errors);
+ {error,Reason} ->
+ inviso_tool_lib:debug(copy_files,Dbg,[File,Destination,Reason]),
+ fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,Rest,LocalFiles,Errors+1)
+ end;
+fetch_logfiles_copy_3_1(_,_,_,[],LocalFiles,Errors) ->
+ {Errors,LocalFiles}.
+
+%% Help function which builds a [{Node,[{Type,[ListOfRemoteFiles]}},...}]
+%% where Type describes trace_log or ti_log and each entry in ListOfRemoteFiles
+%% is a complete path to a file to be copied.
+build_copylist(CtrlNode,Dbg,NodeSpecList,TRDstorage) ->
+ build_copylist_2(CtrlNode,Dbg,NodeSpecList,TRDstorage,[]).
+
+%% For each node specified in the NodeSpecList.
+build_copylist_2(CtrlNode,Dbg,[{Node,SourceDir}|Rest],TRDstorage,Acc) ->
+ TRDlist=find_tracerdata_for_node_trd(Node,TRDstorage),
+ CopySpecList=build_copylist_3(CtrlNode,Dbg,SourceDir,Node,TRDlist),
+ build_copylist_2(CtrlNode,Dbg,Rest,TRDstorage,[CopySpecList|Acc]);
+build_copylist_2(_,_,[],_,Acc) ->
+ lists:flatten(Acc).
+
+%% For each tracerdata found for the node.
+build_copylist_3(void,Dbg,SourceDir,Node,[TRD|Rest]) -> % The non-distributed case.
+ case inviso:list_logs(TRD) of
+ {ok,FileSpec} when list(FileSpec) -> % [{trace_log,Dir,Files},...]
+ NewFileSpec=build_copylist_4(SourceDir,FileSpec,[]),
+ [{Node,NewFileSpec}|build_copylist_3(void,Dbg,SourceDir,Node,Rest)];
+ {ok,no_log} -> % This tracedata not associated with any log.
+ build_copylist_3(void,Dbg,SourceDir,Node,Rest);
+ {error,Reason} ->
+ inviso_tool_lib:debug(list_logs,Dbg,[Node,TRD,Reason]),
+ build_copylist_3(void,Dbg,SourceDir,Node,Rest)
+ end;
+build_copylist_3(CtrlNode,Dbg,SourceDir,Node,[TRD|Rest]) -> % The distributed case.
+ case inviso_tool_lib:inviso_cmd(CtrlNode,list_logs,[[{Node,TRD}]]) of
+ {ok,[{Node,{ok,FileSpec}}]} when list(FileSpec) ->
+ NewFileSpec=build_copylist_4(SourceDir,FileSpec,[]),
+ [{Node,NewFileSpec}|build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest)];
+ {ok,[{Node,{ok,no_log}}]} -> % It relays to another node, no files!
+ build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest);
+ {ok,[{Node,{error,Reason}}]} ->
+ inviso_tool_lib:debug(list_logs,Dbg,[Node,TRD,Reason]),
+ build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest);
+ {error,Reason} -> % Some general failure.
+ inviso_tool_lib:debug(list_logs,Dbg,[Node,TRD,Reason]),
+ build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest)
+ end;
+build_copylist_3(_,_,_,_,[]) ->
+ [].
+
+%% Help function which makes a [{Type,Files},...] list where each file in Files
+%% is with full path as found from our file-system.
+build_copylist_4(SourceDir,[{Type,_Dir,Files}|Rest],Accum) ->
+ NewFiles=
+ lists:foldl(fun(FName,LocalAcc)->[filename:join(SourceDir,FName)|LocalAcc] end,
+ [],
+ Files),
+ build_copylist_4(SourceDir,Rest,[{Type,NewFiles}|Accum]);
+build_copylist_4(_,[],Accum) ->
+ Accum.
+
+
+%% Help function which copies a file using os:cmd.
+%% Returns 'ok' or {error,Reason}.
+do_copy_file(Source,Destination) ->
+ case os:type() of
+ {win32,_} ->
+ os:cmd("copy "++Source++" "++Destination), % Perhaps a test on success?
+ ok;
+ {unix,_} ->
+ os:cmd("cp "++Source++" "++Destination), % Perhaps a test on success?
+ ok
+ end.
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+
+%% ==============================================================================
+%% Various help functions.
+%% ==============================================================================
+
+%% Help function going through the Nodes list and checking that only nodes
+%% mentioned in OurNodes gets returned. It also makes the nodes in the return
+%% value unique.
+remove_nodes_not_ours(Nodes,OurNodes) ->
+ remove_nodes_not_ours_2(Nodes,OurNodes,[],[]).
+
+remove_nodes_not_ours_2([Node|Rest],OurNodes,OurAcc,OtherAcc) ->
+ case lists:member(Node,OurNodes) of
+ true -> % Ok it is one of our nodes.
+ case lists:member(Node,OurAcc) of
+ true -> % Already in the list, skip.
+ remove_nodes_not_ours_2(Rest,OurNodes,OurAcc,OtherAcc);
+ false ->
+ remove_nodes_not_ours_2(Rest,OurNodes,[Node|OurAcc],OtherAcc)
+ end;
+ false ->
+ case lists:member(Node,OtherAcc) of
+ true ->
+ remove_nodes_not_ours_2(Rest,OurNodes,OurAcc,OtherAcc);
+ false ->
+ remove_nodes_not_ours_2(Rest,OurNodes,OurAcc,[Node|OtherAcc])
+ end
+ end;
+remove_nodes_not_ours_2([],_,OurAcc,OtherAcc) ->
+ {lists:reverse(OurAcc),lists:reverse(OtherAcc)}.
+%% ------------------------------------------------------------------------------
+
+%% Help function which returns 'true' or 'false' depending on if TracerData is
+%% meant to be used by the session handler (true) or if it supposed to be passed
+%% on to the trace system.
+is_tool_internal_tracerdata(_) -> % CURRENTLY NO INTERNAL TRACER DATA!
+ false.
+%% ------------------------------------------------------------------------------
+
+%% Help function which checks that all nodes in the first list of nodes exists
+%% in the second list of nodes. Returns 'true' or 'false'. The latter if as much
+%% as one incorrect node was found.
+check_our_nodes([Node|Rest],AllNodes) ->
+ case lists:member(Node,AllNodes) of
+ true ->
+ check_our_nodes(Rest,AllNodes);
+ false -> % Then we can stop right here.
+ false
+ end;
+check_our_nodes([],_) ->
+ true.
+%% ------------------------------------------------------------------------------
+
+%% Help function which checks that a directory actually exists. Returns 'true' or
+%% 'false'.
+check_directory_exists(Dir) ->
+ case file:read_file_info(Dir) of
+ {ok,#file_info{type=directory}} ->
+ true;
+ _ -> % In all other cases it is not valid.
+ false
+ end.
+%% ------------------------------------------------------------------------------
+
+%% This function stops the tracing on all nodes in Nodes. Preferably Nodes is a list
+%% of only tracing runtime components. Not that there will actually be any difference
+%% since the return value does not reflect how stopping the nodes went.
+%% Returns 'ok' or {error,Reason}, the latter only in case of general failure.
+stop_all_tracing(void,Dbg,[?LOCAL_RUNTIME]) -> % The non-distributed case, and is tracing.
+ case inviso:stop_tracing() of
+ {ok,_State} ->
+ ok;
+ {error,Reason} -> % We actually don't care.
+ inviso_tool_lib:debug(stop_tracing,Dbg,[?LOCAL_RUNTIME,Reason]),
+ ok
+ end;
+stop_all_tracing(void,_,_) -> % There is no local runtime started.
+ ok;
+stop_all_tracing(CtrlNode,Dbg,Nodes) ->
+ case inviso_tool_lib:inviso_cmd(CtrlNode,stop_tracing,[Nodes]) of
+ {ok,Result} -> % The result is only used for debug.
+ Failed=lists:foldl(fun({N,{error,Reason}},Acc)->[{N,{error,Reason}}|Acc];
+ (_,Acc)->Acc
+ end,
+ [],
+ Result),
+ if
+ Failed==[] ->
+ ok;
+ true ->
+ inviso_tool_lib:debug(stop_tracing,Dbg,[Nodes,Failed]),
+ ok
+ end;
+ {error,Reason} ->
+ {error,{stop_tracing,Reason}}
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Help function removing all local logs using the tracerdata to determine what
+%% logs to remove from where.
+%% There is no significant return value since it is not really clear what to do
+%% if removal went wrong. The function can make debug-reports thought.
+remove_all_local_logs(CtrlNode,TRDstorage,Nodes,Dbg) ->
+ LogSpecList=build_logspeclist_remove_logs(Nodes,TRDstorage),
+ case inviso_tool_lib:inviso_cmd(CtrlNode,delete_log,[LogSpecList]) of
+ {ok,Results} ->
+ case look_for_errors_resultlist(Results) of
+ [] -> % No errors found in the result!
+ true;
+ Errors ->
+ inviso_tool_lib:debug(remove_all_local_logs,Dbg,[Errors]),
+ true
+ end;
+ {error,Reason} -> % Some general error.
+ inviso_tool_lib:debug(remove_all_local_logs,Dbg,[{error,Reason}]),
+ true
+ end.
+
+%% Help function which puts together a list of {Node,Tracerdata} tuples. Note that
+%% we must build one tuple for each tracerdata for one node.
+build_logspeclist_remove_logs(Nodes,TRDstorage) ->
+ [{Node,TracerData}||Node<-Nodes,TracerData<-find_tracerdata_for_node_trd(Node,TRDstorage)].
+%% ------------------------------------------------------------------------------
+
+%% Help function which traverses a resultlist from an inviso function. Such are
+%% built up as [{Node,SubResults},...] where SubResult is a list of tuples for each
+%% file-type (e.g trace_log) {FType,FileList} where a FileList is either {error,Reason}
+%% or {ok,FileName}.
+%% Returns a list of {Node,[{error,Reason},...]}.
+look_for_errors_resultlist([{Node,{error,Reason}}|Rest]) ->
+ [{Node,{error,Reason}}|look_for_errors_resultlist(Rest)];
+look_for_errors_resultlist([{Node,{ok,NResults}}|Rest]) when list(NResults) ->
+ case look_for_errors_resultlist_2(NResults,[]) of
+ [] ->
+ look_for_errors_resultlist(Rest);
+ Errors -> % A list of lists.
+ [{Node,lists:flatten(Errors)}|look_for_errors_resultlist(Rest)]
+ end;
+look_for_errors_resultlist([_|Rest]) ->
+ look_for_errors_resultlist(Rest);
+look_for_errors_resultlist([]) ->
+ [].
+
+look_for_errors_resultlist_2([{_FType,NSubResult}|Rest],Accum) ->
+ case lists:filter(fun({error,_Reason})->true;(_)->false end,NSubResult) of
+ [] -> % No errors for this node.
+ look_for_errors_resultlist_2(Rest,Accum);
+ Errors -> % A list of at least one error.
+ look_for_errors_resultlist_2(Rest,[Errors|Accum])
+ end;
+look_for_errors_resultlist_2([],Accum) ->
+ Accum.
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Functions working on the loopdata structure.
+%% Its main purpose is to store information about runtime components participating
+%% in the session and their current status.
+%% ------------------------------------------------------------------------------
+
+-record(ld,{parent,
+ ctrlnode,
+ ctrlpid, % To where to send inviso cmd.
+ rtstates,
+ tracerdata,
+ safetycatches,
+ dbg,
+ actstorage % Activity storage, for reactivate.
+ }).
+
+%% Function creating the initial datastructure.
+%% The datastructure is [{Node,State},...].
+%%
+%% The tracerdata table is a bag simply for the reason that if we try to insert
+%% the same tracerdata for a node twice, we will end up with one tracerdata after
+%% all. This is useful when we insert tracerdata ourselves, the tracerdata will
+%% come as a state-change too.
+mk_ld(Parent,CtrlNode,CtrlPid,RTStates,NodeParams,OtherNodes,SafetyCatches,Dbg) ->
+ TRDtableName=list_to_atom("inviso_tool_sh_trdstorage_"++pid_to_list(self())),
+ TRDtid=ets:new(TRDtableName,[bag]),
+ ACTtableName=list_to_atom("inviso_tool_sh_actstorage_"++pid_to_list(self())),
+ ACTtid=ets:new(ACTtableName,[bag]),
+ mk_ld_fill_tracerdata(CtrlNode,TRDtid,NodeParams,OtherNodes), % Fill the ETS table.
+ #ld{parent=Parent, % The tool main process.
+ ctrlnode=CtrlNode, % Node name where the control component is.
+ ctrlpid=CtrlPid, % The process id of the control component.
+ rtstates=RTStates, % All nodes and their state/status.
+ tracerdata=TRDtid,
+ safetycatches=SafetyCatches,
+ dbg=Dbg,
+ actstorage=ACTtid
+ }.
+
+%% Help function which inserts tracer data for the nodes. Note that we can get
+%% tracer data either from the return value from init_tracing or by asking the
+%% node for it. The latter is necessary for the nodes which were marked not to
+%% be initiated by the session handler. This maybe because those nodes have
+%% autostarted.
+mk_ld_fill_tracerdata(CtrlNode,TId,NodeParams,OtherNodes) ->
+ mk_ld_fill_tracerdata_nodeparams(TId,NodeParams),
+ mk_ld_fill_tracerdata_othernodes(CtrlNode,TId,OtherNodes).
+
+mk_ld_fill_tracerdata_nodeparams(TId,[{Node,TracerData}|Rest]) ->
+ ets:insert(TId,{Node,TracerData}),
+ mk_ld_fill_tracerdata_nodeparams(TId,Rest);
+mk_ld_fill_tracerdata_nodeparams(_,[]) ->
+ ok.
+
+mk_ld_fill_tracerdata_othernodes(_,_,[]) -> % Then not necessary to do anything.
+ ok;
+mk_ld_fill_tracerdata_othernodes(void,TId,[Node]) -> % The non-distributed case.
+ case inviso:get_tracerdata() of
+ {error,_Reason} -> % Perhaps in state new or disconnected.
+ ok; % Do nothing.
+ {ok,TracerData} ->
+ ets:insert(TId,{Node,TracerData})
+ end;
+mk_ld_fill_tracerdata_othernodes(CtrlNode,TId,Nodes) ->
+ case inviso_tool_lib:invisomd(CtrlNode,get_tracerdata,[Nodes]) of
+ {ok,Results} ->
+ mk_ld_fill_tracerdata_othernodes_2(TId,Results);
+ {error,_Reason} -> % Strange, we will probably crash later.
+ ok
+ end.
+
+mk_ld_fill_tracerdata_othernodes_2(TId,[{_Node,{ok,no_tracerdata}}|Rest]) ->
+ mk_ld_fill_tracerdata_othernodes_2(TId,Rest); % It was not initiated then!
+mk_ld_fill_tracerdata_othernodes_2(TId,[{Node,{ok,TracerData}}|Rest]) ->
+ ets:insert(TId,{Node,TracerData}),
+ mk_ld_fill_tracerdata_othernodes_2(TId,Rest);
+mk_ld_fill_tracerdata_othernodes_2(_,[]) ->
+ ok.
+%% ------------------------------------------------------------------------------
+
+get_ctrlnode_ld(#ld{ctrlnode=CtrlNode}) ->
+ CtrlNode.
+%% ------------------------------------------------------------------------------
+
+
+get_ctrlpid_ld(#ld{ctrlpid=CtrlPid}) ->
+ CtrlPid.
+%% ------------------------------------------------------------------------------
+
+get_rtstates_ld(#ld{rtstates=RTStates}) ->
+ RTStates.
+
+put_rtstates_ld(NewRTStates,LD) ->
+ LD#ld{rtstates=NewRTStates}.
+%% ------------------------------------------------------------------------------
+
+get_trdstorage_ld(#ld{tracerdata=TId}) ->
+ TId.
+
+put_trdstorage_ld(_NewTId,LD) ->
+ LD.
+%% ------------------------------------------------------------------------------
+
+%% Help function which adds the current tracerdata of node Node to the tracerdata
+%% storage. We only want to add tracerdata we have not seen before. We therefore
+%% avoid adding it if the node already is in state ?TRACING.
+%% Returns a new tracerdata (what ever it is)!
+add_current_tracerdata_ld(CtrlNode,Node,RTStates,TId) ->
+ case get_statestatus_rtstates(Node,RTStates) of
+ {ok,{?TRACING,_}} -> % Then we have already added the tracerdata.
+ TId; % Then do nothing.
+ {ok,_} -> % Since we were not tracing before.
+ case add_current_tracerdata_ld_fetchtracerdata(CtrlNode,Node) of
+ {ok,TracerData} ->
+ ets:insert(TId,{Node,TracerData});
+ no_tracerdata -> % Strange, how could we become tracing
+ ok;
+ {error,_Reason} -> % The node perhaps disconnected!?
+ ok
+ end;
+ false -> % Very strange, not our node!
+ ok % Do nothing.
+ end.
+
+add_current_tracerdata_ld_fetchtracerdata(void,_Node) ->
+ case inviso:get_tracerdata() of
+ {ok,TracerData} ->
+ {ok,TracerData};
+ {error,no_tracerdata} ->
+ no_tracerdata;
+ {error,Reason} ->
+ {error,Reason}
+ end;
+add_current_tracerdata_ld_fetchtracerdata(CtrlNode,Node) ->
+ case inviso_tool_lib:inviso_cmd(CtrlNode,get_tracerdata,[[Node]]) of
+ {ok,[{Node,{ok,TracerData}}]} ->
+ {ok,TracerData};
+ {ok,[{Node,{error,no_tracerdata}}]} ->
+ no_tracerdata;
+ {ok,[{Node,{error,Reason}}]} ->
+ {error,Reason};
+ {error,Reason} ->
+ {error,Reason}
+ end.
+%% ------------------------------------------------------------------------------
+
+
+get_safetycatches_ld(#ld{safetycatches=SCs}) ->
+ SCs.
+%% ------------------------------------------------------------------------------
+
+get_dbg_ld(#ld{dbg=Dbg}) ->
+ Dbg.
+%% ------------------------------------------------------------------------------
+
+get_actstorage_ld(#ld{actstorage=ACTstorage}) ->
+ ACTstorage.
+
+put_actstorage_ld(_NewACTstorage,LD) ->
+ LD.
+%% ------------------------------------------------------------------------------
+
+
+
+%% ------------------------------------------------------------------------------
+%% Functions working on the rtstates structure (which is a substructure of loopdata).
+%% It is either:
+%% [{Node,StateStatus,Opts},...]
+%% Node is either the node name of the runtime component erlang node or
+%% ?LOCAL_RUNTIME as returned from the trace control component.
+%% StateStatus is {State,Status}, 'unavailable' or 'unknown'.
+%% Status is the returnvalue from trace control component.
+%% i.e: running | {suspended,Reason}
+%% ------------------------------------------------------------------------------
+
+%% Function contructing an rtstates structure from a list of [{Node,StateStatus,Opts},...].
+to_rtstates(ListOfStates) when list(ListOfStates) ->
+ ListOfStates.
+%% ------------------------------------------------------------------------------
+
+%% Function which takes a rtstates structure and returns a list of [{Node,StateStatus},...].
+from_rtstates(RTStates) ->
+ RTStates.
+%% ------------------------------------------------------------------------------
+
+%% Function which takes an rtstates structure and a result as returned from
+%% init_tracing. The RTStates is modified for the nodes that changed state as a
+%% result of successful init_tracing.
+%% Returns a new RTStates.
+set_tracing_rtstates([E={Node,_StateStatus,Opts}|Rest],Result) ->
+ case lists:keysearch(Node,1,Result) of
+ {value,{_,ok}} -> % Means state-change to tracing!
+ [{Node,{tracing,running},Opts}|set_tracing_rtstates(Rest,Result)];
+ _ -> % Otherwise, leave it as is.
+ [E|set_tracing_rtstates(Rest,Result)]
+ end;
+set_tracing_rtstates([],_Result) ->
+ [].
+%% ------------------------------------------------------------------------------
+
+%% Function updating the state/status for a certain runtime component.
+%% Returns a new RTStates structure. Note that Node must not necessarily be one
+%% of the nodes in the session. Meaning that Node shall not be added to RTStates
+%% should it not already be in there.
+statechange_rtstates(Node,State,Status,RTStates) when list(RTStates) ->
+ case lists:keysearch(Node,1,RTStates) of
+ {value,{_,_,Opts}} ->
+ lists:keyreplace(Node,1,RTStates,{Node,{State,Status},Opts});
+ _ -> % Then Node does not exist.
+ RTStates % Just keep it as is, as keyreplace would have done.
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Function updating the state/status for a certain runtime component. The
+%% state/status is set to 'unavailable'.
+%% Returns a new RTStates structure.
+set_unavailable_rtstates(Node,RTStates) when list(RTStates) ->
+ case lists:keysearch(Node,1,RTStates) of
+ {value,{_,_,Opts}} ->
+ lists:keyreplace(Node,1,RTStates,{Node,unavailable,Opts});
+ _ -> % Then Node does not exist.
+ RTStates % Just keep it as is, as keyreplace would have done.
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Function finding the statestatus associated with Node in the RTStates structure.
+%% Returns {ok,StateStatus} or 'false'.
+get_statestatus_rtstates(Node,RTStates) ->
+ case lists:keysearch(Node,1,RTStates) of
+ {value,{_,StateStatus,_}} ->
+ {ok,StateStatus};
+ false ->
+ false
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Help function which returns a list of all nodes that are currently marked
+%% as available to us in the runtime state structure.
+get_all_available_nodes_rtstates(RTStates) ->
+ get_all_session_nodes_rtstates(lists:filter(fun({_N,unavailable,_})->false;
+ (_)->true
+ end,
+ RTStates)).
+%% ------------------------------------------------------------------------------
+
+%% Help function returning a list of all nodes belonging to this session.
+get_all_session_nodes_rtstates(RTStates) ->
+ lists:map(fun({Node,_,_})->Node end,RTStates).
+%% ------------------------------------------------------------------------------
+
+%% Function which returns a list of nodes that are indicated as tracing in the
+%% RTStates structure.
+get_all_tracing_nodes_rtstates(RTStates) ->
+ lists:map(fun({N,_,_})->N end,
+ lists:filter(fun({_,{tracing,_},_})->true;(_)->false end,RTStates)).
+%% ------------------------------------------------------------------------------
+
+%% Returns the options associated with Node in the RTStates structure.
+get_opts_rtstates(Node,RTStates) ->
+ case lists:keysearch(Node,1,RTStates) of
+ {value,{_,_,Opts}} ->
+ {ok,Opts};
+ false ->
+ false
+ end.
+
+%% ------------------------------------------------------------------------------
+%% Functions working on the tracerdata structure, which is a part of the loopdata.
+%% The tracerdata structure is an ETS-table of type bag storing:
+%% {Node,TracerData}.
+%% Note that there can of course be multiple entries for a node.
+%% ------------------------------------------------------------------------------
+
+%% Help function which takes a tracerdata loopdata structure and returns a list
+%% of all stored tracerdata for a certain Node.
+find_tracerdata_for_node_trd(Node,TRD) ->
+ case ets:lookup(TRD,Node) of
+ Result when list(Result) ->
+ lists:map(fun({_Node,TracerData})->TracerData end,Result);
+ _ -> % Should probably never happend.
+ []
+ end.
+%% ------------------------------------------------------------------------------
+
+
+%% ------------------------------------------------------------------------------
+%% Functions working on the activity storage structure, which is part of the
+%% loopdata. It stores entries about things that needs to be "redone" in case
+%% of a reactivation of the node. The time order is also important.
+%% Note that for every ActivityType there must be a "handler" in the reactivation
+%% functionality.
+%%
+%% The structure is a bag of {Node,ActivityType,What}.
+%% ActivityType/What=tf/{Op,TraceConfList}|tpm/{Op,[Mod,Func,Arity,MS,CallFunc]}
+%% /{Op,[Mod,Func,Arity,MS,CallFunc,ReturnFunc]}
+%% /{Op,[]}
+%% TraceConfList=[{Proc,Flags},...]
+%% How=true|false
+%% ------------------------------------------------------------------------------
+
+%% Function that adds meta-pattern activities to the activity storage. Note
+%% that one of the parameters to the function is a return value from an
+%% inviso call. In that way we do not enter activities that were unsuccessful.
+%% Op can be either the setting or clearing of a meta pattern.
+%% Returns a new ACTstorage.
+add_tpm_actstorage([{Node,ok}|Rest],Op,InvisoCmdParams,ACTstorage) ->
+ true=ets:insert(ACTstorage,{Node,tpm,{Op,InvisoCmdParams}}),
+ add_tpm_actstorage(Rest,Op,InvisoCmdParams,ACTstorage);
+add_tpm_actstorage([_|Rest],Op,InvisoCmdParams,ACTstorage) ->
+ add_tpm_actstorage(Rest,Op,InvisoCmdParams,ACTstorage);
+add_tpm_actstorage([],_,_,ACTstorage) ->
+ ACTstorage.
+
+%% Function that adds process trace-flags to the activity storage. Note that one
+%% of the parameters is the return value from an inviso function. Meaning that
+%% if the flags failed in their entirety, no activity will be saved. If only
+%% some of the flags failed, we will not go through the effort of trying to find
+%% out exactly which.
+%% Returns a new activity storage structure.
+add_tf_actstorage([{_Node,{error,_Reason}}|Rest],Op,TraceConfList,ACTstorage) ->
+ add_tf_actstorage(Rest,Op,TraceConfList,ACTstorage);
+add_tf_actstorage([{Node,_Result}|Rest],Op,TraceConfList,ACTstorage) ->
+ true=ets:insert(ACTstorage,{Node,tf,{Op,TraceConfList}}),
+ add_tf_actstorage(Rest,Op,TraceConfList,ACTstorage);
+add_tf_actstorage([],_,_,ACTstorage) ->
+ ACTstorage.
+%% ------------------------------------------------------------------------------
+
+%% Finds all activities associated with Node. Returns a list of them in the
+%% same order as they were inserted.
+get_activities_actstorage(Node,ACTstorage) ->
+ case ets:lookup(ACTstorage,Node) of
+ [] ->
+ false;
+ Result when list(Result) ->
+ {ok,lists:map(fun({_N,Type,What})->{Type,What} end,Result)}
+ end.
+%% ------------------------------------------------------------------------------
+
+%% Function removing all activity entries associated with Node. This is useful
+%% if the Node disconnects for instance.
+del_node_actstorage(Node,ACTstorage) ->
+ ets:delete(ACTstorage,Node),
+ ACTstorage.
+%% ------------------------------------------------------------------------------
+
|