%% %CopyrightBegin% %% %% Copyright Ericsson AB 2005-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% Description: %% The inviso_tool implementation. A tool that uses inviso. %% %% Authors: %% Lennart Öhman, lennart.ohman@st.se %% ----------------------------------------------------------------------------- -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 is_atom(Node) -> reconnect_nodes([Node]); reconnect_nodes(Nodes) when is_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 is_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 is_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}; is_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 is_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 is_list(MoreTDGargs) -> case is_tracing(LD#ld.session_state) of false -> case catch make_absolute_path(FileName,LD#ld.dir) of AbsFileName when is_list(AbsFileName) -> case file:read_file(AbsFileName) of {ok,Bin} -> if is_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 is_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 is_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 is_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 is_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 is_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}) -> {ok,ASD} = build_autostart_data(lists:keysort(2,get_loglist_chl(CHL)),LD#ld.tc_dict), 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}; 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 is_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 is_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 is_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 is_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) -> {ok,ProcH} = exec_trace_case_on(CNode,TraceCase,Bindings,Nodes), %% Trace cases have no return values. NewCHL=set_activating_chl(TC,Id,CHL,Bindings,ProcH), {ok,LD#ld{chl=NewCHL}}. %% ----------------------------------------------------------------------------- %% ----------------------------------------------------------------------------- %% 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 is_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 is_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 is_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 is_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 is_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 is_atom(M) -> [E|expand_module_regexps_tp(Rest,RegExpNode,Nodes)]; expand_module_regexps_tp([{M,F,Arity,MS,Opts}|Rest],RegExpNode,Nodes) when is_list(M);is_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 is_atom(M) -> [E|expand_module_regexps_ctp(Rest,RegExpNode,Nodes)]; expand_module_regexps_ctp([{M,F,Arity}|Rest],RegExpNode,Nodes) when is_list(M);is_tuple(M) -> case inviso_tool_lib:expand_module_names([RegExpNode], M, [{expand_only_at,RegExpNode}]) of {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 reactivator_skip_log_entries(Rest,C1); reactivator_skip_log_entries([{_,C}|Rest],C1) when C 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 = {error,_Reason} -> % Problem finding/opening file. Error 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 is_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 is_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) -> LD#ld{ nodes = case mk_nodes(proplists:get_value(nodes,Terms,LD#ld.nodes)) of {ok,Nodes} -> Nodes; _ -> LD#ld.nodes end, c_node = proplists:get_value(c_node,Terms,LD#ld.c_node), % atom8) regexp_node = proplists:get_value(regexp_node,Terms,LD#ld.regexp_node), % atom() tc_def_file = proplists:get_value(tc_def_file,Terms,LD#ld.tc_def_file), tdg = proplists:get_value(tdg,Terms,LD#ld.tdg), debug = proplists:get_value(debug,Terms,LD#ld.debug), initial_tcs = proplists:get_value(initial_tcs,Terms,LD#ld.initial_tcs), dir = proplists:get_value(dir,Terms,LD#ld.dir), optg = proplists:get_value(optg,Terms,LD#ld.optg) }. %% ----------------------------------------------------------------------------- %% 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 is_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 is_list(Nodes) -> {ok,lists:map(fun(N) when is_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. {{up,{tracing,running}},local_runtime}; 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 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; 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. %% ----------------------------------------------------------------------------- %% 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 is_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 is_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]. %% ----------------------------------------------------------------------------- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%