%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2006-2010. 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%
%%
%% Author: Lennart �hman, [email protected]
%%
-module(inviso_autostart_server).
-export([init/1]).
%% -----------------------------------------------------------------------------
%% Internal exports
%% -----------------------------------------------------------------------------
-export([cmd_file_interpreter_init/4]).
%% -----------------------------------------------------------------------------
%% This module provides a (well working) example of how to program an
%% autostart server responsible for initializing trace, setting patterns
%% and flags.
%%
%% The general idea is that this code spawns interpreter processes in order to
%% execute commands concurrently. Each of the interpreter processes opens one or
%% several files (in sequence) containing erlang function calls which are evaluated
%% in the interpreter process context.
%% The argument provided to init shall be a list of options controlling
%% how to initialize tracing, which file(s) to open and variable bindings.
%%
%% This autostart_server interpreters understands standard inviso trace case files.
%%
%% The runtime component provides an API very similar to the API provided
%% by the control component. It is therefore easy to translate inviso calls to
%% inviso_rt calls.
%%
%% This process may be killed by the inviso_rt process if stop_tracing is called.
%% The reason is that there is no time limit to the interpreter processes. Hence
%% they should be killed if tracing is not possible anylonger.
%% =============================================================================
%% -----------------------------------------------------------------------------
%% The independent autostart process spawned by the runtime component to carry
%% out initializations is spawened on this function (if using the example
%% autostart which comes with inviso).
%% ArgsFromConfig is as can be heard from the name comming from a paramater in
%% the autostart configuration file. Here it is supposed to be:
%% ArgsFromConfig=[ServerParam,...]
%% ServerParam={tracerdata,TracerData}|{cmdfiles,Files}|{bindings,Bindings}|
%% {translations,Translations}|{debug,DbgLevel}
%% TracerData=tracerdata given to inviso_rt:init_tracing/1 function.
%% Files=[FileNameSpecs,...] where each FileNameSpecs will be executed in
%% a separate process. Making each FileNameSpec parallel.
%% FileNameSpecs=[FileNameSpec,...]
%% FileNameSpec=FileName | {FileName,Bindings}
%% Bindings=[{Var,Value},...] variable environment understood by
%% erl_eval:exprs/2.
%% Translations=[Translation,...]
%% A translation file is a text-file with following tuples
%% Translation={{Mod,Func,Arity,{Mod2,Func2,ParamMF}}}|
%% {{Func,Arity,{Mod2,Func2,ParamMF}}}
%% ParamMF={M,F} | any()
%% Translates Mod:Func/Arity to Mod2:Func2 with the arguments to
%% Mod:Func translated using M:F/1. Note that ParamMF is not
%% necessarily an MF. If no translation shall be done, ParamMF
%% shall be anything else but an MF.
%% Also note that Mod is optional in a Translation. That means that
%% function calls without a module in the trace case file will
%% be translated according to that translation.
init(ArgsFromConfig) ->
case get_tracerdata_opts(ArgsFromConfig) of
{ok,TracerData} -> % Otherwise we can not start a trace!
case inviso_rt:init_tracing(TracerData) of
{ok,_Response} -> % Ok, tracing has been initiated.
case get_cmdfiles_opts(ArgsFromConfig) of
{ok,CmdFiles} -> % List of cmd-files.
Bindings=get_initialbindings_opts(ArgsFromConfig),
Translations=get_translations_opts(ArgsFromConfig),
Dbg=get_dbg_opts(ArgsFromConfig),
Procs=start_cmd_file_interpreters(CmdFiles,
Bindings,
Translations,
Dbg),
loop(Procs,Dbg); % Wait for procs to be done.
false -> % Then we can terminate normally.
true
end;
{error,Reason} -> % This is fault, lets terminate abnormally.
exit({inviso,{error,Reason}})
end;
false -> % Then there is not much use then.
true % Just terminate normally.
end.
%% -----------------------------------------------------------------------------
%% Help function which starts a process for each item found in the FileNames
%% list. The idea is that each item will be processed concurrently. The items
%% them selves may be a sequence of filenames.
%% Returns a list of spawned interpret processes.
start_cmd_file_interpreters([FileNames|Rest],Bindings,Translations,Dbg) ->
P=spawn_link(?MODULE,cmd_file_interpreter_init,[FileNames,Bindings,Translations,Dbg]),
MRef=erlang:monitor(process,P), % Can't trap exits in this process.
[{P,MRef}|start_cmd_file_interpreters(Rest,Bindings,Translations,Dbg)];
start_cmd_file_interpreters([],_,_,_) ->
[].
%% -----------------------------------------------------------------------------
%% The loop where this process simply waits for all of the interpreters to be
%% done. Note that that may take som time. An interpreter may take as long time
%% necessary to do its task.
loop(Procs,Dbg) ->
receive
{'DOWN',MRef,process,Pid,_Reason} ->
case lists:keysearch(MRef,1,Procs) of
{value,{Pid,_}} -> % It was an interpreter that terminated.
case lists:keydelete(MRef,1,Procs) of
[] -> % No more interpreters.
true; % Then terminate.
NewProcs ->
loop(NewProcs,Dbg)
end;
false ->
loop(Procs,Dbg)
end;
_ ->
loop(Procs,Dbg)
end.
%% -----------------------------------------------------------------------------
%% The interpret process.
%%
%% An interpreter process executes trace case files. Several interpreter processes
%% may be running in parallel. It is not within the scoop of this implementation
%% of an autostart server to solve conflicts. (You may implement your own autostart
%% server!).
%% An interpret process may run for as long as necessary. Hence the function called
%% within the trace case file can contain wait functions, waiting for a certain
%% system state to occure before continuing.
%% Note that this process also mixes global and local bindings. GlobalBindings
%% is a binding() structure, where LocalBindings is a list of {Var,Value}.
%% Further it is possible to let FileName be a {inviso,Func,Args} tuple instead.
%% -----------------------------------------------------------------------------
%% Init function for an interpreter process instance.
cmd_file_interpreter_init(FileNames,GlobalBindings,Translations,Dbg) ->
interpret_cmd_files(FileNames,GlobalBindings,Translations,Dbg).
interpret_cmd_files([{FileName,LocalBindings}|Rest],GlobalBindings,Translations,Dbg) ->
Bindings=join_local_and_global_vars(LocalBindings,GlobalBindings),
interpret_cmd_files_1(FileName,Bindings,Translations,Dbg),
interpret_cmd_files(Rest,GlobalBindings,Translations,Dbg);
interpret_cmd_files([],_,_,_) -> % Done, return nothing significant!
true;
interpret_cmd_files(FileName,GlobalBindings,Translations,Dbg) ->
interpret_cmd_files_1(FileName,GlobalBindings,Translations,Dbg).
% interpret_cmd_files(Rest,GlobalBindings,Translations,Dbg).
%% This is "inline" inviso calls.
interpret_cmd_files_1({inviso,F,Args},Bindings,Translations,Dbg) ->
{ok,Tokens1,_}=erl_scan:string("inviso:"++atom_to_list(F)++"("),
Tokens2=tokenize_args(Args),
{ok,Tokens3,_}=erl_scan:string(")."),
case erl_parse:parse_exprs(Tokens1++Tokens2++Tokens3) of
{ok,Exprs} ->
interpret_cmd_files_3(Bindings,Exprs,Translations,Dbg);
{error,_Reason} ->
error
end;
interpret_cmd_files_1({Mod,Func,Args},_Bindings,_Translations,_Dbg) ->
catch apply(Mod,Func,Args);
%% This is the case when it actually is a trace case file.
interpret_cmd_files_1(FileName,Bindings,Translations,Dbg) ->
case file:open(FileName,[read]) of
{ok,FD} ->
interpret_cmd_files_2(FD,Bindings,io:parse_erl_exprs(FD,""),Translations,Dbg),
file:close(FD);
{error,Reason} -> % Something wrong with the file.
inviso_rt_lib:debug(Dbg,interpret_cmd_files,[FileName,{error,Reason}])
end.
%% Help function which handles Exprs returned from io:parse_erl_exprs and
%% tries to eval them. It is the side-effects we are interested in, like
%% setting flags and patterns. Note that we will get a failure should there
%% be a variable conflict.
%% Also note that there is logic to translate control component API calls to
%% corresponding runtime component calls.
%% Returns nothing significant.
interpret_cmd_files_2(FD,Bindings,{ok,Exprs,_},Translations,Dbg) ->
{next,NewBindings}=interpret_cmd_files_3(Bindings,Exprs,Translations,Dbg),
interpret_cmd_files_2(FD,NewBindings,io:parse_erl_exprs(FD,""),Translations,Dbg);
interpret_cmd_files_2(FD,Bindings,{error,ErrorInfo,Line},Translations,Dbg) ->
inviso_rt_lib:debug(Dbg,parse_erl_exprs,[ErrorInfo,Line]),
interpret_cmd_files_2(FD,Bindings,io:parse_erl_exprs(FD,""),Translations,Dbg);
interpret_cmd_files_2(_,_,{eof,_},_,_) -> % End of file.
true.
interpret_cmd_files_3(Bindings,Exprs,Translations,Dbg) ->
case catch inviso_rt_lib:transform(Exprs,Translations) of
NewExprs when is_list(NewExprs) -> % We may have translated the API.
case catch erl_eval:exprs(NewExprs,Bindings) of
{'EXIT',Reason} ->
inviso_rt_lib:debug(Dbg,exprs,[Exprs,Bindings,{'EXIT',Reason}]),
{next,Bindings};
{value,_Val,NewBindings} -> % Only interested in the side effects!
{next,NewBindings}
end;
{'EXIT',Reason} ->
inviso_rt_lib:debug(Dbg,translate2runtime_funcs,[Exprs,Reason]),
{next,Bindings}
end.
%% Help function adding variables to a bindings structure. If the variable already
%% is assigned in the structure, it will be overridden. Returns a new
%% bindings structure.
join_local_and_global_vars([{Var,Val}|Rest],Bindings) when is_atom(Var) ->
join_local_and_global_vars(Rest,erl_eval:add_binding(Var,Val,Bindings));
join_local_and_global_vars([_|Rest],Bindings) ->
join_local_and_global_vars(Rest,Bindings);
join_local_and_global_vars([],Bindings) ->
Bindings.
%% Help function returning a string of tokens, including "," separation
%% between the arguments.
tokenize_args(Args=[Arg|Rest]) when length(Args)>1 ->
AbsTerm=erl_parse:abstract(Arg),
Tokens=erl_parse:tokens(AbsTerm),
{ok,Token,_}=erl_scan:string(","),
Tokens++Token++tokenize_args(Rest);
tokenize_args([Arg]) ->
AbsTerm=erl_parse:abstract(Arg),
erl_parse:tokens(AbsTerm);
tokenize_args([]) ->
"".
%% -----------------------------------------------------------------------------
%% -----------------------------------------------------------------------------
%% Help functions working on the options given as argument to init during spawn.
%% -----------------------------------------------------------------------------
get_tracerdata_opts(ArgsFromConfig) ->
case lists:keysearch(tracerdata,1,ArgsFromConfig) of
{value,{_,{mfa,{M,F,CompleteTDGargs}}}} -> % Dynamic tracerdata.
case catch apply(M,F,CompleteTDGargs) of
{'EXIT',_Reason} ->
false;
TracerData ->
{ok,TracerData}
end;
{value,{_,TracerData}} -> % Interpret this as static tracerdata.
{ok,TracerData};
false ->
false
end.
%% -----------------------------------------------------------------------------
get_cmdfiles_opts(ArgsFromConfig) ->
case lists:keysearch(cmdfiles,1,ArgsFromConfig) of
{value,{_,CmdFiles}} ->
{ok,CmdFiles};
false ->
false
end.
%% -----------------------------------------------------------------------------
get_initialbindings_opts(ArgsFromConfig) ->
case lists:keysearch(bindings,1,ArgsFromConfig) of
{value,{_,Bindings}} ->
Bindings;
false -> % Then we use empty bindings.
erl_eval:new_bindings()
end.
%% -----------------------------------------------------------------------------
get_translations_opts(ArgsFromConfig) ->
case lists:keysearch(translations,1,ArgsFromConfig) of
{value,{_,Translations}} ->
Translations;
false -> % This becomes nearly point less.
[]
end.
%% -----------------------------------------------------------------------------
get_dbg_opts(ArgsFromConfig) ->
case lists:keysearch(debug,1,ArgsFromConfig) of
{value,{_,DbgLevel}} ->
DbgLevel;
false ->
off
end.
%% -----------------------------------------------------------------------------
%% EOF