aboutsummaryrefslogtreecommitdiffstats
path: root/lib/runtime_tools/src
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/runtime_tools/src
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/runtime_tools/src')
-rw-r--r--lib/runtime_tools/src/Makefile109
-rw-r--r--lib/runtime_tools/src/dbg.erl1749
-rw-r--r--lib/runtime_tools/src/erts_alloc_config.erl670
-rw-r--r--lib/runtime_tools/src/inviso_as_lib.erl155
-rw-r--r--lib/runtime_tools/src/inviso_autostart.erl208
-rw-r--r--lib/runtime_tools/src/inviso_autostart_server.erl311
-rw-r--r--lib/runtime_tools/src/inviso_rt.erl2895
-rw-r--r--lib/runtime_tools/src/inviso_rt_lib.erl474
-rw-r--r--lib/runtime_tools/src/inviso_rt_meta.erl1207
-rw-r--r--lib/runtime_tools/src/observer_backend.erl320
-rw-r--r--lib/runtime_tools/src/percept_profile.erl196
-rw-r--r--lib/runtime_tools/src/runtime_tools.app.src32
-rw-r--r--lib/runtime_tools/src/runtime_tools.appup.src19
-rw-r--r--lib/runtime_tools/src/runtime_tools.erl49
-rw-r--r--lib/runtime_tools/src/runtime_tools_sup.erl43
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}}.
+%% -----------------------------------------------------------------------------