%% %% %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, lennart.ohman@st.se %% -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