%% %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 runtime component of the trace tool Inviso.
%%
%% Authors:
%% Lennart �hman, lennart.ohman@st.se
%% -----------------------------------------------------------------------------
-module(inviso_tool_sh).
%% Inviso Session Handler.
%% This is the code for the session handler process. Its purpose is that we have
%% one session handler process for each trace session started through the
%% start_session inviso tool API. The session handler process is responsible for:
%%
%% -Knowing the state/status of all participating runtime components.
%% -Keeping storage of all tracerdata all our participants have used. This means
%% also to find out the tracerdata of runtime components connecting by them
%% selves.
%%
%% STORAGE STRATEGY
%% ----------------
%% The local information storage can be changed by two things. Either by executing
%% commands issued through our APIs. Or by receiving trace_event from the control
%% component. When we execute commands, a corresponding event will also follow.
%% Meaning that in those situations we are informed twice.
%% A simple strategy could be to wait for the event even when doing the changes
%% to the runtime components our self (through commands). But that may result in
%% a small time frame where someone might do yet another command and failing
%% because the local information storage is not uptodate as it would have been
%% expected to be. Therefore we always update the local storage when making changes
%% to a runtime component our selves. There will eventually be a double update
%% through an incoming event. But the storage must coop with that, preventing
%% inconsitancies to happend. An example of a strategy is that the tracerdata table
%% is a bag, not allowing for double entries of the same kind. Therefore a double
%% update is harmless there.
%% ------------------------------------------------------------------------------
%% Module wide constants.
%% ------------------------------------------------------------------------------
-define(LOCAL_RUNTIME,local_runtime). % Used as node name when non-disitrbuted.
-define(TRACING,tracing). % A state defined by the control component.
-define(RUNNING,running). % A status according to control componet.
-define(COPY_LOG_FROM,copy_log_from). % Common fileystem option.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% API exports.
%% ------------------------------------------------------------------------------
-export([start_link/5,start_link/8]).
-export([cancel_session/1,stop_session/3]).
-export([reactivate/1,reactivate/2]).
-export([ctpl/5,tpl/5,tpl/6,tpl/7,
tf/2,tf/3,
tpm_localnames/2,init_tpm/6,init_tpm/9,tpm/6,tpm/7,tpm/10,
tpm_ms/7,ctpm_ms/6,ctpm/5
]).
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Internal exports.
%% ------------------------------------------------------------------------------
-export([init/1,handle_call/3,handle_info/2,terminate/2]).
-export([get_loopdata/1]).
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Includes.
%% ------------------------------------------------------------------------------
-include_lib("kernel/include/file.hrl"). % Necessary for file module.
%% ------------------------------------------------------------------------------
%% ==============================================================================
%% Exported API functions.
%% ==============================================================================
%% start_link(From,NodeParams,CtrlNode,CtrlPid,SafetyCatches,NodesIn,NodesNotIn) =
%% {ok,Pid} | {error,Reason}
%% From= pid(), the initial client expecting the reply.
%% NodeParams=[{Node,TracerData},{Node,TracerData,Opts}...]
%% CtrlNode=atom() | 'void', the node where the trace control component is.
%% CtrlPid=pid(), the pid of the trace control component.
%% SafetyCatches=
%% Dir=string(), where to place fetched logs and the merged log.
%% Dbg=debug structure.
%% NodesIn=[Node,...], list of nodes already in another session.
%% NodesNotIn=[Node,...], list of nodes not in another session.
%%
%% Starts a session-handler. It keeps track of the the state and status of all
%% participating runtime components. Note that there is a non-distributed case too.
%% In the non-distributed case there is no things such as CtrlNode.
start_link(From,TracerData,CtrlPid,SafetyCatches,Dbg) ->
gen_server:start_link(?MODULE,
{self(),From,TracerData,CtrlPid,SafetyCatches,Dbg},
[]).
start_link(From,NodeParams,CtrlNode,CtrlPid,SafetyCatches,Dbg,NodesIn,NodesNotIn) ->
gen_server:start_link(?MODULE,
{self(),From,NodeParams,CtrlNode,CtrlPid,
SafetyCatches,Dbg,NodesIn,NodesNotIn},
[]).
%% ------------------------------------------------------------------------------
%% Stops tracing where it is ongoing. Fetches all logfiles.
stop_session(SID,Dir,Prefix) ->
gen_server:call(SID,{stop_session,Dir,Prefix}).
%% ------------------------------------------------------------------------------
%% stop_session(SID) = ok
%%
%% Cancels the session brutaly. All runtime components are made to stop tracing,
%% all local log files are removed using the tracerdata we know for them.
cancel_session(SID) ->
gen_server:call(SID,cancel_session).
%% ------------------------------------------------------------------------------
%% reactivate(SID) = {ok,
%% reactivate(SID,Nodes) = {ok,NodeResults} | {error,Reason}.
%% SID=session id, pid().
%% Nodes=[Node,...]
%% NodeResult=[{Node,Result},...]
%% Result={Good,Bad}
%% Good,Bad=integer(), the number of redone activities.
%%
%% Function which reactivates runtime components being suspended. This is done
%% replaying all trace flags (in the correct order) to the corresponding nodes.
%% Note that this may also mean turning flags off. Like first turning them on
%% then off a split second later.
reactivate(SID) ->
gen_server:call(SID,reactivate). %% NOT IMPLEMENTED YET.
reactivate(SID,Nodes) ->
gen_server:call(SID,{reactivate,Nodes}).
%% ------------------------------------------------------------------------------
%% tpl(SessionID,Mod,Func,Arity,MS)=
%% tpl(SessionID,Mod,Func,Arity,MS,Opts)={ok,N}|{error,Reason}.
%% tpl(SessionID,Nodes,Mod,Func,Arity,MS)=
%% tpl(SessionID,Nodes,Mod,Func,Arity,MS,Opts)={ok,Result}|{error,Reason}
%% Mod='_' | ModuleName | ModRegExp | {DirRegExp,ModRegExp}
%% ModRegExp=DirRegExp= string()
%% Func='_' | FunctionName
%% Arity='_' | integer()
%% MS=[] | false | a match specification
%% Opts=[Opts,...]
%% Opt={arg,Arg}, disable_safety, {expand_regexp_at,NodeName}, only_loaded
%% Nodes=[NodeName,...]
tpl(SID,Mod,Func,Arity,MS) ->
gen_server:call(SID,{tp,tpl,Mod,Func,Arity,MS,[]}).
tpl(SID,Mod,Func,Arity,MS,Opts) when list(MS);MS==true;MS==false ->
gen_server:call(SID,{tp,tpl,Mod,Func,Arity,MS,Opts});
tpl(SID,Nodes,Mod,Func,Arity,MS) when integer(Arity);Arity=='_' ->
gen_server:call(SID,{tp,tpl,Nodes,Mod,Func,Arity,MS,[]}).
tpl(SID,Nodes,Mod,Func,Arity,MS,Opts) ->
gen_server:call(SID,{tp,tpl,Nodes,Mod,Func,Arity,MS,Opts}).
%% ------------------------------------------------------------------------------
%% ctpl(SessionID,Nodes,Mod,Func,Arity)=
%% See tpl/X for arguments.
%%
%% Removes local trace-patterns from functions.
ctpl(SID,Nodes,Mod,Func,Arity) ->
gen_server:call(SID,{ctp,ctpl,Nodes,Mod,Func,Arity}).
%% ------------------------------------------------------------------------------
tpm_localnames(SID,Nodes) ->
gen_server:call(SID,{tpm_localnames,Nodes}).
%% tpm_globalnames(SID,Nodes) ->
%% gen_server:call(SID,{tpm_globalnames,Nodes}).
init_tpm(SID,Nodes,Mod,Func,Arity,CallFunc) ->
gen_server:call(SID,{init_tpm,Nodes,Mod,Func,Arity,CallFunc}).
init_tpm(SID,Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
gen_server:call(SID,
{init_tpm,Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc}).
tpm(SID,Nodes,Mod,Func,Arity,MS) ->
gen_server:call(SID,{tpm,Nodes,Mod,Func,Arity,MS}).
tpm(SID,Nodes,Mod,Func,Arity,MS,CallFunc) ->
gen_server:call(SID,{tpm,Nodes,Mod,Func,Arity,MS,CallFunc}).
tpm(SID,Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) ->
gen_server:call(SID,{tpm,Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc}).
tpm_ms(SID,Nodes,Mod,Func,Arity,MSname,MS) ->
gen_server:call(SID,{tpm_ms,Nodes,Mod,Func,Arity,MSname,MS}).
ctpm_ms(SID,Nodes,Mod,Func,Arity,MSname) ->
gen_server:call(SID,{tpm_ms,Nodes,Mod,Func,Arity,MSname}).
ctpm(SID,Nodes,Mod,Func,Arity) ->
gen_server:call(SID,{ctpm,Nodes,Mod,Func,Arity}).
%% ------------------------------------------------------------------------------
%% tf(SessionID,Nodes,TraceConfList)=
%% TraceConfList=[{PidSpec,Flags},...]
%% PidSpec=pid()|atom()|all|new|existing
%% Flags=[Flag,...]
tf(SID,TraceConfList) ->
gen_server:call(SID,{tf,TraceConfList}).
tf(SID,Nodes,TraceConfList) ->
gen_server:call(SID,{tf,Nodes,TraceConfList}).
%% ------------------------------------------------------------------------------
get_loopdata(SID) ->
gen_server:call(SID,get_loopdata).
%% ------------------------------------------------------------------------------
%% ==============================================================================
%% Genserver call-backs.
%% ==============================================================================
%% Initial function for the session handler process. The nodes participating in
%% the session must previously have been added to our control component by the tool.
%% The session handler first finds out the state/status of the specified runtime
%% components, then it tries to initiate tracing on those where it is applicable.
%% Note that a reply to the initial (tool)client is done from here instead from
%% the tool-server.
init({Parent,From,TracerData,CtrlPid,SafetyCatches,Dbg}) -> % The non-distributed case.
{ok,StateStatus}=init_rtcomponent_states([],void,CtrlPid,[?LOCAL_RUNTIME]),
case is_tool_internal_tracerdata(TracerData) of
false -> % We shall initiate local runtime.
case inviso:init_tracing(TracerData) of
ok ->
gen_server:reply(From,{ok,{self(),ok}}),
{ok,mk_ld(Parent,
void,
CtrlPid,
to_rtstates([{?LOCAL_RUNTIME,{tracing,?RUNNING},[]}]),
[{?LOCAL_RUNTIME,TracerData}],
[],
SafetyCatches,
Dbg)};
{error,Reason} -> % It might have become suspended?!
gen_server:reply(From,{error,Reason}),
{ok,mk_ld(Parent,
void,
CtrlPid,
to_rtstates([{?LOCAL_RUNTIME,StateStatus,[]}]),
[{?LOCAL_RUNTIME,TracerData}],
[],
SafetyCatches,
Dbg)}
end;
true -> % We shall not pass this one on.
gen_server:reply(From,{ok,{self(),ok}}), % Then it is ok.
{ok,mk_ld(Parent,
void,
CtrlPid,
to_rtstates([{?LOCAL_RUNTIME,StateStatus,[]}]),
[],
[?LOCAL_RUNTIME],
SafetyCatches,
Dbg)}
end;
init({Parent,From,NodeParams,CtrlNode,CtrlPid,SafetyCatches,Dbg,NodesIn,NodesNotIn}) ->
case init_rtcomponent_states(NodeParams,CtrlNode,CtrlPid,NodesNotIn) of
{ok,States} -> % A list of {Node,{State,Status},Opts}.
{NodeParams2,Nodes2}=remove_nodeparams(NodesIn,NodeParams),
case inviso_tool_lib:inviso_cmd(CtrlNode,init_tracing,[NodeParams2]) of
{ok,Result} -> % Resulted in state changes!
RTStates=set_tracing_rtstates(to_rtstates(States),Result),
ReplyValue=init_fix_resultnodes(NodesIn,Nodes2,Result),
gen_server:reply(From,{ok,{self(),ReplyValue}}),
{ok,mk_ld(Parent,CtrlNode,CtrlPid,RTStates,
NodeParams2,Nodes2,SafetyCatches,Dbg)};
{error,Reason} -> % Some general failure.
inviso_tool_lib:inviso_cmd(CtrlNode,unsubscribe,[]),
gen_server:reply(From,{error,{init_tracing,Reason}}),
{stop,{init_tracing,Reason}};
What ->
io:format("GOT:~n~w~n",[What]),
exit(foo)
end;
{error,Reason} -> % Unable to get the state/status.
inviso_tool_lib:inviso_cmd(CtrlNode,unsubscribe,[]),
gen_server:reply(From,{error,Reason}),
{stop,{error,Reason}};
What ->
io:format("GOT:~n~w~n",[What]),
exit(foo)
end.
%% ------------------------------------------------------------------------------
%% To stop a session means stop the tracing and remove all local files on the
%% runtime nodes. We do have a table with all tracer data and that is how we are
%% going to recreate what files to remove.
%% Since runtime components may actually change state when this procedure is
%% on-going, we do not care! It is the state in the session handling process at
%% the time of start of this procedure which is used.
handle_call(cancel_session,_From,LD) ->
CtrlNode=get_ctrlnode_ld(LD),
RTStates=get_rtstates_ld(LD),
Dbg=get_dbg_ld(LD),
TracingNodes=get_all_tracing_nodes_rtstates(RTStates),
case stop_all_tracing(CtrlNode,Dbg,TracingNodes) of
ok-> % Hopefully all nodes are stopped now.
AvailableNodes=get_all_available_nodes_rtstates(RTStates),
TRDstorage=get_trdstorage_ld(LD),
remove_all_local_logs(CtrlNode,TRDstorage,AvailableNodes,Dbg),
{stop,normal,ok,LD}; % LD actually not correct now!
{error,Reason} -> % Some serious error when stop_tracing.
{stop,normal,{error,Reason},LD}
end;
%% ------------------------------------------------------------------------------
%% *Stop all tracing on runtime components still tracing.
%% *Copy all local log files to the collection directory.
handle_call({stop_session,Dir,Prefix},_From,LD) ->
case check_directory_exists(Dir) of % Check that this directory exists here.
true ->
RTStates=get_rtstates_ld(LD),
CtrlNode=get_ctrlnode_ld(LD),
Dbg=get_dbg_ld(LD),
TracingNodes=get_all_tracing_nodes_rtstates(RTStates),
case stop_all_tracing(CtrlNode,Dbg,TracingNodes) of
ok -> % Hopefully no node is still tracing now.
TRDstorage=get_trdstorage_ld(LD),
AvailableNodes=get_all_available_nodes_rtstates(RTStates),
{FailedNodes,FetchedFiles}=
transfer_logfiles(RTStates,CtrlNode,Dir,Prefix,
TRDstorage,Dbg,AvailableNodes),
RemoveNodes= % We only delete local logs where fetch ok.
lists:filter(fun(N)->
case lists:keysearch(N,1,FailedNodes) of
{value,_} ->
false;
false ->
true
end
end,
AvailableNodes),
remove_all_local_logs(CtrlNode,TRDstorage,RemoveNodes,Dbg),
{stop,normal,{ok,{FailedNodes,FetchedFiles}},LD};
{error,Reason} -> % Some general failure, quit.
{stop,normal,{error,Reason},LD}
end;
false -> % You specified a non-existing directory!
{reply,{error,{faulty_dir,Dir}},LD}
end;
%% ------------------------------------------------------------------------------
handle_call({reactivate,Nodes},_From,LD) ->
RTStates=get_rtstates_ld(LD),
{OurNodes,OtherNodes}=
remove_nodes_not_ours(Nodes,get_all_session_nodes_rtstates(RTStates)),
CtrlNode=get_ctrlnode_ld(LD),
ACTstorage=get_actstorage_ld(LD),
case h_reactivate(CtrlNode,OurNodes,ACTstorage) of
{ok,Results} -> % A list of {Node,Result}.
if
OtherNodes==[] -> % Normal case, no non-session nodes.
{reply,{ok,Results},LD};
true -> % Add error values for non-session nodes.
{reply,
{ok,
lists:map(fun(N)->{N,{error,not_in_session}} end,OtherNodes)++
Results},
LD}
end;
{error,Reason} -> % Then this error takes presidence.
{reply,{error,Reason},LD}
end;
%% ------------------------------------------------------------------------------
%% Call-back for set trace-pattern for both global and local functions.
handle_call({tp,PatternFunc,Mod,F,A,MS,Opts},_From,LD) ->
Reply=h_tp(all,PatternFunc,Mod,F,A,MS,Opts,LD), % For all active nodes in the session.
{reply,Reply,LD};
handle_call({tp,PatternFunc,Nodes,Mod,F,A,MS,Opts},_From,LD) ->
RTStates=get_rtstates_ld(LD),
SNodes=get_all_session_nodes_rtstates(RTStates), % Notes belongoing to the session.
{Nodes2,FaultyNodes}=remove_nodes_not_ours(Nodes,SNodes),
Reply=h_tp(Nodes2,PatternFunc,Mod,F,A,MS,Opts,LD),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,FaultyNodes),
{reply,ErrorReply++Reply,LD};
%% ------------------------------------------------------------------------------
%% Call-back handling the removal of both local and global trace-patterns.
%% NOT IMPLEMENTED YET.
handle_call({ctp,PatternFunc,Nodes,Mod,F,A},_From,LD) ->
Reply=h_ctp(Nodes,PatternFunc,Mod,F,A,LD),
{reply,Reply,LD};
%% ------------------------------------------------------------------------------
handle_call({tpm_localnames,Nodes},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_tpm_localnames(get_ctrlnode_ld(LD),Nodes2,RTStates,ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({init_tpm,Nodes,Mod,Func,Arity,CallFunc},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),
Nodes2,
init_tpm,
[Mod,Func,Arity,CallFunc],
RTStates,
ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({init_tpm,Nodes,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),
Nodes2,
init_tpm,
[Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc],
RTStates,
ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({tpm,Nodes,Mod,Func,Arity,MS},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),Nodes2,tpm,[Mod,Func,Arity,MS],RTStates,ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({tpm,Nodes,Mod,Func,Arity,MS,CallFunc},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),
Nodes2,
tpm,
[Mod,Func,Arity,MS,CallFunc],
RTStates,
ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({tpm,Nodes,Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),
Nodes2,
tpm,
[Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc],
RTStates,
ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({tpm_ms,Nodes,Mod,Func,Arity,MSname,MS},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),
Nodes2,
tpm_ms,
[Mod,Func,Arity,MSname,MS],
RTStates,
ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({ctpm_ms,Nodes,Mod,Func,Arity,MSname},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),
Nodes2,
ctpm_ms,
[Mod,Func,Arity,MSname],
RTStates,
ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
handle_call({ctpm,Nodes,Mod,Func,Arity},_From,LD) ->
RTStates=get_rtstates_ld(LD),
OurNodes=get_all_session_nodes_rtstates(RTStates),
{Nodes2,NotOurNodes}=remove_nodes_not_ours(Nodes,OurNodes),
ACTstorage=get_actstorage_ld(LD),
{Reply,NewACTstorage}=
h_all_tpm(get_ctrlnode_ld(LD),Nodes2,ctpm,[Mod,Func,Arity],RTStates,ACTstorage),
ErrorReply=lists:map(fun(N)->{N,{error,not_in_session}} end,NotOurNodes),
{reply,ErrorReply++Reply,put_actstorage_ld(NewACTstorage,LD)};
%% ------------------------------------------------------------------------------
%% Call-back for setting process trace-flags. Handles both distributed and non-
%% distributed case.
handle_call({tf,TraceConfList},From,LD) ->
handle_call({tf,all,TraceConfList},From,LD);
handle_call({tf,Nodes,TraceConfList},_From,LD) ->
{Reply,NewACTstorage}=h_tf(get_ctrlnode_ld(LD),
Nodes,
TraceConfList,
get_actstorage_ld(LD),
get_rtstates_ld(LD)),
{reply,Reply,put_actstorage_ld(NewACTstorage,LD)};
%% ------------------------------------------------------------------------------
handle_call(get_loopdata,_From,LD) ->
io:format("The loopdata:~n~p~n",[LD]),
{reply,ok,LD}.
%% ------------------------------------------------------------------------------
%% Clause handling an incomming state-change event from the control component.
%% Note that it does not have to be one of our nodes since it is not possible
%% to subscribe to certain node-events.
%% We may very well get state-change events for state-changes we are the source
%% to our selves. Those state-changes are already incorporated into the RTStates.
%% There is however no harm in doing them again since we know that this event
%% message will reach us before a reply to a potentially following state-change
%% request will reach us. Hence we will do all state-changes in the correct order,
%% even if sometimes done twice.
handle_info({trace_event,CtrlPid,_Time,{state_change,Node,{State,Status}}},LD) ->
case get_ctrlpid_ld(LD) of
CtrlPid -> % It is from our control component.
case {State,Status} of
{?TRACING,?RUNNING} -> % This is the only case when new tracerdata!
NewTracerData=add_current_tracerdata_ld(get_ctrlnode_ld(LD),
Node,
get_rtstates_ld(LD),
get_trdstorage_ld(LD)),
NewRTStates=statechange_rtstates(Node,State,Status,get_rtstates_ld(LD)),
{noreply,put_trdstorage_ld(NewTracerData,
put_rtstates_ld(NewRTStates,LD))};
_ -> % In all other cases, just fix rtstates.
NewRTStates=statechange_rtstates(Node,State,Status,get_rtstates_ld(LD)),
{noreply,put_rtstates_ld(NewRTStates,LD)}
end;
_ ->
{noreply,LD}
end;
%% If a new runtime component connects to our trace control component, and it is
%% in our list of runtime components belonging to this session, we may update its
%% state to now being present. Otherwise it does not belong to this session.
%% Note that we avoid updating an already connected runtime component. This
%% can happend if it connected by itself after we started the session handler,
%% but before we managed to initiate tracing. Doing so or not will not result in
%% any error in the long run, but during a short period of time we might be
%% prevented from doing things with the runtime though it actually is tracing.
handle_info({trace_event,CtrlPid,_Time,{connected,Node,{_Tag,{State,Status}}}},LD) ->
case get_ctrlpid_ld(LD) of
CtrlPid -> % It is from our control component.
case get_statestatus_rtstates(Node,get_rtstates_ld(LD)) of
{ok,unavailable} -> % This is the situation when we update!
NewRTStates=statechange_rtstates(Node,State,Status,get_rtstates_ld(LD)),
{noreply,put_rtstates_ld(NewRTStates,LD)};
_ -> % In all other cases, let it be.
{noreply,LD}
end;
_ -> % Not from our control component.
{noreply,LD}
end;
%% If a runtime component disconnects we mark it as unavailable. We must also
%% remove all saved trace-flags in order for them to not be accidently reactivated
%% should the runtime component reconnect and then suspend.
handle_info({trace_event,CtrlPid,_Time,{disconnected,Node,_}},LD) ->
case get_ctrlpid_ld(LD) of
CtrlPid -> % It is from our control component.
NewRTStates=set_unavailable_rtstates(Node,get_rtstates_ld(LD)),
NewACTstorage=del_node_actstorage(Node,get_actstorage_ld(LD)),
{noreply,put_actstorage_ld(NewACTstorage,put_rtstates_ld(NewRTStates,LD))};
_ ->
{noreply,LD}
end;
handle_info(_,LD) ->
{noreply,LD}.
%% ------------------------------------------------------------------------------
%% In terminate we cancel our subscription to event from the trace control component.
%% That should actually not be necessary, but lets do it the correct way!
terminate(_,LD) ->
case get_ctrlnode_ld(LD) of
void -> % Non-distributed.
inviso:unsubscribe();
Node ->
inviso_tool_lib:inviso_cmd(Node,unsubscribe,[])
end.
%% ------------------------------------------------------------------------------
%% ==============================================================================
%% First level help functions to call-backs.
%% ==============================================================================
%% ------------------------------------------------------------------------------
%% Help functions to init.
%% ------------------------------------------------------------------------------
%% Help function which find out the state/status of the runtime components.
%% Note that since we have just started subscribe to state changes we must
%% check our inqueue to see that we have no waiting messages for the nodes
%% we learned the state/status of. If there is a waiting message we don't
%% know whether that was a state change received before or after the state
%% check was done. We will then redo the state-check.
%% Returns {ok,States} or {error,Reason}.
%% Where States is [{Node,{State,Status},Opts},...].
%% Note that {error,Reason} can not occur in the non-distributed case.
init_rtcomponent_states(NodeParams,void,CtrlPid,Nodes) -> % The non-distributed case.
ok=inviso:subscribe(),
init_rtcomponent_states_2(NodeParams,void,CtrlPid,Nodes,[]);
init_rtcomponent_states(NodeParams,CtrlNode,CtrlPid,Nodes) ->
ok=inviso_tool_lib:inviso_cmd(CtrlNode,subscribe,[]),
init_rtcomponent_states_2(NodeParams,CtrlNode,CtrlPid,Nodes,[]).
init_rtcomponent_states_2(_,_,_,[],States) ->
{ok,States};
init_rtcomponent_states_2(NodeParams,void,CtrlPid,_Nodes,States) ->
case inviso:get_status() of
{ok,StateStatus} -> % Got its state/status, now...
{ProblemNodes,NewStates}=
init_rtcomponent_states_3(NodeParams,CtrlPid,[{?LOCAL_RUNTIME,{ok,StateStatus}}],
[],States),
init_rtcomponent_states_2(NodeParams,void,CtrlPid,ProblemNodes,NewStates);
{error,_Reason} -> % The runtime is not available!?
{ok,[{?LOCAL_RUNTIME,unavailable,[]}]} % Create the return value immediately.
end;
init_rtcomponent_states_2(NodeParams,CtrlNode,CtrlPid,Nodes,States) ->
case inviso_tool_lib:inviso_cmd(CtrlNode,get_status,[Nodes]) of
{ok,NodeResult} ->
{ProblemNodes,NewStates}=
init_rtcomponent_states_3(NodeParams,CtrlPid,NodeResult,[],States),
init_rtcomponent_states_2(NodeParams,CtrlNode,CtrlPid,ProblemNodes,NewStates);
{error,Reason} -> % Severe problem, abort the session.
{error,{get_status,Reason}}
end.
%% Traverses the list of returnvalues and checks that we do not have an event
%% waiting in the message queue. If we do have, it is a problem. That node will
%% be asked about its state again.
%% Note that it is here we construct the RTStatesList.
init_rtcomponent_states_3(NodeParams,CtrlPid,[{Node,{ok,{State,Status}}}|Rest],Problems,States) ->
receive
{trace_event,CtrlPid,_Time,{state_change,Node,_}} ->
init_rtcomponent_states_3(NodeParams,CtrlPid,Rest,[Node|Problems],States)
after
0 -> % Not in msg queue, then we're safe!
RTState=case lists:keysearch(Node,1,NodeParams) of
{value,{_Node,_TracerData,Opts}} ->
{Node,{State,Status},Opts};
_ -> % No option available, use [].
{Node,{State,Status},[]}
end,
init_rtcomponent_states_3(NodeParams,CtrlPid,Rest,Problems,[RTState|States])
end;
init_rtcomponent_states_3(NodeParams,CtrlPid,[{Node,{error,_Reason}}|Rest],Problems,States) ->
RTState=case lists:keysearch(Node,1,NodeParams) of
{value,{_Node,_TracerData,Opts}} ->
{Node,unavailable,Opts};
_ -> % No option available, use [].
{Node,unavailable,[]}
end,
init_rtcomponent_states_3(NodeParams,CtrlPid,Rest,Problems,[RTState|States]);
init_rtcomponent_states_3(_,_,[],Problems,States) ->
{Problems,States}.
%% ------------------------------------------------------------------------------
%% Help function removing nodes from NodeParams. The reason for this can either
%% be that we are using a tool internal tracerdata that shall not be forwarded to
%% the trace control component, or that the node is actually already part of
%% another session.
%% Returns {NewNodeParams,NodesWhichShallNotBeInitiated}.
remove_nodeparams(Nodes,NodesParams) ->
remove_nodeparams_2(Nodes,NodesParams,[],[]).
remove_nodeparams_2(Nodes,[NodeParam|Rest],NPAcc,NAcc) when % NPAcc=NodeParamsAcc.
(is_tuple(NodeParam) and ((size(NodeParam)==2) or (size(NodeParam)==3))) ->
Node=element(1,NodeParam),
Params=element(2,NodeParam), % This is tracerdata!
case lists:member(Node,Nodes) of
true -> % Remove this one, in another session.
remove_nodeparams_2(Nodes,Rest,NPAcc,NAcc);
false -> % Ok so far...
case is_tool_internal_tracerdata(Params) of
false -> % Then keep it and use it later!
remove_nodeparams_2(Nodes,Rest,[{Node,Params}|NPAcc],NAcc);
true -> % Since it is, remove it from the list.
remove_nodeparams_2(Nodes,Rest,NPAcc,[Node|NAcc])
end
end;
remove_nodeparams_2(Nodes,[_|Rest],NPAcc,NAcc) -> % Faulty NodeParam, skip it!
remove_nodeparams_2(Nodes,Rest,NPAcc,NAcc);
remove_nodeparams_2(_,[],NPAcc,NAcc) ->
{lists:reverse(NPAcc),NAcc}.
%% ------------------------------------------------------------------------------
%% Help function which adds both the nodes which were already part of another
%% session and the nodes that we actually did not issue any init_tracing for.
%% Returns a new Result list of [{Node,NodeResult},...].
init_fix_resultnodes(NodesOtherSes,NodesNotInit,Result) ->
NewResult=init_fix_resultnodes_2(NodesOtherSes,{error,in_other_session},Result),
init_fix_resultnodes_2(NodesNotInit,ok,NewResult).
init_fix_resultnodes_2([Node|Rest],NodeResult,Result) ->
[{Node,NodeResult}|init_fix_resultnodes_2(Rest,NodeResult,Result)];
init_fix_resultnodes_2([],_,Result) ->
Result. % Append Result to the end of the list.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Help functions to reactivate.
%% ------------------------------------------------------------------------------
h_reactivate(CtrlNode,Nodes,ACTstorage) -> % Distributed case.
case inviso_tool_lib:inviso_cmd(CtrlNode,cancel_suspension,[Nodes]) of
{ok,CSuspResults} ->
{GoodNodes,BadResults}= % Sort out nodes no longer suspended.
lists:foldl(fun({Node,ok},{GoodNs,BadNs})->
{[Node|GoodNs],BadNs};
({Node,{error,Reason}},{GoodNs,BadNs})->
{GoodNs,[{Node,{error,{cancel_suspension,Reason}}}|BadNs]}
end,
{[],[]},
CSuspResults),
Results=h_reactivate_redo_activity(CtrlNode,GoodNodes,ACTstorage,[]),
{ok,BadResults++Results};
{error,Reason} -> % General failure cancelling suspend.
{error,{cancel_suspension,Reason}}
end.
%% ------------------------------------------------------------------------------
%% Help function which traverses the list of nodes known to be ours and have
%% cancelled their suspend. If we fail redoing one of the activities associated
%% with a node, the node will be reported in the return value as failed. From
%% that point on its state must be considered unknown since we do not know how
%% many of the activities were successfully redone.
h_reactivate_redo_activity(CtrlNode,[Node|Rest],ACTstorage,Acc) ->
case get_activities_actstorage(Node,ACTstorage) of
{ok,Activities} -> % The node existed in activity storage.
{Good,Bad}=h_reactivate_redo_activity_2(CtrlNode,Node,Activities,0,0),
h_reactivate_redo_activity(CtrlNode,Rest,ACTstorage,[{Node,{Good,Bad}}|Acc]);
false -> % Node not present in activity storage.
h_reactivate_redo_activity(CtrlNode,Rest,ACTstorage,[{Node,{0,0}}|Acc])
end;
h_reactivate_redo_activity(_CtrlNode,[],_,Acc) ->
lists:reverse(Acc).
%% Help function actually redoing the activity. Note that there must be one
%% clause here for every type of activity.
%% Returns {NrGoodCmds,NrBadCmds}.
%% The number of good or bad commands refers to inviso commands done. If any
%% of the subparts of such a command returned an error, the command is concidered
%% no good.
h_reactivate_redo_activity_2(CtrlNode,Node,[{tf,{Op,TraceConfList}}|Rest],Good,Bad) ->
case inviso_tool_lib:inviso_cmd(CtrlNode,Op,[[Node],TraceConfList]) of
{ok,[{_Node,{ok,Answers}}]} ->
case h_reactivate_redo_activity_check_tf(Answers) of
ok ->
h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good+1,Bad);
error -> % At least oneReports the first encountered error.
h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1)
end;
{ok,[{_Node,{error,_Reason}}]} ->
h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1);
{error,_Reason} -> % General error when doing cmd.
h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1)
end;
h_reactivate_redo_activity_2(CtrlNode,Node,[{tpm,{Op,InvisoCmdParams}}|Rest],Good,Bad) ->
case inviso_tool_lib:inviso_cmd(CtrlNode,Op,[[Node]|InvisoCmdParams]) of
{ok,[{_Node,ok}]} ->
h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good+1,Bad);
{ok,[{_Node,{error,_Reason}}]} ->
h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1);
{error,_Reason} -> % General error when doing cmd.
h_reactivate_redo_activity_2(CtrlNode,Node,Rest,Good,Bad+1)
end;
h_reactivate_redo_activity_2(_CtrlNode,_Node,[],Good,Bad) ->
{Good,Bad}.
%% Help function traversing a list of results from inviso:tf/2 or inviso:ctf/2
%% to see if there were any errors.
h_reactivate_redo_activity_check_tf([N|Rest]) when integer(N) ->
h_reactivate_redo_activity_check_tf(Rest);
h_reactivate_redo_activity_check_tf([{error,_Reason}|_]) ->
error;
h_reactivate_redo_activity_check_tf([]) ->
ok.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Help functions to tp (setting trace patterns, both local and global).
%% ------------------------------------------------------------------------------
%% Help function which handles both tpl and tp. Note that the non-distributed case
%% handled with Nodes='all'.
%% Returns what shall be the reply to the client.
h_tp(all,PatternFunc,Mod,F,A,MS,Opts,LD) -> % All available runtime nodes.
Nodes=get_all_available_nodes_rtstates(get_rtstates_ld(LD)),
h_tp(Nodes,PatternFunc,Mod,F,A,MS,Opts,LD);
h_tp(Nodes,PatternFunc,Mod,F,A,MS,Opts,LD) -> % Only certain nodes in the session.
CtrlNode=get_ctrlnode_ld(LD),
Dbg=get_dbg_ld(LD),
SafetyCatches=get_safetycatches_ld(LD),
case inviso_tool_lib:expand_module_names(Nodes,Mod,Opts) of % Take care of any reg-exps.
{multinode_expansion,NodeMods} ->
NodeTPs=inviso_tool_lib:make_patterns(SafetyCatches,Opts,Dbg,NodeMods,F,A,MS),
h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,NodeTPs,[]);
{singlenode_expansion,Modules} ->
TPs=inviso_tool_lib:make_patterns(SafetyCatches,Opts,Dbg,Modules,F,A,MS),
h_tp_do_tps(CtrlNode,Nodes,TPs,PatternFunc,Dbg);
module ->
TPs=inviso_tool_lib:make_patterns(SafetyCatches,Opts,Dbg,[Mod],F,A,MS),
h_tp_do_tps(CtrlNode,Nodes,TPs,PatternFunc,Dbg);
wildcard -> % Means do for all modules, no safety.
h_tp_do_tps(CtrlNode,Nodes,[{Mod,F,A,MS}],PatternFunc,Dbg);
{error,Reason} ->
{error,Reason}
end.
%% Note that this function can never be called in the non-distributed case.
h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,[{Node,TPs}|Rest],Accum) ->
case h_tp_do_tps(CtrlNode,[Node],TPs,PatternFunc,Dbg) of
{ok,[{Node,Result}]} ->
h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,Rest,[{Node,Result}|Accum]);
{error,Reason} -> % Failure, but don't stop.
h_tp_node_by_node(CtrlNode,PatternFunc,Dbg,Rest,[{Node,{error,Reason}}|Accum])
end;
h_tp_node_by_node(_,_,_,[],Accum) ->
{ok,lists:reverse(Accum)}.
%% Help function which does the actual call to the trace control component.
%% Note that Nodes can be a list of nodes (including a single one) or
%% ?LOCAL_RUNTIME if we are not distributed. The non-distributed case is otherwise
%% detected by the 'void' CtrlNode.
%% Returns {ok,[{Node,{ok,{NrOfFunctions,NrOfErrors}}},{Node,{error,Reason}},...]} or
%% {error,Reason}. In the non-distributed case {ok,{NrOfFunctions,NrOfErros}} or
%% {error,Reason}.
h_tp_do_tps(void,_Nodes,TPs,PatternFunc,Dbg) -> % Non distributed case!
inviso_tool_lib:debug(tp,Dbg,[TPs,PatternFunc]),
case inviso:PatternFunc(TPs) of
{ok,Result} -> % A list of [Nr1,Nr2,error,...].
{ok,
lists:foldl(fun(N,{AccNr,AccErr}) when integer(N) ->
{AccNr+N,AccErr};
(error,{AccNr,AccErr}) ->
{AccNr,AccErr+1}
end,
{0,0},
Result)};
{error,Reason} ->
{error,{PatternFunc,Reason}}
end;
h_tp_do_tps(CtrlNode,Nodes,TPs,PatternFunc,Dbg) ->
inviso_tool_lib:debug(tp,Dbg,[Nodes,TPs,PatternFunc]),
case inviso_tool_lib:inviso_cmd(CtrlNode,PatternFunc,[Nodes,TPs]) of
{ok,Result} -> % Result is [{Node,Result},...].
{ok,
lists:map(fun({Node,{ok,Res}})->
{Node,lists:foldl(fun(N,{ok,{AccNr,AccErr}}) when integer(N) ->
{ok,{AccNr+N,AccErr}};
(error,{AccNr,AccErr}) ->
{ok,{AccNr,AccErr+1}}
end,
{ok,{0,0}},
Res)};
({_Node,{error,Reason}})->
{error,Reason}
end,
Result)};
{error,Reason} ->
{error,{PatternFunc,Reason}}
end.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Help functions for removing trace-patterns.
%% ------------------------------------------------------------------------------
%% NOT IMPLEMENTED YET.
h_ctp(Node,PatternFunc,Mod,F,A,LD) ->
tbd.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Help functions for calling the trace information facility.
%% ------------------------------------------------------------------------------
%% Function handling the meta trace pattern for capturing registration of local
%% process names.
h_tpm_localnames(CtrlNode,Nodes,RTStates,ACTstorage) ->
AvailableNodes=get_all_available_nodes_rtstates(RTStates),
{Nodes3,FaultyNodes}=remove_nodes_not_ours(Nodes,AvailableNodes),
case inviso_tool_lib:inviso_cmd(CtrlNode,tpm_localnames,[Nodes3]) of
{ok,Result} -> % That good we want to modify tpmstorage!
NewACTstorage=add_tpm_actstorage(Result,tpm_localnames,[],ACTstorage),
ErrorResult=lists:map(fun(N)->{N,{error,not_available}} end,FaultyNodes),
{{ok,ErrorResult++Result},NewACTstorage};
{error,Reason} -> % If general failure, do not modify storage.
{{error,Reason},ACTstorage}
end.
%% ------------------------------------------------------------------------------
%% Functions calling meta trace functions for specified nodes. This function is
%% intended for use with all tmp function calls, init_tpm,tpm,tpm_ms,ctpm_ms and
%% ctpm.
%% Note that we must store called meta trace functions and their parameters in the
%% activity storage in order to be able to redo them in case of a reactivate.
h_all_tpm(CtrlNode,Nodes,TpmCmd,InvisoCmdParams,RTStates,ACTstorage) ->
AvailableNodes=get_all_available_nodes_rtstates(RTStates),
{Nodes3,FaultyNodes}=remove_nodes_not_ours(Nodes,AvailableNodes),
case inviso_tool_lib:inviso_cmd(CtrlNode,TpmCmd,[Nodes3|InvisoCmdParams]) of
{ok,Result} -> % That good we want to modify tpmstorage!
NewACTstorage=add_tpm_actstorage(Result,TpmCmd,InvisoCmdParams,ACTstorage),
ErrorResult=lists:map(fun(N)->{N,{error,not_available}} end,FaultyNodes),
{{ok,ErrorResult++Result},NewACTstorage};
{error,Reason} -> % If general failure, do not modify storage.
{{error,Reason},ACTstorage}
end.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Help functions for set trace flags.
%% ------------------------------------------------------------------------------
%% Help function which sets the tracepatterns in TraceConfList for all nodes
%% mentioned in Nodes. Note that non-distributed case is handled with Nodes='all'.
%% Returns {Reply,NewACTstorage} where Reply is whatever shall be returned to caller
%% and NewACTstorage is traceflag storage modified with the flags added to the
%% corresponding nodes.
h_tf(void,_Nodes,TraceConfList,ACTstorage,_RTStates) -> % The non-distributed case.
Reply=inviso:tf(TraceConfList),
NewACTstorage=add_tf_actstorage([{?LOCAL_RUNTIME,Reply}],tf,TraceConfList,ACTstorage),
{Reply,NewACTstorage};
h_tf(CtrlNode,all,TraceConfList,ACTstorage,RTStates) ->
AllNodes=get_all_session_nodes_rtstates(RTStates),
h_tf(CtrlNode,AllNodes,TraceConfList,ACTstorage,RTStates);
h_tf(CtrlNode,Nodes,TraceConfList,ACTstorage,_RTStates) ->
case inviso_tool_lib:inviso_cmd(CtrlNode,tf,[Nodes,TraceConfList]) of
{ok,Result} -> % That good we want to modify actstorage!
NewACTstorage=add_tf_actstorage(Result,tf,TraceConfList,ACTstorage),
{{ok,Result},NewACTstorage};
{error,Reason} -> % If general failure, do not modify actstorage.
{{error,Reason},ACTstorage}
end.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Help functions to stop_session.
%% ------------------------------------------------------------------------------
%% This function fetches all local log-files using our stored tracerdata. Note
%% that there are two major ways of tranfering logfiles. Either via distributed
%% Erlang or by common filesystem (like NFS). The default is distributed Erlang.
%% But there may be info in the RTStates structure about a common file-system.
%% Returns {FailedNodes,FetchedFileNames} where FailedNodes is a list of
%% nodenames where problems occurred. Note that problems does not necessarily
%% mean that no files were copied.
%% FetchedFileNames contains one or two of the tuples {trace_log,Files} and/or
%% {ti_log,Files}, listing all files successfully fetched. Note that the
%% list of fetched files contains sublists of filenames. One for each node and
%% tracerdata.
%% In the non-distributed system we always use copy (since the files always
%% resides locally).
transfer_logfiles(RTStates,CtrlNode,Dir,Prefix,TRDstorage,Dbg,AvailableNodes) ->
if
CtrlNode==void -> % When non-distributed, always copy!
fetch_logfiles_copy(CtrlNode,Dir,Prefix,TRDstorage,Dbg,[?LOCAL_RUNTIME]);
true -> % The distributed case.
{FetchNodes,CopyNodes}=find_logfile_transfer_methods(AvailableNodes,RTStates),
{FailedFetchNodes,FetchedFiles}=
case fetch_logfiles_distributed(CtrlNode,Dir,Prefix,TRDstorage,Dbg,FetchNodes) of
{ok,Failed,Files} -> % So far no disasters.
{Failed,Files};
{error,Reason} -> % Means all fetch-nodes failed!
inviso_tool_lib:debug(transfer_logfiles,Dbg,[FetchNodes,Reason]),
{lists:map(fun(N)->{N,error} end,FetchNodes),[]}
end,
{FailedCopyNodes,CopiedFiles}=
fetch_logfiles_copy(CtrlNode,Dir,Prefix,TRDstorage,Dbg,CopyNodes),
{FailedFetchNodes++FailedCopyNodes,FetchedFiles++CopiedFiles}
end.
%% Help function which finds out which node we have a common file system with
%% and from which we must make distributed erlang tranfere.
%% Returns {DistributedNodes,CopyNodes} where CopyNode is [{Node,CopyFromDir},...].
find_logfile_transfer_methods(Nodes,RTStates) ->
find_logfile_transfer_methods_2(Nodes,RTStates,[],[]).
find_logfile_transfer_methods_2([Node|Rest],RTStates,FetchAcc,CopyAcc) ->
{ok,Opts}=get_opts_rtstates(Node,RTStates), % Node must be in RTStates!
case lists:keysearch(?COPY_LOG_FROM,1,Opts) of
{value,{_,FromDir}} when list(FromDir) -> % Node has common filesystem.
find_logfile_transfer_methods_2(Rest,RTStates,FetchAcc,[{Node,FromDir}|CopyAcc]);
{value,_} -> % Can't understand dir option.
find_logfile_transfer_methods_2(Rest,RTStates,[Node|FetchAcc],CopyAcc);
false -> % Then we want to use fetch instead.
find_logfile_transfer_methods_2(Rest,RTStates,[Node|FetchAcc],CopyAcc)
end;
find_logfile_transfer_methods_2([],_,FetchAcc,CopyAcc) ->
{FetchAcc,CopyAcc}.
%% ------------------------------------------------------------------------------
%% Help function which transferes all local logfiles according to the tracerdata
%% stored for the nodes in Nodes.
%% Returns {ok,FailedNodes,FileNodeSpecs} or {error,Reason}.
%% FailedNodes is a list of nodes where fetching logs did not succeed, partially
%% or not at all.
%% FileNames is a list of list of actually fetched files (the name as it is here, including
%% Dir). The sublists are files which belong together.
fetch_logfiles_distributed(CtrlNode,Dir,Prefix,TRDstorage,Dbg,Nodes) ->
LogSpecList=build_logspeclist(Nodes,TRDstorage),
case inviso_fetch_log(inviso_tool_lib:inviso_cmd(CtrlNode,
fetch_log,
[LogSpecList,Dir,Prefix])) of
{ok,Result} ->
Files=get_all_filenames_fetchlog_result(Result,Dbg),
FailedNodes=get_all_failednodes_fetchlog_result(Result),
{ok,FailedNodes,Files};
{error,Reason} -> % Some general failure!
{error,{fetch_log,Reason}}
end.
%% Help function which constructs a list {Node,TracerData} for all nodes in Nodes.
%% Note that there may be more than one tracerdata for a node, resulting in multiple
%% tuples for that node.
build_logspeclist(Nodes,TRDstorage) ->
build_logspeclist_2(Nodes,TRDstorage,[]).
build_logspeclist_2([Node|Rest],TRDstorage,Acc) ->
TRDlist=find_tracerdata_for_node_trd(Node,TRDstorage), % A list of all tracerdata.
build_logspeclist_2(Rest,
TRDstorage,
[lists:map(fun(TRD)->{Node,TRD} end,TRDlist)|Acc]);
build_logspeclist_2([],_,Acc) ->
lists:flatten(Acc).
%% Help function which translates inviso:fetch_log return values to what I
%% want!
inviso_fetch_log({error,Reason}) ->
{error,Reason};
inviso_fetch_log({_Success,ResultList}) ->
{ok,ResultList}.
%% Help function which collects all filenames mentioned in a noderesult structure.
%% The files may or may not be complete.
%% Returns a list of list of filenames. Each sublist contains files which belong
%% together, i.e because they are a wrap-set.
get_all_filenames_fetchlog_result(NodeResult,Dbg) ->
get_all_filenames_fetchlog_result_2(NodeResult,Dbg,[]).
get_all_filenames_fetchlog_result_2([{Node,{Success,FileInfo}}|Rest],Dbg,Accum)
when Success=/=error, list(FileInfo) ->
SubAccum=get_all_filenames_fetchlog_result_3(FileInfo,[]),
get_all_filenames_fetchlog_result_2(Rest,Dbg,[{Node,SubAccum}|Accum]);
get_all_filenames_fetchlog_result_2([{Node,{error,FReason}}|Rest],Dbg,Accum) ->
inviso_tool_lib:debug(fetch_files,Dbg,[Node,FReason]),
get_all_filenames_fetchlog_result_2(Rest,Dbg,Accum);
get_all_filenames_fetchlog_result_2([],_Dbg,Accum) ->
Accum.
get_all_filenames_fetchlog_result_3([{FType,Files}|Rest],SubAccum) ->
FilesOnly=lists:foldl(fun({ok,FName},Acc)->[FName|Acc];(_,Acc)->Acc end,[],Files),
get_all_filenames_fetchlog_result_3(Rest,[{FType,FilesOnly}|SubAccum]);
get_all_filenames_fetchlog_result_3([],SubAccum) ->
SubAccum.
%% Help function which traverses a noderesult and builds a list as return
%% value containing the nodenames of all nodes not being complete.
%% Note that a node may occur multiple times since may have fetched logfiles
%% for several tracerdata from the same node. Makes sure the list contains
%% unique node names.
%% Returns a list nodes.
get_all_failednodes_fetchlog_result(NodeResult) ->
get_all_failednodes_fetchlog_result_2(NodeResult,[]).
get_all_failednodes_fetchlog_result_2([{_Node,{complete,_}}|Rest],Acc) ->
get_all_failednodes_fetchlog_result_2(Rest,Acc);
get_all_failednodes_fetchlog_result_2([{Node,{_Severity,_}}|Rest],Acc) ->
case lists:member(Node,Acc) of
true -> % Already in the list.
get_all_failednodes_fetchlog_result_2(Rest,Acc);
false -> % Not in Acc, add it!
get_all_failednodes_fetchlog_result_2(Rest,[Node|Acc])
end;
get_all_failednodes_fetchlog_result_2([],Acc) ->
Acc.
%% ------------------------------------------------------------------------------
%% Help function which copies files from one location to Dir and at the same time
%% adds the Prefix to the filename. NodeSpecs contains full path to the files. The
%% reason the node information is still part of NodeSpecs is that otherwise we can
%% not report faulty nodes. Note that one node may occur multiple times since there
%% may be more than one tracerdata for a node.
%% Returns {FailedNodes,Files} where FailedNodes is a list of nodes where problems
%% occurred. Files is a tuple list of [{Node,[{FType,FileNames},...]},...].
fetch_logfiles_copy(CtrlNode,Dir,Prefix,TRDstorage,Dbg,NodeSpecs) ->
CopySpecList=build_copylist(CtrlNode,Dbg,NodeSpecs,TRDstorage),
fetch_logfiles_copy_2(Dir,Prefix,Dbg,CopySpecList,[],[]).
fetch_logfiles_copy_2(Dir,Prefix,Dbg,[{Node,CopySpecs}|Rest],FailedNodes,Files) ->
case fetch_logfiles_copy_3(Dir,Prefix,Dbg,CopySpecs,[],0) of
{0,LocalFiles} -> % Copy went ok and zero errors.
fetch_logfiles_copy_2(Dir,Prefix,Dbg,Rest,FailedNodes,[{Node,LocalFiles}|Files]);
{_N,LocalFiles} -> % Copied files, but some went wrong.
case lists:member(Node,FailedNodes) of
true -> % Node already in FailedNodes.
fetch_logfiles_copy_2(Dir,Prefix,Dbg,Rest,FailedNodes,
[{Node,LocalFiles}|Files]);
false -> % Node not marked as failed, yet.
fetch_logfiles_copy_2(Dir,Prefix,Dbg,Rest,[Node|FailedNodes],
[{Node,LocalFiles}|Files])
end
end;
fetch_logfiles_copy_2(_,_,_,[],FailedNodes,Files) ->
{FailedNodes,Files}. % The return value from fetch_logfiles_copy.
fetch_logfiles_copy_3(Dir,Prefix,Dbg,[{FType,RemoteFiles}|Rest],Results,Errors) ->
{Err,LocalFiles}=fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,RemoteFiles,[],0),
fetch_logfiles_copy_3(Dir,Prefix,Dbg,Rest,[{FType,LocalFiles}|Results],Errors+Err);
fetch_logfiles_copy_3(_,_,_,[],Results,Errors) ->
{Errors,Results}.
%% For each file of one file-type (e.g. trace_log).
fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,[File|Rest],LocalFiles,Errors) ->
DestName=Prefix++filename:basename(File),
Destination=filename:join(Dir,DestName),
case do_copy_file(File,Destination) of
ok ->
fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,Rest,[DestName|LocalFiles],Errors);
{error,Reason} ->
inviso_tool_lib:debug(copy_files,Dbg,[File,Destination,Reason]),
fetch_logfiles_copy_3_1(Dir,Prefix,Dbg,Rest,LocalFiles,Errors+1)
end;
fetch_logfiles_copy_3_1(_,_,_,[],LocalFiles,Errors) ->
{Errors,LocalFiles}.
%% Help function which builds a [{Node,[{Type,[ListOfRemoteFiles]}},...}]
%% where Type describes trace_log or ti_log and each entry in ListOfRemoteFiles
%% is a complete path to a file to be copied.
build_copylist(CtrlNode,Dbg,NodeSpecList,TRDstorage) ->
build_copylist_2(CtrlNode,Dbg,NodeSpecList,TRDstorage,[]).
%% For each node specified in the NodeSpecList.
build_copylist_2(CtrlNode,Dbg,[{Node,SourceDir}|Rest],TRDstorage,Acc) ->
TRDlist=find_tracerdata_for_node_trd(Node,TRDstorage),
CopySpecList=build_copylist_3(CtrlNode,Dbg,SourceDir,Node,TRDlist),
build_copylist_2(CtrlNode,Dbg,Rest,TRDstorage,[CopySpecList|Acc]);
build_copylist_2(_,_,[],_,Acc) ->
lists:flatten(Acc).
%% For each tracerdata found for the node.
build_copylist_3(void,Dbg,SourceDir,Node,[TRD|Rest]) -> % The non-distributed case.
case inviso:list_logs(TRD) of
{ok,FileSpec} when list(FileSpec) -> % [{trace_log,Dir,Files},...]
NewFileSpec=build_copylist_4(SourceDir,FileSpec,[]),
[{Node,NewFileSpec}|build_copylist_3(void,Dbg,SourceDir,Node,Rest)];
{ok,no_log} -> % This tracedata not associated with any log.
build_copylist_3(void,Dbg,SourceDir,Node,Rest);
{error,Reason} ->
inviso_tool_lib:debug(list_logs,Dbg,[Node,TRD,Reason]),
build_copylist_3(void,Dbg,SourceDir,Node,Rest)
end;
build_copylist_3(CtrlNode,Dbg,SourceDir,Node,[TRD|Rest]) -> % The distributed case.
case inviso_tool_lib:inviso_cmd(CtrlNode,list_logs,[[{Node,TRD}]]) of
{ok,[{Node,{ok,FileSpec}}]} when list(FileSpec) ->
NewFileSpec=build_copylist_4(SourceDir,FileSpec,[]),
[{Node,NewFileSpec}|build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest)];
{ok,[{Node,{ok,no_log}}]} -> % It relays to another node, no files!
build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest);
{ok,[{Node,{error,Reason}}]} ->
inviso_tool_lib:debug(list_logs,Dbg,[Node,TRD,Reason]),
build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest);
{error,Reason} -> % Some general failure.
inviso_tool_lib:debug(list_logs,Dbg,[Node,TRD,Reason]),
build_copylist_3(CtrlNode,Dbg,SourceDir,Node,Rest)
end;
build_copylist_3(_,_,_,_,[]) ->
[].
%% Help function which makes a [{Type,Files},...] list where each file in Files
%% is with full path as found from our file-system.
build_copylist_4(SourceDir,[{Type,_Dir,Files}|Rest],Accum) ->
NewFiles=
lists:foldl(fun(FName,LocalAcc)->[filename:join(SourceDir,FName)|LocalAcc] end,
[],
Files),
build_copylist_4(SourceDir,Rest,[{Type,NewFiles}|Accum]);
build_copylist_4(_,[],Accum) ->
Accum.
%% Help function which copies a file using os:cmd.
%% Returns 'ok' or {error,Reason}.
do_copy_file(Source,Destination) ->
case os:type() of
{win32,_} ->
os:cmd("copy "++Source++" "++Destination), % Perhaps a test on success?
ok;
{unix,_} ->
os:cmd("cp "++Source++" "++Destination), % Perhaps a test on success?
ok
end.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% ==============================================================================
%% Various help functions.
%% ==============================================================================
%% Help function going through the Nodes list and checking that only nodes
%% mentioned in OurNodes gets returned. It also makes the nodes in the return
%% value unique.
remove_nodes_not_ours(Nodes,OurNodes) ->
remove_nodes_not_ours_2(Nodes,OurNodes,[],[]).
remove_nodes_not_ours_2([Node|Rest],OurNodes,OurAcc,OtherAcc) ->
case lists:member(Node,OurNodes) of
true -> % Ok it is one of our nodes.
case lists:member(Node,OurAcc) of
true -> % Already in the list, skip.
remove_nodes_not_ours_2(Rest,OurNodes,OurAcc,OtherAcc);
false ->
remove_nodes_not_ours_2(Rest,OurNodes,[Node|OurAcc],OtherAcc)
end;
false ->
case lists:member(Node,OtherAcc) of
true ->
remove_nodes_not_ours_2(Rest,OurNodes,OurAcc,OtherAcc);
false ->
remove_nodes_not_ours_2(Rest,OurNodes,OurAcc,[Node|OtherAcc])
end
end;
remove_nodes_not_ours_2([],_,OurAcc,OtherAcc) ->
{lists:reverse(OurAcc),lists:reverse(OtherAcc)}.
%% ------------------------------------------------------------------------------
%% Help function which returns 'true' or 'false' depending on if TracerData is
%% meant to be used by the session handler (true) or if it supposed to be passed
%% on to the trace system.
is_tool_internal_tracerdata(_) -> % CURRENTLY NO INTERNAL TRACER DATA!
false.
%% ------------------------------------------------------------------------------
%% Help function which checks that all nodes in the first list of nodes exists
%% in the second list of nodes. Returns 'true' or 'false'. The latter if as much
%% as one incorrect node was found.
check_our_nodes([Node|Rest],AllNodes) ->
case lists:member(Node,AllNodes) of
true ->
check_our_nodes(Rest,AllNodes);
false -> % Then we can stop right here.
false
end;
check_our_nodes([],_) ->
true.
%% ------------------------------------------------------------------------------
%% Help function which checks that a directory actually exists. Returns 'true' or
%% 'false'.
check_directory_exists(Dir) ->
case file:read_file_info(Dir) of
{ok,#file_info{type=directory}} ->
true;
_ -> % In all other cases it is not valid.
false
end.
%% ------------------------------------------------------------------------------
%% This function stops the tracing on all nodes in Nodes. Preferably Nodes is a list
%% of only tracing runtime components. Not that there will actually be any difference
%% since the return value does not reflect how stopping the nodes went.
%% Returns 'ok' or {error,Reason}, the latter only in case of general failure.
stop_all_tracing(void,Dbg,[?LOCAL_RUNTIME]) -> % The non-distributed case, and is tracing.
case inviso:stop_tracing() of
{ok,_State} ->
ok;
{error,Reason} -> % We actually don't care.
inviso_tool_lib:debug(stop_tracing,Dbg,[?LOCAL_RUNTIME,Reason]),
ok
end;
stop_all_tracing(void,_,_) -> % There is no local runtime started.
ok;
stop_all_tracing(CtrlNode,Dbg,Nodes) ->
case inviso_tool_lib:inviso_cmd(CtrlNode,stop_tracing,[Nodes]) of
{ok,Result} -> % The result is only used for debug.
Failed=lists:foldl(fun({N,{error,Reason}},Acc)->[{N,{error,Reason}}|Acc];
(_,Acc)->Acc
end,
[],
Result),
if
Failed==[] ->
ok;
true ->
inviso_tool_lib:debug(stop_tracing,Dbg,[Nodes,Failed]),
ok
end;
{error,Reason} ->
{error,{stop_tracing,Reason}}
end.
%% ------------------------------------------------------------------------------
%% Help function removing all local logs using the tracerdata to determine what
%% logs to remove from where.
%% There is no significant return value since it is not really clear what to do
%% if removal went wrong. The function can make debug-reports thought.
remove_all_local_logs(CtrlNode,TRDstorage,Nodes,Dbg) ->
LogSpecList=build_logspeclist_remove_logs(Nodes,TRDstorage),
case inviso_tool_lib:inviso_cmd(CtrlNode,delete_log,[LogSpecList]) of
{ok,Results} ->
case look_for_errors_resultlist(Results) of
[] -> % No errors found in the result!
true;
Errors ->
inviso_tool_lib:debug(remove_all_local_logs,Dbg,[Errors]),
true
end;
{error,Reason} -> % Some general error.
inviso_tool_lib:debug(remove_all_local_logs,Dbg,[{error,Reason}]),
true
end.
%% Help function which puts together a list of {Node,Tracerdata} tuples. Note that
%% we must build one tuple for each tracerdata for one node.
build_logspeclist_remove_logs(Nodes,TRDstorage) ->
[{Node,TracerData}||Node<-Nodes,TracerData<-find_tracerdata_for_node_trd(Node,TRDstorage)].
%% ------------------------------------------------------------------------------
%% Help function which traverses a resultlist from an inviso function. Such are
%% built up as [{Node,SubResults},...] where SubResult is a list of tuples for each
%% file-type (e.g trace_log) {FType,FileList} where a FileList is either {error,Reason}
%% or {ok,FileName}.
%% Returns a list of {Node,[{error,Reason},...]}.
look_for_errors_resultlist([{Node,{error,Reason}}|Rest]) ->
[{Node,{error,Reason}}|look_for_errors_resultlist(Rest)];
look_for_errors_resultlist([{Node,{ok,NResults}}|Rest]) when list(NResults) ->
case look_for_errors_resultlist_2(NResults,[]) of
[] ->
look_for_errors_resultlist(Rest);
Errors -> % A list of lists.
[{Node,lists:flatten(Errors)}|look_for_errors_resultlist(Rest)]
end;
look_for_errors_resultlist([_|Rest]) ->
look_for_errors_resultlist(Rest);
look_for_errors_resultlist([]) ->
[].
look_for_errors_resultlist_2([{_FType,NSubResult}|Rest],Accum) ->
case lists:filter(fun({error,_Reason})->true;(_)->false end,NSubResult) of
[] -> % No errors for this node.
look_for_errors_resultlist_2(Rest,Accum);
Errors -> % A list of at least one error.
look_for_errors_resultlist_2(Rest,[Errors|Accum])
end;
look_for_errors_resultlist_2([],Accum) ->
Accum.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Functions working on the loopdata structure.
%% Its main purpose is to store information about runtime components participating
%% in the session and their current status.
%% ------------------------------------------------------------------------------
-record(ld,{parent,
ctrlnode,
ctrlpid, % To where to send inviso cmd.
rtstates,
tracerdata,
safetycatches,
dbg,
actstorage % Activity storage, for reactivate.
}).
%% Function creating the initial datastructure.
%% The datastructure is [{Node,State},...].
%%
%% The tracerdata table is a bag simply for the reason that if we try to insert
%% the same tracerdata for a node twice, we will end up with one tracerdata after
%% all. This is useful when we insert tracerdata ourselves, the tracerdata will
%% come as a state-change too.
mk_ld(Parent,CtrlNode,CtrlPid,RTStates,NodeParams,OtherNodes,SafetyCatches,Dbg) ->
TRDtableName=list_to_atom("inviso_tool_sh_trdstorage_"++pid_to_list(self())),
TRDtid=ets:new(TRDtableName,[bag]),
ACTtableName=list_to_atom("inviso_tool_sh_actstorage_"++pid_to_list(self())),
ACTtid=ets:new(ACTtableName,[bag]),
mk_ld_fill_tracerdata(CtrlNode,TRDtid,NodeParams,OtherNodes), % Fill the ETS table.
#ld{parent=Parent, % The tool main process.
ctrlnode=CtrlNode, % Node name where the control component is.
ctrlpid=CtrlPid, % The process id of the control component.
rtstates=RTStates, % All nodes and their state/status.
tracerdata=TRDtid,
safetycatches=SafetyCatches,
dbg=Dbg,
actstorage=ACTtid
}.
%% Help function which inserts tracer data for the nodes. Note that we can get
%% tracer data either from the return value from init_tracing or by asking the
%% node for it. The latter is necessary for the nodes which were marked not to
%% be initiated by the session handler. This maybe because those nodes have
%% autostarted.
mk_ld_fill_tracerdata(CtrlNode,TId,NodeParams,OtherNodes) ->
mk_ld_fill_tracerdata_nodeparams(TId,NodeParams),
mk_ld_fill_tracerdata_othernodes(CtrlNode,TId,OtherNodes).
mk_ld_fill_tracerdata_nodeparams(TId,[{Node,TracerData}|Rest]) ->
ets:insert(TId,{Node,TracerData}),
mk_ld_fill_tracerdata_nodeparams(TId,Rest);
mk_ld_fill_tracerdata_nodeparams(_,[]) ->
ok.
mk_ld_fill_tracerdata_othernodes(_,_,[]) -> % Then not necessary to do anything.
ok;
mk_ld_fill_tracerdata_othernodes(void,TId,[Node]) -> % The non-distributed case.
case inviso:get_tracerdata() of
{error,_Reason} -> % Perhaps in state new or disconnected.
ok; % Do nothing.
{ok,TracerData} ->
ets:insert(TId,{Node,TracerData})
end;
mk_ld_fill_tracerdata_othernodes(CtrlNode,TId,Nodes) ->
case inviso_tool_lib:invisomd(CtrlNode,get_tracerdata,[Nodes]) of
{ok,Results} ->
mk_ld_fill_tracerdata_othernodes_2(TId,Results);
{error,_Reason} -> % Strange, we will probably crash later.
ok
end.
mk_ld_fill_tracerdata_othernodes_2(TId,[{_Node,{ok,no_tracerdata}}|Rest]) ->
mk_ld_fill_tracerdata_othernodes_2(TId,Rest); % It was not initiated then!
mk_ld_fill_tracerdata_othernodes_2(TId,[{Node,{ok,TracerData}}|Rest]) ->
ets:insert(TId,{Node,TracerData}),
mk_ld_fill_tracerdata_othernodes_2(TId,Rest);
mk_ld_fill_tracerdata_othernodes_2(_,[]) ->
ok.
%% ------------------------------------------------------------------------------
get_ctrlnode_ld(#ld{ctrlnode=CtrlNode}) ->
CtrlNode.
%% ------------------------------------------------------------------------------
get_ctrlpid_ld(#ld{ctrlpid=CtrlPid}) ->
CtrlPid.
%% ------------------------------------------------------------------------------
get_rtstates_ld(#ld{rtstates=RTStates}) ->
RTStates.
put_rtstates_ld(NewRTStates,LD) ->
LD#ld{rtstates=NewRTStates}.
%% ------------------------------------------------------------------------------
get_trdstorage_ld(#ld{tracerdata=TId}) ->
TId.
put_trdstorage_ld(_NewTId,LD) ->
LD.
%% ------------------------------------------------------------------------------
%% Help function which adds the current tracerdata of node Node to the tracerdata
%% storage. We only want to add tracerdata we have not seen before. We therefore
%% avoid adding it if the node already is in state ?TRACING.
%% Returns a new tracerdata (what ever it is)!
add_current_tracerdata_ld(CtrlNode,Node,RTStates,TId) ->
case get_statestatus_rtstates(Node,RTStates) of
{ok,{?TRACING,_}} -> % Then we have already added the tracerdata.
TId; % Then do nothing.
{ok,_} -> % Since we were not tracing before.
case add_current_tracerdata_ld_fetchtracerdata(CtrlNode,Node) of
{ok,TracerData} ->
ets:insert(TId,{Node,TracerData});
no_tracerdata -> % Strange, how could we become tracing
ok;
{error,_Reason} -> % The node perhaps disconnected!?
ok
end;
false -> % Very strange, not our node!
ok % Do nothing.
end.
add_current_tracerdata_ld_fetchtracerdata(void,_Node) ->
case inviso:get_tracerdata() of
{ok,TracerData} ->
{ok,TracerData};
{error,no_tracerdata} ->
no_tracerdata;
{error,Reason} ->
{error,Reason}
end;
add_current_tracerdata_ld_fetchtracerdata(CtrlNode,Node) ->
case inviso_tool_lib:inviso_cmd(CtrlNode,get_tracerdata,[[Node]]) of
{ok,[{Node,{ok,TracerData}}]} ->
{ok,TracerData};
{ok,[{Node,{error,no_tracerdata}}]} ->
no_tracerdata;
{ok,[{Node,{error,Reason}}]} ->
{error,Reason};
{error,Reason} ->
{error,Reason}
end.
%% ------------------------------------------------------------------------------
get_safetycatches_ld(#ld{safetycatches=SCs}) ->
SCs.
%% ------------------------------------------------------------------------------
get_dbg_ld(#ld{dbg=Dbg}) ->
Dbg.
%% ------------------------------------------------------------------------------
get_actstorage_ld(#ld{actstorage=ACTstorage}) ->
ACTstorage.
put_actstorage_ld(_NewACTstorage,LD) ->
LD.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Functions working on the rtstates structure (which is a substructure of loopdata).
%% It is either:
%% [{Node,StateStatus,Opts},...]
%% Node is either the node name of the runtime component erlang node or
%% ?LOCAL_RUNTIME as returned from the trace control component.
%% StateStatus is {State,Status}, 'unavailable' or 'unknown'.
%% Status is the returnvalue from trace control component.
%% i.e: running | {suspended,Reason}
%% ------------------------------------------------------------------------------
%% Function contructing an rtstates structure from a list of [{Node,StateStatus,Opts},...].
to_rtstates(ListOfStates) when list(ListOfStates) ->
ListOfStates.
%% ------------------------------------------------------------------------------
%% Function which takes a rtstates structure and returns a list of [{Node,StateStatus},...].
from_rtstates(RTStates) ->
RTStates.
%% ------------------------------------------------------------------------------
%% Function which takes an rtstates structure and a result as returned from
%% init_tracing. The RTStates is modified for the nodes that changed state as a
%% result of successful init_tracing.
%% Returns a new RTStates.
set_tracing_rtstates([E={Node,_StateStatus,Opts}|Rest],Result) ->
case lists:keysearch(Node,1,Result) of
{value,{_,ok}} -> % Means state-change to tracing!
[{Node,{tracing,running},Opts}|set_tracing_rtstates(Rest,Result)];
_ -> % Otherwise, leave it as is.
[E|set_tracing_rtstates(Rest,Result)]
end;
set_tracing_rtstates([],_Result) ->
[].
%% ------------------------------------------------------------------------------
%% Function updating the state/status for a certain runtime component.
%% Returns a new RTStates structure. Note that Node must not necessarily be one
%% of the nodes in the session. Meaning that Node shall not be added to RTStates
%% should it not already be in there.
statechange_rtstates(Node,State,Status,RTStates) when list(RTStates) ->
case lists:keysearch(Node,1,RTStates) of
{value,{_,_,Opts}} ->
lists:keyreplace(Node,1,RTStates,{Node,{State,Status},Opts});
_ -> % Then Node does not exist.
RTStates % Just keep it as is, as keyreplace would have done.
end.
%% ------------------------------------------------------------------------------
%% Function updating the state/status for a certain runtime component. The
%% state/status is set to 'unavailable'.
%% Returns a new RTStates structure.
set_unavailable_rtstates(Node,RTStates) when list(RTStates) ->
case lists:keysearch(Node,1,RTStates) of
{value,{_,_,Opts}} ->
lists:keyreplace(Node,1,RTStates,{Node,unavailable,Opts});
_ -> % Then Node does not exist.
RTStates % Just keep it as is, as keyreplace would have done.
end.
%% ------------------------------------------------------------------------------
%% Function finding the statestatus associated with Node in the RTStates structure.
%% Returns {ok,StateStatus} or 'false'.
get_statestatus_rtstates(Node,RTStates) ->
case lists:keysearch(Node,1,RTStates) of
{value,{_,StateStatus,_}} ->
{ok,StateStatus};
false ->
false
end.
%% ------------------------------------------------------------------------------
%% Help function which returns a list of all nodes that are currently marked
%% as available to us in the runtime state structure.
get_all_available_nodes_rtstates(RTStates) ->
get_all_session_nodes_rtstates(lists:filter(fun({_N,unavailable,_})->false;
(_)->true
end,
RTStates)).
%% ------------------------------------------------------------------------------
%% Help function returning a list of all nodes belonging to this session.
get_all_session_nodes_rtstates(RTStates) ->
lists:map(fun({Node,_,_})->Node end,RTStates).
%% ------------------------------------------------------------------------------
%% Function which returns a list of nodes that are indicated as tracing in the
%% RTStates structure.
get_all_tracing_nodes_rtstates(RTStates) ->
lists:map(fun({N,_,_})->N end,
lists:filter(fun({_,{tracing,_},_})->true;(_)->false end,RTStates)).
%% ------------------------------------------------------------------------------
%% Returns the options associated with Node in the RTStates structure.
get_opts_rtstates(Node,RTStates) ->
case lists:keysearch(Node,1,RTStates) of
{value,{_,_,Opts}} ->
{ok,Opts};
false ->
false
end.
%% ------------------------------------------------------------------------------
%% Functions working on the tracerdata structure, which is a part of the loopdata.
%% The tracerdata structure is an ETS-table of type bag storing:
%% {Node,TracerData}.
%% Note that there can of course be multiple entries for a node.
%% ------------------------------------------------------------------------------
%% Help function which takes a tracerdata loopdata structure and returns a list
%% of all stored tracerdata for a certain Node.
find_tracerdata_for_node_trd(Node,TRD) ->
case ets:lookup(TRD,Node) of
Result when list(Result) ->
lists:map(fun({_Node,TracerData})->TracerData end,Result);
_ -> % Should probably never happend.
[]
end.
%% ------------------------------------------------------------------------------
%% ------------------------------------------------------------------------------
%% Functions working on the activity storage structure, which is part of the
%% loopdata. It stores entries about things that needs to be "redone" in case
%% of a reactivation of the node. The time order is also important.
%% Note that for every ActivityType there must be a "handler" in the reactivation
%% functionality.
%%
%% The structure is a bag of {Node,ActivityType,What}.
%% ActivityType/What=tf/{Op,TraceConfList}|tpm/{Op,[Mod,Func,Arity,MS,CallFunc]}
%% /{Op,[Mod,Func,Arity,MS,CallFunc,ReturnFunc]}
%% /{Op,[]}
%% TraceConfList=[{Proc,Flags},...]
%% How=true|false
%% ------------------------------------------------------------------------------
%% Function that adds meta-pattern activities to the activity storage. Note
%% that one of the parameters to the function is a return value from an
%% inviso call. In that way we do not enter activities that were unsuccessful.
%% Op can be either the setting or clearing of a meta pattern.
%% Returns a new ACTstorage.
add_tpm_actstorage([{Node,ok}|Rest],Op,InvisoCmdParams,ACTstorage) ->
true=ets:insert(ACTstorage,{Node,tpm,{Op,InvisoCmdParams}}),
add_tpm_actstorage(Rest,Op,InvisoCmdParams,ACTstorage);
add_tpm_actstorage([_|Rest],Op,InvisoCmdParams,ACTstorage) ->
add_tpm_actstorage(Rest,Op,InvisoCmdParams,ACTstorage);
add_tpm_actstorage([],_,_,ACTstorage) ->
ACTstorage.
%% Function that adds process trace-flags to the activity storage. Note that one
%% of the parameters is the return value from an inviso function. Meaning that
%% if the flags failed in their entirety, no activity will be saved. If only
%% some of the flags failed, we will not go through the effort of trying to find
%% out exactly which.
%% Returns a new activity storage structure.
add_tf_actstorage([{_Node,{error,_Reason}}|Rest],Op,TraceConfList,ACTstorage) ->
add_tf_actstorage(Rest,Op,TraceConfList,ACTstorage);
add_tf_actstorage([{Node,_Result}|Rest],Op,TraceConfList,ACTstorage) ->
true=ets:insert(ACTstorage,{Node,tf,{Op,TraceConfList}}),
add_tf_actstorage(Rest,Op,TraceConfList,ACTstorage);
add_tf_actstorage([],_,_,ACTstorage) ->
ACTstorage.
%% ------------------------------------------------------------------------------
%% Finds all activities associated with Node. Returns a list of them in the
%% same order as they were inserted.
get_activities_actstorage(Node,ACTstorage) ->
case ets:lookup(ACTstorage,Node) of
[] ->
false;
Result when list(Result) ->
{ok,lists:map(fun({_N,Type,What})->{Type,What} end,Result)}
end.
%% ------------------------------------------------------------------------------
%% Function removing all activity entries associated with Node. This is useful
%% if the Node disconnects for instance.
del_node_actstorage(Node,ACTstorage) ->
ets:delete(ACTstorage,Node),
ACTstorage.
%% ------------------------------------------------------------------------------