diff options
Diffstat (limited to 'lib/runtime_tools/src')
-rw-r--r-- | lib/runtime_tools/src/Makefile | 109 | ||||
-rw-r--r-- | lib/runtime_tools/src/dbg.erl | 1749 | ||||
-rw-r--r-- | lib/runtime_tools/src/erts_alloc_config.erl | 670 | ||||
-rw-r--r-- | lib/runtime_tools/src/inviso_as_lib.erl | 155 | ||||
-rw-r--r-- | lib/runtime_tools/src/inviso_autostart.erl | 208 | ||||
-rw-r--r-- | lib/runtime_tools/src/inviso_autostart_server.erl | 311 | ||||
-rw-r--r-- | lib/runtime_tools/src/inviso_rt.erl | 2895 | ||||
-rw-r--r-- | lib/runtime_tools/src/inviso_rt_lib.erl | 474 | ||||
-rw-r--r-- | lib/runtime_tools/src/inviso_rt_meta.erl | 1207 | ||||
-rw-r--r-- | lib/runtime_tools/src/observer_backend.erl | 320 | ||||
-rw-r--r-- | lib/runtime_tools/src/percept_profile.erl | 196 | ||||
-rw-r--r-- | lib/runtime_tools/src/runtime_tools.app.src | 32 | ||||
-rw-r--r-- | lib/runtime_tools/src/runtime_tools.appup.src | 19 | ||||
-rw-r--r-- | lib/runtime_tools/src/runtime_tools.erl | 49 | ||||
-rw-r--r-- | lib/runtime_tools/src/runtime_tools_sup.erl | 43 |
15 files changed, 8437 insertions, 0 deletions
diff --git a/lib/runtime_tools/src/Makefile b/lib/runtime_tools/src/Makefile new file mode 100644 index 0000000000..4f831f3dd8 --- /dev/null +++ b/lib/runtime_tools/src/Makefile @@ -0,0 +1,109 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-2009. 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% +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(RUNTIME_TOOLS_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/runtime_tools-$(VSN) + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- + +MODULES= \ + erts_alloc_config \ + inviso_rt \ + inviso_rt_meta \ + inviso_rt_lib \ + inviso_as_lib \ + inviso_autostart \ + inviso_autostart_server \ + runtime_tools \ + runtime_tools_sup \ + dbg \ + percept_profile \ + observer_backend +HRL_FILES= ../include/observer_backend.hrl + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) + +APP_FILE= runtime_tools.app + +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBIN)/$(APP_FILE) + +APPUP_FILE= runtime_tools.appup + +APPUP_SRC= $(APPUP_FILE).src +APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += -I../include + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f errs core *~ + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + + + + + + + diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl new file mode 100644 index 0000000000..66ac0422eb --- /dev/null +++ b/lib/runtime_tools/src/dbg.erl @@ -0,0 +1,1749 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. 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% +%% +-module(dbg). +-export([p/1,p/2,c/3,c/4,i/0,start/0,stop/0,stop_clear/0,tracer/0, + tracer/2, tracer/3, get_tracer/0, get_tracer/1, tp/2, tp/3, tp/4, + ctp/0, ctp/1, ctp/2, ctp/3, tpl/2, tpl/3, tpl/4, ctpl/0, ctpl/1, + ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3, ltp/0, wtp/1, rtp/1, + dtp/0, dtp/1, n/1, cn/1, ln/0, h/0, h/1]). + +-export([trace_port/2, flush_trace_port/0, flush_trace_port/1, + trace_port_control/1, trace_port_control/2, trace_client/2, + trace_client/3, stop_trace_client/1]). + +-export([transform_flags/1,dhandler/2]). + +-export([fun2ms/1]). + +%% Local exports +-export([erlang_trace/3,get_info/0]). + +%% Debug exports +-export([wrap_presort/2, wrap_sort/2, wrap_postsort/1, wrap_sortfix/2, + match_front/2, match_rear/2, + match_0_9/1]). + + +%%% Shell callable utility +fun2ms(ShellFun) when is_function(ShellFun) -> + % Check that this is really a shell fun... + case erl_eval:fun_data(ShellFun) of + {fun_data,ImportList,Clauses} -> + case ms_transform:transform_from_shell( + ?MODULE,Clauses,ImportList) of + {error,[{_,[{_,_,Code}|_]}|_],_} -> + io:format("Error: ~s~n", + [ms_transform:format_error(Code)]), + {error,transform_error}; + Else -> + Else + end; + false -> + exit({badarg,{?MODULE,fun2ms, + [function,called,with,real,'fun', + should,be,transformed,with, + parse_transform,'or',called,with, + a,'fun',generated,in,the, + shell]}}) + end. + + +%%% Client functions. + +%% +%% n(Node) -> {ok, Node} | {error, Reason} +%% Adds Node to the list of traced nodes. +%% +n(Node) when Node =:= node() -> + {error, cant_add_local_node}; +n(Node) -> + case (catch net_adm:ping(Node)) of + {'EXIT',_} -> + {error, {bad_node, Node}}; + pang -> + {error, {nodedown, Node}}; + pong -> + req({add_node, Node}); + Other -> + {error, Other} + end. + +%% +%% cn(Node) -> ok +%% Remove Node from the list of traced nodes. +%% +cn(Node) -> + req({remove_node, Node}). + +%% +%% ln() -> ok +%% List traced nodes +%% +ln() -> + lists:foreach(fun(X) -> + io:format("~p~n",[X]) + end, + req(get_nodes)), + ok. + +%% +%% tp/tpl(Module, Pattern) | tp/tpl(Module,Function,Pattern) | +%% tp/tpl(Module,Function,Arity,Pattern) | tp/tpl({M,F,A},Pattern) +%% -> {ok, [{matched,Node,N}]} | +%% {ok, [{matched,Node,N}, {saved,M}]} | +%% {ok, [{saved,M}]} | +%% {error, Reason} +%% Set trace pattern for function or group of functions. +%% +tp(Module, Function, Pattern) -> + do_tp({Module, Function, '_'}, Pattern, []). +tp(Module, Function, Arity, Pattern) -> + do_tp({Module, Function, Arity}, Pattern, []). +tp(Module, Pattern) when is_atom(Module) -> + do_tp({Module, '_', '_'}, Pattern, []); +tp({_Module, _Function, _Arity} = X, Pattern) -> + do_tp(X,Pattern,[]). +tpl(Module, Function, Pattern) -> + do_tp({Module, Function, '_'}, Pattern, [local]). +tpl(Module, Function, Arity, Pattern) -> + do_tp({Module, Function, Arity}, Pattern, [local]). +tpl(Module, Pattern) when is_atom(Module) -> + do_tp({Module, '_', '_'}, Pattern, [local]); +tpl({_Module, _Function, _Arity} = X, Pattern) -> + do_tp(X,Pattern,[local]). +do_tp({_Module, _Function, _Arity} = X, Pattern, Flags) + when is_integer(Pattern); + is_atom(Pattern) -> + case ets:lookup(get_pattern_table(), Pattern) of + [{_,NPattern}] -> + do_tp(X, binary_to_term(NPattern), Flags); + _ -> + {error, unknown_pattern} + end; +do_tp({Module, _Function, _Arity} = X, Pattern, Flags) when is_list(Pattern) -> + Nodes = req(get_nodes), + case Module of + '_' -> + ok; + M when is_atom(M) -> + %% Try to load M on all nodes + lists:foreach(fun(Node) -> + rpc:call(Node, M, module_info, []) + end, + Nodes) + end, + case lint_tp(Pattern) of + {ok,_} -> + SaveInfo = case save_pattern(Pattern) of + N when is_integer(N), N > 0; is_atom(N) -> + [{saved, N}]; + _ -> + [] + end, + {ok, do_tp_on_nodes(Nodes, X, Pattern, Flags) ++ SaveInfo}; + Other -> + Other + end. + +%% All nodes are handled the same way - also the local node if it is traced +do_tp_on_nodes(Nodes, MFA, P, Flags) -> + lists:map(fun(Node) -> + case rpc:call(Node,erlang,trace_pattern,[MFA,P, Flags]) of + N when is_integer(N) -> + {matched, Node, N}; + Else -> + {matched, Node, 0, Else} + end + end, + Nodes). + +%% +%% ctp/ctpl(Module) | ctp/ctpl(Module,Function) | +%% ctp/ctpl(Module,Function,Arity) | ctp/ctpl({M,F,A}) -> +%% {ok, [{matched, N}]} | {error, Reason} +%% Clears trace pattern for function or group of functions. +%% +ctp() -> + do_ctp({'_','_','_'},[]). +ctp(Module, Function) -> + do_ctp({Module, Function, '_'}, []). +ctp(Module, Function, Arity) -> + do_ctp({Module, Function, Arity}, []). +ctp(Module) when is_atom(Module) -> + do_ctp({Module, '_', '_'}, []); +ctp({_Module, _Function, _Arity} = X) -> + do_ctp(X,[]). +ctpl() -> + do_ctp({'_', '_', '_'}, [local]). +ctpl(Module, Function) -> + do_ctp({Module, Function, '_'}, [local]). +ctpl(Module, Function, Arity) -> + do_ctp({Module, Function, Arity}, [local]). +ctpl(Module) when is_atom(Module) -> + do_ctp({Module, '_', '_'}, [local]); +ctpl({_Module, _Function, _Arity} = X) -> + do_ctp(X,[local]). +ctpg() -> + do_ctp({'_', '_', '_'}, [global]). +ctpg(Module, Function) -> + do_ctp({Module, Function, '_'}, [global]). +ctpg(Module, Function, Arity) -> + do_ctp({Module, Function, Arity}, [global]). +ctpg(Module) when is_atom(Module) -> + do_ctp({Module, '_', '_'}, [global]); +ctpg({_Module, _Function, _Arity} = X) -> + do_ctp(X,[global]). +do_ctp({Module, Function, Arity},[]) -> + do_ctp({Module, Function, Arity},[global]), + do_ctp({Module, Function, Arity},[local]); +do_ctp({_Module, _Function, _Arity}=MFA,Flags) -> + Nodes = req(get_nodes), + {ok,do_tp_on_nodes(Nodes,MFA,false,Flags)}. + +%% +%% ltp() -> ok +%% List saved and built-in trace patterns. +%% +ltp() -> + pt_doforall(fun({X, El},_Ignore) -> + io:format("~p: ~p~n", [X,El]) + end,[]). + +%% +%% dtp() | dtp(N) -> ok +%% Delete saved pattern with number N or all saved patterns +%% +%% Do not delete built-in trace patterns. +dtp() -> + pt_doforall(fun ({Key, _}, _) when is_integer(Key) -> + dtp(Key); + ({_, _}, _) -> + ok + end, + []). +dtp(N) when is_integer(N) -> + ets:delete(get_pattern_table(), N), + ok; +dtp(_) -> + ok. + +%% +%% wtp(FileName) -> ok | {error, Reason} +%% Writes all current saved trace patterns to a file. +%% +%% Actually write the built-in trace patterns too. +wtp(FileName) -> + case file:open(FileName,[write]) of + {error, Reason} -> + {error, Reason}; + {ok, File} -> + pt_doforall(fun ({_, Val}, _) when is_list(Val) -> + io:format(File, "~p.~n", [Val]); + ({_, _}, _) -> + ok + end, + []), + file:close(File), + ok + end. + +%% +%% rtp(FileName) -> ok | {error, Reason} +%% Reads in previously saved pattern file and merges the contents +%% with what's there now. +%% +%% So the saved built-in trace patterns will merge with +%% the already existing, which should be the same. +rtp(FileName) -> + T = get_pattern_table(), + case file:consult(FileName) of + {error, Reason1} -> + {error, {read_error, Reason1}}; + {ok, Data} -> + case check_list(Data) of + ok -> + lists:foreach(fun(X) -> + save_pattern(X,T) + end, Data), + ok; + {error, Reason2} -> + {error, {file_format_error, Reason2}} + end + end. + +tracer() -> + tracer(process, {fun dhandler/2,user}). + +tracer(port, Fun) when is_function(Fun) -> + start(Fun); + +tracer(port, Port) when is_port(Port) -> + start(fun() -> Port end); + +tracer(process, {Handler,HandlerData}) -> + start(fun() -> start_tracer_process(Handler, HandlerData) end). + + +remote_tracer(port, Fun) when is_function(Fun) -> + remote_start(Fun); + +remote_tracer(port, Port) when is_port(Port) -> + remote_start(fun() -> Port end); + +remote_tracer(process, {Handler,HandlerData}) -> + remote_start(fun() -> start_tracer_process(Handler, HandlerData) end). + +remote_start(StartTracer) -> + case (catch StartTracer()) of + {'EXIT', Reason} -> + {error, Reason}; + Tracer -> + {ok,Tracer} + end. + +%% +%% tracer(Node,Type,Data) -> {ok, Node} | {error, Reason} +%% Add Node to the list of traced nodes and a trace port defined by +%% Type and Data is started on Node. +%% +tracer(Node,Type,Data) when Node =:= node() -> + case tracer(Type,Data) of + {ok,_Dbg} -> {ok,Node}; + Error -> Error + end; +tracer(Node,Type,Data) -> + case (catch net_adm:ping(Node)) of + {'EXIT',_} -> + {error, {bad_node, Node}}; + pang -> + {error, {nodedown, Node}}; + pong -> + req({add_node, Node, Type, Data}); + Other -> + {error, Other} + end. + +flush_trace_port() -> + trace_port_control(flush). +flush_trace_port(Node) -> + trace_port_control(Node, flush). + +trace_port_control(Operation) -> + trace_port_control(node(), Operation). + +trace_port_control(Node, flush) -> + Ref = erlang:trace_delivered(all), + receive + {trace_delivered,all,Ref} -> ok + end, + case trace_port_control(Node, $f, "") of + {ok, [0]} -> + ok; + {ok, _} -> + {error, not_supported_by_trace_driver}; + Other -> + Other + end; +trace_port_control(Node,get_listen_port) -> + case trace_port_control(Node,$p, "") of + {ok, <<0, IpPort:16>>} -> + {ok, IpPort}; + {ok, _Other} -> + {error, not_supported_by_trace_driver}; + Other -> + Other + end. + +trace_port_control(Node, Command, Arg) -> + case get_tracer(Node) of + {ok, Port} when is_port(Port) -> + {ok, catch rpc:call(Node,erlang,port_control,[Port, Command, Arg])}; + _ -> + {error, no_trace_driver} + end. + + + + +trace_port(file, {Filename, wrap, Tail}) -> + trace_port(file, {Filename, wrap, Tail, 128*1024}); +trace_port(file, {Filename, wrap, Tail, WrapSize}) -> + trace_port(file, {Filename, wrap, Tail, WrapSize, 8}); +trace_port(file, {Filename, wrap, Tail, WrapSize, WrapCnt}) + when is_list(Tail), + is_integer(WrapSize), WrapSize >= 0, WrapSize < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, WrapSize, WrapCnt, 0}); +trace_port(file, {Filename, wrap, Tail, {time, WrapTime}, WrapCnt}) + when is_list(Tail), + is_integer(WrapTime), WrapTime >= 1, WrapTime < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, 0, WrapCnt, WrapTime}); +trace_port(file, Filename) -> + trace_port1(file, Filename, nowrap); + +trace_port(ip, Portno) when is_integer(Portno) -> + trace_port(ip,{Portno,50}); + +trace_port(ip, {Portno, Qsiz}) when is_integer(Portno), is_integer(Qsiz) -> + fun() -> + Driver = "trace_ip_drv", + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + L = lists:flatten( + io_lib:format("~s ~p ~p 2", + [Driver, Portno, Qsiz])), + open_port({spawn, L}, [eof]) + end. + +trace_port1(file, Filename, Options) -> + Driver = "trace_file_drv", + fun() -> + Name = filename:absname(Filename), + %% Absname is needed since the driver uses + %% the supplied name without further investigations, + %% and if the name is relative the resulting path + %% might be too long which can cause a bus error + %% on vxworks instead of a nice error code return. + %% Also, the absname must be found inside the fun, + %% in case the actual node where the port shall be + %% started is on another node (or even another host) + {Wrap, Tail} = + case Options of + {wrap, T, WrapSize, WrapCnt, WrapTime} -> + {lists:flatten( + io_lib:format("w ~p ~p ~p ~p ", + [WrapSize, WrapCnt, WrapTime, + length(Name)])), + T}; + nowrap -> + {"", ""} + end, + Command = Driver ++ " " ++ Wrap ++ "n " ++ Name ++ Tail, + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + if element(1, Options) == wrap -> + %% Delete all files from any previous wrap log + Files = wrap_postsort(wrap_presort(Name, Tail)), + lists:foreach( + fun(N) -> file:delete(N) end, + Files); + true -> ok + end, + open_port({spawn, Command}, [eof]) + end. + + +trace_client(file, Filename) -> + trace_client(file, Filename, {fun dhandler/2,user}); +trace_client(follow_file, Filename) -> + trace_client(follow_file, Filename, {fun dhandler/2,user}); +trace_client(ip, Portno) when is_integer(Portno) -> + trace_client1(ip, {"localhost", Portno}, {fun dhandler/2,user}); +trace_client(ip, {Host, Portno}) when is_integer(Portno) -> + trace_client1(ip, {Host, Portno}, {fun dhandler/2,user}). + +trace_client(file, {Filename, wrap, Tail}, FD) -> + trace_client(file, {Filename, wrap, Tail, 128*1024}, FD); +trace_client(file, {Filename, wrap, Tail, WrapSize}, FD) -> + trace_client(file, {Filename, wrap, Tail, WrapSize, 8}, FD); +trace_client(file, + {_Filename, wrap, Tail, _WrapSize, WrapCnt} = WrapSpec, + {Fun, _Data} = FD) + when is_list(Tail), is_function(Fun), is_integer(WrapCnt), WrapCnt >= 1 -> + trace_client1(file, WrapSpec, FD); +trace_client(file, Filename, {Fun, Data} ) when is_function(Fun) -> + trace_client1(file, Filename, {Fun, Data}); +trace_client(follow_file, Filename, {Fun, Data} ) when is_function(Fun) -> + trace_client1(follow_file, Filename, {Fun, Data}); +trace_client(ip, Portno, {Fun, Data}) when is_integer(Portno), is_function(Fun) -> + trace_client1(ip, {"localhost", Portno}, {Fun, Data}); +trace_client(ip, {Host, Portno}, {Fun, Data}) when is_integer(Portno), + is_function(Fun) -> + trace_client1(ip, {Host, Portno}, {Fun, Data}). + +trace_client1(Type, OpenData, {Handler,HData}) -> + case req({link_to, + spawn( + fun() -> + tc_loop(gen_reader(Type, OpenData), Handler, HData) + end)}) of + {ok, Pid} -> + Pid; + Other -> + Other + end. + +stop_trace_client(Pid) when is_pid(Pid) -> + process_flag(trap_exit,true), + link(Pid), + exit(to_pidspec(Pid),abnormal), + Res = receive + {'EXIT', Pid, _} -> + ok + after 5000 -> + {error, timeout} + end, + process_flag(trap_exit,false), + Res. + +p(Pid) -> + p(Pid, [m]). + +p(Pid, Flags) when is_atom(Flags) -> + p(Pid, [Flags]); +p(Pid, Flags) -> + req({p,Pid,Flags}). + +i() -> req(i). + +c(M, F, A) -> + c(M, F, A, all). +c(M, F, A, Flags) when is_atom(Flags) -> + c(M, F, A, [Flags]); +c(M, F, A, Flags) -> + case transform_flags(Flags) of + {error,Reason} -> {error,Reason}; + Flags1 -> + tracer(), + {ok, Tracer} = get_tracer(), + S = self(), + Pid = spawn(fun() -> c(S, M, F, A, [{tracer, Tracer} | Flags1]) end), + Mref = erlang:monitor(process, Pid), + receive + {'DOWN', Mref, _, _, Reason} -> + stop_clear(), + {error, Reason}; + {Pid, Res} -> + erlang:demonitor(Mref), + receive {'DOWN', Mref, _, _, _} -> ok after 0 -> ok end, + %% 'sleep' prevents the tracer (recv_all_traces) from + %% receiving garbage {'EXIT',...} when dbg i stopped. + timer:sleep(1), + stop_clear(), + Res + end + end. + +c(Parent, M, F, A, Flags) -> + %% The trace BIF is used directly here instead of the existing function + %% p/2. The reason is that p/2 (when stopping trace) sends messages which + %% we don't want to show up in this simple tracing from the shell. + erlang:trace(self(), true, Flags), + Res = apply(M, F, A), + erlang:trace(self(), false, [all]), + Parent ! {self(), Res}. + +stop() -> + Mref = erlang:monitor(process, dbg), + catch dbg ! {self(),stop}, + receive + {'DOWN',Mref,_,_,_} -> + ok + end. + +stop_clear() -> + ctp(), + stop(). + +%%% Calling the server. + +req(R) -> + P = ensure(), % The pid or perhaps the name of the server + Mref = erlang:monitor(process, P), + catch P ! {self(), R}, % May crash if P = atom() and server died + receive + {'DOWN', Mref, _, _, _} -> % If server died + exit(dbg_server_crash); + {dbg, Reply} -> + erlang:demonitor(Mref), + receive {'DOWN', Mref, _, _, _} -> ok after 0 -> ok end, + Reply + end. + +%% Returns the pid of the dbg server, or in worst case the name. +%% Starts a new server if necessary. +ensure() -> + case whereis(dbg) of + undefined -> + case start() of + {ok, P} -> + P; + {error, already_started} -> + dbg + end; + Pid -> + Pid + end. + + +%%% Server implementation. +start() -> + start(no_tracer). + +start(TracerFun) -> + S = self(), + case whereis(dbg) of + undefined -> + Dbg = spawn(fun() -> init(S) end), + receive {Dbg,started} -> ok end, + case TracerFun of + no_tracer -> + {ok, Dbg}; + Fun when is_function(Fun) -> + req({tracer,TracerFun}) + end; + Pid when is_pid(Pid), is_function(TracerFun) -> + req({tracer,TracerFun}) + end. + +init(Parent) -> + process_flag(trap_exit, true), + register(dbg, self()), + Parent ! {self(),started}, + loop({[],[]},[]). + +% +% SurviveLinks = Processes we should take with us while falling, +% but not get killed by if they die (i. e. trace clients +% and relay processes on other nodes) +% SurviveLinks = {TraceClients,Relays} +% +loop({C,T}=SurviveLinks, Table) -> + receive + {From,i} -> + reply(From, display_info(lists:map(fun({N,_}) -> N end,get()))), + loop(SurviveLinks, Table); + {From,{p,Pid,Flags}} -> + reply(From, trace_process(Pid, Flags)), + loop(SurviveLinks, Table); + {From,{tracer,TracerFun}} when is_function(TracerFun) -> + case get(node()) of + undefined -> + case (catch TracerFun()) of + {'EXIT', Reason} -> + reply(From, {error, Reason}); + Tracer when is_pid(Tracer); is_port(Tracer) -> + put(node(),{self(),Tracer}), + reply(From, {ok,self()}) + end; + {_Relay,_Tracer} -> + reply(From, {error, already_started}) + end, + loop(SurviveLinks,Table); + {From,{get_tracer,Node}} -> + case get(Node) of + undefined -> reply(From,{error, {no_tracer_on_node,Node}}); + {_Relay,Tracer} -> reply(From, {ok,Tracer}) + end, + loop(SurviveLinks, Table); + {From, get_table} -> + Tab = case Table of + [] -> + new_pattern_table(); + _exists -> + Table + end, + reply(From, {ok, Tab}), + loop(SurviveLinks, Tab); + {_From,stop} -> + %% We want to make sure that all trace messages have been delivered + %% on all nodes that might be traced. Since dbg:cn/1 does not turn off + %% tracing on the node it removes from the list of active trace nodes, + %% we will call erlang:trace_delivered/1 on ALL nodes that we have + %% connections to. + Delivered = fun() -> + Ref = erlang:trace_delivered(all), + receive + {trace_delivered,all,Ref} -> ok + end + end, + catch rpc:multicall(nodes(), erlang, apply, [Delivered,[]]), + Ref = erlang:trace_delivered(all), + receive + {trace_delivered,all,Ref} -> + exit(done) + end; + {From, {link_to, Pid}} -> + case (catch link(Pid)) of + {'EXIT', Reason} -> + reply(From, {error, Reason}), + loop(SurviveLinks, Table); + _ -> + reply(From, {ok, Pid}), + loop({[Pid|C],T}, Table) + end; + {From, {add_node, Node}} -> + case get(node()) of + undefined -> + reply(From, {error, no_local_tracer}), + loop(SurviveLinks, Table); + {_LocalRelay,Tracer} when is_port(Tracer) -> + reply(From, {error, cant_trace_remote_pid_to_local_port}), + loop(SurviveLinks, Table); + {_LocalRelay,Tracer} when is_pid(Tracer) -> + case (catch relay(Node, Tracer)) of + {ok,Relay} -> + reply(From, {ok, Node}), + loop({C,[Relay|T]}, Table); + {'EXIT', Something} -> + reply(From, {error, Something}), + loop(SurviveLinks, Table); + Error -> + reply(From, Error), + loop(SurviveLinks, Table) + end + end; + {From, {add_node, Node, Type, Data}} -> + case (catch relay(Node, {Type,Data})) of + {ok,Relay} -> + reply(From, {ok, Node}), + loop({C,[Relay|T]}, Table); + {'EXIT', Something} -> + reply(From, {error, Something}), + loop(SurviveLinks, Table); + Error -> + reply(From, Error), + loop(SurviveLinks, Table) + end; + {From, {remove_node, Node}} -> + erase(Node), + reply(From, ok), + loop(SurviveLinks, Table); + {From, get_nodes} -> + reply(From, lists:map(fun({N,_}) -> N end, get())), + loop(SurviveLinks, Table); + {'EXIT', Pid, Reason} -> + case lists:delete(Pid, C) of + C -> + case lists:delete(Pid,T) of + T -> + io:format(user,"** dbg got EXIT - terminating: ~p~n", + [Reason]), + exit(done); + NewT -> + erase(node(Pid)), + loop({C,NewT}, Table) + end; + NewC -> + loop({NewC,T}, Table) + end; + Other -> + io:format(user,"** dbg got garbage: ~p~n", + [{Other,SurviveLinks,Table}]), + loop(SurviveLinks, Table) + end. + +reply(Pid, Reply) -> + Pid ! {dbg,Reply}. + + +%%% A process-based tracer. + +start_tracer_process(Handler, HandlerData) -> + spawn_opt(fun() -> tracer_init(Handler, HandlerData) end, + [link,{priority,max}]). + + +tracer_init(Handler, HandlerData) -> + process_flag(trap_exit, true), + tracer_loop(Handler, HandlerData). + +tracer_loop(Handler, Hdata) -> + receive + Msg -> + %% Don't match in receive to avoid giving EXIT message higher + %% priority than the trace messages. + case Msg of + {'EXIT',_Pid,_Reason} -> + ok; + Trace -> + NewData = recv_all_traces(Trace, Handler, Hdata), + tracer_loop(Handler, NewData) + end + end. + +recv_all_traces(Trace, Handler, Hdata) -> + Suspended = suspend(Trace, []), + recv_all_traces(Suspended, Handler, Hdata, [Trace]). + +recv_all_traces(Suspended0, Handler, Hdata, Traces) -> + receive + Trace when is_tuple(Trace), element(1, Trace) == trace -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Trace when is_tuple(Trace), element(1, Trace) == trace_ts -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Trace when is_tuple(Trace), element(1, Trace) == seq_trace -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Trace when is_tuple(Trace), element(1, Trace) == drop -> + Suspended = suspend(Trace, Suspended0), + recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]); + Other -> + %%% Is this really a good idea? + io:format(user,"** tracer received garbage: ~p~n", [Other]), + recv_all_traces(Suspended0, Handler, Hdata, Traces) + after 0 -> + case catch invoke_handler(Traces, Handler, Hdata) of + {'EXIT',Reason} -> + resume(Suspended0), + exit({trace_handler_crashed,Reason}); + NewHdata -> + resume(Suspended0), + NewHdata + end + end. + +invoke_handler([Tr|Traces], Handler, Hdata0) -> + Hdata = invoke_handler(Traces, Handler, Hdata0), + Handler(Tr, Hdata); +invoke_handler([], _Handler, Hdata) -> + Hdata. + +suspend({trace,From,call,_Func}, Suspended) when node(From) == node() -> + case (catch erlang:suspend_process(From, [unless_suspending, + asynchronous])) of + true -> + [From | Suspended]; + _ -> + Suspended + end; +suspend(_Other, Suspended) -> Suspended. + +resume([Pid|Pids]) when node(Pid) == node() -> + (catch erlang:resume_process(Pid)), + resume(Pids); +resume([]) -> ok. + + + +%%% Utilities. + +trac(Proc, How, Flags) when is_atom(Proc) -> + %% Proc = all | new | existing | RegisteredName + %% Must go to all nodes + case get() of + [] -> + {error,no_tracers}; + Nodes -> + Matched = [trac(Node, NodeInfo, Proc, How, Flags) + || {Node, NodeInfo} <- Nodes], + {ok,Matched} + end; +trac(Proc, How, Flags) -> + %% Proc = Pid | Integer | {X,Y,Z} | "<X.Y.Z>" + %% One node only + Pid = to_pid(Proc), + case Pid of + {badpid,_} -> + {error,Pid}; + _ -> + Node = if is_pid(Pid) -> node(Pid); true -> node() end, + case get(Node) of + undefined -> + {error,{no_tracer_on_node,Node}}; + NodeInfo -> + Match = trac(Node, NodeInfo, Pid, How, Flags), + {ok,[Match]} + end + end. + +trac(Node, {_Relay, Tracer}, AtomPid, How, Flags) -> + case rpc:call(Node, ?MODULE, erlang_trace, + [AtomPid, How, [{tracer, Tracer} | Flags]]) of + N when is_integer(N) -> + {matched, Node, N}; + {badrpc,Reason} -> + {matched, Node, 0, Reason}; + Else -> + {matched, Node, 0, Else} + end. + +erlang_trace(AtomPid, How, Flags) -> + case to_pidspec(AtomPid) of + {badpid,_} -> + {no_proc,AtomPid}; + P -> + erlang:trace(P, How, Flags) + end. + +%% Since we are not allowed to do erlang:trace/3 on a remote +%% process, we create a relay process at the remote node. + +relay(Node,To) when Node /= node() -> + case get(Node) of + undefined -> + S = self(), + Pid = spawn_link(Node, fun() -> do_relay(S,To) end), + receive {started,Remote} -> put(Node, {Pid,Remote}) end, + {ok,Pid}; + {_Relay,PortOrPid} -> + {error, {already_started, PortOrPid}} + end. + +do_relay(Parent,RelP) -> + process_flag(trap_exit, true), + case RelP of + {Type,Data} -> + {ok,Tracer} = remote_tracer(Type,Data), + Parent ! {started,Tracer}; + Pid when is_pid(Pid) -> + Parent ! {started,self()} + end, + do_relay_1(RelP). + +do_relay_1(RelP) -> + %% In the case of a port tracer, this process exists only so that + %% dbg know that the node is alive... should maybe use monitor instead? + receive + {'EXIT', _P, _} -> + exit(normal); + TraceInfo when is_pid(RelP) -> % Here is the normal case for trace i/o + RelP ! TraceInfo, + do_relay_1(RelP); + Other -> + io:format(user,"** relay got garbage: ~p~n", [Other]), + do_relay_1(RelP) + end. + +dhandler(end_of_trace, Out) -> + Out; +dhandler(Trace, Out) when element(1, Trace) == trace, tuple_size(Trace) >= 3 -> + dhandler1(Trace, tuple_size(Trace), Out); +dhandler(Trace, Out) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 -> + dhandler1(Trace, tuple_size(Trace)-1, Out); +dhandler(Trace, Out) when element(1, Trace) == drop, tuple_size(Trace) =:= 2 -> + io:format(Out, "*** Dropped ~p messages.~n", [element(2,Trace)]), + Out; +dhandler(Trace, Out) when element(1, Trace) == seq_trace, tuple_size(Trace) >= 3 -> + SeqTraceInfo = case Trace of + {seq_trace, Lbl, STI, TS} -> + io:format(Out, "SeqTrace ~p [~p]: ", + [TS, Lbl]), + STI; + {seq_trace, Lbl, STI} -> + io:format(Out, "SeqTrace [~p]: ", + [Lbl]), + STI + end, + case SeqTraceInfo of + {send, Ser, Fr, To, Mes} -> + io:format(Out, "(~p) ~p ! ~p [Serial: ~p]~n", + [Fr, To, Mes, Ser]); + {'receive', Ser, Fr, To, Mes} -> + io:format(Out, "(~p) << ~p [Serial: ~p, From: ~p]~n", + [To, Mes, Ser, Fr]); + {print, Ser, Fr, _, Info} -> + io:format(Out, "-> ~p [Serial: ~p, From: ~p]~n", + [Info, Ser, Fr]); + Else -> + io:format(Out, "~p~n", [Else]) + end, + Out; +dhandler(_Trace, Out) -> + Out. + +dhandler1(Trace, Size, Out) -> +%%%! Self = self(), + From = element(2, Trace), + case element(3, Trace) of + 'receive' -> + case element(4, Trace) of + {dbg,ok} -> ok; + Message -> io:format(Out, "(~p) << ~p~n", [From,Message]) + end; + 'send' -> + Message = element(4, Trace), + case element(5, Trace) of +%%%! This causes messages to disappear when used by ttb (observer). Tests +%%%! so far show that there is no difference in results with dbg even if I +%%%! comment it out, so I hope this is only some old code which isn't +%%%! needed anymore... /siri +%%%! Self -> ok; + To -> io:format(Out, "(~p) ~p ! ~p~n", [From,To,Message]) + end; + call -> + case element(4, Trace) of + MFA when Size == 5 -> + Message = element(5, Trace), + io:format(Out, "(~p) call ~s (~p)~n", [From,ffunc(MFA),Message]); + MFA -> + io:format(Out, "(~p) call ~s~n", [From,ffunc(MFA)]) + end; + return -> %% To be deleted... + case element(4, Trace) of + MFA when Size == 5 -> + Ret = element(5, Trace), + io:format(Out, "(~p) old_ret ~s -> ~p~n", [From,ffunc(MFA),Ret]); + MFA -> + io:format(Out, "(~p) old_ret ~s~n", [From,ffunc(MFA)]) + end; + return_from -> + MFA = element(4, Trace), + Ret = element(5, Trace), + io:format(Out, "(~p) returned from ~s -> ~p~n", [From,ffunc(MFA),Ret]); + return_to -> + MFA = element(4, Trace), + io:format(Out, "(~p) returning to ~s~n", [From,ffunc(MFA)]); + spawn when Size == 5 -> + Pid = element(4, Trace), + MFA = element(5, Trace), + io:format(Out, "(~p) spawn ~p as ~s~n", [From,Pid,ffunc(MFA)]); + Op -> + io:format(Out, "(~p) ~p ~s~n", [From,Op,ftup(Trace,4,Size)]) + end, + Out. + + + +%%% These f* functions returns non-flat strings + +%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)" +%% {M,F,A} -> "M:F/A" +ffunc({M,F,Argl}) when is_list(Argl) -> + io_lib:format("~p:~p(~s)", [M, F, fargs(Argl)]); +ffunc({M,F,Arity}) -> + io_lib:format("~p:~p/~p", [M,F,Arity]); +ffunc(X) -> io_lib:format("~p", [X]). + +%% Integer -> "Integer" +%% [A1, A2, ..., AN] -> "A1, A2, ..., AN" +fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity); +fargs([]) -> []; +fargs([A]) -> io_lib:format("~p", [A]); %% last arg +fargs([A|Args]) -> [io_lib:format("~p,", [A]) | fargs(Args)]; +fargs(A) -> io_lib:format("~p", [A]). % last or only arg + +%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size" +ftup(Trace, Index, Index) -> + io_lib:format("~p", [element(Index, Trace)]); +ftup(Trace, Index, Size) -> + [io_lib:format("~p ", [element(Index, Trace)]) + | ftup(Trace, Index+1, Size)]. + + + +trace_process(Pid, [clear]) -> + trac(Pid, false, all()); +trace_process(Pid, Flags0) -> + case transform_flags(Flags0) of + {error,Reason} -> {error,Reason}; + Flags -> trac(Pid, true, Flags) + end. + +transform_flags(Flags0) -> + transform_flags(Flags0,[]). +transform_flags([],Acc) -> Acc; +transform_flags([m|Tail],Acc) -> transform_flags(Tail,[send,'receive'|Acc]); +transform_flags([s|Tail],Acc) -> transform_flags(Tail,[send|Acc]); +transform_flags([r|Tail],Acc) -> transform_flags(Tail,['receive'|Acc]); +transform_flags([c|Tail],Acc) -> transform_flags(Tail,[call|Acc]); +transform_flags([call|Tail],Acc) -> transform_flags(Tail,[call|Acc]); +transform_flags([p|Tail],Acc) -> transform_flags(Tail,[procs|Acc]); +transform_flags([sos|Tail],Acc) -> transform_flags(Tail,[set_on_spawn|Acc]); +transform_flags([sol|Tail],Acc) -> transform_flags(Tail,[set_on_link|Acc]); +transform_flags([sofs|Tail],Acc) -> transform_flags(Tail,[set_on_first_spawn|Acc]); +transform_flags([sofl|Tail],Acc) -> transform_flags(Tail,[set_on_first_link|Acc]); +transform_flags([all|_],_Acc) -> all(); +transform_flags([F|Tail]=List,Acc) when is_atom(F) -> + case lists:member(F, all()) of + true -> transform_flags(Tail,[F|Acc]); + false -> {error,{bad_flags,List}} + end; +transform_flags(Bad,_Acc) -> {error,{bad_flags,Bad}}. + +all() -> + [send,'receive',call,procs,garbage_collection,running, + set_on_spawn,set_on_first_spawn,set_on_link,set_on_first_link, + timestamp,arity,return_to]. + +display_info([Node|Nodes]) -> + io:format("~nNode ~w:~n",[Node]), + io:format("~-12s ~-21s Trace ~n", ["Pid", "Initial call"]), + List = rpc:call(Node,?MODULE,get_info,[]), + display_info1(List), + display_info(Nodes); +display_info([]) -> + ok. + +display_info1([{Pid,Call,Flags}|T]) -> + io:format("~-12s ~-21s ~s~n", + [io_lib:format("~w",[Pid]), + io_lib:format("~p", [Call]), + format_trace(Flags)]), + display_info1(T); +display_info1([]) -> + ok. + +get_info() -> + get_info(processes(),[]). + +get_info([Pid|T],Acc) -> + case pinfo(Pid, initial_call) of + undefined -> + get_info(T,Acc); + {initial_call, Call} -> + case tinfo(Pid, flags) of + undefined -> + get_info(T,Acc); + {flags,[]} -> + get_info(T,Acc); + {flags,Flags} -> + get_info(T,[{Pid,Call,Flags}|Acc]) + end + end; +get_info([],Acc) -> Acc. + +format_trace([]) -> []; +format_trace([Item]) -> [ts(Item)]; +format_trace([Item|T]) -> [ts(Item) ," | ", format_trace(T)]. + +ts(send) -> "s"; +ts('receive') -> "r"; +ts(call) -> "c"; +ts(procs) -> "p"; +ts(set_on_spawn) -> "sos"; +ts(set_on_first_spawn) -> "sofs"; +ts(set_on_link) -> "sol"; +ts(set_on_first_link) -> "sofl"; +ts(Other) -> atom_to_list(Other). + +%% +%% Turn (pid or) atom into a PidSpec for erlang:trace, +%% return {badpid,X} on failure +%% + +to_pidspec(X) when is_pid(X) -> + case erlang:is_process_alive(X) of + true -> X; + false -> {badpid,X} + end; +to_pidspec(new) -> new; +to_pidspec(all) -> all; +to_pidspec(existing) -> existing; +to_pidspec(X) when is_atom(X) -> + case whereis(X) of + undefined -> {badpid,X}; + Pid -> Pid + end; +to_pidspec(X) -> {badpid,X}. + +%% +%% Turn (pid or) integer or tuple or list into pid +%% + +to_pid(X) when is_pid(X) -> X; +to_pid(X) when is_integer(X) -> to_pid({0,X,0}); +to_pid({X,Y,Z}) -> + to_pid(lists:concat(["<",integer_to_list(X),".", + integer_to_list(Y),".", + integer_to_list(Z),">"])); +to_pid(X) when is_list(X) -> + try list_to_pid(X) of + Pid -> Pid + catch + error:badarg -> {badpid,X} + end; +to_pid(X) -> {badpid,X}. + + +pinfo(P, X) when node(P) == node() -> erlang:process_info(P, X); +pinfo(P, X) -> check(rpc:call(node(P), erlang, process_info, [P, X])). + +tinfo(P, X) when node(P) == node() -> erlang:trace_info(P, X); +tinfo(P, X) -> check(rpc:call(node(P), erlang, trace_info, [P, X])). + +check({badrpc, _}) -> undefined; +check(X) -> X. + +%% Process loop that processes a trace. Reads the trace with +%% the reader Reader, and feeds the trace terms +%% to handler Handler, keeping a state variable for the +%% handler. +%% +%% Exits 'normal' at end of trace, other exits due to errors. +%% +%% Reader is a lazy list, i.e either a list or a fun/0. +%% If it is a fun, it is evaluated for rest of the lazy list. +%% A list head is considered to be a trace term. End of list +%% is interpreted as end of trace. + +tc_loop([Term|Tail], Handler, HData0) -> + HData = Handler(Term, HData0), + tc_loop(Tail, Handler, HData); +tc_loop([], Handler, HData) -> + Handler(end_of_trace, HData), + exit(normal); +tc_loop(Reader, Handler, HData) when is_function(Reader) -> + tc_loop(Reader(), Handler, HData); +tc_loop(Other, _Handler, _HData) -> + io:format("~p:tc_loop ~p~n", [?MODULE, Other]), + exit({unknown_term_from_reader, Other}). + + + +%% Returns a reader (lazy list of trace terms) for tc_loop/2. +gen_reader(ip, {Host, Portno}) -> + case gen_tcp:connect(Host, Portno, [{active, false}, binary]) of + {ok, Sock} -> + mk_reader(fun ip_read/2, Sock); + Error -> + exit(Error) + end; +gen_reader(file, {Filename, wrap, Tail, _, WrapCnt}) -> + mk_reader_wrap(wrap_sort(wrap_presort(Filename, Tail), WrapCnt)); +gen_reader(file, Filename) -> + gen_reader_file(fun file_read/2, Filename); +gen_reader(follow_file, Filename) -> + gen_reader_file(fun follow_read/2, Filename). + +%% Opens a file and returns a reader (lazy list). +gen_reader_file(ReadFun, Filename) -> + case file:open(Filename, [read, raw, binary]) of + {ok, File} -> + mk_reader(ReadFun, File); + Error -> + exit({client_cannot_open, Error}) + end. + +%% Creates and returns a reader (lazy list). +mk_reader(ReadFun, Source) -> + fun() -> + case read_term(ReadFun, Source) of + {ok, Term} -> + [Term | mk_reader(ReadFun, Source)]; + eof -> + [] % end_of_trace + end + end. + +%% Creates and returns a reader (lazy list) for a wrap log. +%% The argument is a sorted list of sort converted +%% wrap log file names, see wrap_presort/2. + +mk_reader_wrap([]) -> + []; +mk_reader_wrap([Hd | _] = WrapFiles) -> + case file:open(wrap_name(Hd), [read, raw, binary]) of + {ok, File} -> + mk_reader_wrap(WrapFiles, File); + Error -> + exit({client_cannot_open, Error}) + end. + +mk_reader_wrap([_Hd | Tail] = WrapFiles, File) -> + fun() -> + case read_term(fun file_read/2, File) of + {ok, Term} -> + [Term | mk_reader_wrap(WrapFiles, File)]; + eof -> + file:close(File), + case Tail of + [_|_] -> + mk_reader_wrap(Tail); + [] -> + [] % end_of_trace + end + end + end. + + + +%% Generic read term function. +%% Returns {ok, Term} | 'eof'. Exits on errors. + +read_term(ReadFun, Source) -> + case ReadFun(Source, 5) of + Bin when is_binary(Bin) -> + read_term(ReadFun, Source, Bin); + List when is_list(List) -> + read_term(ReadFun, Source, list_to_binary(List)); + eof -> + eof + end. + +read_term(ReadFun, Source, <<Op, Size:32>> = Tag) -> + case Op of + 0 -> + case ReadFun(Source, Size) of + eof -> + exit({'trace term missing', + binary_to_list(Tag)}); + Bin when is_binary(Bin) -> + {ok, binary_to_term(Bin)}; + List when is_list(List) -> + {ok, binary_to_term(list_to_binary(List))} + end; + 1 -> + {ok, {drop, Size}}; + Junk -> + exit({'bad trace tag', Junk}) + end. + + + +%% Read functions for different source types, for read_term/2. +%% +%% Returns a binary of length N, an I/O-list of +%% effective length N or 'eof'. Exits on errors. + +file_read(File, N) -> + case file:read(File, N) of + {ok, Bin} when byte_size(Bin) =:= N -> + Bin; + {ok, Bin} when is_binary(Bin) -> + exit({'truncated file', binary_to_list(Bin)}); + eof -> + eof; + {error, Reason} -> + exit({'file read error', Reason}) + end. + +follow_read(File, N) -> + follow_read(File, N, cur). + +follow_read(File, N, Pos) -> + case file:position(File, Pos) of + {ok, Offset} -> + case file:read(File, N) of + {ok, Bin} when byte_size(Bin) =:= N -> + Bin; + {ok, Bin} when is_binary(Bin) -> + follow_read(File, N, Offset); + eof -> + follow_read(File, N, Offset); + {error, Reason} -> + exit({'file read error', Reason}) + end; + {error, Reason} -> + exit({'file position error', Reason}) + end. + +ip_read(Socket, N) -> + case gen_tcp:recv(Socket, N) of + {ok, Bin} when byte_size(Bin) < N -> + [Bin | ip_read(Socket, N-byte_size(Bin))]; + {ok, Bin} when byte_size(Bin) == N -> + [Bin]; + {ok, Bin} when is_binary(Bin) -> + exit({'socket read too much data', Bin}); + {error, closed} -> + eof; + {error, _Reason} = Error -> + exit({'socket read error', Error}) + end. + +get_tracer() -> + req({get_tracer,node()}). +get_tracer(Node) -> + req({get_tracer,Node}). + +save_pattern([]) -> + 0; +save_pattern(P) -> + (catch save_pattern(P, get_pattern_table())). + +save_pattern(Pattern, PT) -> + Last = last_pattern(ets:last(PT), PT), + BPattern = term_to_binary(Pattern), + case ets:match_object(PT, {'_', BPattern}) of + [] -> + ets:insert(PT, {Last + 1, BPattern}), + Last + 1; + [{N, BPattern}] -> + N + end. + +last_pattern('$end_of_table', _PT) -> + 0; +last_pattern(I, PT) when is_atom(I) -> + last_pattern(ets:prev(PT, I), PT); +last_pattern(I, _PT) when is_integer(I) -> + I; +last_pattern(_, _) -> + throw({error, badtable}). + + +get_pattern_table() -> + {ok, Ret} = req(get_table), + Ret. + +new_pattern_table() -> + PT = ets:new(dbg_tab, [ordered_set, public]), + ets:insert(PT, + {x, + term_to_binary([{'_',[],[{exception_trace}]}])}), + ets:insert(PT, + {exception_trace, + term_to_binary(x)}), + PT. + + +pt_doforall(Fun, Ld) -> + T = get_pattern_table(), + pt_doforall(T, Fun, ets:first(T), Ld). + +pt_doforall(_, _, '$end_of_table', _Ld) -> + ok; +pt_doforall(T, Fun, Key, Ld) -> + [{A,B}] = ets:lookup(T,Key), + NLd = Fun({A,binary_to_term(B)},Ld), + pt_doforall(T,Fun,ets:next(T,Key),NLd). + +lint_tp([]) -> + {ok,[]}; +lint_tp(Pattern) -> + case erlang:match_spec_test([],Pattern,trace) of + {ok,_Res,Warnings,_Flags} -> + {ok, Warnings}; + {error, Reasons} -> + {error, Reasons} + end. + +check_list(T) -> + case (catch lists:foldl( + fun(Val,_) -> + {ok,_,_,_} = + erlang:match_spec_test([],Val,trace), + ok + end, + ok, T)) of + {'EXIT',_} -> + {error, bad_match_spec}; + ok -> + ok; + _Else -> + {error, badfile} + end. + + + +%% Find all possible wrap log files. +%% Returns: a list of sort converted filenames. +%% +%% The sort conversion is done by extracting the wrap sequence counter +%% from the filename, and calling wrap_encode/2. +wrap_presort(Filename, Tail) -> + Name = filename:basename(Filename), + Dirname = filename:dirname(Filename), + case file:list_dir(Dirname) of + {ok, Files} -> + lists:zf( + fun(N) -> + case match_front(N, Name) of + false -> + false; + X -> + case match_rear(X, Tail) of + false -> + false; + C -> % Counter + case match_0_9(C) of + true -> + {true, + wrap_encode( + filename:join(Dirname, N), + C)}; + false -> + false + end + end + end + end, + Files); + _ -> + [] + end. + + + +%% Sorts a list of sort converted files +wrap_sort(Files, N) -> + wrap_sortfix(lists:sort(Files), N). + +%% Finish the sorting, since the lists:sort order is not the correct order. +%% Cut the list of files at the gap (at least one file is supposed +%% to be 'missing') and create a new list by cons'ing the two parts +%% in the right order. +wrap_sortfix([], N) when N >= 1 -> + []; +wrap_sortfix([], _N) -> + exit(inconsistent_wrap_file_trace_set); +%% file 0, gap 1..N +wrap_sortfix([{0, _}] = Files, N) when N >= 1 -> + Files; +wrap_sortfix([{0, _}], _N) -> + exit(inconsistent_wrap_file_trace_set); +%% files 0, ... +wrap_sortfix([{0, _} | _] = Files, N) when N >= 1-> + wrap_sortfix_1(Files, N, [], Files); +%% gap 0, files 1, ... +wrap_sortfix([{1, _} | _] = Files, N) when N >= 1 -> + wrap_sortfix_2(Files, N, [], Files); +wrap_sortfix([{_C, _} | _], _N) -> + exit(inconsistent_wrap_file_trace_set). + +%% files 0..C, gap C+1..N +wrap_sortfix_1([{C, _}], N, _R, Files) + when C < N -> + Files; +%% files 0..C1, C1+1==C2, ... +wrap_sortfix_1([{C1, _} = F1 | [{C2, _} | _] = Tail], N, R, Files) + when C1+1 == C2, C2 < N -> + wrap_sortfix_1(Tail, N, [F1 | R], Files); +%% files 0..C1, gap C1+1, files C1+2==C2, ... +wrap_sortfix_1([{C1, _} = F1 | [{C2, _} | _] = Tail], N, R, _Files) + when C1+2 == C2, C2 =< N -> + wrap_sortfix_2(Tail, N, lists:reverse([F1 | R]), Tail); +wrap_sortfix_1([_F1 | [_F2 | _]], _N, _R, _Files) -> + exit(inconsistent_wrap_file_trace_set). + +%% M == length(R); files 0..M-1, gap M, files M+1..N +wrap_sortfix_2([{N, _}], N, R, Files) -> + Files ++ R; +wrap_sortfix_2([{_C, _}], _N, _R, _Files) -> + exit(inconsistent_wrap_file_trace_set); +%% M == length(R); files 0..M-1, gap M, files M+1..C1, C1+1==C2, ... +wrap_sortfix_2([{C1, _} | [{C2, _} | _] = Tail], N, R, Files) + when C1+1 == C2, C2 =< N -> + wrap_sortfix_2(Tail, N, R, Files); +wrap_sortfix_2([{_C1, _} | [{_C2, _} | _]], _N, _R, _Files) -> + exit(inconsistent_wrap_file_trace_set). + + + +%% Extract the filenames from a list of sort converted ones. +wrap_postsort(Files) -> + lists:map(fun wrap_name/1, Files). + +wrap_encode(N, C) -> + {list_to_integer(C), N}. + +wrap_name({_C, N}) -> + N. + +%% Returns what is left of ListA when removing all matching +%% elements from ListB, or false if some element did not match, +%% or if ListA runs out of elements before ListB. +match_front(ListA, []) when is_list(ListA) -> + ListA; +match_front([], ListB) when is_list(ListB) -> + false; +match_front([Hd|TlA], [Hd|TlB]) -> + match_front(TlA,TlB); +match_front([_HdA|_], [_HdB|_]) -> + false. + +%% Reversed version of match_front/2 +match_rear(ListA, ListB) when is_list(ListA), is_list(ListB) -> + case match_front(lists:reverse(ListA), lists:reverse(ListB)) of + false -> + false; + List -> + lists:reverse(List) + end. + +%% Returns true if the non-empty list arguments contains all +%% characters $0 .. $9. +match_0_9([]) -> + false; +match_0_9([H]) when is_integer(H), $0 =< H, H =< $9 -> + true; +match_0_9([H|T]) when is_integer(H), $0 =< H, H =< $9 -> + match_0_9(T); +match_0_9(L) when is_list(L) -> + false. + +%%%%%%%%%%%%%%%%%% +%% Help... +%%%%%%%%%%%%%%%%%% + +help_display([]) -> + io:format("~n",[]), + ok; +help_display([H|T]) -> + io:format("~s~n",[H]), + help_display(T). + +h() -> + help_display( + [ + "The following help items are available:", + " p, c", + " - Set trace flags for processes", + " tp, tpl, ctp, ctpl, ctpg, ltp, dtp, wtp, rtp", + " - Manipulate trace patterns for functions", + " n, cn, ln", + " - Add/remove traced nodes.", + " tracer, trace_port, trace_client, get_tracer, stop, stop_clear", + " - Manipulate tracer process/port", + " i", + " - Info", + "", + "call dbg:h(Item) for brief help a brief description", + "of one of the items above."]). +h(p) -> + help_display(["p(Item) -> {ok, MatchDesc} | {error, term()}", + " - Traces messages to and from Item.", + "p(Item, Flags) -> {ok, MatchDesc} | {error, term()}", + " - Traces Item according to Flags.", + " Flags can be one of s,r,m,c,p,sos,sol,sofs,", + " sofl,all,clear or any flag accepted by erlang:trace/3"]); +h(c) -> + help_display(["c(Mod, Fun, Args)", + " - Evaluates apply(M,F,Args) with all trace flags set.", + "c(Mod, Fun, Args, Flags)", + " - Evaluates apply(M,F,Args) with Flags trace flags set."]); +h(i) -> + help_display(["i() -> ok", + " - Displays information about all traced processes."]); +h(tp) -> + help_display( + ["tp(Module,MatchSpec)", + " - Same as tp({Module, '_', '_'}, MatchSpec)", + "tp(Module,Function,MatchSpec)", + " - Same as tp({Module, Function, '_'}, MatchSpec)", + "tp(Module, Function, Arity, MatchSpec)", + " - Same as tp({Module, Function, Arity}, MatchSpec)", + "tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} " + "| {error, term()}", + " - Set pattern for traced global function calls."]); +h(tpl) -> + help_display( + ["tpl(Module,MatchSpec)", + " - Same as tpl({Module, '_', '_'}, MatchSpec)", + "tpl(Module,Function,MatchSpec)", + " - Same as tpl({Module, Function, '_'}, MatchSpec)", + "tpl(Module, Function, Arity, MatchSpec)", + " - Same as tpl({Module, Function, Arity}, MatchSpec)", + "tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} " + "| {error, term()}", + " - Set pattern for traced local (as well as global) function calls."]); +h(ctp) -> + help_display( + ["ctp()", + " - Same as ctp({'_', '_', '_'})", + "ctp(Module)", + " - Same as ctp({Module, '_', '_'})", + "ctp(Module, Function)", + " - Same as ctp({Module, Function, '_'})", + "ctp(Module, Function, Arity)", + " - Same as ctp({Module, Function, Arity})", + "ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}", + " - Clear call trace pattern for the specified functions"]); +h(ctpl) -> + help_display( + ["ctpl()", + " - Same as ctpl({'_', '_', '_'})", + "ctpl(Module)", + " - Same as ctpl({Module, '_', '_'})", + "ctpl(Module, Function)", + " - Same as ctpl({Module, Function, '_'})", + "ctpl(Module, Function, Arity)", + " - Same as ctpl({Module, Function, Arity})", + "ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}", + " - Clear local call trace pattern for the specified functions"]); +h(ctpg) -> + help_display( + ["ctpg()", + " - Same as ctpg({'_', '_', '_'})", + "ctpg(Module)", + " - Same as ctpg({Module, '_', '_'})", + "ctpg(Module, Function)", + " - Same as ctpg({Module, Function, '_'})", + "ctpg(Module, Function, Arity)", + " - Same as ctpg({Module, Function, Arity})", + "ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}", + " - Clear global call trace pattern for the specified functions"]); +h(ltp) -> + help_display(["ltp() -> ok", + " - Lists saved and built-in match_spec's on the console."]); +h(dtp) -> + help_display(["dtp() -> ok", + " - Deletes all saved match_spec's.", + "dtp(N) -> ok", + " - Deletes a specific saved match_spec."]); +h(wtp) -> + help_display(["wtp(Name) -> ok | {error, IOError}", + " - Writes all saved match_spec's to a file"]); +h(rtp) -> + help_display(["rtp(Name) -> ok | {error, Error}", + " - Read saved match specifications from file."]); +h(n) -> + help_display( + ["n(Nodename) -> {ok, Nodename} | {error, Reason}", + " - Starts a tracer server on the given node.", + "n(Nodename,Type,Data) -> {ok, Nodename} | {error, Reason}", + " - Starts a tracer server with additional args on the given node."]); +h(cn) -> + help_display(["cn(Nodename) -> ok", + " - Clears a node from the list of traced nodes."]); +h(ln) -> + help_display(["ln() -> ok", + " - Shows the list of traced nodes on the console."]); +h(tracer) -> + help_display(["tracer() -> {ok, pid()} | {error, already_started}", + " - Starts a tracer server that handles trace messages.", + "tracer(Type, Data) -> {ok, pid()} | {error, Error}", + " - Starts a tracer server with additional parameters"]); +h(trace_port) -> + help_display(["trace_port(Type, Parameters) -> fun()", + " - Creates and returns a trace port generating fun"]); +h(trace_client) -> + help_display(["trace_client(Type, Parameters) -> pid()", + " - Starts a trace client that reads messages created by " + "a trace port driver", + "trace_client(Type, Parameters, HandlerSpec) -> pid()", + " - Starts a trace client that reads messages created by a", + " trace port driver, with a user defined handler"]); +h(get_tracer) -> + help_display( + ["get_tracer() -> {ok, Tracer}", + " - Returns the process or port to which all trace messages are sent.", + "get_tracer(Node) -> {ok, Tracer}", + " - Returns the process or port to which all trace messages are sent."]); +h(stop) -> + help_display( + ["stop() -> stopped", + " - Stops the dbg server and the tracing of all processes.", + " Does not clear any trace patterns."]); +h(stop_clear) -> + help_display( + ["stop_clear() -> stopped", + " - Stops the dbg server and the tracing of all processes,", + " and clears all trace patterns."]). + diff --git a/lib/runtime_tools/src/erts_alloc_config.erl b/lib/runtime_tools/src/erts_alloc_config.erl new file mode 100644 index 0000000000..0bcb202fd8 --- /dev/null +++ b/lib/runtime_tools/src/erts_alloc_config.erl @@ -0,0 +1,670 @@ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. 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. +%% +%% The Initial Developer of the Original Code is Ericsson AB. +%% +%% %CopyrightEnd% + +%%%------------------------------------------------------------------- +%%% File : erts_alloc_config.erl +%%% Author : Rickard Green +%%% Description : Generate an erts_alloc configuration suitable for +%%% a limited amount of runtime scenarios. +%%% +%%% Created : 9 May 2007 by Rickard Green +%%%------------------------------------------------------------------- + +-module(erts_alloc_config). + +-record(state, {have_scenario = false, + alloc}). + + +-record(alloc, {name, + enabled, + need_config_change, + alloc_util, + instances, + low_mbc_blocks_size, + high_mbc_blocks_size, + sbct, + segments}). + +-record(conf, + {segments, + format_to}). + +-record(segment, {size,number}). + +-define(PRINT_WITDH, 76). + +-define(SERVER, '__erts_alloc_config__'). + +-define(MAX_ALLOCATOR_INSTANCES, 16). + +-define(KB, 1024). +-define(MB, 1048576). + +-define(B2KB(B), ((((B) - 1) div ?KB) + 1)). +-define(ROUNDUP(V, R), ((((V) - 1) div (R)) + 1)*(R)). + +-define(LARGE_GROWTH_ABS_LIMIT, 20*?MB). +-define(MBC_MSEG_LIMIT, 150). +-define(FRAG_FACT, 1.25). +-define(GROWTH_SEG_FACT, 2). +-define(MIN_SEG_SIZE, 1*?MB). +-define(SMALL_GROWTH_SEGS, 5). + +-define(ALLOC_UTIL_ALLOCATOR(A), + A == binary_alloc; + A == std_alloc; + A == ets_alloc; + A == eheap_alloc; + A == ll_alloc; + A == sl_alloc; + A == temp_alloc; + A == driver_alloc). + +-define(ALLOCATORS, + [binary_alloc, + ets_alloc, + eheap_alloc, + fix_alloc, + ll_alloc, + mseg_alloc, + sl_alloc, + std_alloc, + sys_alloc, + temp_alloc, + driver_alloc]). + +-define(MMBCS_DEFAULTS, + [{binary_alloc, 131072}, + {std_alloc, 131072}, + {ets_alloc, 131072}, + {eheap_alloc, 524288}, + {ll_alloc, 2097152}, + {sl_alloc, 131072}, + {temp_alloc, 131072}, + {driver_alloc, 131072}]). + +-define(MMMBC_DEFAULTS, + [{binary_alloc, 10}, + {std_alloc, 10}, + {ets_alloc, 10}, + {eheap_alloc, 10}, + {ll_alloc, 0}, + {sl_alloc, 10}, + {temp_alloc, 10}, + {driver_alloc, 10}]). + + +%%% +%%% Exported interface +%%% + +-export([save_scenario/0, + make_config/0, + make_config/1, + stop/0]). + +%% Test and debug export +-export([state/0]). + + +save_scenario() -> + req(save_scenario). + +make_config() -> + make_config(group_leader()). + +make_config(FileName) when is_list(FileName) -> + case file:open(FileName, [write]) of + {ok, IODev} -> + Res = req({make_config, IODev}), + file:close(IODev), + Res; + Error -> + Error + end; +make_config(IODev) -> + req({make_config, IODev}). + +stop() -> + req(stop). + + +%% state() is intentionally undocumented, and is for testing +%% and debugging only... + +state() -> + req(state). + +%%% +%%% Server +%%% + +req(Req) -> + Ref = make_ref(), + ReqMsg = {request, self(), Ref, Req}, + req(ReqMsg, Ref, true). + +req(ReqMsg, Ref, TryStart) -> + req(ReqMsg, Ref, TryStart, erlang:monitor(process, ?SERVER)). + +req(ReqMsg, Ref, TryStart, Mon) -> + (catch ?SERVER ! ReqMsg), + receive + {response, Ref, Res} -> + erlang:demonitor(Mon, [flush]), + Res; + {'DOWN', Mon, _, _, noproc} -> + case TryStart of + true -> start_server(Ref, ReqMsg); + false -> {error, server_died} + end; + {'DOWN', Mon, _, _, Reason} -> + {error, Reason} + end. + +start_server(Ref, ReqMsg) -> + Starter = self(), + Pid = spawn(fun () -> + register(?SERVER, self()), + Starter ! {Ref, self(), started}, + server_loop(make_state()) + end), + Mon = erlang:monitor(process, Pid), + receive + {Ref, Pid, started} -> + req(ReqMsg, Ref, false, Mon); + {'DOWN', Mon, _, _, _} -> + req(ReqMsg, Ref, false) + end. + +server_loop(State) -> + NewState = receive + {request, From, Ref, save_scenario} -> + Alloc = save_scenario(State#state.alloc), + From ! {response, Ref, ok}, + State#state{alloc = Alloc, have_scenario = true}; + {request, From, Ref, {make_config, IODev}} -> + case State#state.have_scenario of + true -> + Conf = #conf{segments = ?MBC_MSEG_LIMIT, + format_to = IODev}, + Res = mk_config(Conf, State#state.alloc), + From ! {response, Ref, Res}; + _ -> + From ! {response, Ref, no_scenario_saved} + end, + State; + {request, From, Ref, stop} -> + From ! {response, Ref, ok}, + exit(normal); + {request, From, Ref, state} -> + From ! {response, Ref, State}, + State; + {request, From, Ref, Req} -> + From ! {response, Ref, {unknown_request, Req}}, + State; + _ -> + State + end, + server_loop(NewState). + +allocator_instances(temp_alloc) -> + erlang:system_info(schedulers) + 1; +allocator_instances(ll_alloc) -> + 1; +allocator_instances(_Allocator) -> + case erlang:system_info(schedulers) of + Schdlrs when Schdlrs =< ?MAX_ALLOCATOR_INSTANCES -> Schdlrs; + _Schdlrs -> ?MAX_ALLOCATOR_INSTANCES + end. + +make_state() -> + #state{alloc = lists:map(fun (A) -> + #alloc{name = A, + instances = allocator_instances(A)} + end, + ?ALLOCATORS)}. + +%% +%% Save scenario +%% + +ai_value(Key1, Key2, AI) -> + case lists:keysearch(Key1, 1, AI) of + {value, {Key1, Value1}} -> + case lists:keysearch(Key2, 1, Value1) of + {value, Result} -> Result; + _ -> undefined + end; + _ -> undefined + end. + + +chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = undefined, + high_mbc_blocks_size = undefined} = Alc, + Min, + Max) -> + Alc#alloc{low_mbc_blocks_size = Min, + high_mbc_blocks_size = Max, + enabled = true}; +chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = LowBS, + high_mbc_blocks_size = HighBS} = Alc, + Min, + Max) -> + true = is_integer(LowBS), + true = is_integer(HighBS), + Alc1 = case Min < LowBS of + true -> Alc#alloc{low_mbc_blocks_size = Min}; + false -> Alc + end, + case Max > HighBS of + true -> Alc1#alloc{high_mbc_blocks_size = Max}; + false -> Alc1 + end. + +set_alloc_util(#alloc{alloc_util = AU} = Alc, AU) -> + Alc; +set_alloc_util(Alc, Val) -> + Alc#alloc{alloc_util = Val}. + +chk_sbct(#alloc{sbct = undefined} = Alc, AI) -> + case ai_value(options, sbct, AI) of + {sbct, Bytes} when is_integer(Bytes) -> Alc#alloc{sbct = b2kb(Bytes)}; + _ -> Alc + end; +chk_sbct(Alc, _AI) -> + Alc. + +save_scenario(AlcList) -> + %% The high priority is not really necessary. It is + %% used since it will make retrieval of allocator + %% information less spread out in time on a highly + %% loaded system. + OP = process_flag(priority, high), + Res = do_save_scenario(AlcList), + process_flag(priority, OP), + Res. + +save_ai2(Alc, AI) -> + Alc1 = chk_sbct(Alc, AI), + case ai_value(mbcs, blocks_size, AI) of + {blocks_size, MinBS, _, MaxBS} -> + set_alloc_util(chk_mbcs_blocks_size(Alc1, MinBS, MaxBS), true); + _ -> + set_alloc_util(Alc, false) + end. + +save_ai(Alc, [{instance, 0, AI}]) -> + save_ai2(Alc, AI); +save_ai(Alc, [{instance, _, _}, {instance, _, _}| _]) -> + Alc#alloc{enabled = true, need_config_change = true}; +save_ai(Alc, AI) -> + save_ai2(Alc, AI). % Non erts_alloc_util allocator + +do_save_scenario(AlcList) -> + lists:map(fun (#alloc{enabled = false} = Alc) -> + Alc; + (#alloc{name = Name} = Alc) -> + case erlang:system_info({allocator, Name}) of + undefined -> + exit({bad_allocator_name, Name}); + false -> + Alc#alloc{enabled = false}; + AI when is_list(AI) -> + save_ai(Alc, AI) + end + end, + AlcList). + +%% +%% Make configuration +%% + +conf_size(Bytes) when is_integer(Bytes), Bytes < 0 -> + exit({bad_value, Bytes}); +conf_size(Bytes) when is_integer(Bytes), Bytes < 1*?MB -> + ?ROUNDUP(?B2KB(Bytes), 128); +conf_size(Bytes) when is_integer(Bytes), Bytes < 10*?MB -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(1*?MB)); +conf_size(Bytes) when is_integer(Bytes), Bytes < 100*?MB -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(2*?MB)); +conf_size(Bytes) when is_integer(Bytes), Bytes < 256*?MB -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(5*?MB)); +conf_size(Bytes) when is_integer(Bytes) -> + ?ROUNDUP(?B2KB(Bytes), ?B2KB(10*?MB)). + +sbct(#conf{format_to = FTO}, #alloc{name = A, sbct = SBCT}) -> + fc(FTO, "Sbc threshold size of ~p kilobytes.", [SBCT]), + format(FTO, " +M~csbct ~p~n", [alloc_char(A), SBCT]). + +default_mmbcs(temp_alloc = A, _Insts) -> + {value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS), + MMBCS_Default; +default_mmbcs(A, Insts) -> + {value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS), + I = case Insts > 4 of + true -> 4; + _ -> Insts + end, + ?ROUNDUP(MMBCS_Default div I, ?B2KB(1*?KB)). + +mmbcs(#conf{format_to = FTO}, + #alloc{name = A, instances = Insts, low_mbc_blocks_size = BlocksSize}) -> + BS = case A of + temp_alloc -> BlocksSize; + _ -> BlocksSize div Insts + end, + case BS > default_mmbcs(A, Insts) of + true -> + MMBCS = conf_size(BS), + fc(FTO, "Main mbc size of ~p kilobytes.", [MMBCS]), + format(FTO, " +M~cmmbcs ~p~n", [alloc_char(A), MMBCS]); + false -> + ok + end. + +smbcs_lmbcs_mmmbc(#conf{format_to = FTO}, + #alloc{name = A, instances = Insts, segments = Segments}) -> + MMMBC = case {A, Insts} of + {_, 1} -> Segments#segment.number; + {temp_alloc, _} -> Segments#segment.number; + _ -> (Segments#segment.number div Insts) + 1 + end, + MBCS = Segments#segment.size, + AC = alloc_char(A), + fc(FTO, "Mseg mbc size of ~p kilobytes.", [MBCS]), + format(FTO, " +M~csmbcs ~p +M~clmbcs ~p~n", [AC, MBCS, AC, MBCS]), + fc(FTO, "Max ~p mseg mbcs.", [MMMBC]), + format(FTO, " +M~cmmmbc ~p~n", [AC, MMMBC]), + ok. + +alloc_char(binary_alloc) -> $B; +alloc_char(std_alloc) -> $D; +alloc_char(ets_alloc) -> $E; +alloc_char(fix_alloc) -> $F; +alloc_char(eheap_alloc) -> $H; +alloc_char(ll_alloc) -> $L; +alloc_char(mseg_alloc) -> $M; +alloc_char(driver_alloc) -> $R; +alloc_char(sl_alloc) -> $S; +alloc_char(temp_alloc) -> $T; +alloc_char(sys_alloc) -> $Y; +alloc_char(Alloc) -> + exit({bad_allocator, Alloc}). + +conf_alloc(#conf{format_to = FTO}, + #alloc{name = A, enabled = false}) -> + fcl(FTO, A), + fcp(FTO, + "WARNING: ~p has been disabled. Consider enabling ~p by passing " + "the \"+M~ce true\" command line argument and rerun " + "erts_alloc_config.", + [A, A, alloc_char(A)]); +conf_alloc(#conf{format_to = FTO}, + #alloc{name = A, need_config_change = true}) -> + fcl(FTO, A), + fcp(FTO, + "WARNING: ~p has been configured in a way that prevents " + "erts_alloc_config from creating a configuration. The configuration " + "will be automatically adjusted to fit erts_alloc_config if you " + "use the \"+Mea config\" command line argument while running " + "erts_alloc_config.", + [A]); +conf_alloc(#conf{format_to = FTO} = Conf, + #alloc{name = A, alloc_util = true} = Alc) -> + fcl(FTO, A), + chk_xnote(Conf, Alc), + au_conf_alloc(Conf, Alc), + format(FTO, "#~n", []); +conf_alloc(#conf{format_to = FTO} = Conf, #alloc{name = A} = Alc) -> + fcl(FTO, A), + chk_xnote(Conf, Alc). + +chk_xnote(#conf{format_to = FTO}, + #alloc{name = fix_alloc}) -> + fcp(FTO, "Cannot be configured."); +chk_xnote(#conf{format_to = FTO}, + #alloc{name = sys_alloc}) -> + fcp(FTO, "Cannot be configured. Default malloc implementation used."); +chk_xnote(#conf{format_to = FTO}, + #alloc{name = mseg_alloc}) -> + fcp(FTO, "Default configuration used."); +chk_xnote(#conf{format_to = FTO}, + #alloc{name = ll_alloc}) -> + fcp(FTO, + "Note, blocks allocated with ll_alloc are very " + "seldom deallocated. Placing blocks in mseg " + "carriers is therefore very likely only a waste " + "of resources."); +chk_xnote(#conf{}, #alloc{}) -> + ok. + +au_conf_alloc(#conf{format_to = FTO} = Conf, + #alloc{name = A, + alloc_util = true, + instances = Insts, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc) -> + fcp(FTO, "Usage of mbcs: ~p - ~p kilobytes", [?B2KB(Low), ?B2KB(High)]), + case Insts of + 1 -> + fc(FTO, "One instance used."), + format(FTO, " +M~ct false~n", [alloc_char(A)]); + _ -> + fc(FTO, "~p instances used.", + [Insts]), + format(FTO, " +M~ct ~p~n", [alloc_char(A), Insts]) + end, + mmbcs(Conf, Alc), + smbcs_lmbcs_mmmbc(Conf, Alc), + sbct(Conf, Alc). + +large_growth(Low, High) -> + High - Low >= ?LARGE_GROWTH_ABS_LIMIT. + +calc_seg_size(Growth, Segs) -> + conf_size(round(Growth*?FRAG_FACT*?GROWTH_SEG_FACT) div Segs). + +calc_growth_segments(Conf, AlcList0) -> + CalcSmall = fun (#alloc{name = ll_alloc} = Alc, Acc) -> + {Alc#alloc{segments = #segment{size = 0, + number = 0}}, + Acc}; + (#alloc{alloc_util = true, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc, + {SL, AL}) -> + Growth = High - Low, + case large_growth(Low, High) of + true -> + {Alc, {SL, AL+1}}; + false -> + Segs = ?SMALL_GROWTH_SEGS, + SegSize = calc_seg_size(Growth, Segs), + {Alc#alloc{segments + = #segment{size = SegSize, + number = Segs}}, + {SL - Segs, AL}} + + end; + (Alc, Acc) -> {Alc, Acc} + end, + {AlcList1, {SegsLeft, AllocsLeft}} + = lists:mapfoldl(CalcSmall, {Conf#conf.segments, 0}, AlcList0), + case AllocsLeft of + 0 -> + AlcList1; + _ -> + SegsPerAlloc = case (SegsLeft div AllocsLeft) + 1 of + SPA when SPA < ?SMALL_GROWTH_SEGS -> + ?SMALL_GROWTH_SEGS; + SPA -> + SPA + end, + CalcLarge = fun (#alloc{alloc_util = true, + segments = undefined, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc) -> + Growth = High - Low, + SegSize = calc_seg_size(Growth, + SegsPerAlloc), + Alc#alloc{segments + = #segment{size = SegSize, + number = SegsPerAlloc}}; + (Alc) -> + Alc + end, + lists:map(CalcLarge, AlcList1) + end. + +mk_config(#conf{format_to = FTO} = Conf, AlcList) -> + format_header(FTO), + Res = lists:foreach(fun (Alc) -> conf_alloc(Conf, Alc) end, + calc_growth_segments(Conf, AlcList)), + format_footer(FTO), + Res. + +format_header(FTO) -> + {Y,Mo,D} = erlang:date(), + {H,Mi,S} = erlang:time(), + fcl(FTO), + fcl(FTO, "erts_alloc configuration"), + fcl(FTO), + fcp(FTO, + "This erts_alloc configuration was automatically " + "generated at ~w-~2..0w-~2..0w ~2..0w:~2..0w.~2..0w by " + "erts_alloc_config.", + [Y, Mo, D, H, Mi, S]), + fcp(FTO, + "~s was used when generating the configuration.", + [string:strip(erlang:system_info(system_version), both, $\n)]), + case erlang:system_info(schedulers) of + 1 -> ok; + Schdlrs -> + MinSchdlrs = case Schdlrs > ?MAX_ALLOCATOR_INSTANCES of + true -> ?MAX_ALLOCATOR_INSTANCES; + false -> Schdlrs + end, + fcp(FTO, + "NOTE: This configuration was made for ~p schedulers. " + "It is very important that at least ~p schedulers " + "are used.", + [Schdlrs, MinSchdlrs]) + end, + fcp(FTO, + "This configuration is intended as a suggestion and " + "may need to be adjusted manually. Instead of modifying " + "this file, you are advised to write another configuration " + "file and override values that you want to change. " + "Doing it this way simplifies things when you want to " + "rerun erts_alloc_config."), + fcp(FTO, + "This configuration is based on the actual use of " + "multi-block carriers (mbcs) for a set of different " + "runtime scenarios. Note that this configuration may " + "perform bad, ever horrible, for other runtime " + "scenarios."), + fcp(FTO, + "You are advised to rerun erts_alloc_config if the " + "applications run when the configuration was made " + "are changed, or if the load on the applications have " + "changed since the configuration was made. You are also " + "advised to rerun erts_alloc_config if the Erlang runtime " + "system used is changed."), + fcp(FTO, + "Note, that the singel-block carrier (sbc) parameters " + "very much effects the use of mbcs. Therefore, if you " + "change the sbc parameters, you are advised to rerun " + "erts_alloc_config."), + fcp(FTO, + "For more information see the erts_alloc_config(3) " + "documentation."), + ok. + +format_footer(FTO) -> + fcl(FTO). + +%%% +%%% Misc. +%%% + +b2kb(B) when is_integer(B) -> + MaxKB = (1 bsl erlang:system_info(wordsize)*8) div 1024, + case ?B2KB(B) of + KB when KB > MaxKB -> MaxKB; + KB -> KB + end. + +format(false, _Frmt) -> + ok; +format(IODev, Frmt) -> + io:format(IODev, Frmt, []). + +format(false, _Frmt, _Args) -> + ok; +format(IODev, Frmt, Args) -> + io:format(IODev, Frmt, Args). + +%% fcp: format comment paragraf +fcp(IODev, Frmt, Args) -> + fc(IODev, Frmt, Args), + format(IODev, "#~n"). + +fcp(IODev, Frmt) -> + fc(IODev, Frmt), + format(IODev, "#~n"). + +%% fc: format comment +fc(IODev, Frmt, Args) -> + fc(IODev, lists:flatten(io_lib:format(Frmt, Args))). + +fc(IODev, String) -> + fc_aux(IODev, string:tokens(String, " "), 0). + +fc_aux(_IODev, [], 0) -> + ok; +fc_aux(IODev, [], _Len) -> + format(IODev, "~n"); +fc_aux(IODev, [T|Ts], 0) -> + Len = 2 + length(T), + format(IODev, "# ~s", [T]), + fc_aux(IODev, Ts, Len); +fc_aux(IODev, [T|_Ts] = ATs, Len) when (length(T) + Len) >= ?PRINT_WITDH -> + format(IODev, "~n"), + fc_aux(IODev, ATs, 0); +fc_aux(IODev, [T|Ts], Len) -> + NewLen = Len + 1 + length(T), + format(IODev, " ~s", [T]), + fc_aux(IODev, Ts, NewLen). + +%% fcl: format comment line +fcl(FTO) -> + EndStr = "# ", + Precision = length(EndStr), + FieldWidth = -1*(?PRINT_WITDH), + format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, EndStr]). + +fcl(FTO, A) when is_atom(A) -> + fcl(FTO, atom_to_list(A)); +fcl(FTO, Str) when is_list(Str) -> + Str2 = "# --- " ++ Str ++ " ", + Precision = length(Str2), + FieldWidth = -1*(?PRINT_WITDH), + format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, Str2]). diff --git a/lib/runtime_tools/src/inviso_as_lib.erl b/lib/runtime_tools/src/inviso_as_lib.erl new file mode 100644 index 0000000000..75f3d9d004 --- /dev/null +++ b/lib/runtime_tools/src/inviso_as_lib.erl @@ -0,0 +1,155 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. 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% +%% + +%%% File : inviso_as_lib.erl +%%% Author : Lennart �hman <[email protected]> +%%% Description : +%% The purpose of the inviso autostart library is to provide useful functions +%% for anyone wanting to customize the autostart mechanism in the inviso +%% tracer. It is intended to work well with the example 'inviso_autostart_server'. +%%% +%%% Created : 15 Dec 2005 by Lennart �hman +%% ----------------------------------------------------------------------------- + +-module(inviso_as_lib). + +-export([setup_autostart/7,setup_autostart/8,setup_autostart/9, + inhibit_autostart/1, + set_repeat/2,set_repeat_2/2]). +%% ----------------------------------------------------------------------------- + +%% setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings) = ok|{error,Reason}. +%% Repeat=integer(), where 0 means no (more) autostarts. +%% Options=List of options as taken by the runtime component at start-up. +%% TracerData= Tracerdata as given to inviso_rt:init_tracing. +%% CmdFiles=[FileName,...] list of string(), files that will be executed +%% by the subprocess started during autostart. +%% Bindings=[{VarName,Value},...] Variable bindings for CmdFiles. +%% VarName=atom(), +%% +%% This function creates the inviso_autostart.config file on Erlang node Node. +%% This is useful when you wish to prepare for an autostarted trace. +setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings,Translations) -> + setup_autostart(Node,Repeat,Options,TracerData,CmdFiles, + Bindings,Translations,inviso_std_ref,off). +setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings,Translations,RTtag) -> + setup_autostart(Node,Repeat,Options,TracerData,CmdFiles, + Bindings,Translations,RTtag,off). +setup_autostart(Node,Repeat,Options,TracerData,CmdFiles,Bindings,Translations,RTtag,Dbg) -> + case rpc:call(Node,inviso_autostart,which_config_file,[]) of + FileName when is_list(FileName) -> % Write to this file then. + {String,Args}=format_config_file(Repeat,TracerData,Options,CmdFiles, + Bindings,Translations,RTtag,Dbg), + Bytes=list_to_binary(io_lib:format(String,Args)), + case rpc:call(Node,file,write_file,[FileName,Bytes]) of + ok -> + ok; + {error,Reason} -> + {error,{write_file,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{write_file,Reason}}} + end; + {error,Reason} -> + {error,{which_config_file,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{which_config_file,Reason}}} + end. +%% ----------------------------------------------------------------------------- + +%% inhibit_autostart(Node) = ok|{error,Reason} +%% +%% Inhibits autostart by simply making the repeat parameter zero in the +%% configuration file at node Node. All other parameters are left untouched. +inhibit_autostart(Node) -> + set_repeat(Node,0). +%% ----------------------------------------------------------------------------- + +%% set_repeat(Node,N)=ok | {error,Reason} +%% N=integer(), the number of time autostart shall be allowed. +set_repeat(Node,N) -> + case examine_config_file(Node) of + {ok,FileName,Terms} -> + NewTerms=[{repeat,N}|lists:keydelete(repeat,1,Terms)], + case rpc:call(Node,?MODULE,set_repeat_2,[FileName,NewTerms]) of + {badrpc,Reason} -> + {error,{badrpc,{open,Reason}}}; + Result -> + Result + end; + {error,Reason} -> + {error,Reason} + end. + +%% Must be a sepparate function to do rpc on. The entire function must be done +%% in one rpc call. Otherwise the FD will die since it is linked to the opening +%% process. +set_repeat_2(FileName,NewTerms) -> + case file:open(FileName,[write]) of + {ok,FD} -> + String=lists:flatten(lists:map(fun(_)->"~w.~n" end,NewTerms)), + case catch io:format(FD,String,NewTerms) of + ok -> + file:close(FD), + ok; + {'EXIT',Reason} -> + file:close(FD), + {error,{format,Reason}} + end; + {error,Reason} -> + {error,Reason} + end. +%% ----------------------------------------------------------------------------- + +examine_config_file(Node) -> + case rpc:call(Node,inviso_autostart,which_config_file,[]) of + FileName when is_list(FileName) -> % Read this file, and then modify it. + case rpc:call(Node,file,consult,[FileName]) of + {ok,Terms} -> + {ok,FileName,Terms}; + {error,Reason} -> + {error,{consult,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{consult,Reason}}} + end; + {error,Reason} -> + {error,{which_config_file,Reason}}; + {badrpc,Reason} -> + {error,{badrpc,{which_config_file,Reason}}} + end. +%% ----------------------------------------------------------------------------- + +format_config_file(Repeat,TracerData,Options,CmdFiles,Bindings,Translations,RTtag,Dbg) -> + String="~w.~n~w.~n~w.~n~w.~n", + Args=[{repeat,Repeat}, + {mfa,{inviso_autostart_server,init,[[{tracerdata,TracerData}, + {cmdfiles,CmdFiles}, + {bindings,Bindings}, + {translations,Translations}, + {debug,Dbg}]]}}, + {options,Options}, + {tag,RTtag}], + {String,Args}. +%% ----------------------------------------------------------------------------- + + + + + + + diff --git a/lib/runtime_tools/src/inviso_autostart.erl b/lib/runtime_tools/src/inviso_autostart.erl new file mode 100644 index 0000000000..134133ad1f --- /dev/null +++ b/lib/runtime_tools/src/inviso_autostart.erl @@ -0,0 +1,208 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. 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). + +-export([autostart/1,which_config_file/0]). + +%% This module implements the default autostart module for the inviso runtime +%% component. +%% It will: +%% (1) Open the autostart configuration file (either the default or the one +%% pointed out by the runtime_tools application parameter inviso_autostart_config). +%% (2) Check that the incarnation counter has not reached 0. If so, we do not +%% allow (yet) one autostart. +%% (3) Rewrite the configuration file if there was an incarnation counter. +%% (With the counter decreased). +%% (4) Inspect the content of the configuration file and pass paramters in the +%% return value (which is interpreted by the runtime component). +%% +%% CONTENT OF A CONFIGURATION FILE: +%% A plain text file containing erlang tuple terms, each ended with a period(.). +%% The following parameters are recognized: +%% {repeat,N} N=interger(), +%% The number of remaining allowed autostart incarnations of inviso. +%% {options,Options} Options=list() +%% The options which controls the runtime component, such as overload and +%% dependency. +%% {mfa,{Mod,Func,Args}} Args=list() +%% Controls how a spy process initiating tracing, patterns and flags shall +%% be started. +%% {tag,Tag} +%% The tag identifying the runtime component to control components. +%% ============================================================================= + +%% This function is run in the runtime component's context during autostart +%% to determine whether to continue and if, then how. +autostart(_AutoModArgs) -> + ConfigFile= + case application:get_env(inviso_autostart_conf) of + {ok,FileName} when is_list(FileName) -> % Use this filename then. + FileName; + {ok,{load,FileNames,{M,F}}} -> % First load the module, then... + case try_load_module(FileNames) of + ok -> + autostart_apply(M,F); + false -> % No such module available + "inviso_autostart.config" + end; + {ok,{gettia_asc,asc_file}} -> % Uggly hack to not have to change in GSN-CPS. + case try_load_module(["/tmp/DPE_COMMONLOG/gettia_asc", + "/tmp/DPE_COMMONLOG/gettia_overload"]) of + ok -> + autostart_apply(gettia_asc,asc_file); + false -> % No such module available + false + end; + {ok,{M,F}} -> % Use M:F(node()) + autostart_apply(M,F); + {ok,no_autostart} -> + false; + _ -> % Use a default name, in CWD! + "inviso_autostart.config" + end, + if + is_list(ConfigFile) -> + case file:consult(ConfigFile) of + {ok,Terms} -> % There is a configuration. + case handle_repeat(ConfigFile,Terms) of + ok -> % Handled or not, we shall continue. + {get_mfa(Terms),get_options(Terms),get_tag(Terms)}; + stop -> % We are out of allowed starts. + true % Then no autostart. + end; + {error,_} -> % There is no config file + true % Then no autostart! + end; + true -> % Skip it then. + true + end. + +autostart_apply(M,F) -> + case catch M:F(node()) of + FileName when is_list(FileName) -> + FileName; + no_autostart -> % No autostart after all. + false; + _ -> + "inviso_autostart.config" + end. + +%% This function is necessary since it is not always the case that all code-paths +%% are set at the time of an autostart. +try_load_module([AbsFileName|Rest]) when is_list(AbsFileName) -> + case catch code:load_abs(AbsFileName) of % May not be a proper filename. + {module,_Mod} -> + try_load_module(Rest); + _ -> + false + end; +try_load_module([]) -> % Load all beam files successfully. + ok; +try_load_module(AbsFileName) when is_list(AbsFileName) -> + try_load_module([AbsFileName]). +%% ----------------------------------------------------------------------------- + +%% Function returning the filename probably used as autostart config file. +%% Note that this function must be executed at the node in question. +which_config_file() -> + case application:get_env(runtime_tools,inviso_autostart_conf) of + {ok,FileName} when is_list(FileName) -> % Use this filename then. + FileName; + {ok,{M,F}} -> % Use M:F(node()) + case catch M:F(node()) of + FileName when is_list(FileName) -> + FileName; + _ -> + {ok,CWD}=file:get_cwd(), + filename:join(CWD,"inviso_autostart.config") + end; + _ -> % Use a default name, in CWD! + {ok,CWD}=file:get_cwd(), + filename:join(CWD,"inviso_autostart.config") + end. +%% ----------------------------------------------------------------------------- + + +%% Help function which finds out if there is a limit on the number of times +%% we shall autostart. If there is a repeat parameter and it is greater than +%% zero, the file must be rewritten with the parameter decreased with one. +%% Returns 'ok' or 'stop'. +handle_repeat(FileName,Terms) -> + case lists:keysearch(repeat,1,Terms) of + {value,{_,N}} when N>0 -> % Controlls how many time more. + handle_repeat_rewritefile(FileName,Terms,N-1), + ok; % Indicate that we shall continue. + {value,_} -> % No we have reached the limit. + stop; + false -> % There is no repeat parameter. + ok % No restrictions then! + end. + +%% Help function which writes the configuration file again, but with the +%% repeat parameter set to NewN. +%% Returns nothing significant. +handle_repeat_rewritefile(FileName,Term,NewN) -> + case file:open(FileName,[write]) of + {ok,FD} -> + NewTerm=lists:keyreplace(repeat,1,Term,{repeat,NewN}), + handle_repeat_rewritefile_2(FD,NewTerm), + file:close(FD); + {error,_Reason} -> % Not much we can do then?! + error + end. + +handle_repeat_rewritefile_2(FD,[Tuple|Rest]) -> + io:format(FD,"~w.~n",[Tuple]), + handle_repeat_rewritefile_2(FD,Rest); +handle_repeat_rewritefile_2(_,[]) -> + true. +%% ----------------------------------------------------------------------------- + +%% Three help functions finding the parameters possible to give to the runtime +%% component. Note that some of them have default values, should the parameter +%% not exist. +get_mfa(Terms) -> + case lists:keysearch(mfa,1,Terms) of + {value,{_,MFA}} -> + MFA; + false -> + false + end. + +get_options(Terms) -> + case lists:keysearch(options,1,Terms) of + {value,{_,Options}} -> + Options; + false -> + [] + end. + +get_tag(Terms) -> + case lists:keysearch(tag,1,Terms) of + {value,{_,Tag}} -> + Tag; + false -> + default_tag + end. +%% ----------------------------------------------------------------------------- + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + diff --git a/lib/runtime_tools/src/inviso_autostart_server.erl b/lib/runtime_tools/src/inviso_autostart_server.erl new file mode 100644 index 0000000000..5af96e4e39 --- /dev/null +++ b/lib/runtime_tools/src/inviso_autostart_server.erl @@ -0,0 +1,311 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. 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,_} -> % 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([FileName|Rest],GlobalBindings,Translations,Dbg) -> + interpret_cmd_files_1(FileName,GlobalBindings,Translations,Dbg), + interpret_cmd_files(Rest,GlobalBindings,Translations,Dbg); +interpret_cmd_files([],_,_,_) -> % Done, return nothing significant! + true. + +%% 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 + + + diff --git a/lib/runtime_tools/src/inviso_rt.erl b/lib/runtime_tools/src/inviso_rt.erl new file mode 100644 index 0000000000..dfab70b42e --- /dev/null +++ b/lib/runtime_tools/src/inviso_rt.erl @@ -0,0 +1,2895 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2009. 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: +%% Ann-Marie L�f, [email protected] +%% Lennart �hman, [email protected] +%% ----------------------------------------------------------------------------- + +-module(inviso_rt). + + +%% ----------------------------------------------------------------------------- +%% interface for supervisor +%% ----------------------------------------------------------------------------- +-export([start_link_man/3,start_link_auto/1]). + +%% API for controll component. +-export([start/4,stop/1, + init_tracing/2,stop_tracing_parallel/1, + try_to_adopt/3,confirm_connection/2,get_node_info/1, + suspend/2,call_suspend/2,cancel_suspension/1,change_options/2, + clear/2,clear_all_tp/1, + flush/1, + trace_patterns_parallel/3, + trace_flags_parallel/3,trace_flags_parallel/2,trace_flags_parallel/1, + meta_tracer_call_parallel/2, + get_status/1,get_tracerdata/1,list_logs/1,list_logs/2,fetch_log/2,fetch_log/3, + delete_log/1,delete_log/2, + state/1]). +%% ----------------------------------------------------------------------------- + +%% API mostly for autostart scripts, instead of corresponding control component +%% apis not available doing local function calls. +-export([init_tracing/1,tp/4,tp/5,tp/1,tpg/4,tpg/5,tpg/1, + tpl/4,tpl/5,tpl/1, + ctp/1,ctp/3,ctpg/1,ctpg/3,ctpl/1,ctpl/3, + init_tpm/4,init_tpm/7, + tpm/4,tpm/5,tpm/8,tpm_tracer/4,tpm_tracer/5,tpm_tracer/8, + tpm_ms/5,tpm_ms_tracer/5, + ctpm_ms/4, + local_register/0,global_register/0, + ctpm/3,remove_local_register/0,remove_global_register/0, + tf/2,tf/1,ctf/2,ctf/1]). +%% ----------------------------------------------------------------------------- + +%% Internal exports. +-export([init/4,auto_init/2,fetch_init/4]). +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Constants. +%% ----------------------------------------------------------------------------- + +-define(DEFAULT_OVERLOAD_FUNC,default_overload_func). +-define(NO_LOADCHECK,no_loadcheck). + +-define(RT_SUP,runtime_tools_sup). % Refers to the registered name. +-define(CTRL,inviso_c). % Refers to the registered name. +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Record definition. +%% ----------------------------------------------------------------------------- + +%% #rt +%% All record fields must be bound to listed values when leaving init or +%% auto_init. +%% dependency: Timeout accepting being without control component. +%% overload : Controlls which module to call, if any, when time for a check. +%% timer_ref: Used when timing delayed shutdown due to lost control component. +-record(rt,{state = new, % new | idle | tracing + status = running, % running | {suspended, Reason} + next_loadcheck = now(), % now | "No Loadcheck" + parent, % pid() + tracerdata, % undefined|{fun(),term()}|{file,Param}|{ip,Param} + tracer_port, % port() | undefined + handler, % {fun(), term()} | undefined + auto_starter, % pid() | undefined; proc starting interpreters. + meta_tracer, % undefined | pid() + fetchers=[], % [pid(),...] processes transfering logfiles. +% spies = [], + dependency={infinity,node()}, % {TOut,Node} | TOut; TOut=int()|infinity + overload=no_loadcheck, % ?NO_LOADCHECK|{LoadMF,Interval,InitMFA,RemoveMFA} + overload_data=void, % Datastructure given to LoadMF and RemoveMFA. + timer_ref, % undefined | reference() + ctrl, % undefined | pid() + ctrl_ref, % undefined | reference() + vsn, % list() + tag % term() + }). +%% ----------------------------------------------------------------------------- + + +%% ============================================================================== +%% Start API +%% ============================================================================== + +%% Note that the runtime component may be started in many different ways. +%% It can be autostarted by the runtime_tools_sup during initial start-up of the +%% system. It is actually most likely that it will be started that way. However +%% if there are no autostart trace-cases to run, the inviso_rt runtime component +%% will terminate. It will then however remain as a child of the runtime_tools_sup +%% supervisor. This means that if the runtime component is started again, manually, +%% by the control component, some actions must be taken. +%% For instance is it very likely that the child already exists. But since it +%% must be started with different arguments when started manually, the child-spec +%% must be changed. +%% +%% The runtime component is not a proper gen_server, to allow full control of +%% what happens. It however mimcs gen_server behaviour to be managed by the +%% runtime_tools_sup supervisor. + + +%% start_link_auto(AutoModArgs)={ok,Pid} +%% +%% This function is entered into the child-spec when planning on doing autostart +%% of the runtime component. The autostart is controlled by the so called +%% inviso_autostart_mod. It is an application environment parameter of the +%% runtime_tools application. If it exists, it shall point out a module name. +%% If it does not exist, the default 'inviso_autostart' module will be tried. +%% Note that these start_link functions do not implement proper otp-behaviour. +%% For instance they return {ok,Pid} immediately making the init-phase of the +%% runtime component process empty. +%% +%% The inviso_autostart_mod shall export one function: +%% autostart(AutoModArgs) -> {MFA,Options,Tag}, where +%% AutoModArgs=term(), comes from the application start parameters in the +%% runtime_tools application resource file. +%% MFA={Mod,Func,Args} | term(). +%% If it is MFA it will cause a trace initiator process to start spawning +%% on spawn_link(Mod,Func,Args). The trace initiator may for instance +%% initiate the wanted tracing. +start_link_auto(AutoModArgs) -> + {ok,spawn_link(?MODULE,auto_init,[AutoModArgs,self()])}. +%% ------------------------------------------------------------------------------ + +%% This function is entered into the child-specification of the runtime_tools_sup +%% if the runtime component shall be started manually via the control component. +start_link_man(Ctrl,Options,Tag) -> + {ok,spawn_link(?MODULE,init,[Ctrl,Options,Tag,self()])}. +%% ------------------------------------------------------------------------------ + +%% start(Node,Options,Tag,Condition)=tbd +%% Node=The node where the runtime component shall be started. +%% Options=[Opt]; List of options to the runtime component. +%% Opt={dependency,Val}|{dependency,{Val,Node}} +%% Val=int()|infinity +%% If the runtime component may run on its own or not. Val=0 means a runtime +%% component which will terminate immediately without its control component. +%% Note that if the runtime component is started manually, the Node part +%% is never used. The runtime is supposed to be dependent of the Ctrl mentioned +%% in the start_link_man parameters. +%% Opt={overload,OverLoad} | overload +%% The latter means no loadcheck. Necessary if changing the options. +%% Overload=Iterval (int() in milliseconds) | +%% {LoadMF,Interval}|{LoadMF,Interval,InitMFA,RemoveMFA} +%% LoadMF={Mod,Func}|function() +%% InitMFA,RemoveMFA={Mod,Func,ArgList} where +%% apply(InitM,InitF,InitArgs) -> {ok,DataStruct}|'void'. +%% apply(RemoveM,RemoveF,[DataStruct|Args]) -> don't care +%% LoadMF is called each time loadcheck is performed. +%% Mod:Func(DataStruct)->ok|{suspend,Reason} +%% If just Interval is used, it means using a default overload check. +%% Tag=term(), used to identify an incarnation of a runtime component so that +%% a control component reconnecting will know if it was its own incarnation +%% still alive, or some elses. +%% Condition='if_ref'|term(). Controls if we want to adopt the runtime component. +%% If 'if_ref' is stated it means that we only want to adopt a runtime component +%% with the suggested Tag. +%% +%% This is the API used by the control component when tries to start a runtime +%% component. Note that it will try to adopt an already running, if possible. +%% Adoptions are only possible if the runtime component at hand is running +%% without control component. +start(Node, Options, Tag, Condition) when Node == node() -> + ChildSpec = {?MODULE, {?MODULE, start_link_man, [self(), Options, Tag]}, + temporary, 5000, worker, [?MODULE]}, + case catch supervisor:start_child(?RT_SUP, ChildSpec) of + {ok, Pid} when is_pid(Pid) -> + {node_info, _Node, Pid, VSN, State, Status, _Tag} = + get_node_info(Pid), + {node_info, Node, Pid, VSN, State, Status, new}; + {error, already_present} -> + supervisor:delete_child(?RT_SUP, ?MODULE), + start(Node, Options, Tag, Condition); + {error, {already_started, Pid}} -> + try_to_adopt(Pid, Tag, Condition); + {error,Reason} -> + {error,Reason}; + {'EXIT',Reason} -> + {error,Reason} + end; +start(Node, Options, Tag, Condition) -> + case rt_version(Node) of + {error,Error} -> + {error,Error}; + _VSN -> + ChildSpec = {?MODULE, {?MODULE, start_link_man, + [self(), Options, Tag]}, + temporary, 5000, worker, [?MODULE]}, + case catch rpc:call(Node, supervisor, start_child, + [?RT_SUP, ChildSpec]) of + {ok, Pid} when is_pid(Pid) -> + {node_info, _Node, Pid, + VSN, State, Status, _Tag} = get_node_info(Pid), + {node_info, Node, Pid, VSN, State, Status, new}; + {error, already_present} -> + rpc:call(Node, supervisor, delete_child, + [?RT_SUP, ?MODULE]), + start(Node, Options, Tag, Condition); + {error, {already_started, Pid}} -> + try_to_adopt(Pid, Tag, Condition); + {error,Reason} -> % Could not start child. + {error,Reason}; + {badrpc,nodedown} -> + {error,nodedown}; + {badrpc,Reason} -> + {error,{badrpc,Reason}}; + {'EXIT',Reason} -> + {error,Reason} + end + end. + +rt_version(Node) -> + case catch rpc:call(Node,application,loaded_applications,[]) of + List when is_list(List) -> + case lists:keysearch(runtime_tools,1,List) of + {value,{_,_,VSN}} -> + VSN; + false -> + {error,not_loaded} + end; + {badrpc,nodedown} -> + {error,nodedown}; + {'EXIT',Reason} -> + {error,Reason} + end. +%% ------------------------------------------------------------------------------ + +%% stop(Node)=ok|{error,Reason} +%% Stops the runtim component on node Node. Note that this is mearly calling the +%% supervisor API to shutdown the inviso_rt child belonging to the runtime_tools_sup. +stop(Node) when Node==node() -> + supervisor:terminate_child(?RT_SUP,?MODULE), + supervisor:delete_child(?RT_SUP,?MODULE), + ok; +stop(Node) -> + case catch rpc:call(Node,supervisor,terminate_child,[?RT_SUP,?MODULE]) of + ok -> + stop_delete_child(Node); + {error,_} -> % No child running. + stop_delete_child(Node); % Make sure we remove it also. + {badrpc,Reason} -> + {error,{badrpc,Reason}}; + {'EXIT',Reason} -> + {error,Reason} + end. + +stop_delete_child(Node) -> + case catch rpc:call(Node,supervisor,delete_child,[?RT_SUP,?MODULE]) of + ok -> + ok; + {error,_} -> % No child running. + ok; + {badrpc,Reason} -> + {error,{badrpc,Reason}}; + {'EXIT',Reason} -> + {error,Reason} + end. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% API for the control component. +%% ============================================================================== + +%% init_tracing(TracerData) -> +%% TracerData = LogTD | [{trace,LogTD},{ti,TiTD}] +%% LogTD = {HandlerFun, Data} | collector | +%% {relayer, pid()} | {ip, IPPortParameters} | +%% {file, FilePortParameters} +%% TiTD = {file,FileName} | {file,FileName,{InitPublLD,RemovePublLD,CleanPublLD}} +%% | {relay,Node} | {relay,Node,{InitPublLD,RemovePublLD,CleanPublLD}} +%% HandlerFun=fun(TraceMsg,Data)->NewData +%% IPPortParameters = Portno | {Portno, Qsiz} +%% Qsiz = +%% FilePortParameters = {Filename, wrap, Tail, {time, WrapTime}, WrapCnt} | +%% {FileName, wrap, Tail, WrapSize, WrapCnt} | +%% {FileName, wrap, Tail, WrapSize} | +%% {FileName, wrap, Tail} | FileName +%% Defines a tracer: +%% {HandlerFun, Data} - will be used as handler inside the runtime component for +%% every incomming trace message. +%% relayer - the runtime component will relay all comming trace messages to +%% the runtime component Pid. +%% collector - the runtime component is used as tracer or collector of relayed +%% trace messages using the default handler writing them to io. +%% ip | file - will start a tracer port using PortParameters +init_tracing(Pid,TracerData) -> + call(Pid,{init_tracing,TracerData}). +%% ------------------------------------------------------------------------------ + +%% stop_tracing(RTpids)=[{Node,NodeResult},...] +%% RTpids=[RTinfo,...] +%% RTinfo={RTpid,Node} | {{error,Reason},Node} +%% NodeResult={ok,State} | {error,Reason} +%% Sends a request to stop tracing to all nodes in RTpids, in parallel. Stop +%% tracing means that all trace flags are removed and the nodes go to idle +%% state. +stop_tracing_parallel(RTpids) -> + call_parallel(lists:map(fun({Pid,Node})->{Pid,Node,stop_tracing}; + (Error)->Error + end, + RTpids)). +%% ------------------------------------------------------------------------------ + +%% try_to_adopt(Pid,NewTag,Condition)= +%% {node_info,node(),self(),VSN,State,Status,{tag,PreviousTag}}|{error,Reason} +%% NewTag=term(), the identification tag we want the runtime component to use +%% from now on if adoption was successful. +%% Condition='if_ref', only adopt if current tag is NewTag. +%% PreviousTag= the tag the runtime component had before it accepted the +%% adoption. +%% This function shall only be used by a control component wishing to adopt this +%% runtime component. +try_to_adopt(Pid, Tag, Condition) -> + call(Pid,{try_to_adopt,Tag,Condition}). +%% ------------------------------------------------------------------------------ + +%% confirm_connection(Pid,Tag)= {node_info,node(),self(),VSN,State,Status,Tag}| +%% {error,refused}. +%% Must only be used by a control component having been contacted by the runtime +%% component Pid. It confirms to the runtime component that the control component +%% has accepted the connect request. +confirm_connection(Pid,Tag) -> + call(Pid,{confirm_connection,Tag}). +%% ------------------------------------------------------------------------------ + +%% get_node_info(Pid)={node_info,Node,Pid,VSN,State,Status,Tag}. +get_node_info(Pid) -> + call(Pid,get_node_info). +%% ------------------------------------------------------------------------------ + +%% suspend(NodeOrPid,Reason)=ok +%% call_suspend(NodeOrPid,Reason)=ok +%% Makes the runtime component and all of its helpers suspend. suspend/2 is +%% assynchronous. +suspend(NodeOrPid,Reason) -> + cast(NodeOrPid,{suspend,Reason}). + +call_suspend(NodeOrPid,Reason) -> + call(NodeOrPid,{suspend,Reason}). +%% ------------------------------------------------------------------------------ + +%% cancel_suspension(Pid)=ok +%% Function moving the runtime component to status running. Regardless of its +%% current status. +cancel_suspension(Pid) -> + call(Pid,cancel_suspension). +%% ------------------------------------------------------------------------------ + +%% change_options(Pid,Options)=ok +%% Options=list(); see the start_link_XXX functions. +%% Changes options according to Options list. +%% Changing the control component we shall be depending on has no effect. The +%% dependency value in self can however be changed, and takes effect immediately. +change_options(Pid,Options) -> + call(Pid,{change_options,Options}). +%% ------------------------------------------------------------------------------ + +%% clear_all_tp(Pid)=ok +%% Function removing all, both local and global trace-patterns from the node. +clear_all_tp(Pid) -> + call(Pid,clear_all_tp). +%% ------------------------------------------------------------------------------ + +%% clear(Pid,Options)={ok,{new,Status}} +%% Options=[Opt,...] +%% Opt=keep_trace_patterns | keep_log_files +%% Resets the runtime component to state 'new' by stopping all ongoing tracing, +%% closing and removing all associated logfiles. The Options can be used to +%% prevent the runtime component from being totally erased. +clear(Pid,Options) -> + call(Pid,{clear,Options}). +%% ------------------------------------------------------------------------------ + +%% flush(Pid)=ok | {error,Reason} +%% Sends the flush command to the trace-port, if we are using a trace-port and +%% are tracing. +flush(Pid) -> + call(Pid,flush). +%% ------------------------------------------------------------------------------ + +%% trace_patterns_parallel(RTpids,Args,Flags)=[{Node,Answer},...] +%% RTpids=[{RTpid,Node},...] or [{Error,Node},...] +%% Args=[Arg,...] +%% Arg={Mod,Func,Arity,MS}|{Mod,Func,Arity,MS,Opts} +%% Mod=atom()|reg_exp()|{Dir,reg_exp()} +%% Dir=reg_exp() +%% Answer=[Answer,...] +%% Answer=int()|{error,Reason} +%% API function for the control component sending trace-patterns to a list of +%% runtime components. Returns a [{Node,Answer},...] list in the same order. +trace_patterns_parallel(RTpids,Args,Flags) -> % Same args and flags for all. + call_parallel(lists:map(fun({Pid,Node})when is_pid(Pid)->{Pid,Node,{tp,Args,Flags}}; + (Error)-> Error + end, + RTpids)). +%% ------------------------------------------------------------------------------ + +%% trace_flags_parallel(RTpids,Args,How)= +%% trace_flags_parallel(RTpidsArgs,How)= +%% trace_flags_parallel(RTpidsArgsHow)=[{Node,Reply},...] +%% RTpids=[RTpidEntry,...] +%% RTpidEntry={RTpid,Node}|{Error,Node} +%% Error=term(), any term you wish to have as reply in Answer assoc. to Node. +%% Args=[{Process,Flags},...] +%% Process=pid()|registeredname()|'all'|'new'|'existing' +%% Flags=List of the allowed process trace flags. +%% RTpidsArgs=[RTpidArgEntry,...] +%% RTpidArgEntry={RTpid,Node,Args}|{Error,Node} +%% RTpidsArgsHow=[RTpidArgsHowEntry,...] +%% RTpidArgsHowEntry={RTpid,Node,Args,How}|{Error,Node} +%% How=true|false +%% Reply={ok,Answers} +%% Answers=[Answer,...], one for each Args and in the same order. +%% Answer=int()|{error,Reason} +%% API function used by the control component to send flags to a list of runtime +%% components. Returns a list of [{Node,Answer},... ] in the same order. +trace_flags_parallel(RTpids,Args,How) -> % Same args for every node! + call_parallel(lists:map(fun({Pid,Node})when is_pid(Pid)->{Pid,Node,{tf,Args,How}}; + (Error)-> Error + end, + RTpids)). + +trace_flags_parallel(RTpidArgs,How) -> % Different args but same how. + call_parallel(lists:map(fun({Pid,Node,Args})when is_pid(Pid)-> + {Pid,Node,{tf,Args,How}}; + (Error)-> + Error + end, + RTpidArgs)). + +trace_flags_parallel(RTpidArgsHow) -> % Both different args and hows. + call_parallel(lists:map(fun({Pid,Node,Args,How})when is_pid(Pid)-> + {Pid,Node,{tf,Args,How}}; + (Error)-> + Error + end, + RTpidArgsHow)). +%% ------------------------------------------------------------------------------ + +%% meta_pattern(RTpids,Args)=[{Node,Answer},...] +%% RTpids=[{RTpid,Node},...] or [{Error,Node},...] +%% Args={FunctionName,ArgList} +%% FunctionName=atom() +%% ArgList=list(), list of the arguments to FunctionName. +%% Answer=[Answer,...] +%% Answer=int()|{error,Reason} +%% Makes a call to the meta-tracer through its runtime component. Returns a list +%% a answers in the same order as RTpids. Note that if "someone" has discovered +%% that there is an error with a particular node, the error answer can be placed +%% in the RTpids list from the start. +meta_tracer_call_parallel(RTpids,Args) -> % Same args for all nodes. + call_parallel(lists:map(fun({Pid,Node})when is_pid(Pid)-> + {Pid,Node,{meta_tracer_call,Args}}; + (Error)-> + Error + end, + RTpids)). +%% ------------------------------------------------------------------------------ + +%% get_status(Pid)={ok,{State,Status}} +%% State=new|tracing|idle +%% Status=running|{suspended,Reason} +get_status(Pid) -> + call(Pid,get_status). +%% ------------------------------------------------------------------------------ + +%% get_tracerdata(Pid)={ok,TracerData} | {ok,no_tracerdata} | {error,Reason} +%% TracerData=see init_tracing +%% Fetches the current tracerdata from the runtime component. +get_tracerdata(Pid) -> + call(Pid,get_tracerdata). +%% ------------------------------------------------------------------------------ + +%% list_log(Pid)={ok,no_log}|{ok,LogCollection}|{error,Reason} +%% list_log(Pid,TracerData)= +%% LogCollection=[LogTypes,...] +%% LogTypes={trace_log,Dir,Files}|{ti_log,Dir,Files} +%% Dir=string() +%% Files=[FileNameWithoutDir,...] +%% Lists all files associated with the current tracerdata. Or finds out which +%% files there are stored in this node given a tracerdata. +list_logs(Pid) -> + call(Pid,list_logs). +list_logs(Pid,TD) -> + call(Pid,{list_logs,TD}). +%% ------------------------------------------------------------------------------ + +%% fetch_log(Pid,CollectPid)={ok,FetcherPid}|{complete,no_log}|{error,Reason} +%% fetch_log(Pid,CollectPid,Spec)= +%% CollectPid=pid(), the process which will be given the transfered logs. +%% Spec=TracerData|LogCollection +%% Transferes a number of files using ditributed Erlang to CollectPid. This +%% function is supposed to be used internally by a control component. It returns +%% when the transfer is initiated and does not mean it is done or successful. +fetch_log(Pid,CollectPid) -> + call(Pid,{fetch_log,CollectPid}). +fetch_log(Pid,CollectPid,Spec) -> + call(Pid,{fetch_log,CollectPid,Spec}). +%% ------------------------------------------------------------------------------ + +%% delete_log(Pid,TracerDataOrLogList)={ok,Results}|{error,Reason} +%% TracerDataOrLogList=[FileNameWithPath,...]|LogCollection|TracerData +%% Results=[LogType,...] +%% LogType={trace_log,FileSpecs}|{ti_log,FilesSpecs} +%% FilesSpecs=[FileSpec,...] +%% FileSpec={ok,FileName}|{error,{Posix,FileName}} +%% Filename=string(), the filename without dir-path. +delete_log(Pid) -> + call(Pid,delete_logs). +delete_log(Pid,TracerDataOrLogList) -> + call(Pid,{delete_logs,TracerDataOrLogList}). +%% ------------------------------------------------------------------------------ + +%% state(NodeOrPid)=LoopData +%% Returns the loopdata of the runtime component. Only meant for debugging. +state(NodeOrPid) -> + call(NodeOrPid,state). +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% API for local calls made from the same node. E.g autostart. +%% ============================================================================== + +%% init_tracing(TracerData)= +%% See init_tracing/2. +init_tracing(TracerData) -> + call_regname(?MODULE,{init_tracing,TracerData}). +%% ------------------------------------------------------------------------------ + + +%% Meaning that these function does most often not have to be called by a +%% control component because there are more efficient ones above. + +%% tp(Module,Function,Arity,MatchSpec) -> +%% tp(Module,Function,Arity,MatchSpec,Opts) -> +%% tp(PatternList) -> +%% Module = '_'|atom()|ModRegExp|{DirRegExp,ModRegExp} +%% Function == atom() | '_' +%% Arity = integer() | '_' +%% MatchSpec = true | false | [] | matchspec() see ERTS User's guide for a +%% description of match specifications. +%% Opts=list(); 'only_loaded' +%% PatternList = [Pattern], +%% Pattern = {Module,Function,Arity,MatchSpec,Opts}, +%% Set trace pattern (global). +tp(Module,Function,Arity,MatchSpec) -> + tp(Module,Function,Arity,MatchSpec,[]). +tp(Module,Function,Arity,MatchSpec,Opts) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,MatchSpec,Opts}],[global]}). +tp(PatternList) -> + call_regname(?MODULE,{tp,PatternList,[global]}). +%% ------------------------------------------------------------------------------ + +tpg(Mod,Func,Arity,MatchSpec) -> + tp(Mod,Func,Arity,MatchSpec). +tpg(Mod,Func,Arity,MatchSpec,Opts) -> + tp(Mod,Func,Arity,MatchSpec,Opts). +tpg(PatternList) -> + tp(PatternList). +%% ------------------------------------------------------------------------------ + +%% tpl(Module,Function,Arity,MatchSpec) -> +%% tpl(Module,Function,Arity,MatchSpec,Opts) -> +%% tpl(PatternList) -> +%% Module = Function == atom() | '_' | RegExpMod | {RegExpDir,RegExpMod} +%% Arity = integer() | '_' +%% MatchSpec = true | false | [] | matchspec() see ERTS User's guide for a +%% Opts=list(); 'only_loaded' +%% description of match specifications. +%% PatternList = [Pattern], +%% Pattern = {Module, Function, Arity, MatchSpec}, +%% Set trace pattern (local). +tpl(Module,Function,Arity,MatchSpec) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,MatchSpec,[]}],[local]}). +tpl(Module,Function,Arity,MatchSpec,Opts) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,MatchSpec,Opts}],[local]}). +tpl(PatternList) -> + call_regname(?MODULE,{tp,PatternList,[local]}). +%% ------------------------------------------------------------------------------ + +%% ctp(Module,Function,Arity) -> +%% ctp(PatternList)= +%% Module = atom()|'_'|RegExpMod|{RegExpDir,RegExpMod} +%% Function == atom() | '_' +%% Arity = integer() | '_' +%% PatternList=[{Mod,Func,Arity},...] +%% Clear trace pattern (global). +%% Note that it is possible to clear patterns using regexps. But we can for +%% natural reasons only clear patterns for loaded modules. Further more there +%% seems to be a fault in the emulator (<=R10B) crashing if we remove patterns +%% for deleted modules. Therefore we use the only_loaded option. +ctp(Module,Function,Arity) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,false,[only_loaded]}],[global]}). +ctp(PatternList) -> + call_regname(?MODULE, + {tp, + lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList), + [global]}). +%% ------------------------------------------------------------------------------ + +ctpg(Mod,Func,Arity) -> + ctp(Mod,Func,Arity). +ctpg(PatternList) -> + ctp(PatternList). +%% ------------------------------------------------------------------------------ + +%% ctpl(Module,Function,Arity) -> +%% Module = atom()|'_'|RegExpMod|{RegExpDir,RegExpMod} +%% Function == atom() | '_' +%% Arity = integer() | '_' +%% PatternList=[{Mod,Func,Arity},...] +%% Clear trace pattern (local). +ctpl(Module,Function,Arity) -> + call_regname(?MODULE,{tp,[{Module,Function,Arity,false,[only_loaded]}],[local]}). +ctpl(PatternList) -> + call_regname(?MODULE, + {tp, + lists:map(fun({M,F,A})->{M,F,A,false,[only_loaded]} end,PatternList), + [local]}). +%% ------------------------------------------------------------------------------ + +init_tpm(Mod,Func,Arity,CallFunc) -> + call_regname(?MODULE,{meta_tracer_call,{init_tpm,[Mod,Func,Arity,CallFunc]}}). + +init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + call_regname(?MODULE, + {meta_tracer_call, + {init_tpm, + [Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}}). +%% ------------------------------------------------------------------------------ + +tpm(Mod,Func,Arity,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm,[Mod,Func,Arity,MS]}}). +tpm(Mod,Func,Arity,MS,CallFunc) -> + call_regname(?MODULE,{meta_tracer_call,{tpm,[Mod,Func,Arity,MS,CallFunc]}}). +tpm(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + call_regname(?MODULE, + {meta_tracer_call, + {tpm, + [Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}}). +%% ------------------------------------------------------------------------------ + +tpm_tracer(Mod,Func,Arity,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_tracer,[Mod,Func,Arity,MS]}}). +tpm_tracer(Mod,Func,Arity,MS,CallFunc) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_tracer,[Mod,Func,Arity,MS,CallFunc]}}). +tpm_tracer(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + call_regname(?MODULE, + {meta_tracer_call, + {tpm_tracer, + [Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc]}}). +%% ------------------------------------------------------------------------------ + +tpm_ms(Mod,Func,Arity,MSname,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_ms,[Mod,Func,Arity,MSname,MS]}}). +%% ------------------------------------------------------------------------------ + +tpm_ms_tracer(Mod,Func,Arity,MSname,MS) -> + call_regname(?MODULE,{meta_tracer_call,{tpm_ms_tracer,[Mod,Func,Arity,MSname,MS]}}). +%% ------------------------------------------------------------------------------ + +ctpm_ms(Mod,Func,Arity,MSname) -> + call_regname(?MODULE,{meta_tracer_call,{ctpm_ms,[Mod,Func,Arity,MSname]}}). +%% ------------------------------------------------------------------------------ + +local_register() -> + call_regname(?MODULE,{meta_tracer_call,{local_register,[]}}). +%% ------------------------------------------------------------------------------ + +global_register() -> + call_regname(?MODULE,{meta_tracer_call,{global_register,[]}}). +%% ------------------------------------------------------------------------------ + +ctpm(Mod,Func,Arity) -> + call_regname(?MODULE,{meta_tracer_call,{ctpm,[Mod,Func,Arity]}}). +%% ------------------------------------------------------------------------------ + +remove_local_register() -> + call_regname(?MODULE,{meta_tracer_call,{remove_local_register,[]}}). +%% ------------------------------------------------------------------------------ + +remove_global_register() -> + call_regname(?MODULE,{meta_tracer_call,{remove_global_register,[]}}). +%% ------------------------------------------------------------------------------ + +%% tf(PidSpec, FlagList) -> +%% tf(TraceConfList) -> +%% TraceConfList = [{PidSpec, FlagList}], +%% FlagList = [Flags], +%% PidSpec = all | new | existing | pid() | registeredname() +%% Flags = all | send | 'receive' | procs | call | silent | return_to | +%% running | garbage_collection | timestamp | cpu_timestamp | arity | +%% set_on_spawn | set_on_first_spawn | set_on_link | set_on_first_link +%% Set trace flags. +tf(PidSpec, FlagList) -> + call_regname(?MODULE,{tf,[{PidSpec,FlagList}],true}). + +tf(TraceConfList) -> + call_regname(?MODULE,{tf,TraceConfList,true}). +%% ------------------------------------------------------------------------------ + +%% ctf(PidSpec, FlagList) -> +%% ctf(TraceConfList) -> +%% TraceConfList = [{PidSpec, FlagList}], +%% FlagList = [Flags], +%% PidSpec = all | new | existing | pid() | registeredname() +%% Flags = all | send | 'receive' | procs | call | silent | return_to | +%% running | garbage_collection | timestamp | cpu_timestamp | arity | +%% set_on_spawn | set_on_first_spawn | set_on_link | set_on_first_link +%% Clear trace flags. +ctf(PidSpec, FlagList) -> + call_regname(?MODULE,{tf,[{PidSpec,FlagList}],false}). + +ctf(TraceConfList) -> + call_regname(?MODULE,{tf_as,TraceConfList,false}). +%% ------------------------------------------------------------------------------ + + +%% ------------------------------------------------------------------------------ +%% Client side functions. +%% ------------------------------------------------------------------------------ + +%% Call function managing the client to server communication. This function may +%% be run by a client on a different node. +%% Note that we must use two different functions for calling a named process and +%% calling the runtime component at a specified node. +call(Pid,Request) when is_pid(Pid) -> + call_2(Pid,Request); +call(Node,Request) when Node==node() -> % To our node! + call_2(?MODULE,Request); +call(Node,Request) when is_atom(Node) -> + call_2({?MODULE,Node},Request); +call(To,_Request) -> + {error,{badarg,To}}. + +call_regname(Name,Request) when is_atom(Name) -> % To a registered name. + call_2(Name,Request). + +call_2(To,Request) -> + MRef=erlang:monitor(process,To), % Use a monitor to avoid waiting for ever. + Ref=make_ref(), + case catch To ! {Request,self(),Ref} of % Can be a remote pid. + {'EXIT',_} -> % If we use registered name. + erlang:demonitor(MRef), % Maybe not necessary!? + receive + {'DOWN',MRef,_Type,_Obj,_Info} -> + true + after + 0 -> + true + end, + {error,not_started}; + _ -> % At least no obvious error. + receive + {Msg,Ref} -> + erlang:demonitor(MRef), + Msg; + {'DOWN',MRef,_Type,_Obj,Info} -> % The runtime component disapeared. + {error,{no_response,Info}} + end + end. +%% ----------------------------------------------------------------------------- + +%% Multicall function taking a list of [{Pid,Node,Request},...] and sends +%% a request to every Pid. This function then also allows you to send multiple +%% requests to the same Pid since it will sit and wait for all replies. +%% Note that RTspec may also be an [{{error,Reason},Node},...]. That tuple will +%% then be used as reply in the reply list. +%% Returns [{Node,Reply},...] for every element in RTspec, in the same order. +call_parallel(RTspec) -> + Ref=make_ref(), + {Nr,Pending}=call_parallel_2(RTspec,Ref,0,[]), + Replies=call_parallel_3(Ref,Pending,Nr,[],[]), + call_parallel_build_reply(RTspec,1,Replies). + +call_parallel_2([{Pid,Node,Request}|Rest],Ref,Nr,Pending) when is_pid(Pid) -> + Pid ! {Request,self(),{Ref,Nr+1}}, + MRef=erlang:monitor(process,Pid), % So we won't wait for ever for it. + call_parallel_2(Rest,Ref,Nr+1,[{Nr+1,Node,MRef}|Pending]); +call_parallel_2([{{error,_Reason},_Node}|Rest],Ref,Nr,Pending) -> + call_parallel_2(Rest,Ref,Nr,Pending); % Just skip it. This is no process. +call_parallel_2([_Faulty|Rest],Ref,Nr,Pending) -> % Should not happend. + call_parallel_2(Rest,Ref,Nr,Pending); % But we choose to skip it instead of crash. +call_parallel_2([],_,Nr,Pending) -> + {Nr,Pending}. + +%% Help function collecting reply-messages sent from the runtime components. We +%% count down until we got a reply for every pending request. Or if we get a DOWN +%% message indicating that the runtime component is no longer present. Note that +%% we can by accident read away DOWN messages not belonging to this procedure. +%% They are collected to be reissued after we are done. +call_parallel_3(_Ref,_Pending,0,Replies,DownMsgs) -> % All expected received. + lists:foreach(fun({MRef,Pid,Info}) -> self() ! {'DOWN',MRef,process,Pid,Info} end, + DownMsgs), % Reissue the down messages! + Replies; +call_parallel_3(Ref,Pending,NrOfPending,Replies,DownMsgs) -> + receive + {Reply,{Ref,Nr}} -> + case lists:keysearch(Nr,1,Pending) of + {value,{_Nr,Node,MRef}} -> + erlang:demonitor(MRef), + call_parallel_3(Ref,Pending,NrOfPending-1, + [{Nr,Node,Reply}|Replies],DownMsgs); + false -> % Really strange! + call_parallel_3(Ref,Pending,NrOfPending,Replies,DownMsgs) + end; + {'DOWN',MRef,process,Pid,Info} -> % Probably process we monitor terminated. + case lists:keysearch(MRef,3,Pending) of + {value,{Nr,Node,_}} -> % Yes it was one of our processes. + call_parallel_3(Ref,Pending,NrOfPending-1, + [{Nr,Node,{error,no_reponse}}|Replies],DownMsgs); + false -> % We picked up a DOWN msg by misstake. + call_parallel_3(Ref,Pending,NrOfPending,Replies, + [{MRef,Pid,Info}|DownMsgs]) + end + end. + +%% Help function which build up the [{Node,Reply},...] list in the same order as RTspec. +call_parallel_build_reply([],_,_) -> + []; +call_parallel_build_reply([{Pid,Node,_Request}|Rest],Nr,Replies) when is_pid(Pid) -> + {value,{_Nr,_Node,Reply}}=lists:keysearch(Nr,1,Replies), + [{Node,Reply}|call_parallel_build_reply(Rest,Nr+1,Replies)]; +call_parallel_build_reply([{{error,Reason},Node}|Rest],Nr,Replies) -> + [{Node,{error,Reason}}|call_parallel_build_reply(Rest,Nr,Replies)]; +call_parallel_build_reply([_Faulty|Rest],Nr,Replies) -> + call_parallel_build_reply(Rest,Nr,Replies). +%% ------------------------------------------------------------------------------ + +cast(Pid,Request) when is_pid(Pid) -> + cast2(Pid,Request); +cast(Node,Request) when Node==node() -> + catch cast2(?MODULE,Request), + ok; +cast(Node,Request) when is_atom(Node) -> + catch cast2({?MODULE,Node},Request), + ok; +cast(BadAddress,_Request) -> + {error,{badarg,BadAddress}}. + +cast2(To,Request) -> + To ! {Request,void,void}. % Mimics the call protocol. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% Implementation of the runtime component (server side). +%% ============================================================================== + +%% Since the runtime component is not implemented using gen_sever we are "free" +%% to use what ever functionnames we like. + +%% Initial function on which the runtime component is spawned on if started by +%% a controlcomponent. +init(Ctrl, Options, Tag, Parent) when is_list(Options) -> + %% started from controller + process_flag(trap_exit,true), + register(?MODULE,self()), % Will crash if rt is already running + do_clear_trace_patterns(), % Remove potential old patterns left. + LD1=read_option_list(Options, + #rt{state=new, + parent=Parent, + ctrl=Ctrl, + vsn=get_application_vsn(), + tag=Tag}), + OverloadData=initialize_overload(LD1), + CtrlRef=erlang:monitor(process,Ctrl), % Monitor our control component. + loop1(LD1#rt{ctrl_ref=CtrlRef,overload_data=OverloadData}). +%% ---------------------------------------------------------------------------- + +%% Initial function on which the runtime component is spawned on if started +%% by the runtime_tools supervisor. It is here it is determined if we shall +%% autostart. +auto_init(AutoModArgs,Parent) -> + %% autostart + process_flag(trap_exit, true), + register(?MODULE, self()), % Will crash if a rt is already running + AutoMod=get_autostart_module(), % Determine which module to use! + case catch AutoMod:autostart(AutoModArgs) of + {MFA,Options,Tag} -> + do_clear_trace_patterns(), % Remove previously left patterns. + LD1=read_option_list(Options,#rt{state=new, + parent=Parent, + vsn=get_application_vsn(), + tag=Tag}), + case auto_init_connect_control(LD1) of + {ok,LD2} -> % Either connected or running_alone. + OverloadData=initialize_overload(LD2), + case auto_init_check_mfa(MFA) of + {ok,{M,F,A}} -> % We shall start somekind of tracing! + P=spawn_link(M,F,A), % It lives its own life, only link! + loop1(LD2#rt{auto_starter=P,overload_data=OverloadData}); + false -> + loop1(LD2#rt{overload_data=OverloadData}) + end; + stop -> % Not allowed to run alone! + true % Simply terminate. + end; + _ -> % Non existent or faulty autostart mod! + true % Terminate normally. + end. + +auto_init_connect_control(LD1) -> + case auto_init_connect_find_pid(LD1#rt.dependency) of + Pid when is_pid(Pid) -> % There is a control component. + CtrlRef=erlang:monitor(process,Pid), + Pid ! {connect,node(),self(),LD1#rt.vsn,LD1#rt.tag}, + {ok,LD1#rt{ctrl_ref=CtrlRef,ctrl=Pid}}; + _ -> % There is no control component. + do_down_message(LD1) % Will return 'stop' or a LoopData. + end. + +%% Help function which finds the pid of the control component. +auto_init_connect_find_pid({_TimeOut,Node}) when Node==node() -> + whereis(?CTRL); +auto_init_connect_find_pid({_TimeOut,Node}) when is_atom(Node) -> + rpc:call(Node,erlang,whereis,[?CTRL]); +auto_init_connect_find_pid(_) -> % Node is not a proper node. + undefined. % Act as could not find control comp. + +%% Help function checking that the parameter is reasonable to be used as +%% spawn_link argument. +auto_init_check_mfa({M,F,A}) when is_atom(M),is_atom(F),is_list(A) -> + {ok,{M,F,A}}; +auto_init_check_mfa(_) -> + false. + +%% Help function to init_auto which finds out which module to call for +%% guidance on how to proceed. Returns an atom. +get_autostart_module() -> + case application:get_env(inviso_autostart_mod) of + {ok,Mod} when is_atom(Mod) -> + Mod; + _ -> + inviso_autostart % The default autostart module. + end. +%% ---------------------------------------------------------------------------- + + +%% This is the preloop function which performs loadcheck if necessary. Note +%% that it calculates the timeout used in the after in the real loop. There is +%% further no use doing overload checks if we are not tracing or already +%% suspended. There is yet one more situation, we do not want to perform +%% overload checks if the interval is set to infinity. This can be the case if +%% we are using an external source pushing overload information instead. +loop1(LD=#rt{overload=Overload}) -> + if + Overload/=?NO_LOADCHECK,element(2,Overload)/=infinity -> + Now=now(), + if + LD#rt.status==running, + LD#rt.state==tracing, + Now>LD#rt.next_loadcheck -> % Do loadcheck only then! + {NewLD,TimeOut}=do_check_overload(LD,{timeout,LD#rt.overload_data}), + loop(NewLD,TimeOut); + LD#rt.status==running,LD#rt.state==tracing -> + Timeout=calc_diff_to_now(Now,LD#rt.next_loadcheck), + loop(LD,Timeout); + true -> % Do not spend CPU on this! :-) + loop(LD,infinity) + end; + true -> % Either no check or infinity. + loop(LD,infinity) + end. + +loop(LoopData,Timeout) -> + receive + Msg when element(1,Msg)==trace_ts; + element(1,Msg)==trace; + element(1,Msg)==drop; + element(1,Msg)==seq_trace -> + case LoopData#rt.handler of + {HandlerFun,Data} -> + NewData=HandlerFun(Msg,Data), + loop1(LoopData#rt{handler={HandlerFun,NewData}}); + _ -> + loop1(LoopData) + end; + {{tp,Args,Flags},From,Ref} -> + if + LoopData#rt.status==running -> % Not when suspended. + Reply=do_set_trace_patterns(Args,Flags), + if + LoopData#rt.state==new -> % No longer new when tp set. + reply_and_loop({ok,Reply},From,Ref,LoopData#rt{state=idle}); + true -> + reply_and_loop({ok,Reply},From,Ref,LoopData) + end; + true -> % We are suspended! + reply_and_loop({error,suspended},From,Ref,LoopData) + end; + {{tf,Args,How},From,MRef} -> + Reply= + case How of + true -> + if + LoopData#rt.status==running -> + case {LoopData#rt.tracer_port,LoopData#rt.handler} of + {Port,_} when is_port(Port) -> + do_set_trace_flags(Port,Args,How); + {_,{Handler,_D}} when is_function(Handler) -> + do_set_trace_flags(self(),Args,How); + _ -> + {error,no_tracer} + end; + true -> % Can't turn *on* flags if suspended. + {error, suspended} + end; + false -> % No tracer needed when turning off. + do_set_trace_flags(void,Args,How) + end, + reply_and_loop(Reply,From,MRef,LoopData); + {{meta_tracer_call,Args},From,MRef} -> + if + LoopData#rt.status==running -> + case LoopData#rt.meta_tracer of + MPid when is_pid(MPid) -> + Reply=do_meta_pattern(MPid,Args), + reply_and_loop(Reply,From,MRef,LoopData); + _ -> + reply_and_loop({error,no_metatracer},From,MRef,LoopData) + end; + true -> + reply_and_loop({error,suspended},From,MRef,LoopData) + end; + {clear_all_tp,From,MRef} -> + do_clear_trace_patterns(), + reply_and_loop(ok,From,MRef,LoopData); + {{init_tracing,TracerData},From,MRef} -> + {NewLoopData,Reply}= + if + LoopData#rt.status==running -> + if + LoopData#rt.state==tracing -> + {LoopData,{error,already_initiated}}; + true -> % Otherwise, try to init-tracing! + case translate_td(TracerData) of + {ok,LogTD,MetaTD} -> + do_init_tracing(LoopData,TracerData,LogTD,MetaTD); + Error -> + {LoopData,Error} + end + end; + true -> % Can't init tracing if not running. + {LoopData,{error,suspended}} + end, + reply_and_loop(Reply,From,MRef,NewLoopData); + {stop_tracing,From,MRef} -> + case LoopData#rt.state of + tracing -> % Only case we need to do anything. + reply_and_loop({ok,idle},From,MRef,do_stop_tracing(LoopData)); + idle -> % Already idle! + reply_and_loop({ok,idle},From,MRef,LoopData); + new -> % Have actually never traced! + reply_and_loop({ok,new},From,MRef,LoopData) + end; + {{suspend,Reason},From,MRef} -> + if + LoopData#rt.status==running -> + NewLD=do_suspend(LoopData,Reason), + reply_and_loop(ok,From,MRef,NewLD); + true -> % No need suspend if not running! + reply_and_loop(ok,From,MRef,LoopData) + end; + {cancel_suspension,From,MRef} -> + NewLoopData=LoopData#rt{status=running,next_loadcheck=now()}, + send_event(state_change,NewLoopData), + reply_and_loop(ok,From,MRef,NewLoopData); + {{clear,Options},From,MRef} -> + NewLoopData=do_clear(LoopData,Options), + reply_and_loop({ok,{new,NewLoopData#rt.status}},From,MRef,NewLoopData); + {flush,From,MRef} -> + case LoopData#rt.state of + tracing -> % Can only flush if we are tracing. + if + is_port(LoopData#rt.tracer_port) -> + trace_port_control(LoopData#rt.tracer_port,flush), + reply_and_loop(ok,From,MRef,LoopData); + true -> % Not necessary but lets pretend. + reply_and_loop(ok,From,MRef,LoopData) + end; + State -> + reply_and_loop({error,{not_tracing,State}},From,MRef,LoopData) + end; + {list_logs,From,MRef} -> + TracerData=LoopData#rt.tracerdata, % Current tracerdata. + if + TracerData/=undefined -> % There is tracerdata! + reply_and_loop(do_list_logs(TracerData),From,MRef,LoopData); + true -> % Have no current tracerdata! + reply_and_loop({error,no_tracerdata},From,MRef,LoopData) + end; + {{list_logs,TracerData},From,MRef} -> + reply_and_loop(do_list_logs(TracerData),From,MRef,LoopData); + {{fetch_log,CollectPid},From,MRef} -> % Fetch according to current tracerdata. + TracerData=LoopData#rt.tracerdata, % Current tracerdata. + if + TracerData/=undefined -> % There is tracerdata! + {Reply,NewLD}=do_fetch_log(LoopData,CollectPid,TracerData), + reply_and_loop(Reply,From,MRef,NewLD); + true -> % No tracerdata! + reply_and_loop({error,no_tracerdata},From,MRef,LoopData) + end; + {{fetch_log,CollectPid,Spec},From,MRef} -> % Either list of files or tracerdata. + {Reply,NewLD}=do_fetch_log(LoopData,CollectPid,Spec), + reply_and_loop(Reply,From,MRef,NewLD); + {delete_logs,From,MRef} -> + if + LoopData#rt.state==tracing -> % Can't remove then! + reply_and_loop({error,tracing},From,MRef,LoopData); + true -> + TracerData=LoopData#rt.tracerdata, + reply_and_loop(do_delete_logs(TracerData),From,MRef,LoopData) + end; + {{delete_logs,TracerDataOrLogList},From,MRef} -> + if + LoopData#rt.state==tracing -> % Can't remove then! + reply_and_loop({error,tracing},From,MRef,LoopData); + true -> + reply_and_loop(do_delete_logs(TracerDataOrLogList),From,MRef,LoopData) + end; + {get_node_info,From,MRef} -> + Reply=collect_node_info(LoopData), + reply_and_loop(Reply,From,MRef,LoopData); + {{try_to_adopt,Tag,Condition},From,MRef} -> + if + LoopData#rt.ctrl_ref==undefined -> % We have no control component. + {Reply,NewLoopData}=do_try_to_adopt(Tag,Condition,LoopData,From), + reply_and_loop(Reply,From,MRef,NewLoopData); + true -> % We already have a control component. + reply_and_loop({error,refused},From,MRef,LoopData) + end; + {{confirm_connection,_Tag},From,MRef} -> + if + LoopData#rt.ctrl==From -> % It must be from this process! + Reply=collect_node_info(LoopData), + reply_and_loop(Reply,From,MRef,LoopData); + true -> % Strange, some one is joking? + reply_and_loop({error,refused},From,MRef,LoopData) + end; + {{change_options,Options},From,MRef} -> + case do_change_options(Options,LoopData) of + stop -> % Can't run alone with these options! + terminate_overload(LoopData), + From ! {ok,MRef}; % Don't care if From not a proper pid! + NewLoopData when is_record(NewLoopData,rt) -> + reply_and_loop(ok,From,MRef,NewLoopData) + end; + {get_status,From,MRef} -> + Reply={ok,{LoopData#rt.state,LoopData#rt.status}}, + reply_and_loop(Reply,From,MRef,LoopData); + {get_tracerdata,From,MRef} -> + case LoopData#rt.tracerdata of + undefined -> + reply_and_loop({ok,no_tracerdata},From,MRef,LoopData); + TracerData -> + reply_and_loop({ok,TracerData},From,MRef,LoopData) + end; + {state,From,MRef} -> % For debugging purposes. + reply_and_loop(LoopData,From,MRef,LoopData); + + {'DOWN',CtrlRef,process,_,_} when CtrlRef==LoopData#rt.ctrl_ref -> + case do_down_message(LoopData) of + stop -> % inviso_c gone and we must stop! + terminate_overload(LoopData), + exit(running_alone); + {ok,NewLoopData} -> + loop1(NewLoopData) + end; + {'EXIT',Pid,Reason} -> + case act_on_exit(Pid,Reason,LoopData) of + exit -> + terminate_overload(LoopData), + exit(Reason); + NewLoopData when is_record(NewLoopData,rt) -> + loop1(NewLoopData); + {NewLoopData,NewTimeOut} when is_record(NewLoopData,rt) -> + loop(NewLoopData,NewTimeOut) + end; + Other -> % Check if it concerns overload. + if + LoopData#rt.overload/=?NO_LOADCHECK, + LoopData#rt.status==running, + LoopData#rt.state==tracing -> + {NewLD,NewTimeOut}= + do_check_overload(LoopData, + {msg,{Other,LoopData#rt.overload_data}}), + loop(NewLD,NewTimeOut); + true -> + NewTimeOut=calc_diff_to_now(now(),LoopData#rt.next_loadcheck), + loop(LoopData,NewTimeOut) + end + after + Timeout -> + loop1(LoopData) + end. + +reply_and_loop(Reply,To,MRef,LoopData) when is_pid(To) -> + To ! {Reply,MRef}, + loop1(LoopData); +reply_and_loop(_,_,_,LoopData) -> % Used together with incoming casts. + loop1(LoopData). +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% File transfer process implementation. +%% ============================================================================= + +%% Files that are to to be transfered from the runtime component to the control +%% component are done so by reading them as binaries and sending them with +%% normal message passing (over distributed Erlang). +%% Reading the files are done in a process separate to the runtime component, +%% to both make the code more simple. But also to free up the runtime component. +%% +%% This help process must be capable of recognizing the fact that the runtime +%% component has been suspended, and then of course also discontinue any file +%% transfere. +fetch_init(Parent,Files,CollectPid,ChunkSize) -> + process_flag(trap_exit,true), % We must clean-up. + process_flag(priority,low), % Lets be careful. + case fetch_open_file(Files,CollectPid) of + {ok,FileName,FD,RestFiles} -> + MRef=erlang:monitor(process,CollectPid), + fetch_loop(Parent,RestFiles,CollectPid,ChunkSize,FileName,FD,MRef); + done -> + fetch_end(CollectPid); + error -> + fetch_incomplete(CollectPid) + end. + +fetch_loop(Parent,Files,CollectPid,ChunkSize,FName,FD,MRef) -> + receive + {suspend,Parent} -> % The runtime component is suspended. + file:close(FD), % We must clean-up. + fetch_incomplete(CollectPid); + {'DOWN',MRef,process,_,_} -> % The CollectPid terminated! + file:close(FD); % Close file and terminate. + {'EXIT',Parent,_Reason} -> % The runtime component terminated. + file:close(FD), + fetch_incomplete(CollectPid); + _ -> + fetch_loop(Parent,Files,CollectPid,ChunkSize,FName,FD,MRef) + after + 0 -> % If non of the above, get to work! + case file:read(FD,ChunkSize) of + {ok,Bin} -> + fetch_send_chunk(CollectPid,Bin), + case fetch_wait_for_chunk_ack(CollectPid,MRef) of + ok -> % Collector ready to receive next chunk. + fetch_loop(Parent,Files,CollectPid,ChunkSize,FName,FD,MRef); + cancel -> % Send no more files! + file:close(FD), % Close file, send incomplete, terminate! + fetch_incomplete(CollectPid); + 'DOWN' -> % Collector has terminate, stop! + file:close(FD) % Close file and terminate. + end; + eof -> % Ok, go on with the next file. + file:close(FD), + fetch_send_eof(CollectPid), + case fetch_open_file(Files,CollectPid) of + {ok,NewFName,NewFD,RestFiles} -> + fetch_loop(Parent,RestFiles,CollectPid, + ChunkSize,NewFName,NewFD,MRef); + done -> + fetch_end(CollectPid); + error -> + fetch_incomplete(CollectPid) + end; + {error,Reason} -> % Do not continue. + file:close(FD), + fetch_send_readerror(CollectPid,FName,Reason), + fetch_incomplete(CollectPid) + end + end. +%% ----------------------------------------------------------------------------- + +%% Help function which opens the next file to be transferred. It also communicates +%% the opening of the file to the collector process. +%% We know here that it will be a list of three-tuples. But there is no guarantee +%% that Dir or FileName are proper strings. +%% Returns {ok,FileName,FileDescriptor,RemainingFiles} or 'done'. +fetch_open_file([{FType,Dir,FileName}|RestFiles],CollectPid) -> + case catch file:open(filename:join(Dir,FileName),[read,raw,binary]) of + {ok,FD} -> + CollectPid ! {node(),open,{FType,FileName}}, + {ok,FileName,FD,RestFiles}; + {error,_Reason} -> + CollectPid ! {node(),open_failure,{FType,FileName}}, + error; + {'EXIT',_Reason} -> % Faulty Dir or FileName. + CollectPid ! {node(),open_failure,{FType,FileName}}, + error + end; +fetch_open_file([],_CollectPid) -> + done. +%% ----------------------------------------------------------------------------- + +%% A group of help functions sending information to the collector process. +%% Returns nothing significant. +fetch_send_chunk(CollectPid,Bin) -> + CollectPid ! {node(),payload,Bin,self()}. +%% ----------------------------------------------------------------------------- + +fetch_send_eof(CollectPid) -> + CollectPid ! {node(),end_of_file}. +%% ----------------------------------------------------------------------------- + +fetch_end(CollectPid) -> + CollectPid ! {node(),end_of_transmission}. +%% ----------------------------------------------------------------------------- + +fetch_send_readerror(CollectPid,FName,Reason) -> + CollectPid ! {node(),{error,{file_read,{Reason,FName}}}}. +%% ----------------------------------------------------------------------------- + +fetch_incomplete(CollectPid) -> + CollectPid ! {node(),incomplete}. +%% ----------------------------------------------------------------------------- + +%% Help function waiting for the collector to respond that it is ready to receive +%% the next chunk. This is in order to exercise flow control protecting the +%% collector to get swamped if the node where the collector runs is busy. +fetch_wait_for_chunk_ack(CollectPid,MRef) -> + receive + {CollectPid,chunk_ack} -> + ok; + {CollectPid,cancel_transmission} -> % Some problem at collector side. + cancel; + {'DOWN',MRef,process,_,_} -> % The collector terminated. + 'DOWN' + end. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% First level do-functions, called from the main server loop on incomming +%% requests. +%% ============================================================================= + +%% Function performing the overload check. Returns {NewLoopData,TimeOut}. +%% Note that this function may also cause a suspend to be carried out if the +%% loadcheck turns out negative. +do_check_overload(LD,Data) -> + case do_check_overload_2(LD#rt.overload,Data) of + ignore -> % Load check not performed. + {LD,calc_diff_to_now(now(),LD#rt.next_loadcheck)}; + {ok,Interval} -> % No problem, continue. + NextLoadCheck=add_to_now(now(),Interval), + {LD#rt{next_loadcheck=NextLoadCheck},Interval}; + {suspend,Reason} -> % Emergency! suspend, suspend! + NewLD=do_suspend(LD,Reason), + {NewLD,infinity}; % No need to do load-checks now! + {new,NewData,Interval} -> % The overload was restarted or something. + NextLoadCheck=add_to_now(now(),Interval), + {LD#rt{overload_data=NewData,next_loadcheck=NextLoadCheck},Interval}; + error -> % Inhibit overload check then. + {LD#rt{overload=?NO_LOADCHECK},infinity} + end. + +%% Help function performing an overload check. Returns {ok,Interval}, +%% {suspend,Reason}, 'error' ir 'ignore'. +do_check_overload_2({{Mod,Func},Interval,_,_},Data) -> + do_check_overload_3(Interval,catch Mod:Func(Data)); +do_check_overload_2({Fun,Interval,_,_},Data) when is_function(Fun) -> + do_check_overload_3(Interval,catch Fun(Data)); +do_check_overload_2(_,_) -> % Bad loadcheck configuration. + error. % Stop using load checks then. + +do_check_overload_3(Interval,ok) -> + {ok,Interval}; +do_check_overload_3(Interval,{new,NewData}) -> + {new,NewData,Interval}; +do_check_overload_3(_Interval,{suspend,Reason}) -> + {suspend,Reason}; +do_check_overload_3(_Interval,ignore) -> % Loadcheck not triggered. + ignore; +do_check_overload_3(_Interval,_) -> % Failure or other return value. + error. % Stop doing loadchecks from now on. +%% ------------------------------------------------------------------------------ + +%% Function setting the trace-pattern according to Args and Flags. Note that +%% Args can contain regexps which must be expanded here. +%% Returns a list: [Result], where Result can be: int()|{error,Reason}. +%% Sometimes an error tuple will represent an entire pattern, sometimes the +%% pattern will expand to a number of error-tuples. +do_set_trace_patterns(Args,Flags) -> + Replies=do_set_trace_patterns_2(Args,Flags,[]), + lists:reverse(Replies). + +do_set_trace_patterns_2([{M,F,Arity,MS}|Rest],Flags,Replies) -> % Option-less. + do_set_trace_patterns_2([{M,F,Arity,MS,[]}|Rest],Flags,Replies); +do_set_trace_patterns_2([{M,F,Arity,MS,Opts}|Rest],Flags,Replies) when is_atom(M) -> + case load_module_on_option(M,Opts) of + true -> % Already present, loaded or no option! + case catch erlang:trace_pattern({M,F,Arity},MS,Flags) of + No when is_integer(No) -> + do_set_trace_patterns_2(Rest,Flags,[No|Replies]); + {'EXIT',Reason} -> + do_set_trace_patterns_2(Rest, + Flags, + [{error,{bad_trace_args,[{M,F,Arity,MS},Reason]}}| + Replies]) + end; + false -> % Module not present, or not found! + do_set_trace_patterns_2(Rest,Flags,[0|Replies]) + end; +do_set_trace_patterns_2([{M,F,Arity,MS,Opts}|Rest],Flags,Replies) when is_list(M) -> + case check_pattern_parameters(void,F,Arity,MS) of % We don't want to repeat bad params. + ok -> + case inviso_rt_lib:expand_regexp(M,Opts) of % Get a list of real modulnames. + Mods when is_list(Mods) -> + MoreReplies= + do_set_trace_patterns_2(lists:map(fun(Mod)-> + {Mod,F,Arity,MS,Opts} + end, + Mods), + Flags, + Replies), + do_set_trace_patterns_2(Rest,Flags,MoreReplies); + {error,Reason} -> + do_set_trace_patterns_2(Rest,Flags,[{error,Reason}|Replies]) + end; + error -> % Bad pattern parameters. + do_set_trace_patterns_2(Rest, + Flags, + [{error,{bad_trace_args,{M,F,Arity,MS}}}|Replies]) + end; +do_set_trace_patterns_2([{{Dir,M},F,Arity,MS,Opts}|Rest],Flags,Replies) + when is_list(Dir),is_list(M) -> + case check_pattern_parameters(void,F,Arity,MS) of % We don't want to repeat bad params. + ok -> + case inviso_rt_lib:expand_regexp(Dir,M,Opts) of % Get a list of real modulnames. + Mods when is_list(Mods) -> + MoreReplies= + do_set_trace_patterns_2(lists:map(fun(Mod)-> + {Mod,F,Arity,MS,Opts} + end, + Mods), + Flags, + Replies), + do_set_trace_patterns_2(Rest,Flags,MoreReplies); + {error,Reason} -> + do_set_trace_patterns_2(Rest,Flags,[{error,Reason}|Replies]) + end; + error -> % Bad pattern parameters. + do_set_trace_patterns_2(Rest, + Flags, + [{error,{bad_trace_args,{M,F,Arity,MS}}}|Replies]) + end; +do_set_trace_patterns_2([Arg|Rest],Flags,Replies) -> + do_set_trace_patterns_2(Rest,Flags,[{error,{bad_trace_args,Arg}}|Replies]); +do_set_trace_patterns_2([],_Flags,Replies) -> + Replies. +%% ----------------------------------------------------------------------------- + +%% Help function which sets the trace flags for all processes specifed in Args. +%% Args shall be a list of {ProcessSpecification,ProcessTraceFlags}. +%% Returns {ok,Answers} where Answers is a list of integer and error descriptions. +%% Note that a process specification may be a particular pid or a {global,Name}. +%% In the case the process does not exist we will fake a zero instead of an +%% error. +do_set_trace_flags(Tracer,Args,How) -> + Fun=fun({Proc,Flags}) -> + case check_traceflag_pidspec(Proc) of + {ok,Proc2} -> % Reg-names converted. + case check_flags(Flags) of + Flags2 when is_list(Flags2) -> % No error! + case (catch + case How of + true -> + erlang:trace(Proc2, + true, + [{tracer,Tracer}|Flags2]); + false -> % No tracer of turning off. + erlang:trace(Proc2, + false, + Flags2) + end) of + N when is_integer(N) -> + N; + {'EXIT',Reason} -> + if + is_pid(Proc2) -> + 0; % Proc2 not alive or not at this node! + true -> % Otherwise, just error! + {error, + {bad_trace_args, + [Reason,Proc2,How,Flags2,Tracer]}} + end + end; + FlagError -> + FlagError + end; + false -> % Skip it. + 0; % Indicate that zero processes matched. + {error,Reason} -> % Bad process specification. + {error,{bad_process,[Reason,Proc]}} + end; + (Faulty) -> + {error,{bad_process,Faulty}} + end, + {ok,lists:map(Fun,Args)}. +%% ------------------------------------------------------------------------------ + +%% Function calling API:s in the trace information server. Note that we have +%% given the responsibility to form a correct functionsname and argument list +%% to the caller. +%% Returns whatever the called function returns. +do_meta_pattern(MPid,{FuncName,ArgList}) -> + case catch apply(inviso_rt_meta,FuncName,[MPid|ArgList]) of + {'EXIT',_Reason} -> + {error,{badarg,{FuncName,ArgList}}}; + Result -> + Result + end; +do_meta_pattern(_MPid,BadArgs) -> + {error,{bad_args,BadArgs}}. +%% ------------------------------------------------------------------------------ + +%% Function removing *all* patterns. Beaware that the one for local patterns +%% causes a walkthrough of all loaded modules. +do_clear_trace_patterns() -> + erlang:trace_pattern({'_','_','_'},false,[local]), %% inc. meta, call_count + erlang:trace_pattern({'_','_','_'},false,[global]). +%% ------------------------------------------------------------------------------ + +%% Function that takes TracerData and initializes the tracing. That can be +%% opening appropriate logfiles, starting meta-tracer. There must be one +%% clause here for every "type" of logging we want to be able to do. +%% Returns the Reply to be forwarded to the caller. +do_init_tracing(LoopData,TD,{HandlerFun,Data},TiTD) when is_function(HandlerFun) -> + {NewLoopData,Reply}= + case do_init_metatracing(TiTD,self()) of + {ok,MetaPid} -> + {LoopData#rt{handler={HandlerFun,Data}, + tracerdata=TD, + meta_tracer=MetaPid, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,ok}]}}; + false -> % No meta tracing requested. + {LoopData#rt{handler={HandlerFun,Data}, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok}]}}; + {error,Reason} -> % Problems starting meta tracing. + {LoopData#rt{handler={HandlerFun,Data}, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,{error,Reason}}]}} + end, + send_event(state_change,NewLoopData), % Send to subscribing processes. + {NewLoopData,Reply}; +do_init_tracing(LoopData,TD,{Type,Parameters},TiTD) when Type==ip;Type==file -> + case check_traceport_parameters(Type,Parameters) of + ok -> + case catch trace_port(Type,Parameters) of + Fun when is_function(Fun) -> + case catch Fun() of + Port when is_port(Port) -> % Ok, our trace-port is open. + {NewLoopData,Reply}= + case do_init_metatracing(TiTD,Port) of + {ok,MetaPid} -> + {LoopData#rt{tracer_port=Port, + tracerdata=TD, + meta_tracer=MetaPid, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,ok}]}}; + false -> % No meta tracing requested. + {LoopData#rt{tracer_port=Port, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok}]}}; + {error,Reason} -> % Problems starting meta tracing. + {LoopData#rt{tracer_port=Port, + tracerdata=TD, + state=tracing}, + {ok,[{trace_log,ok},{ti_log,{error,Reason}}]}} + end, + send_event(state_change,NewLoopData), + {NewLoopData,Reply}; + {'EXIT',Reason} -> + {LoopData,{error,{bad_port_fun,[Parameters,Reason]}}} + end; + {'EXIT',Reason} -> + {LoopData,{error,{bad_port_args,[Parameters,Reason]}}} + end; + {error,Reason} -> % Bad traceport parameters. + {LoopData,{error,Reason}} + end. + +%% Help function that starts the meta-tracing. Note that the runtime component +%% will becom linked to it. +%% Currently the meta tracer handles two types, 'file' and 'relay'. +%% Note that Tracer tells the meta tracer where regular trace messages shall be +%% sent. This is because the meta tracer is capable of appending a {tracer,Tracer} +%% action term to meta match specs. +do_init_metatracing(LogSpec={_Type,_Arg},Tracer) -> + case inviso_rt_meta:start(LogSpec,Tracer) of + {ok,MetaPid} -> + {ok,MetaPid}; + {error,Reason} -> + {error,Reason} + end; +do_init_metatracing({Type,Arg,{InitPublLDmfa,RemovePublLDmf,CleanPublLDmf}},Tracer)-> + case inviso_rt_meta:start({Type,Arg},Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf) of + {ok,MetaPid} -> + {ok,MetaPid}; + {error,Reason} -> + {error,Reason} + end; +do_init_metatracing(void,_) -> % Means no meta tracer. + false. +%% ----------------------------------------------------------------------------- + +%% Function that stops all tracing and closes all open files. This function +%% can't fail :-) It tries as hard as it can. +%% This function also kills the autostarter process if one exists. Otherwise it +%% will not be possible from a control component to end an ongoing autostarted +%% tracing. +%% Returns a new loopdata structure since stopping tracing involves updating it. +do_stop_tracing(LoopData) -> + do_stop_tracing_kill_autostarter(LoopData#rt.auto_starter), + do_clear_trace_flags(), % Do not generate any more traces. + NewLoopData1=do_stop_tracing_tracelog(LoopData), + NewLoopData2=do_stop_tracing_metatracing(NewLoopData1), + NewLoopData3=NewLoopData2#rt{state=idle,auto_starter=undefined}, + send_event(state_change,NewLoopData3), + NewLoopData3. + +do_stop_tracing_tracelog(LoopData=#rt{tracer_port=Port}) when is_port(Port) -> + trace_port_control(Port,flush), % Write buffered trace messages. + catch port_close(Port), + LoopData#rt{tracer_port=undefined}; +do_stop_tracing_tracelog(LoopData) -> + LoopData#rt{handler=undefined}. + +do_stop_tracing_metatracing(LoopData=#rt{meta_tracer=MPid}) when is_pid(MPid) -> + inviso_rt_meta:stop(MPid), + LoopData#rt{meta_tracer=undefined}; +do_stop_tracing_metatracing(LoopData) -> % No meta tracer running! + LoopData. + +%% Help function killing the autostarter, if one is active. +do_stop_tracing_kill_autostarter(P) when is_pid(P) -> + exit(P,stop_tracing); +do_stop_tracing_kill_autostarter(_) -> % No autostarter, do nothing. + true. +%% ----------------------------------------------------------------------------- + +%% Help function implementing suspending the runtime component. +%% Returns a new loopdata structure. +do_suspend(LD,Reason) -> + do_clear_trace_flags(), % If no process flags, no output! + do_suspend_metatracer(LD#rt.meta_tracer), + do_suspend_fetchers(LD#rt.fetchers), + do_stop_tracing_kill_autostarter(LD#rt.auto_starter), + NewLD=LD#rt{fetchers=[],status={suspended,Reason},auto_starter=undefined}, + send_event(state_change,NewLD), % Notify subscribers. + NewLD. + +do_suspend_metatracer(MetaTracer) when is_pid(MetaTracer) -> + inviso_rt_meta:suspend(MetaTracer); % This makes it suspended. +do_suspend_metatracer(_) -> + true. + +do_suspend_fetchers([FetcherPid|Rest]) -> + FetcherPid ! {suspend,self()}, % This makes it terminate. + do_suspend_fetchers(Rest); +do_suspend_fetchers([]) -> + true. +%% ------------------------------------------------------------------------------ + +%% Function that stops all tracing, removes all trace-patterns and removes all +%% logfiles. The idea is to return the runtime component to the 'new' state. +do_clear(LoopData,Opts) when is_list(Opts) -> + NewLoopData=do_stop_tracing(LoopData), % First stop tracing, if tracing. + case lists:member(keep_trace_patterns,Opts) of + false -> + do_clear_trace_patterns(); + _ -> + true + end, + case lists:member(keep_log_files,Opts) of + false -> + if + NewLoopData#rt.tracerdata/=undefined -> + do_delete_logs(NewLoopData#rt.tracerdata); + true -> % If no tracerdata, nothing to remove! + true % Do nothing then. + end; + _ -> + true + end, + NewLoopData#rt{state=new,tracerdata=undefined}; +do_clear(LoopData,_Opts) -> % Faulty Opts. + do_clear(LoopData,[]). % Then just ignore the options. +%% ----------------------------------------------------------------------------- + +%% Function which takes a tracerdata, either our own or a "suggested" +%% and tries to find the corresponding files. Note that the return value only +%% contains "types" of logs that the tracerdata is pointing out. Hence +%% is there no ti-log, no one will be mentioned in the return value. +do_list_logs(TracerData) -> % Handles both list and tuple. + case translate_td(TracerData) of + {ok,LogTD,TiTD} -> + {TraceDir,TraceLogs}=list_logs_tracelog(LogTD), + {TiDir,TiLogs}=list_logs_tilog(TiTD), + case {TraceLogs,TiLogs} of + {no_log,no_log} -> % Tracerdata not generating logs! + {ok,no_log}; + {_,no_log} -> % No ti logs. + {ok,[{trace_log,TraceDir,TraceLogs}]}; + {no_log,_} -> % Only ti-logs, unusual! + {ok,[{ti_log,TiDir,TiLogs}]}; + _ -> % Both trace and ti logs. + {ok,[{trace_log,TraceDir,TraceLogs},{ti_log,TiDir,TiLogs}]} + end; + {error,Reason} -> + {error,Reason} + end. +%% ----------------------------------------------------------------------------- + +%% Help function implementing fetching logfiles using distributed Erlang. +%% This function works for both situations, a list of specific files are +%% requested, or a tracerdata is specified. +%% Returns {Reply,NewLoopData}. +do_fetch_log(LD,CollectPid,What) -> + if + LD#rt.state/=tracing -> + case is_list_of_files_or_tracerdata(What) of + files -> + FetcherPid=do_fetch_log_listoffiles(CollectPid,What), + {{ok,FetcherPid},add_fetcher_ld(FetcherPid,LD)}; + tracerdata -> + case do_fetch_log_tracerdata(CollectPid,What) of + {Reply,FetcherPid} when is_pid(FetcherPid) -> + {Reply,add_fetcher_ld(FetcherPid,LD)}; + {Reply,_} -> % No fetch process was started. + {Reply,LD} + end; + false -> % It is an empty list! + {{complete,no_log},LD}; + error -> % Incorrect parameter. + {{error,badarg},LD} + end; + true -> % No transfere during tracing. + {{error,tracing},LD} + end. + +%% Function taking tracerdata to find out what files to send over to the RemotePid. +%% Note that we will not go back to the loop function from here but rather call +%% the fetch_loop instead, prepresenting the fetch-log state. Unless we encounter +%% a problem. +do_fetch_log_tracerdata(CollectPid,TracerData) -> + case do_list_logs(TracerData) of + {ok,no_log} -> + {{complete,no_log},void}; + {ok,Logs} -> % Ok, some trace_log and ti_log. + FetcherPid=do_fetch_log_listoffiles(CollectPid,Logs), + {{ok,FetcherPid},FetcherPid}; + {error,Reason} -> % Problem with tracerdata! + {{error,Reason},void} + end. + +do_fetch_log_listoffiles(CollectPid,FileSpec) -> + ExpandedFileSpec=do_fetch_log_expand_filespec(FileSpec), +%% !!! try out different ChunkSizes +% ChunkSize = 60, +% ChunkSize = 7*1024, + ChunkSize=1024, + _Fetcher=spawn_link(?MODULE, + fetch_init, + [self(),ExpandedFileSpec,CollectPid,ChunkSize]). + +%% Help function which expands the list of logs to have tags in front of every +%% file, as required by the fetch_loop. +do_fetch_log_expand_filespec(Logs) -> + TraceLogs= + case lists:keysearch(trace_log,1,Logs) of + {value,{_,Dir1,Logs1}} -> % There is a list of trace-logs. + lists:map(fun(File)->{trace_log,Dir1,File} end,Logs1); + false -> % No trace-logs! + [] + end, + TiLogs= + case lists:keysearch(ti_log,1,Logs) of + {value,{_,Dir2,Logs2}} -> + lists:map(fun(File)->{ti_log,Dir2,File} end,Logs2); + false -> + [] + end, + TiLogs++TraceLogs. + +%% ------------------------------------------------------------------------------ + +%% Function that removes all logfiles associated with a certain tracerdata. +do_delete_logs(TracerDataOrLogList) -> + case is_list_of_files_or_tracerdata(TracerDataOrLogList) of + tracerdata -> + case translate_td(TracerDataOrLogList) of + {ok,LogTD,TiTD} -> + case {list_logs_tracelog(LogTD),list_logs_tilog(TiTD)} of + {{_,no_log},{_,no_log}} -> % No logs nowhere! + {ok,no_log}; + {{LogDir,LogFiles},{_,no_log}} -> % No ti. + {ok,[{trace_log,delete_files(LogDir,LogFiles)}]}; + {{_,no_log},{TiDir,TiFiles}} -> + {ok,[{ti_log,delete_files(TiDir,TiFiles)}]}; + {{LogDir,LogFiles},{TiDir,TiFiles}} -> + {ok,[{trace_log,delete_files(LogDir,LogFiles)}, + {ti_log,delete_files(TiDir,TiFiles)}]} + end; + {error,Reason} -> + {error,Reason} + end; + files -> % It is [{trace_log,Dir,Files},.. + if + is_list(hd(TracerDataOrLogList)) -> % Just a list of files. + {ok,delete_files(".",TracerDataOrLogList)}; + is_tuple(hd(TracerDataOrLogList)) -> % A "modern" logspec. + case {lists:keysearch(trace_log,1,TracerDataOrLogList), + lists:keysearch(ti_log,1,TracerDataOrLogList)} of + {false,false} -> % Hmm, no logs specified! + {ok,[]}; % Easy response! + {{value,{_,LogDir,LogFiles}},false} -> + {ok,[{trace_log,delete_files(LogDir,LogFiles)}]}; + {false,{value,{_,TiDir,TiFiles}}} -> + {ok,[{ti_log,delete_files(TiDir,TiFiles)}]}; + {{value,{_,LogDir,LogFiles}},{value,{_,TiDir,TiFiles}}} -> + {ok,[{trace_log,delete_files(LogDir,LogFiles)}, + {ti_log,delete_files(TiDir,TiFiles)}]} + end + end; + false -> % Can't tell which! + {ok,[]}; + error -> + {error,{badarg,TracerDataOrLogList}} + end. +%% ----------------------------------------------------------------------------- + +%% Function handling the request when a control component wishing to take +%% control over this already existing control component. It does not matter +%% what state it is in. It can very well already be tracing. +%% Returns {Reply,NewLoopData}. +%% Where the Reply tells the control component wether it took control of it +%% or not. {node_info,node(),self(),Vsn,State,Status,{tag,Tag}} means that we +%% can be adopted (and more precisely considers ourselves being adopted now). +do_try_to_adopt(Tag,if_ref,LoopData=#rt{tag=Tag},_Ctrl) -> + {{error,{wrong_reference,LoopData#rt.tag}},LoopData}; +do_try_to_adopt(NewTag,_Condition,LoopData,CtrlPid) -> + case LoopData#rt.timer_ref of % Do we have a running-alone timer? + undefined -> % No we don't. + true; + TimerRef -> + timer:cancel(TimerRef) + end, + CtrlRef=erlang:monitor(process,CtrlPid), % Lets monitor our new "master"! + {DepVal,_}=LoopData#rt.dependency, + {node_info,Node,Pid,VSN,State,Status,Tag}=collect_node_info(LoopData), + NewLoopData= + LoopData#rt{dependency={DepVal,node(CtrlPid)}, + ctrl=CtrlPid, + ctrl_ref=CtrlRef, % Monitoring our new master. + tag=NewTag, % Use this tag from now on. + timer_ref=undefined}, + {{node_info,Node,Pid,VSN,State,Status,{tag,Tag}},NewLoopData}. +%% ----------------------------------------------------------------------------- + +%% Function changing parameters accoring to a new options list. Note that we +%% can not change control component if the one we have is still working. +%% We can however of course change how this runtime component will react to +%% a running alone scenario. +%% Returns 'stop' or NewLoopData. +do_change_options(Options,LoopData) -> + NewLoopData=read_option_list(Options,LoopData), + if + NewLoopData/=LoopData -> % Some options changed. + case do_change_options_ctrl(LoopData,NewLoopData) of + stop -> + stop; + {ok,NewLoopData2} -> + NewLoopData3=do_change_options_overload(LoopData,NewLoopData2), + NewLoopData3#rt{next_loadcheck=now()} % Force a load check next. + end; + true -> + LoopData + end. + +%% Help function which sets up the new dependencies. Note that we only do that +%% if do not have a working control component. +%% Returns {ok,NewLoopData} or 'stop'. +do_change_options_ctrl(OldLD,NewLD) -> + if + OldLD#rt.timer_ref/=undefined -> % No control and waiting to terminate. + timer:cancel(OldLD#rt.timer_ref), + do_down_message(NewLD#rt{timer_ref=undefined}); + OldLD#rt.ctrl==undefiend -> % No control component. + do_down_message(NewLD); + true -> % We have a working control component! + {ok,NewLD} + end. + +do_change_options_overload(OldLD,NewLD) -> + if + OldLD#rt.overload/=NewLD#rt.overload -> + terminate_overload(OldLD), + NewOverloadData=initialize_overload(NewLD), + NewLD#rt{overload_data=NewOverloadData}; + true -> % No changes done. + NewLD + end. +%% ----------------------------------------------------------------------------- + +%% Help function handling an incoming DOWN message from our control component. +%% If the runtime component is not allowed to run without a control component, it +%% simply terminates which closes the trace-port and process trace flags are +%% therefore automatically removed. +%% Returns 'stop' or a {ok,NewLoopData} structure. +do_down_message(LoopData) -> + case LoopData#rt.dependency of + {0,_} -> % Not allowed to run without controller. + stop; + {infinity,_} -> % Don't care. Just remove the controller. + {ok,LoopData#rt{ctrl=undefined,ctrl_ref=undefined}}; + {TimeOut,_} -> % Allowed to run TimeOut ms alone. + {ok,TimerRef}=timer:exit_after(TimeOut,self(),running_alone), + {ok,LoopData#rt{timer_ref=TimerRef,ctrl=undefined,ctrl_ref=undefined}} + end. +%% ----------------------------------------------------------------------------- + +%% Function handling incomming exit signals. We can expect exit signals from the +%% following: Our parent supervisor (runtime_tools_sup), a meta-tracer process, +%% a logfile fetcher process, or the auto_starter. +%% A trace-port may also generate an exit signal. +%% In addition it is possible that an overload mechanism generates exit-signals. +%% We can also get the running_alone exit signal from our self. This is the +%% situation if our control component has terminated and this runtime component +%% is not allowed to exist on its own for ever. +%% Also note that after we have stopped tracing, for any reason, it is not +%% impossible that we receive the EXIT signals from still working parts that +%% we are now shuting down. This is no problem, the code will mearly update +%% the loopdata structure once again. +%% Returns 'exit' indicating that the runtime component shall terminate now, +%% {NewLoopData,NewTimeOut} if the exit-signal resulted in an overload check, or +%% a new loopdata structure shall we ignore the exit, or it simply resulted in +%% a state-change. +act_on_exit(Parent,_Reason,#rt{parent=Parent}) -> + exit; +act_on_exit(_Pid,running_alone,_LoopData) -> + exit; +act_on_exit(MetaTracer,_Reason,LoopData=#rt{meta_tracer=MetaTracer}) -> + LoopData#rt{meta_tracer=undefined}; % It does not exit anylonger. +act_on_exit(Port,Reason,LoopData=#rt{tracer_port=Port}) -> + send_event({port_down,node(),Reason},LoopData), + _NewLoopData=do_stop_tracing(LoopData); +act_on_exit(AutoStarter,_Reason,LoopData=#rt{auto_starter=AutoStarter}) -> + LoopData#rt{auto_starter=undefined}; % The autostarter has terminated. +act_on_exit(Pid,Reason,LoopData) -> + case remove_fetcher_ld(Pid,LoopData) of + {true,NewLoopData} -> % Yes it really was a fetcher. + NewLoopData; + false -> % No it was not a fetcher. + act_on_exit_overload(Pid,Reason,LoopData) + end. + +%% Help function checking if this exit has anything to do with an overload +%% mechanism. Note that here we run the overload mechanism regardless of +%% if we are tracing or not. This because an exit signal from the overload +%% must most likely always be handled. +act_on_exit_overload(Pid,Reason,LoopData) -> + if + LoopData#rt.overload/=?NO_LOADCHECK -> + {_NewLD,_NewTimeOut}= + do_check_overload(LoopData, + {'EXIT',{Pid,Reason,LoopData#rt.overload_data}}); + true -> % Overload not in use. + LoopData + end. +%% ----------------------------------------------------------------------------- + + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%% ============================================================================== +%% Various help functions. +%% ============================================================================== + +%% Help function which calculates a new now-tuple by adding Interval milliseconds +%% to the first argument. Note that Interval may be 'infinity' too. +%% Returns a new now-tuple or "bigvalue" which is greater than any now-tuple. +add_to_now({MegSec,Sec,MicroSec},Interval) when is_integer(Interval) -> + NewSec=Sec+(Interval div 1000), + if + NewSec>=1000000 -> + {MegSec+1,NewSec-1000000,MicroSec}; + true -> + {MegSec,NewSec,MicroSec} + end; +add_to_now(_,infinity) -> + "bigvalue". +%% ------------------------------------------------------------------------------ + +%% Help function calculating the difference in milliseconds between its first +%% and second argument. This is useful when calculating an after timeout value +%% from current now() and next_loadcheck value. +calc_diff_to_now(T1={_,_,_},T2={_,_,_}) -> + TimeOut1=timer:now_diff(T2,T1), % The difference in microseconds. + if + TimeOut1<0 -> + 0; + true -> % Make milliseconds out of it. + TimeOut1 div 1000 + end; +calc_diff_to_now(_T1,_) -> % Next loadcheck is not activated. + infinity. % The the after timeout is infinity. +%% ------------------------------------------------------------------------------ + + +%% Help function returning information about this runtime component. +collect_node_info(#rt{vsn=VSN,state=State,status=Status,tag=Tag}) -> + {node_info,node(),self(),VSN,State,Status,Tag}. +%% ------------------------------------------------------------------------------ + +%% Help function sending information to the control component that state/status +%% change has occurred. Returns nothing significant. +send_event(state_change,LoopData=#rt{ctrl=CtrlPid}) when is_pid(CtrlPid) -> + Event={trace_event,{state_change,node(),{LoopData#rt.state,LoopData#rt.status}}}, + CtrlPid ! Event; +send_event(Event,#rt{ctrl=CtrlPid}) when is_pid(CtrlPid) -> + CtrlPid ! {event,Event}; +send_event(_,_) -> % We have no control to send to! + true. % Maybe tracing alone after autostart. +%% ------------------------------------------------------------------------------ + +%% Help function initializing the overload protection mechanism. This may be +%% necessary if it is a port program or similar. Returns {ok,Data} or 'void'. +%% The datastructure vill be given to LoadMF as argument whenever loadchecks +%% are done. +initialize_overload(#rt{overload={_MF,_Interval,{M,F,Args},_RemoveMFA}}) -> + case catch apply(M,F,Args) of + {ok,Data} -> + Data; + _ -> % 'EXIT' or other faulty returnvalue. + void + end; +initialize_overload(_) -> + void. +%% ------------------------------------------------------------------------------ + +%% Help function which terminates an overload protection mechanism. +%% Returns nothing significant. +terminate_overload(#rt{overload={_MF,_Interval,_InitMFA,{M,F,Args}}, + overload_data=Data}) -> + catch apply(M,F,[Data|Args]), % Interested in the side-effect. + true; +terminate_overload(_) -> + true. +%% ------------------------------------------------------------------------------ + + +%% Help function which checks that a process specified for trace flags is correct. +%% Either the built-in "aliases" for groups of processes, a pid, a locally registered +%% name. This function also works for globally registered names. It must then +%% first be established that the process is local for this node before setting any +%% process flags. +%% Returns {ok,PidSpec}, 'false' or {error,Reason}. +check_traceflag_pidspec(all) -> {ok,all}; +check_traceflag_pidspec(new) -> {ok,new}; +check_traceflag_pidspec(existing) -> {ok,existing}; +check_traceflag_pidspec(Name) when is_atom(Name) -> + check_traceflag_pidspec({local,Name}); +check_traceflag_pidspec({local,A}) when is_atom(A) -> + case whereis(A) of + undefined -> % Then it is considered faulty. + {error,{nonexistent_name,A}}; + Pid when is_pid(Pid) -> + {ok,Pid} + end; +check_traceflag_pidspec({global,Name}) when is_atom(Name) -> + case global:whereis_name(Name) of + undefined -> % Then the name does not exist at all. + {error,{nonexistent_name,{global,Name}}}; + Pid when is_pid(Pid) -> % Ok, but must check that it is here. + if + node()==node(Pid) -> + {ok,Pid}; + true -> % Pid is not at this node. + false % Not an error but cant be used. + end + end; +check_traceflag_pidspec(Pid) when is_pid(Pid) -> + {ok,Pid}; +check_traceflag_pidspec(Proc) -> + {error,{faulty,Proc}}. +%% ------------------------------------------------------------------------------ + +%% Help function removing all trace flags from all processes. Useful in connection +%% with suspend. Returns nothing significant. +do_clear_trace_flags() -> + erlang:trace(all, false, [all]). +%% ------------------------------------------------------------------------------ + +%% Help function which checks that only valid process trace flags are mentioned. +%% In order to create better fault reports. +%% Returns a list of the approved flags, or {error,Reason}. +check_flags(Flags) -> + check_flags_2(Flags,Flags). + +check_flags_2([send|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2(['receive'|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([call|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([return_to|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([procs|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([garbage_collection|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([running|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([set_on_spawn|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([set_on_first_spawn|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([set_on_link|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([timestamp|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([arity|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([silent|Rest],Flags) -> check_flags_2(Rest,Flags); +check_flags_2([],Flags) -> Flags; +check_flags_2([Faulty|_],_Flags) -> {error,{bad_flag,Faulty}}. +%% ------------------------------------------------------------------------------ + +%% Help function which checks parameters to erlang:trace_pattern. The purpose of +%% the function is to avoid to get multiple error return values in the return +%% list for a pattern used together with a regexp expanded module name. +check_pattern_parameters(Mod,Func,Arity,MS) -> + if + (Mod=='_') and (Func=='_') and (Arity=='_') and + (is_list(MS) or (MS==true) or (MS==false)) -> + ok; + (is_atom(Mod) and (Mod/='_')) and (Func=='_') and (Arity=='_') and + (is_list(MS) or (MS==true) or (MS==false)) -> + ok; + (is_atom(Mod) and (Mod/='_')) and + (is_atom(Func) and (Func/='_')) and + ((Arity=='_') or is_integer(Arity)) and + (is_list(MS) or (MS==true) or (MS==false)) -> + ok; + true -> + error + end. +%% ----------------------------------------------------------------------------- + +%% Help function finding out if Mod is loaded, and if not, if it can successfully +%% be loaded. The Opts list can prevent modules from being loaded. +%% Returns 'true' or 'false'. +load_module_on_option(Mod,Opts) when is_list(Opts) -> + case lists:member(no_loadcheck,Opts) of + true -> % Then just skip this, return true. + true; + false -> + case erlang:module_loaded(Mod) of + true -> + true; % It is loaded, do no more. + false -> + case lists:member(only_loaded,Opts) of + true -> % Then, make no attempts to load. + false; + false -> % Try to load! + case code:ensure_loaded(Mod) of + {module,_Mod} -> % Successfully loaded! + true; + {error,_Reason} -> + false + end + end + end + end; +load_module_on_option(Mod,_Opts) -> % Most likely Opts not a list! + load_module_on_option(Mod,[]). % Call without options. +%% ----------------------------------------------------------------------------- + +%% Help function taking a tuplelist of options turning them into a loopdata +%% structure. Returns the loopdata structure with the new values changed. +read_option_list([],LD) -> % Done, return loopdata. + LD; +read_option_list([{dependency,{Value,Node}}|Rest],LD) -> + read_option_list(Rest,LD#rt{dependency={Value,Node}}); +read_option_list([{dependency,Value}|Rest],LD) when is_integer(Value);Value==infinity -> + read_option_list(Rest,LD#rt{dependency={Value,node()}}); +read_option_list([overload|Rest],LD) -> % So that we can remove loadcheck. + read_option_list(Rest,LD#rt{overload=?NO_LOADCHECK}); +read_option_list([{overload,{MF,Interval}}|Rest],LD) + when is_integer(Interval);Interval==infinity -> + read_option_list(Rest,LD#rt{overload={MF,Interval,void,void}}); +read_option_list([{overload,{MF,Interval,InitMFA,RemoveMFA}}|Rest],LD) + when is_integer(Interval);Interval==infinity -> + read_option_list(Rest,LD#rt{overload={MF,Interval,InitMFA,RemoveMFA}}); +read_option_list([{overload,Interval}|Rest],LD) + when is_integer(Interval);Interval==infinity -> + read_option_list(Rest,LD#rt{overload={fun ?DEFAULT_OVERLOAD_FUNC/1, + Interval, + void, + void}}); +read_option_list([_|Rest],LD) -> % Unknown option. + read_option_list(Rest,LD). +%% ----------------------------------------------------------------------------- + +%% Help function which returns the version number for the runtime_tools +%% application. Since it is called from within the runtime_tools application +%% we can be "sure" that it really exists. +get_application_vsn() -> + {value,{_,_,VSN}}=lists:keysearch(runtime_tools,1,application:loaded_applications()), + VSN. +%% ----------------------------------------------------------------------------- + +%% Help function that examines an argument to determine if it is a list of files +%% or tracerdata. This since they are both complex structures, looking alike. +%% Returns 'tracerdata', 'files', 'false' or 'error'. Error is returned if it +%% can not be decided which it is. +is_list_of_files_or_tracerdata(What) -> + case inviso_rt_lib:is_tracerdata(What) of + true -> + tracerdata; + false -> + if + What==[] -> + false; + is_list(What),is_list(hd(What)) -> + files; + is_list(What) -> + case lists:keysearch(trace_log,1,What) of + {value,_} -> + files; + false -> + case lists:keysearch(ti_log,1,What) of + {value,_} -> + files; + false -> + error % Neither tracerdata nor list of files. + end + end; + true -> + error + end + end. +%% ------------------------------------------------------------------------------ + +%% Help function which removes all files in the ListOfFiles, assuming they +%% are located in Dir. +%% Returns a list of [{ok,FileName},...{error,Reason},...] +delete_files(Dir,ListOfFiles) -> + delete_files_2(Dir,ListOfFiles, []). + +delete_files_2(Dir,[File|Tail],Reply) when is_list(Dir),is_list(File) -> + case catch file:delete(filename:join(Dir,File)) of + ok -> + delete_files_2(Dir,Tail,[{ok,File}|Reply]); + {error,Posix} -> + delete_files_2(Dir,Tail,[{error,{Posix,File}}|Reply]); + {'EXIT',_Reason} -> % Probably not proper string. + delete_files_2(Dir,Tail,[{error,{badarg,[Dir,File]}}|Reply]) + end; +delete_files_2(Dir,[Faulty|Tail],Reply) -> + delete_files_2(Dir,Tail,[{error,{badarg,[Dir,Faulty]}}|Reply]); +delete_files_2(_,[],Reply) -> + Reply. +%% ----------------------------------------------------------------------------- + +%% Help function which lists all trace logs belonging to this tracerdata. +%% Note that this function operates on internal LogTD structures. +list_logs_tracelog({file,FileName}) when is_list(FileName) -> + case file:read_file_info(FileName) of + {ok,_} -> % The file exists. + {filename:dirname(FileName),[filename:basename(FileName)]}; + _ -> % The file does not exist + {filename:dirname(FileName),[]} + end; +list_logs_tracelog({file,Wrap}) when is_tuple(Wrap),element(2,Wrap)==wrap -> + case {element(1,Wrap),element(3,Wrap)} of + {FileName,Tail} when is_list(FileName),is_list(Tail) -> + case catch {filename:dirname(FileName),list_wrapset(FileName,Tail)} of + {'EXIT',_Reason} -> % Garbage in either lists. + {"",no_log}; % Interpret as no log for tracerdata. + Tuple -> + Tuple + end; + _ -> + {"",no_log} + end; +list_logs_tracelog(void) -> % Trace log not used. + {"",no_log}; +list_logs_tracelog(_) -> % Some fun or similar. + {"",no_log}. % Then there are no files to report. +%% ----------------------------------------------------------------------------- + +%% Help function which lists all ti-files belonging to this tracerdata. +%% Note that this function operates on the internal TiTD structure. +list_logs_tilog(TiTD) + when tuple_size(TiTD)>=2,element(1,TiTD)==file,is_list(element(2,TiTD)) -> + FileName=element(2,TiTD), + case file:read_file_info(FileName) of + {ok,_} -> % Yes the file exists. + {filename:dirname(FileName),[filename:basename(FileName)]}; + _ -> + {filename:dirname(FileName),[]} + end; +list_logs_tilog(void) -> % Internal representation for + {"",no_log}; % ti-file not in use. +list_logs_tilog(_) -> + {"",no_log}. +%% ----------------------------------------------------------------------------- + +%% Help function which lists all files belonging to the wrap-set specified by +%% Prefix and Suffix. Note that there can be a directory in Prefix as well. +%% Will fail if either of Prefix or Suffix are not proper strings. +%% Returns a list of files, without dirname. +list_wrapset(Prefix,Suffix) -> + Name=filename:basename(Prefix), + Dirname=filename:dirname(Prefix), + case file:list_dir(Dirname) of + {ok,Files} -> + RegExp="^"++list_wrapset_escapes(Name)++"[0-9]+"++ + list_wrapset_escapes(Suffix)++"$", + list_wrapset_2(Files,RegExp); + {error,_Reason} -> % Translate this to no files! + [] + end. + +list_wrapset_2([File|Rest],RegExp) -> + Length=length(File), + case regexp:first_match(File,RegExp) of + {match,1,Length} -> % This is a member of the set. + [File|list_wrapset_2(Rest,RegExp)]; + _ -> + list_wrapset_2(Rest,RegExp) + end; +list_wrapset_2([],_) -> + []. + +%% Help function which inserts escape characters infront of characters which +%% will otherwise be missinterpreted by the regexp function as meta rather than +%% just the character itself. +list_wrapset_escapes([$.|Rest]) -> + [$\\,$.|list_wrapset_escapes(Rest)]; +list_wrapset_escapes([Char|Rest]) -> + [Char|list_wrapset_escapes(Rest)]; +list_wrapset_escapes([]) -> + []. +%% ----------------------------------------------------------------------------- + + + + + + +%% ============================================================================== +%% Handler functions for implementing simple trace-message handlers. +%% ============================================================================== +%% +%% A handler must be a function taking two arguments. The first is the trace- +%% message. The second is datastructure used by the handler. The handler shall +%% returns (possibly) new datastructure. + +%% ------------------------------------------------------------------------------ +%% Function implementing a relayer. This function is used to creat a fun handler +%% if the relay option is used in tracer-data. +%% ------------------------------------------------------------------------------ +relay_handler(Msg,Tracer) -> + Tracer ! Msg, + Tracer. + +%% ------------------------------------------------------------------------------ +%% Function implementing a default terminal io handler. +%% ------------------------------------------------------------------------------ + +dhandler(end_of_trace, Out) -> + Out; +dhandler(Trace, Out) when element(1, Trace) == trace, + tuple_size(Trace) >= 3 -> + dhandler1(Trace, tuple_size(Trace), Out); +dhandler(Trace, Out) when element(1, Trace) == trace_ts, + tuple_size(Trace) >= 4 -> + dhandler1(Trace, tuple_size(Trace)-1, Out); +dhandler(Trace, Out) when element(1, Trace) == drop, + tuple_size(Trace) == 2 -> + io:format(Out, "*** Dropped ~p messages.~n", [element(2,Trace)]), + Out; +dhandler(Trace, Out) when element(1, Trace) == seq_trace, + tuple_size(Trace) >= 3 -> + SeqTraceInfo = case Trace of + {seq_trace, Lbl, STI, TS} -> + io:format(Out, "SeqTrace ~p [~p]: ", + [TS, Lbl]), + STI; + {seq_trace, Lbl, STI} -> + io:format(Out, "SeqTrace [~p]: ", + [Lbl]), + STI + end, + case SeqTraceInfo of + {send, Ser, Fr, To, Mes} -> + io:format(Out, "(~p) ~p ! ~p [Serial: ~p]~n", + [Fr, To, Mes, Ser]); + {'receive', Ser, Fr, To, Mes} -> + io:format(Out, "(~p) << ~p [Serial: ~p, From: ~p]~n", + [To, Mes, Ser, Fr]); + {print, Ser, Fr, _, Info} -> + io:format(Out, "-> ~p [Serial: ~p, From: ~p]~n", + [Info, Ser, Fr]); + Else -> + io:format(Out, "~p~n", [Else]) + end, + Out; +dhandler(_Trace, Out) -> + Out. + +dhandler1(Trace, Size, Out) -> +%%%! Self = self(), + From = element(2, Trace), + case element(3, Trace) of + 'receive' -> + case element(4, Trace) of + {dbg,ok} -> ok; + Message -> io:format(Out, "(~p) << ~p~n", [From,Message]) + end; + 'send' -> + Message = element(4, Trace), + case element(5, Trace) of +%%%! This causes messages to disappear when used by ttb (observer). Tests +%%%! so far show that there is no difference in results with dbg even if I +%%%! comment it out, so I hope this is only some old code which isn't +%%%! needed anymore... /siri +%%%! Self -> ok; + To -> io:format(Out, "(~p) ~p ! ~p~n", [From,To,Message]) + end; + call -> + case element(4, Trace) of + MFA when Size == 5 -> + Message = element(5, Trace), + io:format(Out, "(~p) call ~s (~p)~n", + [From,ffunc(MFA),Message]); + MFA -> + io:format(Out, "(~p) call ~s~n", [From,ffunc(MFA)]) + end; + return -> %% To be deleted... + case element(4, Trace) of + MFA when Size == 5 -> + Ret = element(5, Trace), + io:format(Out, "(~p) old_ret ~s -> ~p~n", + [From,ffunc(MFA),Ret]); + MFA -> + io:format(Out, "(~p) old_ret ~s~n", [From,ffunc(MFA)]) + end; + return_from -> + MFA = element(4, Trace), + Ret = element(5, Trace), + io:format(Out, "(~p) returned from ~s -> ~p~n", + [From,ffunc(MFA),Ret]); + return_to -> + MFA = element(4, Trace), + io:format(Out, "(~p) returning to ~s~n", [From,ffunc(MFA)]); + spawn when Size == 5 -> + Pid = element(4, Trace), + MFA = element(5, Trace), + io:format(Out, "(~p) spawn ~p as ~s~n", [From,Pid,ffunc(MFA)]); + Op -> + io:format(Out, "(~p) ~p ~s~n", [From,Op,ftup(Trace,4,Size)]) + end, + Out. + + +%%% These f* functions returns non-flat strings + +%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)" +%% {M,F,A} -> "M:F/A" +ffunc({M,F,Argl}) when is_list(Argl) -> + io_lib:format("~p:~p(~s)", [M, F, fargs(Argl)]); +ffunc({M,F,Arity}) -> + io_lib:format("~p:~p/~p", [M,F,Arity]); +ffunc(X) -> io_lib:format("~p", [X]). + +%% Integer -> "Integer" +%% [A1, A2, ..., AN] -> "A1, A2, ..., AN" +fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity); +fargs([]) -> []; +fargs([A]) -> io_lib:format("~p", [A]); %% last arg +fargs([A|Args]) -> [io_lib:format("~p,", [A]) | fargs(Args)]; +fargs(A) -> io_lib:format("~p", [A]). % last or only arg + +%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size" +ftup(Trace, Index, Index) -> + io_lib:format("~p", [element(Index, Trace)]); +ftup(Trace, Index, Size) -> + [io_lib:format("~p ", [element(Index, Trace)]) + | ftup(Trace, Index+1, Size)]. +%% ------------------------------------------------------------------------------ + +%% ============================================================================== +%% Functions handling the trace-port. Copied from dbg.erl +%% ============================================================================== + +trace_port_control(Port, flush) -> + case trace_port_control(Port, $f, "") of + {ok, [0]} -> ok; + {ok, _} -> {error, not_supported_by_trace_driver}; + Other -> Other + end. + +trace_port_control(Port, Command, Arg) when is_port(Port)-> + case catch port_control(Port, Command, Arg) of + {'EXIT', _} -> {error, {no_trace_driver, node()}}; + Result -> Result + end. + + +trace_port(file, {Filename, wrap, Tail}) -> + trace_port(file, {Filename, wrap, Tail, 128*1024}); +trace_port(file, {Filename, wrap, Tail, WrapSize}) -> + trace_port(file, {Filename, wrap, Tail, WrapSize, 8}); +trace_port(file, {Filename, wrap, Tail, WrapSize, WrapCnt}) + when is_list(Tail), + is_integer(WrapSize), WrapSize >= 0, WrapSize < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, WrapSize, WrapCnt, 0}); +trace_port(file, {Filename, wrap, Tail, {time, WrapTime}, WrapCnt}) + when is_list(Tail), + is_integer(WrapTime), WrapTime >= 1, WrapTime < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + trace_port1(file, Filename, {wrap, Tail, 0, WrapCnt, WrapTime}); +trace_port(file, Filename) when is_list(Filename) -> + trace_port1(file, Filename, nowrap); + +trace_port(ip, Portno) when is_integer(Portno) -> + trace_port(ip,{Portno,50}); + +trace_port(ip, {Portno, Qsiz}) when is_integer(Portno), is_integer(Qsiz) -> + fun() -> + Driver = "trace_ip_drv", + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + L = lists:flatten( + io_lib:format("~s ~p ~p 2", + [Driver, Portno, Qsiz])), + open_port({spawn, L}, [eof]) + end. + +trace_port1(file, Filename, Options) -> + Driver = "trace_file_drv", + fun() -> + Name = filename:absname(Filename), + %% Absname is needed since the driver uses + %% the supplied name without further investigations, + %% and if the name is relative the resulting path + %% might be too long which can cause a bus error + %% on vxworks instead of a nice error code return. + %% Also, the absname must be found inside the fun, + %% in case the actual node where the port shall be + %% started is on another node (or even another host) + {Wrap, Tail} = + case Options of + {wrap, T, WrapSize, WrapCnt, WrapTime} -> + {lists:flatten( + io_lib:format("w ~p ~p ~p ~p ", + [WrapSize, WrapCnt, WrapTime, + length(Name)])), + T}; + nowrap -> + {"", ""} + end, + Command = Driver ++ " " ++ Wrap ++ "n " ++ Name ++ Tail, + Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"), + case catch erl_ddll:load_driver(Dir1, Driver) of + ok -> + ok; + _ -> + Dir2 = filename:join( + Dir1, + erlang:system_info(system_architecture)), + catch erl_ddll:load_driver(Dir2, Driver) + end, + if element(1, Options) == wrap -> + %% Delete all files from any previous wrap log + Files = wrap_postsort(wrap_presort(Name, Tail)), + lists:foreach( + fun(N) -> file:delete(N) end, + Files); + true -> ok + end, + open_port({spawn, Command}, [eof]) + end. + +%% Find all possible wrap log files. +%% Returns: a list of sort converted filenames. +%% +%% The sort conversion is done by extracting the wrap sequence counter +%% from the filename, and calling wrap_encode/2. +wrap_presort(Filename, Tail) -> + Name = filename:basename(Filename), + Dirname = filename:dirname(Filename), + case file:list_dir(Dirname) of + {ok, Files} -> + lists:zf( + fun(N) -> + case match_front(N, Name) of + false -> + false; + X -> + case match_rear(X, Tail) of + false -> + false; + C -> % Counter + case match_0_9(C) of + true -> + {true, +% filename:join(Dirname, N)} + wrap_encode( + filename:join(Dirname, N), + C)}; + false -> + false + end + end + end + end, + Files); + _ -> + [] + end. + +%% Extract the filenames from a list of sort converted ones. +wrap_postsort(Files) -> + lists:map(fun wrap_name/1, Files). + +wrap_encode(N, C) -> + {list_to_integer(C), N}. + +wrap_name({_C, N}) -> + N. + +%% Returns what is left of ListA when removing all matching +%% elements from ListB, or false if some element did not match, +%% or if ListA runs out of elements before ListB. +match_front(ListA, []) when is_list(ListA) -> + ListA; +match_front([], ListB) when is_list(ListB) -> + false; +match_front([Hd|TlA], [Hd|TlB]) -> + match_front(TlA,TlB); +match_front([_HdA|_], [_HdB|_]) -> + false. + +%% Reversed version of match_front/2 +match_rear(ListA, ListB) when is_list(ListA), is_list(ListB) -> + case match_front(lists:reverse(ListA), lists:reverse(ListB)) of + false -> + false; + List -> + lists:reverse(List) + end. + +%% Returns true if the non-empty list arguments contains all +%% characters $0 .. $9. +match_0_9([]) -> + false; +match_0_9([H]) when is_integer(H), $0 =< H, H =< $9 -> + true; +match_0_9([H|T]) when is_integer(H), $0 =< H, H =< $9 -> + match_0_9(T); +match_0_9(L) when is_list(L) -> + false. +%% ----------------------------------------------------------------------------- + + +%% ----------------------------------------------------------------------------- +%% Functions working on the tracerdata structure. +%% ----------------------------------------------------------------------------- + +%% Tracerdata is the structure which specifies to where tracing is logged at this +%% runtime component. It may now (and in the future specify) several things. +%% Currently it can consist of: +%% LogTD: specifying how trace-log data shall be handled. +%% TiTD : trace information, specifying how trace information shall be handled. +%% +%% Tracerdata may also contain quick or standard forms of LogTD and/or TiTD. +%% For instance if a standard handler-fun shall be used. The handler fun is not +%% part of the tracerdata but rather specified by a constant. + + +%% Help function that translates an input-tracerdata to useful internal formats. +%% This since the tracerdata may consist of specifications which shall be +%% translated into funs or similar. +%% Returns {ok,LogTD,TiTD} or {error,Reason}. +%% Note that TiTD may be 'void' since TiTD is not mandatory. +translate_td(TracerData) when is_list(TracerData) -> % Both log and ti. + case translate_td_logtd(get_trace_log_tracerdata(TracerData)) of + {ok,LogTD} -> + case translate_td_titd(get_ti_log_tracerdata(TracerData)) of + {ok,TiTD} -> + {ok,LogTD,TiTD}; + {error,Reason} -> + {error,Reason} + end; + {error,Reason} -> + {error,Reason} + end; +translate_td(TracerData) -> % The it is just LogTD!? + case translate_td_logtd(TracerData) of + {ok,LogTD} -> + {ok,LogTD,void}; + {error,Reason} -> + {error,Reason} + end. +%% ----------------------------------------------------------------------------- + +%% Help function translating trace-log tracerdata. +translate_td_logtd(collector) -> % This rt will act as receiver. + {ok,{fun dhandler/2,user}}; % Simple terminal io. +translate_td_logtd({relayer,Tracer}) when is_pid(Tracer) -> + {ok,{fun relay_handler/2,Tracer}}; % Relay trace-msg to Tracer-pid. +translate_td_logtd({HandlerFun,Data}) when is_function(HandlerFun) -> + {ok,{HandlerFun,Data}}; % Own invented fun. +translate_td_logtd({Type,Parameters}) when Type==ip;Type==file -> + {ok,{Type,Parameters}}; % Built in trace-port +translate_td_logtd(false) -> % Unusual but no trace log. + {ok,void}; +translate_td_logtd(Arg) -> + {error,{bad_log_td,Arg}}. +%% ----------------------------------------------------------------------------- + +%% Help function translating ti-log tracerdata. +translate_td_titd(TiTD={file,FileName}) when is_list(FileName) -> + {ok,TiTD}; +translate_td_titd({file,FileName, + {InitPublLDmfa={M1,F1,L1}, + RemovePublLDmf={M2,F2}, + CleanPublLDmf={M3,F3}}}) + when is_list(FileName),is_atom(M1),is_atom(F1),is_atom(M2),is_atom(F2),is_list(L1),is_atom(M3),is_atom(F3) -> + {ok,{file,FileName,{InitPublLDmfa,RemovePublLDmf,CleanPublLDmf}}}; +translate_td_titd({file,FileName, + {InitPublLDmfa={M1,F1,L1}, + void, + CleanPublLDmf={M3,F3}}}) + when is_list(FileName),is_atom(M1),is_atom(F1),is_list(L1),is_atom(M3),is_atom(F3) -> + {ok,{file,FileName,{InitPublLDmfa,void,CleanPublLDmf}}}; +translate_td_titd(false) -> % Means no ti-tracerdata. + {ok,void}; +translate_td_titd(TiTD) -> + {error,{bad_ti_td,TiTD}}. +%% ----------------------------------------------------------------------------- + +%% This function retrieves the trace-log part of a TracerData list structure. +%% Returns TraceLogTD or 'false'. +get_trace_log_tracerdata(TracerData) -> + case lists:keysearch(trace,1,TracerData) of + {value,{_,LogTD}} -> + LogTD; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +%% This function retrieves the ti-log part of a TracerData list structure. +%% Returns TiLogTD or 'false'. +get_ti_log_tracerdata(TracerData) -> + case lists:keysearch(ti,1,TracerData) of + {value,{_,TiTD}} -> + TiTD; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +%% Help function which checks that parameters to the built in trace-port are +%% sane. +check_traceport_parameters(Type,Args) -> + case {Type,Args} of + {file,{FileName,wrap,Tail}} when is_list(FileName),is_list(Tail) -> + ok; + {file,{FileName,wrap,Tail,WrapSize}} + when is_list(FileName), + is_list(Tail), + is_integer(WrapSize),WrapSize>=0,WrapSize< (1 bsl 32) -> + ok; + {file,{FileName,wrap,Tail,WrapSize,WrapCnt}} + when is_list(FileName),is_list(Tail), + is_integer(WrapSize), WrapSize >= 0, WrapSize < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + ok; + {file,{FileName,wrap,Tail,{time,WrapTime},WrapCnt}} + when is_list(FileName),is_list(Tail), + is_integer(WrapTime), WrapTime >= 1, WrapTime < (1 bsl 32), + is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) -> + ok; + {file,FileName} when is_list(FileName) -> + ok; + {ip,Portno} when is_integer(Portno),Portno=<16#FFFF -> + ok; + {ip,{Portno,Qsiz}} when is_integer(Portno),Portno=<16#FFFF,is_integer(Qsiz) -> + ok; + _ -> + {error,{trace_port_args,[Type,Args]}} + end. +%% ----------------------------------------------------------------------------- + + +%% ----------------------------------------------------------------------------- +%% Default overload functionality. +%% ----------------------------------------------------------------------------- + +%% A default overload protection function. An overload function must take +%% one argument and return 'ok' or {suspend,SuspendReason}. +default_overload_func(_) -> + case process_info(self(),message_queue_len) of + {message_queue_len,N} when N > 1000 -> + {suspend,rt_max_queue_len}; + _ -> + ok + end. +%% ----------------------------------------------------------------------------- + +%% ============================================================================= +%% Functions working on the internal loopdata structure. +%% ============================================================================= + +%% Help function simply adding Fetcher as a fetcher process to the loopdata. +%% Returns a new loopdata structure. +add_fetcher_ld(Fetcher,LD) -> + LD#rt{fetchers=[Fetcher|LD#rt.fetchers]}. +%% ----------------------------------------------------------------------------- + +%% Help function investigating if the first argument is a known fetcher process +%% or not. If it is, it also removed it from the fetchers list in the loopdata +%% structure. +%% Returns {true,NewLoopData} or 'false'. +remove_fetcher_ld(Fetcher,LD) -> + NewFetchers=lists:delete(Fetcher,LD#rt.fetchers), + if + NewFetchers/=LD#rt.fetchers -> + {true,LD#rt{fetchers=NewFetchers}}; + true -> % No it was not a fetcher process. + false + end. +%% ----------------------------------------------------------------------------- + +%%% end of file + diff --git a/lib/runtime_tools/src/inviso_rt_lib.erl b/lib/runtime_tools/src/inviso_rt_lib.erl new file mode 100644 index 0000000000..2c6964e53e --- /dev/null +++ b/lib/runtime_tools/src/inviso_rt_lib.erl @@ -0,0 +1,474 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2009. 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% +%% +%% ------------------------------------------------------------------------------ +%% File : inviso_rt_lib.erl +%% Author : Lennart �hman <[email protected]> +%% Description : +%% +%% Created : 27 Sep 2005 by Lennart �hman <[email protected]> +%% ------------------------------------------------------------------------------ +-module(inviso_rt_lib). + +-export([expand_regexp/2,expand_regexp/3,expand_regexp/4]). +-export([is_tracerdata/1]). +-export([transform/2]). + +-export([rpc/4,rpc/5,match_modules/2,match_modules/3]). +-export([debug/3]). + +%% ------------------------------------------------------------------------------ + +%% ============================================================================== +%% Exported API functions. +%% ============================================================================== + +%% ------------------------------------------------------------------------------ +%% expand_regexp(Nodes,RegExpDir,RegExpMod,Opts) = [{Node,Answer},...] | {error,Reason} +%% expand_regexp(Nodes,RegExpMod,Opts) = [{Node,Answer},...] | {error,Reason} +%% expand_regexp(RegExpDir,RegExpMod,Opts) = ListOfModules | {error,Reason} +%% expand_regexp(RegExpMod,Opts) = ListOfModules | {error,Reason} +%% Nodes=List of all nodes (atoms) where to expand. +%% RegExpDir=Reg.exp (string) specifying directories. +%% RegExpMod=Reg.exp (string) specifying module names. +%% Node=node name (atom). +%% Opts=[Opt,...] +%% Opt=only_loaded +%% Answer=List of modules (atoms) | 'badrpc' +%% +%% Expands, concurrently, the regular expression on Nodes and returns a list +%% of what modules it expanded to on the different nodes. Note that it may +%% differ between Erlang nodes depending on whether the modules are the same +%% or not. Also note that all modules becomes loaded as a result. +%% RegExpDir can further limit the modules. It introduces the requirement that +%% a module must be loaded from a directory with a path satisfying the RegExpDir. +%% All regular expression are according to the standard lib regexp module. +expand_regexp(RegExpMod,Opts) when is_list(RegExpMod),is_list(Opts) -> + match_modules(RegExpMod,Opts); +expand_regexp(RegExpMod,Opts) -> + {error,{badarg,[RegExpMod,Opts]}}. +expand_regexp(NodesOrRegExpDir,RegExpMod,Opts) + when is_list(NodesOrRegExpDir),is_list(RegExpMod),is_list(Opts) -> + case is_list_of_atoms(NodesOrRegExpDir) of + true -> % Interpret as list of nodes. + lists:foreach(fun(N)->spawn(?MODULE,rpc,[self(),N,RegExpMod,Opts]) end, + NodesOrRegExpDir), + expand_regexp_answers(NodesOrRegExpDir,[]); + false -> % Interpret as a string. + match_modules(NodesOrRegExpDir,RegExpMod,Opts) + end; +expand_regexp(NodesOrRegExpDir,RegExpMod,Opts) -> + {error,{badarg,[NodesOrRegExpDir,RegExpMod,Opts]}}. +expand_regexp(Nodes,RegExpDir,RegExpMod,Opts) + when is_list(Nodes),is_list(RegExpDir),is_list(RegExpMod),is_list(Opts) -> + lists:foreach(fun(N)-> + spawn(?MODULE,rpc,[self(),N,RegExpDir,RegExpMod,Opts]) + end, + Nodes), + expand_regexp_answers(Nodes,[]); +expand_regexp(Nodes,RegExpDir,RegExpMod,Opts) -> + {error,{badarg,[Nodes,RegExpDir,RegExpMod,Opts]}}. + +expand_regexp_answers([],Answers) -> Answers; % List of [{Node,Answer},...]. +expand_regexp_answers(Nodes,Answers) -> + receive + {?MODULE,Node,Answer} -> + expand_regexp_answers(lists:delete(Node,Nodes),[{Node,Answer}|Answers]) + end. +%% ------------------------------------------------------------------------------ + +%% is_tracerdata(TracerData)=true|false +%% Answers the question if TracerData is proper tracerdata. Note that true can be +%% returned if it resembles tracerdata very closely. +is_tracerdata({Fun,_Data}) when is_function(Fun) -> true; +is_tracerdata({relayer,To}) when is_pid(To);is_atom(To) -> true; +is_tracerdata(collector) -> true; +is_tracerdata({file,Param}) when is_tuple(Param);is_list(Param) -> true; +is_tracerdata({ip,_Param}) -> true; +is_tracerdata([{trace,LogTD}|Rest]) -> + case is_tracerdata(LogTD) of + true -> + is_tracerdata(Rest); + false -> + false + end; +is_tracerdata([{ti,TiData}|Rest]) -> + case is_tidata(TiData) of + true -> + is_tracerdata(Rest); + false -> + false + end; +is_tracerdata([]) -> + true; +is_tracerdata(_) -> + false. + +is_tidata({file,FileName}) when is_list(FileName) -> true; +is_tidata({file,FileName,{M,F,Args}}) when is_list(FileName),is_atom(M),is_atom(F),is_list(Args) -> + true; +is_tidata(_) -> false. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% Help functions. +%% ============================================================================== + +%% Help function intended to be run in its own process. Will report with +%% a message when done. +%% This function will be spawned on. +rpc(Parent,Node,RegExpMod,Opts) -> + case rpc:call(Node,?MODULE,match_modules,[RegExpMod,Opts]) of + {badrpc,_Reason} -> % The node is probably not healthy. + Parent ! {?MODULE,Node,badrpc}; + Modules -> + Parent ! {?MODULE,Node,Modules} + end. + +rpc(Parent,Node,RegExpDir,RegExpMod,Opts) -> + case rpc:call(Node,?MODULE,match_modules,[RegExpDir,RegExpMod,Opts]) of + {badrpc,_Reason} -> % The node is probably not healthy. + Parent ! {?MODULE,Node,badrpc}; + Modules -> + Parent ! {?MODULE,Node,Modules} + end. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================== +%% Exported function which actually shall be in code.erl. +%% ============================================================================== + +%% match_modules(RegExpMod,Actions) = [Module,...] | {error,Reason} +%% match_modules(RegExpDir,RegExpMod,Actions)=[Module,...] | {error,Reason} +%% RegExpMod=Erlang regular expression describing module names (string). +%% RegExpDir=Erlang regular expression describing directory paths(string) | +%% void +%% Actions=List of;'only_loaded'. +%% +%% Function which matches a regular expresion against module names. The function +%% can also match the directory from where the module is loaded or will be loaded +%% against a regular expresion for directory paths. +%% The function uses the same strategy as code-loading if the same module is +%% discovered in several places. +%% (1) An already loaded module shadows all other occurancies. +%% (2) .beams found in by a path shadows .beams found by paths later in the +%% code paths. +%% +%% Description of actions: +%% only_loaded: Only consider modules which are loaded. +match_modules(RegExpMod,Actions) -> + match_modules(void,RegExpMod,Actions). +match_modules(RegExpDir,RegExpMod,Actions) -> + AllLoaded=code:all_loaded(), + Mods1=handle_expand_regexp_2(AllLoaded,RegExpDir,RegExpMod,[]), + case lists:member(only_loaded,Actions) of % Shall we do not loaded too? + false -> % Ok, search all paths too then. + Paths=code:get_path(), + handle_expand_regexp_3(Paths,RegExpDir,RegExpMod,AllLoaded,Mods1); + true -> % Only loaded modules then. + Mods1 + end. + + +%% Help function which traverses all loaded modules and determines +%% which shall be returned. First we check that the module satisfies the +%% module-regexp. Then we, if a dir reg-exp is given, checks that the +%% module is loaded from an approved path. Note that if it can not be +%% determined from where it was loaded (like preloaded or cover-compiled +%% etc), but dir reg-exps are used. That module will be excluded. +%% Returns a list of modules. +handle_expand_regexp_2([{Mod,Path}|Rest],RegExpDir,RegExpMod,Result) -> + ModStr=atom_to_list(Mod), + ModLen=length(ModStr), + case regexp:first_match(ModStr,RegExpMod) of + {match,1,ModLen} -> % Ok, The regexp matches the module. + if + is_list(RegExpDir),is_atom(Path) -> % Preloaded or covercompiled... + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,Result); + is_list(RegExpDir),is_list(Path) -> % Dir reg-exp is used! + PathOnly=filename:dirname(Path), % Must remove beam-file name. + case regexp:first_match(PathOnly,RegExpDir) of + {match,_,_} -> % Did find a match, that is enough! + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,[Mod|Result]); + _ -> % Either error or nomatch. + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,Result) + end; + true -> % Otherwise already done! + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,[Mod|Result]) + end; + _ -> % Then Mod is not part of the set. + handle_expand_regexp_2(Rest,RegExpDir,RegExpMod,Result) + end; +handle_expand_regexp_2([],_,_,Result) -> Result. + +%% Help function which traverses all paths and looks for modules satisfying +%% the module reg.exp. +%% Returns a list of unique module names. +handle_expand_regexp_3([Path|Rest],RegExpDir,RegExpMod,AllLoaded,Result) -> + if + is_list(RegExpDir) -> % We must consider the directory name. + AbsPath= + case filename:pathtype(Path) of + absolute -> % Is already abs. + Path; + relative -> % Then it must be made absolute. + filename:absname(Path); + volumerelative -> % Only on Windows!? + filename:absname(Path) + end, + case regexp:first_match(AbsPath,RegExpDir) of + {match,_,_} -> % Ok, the directory is allowed. + NewResult=handle_expand_regexp_3_1(Path,RegExpMod,AllLoaded,Result), + handle_expand_regexp_3(Rest,RegExpDir,RegExpMod,AllLoaded,NewResult); + _ -> % This directory does not qualify. + handle_expand_regexp_3(Rest,RegExpDir,RegExpMod,AllLoaded,Result) + end; + true -> % RegExpDir is not used! + NewResult=handle_expand_regexp_3_1(Path,RegExpMod,AllLoaded,Result), + handle_expand_regexp_3(Rest,RegExpDir,RegExpMod,AllLoaded,NewResult) + end; +handle_expand_regexp_3([],_,_,_,Result) -> Result. + +handle_expand_regexp_3_1(Path,RegExpMod,AllLoaded,Result) -> + case file:list_dir(Path) of + {ok,FileNames} -> + handle_expand_regexp_3_2(FileNames,RegExpMod,AllLoaded,Result); + {error,_Reason} -> % Bad path!? Skip it. + Result + end. + +handle_expand_regexp_3_2([File|Rest],RegExpMod,AllLoaded,Result) -> + case filename:extension(File) of + ".beam" -> % It is a beam-file. Consider it! + ModStr=filename:basename(File,".beam"), + Mod=list_to_atom(ModStr), + case {lists:keysearch(Mod,1,AllLoaded),lists:member(Mod,Result)} of + {false,false} -> % This module is not tried before. + ModLen=length(ModStr), + case regexp:first_match(ModStr,RegExpMod) of + {match,1,ModLen} -> % This module satisfies the regexp. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,[Mod|Result]); + _ -> % Error or not perfect match. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,Result) + end; + {_,_} -> % This module is already tested. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,Result) + end; + _ -> % Not a beam-file, skip it. + handle_expand_regexp_3_2(Rest,RegExpMod,AllLoaded,Result) + end; +handle_expand_regexp_3_2([],_,_,Result) -> Result. +%% ------------------------------------------------------------------------------ + +%% Help function which finds out if its argument is a list of zero or more +%% atoms. +%% Returns 'true' or 'false'. +is_list_of_atoms([A|Rest]) when is_atom(A) -> + is_list_of_atoms(Rest); +is_list_of_atoms([_|_]) -> + false; +is_list_of_atoms([]) -> + true. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================= +%% Functions transforming function calls in trace-case file. +%% ============================================================================= + +%% transform(Exprs,Translations)=NewExprs +%% Exprs=list(); List of abstract format erlang terms, as returned by +%% io:parse_erl_exprs/2. +%% Translations=list(); List of translations from function calls to other +%% function calls. [{Mod,Func,Arity,{NewMod,NewFunc,ParamTransformMF}},...] +%% Mod can actually be omitted, ParamTransformMF shall be {M,F} where F is +%% a function taking one argument (the parameter list), and returning the +%% new parameter list. It can also be anything else should no transformation +%% of the parameters be the case. +%% +%% Function that transforms function calls in a trace-case file. The transform/2 +%% can only transform shallow function calls. I.e where both module and function +%% name are specified as atoms. Any binding-environment is not examined. +transform([Expr|Rest],Translations) -> + [transform_2(Expr,Translations)|transform(Rest,Translations)]; +transform([],_) -> + []. + +%% Help function handling a single expr. +transform_2({call,L1,{remote,L2,ModExpr,FuncExpr},Params},Translations) -> + case transform_2(ModExpr,Translations) of + {atom,L3,M} -> + case transform_2(FuncExpr,Translations) of + {atom,L4,F} -> % Now we have a M:F/Arity! + case do_call_translation(M,F,Params,Translations) of + {ok,NewM,NewF,NewP} -> + NewParams=transform(NewP,Translations), + {call,L1,{remote,L2,{atom,L3,NewM},{atom,L4,NewF}},NewParams}; + false -> % No translation or faulty. + NewParams=transform(Params,Translations), + {call,L1,{remote,L2,ModExpr,FuncExpr},NewParams} + end; + NewFuncExpr -> % Not translated to a shallow term. + NewParams=transform(Params,Translations), + {call,L1,{remote,L2,ModExpr,NewFuncExpr},NewParams} + end; + NewModExpr -> % Not translated to a shallow term. + NewFuncExpr=transform_2(FuncExpr,Translations), + NewParams=transform(Params,Translations), + {call,L1,{remote,L2,NewModExpr,NewFuncExpr},NewParams} + end; +transform_2({call,L1,FuncExpr,Params},Translations) -> + case transform_2(FuncExpr,Translations) of + {atom,L3,F} -> % Now we have a M:F/Arity! + case do_call_translation(F,Params,Translations) of + {ok,NewM,NewF,NewP} -> % It is turned into a global call. + NewParams=transform(NewP,Translations), + {call,L1,{remote,L1,{atom,L3,NewM},{atom,L3,NewF}},NewParams}; + false -> % No translation or faulty. + NewParams=transform(Params,Translations), + {call,L1,FuncExpr,NewParams} + end; + NewFuncExpr -> % Not translated to a shallow term. + NewParams=transform(Params,Translations), + {call,L1,NewFuncExpr,NewParams} + end; +transform_2({match,L,P,E},Translations) -> + NewPattern=transform_2(P,Translations), + NewExpr=transform_2(E,Translations), + {match,L,NewPattern,NewExpr}; +transform_2({op,L,Op,Arg1,Arg2},Translations) -> + NewArg1=transform_2(Arg1,Translations), + NewArg2=transform_2(Arg2,Translations), + {op,L,Op,NewArg1,NewArg2}; +transform_2({op,L,Op,Arg},Translations) -> + NewArg=transform_2(Arg,Translations), + {op,L,Op,NewArg}; +transform_2({block,L,Body},Translations) -> + NewBody=transform(Body,Translations), + {block,L,NewBody}; +transform_2({'if',L,Clauses},Translations) -> + NewClauses=transform_clauses(Clauses,Translations), + {'if',L,NewClauses}; +transform_2({'case',L,Func,Clauses},Translations) -> + NewFunc=transform_2(Func,Translations), + NewClauses=transform_clauses(Clauses,Translations), + {'case',L,NewFunc,NewClauses}; +transform_2({'fun',L,{clauses,Clauses}},Translations) -> + NewClauses=transform_clauses(Clauses,Translations), + {'fun',L,NewClauses}; +transform_2({lc,L,Items,GeneratorsFilters},Translations) -> + NewItem=transform_2(Items,Translations), + NewGensAndFilters=transform_gensandfilters(GeneratorsFilters,Translations), + {lc,L,NewItem,NewGensAndFilters}; +transform_2({'catch',L,Expr},Translations) -> + NewExpr=transform_2(Expr,Translations), + {'catch',L,NewExpr}; +transform_2({tuple,L,Elements},Translations) -> + NewElements=transform(Elements,Translations), + {tuple,L,NewElements}; +transform_2({cons,L,Element,Tail},Translations) -> + NewElement=transform_2(Element,Translations), + NewTail=transform_2(Tail,Translations), + {cons,L,NewElement,NewTail}; +transform_2({nil,L},_) -> + {nil,L}; +transform_2({bin,L,Elements},Translations) -> + NewElements=transform_binary(Elements,Translations), + {bin,L,NewElements}; +transform_2(Expr,_) -> % Can be a var for instance. + Expr. + +transform_binary([{bin_element,L,Val,Size,TSL}|Rest],Translations) -> + NewVal=transform_2(Val,Translations), + NewSize=transform_2(Size,Translations), + [{bin_element,L,NewVal,NewSize,TSL}|transform_binary(Rest,Translations)]; +transform_binary([],_) -> + []. + +transform_clauses([{clause,L,Pattern,Guards,Body}|Rest],Translations) -> + NewPattern=transform(Pattern,Translations), + NewBody=transform(Body,Translations), + [{clause,L,NewPattern,Guards,NewBody}|transform_clauses(Rest,Translations)]; +transform_clauses([],_Translations) -> + []. + +transform_gensandfilters([{generator,L,Pattern,Exprs}|Rest],Translations) -> + NewExprs=transform(Exprs,Translations), + [{generator,L,Pattern,NewExprs}|transform_gensandfilters(Rest,Translations)]; +transform_gensandfilters([Expr|Rest],Translations) -> + [transform_2(Expr,Translations)|transform_gensandfilters(Rest,Translations)]; +transform_gensandfilters([],_) -> + []. +%% ------------------------------------------------------------------------------ + +%% This is the heart of the translation functionality. Here we actually try to +%% replace calls to certain functions with other calls. This can include removing +%% arguments. +do_call_translation(M,F,Params,Translations) -> + case lists:keysearch({M,F,length(Params)},1,Translations) of + {value,{_,{NewM,NewF,ArgFun}}} -> % Lets transform the function. + do_call_translation_2(Params,NewM,NewF,ArgFun); + _ -> + false % No translations at all. + end. +do_call_translation(F,Params,Translations) -> + case lists:keysearch({F,length(Params)},1,Translations) of + {value,{_,{NewM,NewF,ArgFun}}} -> % Lets transform the function. + do_call_translation_2(Params,NewM,NewF,ArgFun); + _ -> + false % No translations at all. + end. + +do_call_translation_2(Params,NewM,NewF,ArgFun) -> + case ArgFun of + {M,F} when is_atom(M),is_atom(F) -> + case catch M:F(Params) of + {'EXIT',_Reason} -> + false; % If it does not work, skipp it. + MungedParams when is_list(MungedParams) -> + {ok,NewM,NewF,MungedParams}; + _ -> + false + end; + _ -> % No munging of parameters. + {ok,NewM,NewF,Params} + end. +%% ------------------------------------------------------------------------------ + + +%% ============================================================================= +%% Functions for the runtime component internal debugging system. +%% ============================================================================= + +%% The debug system is meant to provide tracing of ttb at different levels. +%% +%% debug(What,Level,Description) -> nothing significant. +%% What : controls what kind of event. This can both be certain parts of ttb +%% as well as certain levels (info to catastrophy). +%% Level: Determines if What shall be printed or not. +%% Description: this is what happend. +debug(off,_What,_Description) -> + true; % Debug is off, no action. +debug(On,What,Description) -> + debug_2(On,What,Description). + +debug_2(_,What,Description) -> + io:format("INVISO DEBUG:~w, ~p~n",[What,Description]). +%% ----------------------------------------------------------------------------- diff --git a/lib/runtime_tools/src/inviso_rt_meta.erl b/lib/runtime_tools/src/inviso_rt_meta.erl new file mode 100644 index 0000000000..6865dc2242 --- /dev/null +++ b/lib/runtime_tools/src/inviso_rt_meta.erl @@ -0,0 +1,1207 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. 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] +%% +%% This module implements the meta tracer process belonging to the +%% runtime component. Its main purpose is to write the ti-file (traceinformation). +%% The ti-file contains translations between process id:s and what ever "you" +%% want to read in the merged and formatted logfile. +%% This process interacts with the runtime component process. +%% +%% Currently it handles the following types of ti-files: +%% Plain raw, binary log. +%% Relay to other inviso_rt_meta process on another node. +%% +%% The TI file will be on binary format and each entry is: +%% <<LengthIndicator:32, {Pid,Alias,Op,NowStamp} >> +%% Pid=pid(), or if OP==unalias pid()|any_other_than_pid() +%% Op=alias|unalias +%% ----------------------------------------------------------------------------- +-module(inviso_rt_meta). + +%% ----------------------------------------------------------------------------- +%% API exports. +%% ----------------------------------------------------------------------------- + +-export([start/2,start/5]). +-export([stop/1,suspend/1]). +-export([init_tpm/5,init_tpm/8]). +-export([tpm/5,tpm/6,tpm/9,tpm_tracer/5,tpm_tracer/6,tpm_tracer/9]). +-export([tpm_ms/6,tpm_ms_tracer/6,ctpm_ms/5,ctpm/4]). +-export([local_register/1,global_register/1]). +-export([remove_local_register/1,remove_global_register/1]). + +-export([write_ti/1]). + +-export([get_tracer/0,tpm_ms/5,tpm_ms_tracer/5,list_tpm_ms/3,ctpm_ms/4]). + +-export([metacast_call/5,metacast_return_from/6]). +-export([get_state/1]). +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Internal exports. +%% ----------------------------------------------------------------------------- + +-export([init/6]). +-export([init_std_publld/2,clean_std_publld/1]). +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Constants. +%% ----------------------------------------------------------------------------- + +-define(NAMED_MS_TAB,inviso_rt_meta_named_ms). + +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% Exported API (Meant to be used by a runtime component). +%% ============================================================================= + +%% start(TiData,Tracer)={ok,Pid} | {error,Reason} +%% start(TiData,Tracer,InitPublLDmfa,RemovePublLDmfa,CleanPublLDmf)= +%% {ok,Pid} | {error,Reason} +%% TiData={file,FileName}|{relay,Node} +%% Tracer=pid()|port() +%% FileName=string() +%% InitPublLDmfa={Mod,Func,ArgList} +%% RemovePublLDmf={Mod,Func} | void +%% RemovePublLDmf(PublLD)->nothing significant. +%% These functions are called to create and destroy the public loopdata +%% structure available to the meta-trace CallFunc and ReturnFunc. +%% CleanPublLDmf={Mod,Func} +%% This function will periodically be called to clean the public LD from +%% pending meta-trace messages waiting for a corresponding return_from +%% message. +%% +%% Starts a meta-tracer process, opening the ti-file specified in TiData. PublLD +%% is used to communicate data, typically between a call and return_from. +%% If no special initialization function is specified a standard one is used. +%% Note that the meta tracer function must know "who" is the regular tracer +%% (process or port). This because it must be possible to append {tracer,Tracer} +%% in meta match specs. +start(TiData,Tracer) -> + Pid=spawn_link(?MODULE, + init, + [self(), + TiData, + Tracer, + {?MODULE,init_std_publld,[2,[]]}, + void, + {?MODULE,clean_std_publld}]), + wait_for_reply(Pid). +start(TiData,Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf) -> + Pid=spawn_link(?MODULE, + init, + [self(),TiData,Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf]), + wait_for_reply(Pid). + +wait_for_reply(Pid) -> + receive + {Pid,ok} -> + {ok,Pid}; + {Pid,{error,Reason}} -> + {error,Reason} + after + 10000 -> % After very long time. + exit(Pid,kill), % It must be hanging. + {error,time_out} + end. +%% ----------------------------------------------------------------------------- + +%% stop(Pid)=ok +%% Pid=Adders to the meta tracer, pid(). +%% Shutsdown the metatracer. +stop(Pid) -> + Pid ! {stop,self()}, + ok. +%% ----------------------------------------------------------------------------- + +%% suspend(Pid)=ok +%% Pid=Adders to the meta tracer, pid(). +%% Suspends the meta tracer by removing all meta trace patterns. +suspend(Pid) -> + Pid ! {suspend,self()}, + ok. +%% ----------------------------------------------------------------------------- + +%% init_tpm(Pid,Mod,Func,Arity,CallFunc)= +%% init_tpm(Pid,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc)=ok|{error,Reason}. +%% Pid=Address to meta tracer process, pid(). +%% Mod,Func=Pointing out the function which shall be meta traced, atom(). +%% Arity=As above, integer(). +%% InitFunc,RemoveFunc={Module,Function}|fun(), functions being called when +%% to initialize the public loopdata structure, and to reset it. +%% InitFunc(Mod,Func,Arity,PublLD)->{ok,NewPublLD,Output} +%% Supposed to initialize whatever needs to be done before +%% handling any incoming meta-trace message for the Mod:Func/Arity. +%% RemoveFunc(Mod,Func,Arity,PublLD)->{ok,NewPublLD} +%% Called when meta tracing of Mod:Func/Arity is stopped. It is supposed +%% to clear datastructures away from the PublLD. +%% Initializes the public loopdata for this function. Note that we can not use wildcards +%% here (even if it is perfectly legal in Erlang). It also sets the CallFunc and +%% ReturnFunc for the meta traced function. The function is hence ready to be +%% meta traced with either tpm/5 or tpm_ms/5. +%% This function is synchronous, waiting for a reply from the meta server. +init_tpm(Pid,Mod,Func,Arity,CallFunc) -> + init_tpm(Pid,Mod,Func,Arity,void,CallFunc,void,void). +init_tpm(Pid,Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc) -> + send_wait(Pid, + {init_tpm,{Mod,Func,Arity},InitFunc,CallFunc,ReturnFunc,RemoveFunc}). +%% ----------------------------------------------------------------------------- + +%% tpm(Pid,Mod,Func,Arity,MatchSpec)={ok,N}|{error,Reason} +%% tpm(Pid,Mod,Func,Arity,MatchSpec,CallFunc)={ok,N}|{error,Reason} +%% tpm(Pid,Mod,Func,Arity,MatchSpec,InitFunc,CallFunc,ReturnFunc,RemoveFunc)= +%% Pid=Address to meta tracer process, pid(). +%% Mod,Func=Pointing out the function which shall be meta traced, atom(). +%% Arity=As above, integer(). +%% MatchSpec=List of match specification, possibly empty. Remember {return_trace} +%% if expecting return_from messages. +%% InitFunc,CallFunc,ReturnFunc,RemoveFunc={Module,Function}|fun(), +%% functions being called when these functions are called by the meta trace +%% server at certain events. +%% CallFunc(CallingPid,ActualArgList,PublLD)->{ok,NewPrivLD,Output} +%% ReturnFunc(CallingPid,ReturnValue,PublLD)->{ok,NewPrivLD,Output} +%% When a call respectively return_from trace message arrives for the meta +%% traced function, the corresponding function is called. +%% The ReturnFunc must handle the fact that a return_from message arrives +%% for a call which was never noticed. This because the message queue of the +%% meta tracer may have been emptied. +%% Reason=badarg | +%% Output=Characters to be written to the ti-file, bin() | 'void' +%% The tpm/5 function simply starts meta tracing for the function. It must +%% previously have been initialized. +%% tpm/6 & /9 initializes the function and starts meta tracing. +tpm(Pid,Mod,Func,Arity,MatchSpec) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_'-> + send_wait(Pid,{tpm,{Mod,Func,Arity,MatchSpec}}); +tpm(_,_,_,_,_) -> + {error,badarg}. + +tpm(Pid,Mod,Func,Arity,MatchSpec,CallFunc) -> + tpm(Pid,Mod,Func,Arity,MatchSpec,void,CallFunc,void,void). + +tpm(Pid,Mod,Func,Arity,MatchSpec,InitFunc,CallFunc,ReturnFunc,RemoveFunc) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_' -> + send_wait(Pid,{tpm,{Mod,Func,Arity,MatchSpec},InitFunc,CallFunc,ReturnFunc,RemoveFunc}); +tpm(_,_,_,_,_,_,_,_,_) -> + {error,badarg}. +%% ----------------------------------------------------------------------------- + +%% Same as tpm/X but the meta tracer will automatically append {tracer,Tracer} +%% to the enable list in a {trace,Disable,Enable} match spec action term. +tpm_tracer(Pid,Mod,Func,Arity,MatchSpec) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_'-> + send_wait(Pid,{tpm_tracer,{Mod,Func,Arity,MatchSpec}}); +tpm_tracer(_,_,_,_,_) -> + {error,badarg}. + +tpm_tracer(Pid,Mod,Func,Arity,MatchSpec,CallFunc) -> + tpm_tracer(Pid,Mod,Func,Arity,MatchSpec,void,CallFunc,void,void). + +tpm_tracer(Pid,Mod,Func,Arity,MatchSpec,InitFunc,CallFunc,ReturnFunc,RemoveFunc) + when is_atom(Mod),is_atom(Func),is_integer(Arity),is_list(MatchSpec),Mod/='_',Func/='_' -> + send_wait(Pid,{tpm_tracer, + {Mod,Func,Arity,MatchSpec}, + InitFunc,CallFunc,ReturnFunc,RemoveFunc}); +tpm_tracer(_,_,_,_,_,_,_,_,_) -> + {error,badarg}. +%% ----------------------------------------------------------------------------- + +%% tpm_ms(Pid,Mod,Func,Arity,MSname,MS)={ok,N}|{error,Reason} +%% Pid=Address to meta tracer process, pid(). +%% Mod,Func=Pointing out the function to which we shall add a match-spec., atom(). +%% Arity=As above, integer(). +%% MSname=A name to be used if this MS shall be removed later. term(). +%% MatchSpec=List of match specification, Remember {return_trace} +%% if expecting return_from messages. +%% This function adds a list of match-specs to the already existing ones. It +%% uses an internal database to keep track of existing match-specs. If the +%% match-spec does not result in any meta traced functions (for whatever reason), +%% the MS is not saved in the database. The previously known match-specs are +%% not removed. +tpm_ms(Pid,Mod,Func,Arity,MSname,MS) -> + send_wait(Pid,{tpm_ms,{Mod,Func,Arity},MSname,MS}). +%% ----------------------------------------------------------------------------- + +%% Same as tpm_ms/6 but the meta tracer will automatically append {tracer,Tracer} +%% to the enable list in a {trace,Disable,Enable} match spec action term. +tpm_ms_tracer(Pid,Mod,Func,Arity,MSname,MS) -> + send_wait(Pid,{tpm_ms_tracer,{Mod,Func,Arity},MSname,MS}). +%% ----------------------------------------------------------------------------- + +%% ctpm_ms(Pid,Mod,Func,Arity)=ok +%% +%% Removes a names match-spec from the meta traced function. Note that is never +%% a fault to remove an MS. Not even from a function which is non existant. +ctpm_ms(Pid,Mod,Func,Arity,MSname) -> + send_wait(Pid,{ctpm_ms,{Mod,Func,Arity},MSname}). +%% ----------------------------------------------------------------------------- + +%% Quick versions for erlang:register/2 which also uses a default CallFunc +%% and a default ReturnFunc. +local_register(Pid) -> + Res1=tpm(Pid, + erlang,register,2,[{'_',[],[{exception_trace}]}], + fun metafunc_init/4,fun local_register_call/3, + fun local_register_return/3,void), + Res2=tpm(Pid, + erlang,unregister,1,[], + void,fun local_unregister_call/3,void,void), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% Quick version for global:register_name/2, /3. +global_register(Pid) -> + Res1=tpm(Pid,global,handle_call,3,[{[{register,'_','_','_'},'_','_'],[],[]}], + void,fun global_register_call/3,void,void), + Res2=tpm(Pid,global,delete_global_name,2,[], + void,fun global_unregister_call/3,void,void), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% ctpm(Pid,Mod,Func,Arity)=ok|{error,bad_mfa} +%% +%% Removes the meta trace pattern for the function, means stops generating output +%% for this function. The public LD may be cleared by the previously entered +%% RemoveFunc. +ctpm(Pid,Mod,Func,Arity) -> + send_wait(Pid,{ctpm,{Mod,Func,Arity}}). +%% ----------------------------------------------------------------------------- + +%% remove_local_register(Pid)={Res1,Res2} +%% Res1,Res2=ok|{error,Reason} +remove_local_register(Pid) -> + Res1=ctpm(Pid,erlang,register,2), + Res2=ctpm(Pid,erlang,unregister,1), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% remove_global_register(Pid)={Res1,Res2} +%% Res1,Res2=ok|{error,Reason} +remove_global_register(Pid) -> + Res1=ctpm(Pid,global,handle_call,3), + Res2=ctpm(Pid,global,delete_global_name,2), + {Res1,Res2}. +%% ----------------------------------------------------------------------------- + +%% Exported help functions which may be used in programming CallFunc and/or +%% ReturnFunc. Useful if the call is done on one node but must trigger the +%% start of something at other nodes. +metacast_call(Nodes,OrigPid,M,F,Args) -> + multicast(Nodes,{trace_ts,OrigPid,call,{M,F,Args},void}), + ok. + +metacast_return_from(Nodes,OrigPid,M,F,Arity,Value) -> + multicast(Nodes,{trace_ts,OrigPid,return_from,{M,F,Arity},Value,void}), + ok. + +multicast([Node|Rest],Msg) -> + {?MODULE,Node} ! Msg, + multicast(Rest,Msg); +multicast([],_) -> + true. +%% ----------------------------------------------------------------------------- + +%% get_states(Pid)={ok,LD,PubLD}. +get_state(Pid) -> + send_wait(Pid,get_state). +%% ----------------------------------------------------------------------------- + + +send_wait(To,Msg) -> + Ref=make_ref(), + MRef=erlang:monitor(process,To), + To ! {Msg,Ref,self()}, + receive + {inviso_rt_meta_reply,Ref,Reply} -> + erlang:demonitor(MRef), + Reply; + {'DOWN',MRef,_,_To,_Reason} -> + {error,no_metatracer} + end. + +reply(To,Ref,Reply) -> + To ! {inviso_rt_meta_reply,Ref,Reply}. +%% ----------------------------------------------------------------------------- + +%% ============================================================================= +%% Special API. +%% ============================================================================= + +%% write_ti(OutPut)= +%% OutPut=binary() +%% Makes an extra entry into the trace information file (ti-file). This is useful +%% if a pid-alias association is learned in another way than through a meta traced +%% function call. Note that this API can only be used locally at the node in +%% question. +write_ti(OutPut) -> + catch ?MODULE ! {write_ti,OutPut}. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% API intended to be used on CallFuncs and RemoveFuncs. +%% ============================================================================= + +%% The reason there must be a special API for CallFuncs and RemoveFuncs are is +%% that those functions are executed inside *this* process context. Hence they +%% can not make function calls requiering this process to receive messages. + +%% Returns the tracer used for regular tracing. The reason this is implemented +%% in this way is that this function is intended to be used in meta trace call- +%% back functions. And there we can not have message passing API:s to the meta +%% trace(!). +get_tracer() -> + get(tracer). +%% ----------------------------------------------------------------------------- + +%% Function equivalent to inviso_rt:tpm_ms/6. This function can *only* be used +%% inside a CallFunc or a RemoveFunc. +tpm_ms(Mod,Func,Arity,MSname,MS) -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + {ok,h_tpm_ms(Mod,Func,Arity,MSname,MS)}; + no -> + {error,not_initiated} + end. +%% ----------------------------------------------------------------------------- + +tpm_ms_tracer(Mod,Func,Arity,MSname,MS) -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + NewMS=add_tracer(MS,get_tracer()), + {ok,h_tpm_ms(Mod,Func,Arity,MSname,NewMS)}; + no -> + {error,not_initiated} + end. +%% ----------------------------------------------------------------------------- + +%% Function that returns all MSname in use for Mod:Func/Arity +list_tpm_ms(Mod,Func,Arity) -> + {ok,h_list_tpm_ms(Mod,Func,Arity)}. +%% ----------------------------------------------------------------------------- + +%% Function equivalent to inviso_rt:ctpm_ms/5. This function can *only* be used +%% inside a CallFunc or a RemoveFunc. +ctpm_ms(Mod,Func,Arity,MSname) -> + h_ctpm_ms(Mod,Func,Arity,MSname), + ok. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% The server implemenation. +%% ============================================================================= + +init(Parent,TiData,Tracer,InitPublLDmfa,RemovePublLDmf,CleanPublLDmf) -> + process_flag(priority,high), % Since we may receive from many procs. + register(?MODULE,self()), % So we can act as relay receiver. + case open_traceinfo_file(TiData) of + {ok,TI} -> % The ti.-file. + TId=ets:new(?NAMED_MS_TAB,[named_table,set,protected]), + PublLD=do_init_publ_ld(InitPublLDmfa), + Parent ! {self(),ok}, + put(tracer,Tracer), % Uggly quick fix! + loop(Parent, + Tracer, + TI, + mk_new_ld(InitPublLDmfa,RemovePublLDmf,CleanPublLDmf,TId), + PublLD, + now()); + {error,Reason} -> + Parent ! {self(),{error,Reason}} + end. +%% ----------------------------------------------------------------------------- + +loop(Parent,Tracer,TI,LD,PrevPublLD,PrevCleanTime) -> + {PublLD,CleanTime}=throw_old_failed(get_cleanpublldmf_ld(LD),PrevPublLD,PrevCleanTime), + receive + {{init_tpm,{Mod,Func,Arity},InitFunc,CallFunc,ReturnFunc,RemoveFunc},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + no -> % Good then we can add it! + case check_tpm_args(Mod,Func,Arity) of + true -> % Args are ok. + {NewLD,NewPublLD}= + h_init_tpm(Mod,Func,Arity, + InitFunc,CallFunc,ReturnFunc,RemoveFunc, + TI,LD,PublLD), + reply(Parent,Ref,ok), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime); + false -> % Faulty arguments, + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + yes -> % If it already exists, cant init again. + reply(Parent,Ref,{error,already_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm,{Mod,Func,Arity,MS},InitFunc,CallFunc,ReturnFunc,RemoveFunc},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + no -> % Good then we can add it! + case check_tpm_args(Mod,Func,Arity) of + true -> % Args are ok. + {NewLD,NewPublLD,N}= + h_tpm(Mod,Func,Arity,MS, + InitFunc,CallFunc,ReturnFunc,RemoveFunc, + TI,LD,PublLD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime); + false -> + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + yes -> + reply(Parent,Ref,{error,already_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm,{Mod,Func,Arity,MS}},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + {NewLD,N}=h_tpm(Mod,Func,Arity,MS,LD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,PublLD,CleanTime); + no -> % Must be initiated before. + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_tracer,{Mod,Func,Arity,MS},InitFunc,CallFunc,ReturnFunc,RemoveFunc},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + no -> % Good then we can add it! + case check_tpm_args(Mod,Func,Arity) of + true -> % Args are ok. + NewMS=add_tracer(MS,Tracer), + {NewLD,NewPublLD,N}= + h_tpm(Mod,Func,Arity,NewMS, + InitFunc,CallFunc,ReturnFunc,RemoveFunc, + TI,LD,PublLD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime); + false -> + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + yes -> + reply(Parent,Ref,{error,already_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_tracer,{Mod,Func,Arity,MS}},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + NewMS=add_tracer(MS,Tracer), + {NewLD,N}=h_tpm(Mod,Func,Arity,NewMS,LD), + reply(Parent,Ref,{ok,N}), + loop(Parent,Tracer,TI,NewLD,PublLD,CleanTime); + no -> % Must be initiated before. + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_ms,{Mod,Func,Arity},MSname,MS},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + reply(Parent,Ref,{ok,h_tpm_ms(Mod,Func,Arity,MSname,MS)}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + no -> + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{tpm_ms_tracer,{Mod,Func,Arity},MSname,MS},Ref,Parent} -> + case check_mfarity_exists(Mod,Func,Arity) of + yes -> % Ok, and args must be ok then also. + NewMS=add_tracer(MS,Tracer), + reply(Parent,Ref,{ok,h_tpm_ms(Mod,Func,Arity,MSname,NewMS)}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + no -> + reply(Parent,Ref,{error,not_initiated}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {{ctpm_ms,{Mod,Func,Arity},MSname},Ref,Parent} -> + reply(Parent,Ref,ok), + h_ctpm_ms(Mod,Func,Arity,MSname), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + {{ctpm,{Mod,Func,Arity}},Ref,Parent} -> + case get_remove_func_ld(Mod,Func,Arity,LD) of + false -> % Incorrect Mod:Func/Arity! + reply(Parent,Ref,{error,bad_mfa}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); % Do nothing! + MF -> % {M,F}, Func or 'void'. + catch erlang:trace_pattern({Mod,Func,Arity},false,[meta]), + NewPublLD=do_removefunc(MF,Mod,Func,Arity,PublLD), + NewLD=ctpm_ld(Mod,Func,Arity,LD), + reply(Parent,Ref,ok), + loop(Parent,Tracer,TI,NewLD,NewPublLD,CleanTime) + end; + {suspend,Parent} -> % Removes all meta trace patterns. + stop_all_meta_tracing(get_all_meta_funcs_ld(LD),PublLD,LD), + do_remove_publ_ld(get_removepublldmf_ld(LD),PublLD), + NewPublLD=do_init_publ_ld(get_initpublldmfa_ld(LD)), + loop(Parent,Tracer,TI,reset_ld(LD),NewPublLD,CleanTime); + {stop,Parent} -> % Make a controlled shutdown. + stop_all_meta_tracing(get_all_meta_funcs_ld(LD),PublLD,LD), + do_remove_publ_ld(get_removepublldmf_ld(LD),PublLD), + close_traceinfo_file(TI); % And then simply terminate. + {trace_ts,Pid,call,{M,F,Args},TS} -> + case handle_meta(get_call_func_ld(M,F,length(Args),LD),Pid,{call,Args,TS},PublLD) of + {ok,NewPublLD,Output} when is_binary(Output);is_list(Output) -> + write_output(TI,Output), + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + {ok,NewPublLD,_} -> % No output to the ti-file this time. + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + _ -> % Not handled correct, not much to do. + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {trace_ts,Pid,TypeTag,{M,F,Arity},Value,TS} + when TypeTag==return_from;TypeTag==exception_from -> + case handle_meta(get_return_func_ld(M,F,Arity,LD),Pid,{TypeTag,Value,TS},PublLD) of + {ok,NewPublLD,Output} when is_binary(Output);is_list(Output) -> + write_output(TI,Output), + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + {ok,NewPublLD,_} -> % No output to the ti-file this time. + loop(Parent,Tracer,TI,LD,NewPublLD,CleanTime); + _ -> % Not handled correct, not much to do. + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end; + {relayed_meta,Bin} -> + write_output(TI,Bin), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + {write_ti,OutPut} -> + write_output(TI,OutPut), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + {get_state,Ref,From} -> % Debug function. + reply(From,Ref,{ok,LD,PublLD}), + loop(Parent,Tracer,TI,LD,PublLD,CleanTime); + _Other -> + loop(Parent,Tracer,TI,LD,PublLD,CleanTime) + end. + + +%% ============================================================================= +%% First level help functions. +%% ============================================================================= + +%% Function which opens the trace-information file(s). It must understand +%% the tidata specification which is part of the tracerdata given to the +%% runtime component during init_tracing. +%% It must return an internal notation of the time of file open and a +%% useful descriptor the write_output function can use. +%% Returns {ok,TiDescriptor} or {error,Reason}. +open_traceinfo_file({file,FileName}) -> % A plain raw binary file. + case file:open(FileName,[write,raw,binary]) of + {ok,FD} -> + {ok,{file,FD}}; + {error,Reason} -> + {error,{open,[FileName,Reason]}} + end; +open_traceinfo_file({relay,ToNode}) -> % Use distributed Erlang. + {ok,{relay,ToNode}}; +open_traceinfo_file(IncorrectTI) -> + {error,{badarg,IncorrectTI}}. +%% ----------------------------------------------------------------------------- + +close_traceinfo_file({file,FD}) -> + file:close(FD); +close_traceinfo_file(_) -> + ok. +%% ----------------------------------------------------------------------------- + +%% Help function handling initializing meta tracing of a function. +%% Returns {NewLD,NewPublLD}. +h_init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc,TI,LD,PublLD) -> + case do_initfunc(InitFunc,Mod,Func,Arity,PublLD) of + {NewPublLD,Output} -> + write_output(TI,Output), + NewLD=init_tpm_ld(Mod,Func,Arity,CallFunc,ReturnFunc,RemoveFunc,LD), + {NewLD,NewPublLD}; + false -> % The initfunc did not do anything. + NewLD=init_tpm_ld(Mod,Func,Arity,CallFunc,ReturnFunc,RemoveFunc,LD), + {NewLD,PublLD} + end. +%% ----------------------------------------------------------------------------- + +%% Help function handling initializing meta tracing of a function and also +%% set the meta trace pattern as specified. +%% Returns {NewLD,NewPublLD,N}. +h_tpm(Mod,Func,Arity,MS,InitFunc,CallFunc,ReturnFunc,RemoveFunc,TI,LD,PublLD) -> + {NewLD,NewPublLD}= + h_init_tpm(Mod,Func,Arity,InitFunc,CallFunc,ReturnFunc,RemoveFunc,TI,LD,PublLD), + case set_meta_tracing(Mod,Func,Arity,MS) of + true -> % Ok, set one pattern. + {NewLD,NewPublLD,1}; + false -> + {NewLD,NewPublLD,0} + end. +%% ----------------------------------------------------------------------------- + +%% Help function handling setting meta trace patter for a function which has +%% already been intialized. Note that we must remove all potentially stored +%% match-specs, if this function has been given match-specs before with +%% tpm_ms. +%% Returns a {NewLD,N}. +h_tpm(Mod,Func,Arity,MS,LD) -> + case set_meta_tracing(Mod,Func,Arity,MS) of + true -> + {remove_ms_ld(Mod,Func,Arity,LD),1}; + false -> + {LD,0} + end. +%% ----------------------------------------------------------------------------- + +%% Help function that adds a match-spec to Mod:Func/Arity. It is not defined +%% in which order the match-specs will be given to the BIF. +%% Note that if an MS with the same name as an exiting is inserted, the previous +%% match-spec will be removed. +%% Very important to realise is that the empty meta match spec [] imposes no +%% restrictions what so ever on the generating of meta trace call messages. +%% Uncontrolled sending of such messages may quickly drain power from the system. +%% Since an empty match-spec will "disappear" when added to other match specs, +%% the empty match is transformed to what it actually is: [{'_',[],[]}]. +%% Returns 0 or 1 indicating failure or success. +h_tpm_ms(Mod,Func,Arity,MSname,MS) -> + MSsNames=get_ms_ld(Mod,Func,Arity), % Fetch all previous match-specs. + TransformedMS=h_tpm_ms_convert_null_ms(MS), + MSsNames1=lists:keydelete(MSname,1,MSsNames), % If it already existed, it is gone! + NewMSs=lists:flatten([TransformedMS,lists:map(fun({_Name,MSx})->MSx end,MSsNames1)]), + case set_meta_tracing(Mod,Func,Arity,NewMSs) of + true -> % We only save the MS if it was good. + put_ms_ld(Mod,Func,Arity,MSname,TransformedMS,MSsNames1), + 1; + false -> + 0 + end. + +%% Help function converting the null match spec into, still a null match spec, +%% on a proper match spec format. This because it will otherwise be difficult +%% to see the difference between no active tpm_ms and all a set of null ms. +h_tpm_ms_convert_null_ms([]) -> + [{'_',[],[]}]; +h_tpm_ms_convert_null_ms(MS) -> + MS. +%% ----------------------------------------------------------------------------- + +%% Help function returning a list of all names used for match-functions for +%% the Mod:Func/Arity in question. +h_list_tpm_ms(Mod,Func,Arity) -> + MSsNames=get_ms_ld(Mod,Func,Arity), % A list of {MSname,MS}. + lists:map(fun({MSname,_})->MSname end,MSsNames). +%% ----------------------------------------------------------------------------- + +%% Function that removes a named match-spec. Returns nothing significant. +%% Note that if we end up with no match-specs, we must remove the meta trace +%% patten all together. That is bringing the function back to just initiated. +h_ctpm_ms(Mod,Func,Arity,MSname) -> + case get_ms_ld(Mod,Func,Arity) of + [] -> % The name does certainly not exist! + true; % We don't have to do anything. + MSsNames -> + case lists:keysearch(MSname,1,MSsNames) of + {value,{_,_MS}} -> % Ok, we must do something! + NewMSsNames=lists:keydelete(MSname,1,MSsNames), + case lists:flatten(lists:map(fun({_Name,MS})->MS end,NewMSsNames)) of + [] -> % This means stop meta tracing. + set_meta_tracing(Mod,Func,Arity,false); + NewMSs -> + set_meta_tracing(Mod,Func,Arity,NewMSs) + end, + set_ms_ld(Mod,Func,Arity,NewMSsNames); + false -> % But this name does not exist. + true % So we do not have to do anything. + end + end. +%% ----------------------------------------------------------------------------- + +%% Function that checks the arguments to the meta trace pattern. The reason we +%% must do this is that we can only allow meta tracing on specific functions and +%% not using wildpatterns. Otherwise the meta trace server will not understand +%% which callfunc for instance to call when a meta-trace message is generated +%% for a function. +%% Returns 'true' or 'false'. +check_tpm_args(Mod,Func,Arity) + when is_atom(Mod),is_atom(Func),is_integer(Arity),Mod/='_',Func/='_' -> + true; +check_tpm_args(_,_,_) -> + false. +%% ----------------------------------------------------------------------------- + +%% Help function which calls the actual BIF setting meta-trace-patterns. +%% Returns 'true' or 'false'. +set_meta_tracing(Mod,Func,Arity,MS) when is_atom(Mod) -> + case erlang:module_loaded(Mod) of + true -> + set_meta_tracing_2(Mod,Func,Arity,MS); + false -> % The module is not loaded. + case code:ensure_loaded(Mod) of + {module,_Mod} -> + set_meta_tracing_2(Mod,Func,Arity,MS); + {error,_Reason} -> % Could not load the module. + false % No use try to trace. + end + end; +set_meta_tracing(_,_,_,_) -> + false. + +set_meta_tracing_2(Mod,Func,Arity,MS) -> + case catch erlang:trace_pattern({Mod,Func,Arity},MS,[meta]) of + 0 -> % Hmm, nothing happend :-) + false; + N when is_integer(N) -> % The normal case, some functions were hit. + true; + {'EXIT',_Reason} -> + false + end. +%% ----------------------------------------------------------------------------- + +%% Help function which removes all meta trace pattern for the functions mentioned +%% in the list being first argument. It also executes the remove funcs for each +%% and every no longer meta traced function. This done since some of the remove +%% functions may do side-effects (like deleteing ETS tables). +%% Returns nothing significant. +stop_all_meta_tracing([{M,F,Arity}|Rest],PublLD,LD) -> + catch erlang:trace_pattern({M,F,Arity},false,[meta]), + NewPublLD=do_removefunc(get_remove_func_ld(M,F,Arity,LD),M,F,Arity,PublLD), + stop_all_meta_tracing(Rest,NewPublLD,LD); +stop_all_meta_tracing([],_,_) -> + true. +%% ----------------------------------------------------------------------------- + +%% This function calls the function registered to be handler for a certain +%% meta-traced function. Such a function or fun must take three arguments +%% and return {ok,NewPrivLD,OutPutBinary} or 'false'. OutPutBinary may be +%% something else, and is then ignored. +handle_meta({M,F},Pid,Arg1,PrivLD) -> + (catch M:F(Pid,Arg1,PrivLD)); +handle_meta(Fun,Pid,Arg1,PrivLD) when is_function(Fun) -> + (catch Fun(Pid,Arg1,PrivLD)); +handle_meta(_,_,_,_) -> % Don't know how to do this. + false. +%% ----------------------------------------------------------------------------- + +%% Help function writing output from a callback function to the ti-file. +%% Output can be a binary or a list of binaries. +write_output(TI,[OutPut|Rest]) -> + write_output(TI,OutPut), + write_output(TI,Rest); +write_output({file,FD},Bin) when is_binary(Bin) -> % Plain direct-binary file + Size=byte_size(Bin), + file:write(FD,list_to_binary([<<0,Size:32>>,Bin])); +write_output({relay,ToNode},Bin) when is_atom(ToNode),is_binary(Bin) -> + {inviso_rt_meta,ToNode} ! {relayed_meta,Bin}; +write_output(_,_) -> % Don't understand, just skip. + true. +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% Various help functions. +%% ============================================================================= + +%% Help function initializing the public loopdata structure. Note that if the +%% supplied InitPublLDmfa is faulty we let the structure become the error. +%% The error will most likely turn up in an error report somewhere, eventually. +do_init_publ_ld({M,F,Args}) when is_atom(M),is_atom(F),is_list(Args) -> + case catch apply(M,F,Args) of + {'EXIT',_Reason} -> + {error,init_publ_ld_func}; % Let the struct be this error! + InitialPublLD -> + InitialPublLD + end; +do_init_publ_ld(_) -> + {error,init_publ_ld_func}. +%% ----------------------------------------------------------------------------- + +%% Help function which removes the public loopdata structure. The function does +%% not necessarily have to exist. Returns nothing significant. +do_remove_publ_ld({M,F},PublLD) when is_atom(M),is_atom(F) -> + catch M:F(PublLD); +do_remove_publ_ld(_,_) -> + true. +%% ----------------------------------------------------------------------------- + +%% Hlp function initializing a particular meta traced function into the public +%% loopdata. Note that the function is not mandatory. +%% Returns {NewPublLD,Output} or 'false'. +do_initfunc({M,F},Mod,Func,Arity,PublLD) when is_atom(M),is_atom(F) -> + case catch M:F(Mod,Func,Arity,PublLD) of + {ok,NewPublLD,Output} -> + {NewPublLD,Output}; + _ -> % Everything else is an error. + false % Act as no initialization function. + end; +do_initfunc(Fun,Mod,Func,Arity,PublLD) when is_function(Fun) -> + case catch Fun(Mod,Func,Arity,PublLD) of + {ok,NewPublLD,Output} -> + {NewPublLD,Output}; + _ -> % Everything else is an error. + false % Act as no initialization function. + end; +do_initfunc(_,_,_,_,_) -> % Perhaps too generous, should be 'void' only. + false. +%% ----------------------------------------------------------------------------- + +%% Help function removing a particular meta traced function from the public +%% loopdata. Note that we do not make much noice should the call back function +%% be faulty. +do_removefunc({M,F},Mod,Func,Arity,PublLD) when is_atom(M),is_atom(F) -> + case catch M:F(Mod,Func,Arity,PublLD) of + {ok,NewPublLD} -> + NewPublLD; + _ -> % Everything else is an error. + PublLD % Act as no initialization function. + end; +do_removefunc(Fun,Mod,Func,Arity,PublLD) when is_function(Fun) -> + case catch Fun(Mod,Func,Arity,PublLD) of + {ok,NewPublLD} -> + NewPublLD; + _ -> % Everything else is an error. + PublLD % Act as no initialization function. + end; +do_removefunc(_,_,_,_,PublLD) -> + PublLD. +%% ----------------------------------------------------------------------------- + +%% Function that, if the time has come, goes through the priv-ld structure and +%% cleans away entryn left behind. The usual cause is that the function call +%% caused an exception and there were therefore no matching return_from. +%% Returns {NewPrivLD,now()}. +throw_old_failed({M,F},PrivLD,PrevClean) -> + case difference_in_now(PrevClean,now(),60) of % We clean once every minute. + true -> + case catch apply(M,F,[PrivLD]) of + {'EXIT',_Reason} -> % Something went wrong, ignore it. + {PrivLD,now()}; % Just keep the old priv-ld. + NewPrivLD -> % The function must return a priv-ld. + {NewPrivLD,now()} + end; + false -> % Not time yet! + {PrivLD,PrevClean} + end. +%% ----------------------------------------------------------------------------- + +%% Help function comparing two now timestamps. Returns true or false depending +%% on if S2 is more than DiffS seconds after S1. Only works for differences +%% less than 1 million seconds. +difference_in_now({MegaS1,S1,_},{MegaS2,S2,_},DiffS) -> + if + MegaS1+1<MegaS2 -> % More than 1 Mega sec. difference. + true; + MegaS1==MegaS2,S1+DiffS<S2 -> + true; + MegaS1+1==MegaS2,S1+DiffS<S2+1000000 -> + true; + true -> + false + end. +%% ----------------------------------------------------------------------------- + +%% This help function adds a {tracer,Tracer} to the enable-list in a 'trace' +%% match spec action. The reason for this is that the author of the a meta +%% match spec meant to turn tracing on for the process executing the match spec +%% can not know the tracer. This since the match spec is most likely authored +%% at the control component's node, and not here. +%% Note the double tuple necessary to make it just precise a tuple! +%% Returns a new match spec. +add_tracer([MS1|Rest],Tracer) -> + [add_tracer_2(MS1,Tracer)|add_tracer(Rest,Tracer)]; +add_tracer([],_) -> + []; +add_tracer(NotList,_Tracer) -> % Can be 'false', but also an error. + NotList. + +add_tracer_2({Head,Cond,Body},Tracer) -> + {Head,Cond,add_tracer_3(Body,Tracer)}; +add_tracer_2(Faulty,_Tracer) -> + Faulty. + +add_tracer_3([{trace,Disable,Enable}|Rest],Tracer) when is_list(Enable) -> + [{trace,Disable,Enable++[{{tracer,Tracer}}]}|Rest]; +add_tracer_3([ActionTerm|Rest],Tracer) -> + [ActionTerm|add_tracer_3(Rest,Tracer)]; +add_tracer_3([],_Tracer) -> + []; +add_tracer_3(FaultyBody,_Tracer) -> + FaultyBody. +%% ----------------------------------------------------------------------------- + +%% ----------------------------------------------------------------------------- +%% Help functions handling internal loopdata. +%% ----------------------------------------------------------------------------- + +-record(ld,{init_publ_ld_mfa, % {M,F,Args} + remove_publ_ld_mf, % {M,F} | void + clean_publ_ld_mf, % {Mod,Func} + ms_mfarities=notable, % ETS holding names match functions. + call_mfarities=[], % [{{M,F,Arity},2-TupleOrFun},...] + return_mfarities=[], % [{{M,F,Arity},2-TupleOrFun},...] + remove_mfarities=[] + }). + +mk_new_ld(InitPublLDmfa,RemovePublLDmf,CleanPublLDmf,TId) -> + #ld{ + init_publ_ld_mfa=InitPublLDmfa, + remove_publ_ld_mf=RemovePublLDmf, + clean_publ_ld_mf=CleanPublLDmf, + ms_mfarities=TId + }. +%% ----------------------------------------------------------------------------- + +%% Function which restores the internal loop data to somekind of initial state. +%% This is useful when tracing has been suspended. +reset_ld(#ld{init_publ_ld_mfa=InitPublLDmfa, + remove_publ_ld_mf=RemovePublLDmf, + clean_publ_ld_mf=CleanPublLDmf, + ms_mfarities=TId}) -> + ets:match_delete(TId,{'_','_'}), % Empty the table. + #ld{init_publ_ld_mfa=InitPublLDmfa, + remove_publ_ld_mf=RemovePublLDmf, + clean_publ_ld_mf=CleanPublLDmf, + ms_mfarities=TId}. +%% ----------------------------------------------------------------------------- + +get_initpublldmfa_ld(#ld{init_publ_ld_mfa=InitPublLDmfa}) -> + InitPublLDmfa. +%% ----------------------------------------------------------------------------- + +get_removepublldmf_ld(#ld{remove_publ_ld_mf=RemovePublLDmf}) -> + RemovePublLDmf. +%% ----------------------------------------------------------------------------- + +get_cleanpublldmf_ld(#ld{clean_publ_ld_mf=CleanPublLDmf}) -> + CleanPublLDmf. +%% ----------------------------------------------------------------------------- + +%% Help function adding data associated with a meta traced function to the +%% internal loopdata. Called when meta tracing is activated for M:F/Arity. +init_tpm_ld(M,F,Arity,CallFunc,ReturnFunc,RemoveFunc,LD) -> + ets:insert(LD#ld.ms_mfarities,{{M,F,Arity},[]}), + CallFuncs=LD#ld.call_mfarities, + ReturnFuncs=LD#ld.return_mfarities, + RemoveFuncs=LD#ld.remove_mfarities, + LD#ld{call_mfarities=[{{M,F,Arity},CallFunc}|CallFuncs], + return_mfarities=[{{M,F,Arity},ReturnFunc}|ReturnFuncs], + remove_mfarities=[{{M,F,Arity},RemoveFunc}|RemoveFuncs]}. +%% ----------------------------------------------------------------------------- + +%% Help function which answers the question if we have already initiated the +%% function. It is done by looking in the ETS-table with named match-functions. +%% If there is an entry in the set-type table for M:F/Arity, the function is +%% initiated. +%% Returns 'yes' or 'no'. +check_mfarity_exists(M,F,Arity) -> + case ets:lookup(?NAMED_MS_TAB,{M,F,Arity}) of + [] -> + no; + [_] -> + yes + end. +%% ----------------------------------------------------------------------------- + +%% Help function adding an entry with [{MSname,MSlist}|MSsNames] for M:F/Arity. +%% Note that any already existing entry is removed. +%% Returns nothing significant. +put_ms_ld(M,F,Arity,MSname,MS,MSsNames) -> + ets:insert(?NAMED_MS_TAB,{{M,F,Arity},[{MSname,MS}|MSsNames]}). +%% ----------------------------------------------------------------------------- + +%% Help function taking a list of {MSname,MSs} and storing them in the +%% internal loop data structure. The storage is actually implemented as an ETS +%% table. Any previous list of {MSname,MSs} associated with this {M,F,Arity} will +%% be lost. Returns nothing significant. +set_ms_ld(M,F,Arity,MSsNames) -> + ets:insert(?NAMED_MS_TAB,{{M,F,Arity},MSsNames}). +%% ----------------------------------------------------------------------------- + +%% Help function fetching a list of {MSname,MatchSpecs} for a M:F/Arity. The +%% match-functions are stored in an ETS table searchable on {M,F,Arity}. +get_ms_ld(M,F,Arity) -> + case ets:lookup(?NAMED_MS_TAB,{M,F,Arity}) of + [{_MFArity,MSsNames}] -> + MSsNames; + [] -> + [] + end. +%% ----------------------------------------------------------------------------- + +%% Help function removing all saved match-specs for a certain M:F/Arity. +%% Returns a new loopdata structure. +remove_ms_ld(M,F,Arity,LD) -> + ets:delete(LD#ld.ms_mfarities,{M,F,Arity}), + LD. +%% ----------------------------------------------------------------------------- + +%% Help function which removes all information about a meta traced function from +%% the internal loopdata. Returns a new loopdata structure. +ctpm_ld(M,F,Arity,LD) -> + ets:delete(LD#ld.ms_mfarities,{M,F,Arity}), + NewCallFuncs=lists:keydelete({M,F,Arity},1,LD#ld.call_mfarities), + NewReturnFuncs=lists:keydelete({M,F,Arity},1,LD#ld.return_mfarities), + NewRemoveFuncs=lists:keydelete({M,F,Arity},1,LD#ld.remove_mfarities), + LD#ld{call_mfarities=NewCallFuncs, + return_mfarities=NewReturnFuncs, + remove_mfarities=NewRemoveFuncs}. +%% ----------------------------------------------------------------------------- + +get_call_func_ld(M,F,Arity,#ld{call_mfarities=CallFuncs}) -> + case lists:keysearch({M,F,Arity},1,CallFuncs) of + {value,{_,MF}} -> + MF; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +get_return_func_ld(M,F,Arity,#ld{return_mfarities=CallFuncs}) -> + case lists:keysearch({M,F,Arity},1,CallFuncs) of + {value,{_,MF}} -> + MF; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +get_remove_func_ld(M,F,Arity,#ld{remove_mfarities=RemoveFuncs}) -> + case lists:keysearch({M,F,Arity},1,RemoveFuncs) of + {value,{_,MF}} -> + MF; + false -> + false + end. +%% ----------------------------------------------------------------------------- + +%% Function returning a list of all {Mod,Func,Arity} which are currently meta +%% traced. It does do by listifying the call_mfarities field in the internal +%% loopdata. +get_all_meta_funcs_ld(#ld{call_mfarities=CallFuncs}) -> + lists:map(fun({MFArity,_})->MFArity end,CallFuncs). +%% ----------------------------------------------------------------------------- + + +%% ============================================================================= +%% Functions for the standard PublLD structure. +%% +%% It is tuple {Part1,GlobalData} where Part1 is of length at least 2. +%% Where each field is a list of tuples. The last item in each tuple shall be +%% a now tuple, making it possible to clean it away should it be too old to be +%% relevant (there was no return_from message due to a failure). +%% Other fields can be used for other functions. +%% The GlobalData is not cleaned but instead meant to store data must be passed +%% to each CallFunc when a meta trace message arrives. +%% ============================================================================= + +%% Function returning our standard priv-loopdata structure. +init_std_publld(Size,GlobalData) -> + {list_to_tuple(lists:duplicate(Size,[])),GlobalData}. +%% ----------------------------------------------------------------------------- + +%% Function capable of cleaning out a standard publ-ld. The last element of each +%% tuple must be the now item. +%% Returns a new publ-ld structure. +clean_std_publld({Part1,GlobalData}) -> + {clean_std_publld_2(Part1,now(),tuple_size(Part1),[]),GlobalData}. + +clean_std_publld_2(_,_,0,Accum) -> + list_to_tuple(Accum); +clean_std_publld_2(PublLD,Now,Index,Accum) -> + NewTupleList=clean_std_publld_3(element(Index,PublLD),Now), + clean_std_publld_2(PublLD,Now,Index-1,[NewTupleList|Accum]). + +clean_std_publld_3([Tuple|Rest],Now) -> + PrevNow=element(tuple_size(Tuple),Tuple), % Last item shall be the now item. + case difference_in_now(PrevNow,Now,30) of + true -> % Remove it then! + clean_std_publld_3(Rest,Now); + false -> % Keep it! + [Tuple|clean_std_publld_3(Rest,Now)] + end; +clean_std_publld_3([],_) -> + []. +%% ----------------------------------------------------------------------------- + +%% ============================================================================= +%% Functions used as handling functions (as funs) for registered process names. +%% (Given that we use the standard priv-ld, otherwise you must do your own!). +%% ============================================================================= + +%% Call-back for initializing the meta traced functions there are quick functions +%% for. Returns a new public loop data structure. +metafunc_init(erlang,register,2,{Part1,GlobalData}) -> + {setelement(1,Part1,[]),GlobalData}. +%% ----------------------------------------------------------------------------- + +%% Call-function for erlang:register/2. +%% This function adds the call to register/2 to a standard priv-ld structure. +%% Note that we *must* search for previous entries from the same process. If such +%% still in structure it means a failed register/2 call. It must first be removed +%% so it can not be mixed up with this one. Since meta-trace message will arrive +%% in order, there was no return_from message for that call if we are here now. +local_register_call(CallingPid,{call,[Alias,Pid],TS},{Part1,GlobalData}) -> + TupleList=element(1,Part1), % The register/2 entry in a std. priv-ld. + NewTupleList=lists:keydelete(CallingPid,1,TupleList), % If present, remove previous call. + {ok, + {setelement(1,Part1,[{CallingPid,{Alias,Pid},TS}|NewTupleList]),GlobalData}, + void}. + +%% Return-function for the erlang:register/2 BIF. +%% This function formulates the output and removes the corresponding call entry +%% from the standard priv-ld structure. +local_register_return(CallingPid,{return_from,_Val,_TS},PublLD={Part1,GlobalData}) -> + TupleList=element(1,Part1), % The register/2 entry in a std. priv-ld. + case lists:keysearch(CallingPid,1,TupleList) of + {value,{_,{Alias,Pid},NowTS}} -> + NewTupleList=lists:keydelete(CallingPid,1,TupleList), + {ok, + {setelement(1,Part1,NewTupleList),GlobalData}, + term_to_binary({Pid,Alias,alias,NowTS})}; + false -> % Strange, then don't know what to do. + {ok,PublLD,void} % Do nothing seems safe. + end; +local_register_return(CallingPid,{exception_from,_Val,_TS},{Part1,GlobalData}) -> + TupleList=element(1,Part1), % The register/2 entry in a std. priv-ld. + NewTupleList=lists:keydelete(CallingPid,1,TupleList), + {ok,{setelement(1,Part1,NewTupleList),GlobalData},void}; % No association then. +local_register_return(_,_,PublLD) -> % Don't understand this. + {ok,PublLD,void}. + +%% When unregister/1 us called we simply want a unalias entry in the ti-file. +%% We can unfortunately not connect it with a certain pid. +local_unregister_call(_CallingPid,{_TypeTag,[Alias],TS},PublLD) -> + {ok,PublLD,term_to_binary({undefined,Alias,unalias,TS})}. +%% ----------------------------------------------------------------------------- + +%% Call-function for global:register_name/2,/3. +%% This function is actually the call function for the handle_call/3 in the +%% global server. Note that we must check that we only do this on the node +%% where Pid actually resides. +global_register_call(_CallingPid,{call,[{register,Alias,P,_},_,_],TS},PublLD) + when node(P)==node()-> + {ok,PublLD,term_to_binary({P,{global,Alias},alias,TS})}; +global_register_call(_CallingPid,_,PublLD) -> + {ok,PublLD,void}. + +%% Call-function for global:unregister_name. It acutally checks on the use of +%% global:delete_global_name/2 which is called when ever a global name is removed. +global_unregister_call(_CallingPid,{call,[Alias,P],TS},PublLD) when node(P)==node()-> + {ok,PublLD,term_to_binary({P,{global,Alias},unalias,TS})}; +global_unregister_call(_CallingPid,_,PublLD) -> + {ok,PublLD,void}. +%% ----------------------------------------------------------------------------- + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl new file mode 100644 index 0000000000..0f428de07a --- /dev/null +++ b/lib/runtime_tools/src/observer_backend.erl @@ -0,0 +1,320 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2009. 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% +%% +-module(observer_backend). + +%% General +-export([vsn/0]). + +%% etop stuff +-export([etop_collect/1]). +-include("observer_backend.hrl"). + +%% ttb stuff +-export([ttb_init_node/3, + ttb_write_trace_info/3, + ttb_write_binary/2, + ttb_stop/1, + ttb_fetch/2, + ttb_get_filenames/1]). +-define(CHUNKSIZE,8191). % 8 kbytes - 1 byte + +vsn() -> + case application:load(runtime_tools) of + R when R=:=ok; R=:={error,{already_loaded,runtime_tools}} -> + application:get_key(runtime_tools,vsn); + Error -> Error + end. + + + +%% +%% etop backend +%% +etop_collect(Collector) -> + ProcInfo = etop_collect(processes(), []), + Collector ! {self(),#etop_info{now = now(), + n_procs = length(ProcInfo), + run_queue = erlang:statistics(run_queue), + wall_clock = erlang:statistics(wall_clock), + runtime = erlang:statistics(runtime), + memi = etop_memi(), + procinfo = ProcInfo + }}. + +etop_memi() -> + try + [{total, c:memory(total)}, + {processes, c:memory(processes)}, + {ets, c:memory(ets)}, + {atom, c:memory(atom)}, + {code, c:memory(code)}, + {binary, c:memory(binary)}] + catch + error:notsup -> + undefined + end. + +etop_collect([P|Ps], Acc) when P =:= self() -> + etop_collect(Ps, Acc); +etop_collect([P|Ps], Acc) -> + Fs = [registered_name,initial_call,memory,reductions,current_function,message_queue_len], + case process_info(P, Fs) of + undefined -> + etop_collect(Ps, Acc); + [{registered_name,Reg},{initial_call,Initial},{memory,Mem}, + {reductions,Reds},{current_function,Current},{message_queue_len,Qlen}] -> + Name = case Reg of + [] -> Initial; + _ -> Reg + end, + Info = #etop_proc_info{pid=P,mem=Mem,reds=Reds,name=Name, + cf=Current,mq=Qlen}, + etop_collect(Ps, [Info|Acc]) + end; +etop_collect([], Acc) -> Acc. + +%% +%% ttb backend +%% +ttb_init_node(MetaFile,PI,Traci) -> + if + is_list(MetaFile); + is_atom(MetaFile) -> + file:delete(MetaFile); + true -> % {local,_,_} + ok + end, + Self = self(), + MetaPid = spawn(fun() -> ttb_meta_tracer(MetaFile,PI,Self) end), + receive {MetaPid,started} -> ok end, + MetaPid ! {metadata,Traci}, + case PI of + true -> + Proci = pnames(), + MetaPid ! {metadata,Proci}; + false -> + ok + end, + {ok,MetaPid}. + +ttb_write_trace_info(MetaPid,Key,What) -> + MetaPid ! {metadata,Key,What}, + ok. + +ttb_meta_tracer(MetaFile,PI,Parent) -> + case PI of + true -> + ReturnMS = [{'_',[],[{return_trace}]}], + erlang:trace_pattern({erlang,spawn,3},ReturnMS,[meta]), + erlang:trace_pattern({erlang,spawn_link,3},ReturnMS,[meta]), + erlang:trace_pattern({erlang,spawn_opt,1},ReturnMS,[meta]), + erlang:trace_pattern({erlang,register,2},[],[meta]), + erlang:trace_pattern({global,register_name,2},[],[meta]); + false -> + ok + end, + Parent ! {self(),started}, + ttb_meta_tracer_loop(MetaFile,PI,dict:new()). + +ttb_meta_tracer_loop(MetaFile,PI,Acc) -> + receive + {trace_ts,_,call,{erlang,register,[Name,Pid]},_} -> + ttb_store_meta({pid,{Pid,Name}},MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + {trace_ts,_,call,{global,register_name,[Name,Pid]},_} -> + ttb_store_meta({pid,{Pid,{global,Name}}},MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + {trace_ts,CallingPid,call,{erlang,spawn_opt,[{M,F,Args,_}]},_} -> + MFA = {M,F,length(Args)}, + NewAcc = dict:update(CallingPid, + fun(Old) -> [MFA|Old] end, [MFA], + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + {trace_ts,CallingPid,return_from,{erlang,spawn_opt,_Arity},Ret,_} -> + case Ret of + {NewPid,_Mref} when is_pid(NewPid) -> ok; + NewPid when is_pid(NewPid) -> ok + end, + NewAcc = + dict:update(CallingPid, + fun([H|T]) -> + ttb_store_meta({pid,{NewPid,H}},MetaFile), + T + end, + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + {trace_ts,CallingPid,call,{erlang,Spawn,[M,F,Args]},_} + when Spawn==spawn;Spawn==spawn_link -> + MFA = {M,F,length(Args)}, + NewAcc = dict:update(CallingPid, + fun(Old) -> [MFA|Old] end, [MFA], + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + + {trace_ts,CallingPid,return_from,{erlang,Spawn,_Arity},NewPid,_} + when Spawn==spawn;Spawn==spawn_link -> + NewAcc = + dict:update(CallingPid, + fun([H|T]) -> + ttb_store_meta({pid,{NewPid,H}},MetaFile), + T + end, + Acc), + ttb_meta_tracer_loop(MetaFile,PI,NewAcc); + + {metadata,Data} when is_list(Data) -> + ttb_store_meta(Data,MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + + {metadata,Key,Fun} when is_function(Fun) -> + ttb_store_meta([{Key,Fun()}],MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + + {metadata,Key,What} -> + ttb_store_meta([{Key,What}],MetaFile), + ttb_meta_tracer_loop(MetaFile,PI,Acc); + + stop when PI=:=true -> + erlang:trace_pattern({erlang,spawn,3},false,[meta]), + erlang:trace_pattern({erlang,spawn_link,3},false,[meta]), + erlang:trace_pattern({erlang,spawn_opt,1},false,[meta]), + erlang:trace_pattern({erlang,register,2},false,[meta]), + erlang:trace_pattern({global,register_name,2},false,[meta]); + stop -> + ok + end. + +pnames() -> + Processes = processes(), + Globals = lists:map(fun(G) -> {global:whereis_name(G),G} end, + global:registered_names()), + lists:flatten(lists:foldl(fun(Pid,Acc) -> [pinfo(Pid,Globals)|Acc] end, + [], Processes)). + +pinfo(P,Globals) -> + case process_info(P,registered_name) of + [] -> + case lists:keysearch(P,1,Globals) of + {value,{P,G}} -> {pid,{P,{global,G}}}; + false -> + case process_info(P,initial_call) of + {_,I} -> {pid,{P,I}}; + undefined -> [] % the process has terminated + end + end; + {_,R} -> {pid,{P,R}}; + undefined -> [] % the process has terminated + end. + + +ttb_store_meta(Data,{local,MetaFile,Port}) when is_list(Data) -> + ttb_send_to_port(Port,MetaFile,Data); +ttb_store_meta(Data,MetaFile) when is_list(Data) -> + {ok,Fd} = file:open(MetaFile,[raw,append]), + ttb_write_binary(Fd,Data), + file:close(Fd); +ttb_store_meta(Data,MetaFile) -> + ttb_store_meta([Data],MetaFile). + +ttb_write_binary(Fd,[H|T]) -> + file:write(Fd,ttb_make_binary(H)), + ttb_write_binary(Fd,T); +ttb_write_binary(_Fd,[]) -> + ok. + +ttb_send_to_port(Port,MetaFile,[H|T]) -> + B1 = ttb_make_binary(H), + B2 = term_to_binary({metadata,MetaFile,B1}), + erlang:port_command(Port,B2), + ttb_send_to_port(Port,MetaFile,T); +ttb_send_to_port(_Port,_MetaFile,[]) -> + ok. + +ttb_make_binary(Term) -> + B = term_to_binary(Term), + SizeB = byte_size(B), + if SizeB > 255 -> + %% size is bigger than 8 bits, must therefore add an extra + %% size field + SB = term_to_binary({'$size',SizeB}), + <<(byte_size(SB)):8, SB/binary, B/binary>>; + true -> + <<SizeB:8, B/binary>> + end. + + +%% Stop ttb +ttb_stop(MetaPid) -> + Delivered = erlang:trace_delivered(all), + receive + {trace_delivered,all,Delivered} -> ok + end, + Ref = erlang:monitor(process,MetaPid), + MetaPid ! stop, + + %% Must wait for the process to terminate there + %% because dbg will be stopped when this function + %% returns, and then the Port (in {local,MetaFile,Port}) + %% cannot be accessed any more. + receive {'DOWN', Ref, process, MetaPid, _Info} -> ok end, + seq_trace:reset_trace(), + seq_trace:set_system_tracer(false). + +%% Fetch ttb logs from remote node +ttb_fetch(MetaFile,{Port,Host}) -> + erlang:process_flag(priority,low), + Files = ttb_get_filenames(MetaFile), + {ok, Sock} = gen_tcp:connect(Host, Port, [binary, {packet, 2}]), + send_files({Sock,Host},Files), + ok = gen_tcp:close(Sock). + + +send_files({Sock,Host},[File|Files]) -> + {ok,Fd} = file:open(File,[raw,read,binary]), + gen_tcp:send(Sock,<<1,(list_to_binary(File))/binary>>), + send_chunks(Sock,Fd), + file:delete(File), + send_files({Sock,Host},Files); +send_files({_Sock,_Host},[]) -> + done. + +send_chunks(Sock,Fd) -> + case file:read(Fd,?CHUNKSIZE) of + {ok,Bin} -> + ok = gen_tcp:send(Sock, <<0,Bin/binary>>), + send_chunks(Sock,Fd); + eof -> + ok; + {error,Reason} -> + ok = gen_tcp:send(Sock, <<2,(term_to_binary(Reason))/binary>>) + end. + +ttb_get_filenames(MetaFile) -> + Dir = filename:dirname(MetaFile), + Root = filename:rootname(filename:basename(MetaFile)), + {ok,List} = file:list_dir(Dir), + match_filenames(Dir,Root,List,[]). + +match_filenames(Dir,MetaFile,[H|T],Files) -> + case lists:prefix(MetaFile,H) of + true -> match_filenames(Dir,MetaFile,T,[filename:join(Dir,H)|Files]); + false -> match_filenames(Dir,MetaFile,T,Files) + end; +match_filenames(_Dir,_MetaFile,[],Files) -> + Files. diff --git a/lib/runtime_tools/src/percept_profile.erl b/lib/runtime_tools/src/percept_profile.erl new file mode 100644 index 0000000000..b333dee0cf --- /dev/null +++ b/lib/runtime_tools/src/percept_profile.erl @@ -0,0 +1,196 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. 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% +%% + +%% +%% @doc Percept Collector +%% +%% This module provides the user interface for the percept data +% collection (profiling). +%% + +-module(percept_profile). +-export([ + start/1, + start/2, + start/3, + stop/0 + ]). + + +%%========================================================================== +%% +%% Type definitions +%% +%%========================================================================== + +%% @type percept_option() = procs | ports | exclusive + +-type(percept_option() :: 'procs' | 'ports' | 'exclusive' | 'scheduler'). + +%%========================================================================== +%% +%% Interface functions +%% +%%========================================================================== + +%% @spec start(Filename::string()) -> {ok, Port} | {already_started, Port} +%% @equiv start(Filename, [procs]) + +-spec(start/1 :: (Filename :: string()) -> + {'ok', port()} | {'already_started', port()}). + +start(Filename) -> + profile_to_file(Filename, [procs]). + +%% @spec start(Filename::string(), [percept_option()]) -> {ok, Port} | {already_started, Port} +%% Port = port() +%% @doc Starts profiling with supplied options. +%% All events are stored in the file given by Filename. +%% An explicit call to stop/0 is needed to stop profiling. + +-spec(start/2 :: ( + Filename :: string(), + Options :: [percept_option()]) -> + {'ok', port()} | {'already_started', port()}). + +start(Filename, Options) -> + profile_to_file(Filename, Options). + +%% @spec start(string(), MFA::mfa(), [percept_option()]) -> ok | {already_started, Port} | {error, not_started} +%% Port = port() +%% @doc Starts profiling at the entrypoint specified by the MFA. All events are collected, +%% this means that processes outside the scope of the entry-point are also profiled. +%% No explicit call to stop/0 is needed, the profiling stops when +%% the entry function returns. + +-spec(start/3 :: ( + Filename :: string(), + Entry :: {atom(), atom(), list()}, + Options :: [percept_option()]) -> + 'ok' | {'already_started', port()} | {'error', 'not_started'}). + +start(Filename, {Module, Function, Args}, Options) -> + case whereis(percept_port) of + undefined -> + profile_to_file(Filename, Options), + erlang:apply(Module, Function, Args), + stop(); + Port -> + {already_started, Port} + end. + +deliver_all_trace() -> + Tracee = self(), + Tracer = spawn(fun() -> + receive {Tracee, start} -> ok end, + Ref = erlang:trace_delivered(Tracee), + receive {trace_delivered, Tracee, Ref} -> Tracee ! {self(), ok} end + end), + erlang:trace(Tracee, true, [procs, {tracer, Tracer}]), + Tracer ! {Tracee, start}, + receive {Tracer, ok} -> ok end, + erlang:trace(Tracee, false, [procs]), + ok. +-spec(stop/0 :: () -> 'ok' | {'error', 'not_started'}). + +%% @spec stop() -> ok | {'error', 'not_started'} +%% @doc Stops profiling. + + +stop() -> + erlang:system_profile(undefined, [runnable_ports, runnable_procs]), + erlang:trace(all, false, [procs, ports, timestamp]), + deliver_all_trace(), + case whereis(percept_port) of + undefined -> + {error, not_started}; + Port -> + erlang:port_command(Port, erlang:term_to_binary({profile_stop, erlang:now()})), + %% trace delivered? + erlang:port_close(Port), + ok + end. + +%%========================================================================== +%% +%% Auxiliary functions +%% +%%========================================================================== + +profile_to_file(Filename, Opts) -> + case whereis(percept_port) of + undefined -> + io:format("Starting profiling.~n", []), + + erlang:system_flag(multi_scheduling, block), + Port = (dbg:trace_port(file, Filename))(), + % Send start time + erlang:port_command(Port, erlang:term_to_binary({profile_start, erlang:now()})), + erlang:system_flag(multi_scheduling, unblock), + + %% Register Port + erlang:register(percept_port, Port), + set_tracer(Port, Opts), + {ok, Port}; + Port -> + io:format("Profiling already started at port ~p.~n", [Port]), + {already_started, Port} + end. + +%% set_tracer + +set_tracer(Port, Opts) -> + {TOpts, POpts} = parse_profile_options(Opts), + % Setup profiling and tracing + erlang:trace(all, true, [{tracer, Port}, timestamp | TOpts]), + erlang:system_profile(Port, POpts). + +%% parse_profile_options + +parse_profile_options(Opts) -> + parse_profile_options(Opts, {[],[]}). + +parse_profile_options([], Out) -> + Out; +parse_profile_options([Opt|Opts], {TOpts, POpts}) -> + case Opt of + procs -> + parse_profile_options(Opts, { + [procs | TOpts], + [runnable_procs | POpts] + }); + ports -> + parse_profile_options(Opts, { + [ports | TOpts], + [runnable_ports | POpts] + }); + scheduler -> + parse_profile_options(Opts, { + TOpts, + [scheduler | POpts] + }); + exclusive -> + parse_profile_options(Opts, { + TOpts, + [exclusive | POpts] + }); + _ -> + parse_profile_options(Opts, {TOpts, POpts}) + + end. diff --git a/lib/runtime_tools/src/runtime_tools.app.src b/lib/runtime_tools/src/runtime_tools.app.src new file mode 100644 index 0000000000..e6dc7a21d4 --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools.app.src @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. 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% +%% +{application, runtime_tools, + [{description, "RUNTIME_TOOLS version 1"}, + {vsn, "%VSN%"}, + {modules, [dbg,observer_backend,percept_profile, + inviso_rt,inviso_rt_lib,inviso_rt_meta, + inviso_as_lib,inviso_autostart,inviso_autostart_server, + runtime_tools,runtime_tools_sup,erts_alloc_config]}, + {registered, [runtime_tools_sup,inviso_rt,inviso_rt_meta]}, + {applications, [kernel, stdlib]}, +% {env, [{inviso_autostart_mod,your_own_autostart_module}]}, + {env, []}, + {mod, {runtime_tools, []}}]}. + + diff --git a/lib/runtime_tools/src/runtime_tools.appup.src b/lib/runtime_tools/src/runtime_tools.appup.src new file mode 100644 index 0000000000..7a435e9b22 --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools.appup.src @@ -0,0 +1,19 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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% +%% +{"%VSN%",[],[]}. diff --git a/lib/runtime_tools/src/runtime_tools.erl b/lib/runtime_tools/src/runtime_tools.erl new file mode 100644 index 0000000000..2181244610 --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools.erl @@ -0,0 +1,49 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2009. 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: Callback module for the runtime_tools application. +%% ---------------------------------------------------------------------------- + +-module(runtime_tools). +-behaviour(application). + +-export([start/2,stop/1]). + + +%% ----------------------------------------------------------------------------- +%% Callback functions for the runtime_tools application +%% ----------------------------------------------------------------------------- +start(_,AutoModArgs) -> + case supervisor:start_link({local,runtime_tools_sup}, + runtime_tools_sup, + AutoModArgs) of + {ok, Pid} -> + {ok, Pid, []}; + Error -> + Error + end. + +stop(_) -> + ok. +%% ----------------------------------------------------------------------------- + + + + + + diff --git a/lib/runtime_tools/src/runtime_tools_sup.erl b/lib/runtime_tools/src/runtime_tools_sup.erl new file mode 100644 index 0000000000..1a872c355d --- /dev/null +++ b/lib/runtime_tools/src/runtime_tools_sup.erl @@ -0,0 +1,43 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. 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% +%% +%% ------------------------------------------------------------------------------ +%% File : runtime_tools_sup.erl +%% Author : Lennart �hman <[email protected]> + +-module(runtime_tools_sup). +-behaviour(supervisor). + +-export([init/1]). + + +%% ============================================================================= +%% Callback functions for the runtime_tools_sup supervisor +%% ============================================================================= + +%% The runtime tools top most supervisor starts: +%% -The inviso runtime component. This is the only way to get the runtime component +%% started automatically (if for instance autostart is wanted). +%% Note that it is not impossible that the runtime component terminates it self +%% should it discover that no autostart is configured. +init(AutoModArgs) -> + Flags = {one_for_one, 0, 3600}, + Children = [{inviso_rt, {inviso_rt, start_link_auto, [AutoModArgs]}, + temporary, 3000, worker, [inviso_rt]}], + {ok, {Flags, Children}}. +%% ----------------------------------------------------------------------------- |