% ``The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved via the world wide web at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB. %% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings %% AB. All Rights Reserved.'' %% %% $Id$ %% %% Description: %% Support module to the inviso tool. %% %% Authors: %% Lennart Öhman, lennart.ohman@st.se %% ----------------------------------------------------------------------------- -module(inviso_tool_lib). %% ----------------------------------------------------------------------------- %% Exported library APIs %% ----------------------------------------------------------------------------- -export([inviso_cmd/3,expand_module_names/3,make_patterns/7,std_tdg/2]). -export([mk_tdg_args/2,mk_complete_tdg_args/2,get_datetime_from_tdg_args/1]). -export([debug/3]). %% ----------------------------------------------------------------------------- %% Constants. %% ----------------------------------------------------------------------------- -define(DBG_OFF,off). % No internal debug indicator. %% ============================================================================= %% Functions for inviso_cmd %% ============================================================================= %% Help function which executes a trace control call. The reason for having a special %% function is that we either want to do rpc if the trace control component is %% located on another Erlang node than this one. Or call trace_c directly if %% it actually is on this node. %% Returns whatever the inviso function returns. In case of badrpc it is wrapped %% in an error-tuple. inviso_cmd(NodeName,Func,Args) -> case node() of NodeName -> % Control component on this node. apply(inviso,Func,Args); _ -> % On other node, must do RPC. case rpc:call(NodeName,inviso,Func,Args) of {badrpc,Reason} -> {error,{badrpc,Reason}}; Result -> Result end end. %% ----------------------------------------------------------------------------- %% ============================================================================= %% Functions for expand_module_names %% ============================================================================= %% Help function which expands the module name depending on how it is expressed. %% Setting Nodes to 'void' makes it non-distributed, expanding only here. %% The following special cases are handled: %% '_' =All modules, no expansion into individual module names. Instead %% it is intended to use the '_' mechanism in the trace_pattern BIF. %% Can therefore not be combined with a directory regexp. %% "*" =Is translated into ".*". %% "abc"=Means ".*abc.*". Can only be used for module or directory names %% containing upper or lowercase, digits and a slash. %% Returns {multinode_expansion,NodeModules}, %% {singlenode_expansion,Modules}, %% 'module', 'wildcard' or {error,Reason}, %% To limit the places where expansion is done, the option {expand_only_at,Node} %% can be provided in the Opts list. %% In the non-distributed case the singlenode_expansion will be returned. expand_module_names(_Nodes,Mod={_,'_'},_) -> {error,{faulty_regexp_combination,Mod}}; expand_module_names(Nodes,{DirStr,ModStr},Opts) when is_list(DirStr), is_list(ModStr) -> case expand_module_names_special_regexp(DirStr) of {ok,NewDirStr} -> case expand_module_names_special_regexp(ModStr) of {ok,NewModStr} -> expand_module_names_2(Nodes,NewDirStr,NewModStr,Opts); {error,_Reason} -> {error,{faulty_regexp,ModStr}} end; {error,_Reason} -> {error,{faulty_regexp,DirStr}} end; expand_module_names(_,'_',_Opts) -> % If we want to trace all modules wildcard; % we shall not expand it. expand_module_names(_Nodes,Mod,_Opts) when is_atom(Mod) -> module; % If it is an atom, no expansion. expand_module_names(Nodes,"*",Opts) -> % Treat this as a reg.exp. expand_module_names(Nodes,".*",Opts); expand_module_names(Nodes,ModStr,Opts) when is_list(ModStr) -> case expand_module_names_special_regexp(ModStr) of {ok,NewModStr} -> expand_module_names_2(Nodes,NewModStr,Opts); {error,_Reason} -> {error,{faulty_regexp,ModStr}} end. expand_module_names_2(void,ModStr,Opts) -> % Non-distributed case. {singlenode_expansion,inviso_rt_lib:expand_regexp(ModStr,Opts)}; expand_module_names_2(Nodes,ModStr,Opts) -> case get_expand_regexp_at_opts(Opts) of {ok,Node} -> % Expansion only at this node. case inviso_rt_lib:expand_regexp([Node],ModStr,Opts) of [{Node,Modules}] when is_list(Modules) -> {singlenode_expansion,Modules}; [{Node,_}] -> % Most likely badrpc. {error,{faulty_node,Node}} end; false -> % Expand on all nodes. Result=inviso_rt_lib:expand_regexp(Nodes,ModStr,Opts), {multinode_expansion,Result} end. expand_module_names_2(void,DirStr,ModStr,Opts) -> % Non-distributed case. {singlenode_expansion,inviso_rt_lib:expand_regexp(DirStr,ModStr,Opts)}; expand_module_names_2(Nodes,DirStr,ModStr,Opts) -> case get_expand_regexp_at_opts(Opts) of {ok,Node} -> % Expansion only at this node. case inviso_rt_lib:expand_regexp([Node],DirStr,ModStr,Opts) of [{Node,Modules}] when is_list(Modules) -> {singlenode_expansion,Modules}; [{Node,_}] -> % Most likely badrpc. {error,{faulty_node,Node}} end; false -> % Expand on all nodes. Result=inviso_rt_lib:expand_regexp(Nodes,DirStr,ModStr,Opts), {multinode_expansion,Result} end. %% Help function which converts a special regexp into a proper one. With %% special regexps we mean e.g:"abc" which is supposed to mean ".*abc.*". %% Always returns a regexp or {error,Reason}. expand_module_names_special_regexp(Str) -> StrLen=length(Str), case regexp:first_match(Str,"[0-9a-zA-Z_/]*") of {match,1,StrLen} -> % Ok, it is the special case. {ok,".*"++Str++".*"}; % Convert it to a proper regexp. {match,_,_} -> {ok,Str}; % Keep it and hope it is a regexp. nomatch -> {ok,Str}; % Keep it and hope it is a regexp. {error,Reason} -> % Can't continue with this! {error,Reason} end. %% ----------------------------------------------------------------------------- %% ============================================================================= %% Functions for make_pattern. %% ============================================================================= -define(DEPTH,3). % Max recursive safety catch depth. %% Help function that creates trace-patterns for each module in the list. %% It can handle both lists of modules or lists of nodes and modules. %% It will also in the process apply safety catches, if such are not disabled, %% in order to prevent certain patterns to form. %% The safety catch function is supposed to return either 'ok' or {new,NewTracePattern}. %% Where the NewTracePattern is a list of zero or more {M,F,Arity,MS}. The %% NewTracePatter is then the replacement for the tried trace-pattern. %% Note that the new trace-pattern(s) are also tried against all safety catches. %% This can possibly result in even replacements of the replacements. There is %% a depth meter to prevent the safety catch mechanism from circularly expanding %% trace patterns for ever. %% Returns a list of [{Node,PatternList},...] or [Pattern,...]. %% The latter is used when the modules have been expanded on a single node. make_patterns(Catches,Opts,Dbg,NodeModsOrMods,F,A,MS) -> OwnArg=get_ownarg_opts(Opts), case get_disable_safety_opts(Opts) of true -> % Do not use the safety catches. make_patterns_2(void,OwnArg,Dbg,NodeModsOrMods,F,A,MS); false -> make_patterns_2(Catches,OwnArg,Dbg,NodeModsOrMods,F,A,MS) end. make_patterns_2(Catches,OwnArg,Dbg,[{Node,Mods}|Rest],F,A,MS) when is_list(Mods) -> TPs=make_patterns_3(Catches,OwnArg,Dbg,Mods,F,A,MS,[]), [{Node,join_patterns(TPs)}|make_patterns_2(Catches,OwnArg,Dbg,Rest,F,A,MS)]; make_patterns_2(Catches,OwnArg,Dbg,[{_Node,_}|Rest],F,A,MS) -> % badrpc!? make_patterns_2(Catches,OwnArg,Dbg,Rest,F,A,MS); make_patterns_2(Catches,OwnArg,Dbg,Modules,F,A,MS) when is_list(Modules) -> TPs=make_patterns_3(Catches,OwnArg,Dbg,Modules,F,A,MS,[]), join_patterns(TPs); make_patterns_2(_,_,_,[],_,_,_) -> []. make_patterns_3(void,OwnArg,Dbg,[M|Rest],F,A,MS,Result) -> % S-catches not used! make_patterns_3(void,OwnArg,Dbg,Rest,F,A,MS,[{M,F,A,MS}|Result]); make_patterns_3(Catches,OwnArg,Dbg,[M|Rest],F,A,MS,Result) -> NewTPs=try_safety_catches(Catches,OwnArg,[{M,F,A,MS}],Dbg,[],?DEPTH), make_patterns_3(Catches,OwnArg,Dbg,Rest,F,A,MS,[NewTPs|Result]); make_patterns_3(_,_,_,[],_,_,_,Result) -> lists:flatten(Result). try_safety_catches(_Catches,_OwnArg,TPs,Dbg,_Accum,0) -> % Max depth here! debug(max_catch_depth,Dbg,[TPs]), TPs; % Just return them unchanged. try_safety_catches(Catches,OwnArg,[TP={M,F,A,MS}|Rest],Dbg,Accum,Depth) -> case try_safety_catch(Catches,OwnArg,M,F,A,MS,Dbg) of ok -> % This pattern is safe! try_safety_catches(Catches,OwnArg,Rest,Dbg,[TP|Accum],?DEPTH); {new,NewTPs} -> % Then we must try them too! NewTPs2=try_safety_catches(Catches,OwnArg,NewTPs,Dbg,[],Depth-1), try_safety_catches(Catches,OwnArg,Rest,Dbg,[NewTPs2|Accum],?DEPTH) end; try_safety_catches(_,_,[],_,Accum,_) -> Accum. try_safety_catch([{SafetyMod,SafetyFunc}|Rest],OwnArg,M,F,A,MS,Dbg) -> case (catch apply(SafetyMod,SafetyFunc,[M,F,A,MS,OwnArg])) of ok -> % This catch has no oppinion about it. try_safety_catch(Rest,OwnArg,M,F,A,MS,Dbg); % Continue with the next. {new,NewTPs} -> % Replace it with this or these new. debug(safety_catch,Dbg,[new,{SafetyMod,SafetyFunc},M,F,A,MS,NewTPs]), {new,NewTPs}; % and stop trying safety cathes. {'EXIT',Reason} -> % Something wrong with the safety catch. debug(safety_catch,Dbg,['EXIT',{SafetyMod,SafetyFunc},M,F,A,MS,Reason]), try_safety_catch(Rest,OwnArg,M,F,A,MS,Dbg) % Skip it and go on. end; try_safety_catch([],_,_,_,_,_,_) -> ok. % Since it passed all, it is safe! %% ----------------------------------------------------------------------------- %% Help function that joins patterns together. This is necessary since you can %% only set the pattern once for a module-function-arity. This function can not %% remove conflicting match-spec "commands". Match-specs will simply be concatenated. %% Returns a list of patterns where each mod-func-arity is unique. join_patterns(Patterns) -> join_patterns_2(Patterns,[]). join_patterns_2([{M,F,Arity,MS}|Rest],Result) -> case join_patterns_is_already_done(M,F,Arity,Result) of false -> % No we have not collapsed this one. case join_patterns_3(M,F,Arity,Rest) of [] -> % No this combination is unique. join_patterns_2(Rest,[{M,F,Arity,MS}|Result]); MSs -> % We got a list of all other TPs. join_patterns_2(Rest,[{M,F,Arity,MS++MSs}|Result]) end; true -> % We already joined this M-F-Arity. join_patterns_2(Rest,Result) % Simply skip it, already done. end; join_patterns_2([],Result) -> Result. % Reversed but does not matter! %% Help function checking if we have already built a trace-pattern for %% this M-F-Arity. If so, the found M-F-Arity is already handled. join_patterns_is_already_done(M,F,Arity,[{M,F,Arity,_}|_]) -> true; join_patterns_is_already_done(M,F,Arity,[_|Rest]) -> join_patterns_is_already_done(M,F,Arity,Rest); join_patterns_is_already_done(_,_,_,[]) -> false. %% Help function which simply concatenates all match-specs for this %% M-F-Arity. join_patterns_3(M,F,Arity,[{M,F,Arity,MS}|Rest]) -> [MS|join_patterns_3(M,F,Arity,Rest)]; join_patterns_3(M,F,Arity,[_|Rest]) -> join_patterns_3(M,F,Arity,Rest); join_patterns_3(_,_,_,[]) -> []. %% ----------------------------------------------------------------------------- %% ============================================================================= %% Function for tracer data creation. %% ============================================================================= -define(I2L(Arg),integer_to_list(Arg)). %% The inviso_tool uses a tracer-data generator function to create the tracer_data %% specification for each node that shall participate in tracing controlled %% through the inviso-tool. If no own tracer data generator function is specified, %% this function is used. std_tdg(Node,{{Y,Mo,D},{H,Mi,S}}) -> NameStr=atom_to_list(Node)++"_"++?I2L(Y)++"-"++?I2L(Mo)++"-"++?I2L(D)++"_"++ ?I2L(H)++"-"++?I2L(Mi)++"-"++?I2L(S), LogTD={file,NameStr++".log"}, TiTD={file,NameStr++".ti"}, [{trace,LogTD},{ti,TiTD}]. %% ------------------------------------------------------------------------------ %% mk_tdg_args(DateTime,Args)=TDGargs %% DateTime={Date,Time} %% Date=tuple(), %% Time=tuple(), %% Args=list() %% TDGargs=list(), %% Creates the TDGargs list used when calling functions making the CompleteTDGargs. mk_tdg_args(DateTime,Args) -> [DateTime|Args]. %% ------------------------------------------------------------------------------ %% mk_complete_tdg_args(Node,TDGargs)=CompleteTDGargs %% Returns the list of all arguments a tracer data generator function must accept. mk_complete_tdg_args(Node,TDGargs) -> [Node|TDGargs]. %% ------------------------------------------------------------------------------ %% get_datetime_from_tdg_args(TDGargs)=DateTime %% Function returning the DateTime tuple in a TDGargs list. get_datetime_from_tdg_args([DateTime|_]) -> DateTime. %% ------------------------------------------------------------------------------ %% ============================================================================= %% Various help functions. %% ============================================================================= %% ----------------------------------------------------------------------------- %% Functions handling set trace-pattern options. %% ----------------------------------------------------------------------------- %% Gets additional arguments given to various configurable functions. %% Returns a list. get_ownarg_opts(Opts) -> case lists:keysearch(arg,1,Opts) of {value,{_,OwnArg}} when is_list(OwnArg) -> OwnArg; {value,{_,OwnArg}} -> [OwnArg]; false -> [] end. %% ----------------------------------------------------------------------------- get_disable_safety_opts(Opts) -> case lists:member(disable_safety,Opts) of true -> true; false -> false end. %% ----------------------------------------------------------------------------- get_expand_regexp_at_opts(Opts) -> case lists:keysearch(expand_only_at,1,Opts) of {value,{_,Node}} when is_atom(Node) -> {ok,Node}; _ -> false end. %% ----------------------------------------------------------------------------- %% ============================================================================= %% Functions for the internal debugging system. %% ============================================================================= %% The debug system is meant to provide tracing of inviso tool at different levels. %% %% debug(What,Level,Description) -> nothing significant. %% What : controls what kind of event. This can both be certain parts of the tool %% as well as certain levels (info to catastrophy). %% Level: Determines if What shall be printed or not. %% Description: this is what happend. debug(_What,?DBG_OFF,_Description) -> true; % Debug is off, no action. debug(What,On,Description) -> debug_2(What,On,Description). debug_2(What,_,Description) -> io:format("INVISO DEBUG:~w, ~p~n",[What,Description]). %% -----------------------------------------------------------------------------