aboutsummaryrefslogtreecommitdiffstats
path: root/lib/runtime_tools/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/runtime_tools/src')
-rw-r--r--lib/runtime_tools/src/Makefile7
-rw-r--r--lib/runtime_tools/src/appmon_info.erl11
-rw-r--r--lib/runtime_tools/src/dbg.erl373
-rw-r--r--lib/runtime_tools/src/dyntrace.erl75
-rw-r--r--lib/runtime_tools/src/erts_alloc_config.erl18
-rw-r--r--lib/runtime_tools/src/msacc.erl355
-rw-r--r--lib/runtime_tools/src/observer_backend.erl161
-rw-r--r--lib/runtime_tools/src/percept_profile.erl194
-rw-r--r--lib/runtime_tools/src/runtime_tools.app.src15
-rw-r--r--lib/runtime_tools/src/runtime_tools.appup.src2
-rw-r--r--lib/runtime_tools/src/runtime_tools.erl2
-rw-r--r--lib/runtime_tools/src/runtime_tools_sup.erl2
-rw-r--r--lib/runtime_tools/src/system_information.erl295
13 files changed, 1044 insertions, 466 deletions
diff --git a/lib/runtime_tools/src/Makefile b/lib/runtime_tools/src/Makefile
index 99b90f9ec5..5a99c6e240 100644
--- a/lib/runtime_tools/src/Makefile
+++ b/lib/runtime_tools/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2013. All Rights Reserved.
+# Copyright Ericsson AB 1999-2017. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -42,10 +42,11 @@ MODULES= \
runtime_tools_sup \
dbg \
dyntrace \
- percept_profile \
system_information \
observer_backend \
- ttb_autostart
+ ttb_autostart\
+ msacc
+
HRL_FILES= ../include/observer_backend.hrl
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/runtime_tools/src/appmon_info.erl b/lib/runtime_tools/src/appmon_info.erl
index dd4dc34fcb..b5500085a3 100644
--- a/lib/runtime_tools/src/appmon_info.erl
+++ b/lib/runtime_tools/src/appmon_info.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -307,9 +307,10 @@ do_work(Key, State) ->
{Cmd, Aux, From, _OldRef, Old, Opts} = retrieve(WorkStore, Key),
{ok, Result} = do_work2(Cmd, Aux, From, Old, Opts),
if
- Result==Old -> ok;
- true ->
- From ! {delivery, self(), Cmd, Aux, Result}
+ Result==Old -> ok;
+ true ->
+ From ! {delivery, self(), Cmd, Aux, Result},
+ ok
end,
case get_opt(timeout, Opts) of
at_most_once ->
@@ -393,7 +394,7 @@ del_task(Key, WorkStore) ->
{_Cmd, _Aux, _From, Ref, _Old, Opts} ->
if
Ref /= nil ->
- timer:cancel(Ref),
+ {ok,_} = timer:cancel(Ref),
receive
{do_it, Key} ->
Opts
diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl
index d2a7d734c1..92938ed5c1 100644
--- a/lib/runtime_tools/src/dbg.erl
+++ b/lib/runtime_tools/src/dbg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
-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,
+ tpe/2, ctpe/1,
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]).
@@ -49,7 +50,8 @@ fun2ms(ShellFun) when is_function(ShellFun) ->
case ms_transform:transform_from_shell(
?MODULE,Clauses,ImportList) of
{error,[{_,[{_,_,Code}|_]}|_],_} ->
- io:format("Error: ~s~n",
+ Modifier = modifier(),
+ io:format("Error: ~"++Modifier++"s~n",
[ms_transform:format_error(Code)]),
{error,transform_error};
Else ->
@@ -128,7 +130,12 @@ 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)
+
+tpe(Event, Pattern) when Event =:= send;
+ Event =:= 'receive' ->
+ do_tp(Event, Pattern, []).
+
+do_tp(X, Pattern, Flags)
when is_integer(Pattern);
is_atom(Pattern) ->
case ets:lookup(get_pattern_table(), Pattern) of
@@ -137,17 +144,16 @@ do_tp({_Module, _Function, _Arity} = X, Pattern, Flags)
_ ->
{error, unknown_pattern}
end;
-do_tp({Module, _Function, _Arity} = X, Pattern, Flags) when is_list(Pattern) ->
+do_tp(X, Pattern, Flags) when is_list(Pattern) ->
Nodes = req(get_nodes),
- case Module of
- '_' ->
- ok;
- M when is_atom(M) ->
+ case X of
+ {M,_,_} when is_atom(M) ->
%% Try to load M on all nodes
lists:foreach(fun(Node) ->
rpc:call(Node, M, module_info, [])
end,
- Nodes)
+ Nodes);
+ _ -> ok
end,
case lint_tp(Pattern) of
{ok,_} ->
@@ -163,9 +169,9 @@ do_tp({Module, _Function, _Arity} = X, Pattern, Flags) when is_list(Pattern) ->
end.
%% All nodes are handled the same way - also the local node if it is traced
-do_tp_on_nodes(Nodes, MFA, P, Flags) ->
+do_tp_on_nodes(Nodes, X, P, Flags) ->
lists:map(fun(Node) ->
- case rpc:call(Node,erlang,trace_pattern,[MFA,P, Flags]) of
+ case rpc:call(Node,erlang,trace_pattern,[X,P, Flags]) of
N when is_integer(N) ->
{matched, Node, N};
Else ->
@@ -210,20 +216,28 @@ 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]),
+ {ok,_} = 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)}.
+ctpe(Event) when Event =:= send;
+ Event =:= 'receive' ->
+ Nodes = req(get_nodes),
+ {ok,do_tp_on_nodes(Nodes,Event,true,[])}.
+
%%
%% ltp() -> ok
%% List saved and built-in trace patterns.
%%
ltp() ->
+ Modifier = modifier(),
+ Format = "~p: ~"++Modifier++"p~n",
pt_doforall(fun({X, El},_Ignore) ->
- io:format("~p: ~p~n", [X,El])
+ io:format(Format, [X,El])
end,[]).
%%
@@ -250,18 +264,18 @@ dtp(_) ->
%%
%% Actually write the built-in trace patterns too.
wtp(FileName) ->
- case file:open(FileName,[write]) of
+ case file:open(FileName,[write,{encoding,utf8}]) of
{error, Reason} ->
{error, Reason};
{ok, File} ->
+ io:format(File, "%% ~s\n", [epp:encoding_to_string(utf8)]),
pt_doforall(fun ({_, Val}, _) when is_list(Val) ->
- io:format(File, "~p.~n", [Val]);
+ io:format(File, "~tp.~n", [Val]);
({_, _}, _) ->
ok
end,
[]),
- file:close(File),
- ok
+ ok = file:close(File)
end.
%%
@@ -298,7 +312,12 @@ tracer(port, Port) when is_port(Port) ->
start(fun() -> Port end);
tracer(process, {Handler,HandlerData}) ->
- start(fun() -> start_tracer_process(Handler, HandlerData) end).
+ start(fun() -> start_tracer_process(Handler, HandlerData) end);
+
+tracer(module, Fun) when is_function(Fun) ->
+ start(Fun);
+tracer(module, {Module, State}) ->
+ start(fun() -> {Module, State} end).
remote_tracer(port, Fun) when is_function(Fun) ->
@@ -308,7 +327,13 @@ 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(fun() -> start_tracer_process(Handler, HandlerData) end);
+
+remote_tracer(module, Fun) when is_function(Fun) ->
+ remote_start(Fun);
+remote_tracer(module, {Module, State}) ->
+ remote_start(fun() -> {Module, State} end).
+
remote_start(StartTracer) ->
case (catch StartTracer()) of
@@ -406,7 +431,7 @@ 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,200});
trace_port(ip, {Portno, Qsiz}) when is_integer(Portno), is_integer(Qsiz) ->
fun() ->
@@ -543,9 +568,8 @@ c(M, F, A, Flags) ->
{error,Reason} -> {error,Reason};
Flags1 ->
tracer(),
- {ok, Tracer} = get_tracer(),
S = self(),
- Pid = spawn(fun() -> c(S, M, F, A, [{tracer, Tracer} | Flags1]) end),
+ Pid = spawn(fun() -> c(S, M, F, A, [get_tracer_flag() | Flags1]) end),
Mref = erlang:monitor(process, Pid),
receive
{'DOWN', Mref, _, _, Reason} ->
@@ -579,7 +603,7 @@ stop() ->
end.
stop_clear() ->
- ctp(),
+ {ok, _} = ctp(),
stop().
%%% Calling the server.
@@ -647,7 +671,9 @@ init(Parent) ->
loop({C,T}=SurviveLinks, Table) ->
receive
{From,i} ->
- reply(From, display_info(lists:map(fun({N,_}) -> N end,get()))),
+ Modifier = modifier(),
+ Reply = display_info(lists:map(fun({N,_}) -> N end,get()), Modifier),
+ reply(From, Reply),
loop(SurviveLinks, Table);
{From,{p,Pid,Flags}} ->
reply(From, trace_process(Pid, Flags)),
@@ -660,6 +686,9 @@ loop({C,T}=SurviveLinks, Table) ->
reply(From, {error, Reason});
Tracer when is_pid(Tracer); is_port(Tracer) ->
put(node(),{self(),Tracer}),
+ reply(From, {ok,self()});
+ {Module, _State} = Tracer when is_atom(Module) ->
+ put(node(),{self(),Tracer}),
reply(From, {ok,self()})
end;
{_Relay,_Tracer} ->
@@ -710,6 +739,9 @@ loop({C,T}=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_tuple(Tracer) ->
+ reply(From, {error, cant_trace_remote_pid_to_local_module}),
+ loop(SurviveLinks, Table);
{_LocalRelay,Tracer} when is_pid(Tracer) ->
case (catch relay(Node, Tracer)) of
{ok,Relay} ->
@@ -747,7 +779,10 @@ loop({C,T}=SurviveLinks, Table) ->
C ->
case lists:delete(Pid,T) of
T ->
- io:format(user,"** dbg got EXIT - terminating: ~p~n",
+ Modifier = modifier(user),
+ io:format(user,
+ "** dbg got EXIT - terminating: ~"++
+ Modifier++"p~n",
[Reason]),
exit(done);
NewT ->
@@ -758,13 +793,15 @@ loop({C,T}=SurviveLinks, Table) ->
loop({NewC,T}, Table)
end;
Other ->
- io:format(user,"** dbg got garbage: ~p~n",
+ Modifier = modifier(user),
+ io:format(user,"** dbg got garbage: ~"++Modifier++"p~n",
[{Other,SurviveLinks,Table}]),
loop(SurviveLinks, Table)
end.
reply(Pid, Reply) ->
- Pid ! {dbg,Reply}.
+ Pid ! {dbg,Reply},
+ ok.
%%% A process-based tracer.
@@ -809,7 +846,9 @@ recv_all_traces(Suspended0, Traces, Timeout) ->
{done, Suspended0, Traces};
Other ->
%%% Is this really a good idea?
- io:format(user,"** tracer received garbage: ~p~n", [Other]),
+ Modifier = modifier(user),
+ io:format(user,"** tracer received garbage: ~"++Modifier++"p~n",
+ [Other]),
recv_all_traces(Suspended0, Traces, Timeout)
after Timeout ->
{loop, Suspended0, Traces}
@@ -879,9 +918,9 @@ trac(Proc, How, Flags) ->
end
end.
-trac(Node, {_Relay, Tracer}, AtomPid, How, Flags) ->
+trac(Node, {_Replay, Tracer}, AtomPid, How, Flags) ->
case rpc:call(Node, ?MODULE, erlang_trace,
- [AtomPid, How, [{tracer, Tracer} | Flags]]) of
+ [AtomPid, How, [get_tracer_flag(Tracer) | Flags]]) of
N when is_integer(N) ->
{matched, Node, N};
{badrpc,Reason} ->
@@ -917,9 +956,11 @@ do_relay(Parent,RelP) ->
case RelP of
{Type,Data} ->
{ok,Tracer} = remote_tracer(Type,Data),
- Parent ! {started,Tracer};
+ Parent ! {started,Tracer},
+ ok;
Pid when is_pid(Pid) ->
- Parent ! {started,self()}
+ Parent ! {started,self()},
+ ok
end,
do_relay_1(RelP).
@@ -933,165 +974,224 @@ do_relay_1(RelP) ->
RelP ! TraceInfo,
do_relay_1(RelP);
Other ->
- io:format(user,"** relay got garbage: ~p~n", [Other]),
+ Modifier = modifier(user),
+ io:format(user,"** relay got garbage: ~"++Modifier++"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);
+ dhandler1(Trace, tuple_size(Trace), out(Out));
dhandler(Trace, Out) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 ->
- dhandler1(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace), Out);
+ dhandler1(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace)
+ , out(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 ->
+ {Device,Modifier} = out(Out),
+ io:format(Device, "*** Dropped ~p messages.~n", [element(2,Trace)]),
+ {Device,Modifier};
+dhandler(Trace, Out) when element(1, Trace) == seq_trace,
+ tuple_size(Trace) >= 3 ->
+ {Device,Modifier} = out(Out),
SeqTraceInfo = case Trace of
{seq_trace, Lbl, STI, TS} ->
- io:format(Out, "SeqTrace ~p [~p]: ",
+ io:format(Device, "SeqTrace ~p [~p]: ",
[TS, Lbl]),
STI;
{seq_trace, Lbl, STI} ->
- io:format(Out, "SeqTrace [~p]: ",
+ io:format(Device, "SeqTrace [~p]: ",
[Lbl]),
STI
end,
case SeqTraceInfo of
{send, Ser, Fr, To, Mes} ->
- io:format(Out, "(~p) ~p ! ~p [Serial: ~p]~n",
+ io:format(Device, "(~p) ~p ! ~"++Modifier++"p [Serial: ~p]~n",
[Fr, To, Mes, Ser]);
{'receive', Ser, Fr, To, Mes} ->
- io:format(Out, "(~p) << ~p [Serial: ~p, From: ~p]~n",
+ io:format(Device, "(~p) << ~"++Modifier++"p [Serial: ~p, From: ~p]~n",
[To, Mes, Ser, Fr]);
{print, Ser, Fr, _, Info} ->
- io:format(Out, "-> ~p [Serial: ~p, From: ~p]~n",
+ io:format(Device, "-> ~"++Modifier++"p [Serial: ~p, From: ~p]~n",
[Info, Ser, Fr]);
Else ->
- io:format(Out, "~p~n", [Else])
+ io:format(Device, "~"++Modifier++"p~n", [Else])
end,
- Out;
+ {Device,Modifier};
dhandler(_Trace, Out) ->
Out.
-dhandler1(Trace, Size, Out) ->
+dhandler1(Trace, Size, {Device,Modifier}) ->
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])
+ io:format(Device, "(~p) << ~"++Modifier++"p~n",
+ [From,Message])
end;
'send' ->
Message = element(4, Trace),
To = element(5, Trace),
- io:format(Out, "(~p) ~p ! ~p~n", [From,To,Message]);
+ io:format(Device, "(~p) ~p ! ~"++Modifier++"p~n", [From,To,Message]);
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]);
+ io:format(Device,
+ "(~p) call ~"++Modifier++"s (~"++Modifier++"p)~n",
+ [From,ffunc(MFA,Modifier),Message]);
MFA ->
- io:format(Out, "(~p) call ~s~n", [From,ffunc(MFA)])
+ io:format(Device, "(~p) call ~"++Modifier++"s~n",
+ [From,ffunc(MFA,Modifier)])
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]);
+ io:format(Device,
+ "(~p) old_ret ~"++Modifier++"s -> ~"++Modifier++
+ "p~n",
+ [From,ffunc(MFA,Modifier),Ret]);
MFA ->
- io:format(Out, "(~p) old_ret ~s~n", [From,ffunc(MFA)])
+ io:format(Device, "(~p) old_ret ~"++Modifier++"s~n",
+ [From,ffunc(MFA,Modifier)])
end;
return_from ->
MFA = element(4, Trace),
Ret = element(5, Trace),
- io:format(Out, "(~p) returned from ~s -> ~p~n", [From,ffunc(MFA),Ret]);
+ io:format(Device,
+ "(~p) returned from ~"++Modifier++"s -> ~"++Modifier++"p~n",
+ [From,ffunc(MFA,Modifier),Ret]);
return_to ->
MFA = element(4, Trace),
- io:format(Out, "(~p) returning to ~s~n", [From,ffunc(MFA)]);
+ io:format(Device, "(~p) returning to ~"++Modifier++"s~n",
+ [From,ffunc(MFA,Modifier)]);
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)]);
+ io:format(Device, "(~p) spawn ~p as ~"++Modifier++"s~n",
+ [From,Pid,ffunc(MFA,Modifier)]);
Op ->
- io:format(Out, "(~p) ~p ~s~n", [From,Op,ftup(Trace,4,Size)])
+ io:format(Device, "(~p) ~p ~"++Modifier++"s~n",
+ [From,Op,ftup(Trace,4,Size,Modifier)])
end,
- Out.
+ {Device,Modifier}.
-dhandler1(Trace, Size, TS, Out) ->
+dhandler1(Trace, Size, TS, {Device,Modifier}) ->
From = element(2, Trace),
case element(3, Trace) of
'receive' ->
case element(4, Trace) of
{dbg,ok} -> ok;
Message ->
- io:format(Out, "(~p) << ~p (Timestamp: ~p)~n", [From,Message,TS])
+ io:format(Device,
+ "(~p) << ~"++Modifier++"p (Timestamp: ~p)~n",
+ [From,Message,TS])
end;
'send' ->
Message = element(4, Trace),
To = element(5, Trace),
- io:format(Out, "(~p) ~p ! ~p (Timestamp: ~p)~n", [From,To,Message,TS]);
+ io:format(Device, "(~p) ~p ! ~"++Modifier++"p (Timestamp: ~p)~n",
+ [From,To,Message,TS]);
call ->
case element(4, Trace) of
MFA when Size == 5 ->
Message = element(5, Trace),
- io:format(Out, "(~p) call ~s (~p) (Timestamp: ~p)~n", [From,ffunc(MFA),Message,TS]);
+ io:format(Device,
+ "(~p) call ~"++Modifier++"s (~"++Modifier++
+ "p) (Timestamp: ~p)~n",
+ [From,ffunc(MFA,Modifier),Message,TS]);
MFA ->
- io:format(Out, "(~p) call ~s (Timestamp: ~p)~n", [From,ffunc(MFA),TS])
+ io:format(Device,
+ "(~p) call ~"++Modifier++"s (Timestamp: ~p)~n",
+ [From,ffunc(MFA,Modifier),TS])
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 (Timestamp: ~p)~n", [From,ffunc(MFA),Ret,TS]);
+ io:format(Device,
+ "(~p) old_ret ~"++Modifier++"s -> ~"++Modifier++
+ "p (Timestamp: ~p)~n",
+ [From,ffunc(MFA,Modifier),Ret,TS]);
MFA ->
- io:format(Out, "(~p) old_ret ~s (Timestamp: ~p)~n", [From,ffunc(MFA),TS])
+ io:format(Device,
+ "(~p) old_ret ~"++Modifier++"s (Timestamp: ~p)~n",
+ [From,ffunc(MFA,Modifier),TS])
end;
return_from ->
MFA = element(4, Trace),
Ret = element(5, Trace),
- io:format(Out, "(~p) returned from ~s -> ~p (Timestamp: ~p)~n", [From,ffunc(MFA),Ret,TS]);
+ io:format(Device,
+ "(~p) returned from ~"++Modifier++"s -> ~"++Modifier++
+ "p (Timestamp: ~p)~n",
+ [From,ffunc(MFA,Modifier),Ret,TS]);
return_to ->
MFA = element(4, Trace),
- io:format(Out, "(~p) returning to ~s (Timestamp: ~p)~n", [From,ffunc(MFA),TS]);
+ io:format(Device,
+ "(~p) returning to ~"++Modifier++"s (Timestamp: ~p)~n",
+ [From,ffunc(MFA,Modifier),TS]);
spawn when Size == 5 ->
Pid = element(4, Trace),
MFA = element(5, Trace),
- io:format(Out, "(~p) spawn ~p as ~s (Timestamp: ~p)~n", [From,Pid,ffunc(MFA),TS]);
+ io:format(Device,
+ "(~p) spawn ~p as ~"++Modifier++"s (Timestamp: ~p)~n",
+ [From,Pid,ffunc(MFA,Modifier),TS]);
Op ->
- io:format(Out, "(~p) ~p ~s (Timestamp: ~p)~n", [From,Op,ftup(Trace,4,Size),TS])
+ io:format(Device, "(~p) ~p ~"++Modifier++"s (Timestamp: ~p)~n",
+ [From,Op,ftup(Trace,4,Size,Modifier),TS])
end,
- Out.
-
-
+ {Device,Modifier}.
%%% 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]).
+ffunc({M,F,Argl},Modifier) when is_list(Argl) ->
+ io_lib:format("~p:~"++Modifier++"p(~"++Modifier++"s)",
+ [M, F, fargs(Argl,Modifier)]);
+ffunc({M,F,Arity},Modifier) ->
+ io_lib:format("~p:~"++Modifier++"p/~p", [M,F,Arity]);
+ffunc(X,Modifier) -> io_lib:format("~"++Modifier++"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
+fargs(Arity,_) when is_integer(Arity) -> integer_to_list(Arity);
+fargs([],_) -> [];
+fargs([A],Modifier) ->
+ io_lib:format("~"++Modifier++"p", [A]); %% last arg
+fargs([A|Args],Modifier) ->
+ [io_lib:format("~"++Modifier++"p,", [A]) | fargs(Args,Modifier)];
+fargs(A,Modifier) ->
+ io_lib:format("~"++Modifier++"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)].
-
+ftup(Trace, Index, Index, Modifier) ->
+ io_lib:format("~"++Modifier++"p", [element(Index, Trace)]);
+ftup(Trace, Index, Size, Modifier) ->
+ [io_lib:format("~"++Modifier++"p ", [element(Index, Trace)])
+ | ftup(Trace, Index+1, Size, Modifier)].
+out({_,_}=Out) ->
+ Out;
+out(Device) ->
+ {Device,modifier(Device)}.
+
+modifier() ->
+ modifier(group_leader()).
+modifier(Device) ->
+ Encoding =
+ case io:getopts(Device) of
+ List when is_list(List) ->
+ proplists:get_value(encoding,List,latin1);
+ _ ->
+ latin1
+ end,
+ encoding_to_modifier(Encoding).
+
+encoding_to_modifier(latin1) -> "";
+encoding_to_modifier(_) -> "t".
trace_process(Pid, [clear]) ->
trac(Pid, false, all());
@@ -1114,7 +1214,7 @@ 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()--[silent];
+transform_flags([all|_],_Acc) -> all()--[silent,running];
transform_flags([F|Tail]=List,Acc) when is_atom(F) ->
case lists:member(F, all()) of
true -> transform_flags(Tail,[F|Acc]);
@@ -1123,47 +1223,58 @@ transform_flags([F|Tail]=List,Acc) when is_atom(F) ->
transform_flags(Bad,_Acc) -> {error,{bad_flags,Bad}}.
all() ->
- [send,'receive',call,procs,garbage_collection,running,
+ [send,'receive',call,procs,ports,garbage_collection,running,
set_on_spawn,set_on_first_spawn,set_on_link,set_on_first_link,
- timestamp,arity,return_to,silent].
+ timestamp,monotonic_timestamp,strict_monotonic_timestamp,
+ arity,return_to,silent,running_procs,running_ports,exiting].
-display_info([Node|Nodes]) ->
+display_info([Node|Nodes],Modifier) ->
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([]) ->
+ display_info1(List,Modifier),
+ display_info(Nodes,Modifier);
+display_info([],_) ->
ok.
-display_info1([{Pid,Call,Flags}|T]) ->
- io:format("~-12s ~-21s ~s~n",
+display_info1([{Pid,Call,Flags}|T],Modifier) ->
+ io:format("~-12s ~-21"++Modifier++"s ~s~n",
[io_lib:format("~w",[Pid]),
- io_lib:format("~p", [Call]),
+ io_lib:format("~"++Modifier++"p", [Call]),
format_trace(Flags)]),
- display_info1(T);
-display_info1([]) ->
+ display_info1(T,Modifier);
+display_info1([],_) ->
ok.
get_info() ->
- get_info(processes(),[]).
+ get_info(processes(),get_info(erlang:ports(),[])).
+get_info([Port|T], Acc) when is_port(Port) ->
+ case pinfo(Port, name) of
+ undefined ->
+ get_info(T,Acc);
+ {name, Name} ->
+ get_info(T,get_tinfo(Port, Name, Acc))
+ end;
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
+ get_info(T,get_tinfo(Pid, Call, Acc))
end;
get_info([],Acc) -> Acc.
+get_tinfo(P, Id, Acc) ->
+ case tinfo(P, flags) of
+ undefined ->
+ Acc;
+ {flags,[]} ->
+ Acc;
+ {flags,Flags} ->
+ [{P,Id,Flags}|Acc]
+ end.
+
format_trace([]) -> [];
format_trace([Item]) -> [ts(Item)];
format_trace([Item|T]) -> [ts(Item) ," | ", format_trace(T)].
@@ -1188,9 +1299,22 @@ to_pidspec(X) when is_pid(X) ->
true -> X;
false -> {badpid,X}
end;
-to_pidspec(new) -> new;
-to_pidspec(all) -> all;
-to_pidspec(existing) -> existing;
+to_pidspec(X) when is_port(X) ->
+ case erlang:port_info(X) of
+ undefined -> {badport, X};
+ _ -> X
+ end;
+to_pidspec(Tag)
+ when Tag =:= all;
+ Tag =:= ports;
+ Tag =:= processes;
+ Tag =:= new;
+ Tag =:= new_ports;
+ Tag =:= new_processes;
+ Tag =:= existing;
+ Tag =:= existing_ports;
+ Tag =:= existing_processes ->
+ Tag;
to_pidspec(X) when is_atom(X) ->
case whereis(X) of
undefined -> {badpid,X};
@@ -1203,6 +1327,7 @@ to_pidspec(X) -> {badpid,X}.
%%
to_pid(X) when is_pid(X) -> X;
+to_pid(X) when is_port(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),".",
@@ -1217,9 +1342,12 @@ to_pid(X) when is_list(X) ->
to_pid(X) -> {badpid,X}.
+pinfo(P, X) when node(P) == node(), is_port(P) -> erlang:port_info(P, X);
pinfo(P, X) when node(P) == node() -> erlang:process_info(P, X);
+pinfo(P, X) when is_port(P) -> check(rpc:call(node(P), erlang, port_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])).
@@ -1247,7 +1375,8 @@ tc_loop([], Handler, HData) ->
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]),
+ Modifier = modifier(),
+ io:format("~p:tc_loop ~"++Modifier++"p~n", [?MODULE, Other]),
exit({unknown_term_from_reader, Other}).
@@ -1256,6 +1385,9 @@ tc_loop(Other, _Handler, _HData) ->
gen_reader(ip, {Host, Portno}) ->
case gen_tcp:connect(Host, Portno, [{active, false}, binary]) of
{ok, Sock} ->
+ %% Just in case this is on the traced node,
+ %% make sure the port is not traced.
+ p(Sock,clear),
mk_reader(fun ip_read/2, Sock);
Error ->
exit(Error)
@@ -1269,13 +1401,15 @@ gen_reader(follow_file, Filename) ->
%% Opens a file and returns a reader (lazy list).
gen_reader_file(ReadFun, Filename) ->
- case file:open(Filename, [read, raw, binary]) of
+ case file:open(Filename, [read, raw, binary, read_ahead]) of
{ok, File} ->
mk_reader(ReadFun, File);
Error ->
exit({client_cannot_open, Error})
end.
+-dialyzer({no_improper_lists, mk_reader/2}).
+
%% Creates and returns a reader (lazy list).
mk_reader(ReadFun, Source) ->
fun() ->
@@ -1294,20 +1428,22 @@ mk_reader(ReadFun, Source) ->
mk_reader_wrap([]) ->
[];
mk_reader_wrap([Hd | _] = WrapFiles) ->
- case file:open(wrap_name(Hd), [read, raw, binary]) of
+ case file:open(wrap_name(Hd), [read, raw, binary, read_ahead]) of
{ok, File} ->
mk_reader_wrap(WrapFiles, File);
Error ->
exit({client_cannot_open, Error})
end.
+-dialyzer({no_improper_lists, mk_reader_wrap/2}).
+
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),
+ ok = file:close(File),
case Tail of
[_|_] ->
mk_reader_wrap(Tail);
@@ -1407,6 +1543,13 @@ get_tracer() ->
req({get_tracer,node()}).
get_tracer(Node) ->
req({get_tracer,Node}).
+get_tracer_flag() ->
+ {ok, Tracer} = get_tracer(),
+ get_tracer_flag(Tracer).
+get_tracer_flag({Module,State}) ->
+ {tracer, Module, State};
+get_tracer_flag(Port = Pid) when is_port(Port); is_pid(Pid)->
+ {tracer, Pid = Port}.
save_pattern([]) ->
0;
diff --git a/lib/runtime_tools/src/dyntrace.erl b/lib/runtime_tools/src/dyntrace.erl
index f7dbef6929..5fe62a46f6 100644
--- a/lib/runtime_tools/src/dyntrace.erl
+++ b/lib/runtime_tools/src/dyntrace.erl
@@ -41,7 +41,28 @@
pn/1, pn/2, pn/3, pn/4, pn/5, pn/6, pn/7, pn/8, pn/9]).
-export([put_tag/1, get_tag/0, get_tag_data/0, spread_tag/1, restore_tag/1]).
+-export([trace/5,
+ trace_procs/5,
+ trace_ports/5,
+ trace_running_procs/5,
+ trace_running_ports/5,
+ trace_call/5,
+ trace_send/5,
+ trace_receive/5,
+ trace_garbage_collection/5]).
+
+-export([enabled_procs/3,
+ enabled_ports/3,
+ enabled_running_procs/3,
+ enabled_running_ports/3,
+ enabled_call/3,
+ enabled_send/3,
+ enabled_receive/3,
+ enabled_garbage_collection/3,
+ enabled/3]).
+
-export([user_trace_i4s4/9]). % Know what you're doing!
+-compile(no_native).
-on_load(on_load/0).
-type probe_arg() :: integer() | iolist().
@@ -125,6 +146,60 @@ user_trace_i4s4(_, _, _, _, _, _, _, _, _) ->
user_trace_n(_, _, _, _, _, _, _, _, _, _) ->
erlang:nif_error(nif_not_loaded).
+trace(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_procs(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_ports(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_running_procs(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_running_ports(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_call(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_send(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_receive(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+trace_garbage_collection(_TraceTag, _TracerState, _Tracee, _TraceTerm, _Opts) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_procs(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_ports(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_running_procs(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_running_ports(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_call(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_send(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_receive(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
+enabled_garbage_collection(_TraceTag, _TracerState, _Tracee) ->
+ erlang:nif_error(nif_not_loaded).
+
%%%
%%% Erlang support functions
%%%
diff --git a/lib/runtime_tools/src/erts_alloc_config.erl b/lib/runtime_tools/src/erts_alloc_config.erl
index 85aacdd6e1..4b028681a0 100644
--- a/lib/runtime_tools/src/erts_alloc_config.erl
+++ b/lib/runtime_tools/src/erts_alloc_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -128,7 +128,7 @@ make_config(FileName) when is_list(FileName) ->
case file:open(FileName, [write]) of
{ok, IODev} ->
Res = req({make_config, IODev}),
- file:close(IODev),
+ ok = file:close(IODev),
Res;
Error ->
Error
@@ -200,9 +200,11 @@ server_loop(State) ->
Conf = #conf{segments = ?MBC_MSEG_LIMIT,
format_to = IODev},
Res = mk_config(Conf, State#state.alloc),
- From ! {response, Ref, Res};
+ From ! {response, Ref, Res},
+ ok;
_ ->
- From ! {response, Ref, no_scenario_saved}
+ From ! {response, Ref, no_scenario_saved},
+ ok
end,
State;
{request, From, Ref, stop} ->
@@ -263,7 +265,13 @@ strategy_str(aoff) ->
strategy_str(aoffcbf) ->
"Address order first fit carrier best fit";
strategy_str(aoffcaobf) ->
- "Address order first fit carrier adress order best fit".
+ "Address order first fit carrier adress order best fit";
+strategy_str(ageffcaoff) ->
+ "Age order first fit carrier address order first fit";
+strategy_str(ageffcbf) ->
+ "Age order first fit carrier best fit";
+strategy_str(ageffcaobf) ->
+ "Age order first fit carrier adress order best fit".
default_acul(A, S) ->
case carrier_migration_support(S) of
diff --git a/lib/runtime_tools/src/msacc.erl b/lib/runtime_tools/src/msacc.erl
new file mode 100644
index 0000000000..0d9b2690e5
--- /dev/null
+++ b/lib/runtime_tools/src/msacc.erl
@@ -0,0 +1,355 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2014-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% @doc Microstate accounting utility function
+%%
+%% This module provides a user interface for analysing
+%% erlang:statistics(microstate_accounting) data.
+%%
+
+-module(msacc).
+-export([available/0, start/0, start/1, stop/0, reset/0, to_file/1,
+ from_file/1, stats/0, stats/2, print/0, print/1, print/2,
+ print/3]).
+
+-type msacc_data() :: [msacc_data_thread()].
+
+-type msacc_data_thread() :: #{ '$type' := msacc_data,
+ type := msacc_type(), id := msacc_id(),
+ counters := msacc_data_counters() }.
+-type msacc_data_counters() :: #{ msacc_state() => non_neg_integer()}.
+
+-type msacc_stats() :: [msacc_stats_thread()].
+-type msacc_stats_thread() :: #{ '$type' := msacc_stats,
+ type := msacc_type(), id := msacc_id(),
+ system := float(),
+ counters := msacc_stats_counters()}.
+-type msacc_stats_counters() :: #{ msacc_state() => #{ thread := float(),
+ system := float()}}.
+
+
+-type msacc_type() :: scheduler | aux | async.
+-type msacc_id() :: non_neg_integer().
+-type msacc_state() :: alloc | aux | bif | busy_wait | check_io |
+ emulator | ets | gc | gc_fullsweep | nif |
+ other | port | send | sleep | timers.
+
+-type msacc_print_options() :: #{ system => boolean() }.
+
+-spec available() -> boolean().
+available() ->
+ try
+ [_|_] = erlang:statistics(microstate_accounting),
+ true
+ catch _:_ ->
+ false
+ end.
+
+-spec start() -> boolean().
+start() ->
+ erlang:system_flag(microstate_accounting, true).
+
+-spec stop() -> boolean().
+stop() ->
+ erlang:system_flag(microstate_accounting, false).
+
+-spec reset() -> boolean().
+reset() ->
+ erlang:system_flag(microstate_accounting, reset).
+
+-spec start(Time) -> true when
+ Time :: timeout().
+start(Tmo) ->
+ stop(), reset(), start(),
+ timer:sleep(Tmo),
+ stop().
+
+-spec to_file(Filename) -> ok | {error, file:posix()} when
+ Filename :: file:name_all().
+to_file(Filename) ->
+ file:write_file(Filename, io_lib:format("~p.~n",[stats()])).
+
+-spec from_file(Filename) -> msacc_data() when
+ Filename :: file:name_all().
+from_file(Filename) ->
+ {ok, [Stats]} = file:consult(Filename),
+ Stats.
+
+-spec print() -> ok.
+print() ->
+ print(stats()).
+
+-spec print(DataOrStats) -> ok when
+ DataOrStats :: msacc_data() | msacc_stats().
+print(Stats) ->
+ print(Stats, #{}).
+
+-spec print(DataOrStats, Options) -> ok when
+ DataOrStats :: msacc_data() | msacc_stats(),
+ Options :: msacc_print_options().
+print(Stats, Options) ->
+ print(group_leader(), Stats, Options).
+
+-spec print(FileOrDevice, DataOrStats, Options) -> ok when
+ FileOrDevice :: file:filename() | io:device(),
+ DataOrStats :: msacc_data() | msacc_stats(),
+ Options :: msacc_print_options().
+print(Filename, Stats, Options) when is_list(Filename) ->
+ case file:open(Filename,[write]) of
+ {ok, D} -> print(D, Stats, Options),file:close(D);
+ Error -> Error
+ end;
+print(Device, Stats, Options) ->
+ DefaultOpts = #{ system => false },
+ print_int(Device, Stats, maps:merge(DefaultOpts, Options)).
+print_int(Device, [#{ '$type' := msacc_data, id := _Id }|_] = Stats, Options) ->
+ TypeStats = stats(type, Stats),
+ io:format(Device, "~s", [print_stats_overview(Stats, Options)]),
+ io:format(Device, "~s", [print_stats_header(Stats, Options)]),
+ io:format(Device, "~s", [print_stats_threads(
+ stats(realtime, Stats), Options)]),
+ io:format(Device, "~s", [print_stats_type(
+ stats(realtime, TypeStats), Options)]);
+print_int(Device, [#{ '$type' := msacc_data }|_] = Stats, Options) ->
+ io:format(Device, "~s", [print_stats_header(Stats, Options)]),
+ io:format(Device, "~s", [print_stats_type(
+ stats(realtime, Stats), Options)]);
+print_int(Device, [#{ '$type' := msacc_stats, id := _Id }|_] = Stats,Options) ->
+ io:format(Device, "~s", [print_stats_header(Stats, Options)]),
+ io:format(Device, "~s", [print_stats_threads(Stats, Options)]),
+ io:format(Device, "~s", [print_stats_type(
+ msacc:stats(type, Stats), Options)]);
+print_int(Device, [#{ '$type' := msacc_stats }|_] = Stats, Options) ->
+ io:format(Device, "~s", [print_stats_header(Stats, Options)]),
+ io:format(Device, "~s", [print_stats_type(Stats, Options)]).
+
+
+-spec stats() -> msacc_data().
+stats() ->
+ Fun = fun F(K,{PerfCount,StateCount}) ->
+ %% Need to handle ERTS_MSACC_STATE_COUNTERS
+ {F(K,PerfCount),StateCount};
+ F(_K,PerfCount) ->
+ erlang:convert_time_unit(PerfCount, perf_counter, 1000000)
+ end,
+ UsStats = lists:map(
+ fun(#{ counters := Cnt } = M) ->
+ UsCnt = maps:map(Fun,Cnt),
+ M#{ '$type' => msacc_data, counters := UsCnt }
+ end, erlang:statistics(microstate_accounting)),
+ statssort(UsStats).
+
+-spec stats(Analysis, Stats) -> non_neg_integer() when
+ Analysis :: system_realtime | system_runtime,
+ Stats :: msacc_data();
+ (Analysis, Stats) -> msacc_stats() when
+ Analysis :: realtime | runtime,
+ Stats :: msacc_data();
+ (Analysis, StatsOrData) -> msacc_data() | msacc_stats() when
+ Analysis :: type,
+ StatsOrData :: msacc_data() | msacc_stats().
+stats(system_realtime, Stats) ->
+ lists:foldl(fun(#{ counters := Cnt }, Acc) ->
+ get_total(Cnt, Acc)
+ end, 0, Stats);
+stats(system_runtime, Stats) ->
+ lists:foldl(fun(#{ counters := Cnt }, Acc) ->
+ get_total(maps:remove(sleep, Cnt), Acc)
+ end, 0, Stats);
+stats(realtime, Stats) ->
+ RealTime = stats(system_realtime, Stats),
+ statssort([get_thread_perc(Thread, RealTime) || Thread <- Stats]);
+stats(runtime, Stats) ->
+ RunTime = stats(system_runtime, Stats),
+ statssort([get_thread_perc(T#{ counters := maps:remove(sleep,Cnt)}, RunTime)
+ || T = #{ counters := Cnt } <- Stats]);
+stats(type, Stats) ->
+ statssort(merge_threads(Stats, [])).
+
+print_stats_overview(Stats, _Options) ->
+ RunTime = stats(system_runtime, Stats),
+ RealTime = stats(system_realtime, Stats) div length(Stats),
+ SchedStats = [S || #{ type := scheduler } = S <- Stats],
+ AvgSchedRunTime = stats(system_runtime, SchedStats) div length(SchedStats),
+ NumSize = if
+ RealTime > RunTime -> length(integer_to_list(RealTime));
+ true -> length(integer_to_list(RunTime))
+ end,
+ [io_lib:format("Average thread real-time : ~*B us~n",
+ [NumSize, RealTime]),
+ io_lib:format("Accumulated system run-time : ~*B us~n",
+ [NumSize, RunTime]),
+ io_lib:format("Average scheduler run-time : ~*B us~n",
+ [NumSize, AvgSchedRunTime]),
+ io_lib:format("~n",[])].
+
+print_stats_threads(Stats, Options) ->
+ [io_lib:format("~nStats per thread:~n", []),
+ [print_thread_info(Thread, Options) || Thread <- Stats]].
+
+print_stats_type(Stats, Options) ->
+ [io_lib:format("~nStats per type:~n", []),
+ [print_thread_info(Thread, Options) || Thread <- Stats]].
+
+
+print_stats_header([#{ counters := Cnt }|_], #{ system := PrintSys }) ->
+ [io_lib:format("~14s", ["Thread"]),
+ map(fun(Counter, _) when PrintSys->
+ io_lib:format("~9s ", [atom_to_list(Counter)]);
+ (Counter, _) ->
+ io_lib:format("~9s", [atom_to_list(Counter)])
+ end, Cnt),
+ io_lib:format("~n",[])].
+
+print_thread_info(#{ '$type' := msacc_stats,
+ counters := Cnt } = Thread, #{ system := PrintSys }) ->
+ [case maps:find(id, Thread) of
+ error ->
+ io_lib:format("~14s", [atom_to_list(maps:get(type, Thread))]);
+ {ok, Id} ->
+ io_lib:format("~10s(~2B)", [atom_to_list(maps:get(type,Thread)),Id])
+ end,
+ map(fun(_Key, #{ thread := ThreadPerc, system := SystemPerc }) when PrintSys ->
+ io_lib:format("~6.2f%(~4.1f%)", [ThreadPerc, SystemPerc]);
+ (_Key, #{ thread := ThreadPerc }) ->
+ io_lib:format("~8.2f%", [ThreadPerc])
+ end, Cnt),
+ io_lib:format("~n",[])].
+
+get_total(Cnt, Base) ->
+ maps:fold(fun(_, {Val,_}, Time) ->
+ %% Have to handle ERTS_MSACC_STATE_COUNTERS
+ Time + Val;
+ (_, Val, Time) -> Time + Val
+ end, Base, Cnt).
+
+get_thread_perc(#{ '$type' := msacc_data, counters := Cnt } = Thread,
+ SystemTime) ->
+ ThreadTime = get_total(Cnt, 0),
+ Thread#{ '$type' := msacc_stats,
+ system => percentage(ThreadTime,SystemTime),
+ counters => get_thread_perc(Cnt, ThreadTime, SystemTime)}.
+get_thread_perc(Cnt, ThreadTime, SystemTime) ->
+ maps:map(fun F(Key, {Val, C}) ->
+ M = F(Key, Val),
+ M#{ cnt => C };
+ F(_Key, Val) ->
+ #{ thread => percentage(Val, ThreadTime),
+ system => percentage(Val, SystemTime) }
+ end, Cnt).
+
+%% This code is a little bit messy as it has to be able to deal with
+%% both [msacc_data()] and [msacc_stats()].
+merge_threads([#{ '$type' := msacc_stats,
+ type := Type,
+ counters := Cnt } = M0|R], Acc) ->
+ case keyfind(type, Type, Acc) of
+ false ->
+ merge_threads(R, [maps:remove(id,M0#{ threads => 1 })|Acc]);
+ #{ '$type' := msacc_stats, counters := Cnt0,
+ threads := Threads, system := System } = M ->
+ NewMap = M#{ counters := add_counters(Cnt, Cnt0),
+ system := System + maps:get(system, M0),
+ threads := Threads + 1},
+ NewAcc = keyreplace(type, Type, NewMap, Acc),
+ merge_threads(R, NewAcc)
+ end;
+merge_threads([], [#{ '$type' := msacc_stats,
+ system := System,
+ threads := Threads,
+ counters := Cnt} = M0|R]) ->
+ Counters = maps:map(fun(_,#{ thread := Thr } = Map) ->
+ Map#{ thread := Thr / Threads }
+ end, Cnt),
+ M = maps:remove(threads, M0),
+ [M#{ system := System, counters := Counters} | merge_threads([],R)];
+merge_threads([], []) ->
+ [];
+%% The clauses below deal with msacc_data()
+merge_threads([#{ '$type' := msacc_data,
+ type := Type,
+ counters := Cnt } = M0|R], Acc) ->
+ case keyfind(type, Type, Acc) of
+ false ->
+ merge_threads(R, [maps:remove(id,M0)|Acc]);
+ #{ '$type' := msacc_data, counters := Cnt0 } = M ->
+ NewMap = M#{ counters := add_counters(Cnt, Cnt0) },
+ NewAcc = keyreplace(type, Type, NewMap, Acc),
+ merge_threads(R, NewAcc)
+ end;
+merge_threads([], Acc) ->
+ Acc.
+
+add_counters(M1, M2) ->
+ maps:map(
+ fun(Key, #{ thread := Thr1, system := Sys1, cnt := Cnt1}) ->
+ %% Have to handle ERTS_MSACC_STATE_COUNTERS
+ #{ thread := Thr2, system := Sys2, cnt := Cnt2} = maps:get(Key, M2),
+ #{ thread => Thr1 + Thr2, system => Sys1 + Sys2,
+ cnt => Cnt1 + Cnt2 };
+ (Key, #{ thread := Thr1, system := Sys1}) ->
+ #{ thread := Thr2, system := Sys2} = maps:get(Key, M2),
+ #{ thread => Thr1 + Thr2, system => Sys1 + Sys2};
+ (Key, {V1,C1}) ->
+ %% Have to handle ERTS_MSACC_STATE_COUNTERS
+ {V2,C2} = maps:get(Key, M2),{V1+V2,C1+C2};
+ (Key, V1) -> maps:get(Key, M2) + V1
+ end, M1).
+
+percentage(Divident, Divisor) ->
+ if Divisor == 0 andalso Divident /= 0 ->
+ 100.0;
+ Divisor == 0 ->
+ 0.0;
+ true ->
+ Divident / Divisor * 100
+ end.
+
+keyfind(Key, Value, [H|T]) ->
+ case maps:find(Key, H) of
+ {ok, Value} ->
+ H;
+ _ ->
+ keyfind(Key, Value, T)
+ end;
+keyfind(_, _, []) ->
+ false.
+
+keyreplace(Key, Value, NewMap, [H|T]) ->
+ case maps:find(Key, H) of
+ {ok, Value} ->
+ [NewMap|T];
+ _ ->
+ [H|keyreplace(Key, Value, NewMap, T)]
+ end;
+keyreplace(_, _, _, []) ->
+ [].
+
+statssort(Stats) ->
+ lists:sort(fun(#{ type := Type1, id := Id1},
+ #{ type := Type2, id := Id2}) ->
+ {Type1, Id1} < {Type2, Id2};
+ (#{ type := Type1}, #{ type := Type2}) ->
+ Type1 < Type2
+ end, Stats).
+
+map(Fun,Map) ->
+ [ Fun(K,V) || {K,V} <- maps:to_list(Map) ].
diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl
index 9177752cef..1b075a507d 100644
--- a/lib/runtime_tools/src/observer_backend.erl
+++ b/lib/runtime_tools/src/observer_backend.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,7 +23,8 @@
-export([vsn/0]).
%% observer stuff
--export([sys_info/0, get_table/3, get_table_list/2, fetch_stats/2]).
+-export([sys_info/0, get_port_list/0, procs_info/1,
+ get_table/3, get_table_list/2, fetch_stats/2]).
%% etop stuff
-export([etop_collect/1]).
@@ -35,6 +36,7 @@
ttb_write_binary/2,
ttb_stop/1,
ttb_fetch/2,
+ ttb_fetch/3,
ttb_resume_trace/0,
ttb_get_filenames/1]).
-define(CHUNKSIZE,8191). % 8 kbytes - 1 byte
@@ -62,9 +64,7 @@ sys_info() ->
end,
{{_,Input},{_,Output}} = erlang:statistics(io),
- [{process_count, erlang:system_info(process_count)},
- {process_limit, erlang:system_info(process_limit)},
- {uptime, element(1, erlang:statistics(wall_clock))},
+ [{uptime, element(1, erlang:statistics(wall_clock))},
{run_queue, erlang:statistics(run_queue)},
{io_input, Input},
{io_output, Output},
@@ -85,7 +85,17 @@ sys_info() ->
{thread_pool_size, erlang:system_info(thread_pool_size)},
{wordsize_internal, erlang:system_info({wordsize, internal})},
{wordsize_external, erlang:system_info({wordsize, external})},
- {alloc_info, alloc_info()}
+ {alloc_info, alloc_info()},
+ {process_count, erlang:system_info(process_count)},
+ {atom_limit, erlang:system_info(atom_limit)},
+ {atom_count, erlang:system_info(atom_count)},
+ {process_limit, erlang:system_info(process_limit)},
+ {process_count, erlang:system_info(process_count)},
+ {port_limit, erlang:system_info(port_limit)},
+ {port_count, erlang:system_info(port_count)},
+ {ets_limit, erlang:system_info(ets_limit)},
+ {ets_count, length(ets:all())},
+ {dist_buf_busy_limit, erlang:system_info(dist_buf_busy_limit)}
| MemInfo].
alloc_info() ->
@@ -139,6 +149,60 @@ get_mnesia_loop(Parent, {Match, Cont}) ->
Parent ! {self(), Match},
get_mnesia_loop(Parent, mnesia:select(Cont)).
+get_port_list() ->
+ ExtraItems = [monitors,monitored_by,parallelism,locking,queue_size,memory],
+ [begin
+ [{port_id,P}|erlang:port_info(P)] ++
+ port_info(P,ExtraItems) ++
+ inet_port_extra(erlang:port_info(P, name), P)
+ end || P <- erlang:ports()].
+
+port_info(P,[Item|Items]) ->
+ case erlang:port_info(P,Item) of
+ undefined -> port_info(P,Items);
+ Value -> [Value|port_info(P,Items)]
+ end;
+port_info(_,[]) ->
+ [].
+
+inet_port_extra({_,Type},Port) when Type =:= "udp_inet";
+ Type =:= "tcp_inet";
+ Type =:= "sctp_inet" ->
+ Data =
+ case inet:getstat(Port) of
+ {ok, Stats} -> [{statistics, Stats}];
+ _ -> []
+ end ++
+ case inet:peername(Port) of
+ {ok, {RAddr,RPort}} when is_tuple(RAddr), is_integer(RPort) ->
+ [{remote_address,RAddr},{remote_port,RPort}];
+ {ok, RAddr} ->
+ [{remote_address,RAddr}];
+ {error, _} -> []
+ end ++
+ case inet:sockname(Port) of
+ {ok, {LAddr,LPort}} when is_tuple(LAddr), is_integer(LPort) ->
+ [{local_address,LAddr},{local_port,LPort}];
+ {ok, LAddr} ->
+ [{local_address,LAddr}];
+ {error, _} -> []
+ end ++
+ case inet:getopts(Port,
+ [active, broadcast, buffer, bind_to_device,
+ delay_send, deliver, dontroute, exit_on_close,
+ header, high_msgq_watermark, high_watermark,
+ ipv6_v6only, keepalive, linger, low_msgq_watermark,
+ low_watermark, mode, netns, nodelay, packet,
+ packet_size, priority, read_packets, recbuf,
+ reuseaddr, send_timeout, send_timeout_close,
+ show_econnreset, sndbuf, tos, tclass]) of
+ {ok, Opts} -> [{options, Opts}];
+ {error, _} -> []
+ end,
+ [{inet,Data}];
+inet_port_extra(_,_) ->
+ [].
+
get_table_list(ets, Opts) ->
HideUnread = proplists:get_value(unread_hidden, Opts, true),
HideSys = proplists:get_value(sys_hidden, Opts, true),
@@ -238,6 +302,23 @@ fetch_stats_loop(Parent, Time) ->
try erlang:memory() catch _:_ -> [] end},
fetch_stats_loop(Parent, Time)
end.
+
+%%
+%% Chunk sending process info to etop/observer
+%%
+procs_info(Collector) ->
+ All = processes(),
+ Send = fun Send (Pids) ->
+ try lists:split(10000, Pids) of
+ {First, Rest} ->
+ Collector ! {procs_info, self(), etop_collect(First, [])},
+ Send(Rest)
+ catch _:_ ->
+ Collector ! {procs_info, self(), etop_collect(Pids, [])}
+ end
+ end,
+ Send(All).
+
%%
%% etop backend
%%
@@ -259,12 +340,12 @@ etop_collect(Collector) ->
case SchedulerWallTime of
undefined ->
- spawn(fun() -> flag_holder_proc(Collector) end);
+ erlang:system_flag(scheduler_wall_time,true),
+ spawn(fun() -> flag_holder_proc(Collector) end),
+ ok;
_ ->
ok
- end,
-
- erlang:system_flag(scheduler_wall_time,true).
+ end.
flag_holder_proc(Collector) ->
Ref = erlang:monitor(process,Collector),
@@ -334,8 +415,8 @@ ttb_init_node(MetaFile_0,PI,Traci) ->
MetaPid ! {metadata,Traci},
case PI of
true ->
- Proci = pnames(),
- MetaPid ! {metadata,Proci};
+ MetaPid ! {metadata,pnames()},
+ ok;
false ->
ok
end,
@@ -354,7 +435,8 @@ ttb_meta_tracer(MetaFile,PI,Parent,SessionData) ->
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]);
+ erlang:trace_pattern({global,register_name,2},[],[meta]),
+ ok;
false ->
ok
end,
@@ -362,7 +444,8 @@ ttb_meta_tracer(MetaFile,PI,Parent,SessionData) ->
case proplists:get_value(overload_check, SessionData) of
{Ms, M, F} ->
catch M:F(init),
- erlang:send_after(Ms, self(), overload_check);
+ erlang:send_after(Ms, self(), overload_check),
+ ok;
_ ->
ok
end,
@@ -371,10 +454,10 @@ ttb_meta_tracer(MetaFile,PI,Parent,SessionData) ->
ttb_meta_tracer_loop(MetaFile,PI,Acc,State) ->
receive
{trace_ts,_,call,{erlang,register,[Name,Pid]},_} ->
- ttb_store_meta({pid,{Pid,Name}},MetaFile),
+ ok = ttb_store_meta({pid,{Pid,Name}},MetaFile),
ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
{trace_ts,_,call,{global,register_name,[Name,Pid]},_} ->
- ttb_store_meta({pid,{Pid,{global,Name}}},MetaFile),
+ ok = ttb_store_meta({pid,{Pid,{global,Name}}},MetaFile),
ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
{trace_ts,CallingPid,call,{erlang,spawn_opt,[{M,F,Args,_}]},_} ->
MFA = {M,F,length(Args)},
@@ -390,7 +473,7 @@ ttb_meta_tracer_loop(MetaFile,PI,Acc,State) ->
NewAcc =
dict:update(CallingPid,
fun([H|T]) ->
- ttb_store_meta({pid,{NewPid,H}},MetaFile),
+ ok = ttb_store_meta({pid,{NewPid,H}},MetaFile),
T
end,
Acc),
@@ -408,22 +491,22 @@ ttb_meta_tracer_loop(MetaFile,PI,Acc,State) ->
NewAcc =
dict:update(CallingPid,
fun([H|T]) ->
- ttb_store_meta({pid,{NewPid,H}},MetaFile),
+ ok = ttb_store_meta({pid,{NewPid,H}},MetaFile),
T
end,
Acc),
ttb_meta_tracer_loop(MetaFile,PI,NewAcc,State);
{metadata,Data} when is_list(Data) ->
- ttb_store_meta(Data,MetaFile),
+ ok = ttb_store_meta(Data,MetaFile),
ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
{metadata,Key,Fun} when is_function(Fun) ->
- ttb_store_meta([{Key,Fun()}],MetaFile),
+ ok = ttb_store_meta([{Key,Fun()}],MetaFile),
ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
{metadata,Key,What} ->
- ttb_store_meta([{Key,What}],MetaFile),
+ ok = ttb_store_meta([{Key,What}],MetaFile),
ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
overload_check ->
{Ms, M, F} = proplists:get_value(overload_check, State),
@@ -439,7 +522,7 @@ ttb_meta_tracer_loop(MetaFile,PI,Acc,State) ->
ttb_meta_tracer_loop(MetaFile,PI,Acc, State)
end;
{'DOWN', _, _, _, _} ->
- stop_seq_trace(),
+ _ = stop_seq_trace(),
self() ! stop,
ttb_meta_tracer_loop(MetaFile,PI,Acc, State);
stop when PI=:=true ->
@@ -528,7 +611,7 @@ ttb_store_meta(Data,MetaFile) ->
ttb_store_meta([Data],MetaFile).
ttb_write_binary(Fd,[H|T]) ->
- file:write(Fd,ttb_make_binary(H)),
+ ok = file:write(Fd,ttb_make_binary(H)),
ttb_write_binary(Fd,T);
ttb_write_binary(_Fd,[]) ->
ok.
@@ -576,22 +659,42 @@ stop_seq_trace() ->
%% Fetch ttb logs from remote node
ttb_fetch(MetaFile,{Port,Host}) ->
+ ttb_fetch(MetaFile,{Port,Host},undefined).
+ttb_fetch(MetaFile,{Port,Host},MasterEnc) ->
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),
+ send_files({Sock,Host},Files,MasterEnc,file:native_name_encoding()),
ok = gen_tcp:close(Sock).
-send_files({Sock,Host},[File|Files]) ->
+send_files({Sock,Host},[File|Files],MasterEnc,MyEnc) ->
{ok,Fd} = file:open(File,[raw,read,binary]),
- gen_tcp:send(Sock,<<1,(list_to_binary(filename:basename(File)))/binary>>),
+ Basename = filename:basename(File),
+ {Code,FilenameBin} = encode_filename(Basename,MasterEnc,MyEnc),
+ ok = gen_tcp:send(Sock,<<Code,FilenameBin/binary>>),
send_chunks(Sock,Fd),
- file:delete(File),
- send_files({Sock,Host},Files);
-send_files({_Sock,_Host},[]) ->
+ ok = file:delete(File),
+ send_files({Sock,Host},Files,MasterEnc,MyEnc);
+send_files({_Sock,_Host},[],_MasterEnc,_MyEnc) ->
done.
+encode_filename(Basename,undefined,MyEnc) ->
+ %% Compatible with old version of ttb.erl, but no longer crashing
+ %% for code points > 255.
+ {1,unicode:characters_to_binary(Basename,MyEnc,MyEnc)};
+encode_filename(Basename,MasterEnc,MyEnc) ->
+ case unicode:characters_to_binary(Basename,MyEnc,MasterEnc) of
+ Bin when is_binary(Bin) ->
+ %% Encoding succeeded
+ {2,Bin};
+ _ ->
+ %% Can't convert Basename from my encoding to the master
+ %% node's encoding. Doing my best and hoping that master
+ %% node can fix it...
+ {3,unicode:characters_to_binary(Basename,MyEnc,MyEnc)}
+ end.
+
send_chunks(Sock,Fd) ->
case file:read(Fd,?CHUNKSIZE) of
{ok,Bin} ->
diff --git a/lib/runtime_tools/src/percept_profile.erl b/lib/runtime_tools/src/percept_profile.erl
deleted file mode 100644
index d5ea2c7032..0000000000
--- a/lib/runtime_tools/src/percept_profile.erl
+++ /dev/null
@@ -1,194 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions 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(Filename :: file:filename()) ->
- {'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(Filename :: file:filename(),
- 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(Filename :: file:filename(),
- 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() -> ok | {'error', 'not_started'}
-%% @doc Stops profiling.
-
--spec stop() -> 'ok' | {'error', 'not_started'}.
-
-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:timestamp()})),
- %% 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:timestamp()})),
- 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
index ad10655aa0..449532e5c4 100644
--- a/lib/runtime_tools/src/runtime_tools.app.src
+++ b/lib/runtime_tools/src/runtime_tools.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2012. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,14 +20,13 @@
{application, runtime_tools,
[{description, "RUNTIME_TOOLS"},
{vsn, "%VSN%"},
- {modules, [appmon_info, dbg,observer_backend,percept_profile,
- runtime_tools,runtime_tools_sup,erts_alloc_config,
- ttb_autostart,dyntrace,system_information]},
+ {modules, [appmon_info, dbg,observer_backend,runtime_tools,
+ runtime_tools_sup,erts_alloc_config,
+ ttb_autostart,dyntrace,system_information,
+ msacc]},
{registered, [runtime_tools_sup]},
{applications, [kernel, stdlib]},
{env, []},
{mod, {runtime_tools, []}},
- {runtime_dependencies, ["stdlib-2.0","mnesia-4.12","kernel-3.0",
- "erts-7.0"]}]}.
-
-
+ {runtime_dependencies, ["stdlib-3.0","mnesia-4.12","kernel-5.0",
+ "erts-8.0"]}]}.
diff --git a/lib/runtime_tools/src/runtime_tools.appup.src b/lib/runtime_tools/src/runtime_tools.appup.src
index 883ff1e074..a42673c87e 100644
--- a/lib/runtime_tools/src/runtime_tools.appup.src
+++ b/lib/runtime_tools/src/runtime_tools.appup.src
@@ -1,7 +1,7 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/runtime_tools/src/runtime_tools.erl b/lib/runtime_tools/src/runtime_tools.erl
index 62b9dfd71e..52ae5cc0eb 100644
--- a/lib/runtime_tools/src/runtime_tools.erl
+++ b/lib/runtime_tools/src/runtime_tools.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/runtime_tools/src/runtime_tools_sup.erl b/lib/runtime_tools/src/runtime_tools_sup.erl
index e69b495cb5..efa37de42d 100644
--- a/lib/runtime_tools/src/runtime_tools_sup.erl
+++ b/lib/runtime_tools/src/runtime_tools_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/runtime_tools/src/system_information.erl b/lib/runtime_tools/src/system_information.erl
index ad7ee7311c..3772fcd2f9 100644
--- a/lib/runtime_tools/src/system_information.erl
+++ b/lib/runtime_tools/src/system_information.erl
@@ -28,31 +28,26 @@
-behaviour(gen_server).
%% API
--export([
- report/0,
+-export([report/0,
from_file/1,
- to_file/1
- ]).
--export([
- start/0, stop/0,
- load_report/0, load_report/2,
- applications/0, applications/1,
- application/1, application/2,
- environment/0, environment/1,
- module/1, module/2,
- modules/1,
- sanity_check/0
- ]).
+ to_file/1]).
+
+-export([start/0, stop/0,
+ load_report/0, load_report/2,
+ applications/0, applications/1,
+ application/1, application/2,
+ environment/0, environment/1,
+ module/1, module/2,
+ modules/1,
+ sanity_check/0]).
%% gen_server callbacks
--export([
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
-define(SERVER, ?MODULE).
@@ -70,72 +65,67 @@
start() ->
gen_server:start({local, ?SERVER}, ?MODULE, [], []).
+
stop() ->
- gen_server:call(?SERVER, stop).
+ gen_server:call(?SERVER, stop, infinity).
load_report() -> load_report(data, report()).
load_report(file, File) -> load_report(data, from_file(File));
load_report(data, Report) ->
- start(), gen_server:call(?SERVER, {load_report, Report}).
-
-report() -> [
- {init_arguments, init:get_arguments()},
- {code_paths, code:get_path()},
- {code, code()},
- {system_info, erlang_system_info()},
- {erts_compile_info, erlang:system_info(compile_info)},
- {beam_dynamic_libraries, get_dynamic_libraries()},
- {environment_erts, os_getenv_erts_specific()},
- {environment, [split_env(Env) || Env <- os:getenv()]},
- {sanity_check, sanity_check()}
- ].
+ ok = start_internal(), gen_server:call(?SERVER, {load_report, Report}, infinity).
+
+report() ->
+ %% This is ugly but beats having to maintain two distinct implementations,
+ %% and we don't really care about memory use since it's internal and
+ %% undocumented.
+ {ok, Fd} = file:open([], [ram, read, write]),
+ to_fd(Fd),
+ {ok, _} = file:position(Fd, bof),
+ from_fd(Fd).
-spec to_file(FileName) -> ok | {error, Reason} when
FileName :: file:name_all(),
Reason :: file:posix() | badarg | terminated | system_limit.
to_file(File) ->
- file:write_file(File, iolist_to_binary([
- io_lib:format("{system_information_version, ~p}.~n", [
- ?REPORT_FILE_VSN
- ]),
- io_lib:format("{system_information, ~p}.~n", [
- report()
- ])
- ])).
+ case file:open(File, [raw, write, binary, delayed_write]) of
+ {ok, Fd} ->
+ try
+ to_fd(Fd)
+ after
+ file:close(Fd)
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
from_file(File) ->
- case file:consult(File) of
- {ok, Data} ->
- case get_value([system_information_version], Data) of
- ?REPORT_FILE_VSN ->
- get_value([system_information], Data);
- Vsn ->
- erlang:error({unknown_version, Vsn})
- end;
- _ ->
- erlang:error(bad_report_file)
+ {ok, Fd} = file:open(File, [raw, read]),
+ try
+ from_fd(Fd)
+ after
+ file:close(Fd)
end.
applications() -> applications([]).
applications(Opts) when is_list(Opts) ->
- gen_server:call(?SERVER, {applications, Opts}).
+ gen_server:call(?SERVER, {applications, Opts}, infinity).
application(App) when is_atom(App) -> application(App, []).
application(App, Opts) when is_atom(App), is_list(Opts) ->
- gen_server:call(?SERVER, {application, App, Opts}).
+ gen_server:call(?SERVER, {application, App, Opts}, infinity).
environment() -> environment([]).
environment(Opts) when is_list(Opts) ->
- gen_server:call(?SERVER, {environment, Opts}).
+ gen_server:call(?SERVER, {environment, Opts}, infinity).
module(M) when is_atom(M) -> module(M, []).
module(M, Opts) when is_atom(M), is_list(Opts) ->
- gen_server:call(?SERVER, {module, M, Opts}).
+ gen_server:call(?SERVER, {module, M, Opts}, infinity).
modules(Opt) when is_atom(Opt) ->
- gen_server:call(?SERVER, {modules, Opt}).
+ gen_server:call(?SERVER, {modules, Opt}, infinity).
-spec sanity_check() -> ok | {failed, Failures} when
@@ -225,6 +215,13 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions
%%===================================================================
+start_internal() ->
+ case start() of
+ {ok,_} -> ok;
+ {error, {already_started,_}} -> ok;
+ Error -> Error
+ end.
+
%% handle report values
get_value([], Data) -> Data;
@@ -454,61 +451,151 @@ split_env([$=|Vs], Key) -> {lists:reverse(Key), Vs};
split_env([I|Vs], Key) -> split_env(Vs, [I|Key]);
split_env([], KV) -> lists:reverse(KV). % should not happen.
-%% get applications
+from_fd(Fd) ->
+ try
+ [{system_information_version, "1.0"},
+ {system_information, Data}] = consult_fd(Fd),
+ Data
+ catch
+ _:_ -> erlang:error(bad_report_file)
+ end.
-code() ->
- % order is important
- get_code_from_paths(code:get_path()).
+consult_fd(Fd) ->
+ consult_fd_1(Fd, [], {ok, []}).
+consult_fd_1(Fd, Cont0, ReadResult) ->
+ Data =
+ case ReadResult of
+ {ok, Characters} -> Characters;
+ eof -> eof
+ end,
+ case erl_scan:tokens(Cont0, Data, 1) of
+ {done, {ok, Tokens, _}, Next} ->
+ {ok, Term} = erl_parse:parse_term(Tokens),
+ [Term | consult_fd_1(Fd, [], {ok, Next})];
+ {more, Cont} ->
+ consult_fd_1(Fd, Cont, file:read(Fd, 1 bsl 20));
+ {done, {eof, _}, eof} -> []
+ end.
-get_code_from_paths([]) -> [];
-get_code_from_paths([Path|Paths]) ->
- case is_application_path(Path) of
- true ->
- [{application, get_application_from_path(Path)}|get_code_from_paths(Paths)];
- false ->
- [{code, [
- {path, Path},
- {modules, get_modules_from_path(Path)}
- ]}|get_code_from_paths(Paths)]
+%%
+%% Dumps a system_information tuple to the given Fd, writing the term in chunks
+%% to avoid eating too much memory on large systems.
+%%
+
+to_fd(Fd) ->
+ EmitChunk =
+ fun(Format, Args) ->
+ ok = file:write(Fd, io_lib:format(Format, Args))
+ end,
+
+ EmitChunk("{system_information_version, ~w}.~n"
+ "{system_information,["
+ "{init_arguments,~w},"
+ "{code_paths,~w},",
+ [?REPORT_FILE_VSN,
+ init:get_arguments(),
+ code:get_path()]),
+
+ emit_code_info(EmitChunk),
+
+ EmitChunk( "," %% Note the leading comma!
+ "{system_info,~w},"
+ "{erts_compile_info,~w},"
+ "{beam_dynamic_libraries,~w},"
+ "{environment_erts,~w},"
+ "{environment,~w},"
+ "{sanity_check,~w}"
+ "]}.~n",
+ [erlang_system_info(),
+ erlang:system_info(compile_info),
+ get_dynamic_libraries(),
+ os_getenv_erts_specific(),
+ [split_env(Env) || Env <- os:getenv()],
+ sanity_check()]).
+
+%% Emits all modules/applications in the *code path order*
+emit_code_info(EmitChunk) ->
+ EmitChunk("{code, [", []),
+ comma_separated_foreach(EmitChunk,
+ fun(Path) ->
+ case is_application_path(Path) of
+ true -> emit_application_info(EmitChunk, Path);
+ false -> emit_code_path_info(EmitChunk, Path)
+ end
+ end, code:get_path()),
+ EmitChunk("]}", []).
+
+emit_application_info(EmitChunk, Path) ->
+ [Appfile|_] = filelib:wildcard(filename:join(Path, "*.app")),
+ case file:consult(Appfile) of
+ {ok, [{application, App, Info}]} ->
+ RtDeps = proplists:get_value(runtime_dependencies, Info, []),
+ Description = proplists:get_value(description, Info, []),
+ Version = proplists:get_value(vsn, Info, []),
+
+ EmitChunk("{application, {~w,["
+ "{description,~w},"
+ "{vsn,~w},"
+ "{path,~w},"
+ "{runtime_dependencies,~w},",
+ [App, Description, Version, Path, RtDeps]),
+ emit_module_info_from_path(EmitChunk, Path),
+ EmitChunk("]}}", [])
end.
+emit_code_path_info(EmitChunk, Path) ->
+ EmitChunk("{code, ["
+ "{path, ~w},", [Path]),
+ emit_module_info_from_path(EmitChunk, Path),
+ EmitChunk("]}", []).
+
+emit_module_info_from_path(EmitChunk, Path) ->
+ BeamFiles = filelib:wildcard(filename:join(Path, "*.beam")),
+
+ EmitChunk("{modules, [", []),
+ comma_separated_foreach(EmitChunk,
+ fun(Beam) ->
+ emit_module_info(EmitChunk, Beam)
+ end, BeamFiles),
+ EmitChunk("]}", []).
+
+emit_module_info(EmitChunk, Beam) ->
+ %% FIXME: The next three calls load *all* significant chunks onto the heap,
+ %% which may cause us to run out of memory if there's a huge module in the
+ %% code path.
+ {ok,{Mod, Md5}} = beam_lib:md5(Beam),
+
+ CompilerVersion = get_compiler_version(Beam),
+ Native = beam_is_native_compiled(Beam),
+
+ Loaded = case code:is_loaded(Mod) of
+ false -> false;
+ _ -> true
+ end,
+
+ EmitChunk("{~w,["
+ "{loaded,~w},"
+ "{native,~w},"
+ "{compiler,~w},"
+ "{md5,~w}"
+ "]}",
+ [Mod, Loaded, Native, CompilerVersion, hexstring(Md5)]).
+
+comma_separated_foreach(_EmitChunk, _Fun, []) ->
+ ok;
+comma_separated_foreach(_EmitChunk, Fun, [H]) ->
+ Fun(H);
+comma_separated_foreach(EmitChunk, Fun, [H | T]) ->
+ Fun(H),
+ EmitChunk(",", []),
+ comma_separated_foreach(EmitChunk, Fun, T).
+
is_application_path(Path) ->
case filelib:wildcard(filename:join(Path, "*.app")) of
[] -> false;
_ -> true
end.
-get_application_from_path(Path) ->
- [Appfile|_] = filelib:wildcard(filename:join(Path, "*.app")),
- case file:consult(Appfile) of
- {ok, [{application, App, Info}]} ->
- {App, [
- {description, proplists:get_value(description, Info, [])},
- {vsn, proplists:get_value(vsn, Info, [])},
- {path, Path},
- {runtime_dependencies,
- proplists:get_value(runtime_dependencies, Info, [])},
- {modules, get_modules_from_path(Path)}
- ]}
- end.
-
-get_modules_from_path(Path) ->
- [
- begin
- {ok,{Mod, Md5}} = beam_lib:md5(Beam),
- Loaded = case code:is_loaded(Mod) of
- false -> false;
- _ -> true
- end,
- {Mod, [
- {loaded, Loaded},
- {native, beam_is_native_compiled(Beam)},
- {compiler, get_compiler_version(Beam)},
- {md5, hexstring(Md5)}
- ]}
- end || Beam <- filelib:wildcard(filename:join(Path, "*.beam"))
- ].
-
hexstring(Bin) when is_binary(Bin) ->
lists:flatten([io_lib:format("~2.16.0b", [V]) || <<V>> <= Bin]).