aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer')
-rwxr-xr-xlib/observer/priv/bin/cdv2
-rw-r--r--lib/observer/priv/bin/cdv.bat2
-rw-r--r--lib/observer/src/cdv_atom_cb.erl2
-rw-r--r--lib/observer/src/cdv_bin_cb.erl3
-rw-r--r--lib/observer/src/cdv_detail_wx.erl30
-rw-r--r--lib/observer/src/cdv_html_wx.erl17
-rw-r--r--lib/observer/src/cdv_info_wx.erl8
-rw-r--r--lib/observer/src/cdv_multi_wx.erl8
-rw-r--r--lib/observer/src/cdv_proc_cb.erl2
-rw-r--r--lib/observer/src/cdv_table_wx.erl8
-rw-r--r--lib/observer/src/cdv_term_cb.erl16
-rw-r--r--lib/observer/src/cdv_virtual_list_wx.erl16
-rw-r--r--lib/observer/src/cdv_wx.erl40
-rw-r--r--lib/observer/src/crashdump_viewer.erl546
-rw-r--r--lib/observer/src/etop_tr.erl4
-rw-r--r--lib/observer/src/etop_txt.erl38
-rw-r--r--lib/observer/src/multitrace.erl14
-rw-r--r--lib/observer/src/observer.app.src2
-rw-r--r--lib/observer/src/observer_alloc_wx.erl4
-rw-r--r--lib/observer/src/observer_app_wx.erl2
-rw-r--r--lib/observer/src/observer_html_lib.erl62
-rw-r--r--lib/observer/src/observer_lib.erl119
-rw-r--r--lib/observer/src/observer_perf_wx.erl4
-rw-r--r--lib/observer/src/observer_port_wx.erl2
-rw-r--r--lib/observer/src/observer_pro_wx.erl12
-rw-r--r--lib/observer/src/observer_procinfo.erl10
-rw-r--r--lib/observer/src/observer_sys_wx.erl63
-rw-r--r--lib/observer/src/observer_trace_wx.erl49
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl19
-rw-r--r--lib/observer/src/observer_tv_table.erl55
-rw-r--r--lib/observer/src/observer_tv_wx.erl2
-rw-r--r--lib/observer/src/observer_wx.erl4
-rw-r--r--lib/observer/src/ttb.erl69
-rw-r--r--lib/observer/src/ttb_et.erl12
-rw-r--r--lib/observer/test/Makefile5
-rw-r--r--lib/observer/test/crashdump_helper_unicode.erl22
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl156
-rw-r--r--lib/observer/test/observer_SUITE.erl9
-rw-r--r--lib/observer/test/ttb_SUITE.erl26
39 files changed, 928 insertions, 536 deletions
diff --git a/lib/observer/priv/bin/cdv b/lib/observer/priv/bin/cdv
index d14fd47e41..2a509c16af 100755
--- a/lib/observer/priv/bin/cdv
+++ b/lib/observer/priv/bin/cdv
@@ -1,4 +1,4 @@
#!/bin/sh
-erl -noinput -s crashdump_viewer script_start $@
+erl -env ERL_CRASH_DUMP_SECONDS 0 -noinput -s crashdump_viewer script_start $@
diff --git a/lib/observer/priv/bin/cdv.bat b/lib/observer/priv/bin/cdv.bat
index 18136a30d6..fa87c08adf 100644
--- a/lib/observer/priv/bin/cdv.bat
+++ b/lib/observer/priv/bin/cdv.bat
@@ -1,2 +1,2 @@
@ECHO OFF
-CALL werl -s crashdump_viewer script_start %*
+CALL werl -env ERL_CRASH_DUMP_SECONDS 0 -s crashdump_viewer script_start %*
diff --git a/lib/observer/src/cdv_atom_cb.erl b/lib/observer/src/cdv_atom_cb.erl
index a123354c8f..86cdf2fd6d 100644
--- a/lib/observer/src/cdv_atom_cb.erl
+++ b/lib/observer/src/cdv_atom_cb.erl
@@ -42,7 +42,7 @@ get_info(_) ->
{Info,TW}.
format({Bin,q}) when is_binary(Bin) ->
- [$'|binary_to_list(Bin)];
+ [$'|lists:flatten(io_lib:format("~ts",[Bin]))];
format({Bin,nq}) when is_binary(Bin) ->
lists:flatten(io_lib:format("~ts",[Bin]));
format(D) ->
diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl
index 5472d36a6f..5502869973 100644
--- a/lib/observer/src/cdv_bin_cb.erl
+++ b/lib/observer/src/cdv_bin_cb.erl
@@ -38,6 +38,7 @@ init_bin_page(Parent,{Type,Bin}) ->
[{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin)}},
{"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin)}},
{"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin)}},
+ {"Format \~tw",cdv_html_wx,{Type,format_bin_fun("~tw",Bin)}},
{"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin)}},
{"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin)}},
{"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin)}},
@@ -56,7 +57,7 @@ format_bin_fun(Format,Bin) ->
binary_to_term_fun(Bin) ->
fun() ->
try binary_to_term(Bin) of
- Term -> plain_html(io_lib:format("~p",[Term]))
+ Term -> plain_html(io_lib:format("~tp",[Term]))
catch error:badarg ->
Warning = "This binary can not be converted to an Erlang term",
observer_html_lib:warning(Warning)
diff --git a/lib/observer/src/cdv_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl
index 27057fd27f..f6d282638a 100644
--- a/lib/observer/src/cdv_detail_wx.erl
+++ b/lib/observer/src/cdv_detail_wx.erl
@@ -20,7 +20,7 @@
-behaviour(wx_object).
--export([start_link/4]).
+-export([start_link/5]).
-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
handle_call/3, handle_info/2]).
@@ -39,27 +39,42 @@
-define(ID_NOTEBOOK, 604).
%% Detail view
-start_link(Id, Data, ParentFrame, Callback) ->
- wx_object:start_link(?MODULE, [Id, Data, ParentFrame, Callback, self()], []).
+start_link(Id, Data, ParentFrame, Callback, App) ->
+ wx_object:start_link(?MODULE,[Id,Data,ParentFrame,Callback,App,self()],[]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-init([Id, Data, ParentFrame, Callback, Parent]) ->
+init([Id, Data, ParentFrame, Callback, App, Parent]) ->
+ display_progress(ParentFrame,App),
case Callback:get_details(Id, Data) of
{ok,Details} ->
- init(Id,ParentFrame,Callback,Parent,Details);
+ init(Id,ParentFrame,Callback,App,Parent,Details);
{yes_no, Info, Fun} ->
+ destroy_progress(App),
case observer_lib:display_yes_no_dialog(Info) of
?wxID_YES -> Fun();
?wxID_NO -> ok
end,
{stop,normal};
{info,Info} ->
+ destroy_progress(App),
observer_lib:display_info_dialog(ParentFrame,Info),
{stop,normal}
end.
-init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
+%% Display progress bar only if the calling app is crashdump_viewer
+display_progress(ParentFrame,cdv) ->
+ observer_lib:display_progress_dialog(ParentFrame,
+ "Crashdump Viewer",
+ "Reading data");
+display_progress(_,_) ->
+ ok.
+destroy_progress(cdv) ->
+ observer_lib:destroy_progress_dialog();
+destroy_progress(_) ->
+ ok.
+
+init(Id,ParentFrame,Callback,App,Parent,{Title,Info,TW}) ->
Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [Title],
[{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]),
MenuBar = wxMenuBar:new(),
@@ -88,6 +103,7 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) ->
wxFrame:connect(Frame, close_window),
wxMenu:connect(Frame, command_menu_selected),
wxFrame:show(Frame),
+ destroy_progress(App),
{Frame, #state{parent=Parent,
id=Id,
frame=Frame,
@@ -133,7 +149,7 @@ handle_event(Event, _State) ->
error({unhandled_event, Event}).
handle_info(_Info, State) ->
- %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, _Info]),
+ %% io:format("~p: ~p, Handle info: ~tp~n", [?MODULE, ?LINE, _Info]),
{noreply, State}.
handle_call(Call, From, _State) ->
diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl
index 0ab0ba4315..4b43b6a840 100644
--- a/lib/observer/src/cdv_html_wx.erl
+++ b/lib/observer/src/cdv_html_wx.erl
@@ -52,8 +52,12 @@ init([ParentWin, HtmlText]) ->
init(ParentWin, HtmlText, undefined, cdv).
init(ParentWin, HtmlText, Tab, App) ->
+ %% If progress dialog is shown, remove it now - and sett cursor busy instead
+ observer_lib:destroy_progress_dialog(),
+ wx_misc:beginBusyCursor(),
HtmlWin = observer_lib:html_window(ParentWin),
wxHtmlWindow:setPage(HtmlWin,HtmlText),
+ wx_misc:endBusyCursor(),
{HtmlWin, #state{panel=HtmlWin,expand_table=Tab,app=App}}.
%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -62,7 +66,7 @@ handle_info(active, State) ->
{noreply, State};
handle_info(Info, State) ->
- io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
terminate(_Reason, _State) ->
@@ -72,7 +76,7 @@ code_change(_, _, State) ->
{ok, State}.
handle_call(Msg, _From, State) ->
- io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),
{reply, ok, State}.
handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) ->
@@ -80,7 +84,7 @@ handle_cast({detail_win_closed, Id},#state{expand_wins=Opened0}=State) ->
{noreply, State#state{expand_wins=Opened}};
handle_cast(Msg, State) ->
- io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked,
@@ -118,16 +122,17 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked,
{noreply, NewState};
handle_event(Event, State) ->
- io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),
{noreply, State}.
%%%-----------------------------------------------------------------
%%% Internal
-expand(Id,Callback,#state{expand_wins=Opened0}=State) ->
+expand(Id,Callback,#state{expand_wins=Opened0, app=App}=State) ->
Opened =
case lists:keyfind(Id,1,Opened0) of
false ->
- EW = cdv_detail_wx:start_link(Id,[],State#state.panel,Callback),
+ EW = cdv_detail_wx:start_link(Id,[],State#state.panel,
+ Callback,App),
wx_object:get_pid(EW) ! active,
[{Id,EW}|Opened0];
{_,EW} ->
diff --git a/lib/observer/src/cdv_info_wx.erl b/lib/observer/src/cdv_info_wx.erl
index 01fe6b15f2..7e416dd11a 100644
--- a/lib/observer/src/cdv_info_wx.erl
+++ b/lib/observer/src/cdv_info_wx.erl
@@ -65,7 +65,7 @@ handle_info(active, State) ->
{noreply, State};
handle_info(Info, State) ->
- io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
terminate(_Reason, _State) ->
@@ -88,11 +88,11 @@ handle_call(new_dump, _From, #state{callback=Callback,panel=Panel,
{reply, ok, State#state{fpanel=NewFPanel,trunc_warn=TW}};
handle_call(Msg, _From, State) ->
- io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),
{reply, ok, State}.
handle_cast(Msg, State) ->
- io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
handle_event(#wx{event=#wxMouse{type=left_down},userData=Target}, State) ->
@@ -108,7 +108,7 @@ handle_event(#wx{obj=Obj,event=#wxMouse{type=leave_window}},State) ->
{noreply, State};
handle_event(Event, State) ->
- io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),
{noreply, State}.
%%%-----------------------------------------------------------------
diff --git a/lib/observer/src/cdv_multi_wx.erl b/lib/observer/src/cdv_multi_wx.erl
index b511503752..93f045b1da 100644
--- a/lib/observer/src/cdv_multi_wx.erl
+++ b/lib/observer/src/cdv_multi_wx.erl
@@ -94,7 +94,7 @@ handle_info(active, State) ->
{noreply, NewState};
handle_info(Info, State) ->
- io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
terminate(_Reason, _State) ->
@@ -112,11 +112,11 @@ handle_call(new_dump, _From, State) ->
{reply, ok, NewState};
handle_call(Msg, _From, State) ->
- io:format("~p:~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p:~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),
{reply, ok, State}.
handle_cast(Msg, State) ->
- io:format("~p:~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p:~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
handle_event(#wx{event=#wxCommand{type=command_listbox_selected,
@@ -136,7 +136,7 @@ handle_event(#wx{event=#wxCommand{type=command_listbox_selected,
{noreply,NewState};
handle_event(Event, State) ->
- io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),
{noreply, State}.
%%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl
index 592150146b..f10650bbb7 100644
--- a/lib/observer/src/cdv_proc_cb.erl
+++ b/lib/observer/src/cdv_proc_cb.erl
@@ -71,7 +71,7 @@ get_details(Id, _) ->
Proplist0 =
crashdump_viewer:to_proplist(record_info(fields,proc),Info),
Proplist = [{expand_table,Tab}|Proplist0],
- Title = io_lib:format("~s (~s)",[Info#proc.name, Id]),
+ Title = io_lib:format("~ts (~s)",[Info#proc.name, Id]),
{ok,{Title,Proplist,TW}};
{error,{other_node,NodeId}} ->
Info = "The process you are searching for was residing on "
diff --git a/lib/observer/src/cdv_table_wx.erl b/lib/observer/src/cdv_table_wx.erl
index df16230b70..ba23758ea6 100644
--- a/lib/observer/src/cdv_table_wx.erl
+++ b/lib/observer/src/cdv_table_wx.erl
@@ -74,7 +74,7 @@ handle_info(active, State) ->
{noreply, State};
handle_info(Info, State) ->
- io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
terminate(_Reason, _State) ->
@@ -84,15 +84,15 @@ code_change(_, _, State) ->
{ok, State}.
handle_call(Msg, _From, State) ->
- io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),
{reply, ok, State}.
handle_cast(Msg, State) ->
- io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
handle_event(Event, State) ->
- io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),
{noreply, State}.
%%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl
index f0d90dde7c..bdcb13f22d 100644
--- a/lib/observer/src/cdv_term_cb.erl
+++ b/lib/observer/src/cdv_term_cb.erl
@@ -30,23 +30,31 @@ detail_pages() ->
[{"Term", fun init_term_page/2}].
init_term_page(ParentWin, {Type, [Term, Tab]}) ->
+ observer_lib:report_progress({ok,"Expanding term"}),
+ observer_lib:report_progress({ok,start_pulse}),
Expanded = expand(Term, true),
BinSaved = expand(Term, Tab),
+ observer_lib:report_progress({ok,stop_pulse}),
cdv_multi_wx:start_link(
ParentWin,
[{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab)}},
{"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab)}},
{"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab)}},
+ {"Format \~tw",cdv_html_wx,{Type,format_term_fun("~tw",BinSaved,Tab)}},
{"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab)}},
{"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab)}}]).
format_term_fun(Format,Term,Tab) ->
fun() ->
+ observer_lib:report_progress({ok,"Formatting term"}),
+ observer_lib:report_progress({ok,start_pulse}),
try io_lib:format(Format,[Term]) of
Str -> {expand, plain_html(Str), Tab}
catch error:badarg ->
Warning = "This term can not be formatted with " ++ Format,
observer_html_lib:warning(Warning)
+ after
+ observer_lib:report_progress({ok,stop_pulse})
end
end.
@@ -57,13 +65,7 @@ expand(['#CDVBin',Offset,Size,Pos], true) ->
{ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}),
Bin;
expand(Bin, Tab) when is_binary(Bin), not is_boolean(Tab) ->
- Size = byte_size(Bin),
- PrevSize = min(Size, 10) * 8,
- <<Preview:PrevSize, _/binary>> = Bin,
- Hash = erlang:phash2(Bin),
- Key = {Preview, Size, Hash},
- ets:insert(Tab, {Key,Bin}),
- ['#OBSBin',Preview,Size,Hash];
+ observer_lib:make_obsbin(Bin, Tab);
expand([H|T], Expand) ->
case expand(T, Expand) of
ET when is_list(ET) ->
diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl
index ebf58865e9..33c0c880b1 100644
--- a/lib/observer/src/cdv_virtual_list_wx.erl
+++ b/lib/observer/src/cdv_virtual_list_wx.erl
@@ -73,7 +73,7 @@ start_detail_win(Id) ->
"#Port"++_ ->
start_detail_win(Id, port);
_ ->
- io:format("cdv: unknown identifier: ~p~n",[Id]),
+ io:format("cdv: unknown identifier: ~tp~n",[Id]),
ignore
end.
@@ -174,7 +174,7 @@ do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened,
case lists:keyfind(Id, 1, Opened) of
false ->
Data = call(Holder, {get_data, self(), Id}),
- case cdv_detail_wx:start_link(Id, Data, Panel, Callback) of
+ case cdv_detail_wx:start_link(Id, Data, Panel, Callback, cdv) of
{error, _} -> Opened;
IW -> [{Id, IW} | Opened]
end;
@@ -195,7 +195,7 @@ call(Holder, What) when is_pid(Holder) ->
erlang:demonitor(Ref),
Res
after 5000 ->
- io:format("Hanging call ~p~n",[What]),
+ io:format("Hanging call ~tp~n",[What]),
""
end;
call(_,_) ->
@@ -214,7 +214,7 @@ handle_info(active, State) ->
{noreply, State};
handle_info(Info, State) ->
- io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]),
+ io:format("~p:~p, Unexpected info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
terminate(_Reason, #state{holder=Holder}) ->
@@ -236,7 +236,7 @@ handle_call(new_dump, _From,
{reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}};
handle_call(Msg, _From, State) ->
- io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p:~p: Unhandled call ~tp~n",[?MODULE, ?LINE, Msg]),
{reply, ok, State}.
handle_cast({start_detail_win,Id}, State) ->
@@ -248,7 +248,7 @@ handle_cast({detail_win_closed, Id},#state{detail_wins=Opened}=State) ->
{noreply, State#state{detail_wins=Opened2}};
handle_cast(Msg, State) ->
- io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
+ io:format("~p:~p: Unhandled cast ~tp~n", [?MODULE, ?LINE, Msg]),
{noreply, State}.
%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -322,7 +322,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated,
{noreply, State};
handle_event(Event, State) ->
- io:format("~p:~p: handle event ~p\n", [?MODULE, ?LINE, Event]),
+ io:format("~p:~p: handle event ~tp\n", [?MODULE, ?LINE, Event]),
{noreply, State}.
@@ -382,7 +382,7 @@ table_holder(#holder{callback=Callback, attrs=Attrs, info=Info}=S0) ->
stop ->
ok;
What ->
- io:format("Table holder got ~p~n",[What]),
+ io:format("Table holder got ~tp~n",[What]),
table_holder(S0)
end.
diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl
index 1e3fb6289e..c3f36cd689 100644
--- a/lib/observer/src/cdv_wx.erl
+++ b/lib/observer/src/cdv_wx.erl
@@ -306,7 +306,7 @@ handle_info({'EXIT', Pid, normal}, #state{server=Pid}=State) ->
{stop, normal, State};
handle_info({'EXIT', Pid, _Reason}, State) ->
- io:format("Child (~s) crashed exiting: ~p ~p~n",
+ io:format("Child (~s) crashed exiting: ~p ~tp~n",
[pid2panel(Pid, State), Pid,_Reason]),
{stop, normal, State};
@@ -412,7 +412,16 @@ load_dump(Frame,undefined) ->
error
end;
load_dump(Frame,FileName) ->
- ok = observer_lib:display_progress_dialog("Crashdump Viewer",
+ case maybe_warn_filename(FileName) of
+ continue ->
+ do_load_dump(Frame,FileName);
+ stop ->
+ error
+ end.
+
+do_load_dump(Frame,FileName) ->
+ ok = observer_lib:display_progress_dialog(wx:null(),
+ "Crashdump Viewer",
"Loading crashdump"),
crashdump_viewer:read_file(FileName),
case observer_lib:wait_for_progress() of
@@ -431,6 +440,33 @@ load_dump(Frame,FileName) ->
error
end.
+maybe_warn_filename(FileName) ->
+ case os:getenv("ERL_CRASH_DUMP_SECONDS")=="0" orelse
+ os:getenv("ERL_CRASH_DUMP_BYTES")=="0" of
+ true ->
+ continue;
+ false ->
+ DumpName = case os:getenv("ERL_CRASH_DUMP") of
+ false -> filename:absname("erl_crash.dump");
+ Name -> filename:absname(Name)
+ end,
+ case filename:absname(FileName) of
+ DumpName ->
+ Warning =
+ "WARNING: the current crashdump might be overwritten "
+ "if the crashdump_viewer node crashes.\n\n"
+ "Renaming the file before inspecting it will "
+ "remove the problem.\n\n"
+ "Do you want to continue?",
+ case observer_lib:display_yes_no_dialog(Warning) of
+ ?wxID_YES -> continue;
+ ?wxID_NO -> stop
+ end;
+ _ ->
+ continue
+ end
+ end.
+
%%%-----------------------------------------------------------------
%%% Find help document (HTML files)
get_help_doc(HelpId) ->
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index 8d70f5e2c8..95e12887cd 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -36,7 +36,6 @@
%% file: The name of the crashdump currently viewed.
%% dump_vsn: The version number of the crashdump
%% wordsize: 4 | 8, the number of bytes in a word.
-%% binaries: a gb_tree containing binaries or links to binaries in the dump
%%
%% User API
@@ -87,6 +86,7 @@
-define(max_line_size,100). % max number of bytes (i.e. characters) the
% line_head/1 function can return
-define(not_available,"N/A").
+-define(binary_size_progress_limit,10000).
%% All possible tags - use macros in order to avoid misspelling in the code
@@ -106,6 +106,8 @@
-define(internal_ets,internal_ets).
-define(loaded_modules,loaded_modules).
-define(memory,memory).
+-define(memory_map,memory_map).
+-define(memory_status,memory_status).
-define(mod,mod).
-define(no_distribution,no_distribution).
-define(node,node).
@@ -122,7 +124,7 @@
-define(visible_node,visible_node).
--record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown",binaries}).
+-record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown"}).
%%%-----------------------------------------------------------------
%%% Debugging
@@ -200,13 +202,14 @@ do_script_start(StartFun) ->
{'EXIT', Pid, normal} ->
ok;
{'EXIT', Pid, Reason} ->
- io:format("\ncdv crash: ~p\n",[Reason])
+ io:format("\ncdv crash: ~tp\n",[Reason])
end;
_ ->
- io:format("\ncdv crash: ~p\n",[unknown_reason])
+ %io:format("\ncdv crash: ~p\n",[unknown_reason])
+ ok
end;
Error ->
- io:format("\ncdv start failed: ~p\n",[Error])
+ io:format("\ncdv start failed: ~tp\n",[Error])
end.
usage() ->
@@ -305,6 +308,8 @@ expand_binary(Pos) ->
init([]) ->
ets:new(cdv_dump_index_table,[ordered_set,named_table,public]),
ets:new(cdv_reg_proc_table,[ordered_set,named_table,public]),
+ ets:new(cdv_binary_index_table,[ordered_set,named_table,public]),
+ ets:new(cdv_heap_file_chars,[ordered_set,named_table,public]),
{ok, #state{}}.
%%--------------------------------------------------------------------
@@ -340,7 +345,7 @@ handle_call(general_info,_From,State=#state{file=File}) ->
handle_call({expand_binary,{Offset,Size,Pos}},_From,State=#state{file=File}) ->
Fd = open(File),
pos_bof(Fd,Pos),
- {Bin,_Line} = get_binary(Offset,Size,val(Fd)),
+ {Bin,_Line} = get_binary(Offset,Size,bytes(Fd)),
close(Fd),
{reply,{ok,Bin},State};
handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) ->
@@ -348,9 +353,9 @@ handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) ->
Procs = procs_summary(File,WS),
{reply,{ok,Procs,TW},State};
handle_call({proc_details,Pid},_From,
- State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn,binaries=B})->
+ State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn})->
Reply =
- case get_proc_details(File,Pid,WS,DumpVsn,B) of
+ case get_proc_details(File,Pid,WS,DumpVsn) of
{ok,Proc,TW} ->
{ok,Proc,TW};
Other ->
@@ -462,9 +467,9 @@ handle_call(schedulers,_From,State=#state{file=File}) ->
%%--------------------------------------------------------------------
handle_cast({read_file,File}, _State) ->
case do_read_file(File) of
- {ok,Binaries,DumpVsn} ->
+ {ok,DumpVsn} ->
observer_lib:report_progress({ok,done}),
- {noreply, #state{file=File,binaries=Binaries,dump_vsn=DumpVsn}};
+ {noreply, #state{file=File,dump_vsn=DumpVsn}};
Error ->
end_progress(Error),
{noreply, #state{}}
@@ -512,9 +517,9 @@ unexpected(_Fd,{eof,_LastLine},_Where) ->
ok; % truncated file
unexpected(Fd,{part,What},Where) ->
skip_rest_of_line(Fd),
- io:format("WARNING: Found unexpected line in ~s:~n~s ...~n",[Where,What]);
+ io:format("WARNING: Found unexpected line in ~ts:~n~ts ...~n",[Where,What]);
unexpected(_Fd,What,Where) ->
- io:format("WARNING: Found unexpected line in ~s:~n~s~n",[Where,What]).
+ io:format("WARNING: Found unexpected line in ~ts:~n~ts~n",[Where,What]).
truncated_warning([]) ->
[];
@@ -701,9 +706,24 @@ skip(Fd,<<>>) ->
end.
-val(Fd) ->
- val(Fd, "-1").
-val(Fd, NoExist) ->
+string(Fd) ->
+ string(Fd, "-1").
+string(Fd,NoExist) ->
+ case bytes(Fd,noexist) of
+ noexist -> NoExist;
+ Val -> byte_list_to_string(Val)
+ end.
+
+byte_list_to_string(ByteList) ->
+ Bin = list_to_binary(ByteList),
+ case unicode:characters_to_list(Bin) of
+ Str when is_list(Str) -> Str;
+ _ -> ByteList
+ end.
+
+bytes(Fd) ->
+ bytes(Fd, "-1").
+bytes(Fd, NoExist) ->
case get_rest_of_line(Fd) of
{eof,[]} -> NoExist;
[] -> NoExist;
@@ -731,32 +751,6 @@ get_rest_of_line_1(Fd, <<>>, Acc) ->
eof -> {eof,lists:reverse(Acc)}
end.
-get_lines_to_empty(Fd) ->
- case get_chunk(Fd) of
- {ok,Bin} ->
- get_lines_to_empty(Fd,Bin,[],[]);
- eof ->
- []
- end.
-get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,[],Lines) ->
- put_chunk(Fd,Bin),
- lists:reverse(Lines);
-get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) ->
- get_lines_to_empty(Fd,Bin,[],[lists:reverse(Acc)|Lines]);
-get_lines_to_empty(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) ->
- get_lines_to_empty(Fd,Bin,Acc,Lines);
-get_lines_to_empty(Fd,<<$\s:8,Bin/binary>>,[],Lines) ->
- get_lines_to_empty(Fd,Bin,[],Lines);
-get_lines_to_empty(Fd,<<Char:8,Bin/binary>>,Acc,Lines) ->
- get_lines_to_empty(Fd,Bin,[Char|Acc],Lines);
-get_lines_to_empty(Fd,<<>>,Acc,Lines) ->
- case get_chunk(Fd) of
- {ok,Bin} ->
- get_lines_to_empty(Fd,Bin,Acc,Lines);
- eof ->
- lists:reverse(Lines,[lists:reverse(Acc)])
- end.
-
split(Str) ->
split($ ,Str,[]).
split(Char,Str) ->
@@ -802,21 +796,20 @@ do_read_file(File) ->
{Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1),
case Tag of
?erl_crash_dump ->
- reset_index_table(),
+ reset_tables(),
insert_index(Tag,Id,N1+1),
put_last_tag(Tag,""),
- indexify(Fd,Rest,N1),
+ DumpVsn = [list_to_integer(L) ||
+ L<-string:tokens(Id,".")],
+ AddrAdj = get_bin_addr_adj(DumpVsn),
+ indexify(Fd,AddrAdj,Rest,N1),
end_progress(),
check_if_truncated(),
- [{DumpVsn0,_}] = lookup_index(?erl_crash_dump),
- DumpVsn = [list_to_integer(L) ||
- L<-string:tokens(DumpVsn0,".")],
- Binaries = read_binaries(Fd,DumpVsn),
close(Fd),
- {ok,Binaries,DumpVsn};
+ {ok,DumpVsn};
_Other ->
R = io_lib:format(
- "~s is not an Erlang crash dump~n",
+ "~ts is not an Erlang crash dump~n",
[File]),
close(Fd),
{error,R}
@@ -824,32 +817,48 @@ do_read_file(File) ->
{ok,<<"<Erlang crash dump>",_Rest/binary>>} ->
%% old version - no longer supported
R = io_lib:format(
- "The crashdump ~s is in the pre-R10B format, "
+ "The crashdump ~ts is in the pre-R10B format, "
"which is no longer supported.~n",
[File]),
close(Fd),
{error,R};
_Other ->
R = io_lib:format(
- "~s is not an Erlang crash dump~n",
+ "~ts is not an Erlang crash dump~n",
[File]),
close(Fd),
{error,R}
end;
_other ->
- R = io_lib:format("~s is not an Erlang crash dump~n",[File]),
+ R = io_lib:format("~ts is not an Erlang crash dump~n",[File]),
{error,R}
end.
-indexify(Fd,Bin,N) ->
+indexify(Fd,AddrAdj,Bin,N) ->
case binary:match(Bin,<<"\n=">>) of
{Start,Len} ->
Pos = Start+Len,
<<_:Pos/binary,TagAndRest/binary>> = Bin,
{Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+Pos),
- insert_index(Tag,Id,N1+1), % +1 to get past newline
- put_last_tag(Tag,Id),
- indexify(Fd,Rest,N1);
+ NewPos = N1+1, % +1 to get past newline
+ case Tag of
+ ?binary ->
+ %% Binaries are stored in a separate table in
+ %% order to minimize lookup time. Key is the
+ %% translated address.
+ {HexAddr,_} = get_hex(Id),
+ Addr = HexAddr bor AddrAdj,
+ insert_binary_index(Addr,NewPos);
+ _ ->
+ insert_index(Tag,Id,NewPos)
+ end,
+ case put_last_tag(Tag,Id) of
+ {?proc_heap,LastId} ->
+ [{_,LastPos}] = lookup_index(?proc_heap,LastId),
+ ets:insert(cdv_heap_file_chars,{LastId,N+Start+1-LastPos});
+ _ -> ok
+ end,
+ indexify(Fd,AddrAdj,Rest,N1);
nomatch ->
case progress_read(Fd) of
{ok,Chunk0} when is_binary(Chunk0) ->
@@ -860,7 +869,7 @@ indexify(Fd,Bin,N) ->
_ ->
{Chunk0,N+byte_size(Bin)}
end,
- indexify(Fd,Chunk,N1);
+ indexify(Fd,AddrAdj,Chunk,N1);
eof ->
eof
end
@@ -896,7 +905,11 @@ check_if_truncated() ->
find_truncated_proc(TruncatedTag)
end.
-find_truncated_proc({?atoms,_Id}) ->
+find_truncated_proc({Tag,_Id}) when Tag==?atoms;
+ Tag==?binary;
+ Tag==?instr_data;
+ Tag==?memory_status;
+ Tag==?memory_map ->
put(truncated_proc,false);
find_truncated_proc({Tag,Pid}) ->
case is_proc_tag(Tag) of
@@ -986,7 +999,7 @@ general_info(File) ->
instr_info=InstrInfo}.
get_slogan_and_sysvsn(Fd,Acc) ->
- case val(Fd,eof) of
+ case string(Fd,eof) of
"Slogan: " ++ SloganPart when Acc==[] ->
get_slogan_and_sysvsn(Fd,[SloganPart]);
"System version: " ++ SystemVsn ->
@@ -1000,14 +1013,14 @@ get_slogan_and_sysvsn(Fd,Acc) ->
get_general_info(Fd,GenInfo) ->
case line_head(Fd) of
"Compiled" ->
- get_general_info(Fd,GenInfo#general_info{compile_time=val(Fd)});
+ get_general_info(Fd,GenInfo#general_info{compile_time=bytes(Fd)});
"Taints" ->
- Val = case val(Fd) of "-1" -> "(none)"; Line -> Line end,
+ Val = case string(Fd) of "-1" -> "(none)"; Line -> Line end,
get_general_info(Fd,GenInfo#general_info{taints=Val});
"Atoms" ->
- get_general_info(Fd,GenInfo#general_info{num_atoms=val(Fd)});
+ get_general_info(Fd,GenInfo#general_info{num_atoms=bytes(Fd)});
"Calling Thread" ->
- get_general_info(Fd,GenInfo#general_info{thread=val(Fd)});
+ get_general_info(Fd,GenInfo#general_info{thread=bytes(Fd)});
"=" ++ _next_tag ->
GenInfo;
Other ->
@@ -1045,14 +1058,14 @@ procs_summary(File,WS) ->
%%-----------------------------------------------------------------
%% Page with one process
-get_proc_details(File,Pid,WS,DumpVsn,Binaries) ->
+get_proc_details(File,Pid,WS,DumpVsn) ->
case lookup_index(?proc,Pid) of
[{_,Start}] ->
Fd = open(File),
{{Stack,MsgQ,Dict},TW} =
case truncated_warning([{?proc,Pid}]) of
[] ->
- {expand_memory(Fd,Pid,DumpVsn,Binaries),[]};
+ {expand_memory(Fd,Pid,DumpVsn),[]};
TW0 ->
{{[],[],[]},TW0}
end,
@@ -1068,15 +1081,15 @@ get_proc_details(File,Pid,WS,DumpVsn,Binaries) ->
get_procinfo(Fd,Fun,Proc,WS) ->
case line_head(Fd) of
"State" ->
- State = case val(Fd) of
+ State = case bytes(Fd) of
"Garbing" -> "Garbing\n(limited info)";
State0 -> State0
end,
get_procinfo(Fd,Fun,Proc#proc{state=State},WS);
"Name" ->
- get_procinfo(Fd,Fun,Proc#proc{name=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{name=string(Fd)},WS);
"Spawned as" ->
- IF = val(Fd),
+ IF = string(Fd),
case Proc#proc.name of
undefined ->
get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF},WS);
@@ -1085,17 +1098,17 @@ get_procinfo(Fd,Fun,Proc,WS) ->
end;
"Message queue length" ->
%% stored as integer so we can sort on it
- get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(val(Fd))},WS);
+ get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(bytes(Fd))},WS);
"Reductions" ->
%% stored as integer so we can sort on it
- get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(val(Fd))},WS);
+ get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(bytes(Fd))},WS);
"Stack+heap" ->
%% stored as integer so we can sort on it
get_procinfo(Fd,Fun,Proc#proc{stack_heap=
- list_to_integer(val(Fd))*WS},WS);
+ list_to_integer(bytes(Fd))*WS},WS);
"Memory" ->
%% stored as integer so we can sort on it
- get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))},WS);
+ get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(bytes(Fd))},WS);
{eof,_} ->
Proc; % truncated file
Other ->
@@ -1117,67 +1130,67 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) ->
case LineHead of
%% - START - moved from get_procinfo -
"Spawned by" ->
- case val(Fd) of
+ case bytes(Fd) of
"[]" ->
get_procinfo(Fd,Fun,Proc,WS);
Parent ->
get_procinfo(Fd,Fun,Proc#proc{parent=Parent},WS)
end;
"Started" ->
- get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{start_time=bytes(Fd)},WS);
"Last scheduled in for" ->
get_procinfo(Fd,Fun,Proc#proc{current_func=
{"Last scheduled in for",
- val(Fd)}},WS);
+ string(Fd)}},WS);
"Current call" ->
get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call",
- val(Fd)}},WS);
+ string(Fd)}},WS);
"Number of heap fragments" ->
- get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=bytes(Fd)},WS);
"Heap fragment data" ->
- get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=bytes(Fd)},WS);
"OldHeap" ->
- Bytes = list_to_integer(val(Fd))*WS,
+ Bytes = list_to_integer(bytes(Fd))*WS,
get_procinfo(Fd,Fun,Proc#proc{old_heap=Bytes},WS);
"Heap unused" ->
- Bytes = list_to_integer(val(Fd))*WS,
+ Bytes = list_to_integer(bytes(Fd))*WS,
get_procinfo(Fd,Fun,Proc#proc{heap_unused=Bytes},WS);
"OldHeap unused" ->
- Bytes = list_to_integer(val(Fd))*WS,
+ Bytes = list_to_integer(bytes(Fd))*WS,
get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=Bytes},WS);
"New heap start" ->
- get_procinfo(Fd,Fun,Proc#proc{new_heap_start=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{new_heap_start=bytes(Fd)},WS);
"New heap top" ->
- get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{new_heap_top=bytes(Fd)},WS);
"Stack top" ->
- get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{stack_top=bytes(Fd)},WS);
"Stack end" ->
- get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{stack_end=bytes(Fd)},WS);
"Old heap start" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap_start=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{old_heap_start=bytes(Fd)},WS);
"Old heap top" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap_top=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{old_heap_top=bytes(Fd)},WS);
"Old heap end" ->
- get_procinfo(Fd,Fun,Proc#proc{old_heap_end=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{old_heap_end=bytes(Fd)},WS);
%% - END - moved from get_procinfo -
"Last calls" ->
- get_procinfo(Fd,Fun,Proc#proc{last_calls=get_lines_to_empty(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{last_calls=get_last_calls(Fd)},WS);
"Link list" ->
- {Links,Monitors,MonitoredBy} = parse_link_list(val(Fd),[],[],[]),
+ {Links,Monitors,MonitoredBy} = parse_link_list(bytes(Fd),[],[],[]),
get_procinfo(Fd,Fun,Proc#proc{links=Links,
monitors=Monitors,
mon_by=MonitoredBy},WS);
"Program counter" ->
- get_procinfo(Fd,Fun,Proc#proc{prog_count=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{prog_count=string(Fd)},WS);
"CP" ->
- get_procinfo(Fd,Fun,Proc#proc{cp=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{cp=string(Fd)},WS);
"arity = " ++ Arity ->
%%! Temporary workaround
get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"},WS);
"Run queue" ->
- get_procinfo(Fd,Fun,Proc#proc{run_queue=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{run_queue=string(Fd)},WS);
"Internal State" ->
- get_procinfo(Fd,Fun,Proc#proc{int_state=val(Fd)},WS);
+ get_procinfo(Fd,Fun,Proc#proc{int_state=string(Fd)},WS);
"=" ++ _next_tag ->
Proc;
Other ->
@@ -1185,6 +1198,66 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) ->
get_procinfo(Fd,Fun,Proc,WS)
end.
+%% The end of the 'Last calls' section is meant to be an empty line,
+%% but in some cases this is not the case, so we also need to look for
+%% the next heading which currently (OTP-20.1) can be "Link list: ",
+%% "Dictionary: " or "Reductions: ". We do this by looking for ": "
+%% and when found, pushing the heading back into the saved chunk.
+%%
+%% Note that the 'Last calls' section is only present if the
+%% 'save_calls' process flag is set.
+get_last_calls(Fd) ->
+ case get_chunk(Fd) of
+ {ok,Bin} ->
+ get_last_calls(Fd,Bin,[],[]);
+ eof ->
+ []
+ end.
+get_last_calls(Fd,<<$\n:8,Bin/binary>>,[],Lines) ->
+ %% Empty line - we're done
+ put_chunk(Fd,Bin),
+ lists:reverse(Lines);
+get_last_calls(Fd,<<$::8>>,Acc,Lines) ->
+ case get_chunk(Fd) of
+ {ok,Bin} ->
+ %% Could be a colon followed by a space - see next function clause
+ get_last_calls(Fd,<<$::8,Bin/binary>>,Acc,Lines);
+ eof ->
+ %% Truncated here - either we've got the next heading, or
+ %% it was truncated in a last call function, in which case
+ %% we note that it was truncated
+ case byte_list_to_string(lists:reverse(Acc)) of
+ NextHeading when NextHeading=="Link list";
+ NextHeading=="Dictionary";
+ NextHeading=="Reductions" ->
+ put_chunk(Fd,list_to_binary(NextHeading++":")),
+ lists:reverse(Lines);
+ LastCallFunction->
+ lists:reverse(Lines,[LastCallFunction++":...(truncated)"])
+ end
+ end;
+get_last_calls(Fd,<<$\::8,$\s:8,Bin/binary>>,Acc,Lines) ->
+ %% ": " - means we have the next heading in Acc - save it back
+ %% into the chunk and return the lines we've found
+ HeadingBin = list_to_binary(lists:reverse(Acc,[$:])),
+ put_chunk(Fd,<<HeadingBin/binary,Bin/binary>>),
+ lists:reverse(Lines);
+get_last_calls(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) ->
+ get_last_calls(Fd,Bin,[],[byte_list_to_string(lists:reverse(Acc))|Lines]);
+get_last_calls(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) ->
+ get_last_calls(Fd,Bin,Acc,Lines);
+get_last_calls(Fd,<<$\s:8,Bin/binary>>,[],Lines) ->
+ get_last_calls(Fd,Bin,[],Lines);
+get_last_calls(Fd,<<Char:8,Bin/binary>>,Acc,Lines) ->
+ get_last_calls(Fd,Bin,[Char|Acc],Lines);
+get_last_calls(Fd,<<>>,Acc,Lines) ->
+ case get_chunk(Fd) of
+ {ok,Bin} ->
+ get_last_calls(Fd,Bin,Acc,Lines);
+ eof ->
+ lists:reverse(Lines,[byte_list_to_string(lists:reverse(Acc))])
+ end.
+
parse_link_list([SB|Str],Links,Monitors,MonitoredBy) when SB==$[; SB==$] ->
parse_link_list(Str,Links,Monitors,MonitoredBy);
parse_link_list("#Port"++_=Str,Links,Monitors,MonitoredBy) ->
@@ -1204,7 +1277,7 @@ parse_link_list(", "++Rest,Links,Monitors,MonitoredBy) ->
parse_link_list([],Links,Monitors,MonitoredBy) ->
{lists:reverse(Links),lists:reverse(Monitors),lists:reverse(MonitoredBy)};
parse_link_list(Unexpected,Links,Monitors,MonitoredBy) ->
- io:format("WARNING: found unexpected data in link list:~n~s~n",[Unexpected]),
+ io:format("WARNING: found unexpected data in link list:~n~ts~n",[Unexpected]),
parse_link_list([],Links,Monitors,MonitoredBy).
@@ -1310,10 +1383,10 @@ maybe_other_node2(Channel) ->
end.
-expand_memory(Fd,Pid,DumpVsn,Binaries) ->
+expand_memory(Fd,Pid,DumpVsn) ->
BinAddrAdj = get_bin_addr_adj(DumpVsn),
put(fd,Fd),
- Dict = read_heap(Fd,Pid,BinAddrAdj,Binaries),
+ Dict = read_heap(Fd,Pid,BinAddrAdj,gb_trees:empty()),
Expanded = {read_stack_dump(Fd,Pid,BinAddrAdj,Dict),
read_messages(Fd,Pid,BinAddrAdj,Dict),
read_dictionary(Fd,Pid,BinAddrAdj,Dict)},
@@ -1331,25 +1404,6 @@ get_bin_addr_adj(_) ->
0.
%%%
-%%% Read binaries.
-%%%
-read_binaries(Fd,DumpVsn) ->
- AllBinaries = lookup_index(?binary),
- AddrAdj = get_bin_addr_adj(DumpVsn),
- Fun = fun({Addr0,Pos},Dict0) ->
- pos_bof(Fd,Pos),
- {HexAddr,_} = get_hex(Addr0),
- Addr = HexAddr bor AddrAdj,
- Bin =
- case line_head(Fd) of
- {eof,_} -> '#CDVTruncatedBinary';
- _Size -> {'#CDVBin',Pos}
- end,
- gb_trees:enter(Addr,Bin,Dict0)
- end,
- progress_foldl("Processing binaries",Fun,gb_trees:empty(),AllBinaries).
-
-%%%
%%% Read top level section.
%%%
@@ -1363,7 +1417,7 @@ read_stack_dump(Fd,Pid,BinAddrAdj,Dict) ->
end.
read_stack_dump1(Fd,BinAddrAdj,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
- case val(Fd) of
+ case bytes(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
@@ -1391,7 +1445,7 @@ read_messages(Fd,Pid,BinAddrAdj,Dict) ->
end.
read_messages1(Fd,BinAddrAdj,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
- case val(Fd) of
+ case bytes(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
@@ -1419,7 +1473,7 @@ read_dictionary(Fd,Pid,BinAddrAdj,Dict) ->
end.
read_dictionary1(Fd,BinAddrAdj,Dict,Acc) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
- case val(Fd) of
+ case bytes(Fd) of
"=" ++ _next_tag ->
lists:reverse(Acc);
Line ->
@@ -1439,6 +1493,8 @@ parse_dictionary(Line0, BinAddrAdj, D) ->
read_heap(Fd,Pid,BinAddrAdj,Dict0) ->
case lookup_index(?proc_heap,Pid) of
[{_,Pos}] ->
+ [{_,Chars}] = ets:lookup(cdv_heap_file_chars,Pid),
+ init_progress("Reading process heap",Chars),
pos_bof(Fd,Pos),
read_heap(BinAddrAdj,Dict0);
[] ->
@@ -1449,13 +1505,16 @@ read_heap(BinAddrAdj,Dict0) ->
%% This function is never called if the dump is truncated in {?proc_heap,Pid}
case get(fd) of
end_of_heap ->
+ end_progress(),
Dict0;
Fd ->
- case val(Fd) of
+ case bytes(Fd) of
"=" ++ _next_tag ->
+ end_progress(),
put(fd, end_of_heap),
Dict0;
Line ->
+ update_progress(length(Line)+1),
Dict = parse(Line,BinAddrAdj,Dict0),
read_heap(BinAddrAdj,Dict)
end
@@ -1498,42 +1557,42 @@ get_portinfo(Fd,Port) ->
case line_head(Fd) of
"Slot" ->
%% stored as integer so we can sort on it
- get_portinfo(Fd,Port#port{slot=list_to_integer(val(Fd))});
+ get_portinfo(Fd,Port#port{slot=list_to_integer(bytes(Fd))});
"Connected" ->
%% stored as pid so we can sort on it
- Connected0 = val(Fd),
+ Connected0 = bytes(Fd),
Connected =
try list_to_pid(Connected0)
catch error:badarg -> Connected0
end,
get_portinfo(Fd,Port#port{connected=Connected});
"Links" ->
- Pids = split_pid_list_no_space(val(Fd)),
+ Pids = split_pid_list_no_space(bytes(Fd)),
Links = [{Pid,Pid} || Pid <- Pids],
get_portinfo(Fd,Port#port{links=Links});
"Registered as" ->
- get_portinfo(Fd,Port#port{name=val(Fd)});
+ get_portinfo(Fd,Port#port{name=string(Fd)});
"Monitors" ->
- Monitors0 = string:tokens(val(Fd),"()"),
+ Monitors0 = string:tokens(bytes(Fd),"()"),
Monitors = [begin
[Pid,Ref] = string:tokens(Mon,","),
{Pid,Pid++" ("++Ref++")"}
end || Mon <- Monitors0],
get_portinfo(Fd,Port#port{monitors=Monitors});
"Port controls linked-in driver" ->
- Str = lists:flatten(["Linked in driver: " | val(Fd)]),
+ Str = lists:flatten(["Linked in driver: " | string(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
"Port controls forker process" ->
- Str = lists:flatten(["Forker process: " | val(Fd)]),
+ Str = lists:flatten(["Forker process: " | string(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
"Port controls external process" ->
- Str = lists:flatten(["External proc: " | val(Fd)]),
+ Str = lists:flatten(["External proc: " | string(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
"Port is a file" ->
- Str = lists:flatten(["File: "| val(Fd)]),
+ Str = lists:flatten(["File: "| string(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
"Port is UNIX fd not opened by emulator" ->
- Str = lists:flatten(["UNIX fd not opened by emulator: "| val(Fd)]),
+ Str = lists:flatten(["UNIX fd not opened by emulator: "| string(Fd)]),
get_portinfo(Fd,Port#port{controls=Str});
"=" ++ _next_tag ->
Port;
@@ -1566,23 +1625,23 @@ tab_is_named(#ets_table{}) -> "no".
get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) ->
case line_head(Fd) of
"Slot" ->
- get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(val(Fd))},WS);
+ get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(bytes(Fd))},WS);
"Table" ->
- get_etsinfo(Fd,EtsTable#ets_table{id=val(Fd)},WS);
+ get_etsinfo(Fd,EtsTable#ets_table{id=string(Fd)},WS);
"Name" ->
- get_etsinfo(Fd,EtsTable#ets_table{name=val(Fd)},WS);
+ get_etsinfo(Fd,EtsTable#ets_table{name=string(Fd)},WS);
"Ordered set (AVL tree), Elements" ->
skip_rest_of_line(Fd),
get_etsinfo(Fd,EtsTable#ets_table{data_type="tree"},WS);
"Buckets" ->
%% A bug in erl_db_hash.c prints a space after the buckets
%% - need to strip the string to make list_to_integer/1 happy.
- Buckets = list_to_integer(string:strip(val(Fd))),
+ Buckets = list_to_integer(string:strip(bytes(Fd))),
get_etsinfo(Fd,EtsTable#ets_table{buckets=Buckets},WS);
"Objects" ->
- get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(val(Fd))},WS);
+ get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(bytes(Fd))},WS);
"Words" ->
- Words = list_to_integer(val(Fd)),
+ Words = list_to_integer(bytes(Fd)),
Bytes =
case Words of
-1 -> -1; % probably truncated
@@ -1592,37 +1651,37 @@ get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) ->
"=" ++ _next_tag ->
EtsTable;
"Chain Length Min" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_min=>Val}},WS);
"Chain Length Avg" ->
- Val = try list_to_float(string:strip(val(Fd))) catch _:_ -> "-" end,
+ Val = try list_to_float(string:strip(bytes(Fd))) catch _:_ -> "-" end,
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_avg=>Val}},WS);
"Chain Length Max" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_max=>Val}},WS);
"Chain Length Std Dev" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_stddev=>Val}},WS);
"Chain Length Expected Std Dev" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_exp_stddev=>Val}},WS);
"Fixed" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{fixed=>Val}},WS);
"Type" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{data_type=Val},WS);
"Protection" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{protection=>Val}},WS);
"Compressed" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{compressed=>Val}},WS);
"Write Concurrency" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{write_c=>Val}},WS);
"Read Concurrency" ->
- Val = val(Fd),
+ Val = bytes(Fd),
get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{read_c=>Val}},WS);
Other ->
unexpected(Fd,Other,"ETS info"),
@@ -1672,9 +1731,9 @@ get_timerinfo(Fd,Id) ->
get_timerinfo_1(Fd,Timer) ->
case line_head(Fd) of
"Message" ->
- get_timerinfo_1(Fd,Timer#timer{msg=val(Fd)});
+ get_timerinfo_1(Fd,Timer#timer{msg=string(Fd)});
"Time left" ->
- TimeLeft = list_to_integer(val(Fd) -- " ms"),
+ TimeLeft = list_to_integer(bytes(Fd) -- " ms"),
get_timerinfo_1(Fd,Timer#timer{time=TimeLeft});
"=" ++ _next_tag ->
Timer;
@@ -1743,37 +1802,37 @@ get_nodeinfo(Fd,Channel,Type,Start) ->
get_nodeinfo(Fd,Nod) ->
case line_head(Fd) of
"Name" ->
- get_nodeinfo(Fd,Nod#nod{name=val(Fd)});
+ get_nodeinfo(Fd,Nod#nod{name=bytes(Fd)});
"Controller" ->
- get_nodeinfo(Fd,Nod#nod{controller=val(Fd)});
+ get_nodeinfo(Fd,Nod#nod{controller=bytes(Fd)});
"Creation" ->
%% Throwing away elements like "(refc=1)", which might be
%% printed from a debug compiled emulator.
Creations = lists:flatmap(fun(C) -> try [list_to_integer(C)]
catch error:badarg -> []
end
- end, string:tokens(val(Fd)," ")),
+ end, string:tokens(bytes(Fd)," ")),
get_nodeinfo(Fd,Nod#nod{creation={creations,Creations}});
"Remote link" ->
- Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>"
+ Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"
{Local,Remote} = split(Procs),
Str = Local++" <-> "++Remote,
NewRemLinks = [{Local,Str} | Nod#nod.remote_links],
get_nodeinfo(Fd,Nod#nod{remote_links=NewRemLinks});
"Remote monitoring" ->
- Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>"
+ Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"
{Local,Remote} = split(Procs),
Str = Local++" -> "++Remote,
NewRemMon = [{Local,Str} | Nod#nod.remote_mon],
get_nodeinfo(Fd,Nod#nod{remote_mon=NewRemMon});
"Remotely monitored by" ->
- Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>"
+ Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"
{Local,Remote} = split(Procs),
Str = Local++" <- "++Remote,
NewRemMonBy = [{Local,Str} | Nod#nod.remote_mon_by],
get_nodeinfo(Fd,Nod#nod{remote_mon_by=NewRemMonBy});
"Error" ->
- get_nodeinfo(Fd,Nod#nod{error="ERROR: "++val(Fd)});
+ get_nodeinfo(Fd,Nod#nod{error="ERROR: "++string(Fd)});
"=" ++ _next_tag ->
Nod;
Other ->
@@ -1817,9 +1876,9 @@ loaded_mods(File) ->
get_loaded_mod_totals(Fd,{CC,OC}) ->
case line_head(Fd) of
"Current code" ->
- get_loaded_mod_totals(Fd,{val(Fd),OC});
+ get_loaded_mod_totals(Fd,{bytes(Fd),OC});
"Old code" ->
- get_loaded_mod_totals(Fd,{CC,val(Fd)});
+ get_loaded_mod_totals(Fd,{CC,bytes(Fd)});
"=" ++ _next_tag ->
{CC,OC};
Other ->
@@ -1830,10 +1889,10 @@ get_loaded_mod_totals(Fd,{CC,OC}) ->
get_loaded_mod_info(Fd,LM,Fun) ->
case line_head(Fd) of
"Current size" ->
- CS = list_to_integer(val(Fd)),
+ CS = list_to_integer(bytes(Fd)),
get_loaded_mod_info(Fd,LM#loaded_mod{current_size=CS},Fun);
"Old size" ->
- OS = list_to_integer(val(Fd)),
+ OS = list_to_integer(bytes(Fd)),
get_loaded_mod_info(Fd,LM#loaded_mod{old_size=OS},Fun);
"=" ++ _next_tag ->
LM;
@@ -1849,16 +1908,16 @@ main_modinfo(_Fd,LM,_LineHead) ->
all_modinfo(Fd,LM,LineHead) ->
case LineHead of
"Current attributes" ->
- Str = hex_to_str(val(Fd,"")),
+ Str = hex_to_str(bytes(Fd,"")),
LM#loaded_mod{current_attrib=Str};
"Current compilation info" ->
- Str = hex_to_str(val(Fd,"")),
+ Str = hex_to_str(bytes(Fd,"")),
LM#loaded_mod{current_comp_info=Str};
"Old attributes" ->
- Str = hex_to_str(val(Fd,"")),
+ Str = hex_to_str(bytes(Fd,"")),
LM#loaded_mod{old_attrib=Str};
"Old compilation info" ->
- Str = hex_to_str(val(Fd,"")),
+ Str = hex_to_str(bytes(Fd,"")),
LM#loaded_mod{old_comp_info=Str};
Other ->
unexpected(Fd,Other,"loaded modules info"),
@@ -1868,7 +1927,7 @@ all_modinfo(Fd,LM,LineHead) ->
hex_to_str(Hex) ->
Term = hex_to_term(Hex,[]),
- io_lib:format("~p~n",[Term]).
+ io_lib:format("~tp~n",[Term]).
hex_to_term([X,Y|Hex],Acc) ->
MS = hex_to_dec([X]),
@@ -1909,17 +1968,17 @@ funs(File) ->
get_funinfo(Fd,Fu) ->
case line_head(Fd) of
"Module" ->
- get_funinfo(Fd,Fu#fu{module=val(Fd)});
+ get_funinfo(Fd,Fu#fu{module=bytes(Fd)});
"Uniq" ->
- get_funinfo(Fd,Fu#fu{uniq=list_to_integer(val(Fd))});
+ get_funinfo(Fd,Fu#fu{uniq=list_to_integer(bytes(Fd))});
"Index" ->
- get_funinfo(Fd,Fu#fu{index=list_to_integer(val(Fd))});
+ get_funinfo(Fd,Fu#fu{index=list_to_integer(bytes(Fd))});
"Address" ->
- get_funinfo(Fd,Fu#fu{address=val(Fd)});
+ get_funinfo(Fd,Fu#fu{address=bytes(Fd)});
"Native_address" ->
- get_funinfo(Fd,Fu#fu{native_address=val(Fd)});
+ get_funinfo(Fd,Fu#fu{native_address=bytes(Fd)});
"Refc" ->
- get_funinfo(Fd,Fu#fu{refc=list_to_integer(val(Fd))});
+ get_funinfo(Fd,Fu#fu{refc=list_to_integer(bytes(Fd))});
"=" ++ _next_tag ->
Fu;
Other ->
@@ -1999,7 +2058,7 @@ get_meminfo(Fd,Acc) ->
{eof,_last_line} ->
lists:reverse(Acc);
Key ->
- get_meminfo(Fd,[{list_to_atom(Key),val(Fd)}|Acc])
+ get_meminfo(Fd,[{list_to_atom(Key),bytes(Fd)}|Acc])
end.
%%-----------------------------------------------------------------
@@ -2023,7 +2082,7 @@ get_allocareainfo(Fd,Acc) ->
{eof,_last_line} ->
lists:reverse(Acc);
Key ->
- Val = val(Fd),
+ Val = bytes(Fd),
AllocInfo =
case split(Val) of
{Alloc,[]} ->
@@ -2061,7 +2120,7 @@ get_allocatorinfo1(Fd,Acc,Max) ->
{eof,_last_line} ->
pad_and_reverse(Acc,Max,[]);
Key ->
- Values = get_all_vals(val(Fd),[]),
+ Values = get_all_vals(bytes(Fd),[]),
L = length(Values),
Max1 = if L > Max -> L; true -> Max end,
get_allocatorinfo1(Fd,[{Key,Values}|Acc],Max1)
@@ -2316,13 +2375,13 @@ get_hashtableinfo(Fd,Name,Start) ->
get_hashtableinfo1(Fd,HashTable) ->
case line_head(Fd) of
"size" ->
- get_hashtableinfo1(Fd,HashTable#hash_table{size=val(Fd)});
+ get_hashtableinfo1(Fd,HashTable#hash_table{size=bytes(Fd)});
"used" ->
- get_hashtableinfo1(Fd,HashTable#hash_table{used=val(Fd)});
+ get_hashtableinfo1(Fd,HashTable#hash_table{used=bytes(Fd)});
"objs" ->
- get_hashtableinfo1(Fd,HashTable#hash_table{objs=val(Fd)});
+ get_hashtableinfo1(Fd,HashTable#hash_table{objs=bytes(Fd)});
"depth" ->
- get_hashtableinfo1(Fd,HashTable#hash_table{depth=val(Fd)});
+ get_hashtableinfo1(Fd,HashTable#hash_table{depth=bytes(Fd)});
"=" ++ _next_tag ->
HashTable;
Other ->
@@ -2353,15 +2412,15 @@ get_indextableinfo(Fd,Name,Start) ->
get_indextableinfo1(Fd,IndexTable) ->
case line_head(Fd) of
"size" ->
- get_indextableinfo1(Fd,IndexTable#index_table{size=val(Fd)});
+ get_indextableinfo1(Fd,IndexTable#index_table{size=bytes(Fd)});
"used" ->
- get_indextableinfo1(Fd,IndexTable#index_table{used=val(Fd)});
+ get_indextableinfo1(Fd,IndexTable#index_table{used=bytes(Fd)});
"limit" ->
- get_indextableinfo1(Fd,IndexTable#index_table{limit=val(Fd)});
+ get_indextableinfo1(Fd,IndexTable#index_table{limit=bytes(Fd)});
"rate" ->
- get_indextableinfo1(Fd,IndexTable#index_table{rate=val(Fd)});
+ get_indextableinfo1(Fd,IndexTable#index_table{rate=bytes(Fd)});
"entries" ->
- get_indextableinfo1(Fd,IndexTable#index_table{entries=val(Fd)});
+ get_indextableinfo1(Fd,IndexTable#index_table{entries=bytes(Fd)});
"=" ++ _next_tag ->
IndexTable;
Other ->
@@ -2393,45 +2452,45 @@ get_schedulerinfo(Fd,Name,Start) ->
get_schedulerinfo1(Fd,Sched=#sched{details=Ds}) ->
case line_head(Fd) of
"Current Process" ->
- get_schedulerinfo1(Fd,Sched#sched{process=val(Fd, "None")});
+ get_schedulerinfo1(Fd,Sched#sched{process=bytes(Fd, "None")});
"Current Port" ->
- get_schedulerinfo1(Fd,Sched#sched{port=val(Fd, "None")});
+ get_schedulerinfo1(Fd,Sched#sched{port=bytes(Fd, "None")});
"Run Queue Max Length" ->
- RQMax = list_to_integer(val(Fd)),
+ RQMax = list_to_integer(bytes(Fd)),
RQ = RQMax + Sched#sched.run_q,
get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_max=>RQMax}});
"Run Queue High Length" ->
- RQHigh = list_to_integer(val(Fd)),
+ RQHigh = list_to_integer(bytes(Fd)),
RQ = RQHigh + Sched#sched.run_q,
get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_high=>RQHigh}});
"Run Queue Normal Length" ->
- RQNorm = list_to_integer(val(Fd)),
+ RQNorm = list_to_integer(bytes(Fd)),
RQ = RQNorm + Sched#sched.run_q,
get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_norm=>RQNorm}});
"Run Queue Low Length" ->
- RQLow = list_to_integer(val(Fd)),
+ RQLow = list_to_integer(bytes(Fd)),
RQ = RQLow + Sched#sched.run_q,
get_schedulerinfo1(Fd,Sched#sched{run_q=RQ, details=Ds#{runq_low=>RQLow}});
"Run Queue Port Length" ->
- RQ = list_to_integer(val(Fd)),
+ RQ = list_to_integer(bytes(Fd)),
get_schedulerinfo1(Fd,Sched#sched{port_q=RQ});
"Scheduler Sleep Info Flags" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_info=>val(Fd, "None")}});
+ get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_info=>bytes(Fd, "None")}});
"Scheduler Sleep Info Aux Work" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_aux=>val(Fd, "None")}});
+ get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_aux=>bytes(Fd, "None")}});
"Run Queue Flags" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{runq_flags=>val(Fd, "None")}});
+ get_schedulerinfo1(Fd,Sched#sched{details=Ds#{runq_flags=>bytes(Fd, "None")}});
"Current Process State" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_state=>val(Fd)}});
+ get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_state=>bytes(Fd)}});
"Current Process Internal State" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_int_state=>val(Fd)}});
+ get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_int_state=>bytes(Fd)}});
"Current Process Program counter" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_prg_cnt=>val(Fd)}});
+ get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_prg_cnt=>string(Fd)}});
"Current Process CP" ->
- get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_cp=>val(Fd)}});
+ get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_cp=>string(Fd)}});
"Current Process Limited Stack Trace" ->
%% If there shall be last in scheduler information block
Sched#sched{details=get_limited_stack(Fd, 0, Ds)};
@@ -2443,7 +2502,7 @@ get_schedulerinfo1(Fd,Sched=#sched{details=Ds}) ->
end.
get_limited_stack(Fd, N, Ds) ->
- case val(Fd) of
+ case string(Fd) of
Addr = "0x" ++ _ ->
get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Addr});
"=" ++ _next_tag ->
@@ -2509,9 +2568,9 @@ parse_heap_term("Yc"++Line0, Addr, BinAddrAdj, D0) -> %Reference-counted binary.
{Offset,":"++Line2} = get_hex(Line1),
{Sz,Line} = get_hex(Line2),
Binp = Binp0 bor BinAddrAdj,
- Term = case gb_trees:lookup(Binp, D0) of
- {value,Bin} -> cdvbin(Offset,Sz,Bin);
- none -> '#CDVNonexistingBinary'
+ Term = case lookup_binary_index(Binp) of
+ [{_,Start}] -> cdvbin(Offset,Sz,{'#CDVBin',Start});
+ [] -> '#CDVNonexistingBinary'
end,
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D};
@@ -2520,15 +2579,14 @@ parse_heap_term("Ys"++Line0, Addr, BinAddrAdj, D0) -> %Sub binary.
{Offset,":"++Line2} = get_hex(Line1),
{Sz,Line} = get_hex(Line2),
Binp = Binp0 bor BinAddrAdj,
- Term = case gb_trees:lookup(Binp, D0) of
- {value,Bin} -> cdvbin(Offset,Sz,Bin);
- none when Binp0=/=Binp ->
+ Term = case lookup_binary_index(Binp) of
+ [{_,Start}] -> cdvbin(Offset,Sz,{'#CDVBin',Start});
+ [] ->
%% Might it be on the heap?
- case gb_trees:lookup(Binp0, D0) of
+ case gb_trees:lookup(Binp, D0) of
{value,Bin} -> cdvbin(Offset,Sz,Bin);
none -> '#CDVNonexistingBinary'
- end;
- none -> '#CDVNonexistingBinary'
+ end
end,
D = gb_trees:insert(Addr, Term, D0),
{Term,Line,D}.
@@ -2595,7 +2653,7 @@ skip_dist_ext([C|Cs], KeptCs) ->
parse_atom([$A|Line0], D) ->
{N,":"++Line1} = get_hex(Line0),
{Chars, Line} = get_chars(N, Line1),
- {list_to_atom(Chars), Line, D}.
+ {binary_to_atom(list_to_binary(Chars),utf8), Line, D}.
parse_atom_translation_table(0, Line0, As) ->
{list_to_tuple(lists:reverse(As)), Line0};
@@ -2614,11 +2672,12 @@ deref_ptr(Ptr, Line, BinAddrAdj, D0) ->
end_of_heap ->
{['#CDVIncompleteHeap'],Line,D0};
Fd ->
- case val(Fd) of
+ case bytes(Fd) of
"="++_ ->
put(fd, end_of_heap),
deref_ptr(Ptr, Line, BinAddrAdj, D0);
L ->
+ update_progress(length(L)+1),
D = parse(L, BinAddrAdj, D0),
deref_ptr(Ptr, Line, BinAddrAdj, D)
end
@@ -2684,19 +2743,33 @@ get_label([H|T], Acc) ->
get_label(T, [H|Acc]).
get_binary(Line0) ->
- {N,":"++Line} = get_hex(Line0),
- do_get_binary(N, Line, []).
+ case get_hex(Line0) of
+ {N,":"++Line} ->
+ do_get_binary(N, Line, [], false);
+ _ ->
+ {'#CDVTruncatedBinary',[]}
+ end.
get_binary(Offset,Size,Line0) ->
- {_N,":"++Line} = get_hex(Line0),
- do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), []).
-
-do_get_binary(0, Line, Acc) ->
+ case get_hex(Line0) of
+ {_N,":"++Line} ->
+ Progress = Size>?binary_size_progress_limit,
+ Progress andalso init_progress("Reading binary",Size),
+ do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), [],
+ Progress);
+ _ ->
+ {'#CDVTruncatedBinary',[]}
+ end.
+
+do_get_binary(0, Line, Acc, Progress) ->
+ Progress andalso end_progress(),
{list_to_binary(lists:reverse(Acc)),Line};
-do_get_binary(N, [A,B|Line], Acc) ->
+do_get_binary(N, [A,B|Line], Acc, Progress) ->
Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B),
- do_get_binary(N-1, Line, [Byte|Acc]);
-do_get_binary(_N, [], _Acc) ->
+ Progress andalso update_progress(),
+ do_get_binary(N-1, Line, [Byte|Acc], Progress);
+do_get_binary(_N, [], _Acc, Progress) ->
+ Progress andalso end_progress(),
{'#CDVTruncatedBinary',[]}.
cdvbin(Offset,Size,{'#CDVBin',Pos}) ->
@@ -2704,12 +2777,17 @@ cdvbin(Offset,Size,{'#CDVBin',Pos}) ->
cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) ->
['#CDVBin',Offset,Size,Pos];
cdvbin(_,_,'#CDVTruncatedBinary') ->
- '#CDVTruncatedBinary'.
+ '#CDVTruncatedBinary';
+cdvbin(_,_,'#CDVNonexistingBinary') ->
+ '#CDVNonexistingBinary'.
%%-----------------------------------------------------------------
-%% Functions for accessing the cdv_dump_index_table
-reset_index_table() ->
- ets:delete_all_objects(cdv_dump_index_table).
+%% Functions for accessing tables
+reset_tables() ->
+ ets:delete_all_objects(cdv_dump_index_table),
+ ets:delete_all_objects(cdv_reg_proc_table),
+ ets:delete_all_objects(cdv_binary_index_table),
+ ets:delete_all_objects(cdv_heap_file_chars).
insert_index(Tag,Id,Pos) ->
ets:insert(cdv_dump_index_table,{{Tag,Pos},Id}).
@@ -2724,6 +2802,11 @@ lookup_index(Tag,Id) ->
count_index(Tag) ->
ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]).
+insert_binary_index(Addr,Pos) ->
+ ets:insert(cdv_binary_index_table,{Addr,Pos}).
+
+lookup_binary_index(Addr) ->
+ ets:lookup(cdv_binary_index_table,Addr).
%%-----------------------------------------------------------------
%% Convert tags read from crashdump to atoms used as first part of key
@@ -2759,7 +2842,7 @@ tag_to_atom("scheduler") -> ?scheduler;
tag_to_atom("timer") -> ?timer;
tag_to_atom("visible_node") -> ?visible_node;
tag_to_atom(UnknownTag) ->
- io:format("WARNING: Found unexpected tag:~s~n",[UnknownTag]),
+ io:format("WARNING: Found unexpected tag:~ts~n",[UnknownTag]),
list_to_atom(UnknownTag).
%%%-----------------------------------------------------------------
@@ -2794,23 +2877,6 @@ to_value_list(Record) ->
Values.
%%%-----------------------------------------------------------------
-%%% Fold over List and report progress in percent.
-%%% Report is the text to be presented in the progress dialog.
-%%% Acc0 is the initial accumulator and will be passed to Fun as the
-%%% second arguement, i.e. Fun = fun(Item,Acc) -> NewAcc end.
-progress_foldl(Report,Fun,Acc0,List) ->
- init_progress(Report, length(List)),
- progress_foldl1(Fun,Acc0,List).
-
-progress_foldl1(Fun,Acc,[H|T]) ->
- update_progress(),
- progress_foldl1(Fun,Fun(H,Acc),T);
-progress_foldl1(_Fun,Acc,[]) ->
- end_progress(),
- Acc.
-
-
-%%%-----------------------------------------------------------------
%%% Map over List and report progress in percent.
%%% Report is the text to be presented in the progress dialog.
%%% Distribute the load over a number of processes, and File is opened
diff --git a/lib/observer/src/etop_tr.erl b/lib/observer/src/etop_tr.erl
index 8e43f8bb35..1e48fefca4 100644
--- a/lib/observer/src/etop_tr.erl
+++ b/lib/observer/src/etop_tr.erl
@@ -89,14 +89,14 @@ handle_data(Last, {_, Pid, out, _, Time2} = G, Store) ->
end,
New;
false ->
- io:format("Erlang top got garbage ~p~n", [G]),
+ io:format("Erlang top got garbage ~tp~n", [G]),
Last
end;
handle_data(_W, {drop, D}, _) -> %% Error case we are missing data here!
io:format("Erlang top dropped data ~p~n", [D]),
[];
handle_data(Last, G, _) ->
- io:format("Erlang top got garbage ~p~n", [G]),
+ io:format("Erlang top got garbage ~tp~n", [G]),
Last.
elapsed({Me1, S1, Mi1}, {Me2, S2, Mi2}) ->
diff --git a/lib/observer/src/etop_txt.erl b/lib/observer/src/etop_txt.erl
index 183641119a..cd3ec62c13 100644
--- a/lib/observer/src/etop_txt.erl
+++ b/lib/observer/src/etop_txt.erl
@@ -48,7 +48,6 @@ do_update(Prev,Config) ->
do_update(standard_io,Info,Prev,Config).
do_update(Fd,Info,Prev,Config) ->
- Encoding = encoding(Fd),
{Cpu,NProcs,RQ,Clock} = loadinfo(Info,Prev),
io:nl(Fd),
writedoubleline(Fd),
@@ -72,7 +71,7 @@ do_update(Fd,Info,Prev,Config) ->
io:nl(Fd),
writepinfo_header(Fd),
writesingleline(Fd),
- writepinfo(Fd,Info#etop_info.procinfo,Encoding),
+ writepinfo(Fd,Info#etop_info.procinfo,modifier(Fd)),
writedoubleline(Fd),
io:nl(Fd),
Info.
@@ -93,26 +92,27 @@ writepinfo(Fd,[#etop_proc_info{pid=Pid,
cf=MFA,
mq=MQ}
|T],
- Encoding) ->
- io:fwrite(Fd,proc_format(Encoding),
- [Pid,to_list(Name,Encoding),Time,Reds,Mem,MQ,
- formatmfa(MFA,Encoding)]),
- writepinfo(Fd,T,Encoding);
+ Modifier) ->
+ io:fwrite(Fd,proc_format(Modifier),
+ [Pid,to_string(Name,Modifier),Time,Reds,Mem,MQ,
+ to_string(MFA,Modifier)]),
+ writepinfo(Fd,T,Modifier);
writepinfo(_Fd,[],_) ->
ok.
+proc_format(Modifier) ->
+ "~-15w~-20"++Modifier++"s~8w~8w~8w~8w ~-20"++Modifier++"s~n".
-formatmfa({M, F, A},latin1) ->
- io_lib:format("~w:~w/~w",[M, F, A]);
-formatmfa({M, F, A},_) ->
- io_lib:format("~w:~tw/~w",[M, F, A]);
-formatmfa(Other,_) ->
- %% E.g. when running hipe - the current_function for some
- %% processes will be 'undefined'
- io_lib:format("~w",[Other]).
+to_string({M,F,A},Modifier) ->
+ io_lib:format("~w:~"++Modifier++"w/~w",[M,F,A]);
+to_string(Other,Modifier) ->
+ io_lib:format("~"++Modifier++"w",[Other]).
-to_list(Name,_) when is_atom(Name) -> atom_to_list(Name);
-to_list({_M,_F,_A}=MFA,Encoding) -> formatmfa(MFA,Encoding).
+modifier(Device) ->
+ case encoding(Device) of
+ latin1 -> "";
+ _ -> "t"
+ end.
encoding(Device) ->
case io:getopts(Device) of
@@ -122,7 +122,3 @@ encoding(Device) ->
latin1
end.
-proc_format(latin1) ->
- "~-15w~-20s~8w~8w~8w~8w ~-20s~n";
-proc_format(_) ->
- "~-15w~-20ts~8w~8w~8w~8w ~-20ts~n".
diff --git a/lib/observer/src/multitrace.erl b/lib/observer/src/multitrace.erl
index a01eeec6ae..82aec05e0d 100644
--- a/lib/observer/src/multitrace.erl
+++ b/lib/observer/src/multitrace.erl
@@ -103,16 +103,16 @@ print_func(Out,{trace_ts,P,call,{M,F,A},C,Ts},N) ->
io:format(Out,
"~w: ~s~n"
"Process : ~w~n"
- "Call : ~w:~w/~w~n"
- "Arguments : ~p~n"
- "Caller : ~w~n~n",
+ "Call : ~w:~tw/~w~n"
+ "Arguments : ~tp~n"
+ "Caller : ~tw~n~n",
[N,ts(Ts),P,M,F,length(A),A,C]);
print_func(Out,{trace_ts,P,return_from,{M,F,A},R,Ts},N) ->
io:format(Out,
"~w: ~s~n"
"Process : ~w~n"
- "Return from : ~w:~w/~w~n"
- "Return value : ~p~n~n",
+ "Return from : ~w:~tw/~w~n"
+ "Return value : ~tp~n~n",
[N,ts(Ts),P,M,F,A,R]).
@@ -181,7 +181,7 @@ handle_schedule(Out,{trace_ts,P,out,Info,Ts},_TI,S) ->
"out:~n"
"Process : ~w~n"
"Time : ~s~n"
- "Function : ~w~n~n",[P,ts(Ts),Info]),
+ "Function : ~tw~n~n",[P,ts(Ts),Info]),
case lists:keysearch(P,1,S) of
{value,{P,List}} ->
lists:keyreplace(P,1,S,{P,[{out,Ts}|List]});
@@ -193,7 +193,7 @@ handle_schedule(Out,{trace_ts,P,in,Info,Ts},_TI,S) ->
"in:~n"
"Process : ~w~n"
"Time : ~s~n"
- "Function : ~w~n~n",[P,ts(Ts),Info]),
+ "Function : ~tw~n~n",[P,ts(Ts),Info]),
case lists:keysearch(P,1,S) of
{value,{P,List}} ->
lists:keyreplace(P,1,S,{P,[{in,Ts}|List]});
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index 3a5bd172e7..f682e3dc7b 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -65,7 +65,7 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["wx-1.2","stdlib-2.0","runtime_tools-1.8.14",
+ {runtime_dependencies, ["wx-1.2","stdlib-3.4","runtime_tools-1.8.14",
"kernel-3.0","inets-5.10","et-1.5",
"erts-7.0"]}]}.
diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl
index ef425f0874..7f4b3dd484 100644
--- a/lib/observer/src/observer_alloc_wx.erl
+++ b/lib/observer/src/observer_alloc_wx.erl
@@ -80,7 +80,7 @@ init([Notebook, Parent, Config]) ->
}
}
catch _:Err ->
- io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]),
+ io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, erlang:get_stacktrace()]),
{stop, Err}
end.
@@ -183,7 +183,7 @@ handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) ->
{noreply, State#state{active=false, appmon=undefined}};
handle_info(_Event, State) ->
- %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]),
+ %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),
{noreply, State}.
terminate(_Event, #state{}) ->
diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl
index bc4f1fe117..2a481966da 100644
--- a/lib/observer/src/observer_app_wx.erl
+++ b/lib/observer/src/observer_app_wx.erl
@@ -320,7 +320,7 @@ handle_info({'EXIT', _, noconnection}, State) ->
handle_info({'EXIT', _, normal}, State) ->
{noreply, State};
handle_info(_Event, State) ->
- %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]),
+ %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),
{noreply, State}.
%%%%%%%%%%
diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl
index 1f1306c370..a85808a472 100644
--- a/lib/observer/src/observer_html_lib.erl
+++ b/lib/observer/src/observer_html_lib.erl
@@ -142,13 +142,13 @@ dict_table(Tab,{Key0,Value0}, Even) ->
tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]).
proc_state(Tab,{Key0,Value0}, Even) ->
- Key = lists:flatten(io_lib:format("~s",[Key0])),
+ Key = lists:flatten(io_lib:format("~ts",[Key0])),
Value = all_or_expand(Tab,Value0),
tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]).
all_or_expand(Tab,Term) ->
- Preview = io_lib:format("~P",[Term,8]),
- Check = io_lib:format("~P",[Term,100]),
+ Preview = io_lib:format("~tP",[Term,8]),
+ Check = io_lib:format("~tP",[Term,100]),
Exp = Preview=/=Check,
all_or_expand(Tab,Term,Preview,Exp).
all_or_expand(_Tab,Term,Str,false)
@@ -166,13 +166,8 @@ all_or_expand(Tab,Term,Preview,true)
"Click to expand above term")];
all_or_expand(Tab,Bin,_PreviewStr,_Expand)
when is_binary(Bin) ->
- Size = byte_size(Bin),
- PrevSize = min(Size, 10) * 8,
- <<Preview:PrevSize, _/binary>> = Bin,
- Hash = erlang:phash2(Bin),
- Key = {Preview, Size, Hash},
- ets:insert(Tab,{Key,Bin}),
- Term = io_lib:format("~p", [['#OBSBin',Preview,Size,Hash]]),
+ OBSBin = observer_lib:make_obsbin(Bin,Tab),
+ Term = io_lib:format("~tp", [OBSBin]),
href_proc_port(lists:flatten(Term), true).
color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN));
@@ -339,26 +334,35 @@ href_proc_bin(From, T, Acc, LTB) ->
BinStr =
case string:tokens(OffsetSizePos,",.| \n") of
[Offset,SizeStr,Pos] when From =:= cdv ->
- Id = {list_to_integer(Offset),10,list_to_integer(Pos)},
- {ok,PreviewBin} = crashdump_viewer:expand_binary(Id),
- PreviewStr = preview_string(list_to_integer(SizeStr), PreviewBin),
- if LTB ->
- href("TARGET=\"expanded\"",
- ["#Binary?offset="++Offset++
- "&size="++SizeStr++
- "&pos="++Pos],
- PreviewStr);
- true ->
- PreviewStr
- end;
- [Preview,SizeStr,Md5] when From =:= obs ->
+ Size = list_to_integer(SizeStr),
+ PreviewSize = min(Size,10),
+ Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)},
+ case crashdump_viewer:expand_binary(Id) of
+ {ok, '#CDVTruncatedBinary'} ->
+ lists:flatten(
+ "<FONT COLOR=\"#FF0000\">"
+ "&lt;&lt;...(Truncated Binary)&gt;&gt;"
+ "</FONT>");
+ {ok, PreviewBin} ->
+ PreviewStr = preview_string(Size, PreviewBin),
+ if LTB ->
+ href("TARGET=\"expanded\"",
+ ["#Binary?offset="++Offset++
+ "&size="++SizeStr++
+ "&pos="++Pos],
+ PreviewStr);
+ true ->
+ PreviewStr
+ end
+ end;
+ [PreviewIntStr,SizeStr,Md5] when From =:= obs ->
Size = list_to_integer(SizeStr),
- PrevSize = min(Size, 10) * 8,
- PreviewStr = preview_string(Size,
- <<(list_to_integer(Preview)):PrevSize>>),
+ PreviewInt = list_to_integer(PreviewIntStr),
+ PrevSize = (trunc(math:log2(PreviewInt)/8)+1)*8,
+ PreviewStr = preview_string(Size,<<PreviewInt:PrevSize>>),
if LTB ->
href("TARGET=\"expanded\"",
- ["#OBSBinary?key1="++Preview++
+ ["#OBSBinary?key1="++PreviewIntStr++
"&key2="++SizeStr++
"&key3="++Md5],
PreviewStr);
@@ -372,14 +376,14 @@ href_proc_bin(From, T, Acc, LTB) ->
preview_string(Size, PreviewBin) when Size > 10 ->
["&lt;&lt;",
- remove_lgt(io_lib:format("~p",[PreviewBin])),
+ remove_lgt(io_lib:format("~tp",[PreviewBin])),
"...(",
observer_lib:to_str({bytes,Size}),
")",
"&gt;&gt"];
preview_string(_, PreviewBin) ->
["&lt;&lt;",
- remove_lgt(io_lib:format("~p",[PreviewBin])),
+ remove_lgt(io_lib:format("~tp",[PreviewBin])),
"&gt;&gt"].
remove_lgt(Deep) ->
diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl
index c7ee294719..29f4f9fabc 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -21,7 +21,7 @@
-export([get_wx_parent/1,
display_info_dialog/2, display_yes_no_dialog/1,
- display_progress_dialog/2, destroy_progress_dialog/0,
+ display_progress_dialog/3, destroy_progress_dialog/0,
wait_for_progress/0, report_progress/1,
user_term/3, user_term_multiline/3,
interval_dialog/4, start_timer/1, start_timer/2, stop_timer/1, timer_config/1,
@@ -30,7 +30,8 @@
create_attrs/0,
set_listctrl_col_size/2,
create_status_bar/1,
- html_window/1, html_window/2
+ html_window/1, html_window/2,
+ make_obsbin/2
]).
-include_lib("wx/include/wx.hrl").
@@ -39,6 +40,7 @@
-define(SINGLE_LINE_STYLE, ?wxBORDER_NONE bor ?wxTE_READONLY bor ?wxTE_RICH2).
-define(MULTI_LINE_STYLE, ?SINGLE_LINE_STYLE bor ?wxTE_MULTILINE).
+-define(pulse_timeout,50).
get_wx_parent(Window) ->
Parent = wxWindow:getParent(Window),
@@ -297,8 +299,10 @@ to_str(No) when is_integer(No) ->
integer_to_list(No);
to_str(Float) when is_float(Float) ->
io_lib:format("~.3f", [Float]);
+to_str({trunc, Float}) when is_float(Float) ->
+ float_to_list(Float, [{decimals,0}]);
to_str(Term) ->
- io_lib:format("~w", [Term]).
+ io_lib:format("~tw", [Term]).
create_menus([], _MenuBar, _Type) -> ok;
create_menus(Menus, MenuBar, Type) ->
@@ -518,7 +522,7 @@ link_entry2(Panel,{Target,Str},Cursor) ->
TC.
to_link(RegName={Name, Node}) when is_atom(Name), is_atom(Node) ->
- Str = io_lib:format("{~p,~p}", [Name, Node]),
+ Str = io_lib:format("{~tp,~p}", [Name, Node]),
{RegName, Str};
to_link(TI = {_Target, _Identifier}) ->
TI;
@@ -639,11 +643,11 @@ parse_string(Str) ->
Tokens = case erl_scan:string(Str, 1, [text]) of
{ok, Ts, _} -> Ts;
{error, {_SLine, SMod, SError}, _} ->
- throw(io_lib:format("~s", [SMod:format_error(SError)]))
+ throw(io_lib:format("~ts", [SMod:format_error(SError)]))
end,
case lib:extended_parse_term(Tokens) of
{error, {_PLine, PMod, PError}} ->
- throw(io_lib:format("~s", [PMod:format_error(PError)]));
+ throw(io_lib:format("~ts", [PMod:format_error(PError)]));
Res -> Res
end
catch
@@ -685,11 +689,11 @@ create_status_bar(Panel) ->
%%%-----------------------------------------------------------------
%%% Progress dialog
-define(progress_handler,cdv_progress_handler).
-display_progress_dialog(Title,Str) ->
+display_progress_dialog(Parent,Title,Str) ->
Caller = self(),
Env = wx:get_env(),
spawn_link(fun() ->
- progress_handler(Caller,Env,Title,Str)
+ progress_handler(Caller,Env,Parent,Title,Str)
end),
ok.
@@ -713,31 +717,38 @@ report_progress(Progress) ->
ok
end.
-progress_handler(Caller,Env,Title,Str) ->
+progress_handler(Caller,Env,Parent,Title,Str) ->
register(?progress_handler,self()),
wx:set_env(Env),
- PD = progress_dialog(Env,Title,Str),
- try progress_loop(Title,PD,Caller)
+ PD = progress_dialog(Env,Parent,Title,Str),
+ try progress_loop(Title,PD,Caller,infinity)
catch closed -> normal end.
-progress_loop(Title,PD,Caller) ->
+progress_loop(Title,PD,Caller,Pulse) ->
receive
{progress,{ok,done}} -> % to make wait_for_progress/0 return
Caller ! continue,
- progress_loop(Title,PD,Caller);
+ progress_loop(Title,PD,Caller,Pulse);
+ {progress,{ok,start_pulse}} ->
+ update_progress_pulse(PD),
+ progress_loop(Title,PD,Caller,?pulse_timeout);
+ {progress,{ok,stop_pulse}} ->
+ progress_loop(Title,PD,Caller,infinity);
{progress,{ok,Percent}} when is_integer(Percent) ->
update_progress(PD,Percent),
- progress_loop(Title,PD,Caller);
+ progress_loop(Title,PD,Caller,Pulse);
{progress,{ok,Msg}} ->
update_progress_text(PD,Msg),
- progress_loop(Title,PD,Caller);
+ progress_loop(Title,PD,Caller,Pulse);
{progress,{error, Reason}} ->
+ {Dialog,_,_} = PD,
+ Parent = wxWindow:getParent(Dialog),
finish_progress(PD),
FailMsg =
if is_list(Reason) -> Reason;
true -> file:format_error(Reason)
end,
- display_info_dialog(PD,"Crashdump Viewer Error",FailMsg),
+ display_info_dialog(Parent,"Crashdump Viewer Error",FailMsg),
Caller ! error,
unregister(?progress_handler),
unlink(Caller);
@@ -745,25 +756,77 @@ progress_loop(Title,PD,Caller) ->
finish_progress(PD),
unregister(?progress_handler),
unlink(Caller)
+ after Pulse ->
+ update_progress_pulse(PD),
+ progress_loop(Title,PD,Caller,?pulse_timeout)
end.
-progress_dialog(_Env,Title,Str) ->
- PD = wxProgressDialog:new(Title,Str,
- [{maximum,101},
- {style,
- ?wxPD_APP_MODAL bor
- ?wxPD_SMOOTH bor
- ?wxPD_AUTO_HIDE}]),
- wxProgressDialog:setMinSize(PD,{200,-1}),
- PD.
+progress_dialog(_Env,Parent,Title,Str) ->
+ progress_dialog_new(Parent,Title,Str).
update_progress(PD,Value) ->
- try wxProgressDialog:update(PD,Value)
+ try progress_dialog_update(PD,Value)
catch _:_ -> throw(closed) %% Port or window have died
end.
update_progress_text(PD,Text) ->
- try wxProgressDialog:update(PD,0,[{newmsg,Text}])
+ try progress_dialog_update(PD,Text)
+ catch _:_ -> throw(closed) %% Port or window have died
+ end.
+update_progress_pulse(PD) ->
+ try progress_dialog_pulse(PD)
catch _:_ -> throw(closed) %% Port or window have died
end.
finish_progress(PD) ->
- wxProgressDialog:destroy(PD).
+ try progress_dialog_update(PD,100)
+ catch _:_ -> ok
+ after progress_dialog_destroy(PD)
+ end.
+
+progress_dialog_new(Parent,Title,Str) ->
+ Dialog = wxDialog:new(Parent, ?wxID_ANY, Title,
+ [{style,?wxDEFAULT_DIALOG_STYLE}]),
+ Panel = wxPanel:new(Dialog),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ Message = wxStaticText:new(Panel, 1, Str),
+ Gauge = wxGauge:new(Panel, 2, 100, [{size, {170, -1}},
+ {style, ?wxGA_HORIZONTAL}]),
+ SizerFlags = ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT bor ?wxTOP,
+ wxSizer:add(Sizer, Message, [{flag,SizerFlags},{border,15}]),
+ wxSizer:add(Sizer, Gauge, [{flag, SizerFlags bor ?wxBOTTOM},{border,15}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxSizer:setSizeHints(Sizer, Dialog),
+ wxDialog:show(Dialog),
+ {Dialog,Message,Gauge}.
+
+progress_dialog_update({_,_,Gauge},Value) when is_integer(Value) ->
+ wxGauge:setValue(Gauge,Value);
+progress_dialog_update({_,Message,Gauge},Text) when is_list(Text) ->
+ wxGauge:setValue(Gauge,0),
+ wxStaticText:setLabel(Message,Text).
+progress_dialog_pulse({_,_,Gauge}) ->
+ wxGauge:pulse(Gauge).
+progress_dialog_destroy({Dialog,_,_}) ->
+ wxDialog:destroy(Dialog).
+
+make_obsbin(Bin,Tab) ->
+ Size = byte_size(Bin),
+ Preview =
+ try
+ %% The binary might be a unicode string, in which case we
+ %% don't want to split it in the middle of a grapheme
+ %% cluster - thus trying string:length and slice.
+ PL1 = min(string:length(Bin), 10),
+ PB1 = string:slice(Bin,0,PL1),
+ PS1 = byte_size(PB1) * 8,
+ <<P1:PS1>> = PB1,
+ P1
+ catch _:_ ->
+ %% Probably not a string, so just split anywhere
+ PS2 = min(Size, 10) * 8,
+ <<P2:PS2, _/binary>> = Bin,
+ P2
+ end,
+ Hash = erlang:phash2(Bin),
+ Key = {Preview, Size, Hash},
+ ets:insert(Tab, {Key,Bin}),
+ ['#OBSBin',Preview,Size,Hash].
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
index fcc51310c8..5adfadb16e 100644
--- a/lib/observer/src/observer_perf_wx.erl
+++ b/lib/observer/src/observer_perf_wx.erl
@@ -87,7 +87,7 @@ init([Notebook, Parent, Config]) ->
},
{Panel, State0}
catch _:Err ->
- io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]),
+ io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, erlang:get_stacktrace()]),
{stop, Err}
end.
@@ -235,7 +235,7 @@ handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) ->
{noreply, State#state{active=false, appmon=undefined}};
handle_info(_Event, State) ->
- %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]),
+ %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),
{noreply, State}.
%%%%%%%%%%
diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl
index 8339267659..5908e99e36 100644
--- a/lib/observer/src/observer_port_wx.erl
+++ b/lib/observer/src/observer_port_wx.erl
@@ -338,7 +338,7 @@ handle_info({info, {port_info_not_available,NodeName}},
{noreply, State};
handle_info({error, Error}, #state{panel=Panel} = State) ->
- Str = io_lib:format("ERROR: ~s~n",[Error]),
+ Str = io_lib:format("ERROR: ~ts~n",[Error]),
observer_lib:display_info_dialog(Panel, Str),
{noreply, State};
diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl
index 3083297f31..2e5fe0bc1a 100644
--- a/lib/observer/src/observer_pro_wx.erl
+++ b/lib/observer/src/observer_pro_wx.erl
@@ -217,7 +217,7 @@ call(Holder, What) ->
erlang:demonitor(Ref),
Res
after 2000 ->
- io:format("Hanging call ~p~n",[What]),
+ io:format("Hanging call ~tp~n",[What]),
""
end.
@@ -256,7 +256,7 @@ handle_info(not_active, #state{timer=Timer0}=State) ->
{noreply, State#state{timer=Timer}};
handle_info(Info, State) ->
- io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]),
+ io:format("~p:~p, Unexpected info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
terminate(_Reason, #state{holder=Holder}) ->
@@ -273,11 +273,11 @@ handle_call(get_config, _, #state{holder=Holder, timer=Timer}=State) ->
{reply, Conf#{acc=>Accum}, State};
handle_call(Msg, _From, State) ->
- io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p:~p: Unhandled call ~tp~n",[?MODULE, ?LINE, Msg]),
{reply, ok, State}.
handle_cast(Msg, State) ->
- io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
+ io:format("~p:~p: Unhandled cast ~tp~n", [?MODULE, ?LINE, Msg]),
{noreply, State}.
%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -401,7 +401,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated}},
{noreply, State#state{procinfo_menu_pids=Opened2}};
handle_event(Event, State) ->
- io:format("~p:~p: handle event ~p\n", [?MODULE, ?LINE, Event]),
+ io:format("~p:~p: handle event ~tp\n", [?MODULE, ?LINE, Event]),
{noreply, State}.
@@ -559,7 +559,7 @@ table_holder(#holder{info=Info, attrs=Attrs,
%% Node crashed will be noticed soon..
table_holder(S0#holder{backend_pid=undefined});
_What ->
- %% io:format("~p: Table holder got ~p~n",[?MODULE, _What]),
+ %% io:format("~p: Table holder got ~tp~n",[?MODULE, _What]),
table_holder(S0)
end.
diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl
index 10decd8b62..fb02ae2728 100644
--- a/lib/observer/src/observer_procinfo.erl
+++ b/lib/observer/src/observer_procinfo.erl
@@ -56,7 +56,7 @@ init([Pid, ParentFrame, Parent]) ->
Table = ets:new(observer_expand,[set,public]),
Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of
[] -> io_lib:format("~p",[Pid]);
- {registered_name, Registered} -> io_lib:format("~p (~p)",[Registered, Pid]);
+ {registered_name, Registered} -> io_lib:format("~tp (~p)",[Registered, Pid]);
undefined -> throw(process_undefined)
end,
Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title],
@@ -151,7 +151,7 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}},
Opened =
case lists:keyfind(Id,1,Opened0) of
false ->
- Win = cdv_detail_wx:start_link(Id,[],Frame,Callback),
+ Win = cdv_detail_wx:start_link(Id,[],Frame,Callback,obs),
[{Id,Win}|Opened0];
{_,Win} ->
wxFrame:raise(Win),
@@ -171,7 +171,7 @@ handle_info({get_debug_info, From}, State = #state{notebook=Notebook}) ->
From ! {procinfo_debug, Notebook},
{noreply, State};
handle_info(_Info, State) ->
- %% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+ %% io:format("~p: ~p, Handle info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
handle_call(Call, From, _State) ->
@@ -263,7 +263,7 @@ init_stack_page(Parent, Pid) ->
wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})),
FileLine = case Info of
[{file,File},{line,Line}] ->
- io_lib:format("~s:~w", [File,Line]);
+ io_lib:format("~ts:~w", [File,Line]);
_ ->
[]
end,
@@ -487,5 +487,5 @@ io_request({put_chars, Encoding, Module, Function, Args}, State) ->
{error, {error, Function}, State}
end;
io_request(_Req, State) ->
- %% io:format("~p: Unknown req: ~p ~n",[?LINE, _Req]),
+ %% io:format("~p: Unknown req: ~tp ~n",[?LINE, _Req]),
{ok, {error, request}, State}.
diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl
index db86c05bed..8c2ffd77b4 100644
--- a/lib/observer/src/observer_sys_wx.erl
+++ b/lib/observer/src/observer_sys_wx.erl
@@ -48,7 +48,7 @@ start_link(Notebook, Parent, Config) ->
init([Notebook, Parent, Config]) ->
SysInfo = observer_backend:sys_info(),
- {Sys, Mem, Cpu, Stats} = info_fields(),
+ {Sys, Mem, Cpu, Stats, Limits} = info_fields(),
Panel = wxPanel:new(Notebook),
Sizer = wxBoxSizer:new(?wxVERTICAL),
HSizer0 = wxBoxSizer:new(?wxHORIZONTAL),
@@ -63,17 +63,26 @@ init([Notebook, Parent, Config]) ->
wxSizer:add(HSizer1, FPanel2, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxSizer:add(HSizer1, FPanel3, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ HSizer2 = wxBoxSizer:new(?wxHORIZONTAL),
+ {FPanel4, _FSizer4, Fields4} = observer_lib:display_info(Panel, observer_lib:fill_info(Limits, SysInfo)),
+ wxSizer:add(HSizer2, FPanel4, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+
BorderFlags = ?wxLEFT bor ?wxRIGHT,
wxSizer:add(Sizer, HSizer0, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP},
{proportion, 0}, {border, 5}]),
wxSizer:add(Sizer, HSizer1, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM},
{proportion, 0}, {border, 5}]),
+ wxSizer:add(Sizer, HSizer2, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM},
+ {proportion, 0}, {border, 5}]),
+
wxPanel:setSizer(Panel, Sizer),
Timer = observer_lib:start_timer(Config, 10),
{Panel, #sys_wx_state{parent=Parent,
parent_notebook=Notebook,
panel=Panel, sizer=Sizer,
- timer=Timer, fields=Fields0 ++ Fields1++Fields2++Fields3}}.
+ timer=Timer, fields=Fields0 ++ Fields1++Fields2++Fields3++Fields4}}.
+
create_sys_menu(Parent) ->
View = {"View", [#create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"},
@@ -83,14 +92,40 @@ create_sys_menu(Parent) ->
update_syspage(#sys_wx_state{node = undefined}) -> ignore;
update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) ->
SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []),
- {Sys, Mem, Cpu, Stats} = info_fields(),
+ {Sys, Mem, Cpu, Stats, Limits} = info_fields(),
observer_lib:update_info(Fields,
observer_lib:fill_info(Sys, SysInfo) ++
observer_lib:fill_info(Mem, SysInfo) ++
observer_lib:fill_info(Cpu, SysInfo) ++
- observer_lib:fill_info(Stats, SysInfo)),
+ observer_lib:fill_info(Stats, SysInfo)++
+ observer_lib:fill_info(Limits, SysInfo)),
+
wxSizer:layout(Sizer).
+
+maybe_convert(undefined) -> "Not available";
+maybe_convert(V) -> observer_lib:to_str(V).
+
+get_dist_buf_busy_limit_info() ->
+ fun(Data) ->
+ maybe_convert(proplists:get_value(dist_buf_busy_limit, Data))
+ end.
+
+get_limit_count_info(Count, Limit) ->
+ fun(Data) ->
+ C = proplists:get_value(Count, Data),
+ L = proplists:get_value(Limit, Data),
+ lists:flatten(
+ io_lib:format("~s / ~s ~s",
+ [maybe_convert(C), maybe_convert(L),
+ if
+ C =:= undefined -> "";
+ L =:= undefined -> "";
+ true -> io_lib:format("(~s % used)",[observer_lib:to_str({trunc, (C / L) *100})])
+ end]))
+ end.
+
+
info_fields() ->
Sys = [{"System and Architecture",
[{"System Version", otp_release},
@@ -122,14 +157,20 @@ info_fields() ->
]}],
Stats = [{"Statistics", right,
[{"Up time", {time_ms, uptime}},
- {"Max Processes", process_limit},
- {"Processes", process_count},
{"Run Queue", run_queue},
{"IO Input", {bytes, io_input}},
{"IO Output", {bytes, io_output}}
]}
],
- {Sys, Mem, Cpu, Stats}.
+ Limits = [{"System statistics / limit",
+ [{"Atoms", get_limit_count_info(atom_count, atom_limit)},
+ {"Processes", get_limit_count_info(process_count, process_limit)},
+ {"Ports", get_limit_count_info(port_count, port_limit)},
+ {"ETS", get_limit_count_info(ets_count, ets_limit)},
+ {"Distribution buffer busy limit", get_dist_buf_busy_limit_info()}
+ ]}],
+ {Sys, Mem, Cpu, Stats, Limits}.
+
%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -158,7 +199,7 @@ handle_info(not_active, #sys_wx_state{timer = Timer} = State) ->
{noreply, State#sys_wx_state{timer = observer_lib:stop_timer(Timer)}};
handle_info(Info, State) ->
- io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]),
+ io:format("~p:~p: Unhandled info: ~tp~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
terminate(_Reason, _State) ->
@@ -171,11 +212,11 @@ handle_call(get_config, _, #sys_wx_state{timer=Timer}=State) ->
{reply, observer_lib:timer_config(Timer), State};
handle_call(Msg, _From, State) ->
- io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled Call ~tp~n",[?MODULE, ?LINE, Msg]),
{reply, ok, State}.
handle_cast(Msg, State) ->
- io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ io:format("~p~p: Unhandled cast ~tp~n",[?MODULE, ?LINE, Msg]),
{noreply, State}.
handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_selected}},
@@ -194,5 +235,5 @@ handle_event(#wx{id = ?ID_REFRESH_INTERVAL,
{noreply, State#sys_wx_state{timer=Timer}};
handle_event(Event, State) ->
- io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]),
+ io:format("~p:~p: Unhandled event ~tp\n", [?MODULE,?LINE,Event]),
{noreply, State}.
diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl
index b960c61ff0..8127248262 100644
--- a/lib/observer/src/observer_trace_wx.erl
+++ b/lib/observer/src/observer_trace_wx.erl
@@ -683,7 +683,7 @@ handle_event(#wx{id=?REMOVE_NODES}, #state{n_view=Nview, nodes=Ns0} = State) ->
{noreply, State#state{nodes = Ns}};
handle_event(#wx{id=ID, event = What}, State) ->
- io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, ?LINE, ID, What]),
+ io:format("~p:~p: Unhandled event: ~p, ~tp ~n", [?MODULE, ?LINE, ID, What]),
{noreply, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -729,7 +729,7 @@ handle_info({update_ms, NewMs}, State) ->
{noreply, State#state{match_specs=NewMs}};
handle_info(Any, State) ->
- io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]),
+ io:format("~p~p: received unexpected message: ~tp\n", [?MODULE, self(), Any]),
{noreply, State}.
terminate(_Reason, #state{nodes=_Nodes}) ->
@@ -1046,33 +1046,33 @@ format_trace(Trace, Size, TS0={_,_,MS}) ->
case element(4, Trace) of
{dbg,ok} -> "";
Message ->
- io_lib:format("~s (~100p) << ~100p~n", [TS,From,Message])
+ io_lib:format("~s (~100p) << ~100tp~n", [TS,From,Message])
end;
'send' ->
Message = element(4, Trace),
To = element(5, Trace),
- io_lib:format("~s (~100p) ~100p ! ~100p~n", [TS,From,To,Message]);
+ io_lib:format("~s (~100p) ~100p ! ~100tp~n", [TS,From,To,Message]);
call ->
case element(4, Trace) of
MFA when Size == 5 ->
Message = element(5, Trace),
- io_lib:format("~s (~100p) call ~s (~100p) ~n", [TS,From,ffunc(MFA),Message]);
+ io_lib:format("~s (~100p) call ~ts (~100tp) ~n", [TS,From,ffunc(MFA),Message]);
MFA ->
- io_lib:format("~s (~100p) call ~s~n", [TS,From,ffunc(MFA)])
+ io_lib:format("~s (~100p) call ~ts~n", [TS,From,ffunc(MFA)])
end;
return_from ->
MFA = element(4, Trace),
Ret = element(5, Trace),
- io_lib:format("~s (~100p) returned from ~s -> ~100p~n", [TS,From,ffunc(MFA),Ret]);
+ io_lib:format("~s (~100p) returned from ~ts -> ~100tp~n", [TS,From,ffunc(MFA),Ret]);
return_to ->
MFA = element(4, Trace),
- io_lib:format("~s (~100p) returning to ~s~n", [TS,From,ffunc(MFA)]);
+ io_lib:format("~s (~100p) returning to ~ts~n", [TS,From,ffunc(MFA)]);
spawn when Size == 5 ->
Pid = element(4, Trace),
MFA = element(5, Trace),
- io_lib:format("~s (~100p) spawn ~100p as ~s~n", [TS,From,Pid,ffunc(MFA)]);
+ io_lib:format("~s (~100p) spawn ~100p as ~ts~n", [TS,From,Pid,ffunc(MFA)]);
Op ->
- io_lib:format("~s (~100p) ~100p ~s~n", [TS,From,Op,ftup(Trace,4,Size)])
+ io_lib:format("~s (~100p) ~100p ~ts~n", [TS,From,Op,ftup(Trace,4,Size)])
end.
%%% These f* functions returns non-flat strings
@@ -1080,24 +1080,24 @@ format_trace(Trace, Size, TS0={_,_,MS}) ->
%% {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("~100p:~100p(~s)", [M, F, fargs(Argl)]);
+ io_lib:format("~100p:~100tp(~ts)", [M, F, fargs(Argl)]);
ffunc({M,F,Arity}) ->
- io_lib:format("~100p:~100p/~100p", [M,F,Arity]);
-ffunc(X) -> io_lib:format("~100p", [X]).
+ io_lib:format("~100p:~100tp/~100p", [M,F,Arity]);
+ffunc(X) -> io_lib:format("~100tp", [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("~100p", [A]); %% last arg
-fargs([A|Args]) -> [io_lib:format("~100p,", [A]) | fargs(Args)];
-fargs(A) -> io_lib:format("~100p", [A]). % last or only arg
+fargs([A]) -> io_lib:format("~100tp", [A]); %% last arg
+fargs([A|Args]) -> [io_lib:format("~100tp,", [A]) | fargs(Args)];
+fargs(A) -> io_lib:format("~100tp", [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("~100p", [element(Index, Trace)]);
+ io_lib:format("~100tp", [element(Index, Trace)]);
ftup(Trace, Index, Size) ->
- [io_lib:format("~100p ", [element(Index, Trace)])
+ [io_lib:format("~100tp ", [element(Index, Trace)])
| ftup(Trace, Index+1, Size)].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1125,18 +1125,19 @@ get_config(#state{def_proc_flags = ProcFlags,
write_file(Frame, Filename, Config) ->
Str =
- ["%%%\n%%% This file is generated by Observer\n",
+ ["%%% ",epp:encoding_to_string(utf8), "\n"
+ "%%%\n%%% This file is generated by Observer\n",
"%%%\n%%% DO NOT EDIT!\n%%%\n",
- [io_lib:format("~p.~n",[MSTerm]) ||
+ [io_lib:format("~tp.~n",[MSTerm]) ||
MSTerm <- proplists:get_value(match_specs, Config)],
io_lib:format("~p.~n",[lists:keyfind(procflags, 1, Config)]),
io_lib:format("~p.~n",[lists:keyfind(portflags, 1, Config)]),
- io_lib:format("~p.~n",[lists:keyfind(output, 1, Config)]),
- [io_lib:format("~p.~n",[ModuleTerm]) ||
+ io_lib:format("~tp.~n",[lists:keyfind(output, 1, Config)]),
+ [io_lib:format("~tp.~n",[ModuleTerm]) ||
ModuleTerm <- proplists:get_value(trace_p, Config)]
],
- case file:write_file(Filename, list_to_binary(Str)) of
+ case file:write_file(Filename, unicode:characters_to_binary(Str)) of
ok ->
success;
{error, Reason} ->
@@ -1200,7 +1201,7 @@ make_ms(MS) ->
make_ms(Name,Term,FunStr).
make_ms(Name, Term, FunStr) ->
- #match_spec{name=Name, term=Term, str=io_lib:format("~w", Term), func = FunStr}.
+ #match_spec{name=Name, term=Term, str=io_lib:format("~tw", Term), func = FunStr}.
parse_tp({tp, Mod, FAs}, State) ->
Patterns = [#tpattern{m=Mod,fa={F,A}, ms=make_ms(List)} ||
diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl
index 285c298c4b..4f46426cf6 100644
--- a/lib/observer/src/observer_traceoptions_wx.erl
+++ b/lib/observer/src/observer_traceoptions_wx.erl
@@ -487,7 +487,7 @@ edit_ms(TextCtrl, Label0, Parent) ->
_ -> Label0
end,
#match_spec{name=Label, term=MatchSpec,
- str=io_lib:format("~w",[MatchSpec]),
+ str=io_lib:format("~tw",[MatchSpec]),
func=Str}
catch
throw:cancel ->
@@ -511,18 +511,18 @@ ms_from_string(Str) ->
Tokens = case erl_scan:string(Str) of
{ok, Ts, _} -> Ts;
{error, {SLine, SMod, SError}, _} ->
- throw(io_lib:format("~w: ~s", [SLine,SMod:format_error(SError)]))
+ throw(io_lib:format("~w: ~ts", [SLine,SMod:format_error(SError)]))
end,
Exprs = case erl_parse:parse_exprs(Tokens) of
{ok, T} -> T;
{error, {PLine, PMod, PError}} ->
- throw(io_lib:format("~w: ~s", [PLine,PMod:format_error(PError)]))
+ throw(io_lib:format("~w: ~ts", [PLine,PMod:format_error(PError)]))
end,
Term = case Exprs of
[{'fun', _, {clauses, Clauses}}|_] ->
case ms_transform:transform_from_shell(dbg,Clauses,orddict:new()) of
{error, [{_,[{MSLine,Mod,MSInfo}]}],_} ->
- throw(io_lib:format("~w: ~p", [MSLine,Mod:format_error(MSInfo)]));
+ throw(io_lib:format("~w: ~tp", [MSLine,Mod:format_error(MSInfo)]));
{error, _} ->
throw("Could not convert fun() to match spec");
Ms ->
@@ -536,7 +536,7 @@ ms_from_string(Str) ->
{error, List} -> throw([[Error, $\n] || {_, Error} <- List])
end
catch error:_Reason ->
- %% io:format("Bad term: ~s~n ~p in ~p~n", [Str, _Reason, erlang:get_stacktrace()]),
+ %% io:format("Bad term: ~ts~n ~tp in ~tp~n", [Str, _Reason, erlang:get_stacktrace()]),
throw("Invalid term")
end.
@@ -556,7 +556,8 @@ filter_listbox_data(Input, Data, ListBox) ->
filter_listbox_data(Input, Data, ListBox, true).
filter_listbox_data(Input, Data, ListBox, AddClientData) ->
- FilteredData = [X || X = {Str, _} <- Data, re:run(Str, Input) =/= nomatch],
+ FilteredData = [X || X = {Str, _} <- Data,
+ re:run(Str, Input, [unicode]) =/= nomatch],
wxListBox:clear(ListBox),
wxListBox:appendStrings(ListBox, [Str || {Str,_} <- FilteredData]),
AddClientData andalso
@@ -648,9 +649,9 @@ parse_function_names(Choices) ->
parse_function_names([], Acc) ->
lists:reverse(Acc);
parse_function_names([{H, Term}|T], Acc) ->
- IsFun = re:run(H, ".*-fun-\\d*?-"),
- IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-"),
- IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-"),
+ IsFun = re:run(H, ".*-fun-\\d*?-", [unicode,ucp]),
+ IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]),
+ IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]),
Parsed =
if IsFun =/= nomatch -> "Fun: " ++ H;
IsLc =/= nomatch -> "List comprehension: " ++ H;
diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index 789e060cfb..d6dcee2cda 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -258,7 +258,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}
State = #state{pid=Pid, grid=Grid, status=StatusBar}) ->
N = wxListCtrl:getItemCount(Grid),
Str = get_row(Pid, Index, all),
- wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w: ~s",[N, Str])),
+ wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w: ~ts",[N, Str])),
{noreply, State#state{selected=Index}};
handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Index}},
@@ -278,7 +278,7 @@ handle_event(#wx{id=?ID_DELETE},
State = #state{grid=Grid, pid=Pid, status=StatusBar, selected=Index}) ->
Str = get_row(Pid, Index, all),
Pid ! {delete, Index},
- wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~s",[Str])),
+ wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~ts",[Str])),
wxListCtrl:setItemState(Grid, Index, 0, ?wxLIST_STATE_FOCUSED),
{noreply, State#state{selected=undefined}};
@@ -338,7 +338,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdS
Pid ! {mark_search_hit, false},
case search(Pid, Str, Pos, Dir, Case) of
false ->
- wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~s",[Str])),
+ wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~ts",[Str])),
Pid ! {mark_search_hit, Find#find.start},
wxListCtrl:refreshItem(Grid, Find#find.start),
{noreply, State#state{search=Search#search{find=Find#find{found=false}}}};
@@ -372,7 +372,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}},
Pid ! {mark_search_hit, false},
case search(Pid, Str, Cont#find.start, Dir, Case) of
false ->
- wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~s",[Str])),
+ wxStatusBar:setStatusText(SB, io_lib:format("Not found (regexp): ~ts",[Str])),
{noreply, State};
Row ->
wxListCtrl:ensureVisible(Grid, Row),
@@ -395,19 +395,19 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL},
{noreply, State#state{timer=Timer}};
handle_event(_Event, State) ->
- %io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]),
+ %io:format("~p:~p, handle event ~tp\n", [?MODULE, ?LINE, Event]),
{noreply, State}.
handle_sync_event(_Event, _Obj, _State) ->
- %io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]),
+ %io:format("~p:~p, handle sync_event ~tp\n", [?MODULE, ?LINE, Event]),
ok.
handle_call(_Event, _From, State) ->
- %io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]),
+ %io:format("~p:~p, handle call (~p) ~tp\n", [?MODULE, ?LINE, From, Event]),
{noreply, State}.
handle_cast(_Event, State) ->
- %io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]),
+ %io:format("~p:~p, handle cast ~tp\n", [?MODULE, ?LINE, Event]),
{noreply, State}.
handle_info({no_rows, N}, State = #state{grid=Grid, status=StatusBar}) ->
@@ -433,7 +433,7 @@ handle_info(refresh_interval, State = #state{pid=Pid}) ->
handle_info({error, Error}, State = #state{frame=Frame}) ->
ErrorStr =
try io_lib:format("~ts", [Error]), Error
- catch _:_ -> io_lib:format("~p", [Error])
+ catch _:_ -> io_lib:format("~tp", [Error])
end,
Dlg = wxMessageDialog:new(Frame, ErrorStr),
wxMessageDialog:showModal(Dlg),
@@ -441,7 +441,7 @@ handle_info({error, Error}, State = #state{frame=Frame}) ->
{noreply, State};
handle_info(_Event, State) ->
- %% io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, _Event]),
+ %% io:format("~p:~p, handle info ~tp\n", [?MODULE, ?LINE, _Event]),
{noreply, State}.
terminate(_Event, #state{pid=Pid, attrs=Attrs}) ->
@@ -554,7 +554,7 @@ table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) ->
edit_row(Row, Term, S0),
table_holder(S0);
What ->
- io:format("Table holder got ~p~n",[What]),
+ io:format("Table holder got ~tp~n",[What]),
Parent ! {refresh, 0, S0#holder.n-1},
table_holder(S0)
end.
@@ -641,7 +641,7 @@ search([Str, Row, Dir0, CaseSens],
true -> 1;
false -> -1
end,
- Res = case re:compile(Str, Opt) of
+ Res = case re:compile(Str, [unicode|Opt]) of
{ok, Re} -> re_search(Row, Dir, N, Re, Table);
{error, _} -> false
end,
@@ -665,7 +665,7 @@ get_row(From, Row, Col, Table) ->
[Object|_] when Col =:= all ->
From ! {self(), format(Object)};
[Object|_] when Col =:= all_multiline ->
- From ! {self(), io_lib:format("~p", [Object])};
+ From ! {self(), io_lib:format("~tp", [Object])};
[Object|_] when Col =:= term ->
From ! {self(), Object};
[Object|_] when tuple_size(Object) >= Col ->
@@ -801,7 +801,7 @@ format(Bin) when is_binary(Bin), byte_size(Bin) > 100 ->
io_lib:format("<<#Bin:~w>>", [byte_size(Bin)]);
format(Bin) when is_binary(Bin) ->
try
- true = printable_list(unicode:characters_to_list(Bin)),
+ true = io_lib:printable_list(unicode:characters_to_list(Bin)),
io_lib:format("<<\"~ts\">>", [Bin])
catch _:_ ->
io_lib:format("~w", [Bin])
@@ -809,7 +809,7 @@ format(Bin) when is_binary(Bin) ->
format(Float) when is_float(Float) ->
io_lib:format("~.3g", [Float]);
format(Term) ->
- io_lib:format("~w", [Term]).
+ io_lib:format("~tw", [Term]).
format_tuple(Tuple, I, Max) when I < Max ->
[format(element(I, Tuple)), $,|format_tuple(Tuple, I+1, Max)];
@@ -820,7 +820,7 @@ format_tuple(_Tuple, 1, 0) ->
format_list([]) -> "[]";
format_list(List) ->
- case printable_list(List) of
+ case io_lib:printable_list(List) of
true -> io_lib:format("\"~ts\"", [map_printable_list(List)]);
false -> [$[ | make_list(List)]
end.
@@ -849,26 +849,3 @@ map_printable_list([$\e|Cs]) ->
map_printable_list([]) -> [];
map_printable_list([C|Cs]) ->
[C|map_printable_list(Cs)].
-
-%% printable_list([Char]) -> bool()
-%% Return true if CharList is a list of printable characters, else
-%% false.
-
-printable_list([C|Cs]) when is_integer(C), C >= $ , C =< 255 ->
- printable_list(Cs);
-printable_list([$\n|Cs]) ->
- printable_list(Cs);
-printable_list([$\r|Cs]) ->
- printable_list(Cs);
-printable_list([$\t|Cs]) ->
- printable_list(Cs);
-printable_list([$\v|Cs]) ->
- printable_list(Cs);
-printable_list([$\b|Cs]) ->
- printable_list(Cs);
-printable_list([$\f|Cs]) ->
- printable_list(Cs);
-printable_list([$\e|Cs]) ->
- printable_list(Cs);
-printable_list([]) -> true;
-printable_list(_Other) -> false. %Everything else is false
diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl
index 9564bdfa1c..e16f3cab6b 100644
--- a/lib/observer/src/observer_tv_wx.erl
+++ b/lib/observer/src/observer_tv_wx.erl
@@ -252,7 +252,7 @@ handle_info(not_active, State = #state{timer = Timer0}) ->
{noreply, State#state{timer=Timer}};
handle_info({error, Error}, #state{panel=Panel,opt=Opt}=State) ->
- Str = io_lib:format("ERROR: ~s~n",[Error]),
+ Str = io_lib:format("ERROR: ~ts~n",[Error]),
observer_lib:display_info_dialog(Panel,Str),
case Opt#opt.type of
mnesia -> wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true);
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index 9b9e80f479..be93b1d5f1 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -459,7 +459,7 @@ handle_info({'EXIT', Pid, Reason}, State) ->
normal ->
{noreply, State};
_ ->
- io:format("Observer: Child (~s) crashed exiting: ~p ~p~n",
+ io:format("Observer: Child (~s) crashed exiting: ~p ~tp~n",
[pid2panel(Pid, State), Pid, Reason]),
{stop, normal, State}
end;
@@ -504,7 +504,7 @@ save_config(Panels) ->
File = config_file(),
case filelib:ensure_dir(File) of
ok ->
- Format = [io_lib:format("~p.~n",[Conf]) || Conf <- Configs],
+ Format = [io_lib:format("~tp.~n",[Conf]) || Conf <- Configs],
_ = file:write_file(File, Format);
_ ->
ignore
diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl
index 09b0bc6710..940fdc9818 100644
--- a/lib/observer/src/ttb.erl
+++ b/lib/observer/src/ttb.erl
@@ -635,7 +635,7 @@ stop(Opts) when is_list(Opts) ->
ok;
{_, {stopped, _}} ->
%% Printout moved out of the ttb loop to avoid occasional deadlock
- io:format("Stored logs in ~s~n", [element(2, Result)]);
+ io:format("Stored logs in ~ts~n", [element(2, Result)]);
{_, _} ->
ok
end,
@@ -792,7 +792,7 @@ do_stop({FetchOrFormat, UserDir}, Sender, NodeInfo, SessionInfo) ->
write_config(?last_config, all),
Localhost = host(node()),
Dir = get_fetch_dir(UserDir, proplists:get_value(logfile, SessionInfo)),
- file:make_dir(Dir),
+ ok = filelib:ensure_dir(filename:join(Dir,"*")),
%% The nodes are traversed twice here because
%% the meta tracing in observer_backend must be
%% stopped before dbg is stopped, and dbg must
@@ -900,21 +900,29 @@ fetch_report(Localhost, Dir, Node, MetaFile) ->
fetch(Localhost,Dir,Node,MetaFile) ->
case (host(Node) == Localhost) orelse is_local(MetaFile) of
- true -> % same host, just move the files
+ true -> % same host, just move the files
Files = get_filenames(Node,MetaFile),
lists:foreach(
- fun(File0) ->
- Dest = filename:join(Dir,filename:basename(File0)),
- file:rename(File0, Dest)
- end,
- Files);
+ fun(File0) ->
+ Dest = filename:join(Dir,filename:basename(File0)),
+ file:rename(File0, Dest)
+ end,
+ Files);
false ->
{ok, LSock} = gen_tcp:listen(0, [binary,{packet,2},{active,false}]),
{ok,Port} = inet:port(LSock),
- rpc:cast(Node,observer_backend,ttb_fetch,
- [MetaFile,{Port,Localhost}]),
+ Enc = file:native_name_encoding(),
+ Args =
+ case rpc:call(Node,erlang,function_exported,
+ [observer_backend,ttb_fetch,3]) of
+ true ->
+ [MetaFile,{Port,Localhost},Enc];
+ false ->
+ [MetaFile,{Port,Localhost}]
+ end,
+ rpc:cast(Node,observer_backend,ttb_fetch,Args),
{ok, Sock} = gen_tcp:accept(LSock),
- receive_files(Dir,Sock,undefined),
+ receive_files(Dir,Sock,undefined,Enc),
ok = gen_tcp:close(LSock),
ok = gen_tcp:close(Sock)
end.
@@ -929,25 +937,48 @@ get_filenames(_N, {local,F,_}) ->
get_filenames(N, F) ->
rpc:call(N, observer_backend,ttb_get_filenames,[F]).
-receive_files(Dir,Sock,Fd) ->
+receive_files(Dir,Sock,Fd,Enc) ->
case gen_tcp:recv(Sock, 0) of
{ok, <<0,Bin/binary>>} ->
file:write(Fd,Bin),
- receive_files(Dir,Sock,Fd);
- {ok, <<1,Bin/binary>>} ->
- File0 = binary_to_list(Bin),
+ receive_files(Dir,Sock,Fd,Enc);
+ {ok, <<Code,Bin/binary>>} when Code==1; Code==2; Code==3 ->
+ File0 = decode_filename(Code,Bin,Enc),
File = filename:join(Dir,File0),
{ok,Fd1} = file:open(File,[raw,write]),
- receive_files(Dir,Sock,Fd1);
+ receive_files(Dir,Sock,Fd1,Enc);
{error, closed} ->
ok = file:close(Fd)
end.
+decode_filename(1,Bin,_Enc) ->
+ %% Old version of observer_backend - filename encoded with
+ %% list_to_binary
+ binary_to_list(Bin);
+decode_filename(2,Bin,Enc) ->
+ %% Successfully encoded filename with correct encoding
+ unicode:characters_to_list(Bin,Enc);
+decode_filename(3,Bin,latin1) ->
+ %% Filename encoded with faulty encoding. This has to be utf8
+ %% remote and latin1 here, and the filename actually containing
+ %% characters outside the latin1 range. So making an escaped
+ %% variant of the filename and warning about it.
+ File0 = unicode:characters_to_list(Bin,utf8),
+ File = [ case X of
+ High when High > 255 ->
+ ["\\\\x{",erlang:integer_to_list(X, 16),$}];
+ Low ->
+ Low
+ end || X <- File0 ],
+ io:format("Warning: fetching file with faulty filename encoding ~ts~n"
+ "Will be written as ~ts~n",
+ [File0,File]),
+ File.
+
host(Node) ->
[_name,Host] = string:tokens(atom_to_list(Node),"@"),
Host.
-
wait_for_fetch([]) ->
ok;
wait_for_fetch(Nodes) ->
@@ -1087,7 +1118,7 @@ read_traci(File) ->
{ok,B} ->
interpret_binary(B,dict:new(),[]);
_ ->
- io:format("Warning: no meta data file: ~s~n",[MetaFile]),
+ io:format("Warning: no meta data file: ~ts~n",[MetaFile]),
{dict:new(),[]}
end.
@@ -1303,7 +1334,7 @@ get_term(B) ->
end.
display_warning(Item,Warning) ->
- io:format("Warning: {~w,~w}~n",[Warning,Item]).
+ io:format("Warning: {~tw,~tw}~n",[Warning,Item]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/observer/src/ttb_et.erl b/lib/observer/src/ttb_et.erl
index 95e8e9aa07..1b828eebc0 100644
--- a/lib/observer/src/ttb_et.erl
+++ b/lib/observer/src/ttb_et.erl
@@ -137,13 +137,13 @@ processes(E0) ->
E = label(E0),
{{FromProc,FromNode},{ToProc,ToNode}} =
get_actors(E#event.from,E#event.to),
- {true,E#event{from = io_lib:format("~w~n~w",[FromProc,FromNode]),
- to = io_lib:format("~w~n~w",[ToProc,ToNode])}}.
+ {true,E#event{from = io_lib:format("~tw~n~w",[FromProc,FromNode]),
+ to = io_lib:format("~tw~n~w",[ToProc,ToNode])}}.
mods_and_procs(E) ->
ActorFun = fun({M,_F,_A},{Proc,Node}) ->
- io_lib:format("~w~n~w~n~w",[M,Proc,Node])
+ io_lib:format("~w~n~tw~n~w",[M,Proc,Node])
end,
calltrace_filter(E,ActorFun).
@@ -155,13 +155,13 @@ modules(E) ->
funcs_and_procs(E) ->
ActorFun = fun({M,F,A},{Proc,Node}) ->
- io_lib:format("~s~n~w~n~w",[mfa(M,F,A),Proc,Node])
+ io_lib:format("~ts~n~tw~n~w",[mfa(M,F,A),Proc,Node])
end,
calltrace_filter(E,ActorFun).
functions(E) ->
ActorFun = fun({M,F,A},{_Proc,Node}) ->
- io_lib:format("~s~n~w",[mfa(M,F,A),Node])
+ io_lib:format("~ts~n~w",[mfa(M,F,A),Node])
end,
calltrace_filter(E,ActorFun).
@@ -221,7 +221,7 @@ label(Event=#event{label=L,contents=C}) ->
false -> Event
end.
label(L,{M,F,A}) -> label(L,M,F,A);
-label(L,Other) -> io_lib:format("~w ~w",[L,Other]).
+label(L,Other) -> io_lib:format("~w ~tw",[L,Other]).
label(call,M,F,A) -> "call " ++ mfa(M,F,A);
label(return_from,M,F,A) -> "return_from " ++ mfa(M,F,A);
label(return_to,M,F,A) -> "return_to " ++ mfa(M,F,A);
diff --git a/lib/observer/test/Makefile b/lib/observer/test/Makefile
index 6100af5e17..a44e54fc52 100644
--- a/lib/observer/test/Makefile
+++ b/lib/observer/test/Makefile
@@ -27,7 +27,8 @@ MODULES = \
ttb_SUITE \
client \
server \
- crashdump_helper
+ crashdump_helper \
+ crashdump_helper_unicode
ERL_FILES= $(MODULES:%=%.erl)
@@ -46,7 +47,7 @@ RELSYSDIR = $(RELEASE_PATH)/observer_test
# FLAGS
# ----------------------------------------------------
ERL_MAKE_FLAGS +=
-ERL_COMPILE_FLAGS +=
+ERL_COMPILE_FLAGS += +warnings_as_errors +nowarn_export_all
EBIN = .
diff --git a/lib/observer/test/crashdump_helper_unicode.erl b/lib/observer/test/crashdump_helper_unicode.erl
new file mode 100644
index 0000000000..60c3d20315
--- /dev/null
+++ b/lib/observer/test/crashdump_helper_unicode.erl
@@ -0,0 +1,22 @@
+-module(crashdump_helper_unicode).
+-behaviour(gen_server).
+-export([start/0, init/1, handle_call/3, handle_cast/2]).
+-record(state, {s,a,b,lb}).
+
+start() ->
+ gen_server:start({local, 'unicode_reg_name_αβ'}, ?MODULE, [], []).
+
+init([]) ->
+ process_flag(trap_exit, true),
+ ets:new('tab_αβ',[set,named_table]),
+ Bin = <<"bin αβ"/utf8>>,
+ LongBin = <<"long bin αβ - a utf8 binary which can be expanded αβ"/utf8>>,
+ {ok, #state{s = "unicode_string_αβ",
+ a = 'unicode_atom_αβ',
+ b = Bin,
+ lb = LongBin}}.
+
+handle_call(_Info, _From, State) ->
+ {reply, ok, State}.
+handle_cast(_Info, State) ->
+ {noreply, State}.
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index 1fd94ffb3c..f9ac884743 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -76,7 +76,7 @@ end_per_testcase(Case, Config) ->
end,
ok.
-suite() -> [{ct_hooks,[ts_install_cth]}].
+suite() -> [].
all() ->
[start_stop,
@@ -364,6 +364,9 @@ special(File,Procs) ->
crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
io:format(" expand binary ok",[]),
+ #proc{last_calls=LastCalls} = ProcDetails,
+ true = length(LastCalls) =< 4,
+
['#CDVPid',X1,Y1,Z1] = proplists:get_value(ext_pid,Dict),
ChannelStr1 = integer_to_list(X1),
ExtPid =
@@ -413,19 +416,102 @@ special(File,Procs) ->
old_attrib=undefined,
old_comp_info=undefined}=Mod2,
ok;
- %% ".strangemodname" ->
- %% {ok,Mods,[]} = crashdump_viewer:loaded_modules(),
- %% lookat_all_mods(Mods),
- %% ok;
- %% ".sort" ->
- %% %% sort ports, atoms and modules ????
- %% ok;
- %% ".trunc" ->
- %% %% ????
- %% ok;
- ".trunc.bytes" ->
+ ".trunc_bin1" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:"
+ %% i.e. no binary exist in the dump
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ '#CDVNonexistingBinary' = proplists:get_value(bin,Dict),
+ '#CDVNonexistingBinary' = proplists:get_value(sub_bin,Dict),
+
+ io:format(" nonexisting binaries ok",[]),
+ ok;
+ ".trunc_bin2" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:Addr\n
+ %% Size"
+ %% i.e. binaries are truncated
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
+
+ io:format(" expand truncated binary ok",[]),
+ ok;
+ ".trunc_bin3" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:Addr\n
+ %% Size:"
+ %% i.e. same as 'trunc_bin2', except the colon exists also
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
+
+ io:format(" expand truncated binary ok",[]),
+ ok;
+ ".trunc_bin4" ->
+ %% This is 'full_dist' truncated after the first
+ %% "=binary:Addr\n
+ %% Size:BinaryMissinOneByte"
+ %% i.e. the full binary is truncated, but the sub binary is complete
+ [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+
+ ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict),
+ {ok,'#CDVTruncatedBinary'} =
+ crashdump_viewer:expand_binary({Offset,Size,Pos}),
+ io:format(" expand truncated binary ok",[]),
+ ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict),
+ {ok,<<_:SSize/binary>>} =
+ crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
+ io:format(" expand complete sub binary ok",[]),
+
+ ok;
+ ".trunc_bytes" ->
{ok,_,[TW]} = crashdump_viewer:general_info(),
{match,_} = re:run(TW,"CRASH DUMP SIZE LIMIT REACHED"),
+ io:format(" size limit information ok",[]),
+ ok;
+ ".unicode" ->
+ #proc{pid=Pid0} =
+ lists:keyfind("'unicode_reg_name_αβ'",#proc.name,Procs),
+ Pid = pid_to_list(Pid0),
+ {ok,#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" unicode registered name ok",[]),
+
+ {ok,[#ets_table{id="'tab_αβ'",name="'tab_αβ'"}],[]} =
+ crashdump_viewer:ets_tables(Pid),
+ io:format(" unicode table name ok",[]),
+
ok;
_ ->
ok
@@ -488,15 +574,45 @@ do_create_dumps(DataDir,Rel) ->
current ->
CD3 = dump_with_args(DataDir,Rel,"instr","+Mim true"),
CD4 = dump_with_strange_module_name(DataDir,Rel,"strangemodname"),
- Bytes = rand:uniform(300000) + 100,
- CD5 = dump_with_args(DataDir,Rel,"trunc.bytes",
+ Tmp = dump_with_args(DataDir,Rel,"trunc_bytes",""),
+ {ok,#file_info{size=Max}} = file:read_file_info(Tmp),
+ ok = file:delete(Tmp),
+ Bytes = max(15,rand:uniform(Max)),
+ CD5 = dump_with_args(DataDir,Rel,"trunc_bytes",
"-env ERL_CRASH_DUMP_BYTES " ++
integer_to_list(Bytes)),
- {[CD1,CD2,CD3,CD4,CD5], DosDump};
+ CD6 = dump_with_unicode_atoms(DataDir,Rel,"unicode"),
+ TruncatedDumps = truncate_dump(CD1),
+ {[CD1,CD2,CD3,CD4,CD5,CD6|TruncatedDumps], DosDump};
_ ->
{[CD1,CD2], DosDump}
end.
+truncate_dump(File) ->
+ {ok,Bin} = file:read_file(File),
+ BinTag = <<"\n=binary:">>,
+ Colon = <<":">>,
+ NewLine = case os:type() of
+ {win32,_} -> <<"\r\n">>;
+ _ -> <<"\n">>
+ end,
+ [StartBin,AfterTag] = binary:split(Bin,BinTag),
+ [AddrAndSize,BinaryAndRest] = binary:split(AfterTag,Colon),
+ [Binary,_Rest] = binary:split(BinaryAndRest,NewLine),
+ TruncSize = byte_size(Binary) - 2,
+ <<TruncBinary:TruncSize/binary,_/binary>> = Binary,
+ TruncName = filename:rootname(File) ++ ".trunc_bin",
+ write_trunc_files(TruncName,StartBin,
+ [BinTag,AddrAndSize,Colon,TruncBinary],1).
+
+write_trunc_files(TruncName0,Bin,[Part|Parts],N) ->
+ TruncName = TruncName0++integer_to_list(N),
+ Bin1 = <<Bin/binary,Part/binary>>,
+ ok = file:write_file(TruncName,Bin1),
+ [TruncName|write_trunc_files(TruncName0,Bin1,Parts,N+1)];
+write_trunc_files(_,_,[],_) ->
+ [].
+
%% Create a dump which has three visible nodes, one hidden and one
%% not connected node, and with monitors and links between nodes.
@@ -573,6 +689,16 @@ dump_with_strange_module_name(DataDir,Rel,DumpName) ->
?t:stop_node(n1),
CD.
+dump_with_unicode_atoms(DataDir,Rel,DumpName) ->
+ Opt = rel_opt(Rel),
+ Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"",
+ PzOpt = [{args,Pz}],
+ {ok,N1} = ?t:start_node(n1,peer,Opt ++ PzOpt),
+ {ok,_Pid} = rpc:call(N1,crashdump_helper_unicode,start,[]),
+ CD = dump(N1,DataDir,Rel,DumpName),
+ ?t:stop_node(n1),
+ CD.
+
dump(Node,DataDir,Rel,DumpName) ->
Crashdump = filename:join(DataDir, dump_prefix(Rel)++DumpName),
rpc:call(Node,os,putenv,["ERL_CRASH_DUMP",Crashdump]),
diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl
index 41726b1521..0db2c1ea77 100644
--- a/lib/observer/test/observer_SUITE.erl
+++ b/lib/observer/test/observer_SUITE.erl
@@ -115,6 +115,7 @@ basic(doc) -> [""];
basic(Config) when is_list(Config) ->
timer:send_after(100, "foobar"), %% Otherwise the timer server gets added to procs
ProcsBefore = processes(),
+ ProcInfoBefore = [{P,process_info(P)} || P <- ProcsBefore],
NumProcsBefore = length(ProcsBefore),
ok = observer:start(),
@@ -145,8 +146,10 @@ basic(Config) when is_list(Config) ->
ProcsAfter = processes(),
NumProcsAfter = length(ProcsAfter),
if NumProcsAfter=/=NumProcsBefore ->
+ BeforeNotAfter = ProcsBefore -- ProcsAfter,
ct:log("Before but not after:~n~p~n",
- [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]),
+ [[{P,I} || {P,I} <- ProcInfoBefore,
+ lists:member(P,BeforeNotAfter)]]),
ct:log("After but not before:~n~p~n",
[[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]),
ct:fail("leaking processes");
@@ -304,10 +307,10 @@ table_win(Config) when is_list(Config) ->
%% Test PR-1296/OTP-14151
%% Clicking a link to a port before the port tab has been activated the
%% first time crashes observer.
-port_win_when_tab_not_initiated(Config) ->
+port_win_when_tab_not_initiated(_Config) ->
{ok,Port} = gen_tcp:listen(0,[]),
ok = observer:start(),
- Notebook = setup_whitebox_testing(),
+ _Notebook = setup_whitebox_testing(),
observer ! {open_link,erlang:port_to_list(Port)},
timer:sleep(1000),
observer:stop(),
diff --git a/lib/observer/test/ttb_SUITE.erl b/lib/observer/test/ttb_SUITE.erl
index c06ec21f36..e8c2f9e37d 100644
--- a/lib/observer/test/ttb_SUITE.erl
+++ b/lib/observer/test/ttb_SUITE.erl
@@ -778,37 +778,37 @@ otp_4967_2(suite) ->
otp_4967_2(doc) ->
["OTP-4967: Trace message sent to {Name, Node}"];
otp_4967_2(Config) when is_list(Config) ->
- io:format("1: ~p",[now()]),
+ io:format("1: ~p",[erlang:timestamp()]),
?line Privdir = priv_dir(Config),
- io:format("2: ~p",[now()]),
+ io:format("2: ~p",[erlang:timestamp()]),
?line File = filename:join(Privdir,"otp_4967"),
- io:format("3: ~p",[now()]),
+ io:format("3: ~p",[erlang:timestamp()]),
?line S = self(),
- io:format("4: ~p",[now()]),
+ io:format("4: ~p",[erlang:timestamp()]),
?line {ok,[Node]} =
ttb:tracer(node(),[{file, File},
{handler,{fun myhandler/4, S}}]),
- io:format("5: ~p",[now()]),
+ io:format("5: ~p",[erlang:timestamp()]),
%% Test that delayed registration of a process works.
receive after 200 -> ok end,
?line register(otp_4967,self()),
- io:format("6: ~p",[now()]),
+ io:format("6: ~p",[erlang:timestamp()]),
?line {ok,[{S,[{matched,Node,1}]}]} = ttb:p(self(),s),
- io:format("7: ~p",[now()]),
+ io:format("7: ~p",[erlang:timestamp()]),
?line {otp_4967,node()} ! heihopp,
- io:format("8: ~p",[now()]),
+ io:format("8: ~p",[erlang:timestamp()]),
?line stopped = ttb:stop([format]),
- io:format("9: ~p",[now()]),
+ io:format("9: ~p",[erlang:timestamp()]),
?line Msgs = flush(),
- io:format("10: ~p",[now()]),
+ io:format("10: ~p",[erlang:timestamp()]),
?line io:format("Messages received: \n~p\n",[Msgs]),
- io:format("11: ~p",[now()]),
+ io:format("11: ~p",[erlang:timestamp()]),
?line true = lists:member(heihopp,Msgs), % the heihopp message itself
- io:format("13: ~p",[now()]),
+ io:format("13: ~p",[erlang:timestamp()]),
?line {value,{trace_ts,_,send,heihopp,{_,otp_4967,Node},{_,_,_}}} =
lists:keysearch(heihopp,4,Msgs), % trace trace of the heihopp message
- io:format("14: ~p",[now()]),
+ io:format("14: ~p",[erlang:timestamp()]),
?line end_of_trace = lists:last(Msgs), % end of the trace
ok.