diff options
Diffstat (limited to 'lib/observer')
-rw-r--r-- | lib/observer/doc/src/notes.xml | 116 | ||||
-rw-r--r-- | lib/observer/doc/src/observer_ug.xml | 5 | ||||
-rw-r--r-- | lib/observer/src/cdv_dist_cb.erl | 12 | ||||
-rw-r--r-- | lib/observer/src/cdv_timer_cb.erl | 7 | ||||
-rw-r--r-- | lib/observer/src/cdv_virtual_list_wx.erl | 11 | ||||
-rw-r--r-- | lib/observer/src/crashdump_viewer.erl | 54 | ||||
-rw-r--r-- | lib/observer/src/crashdump_viewer.hrl | 1 | ||||
-rw-r--r-- | lib/observer/src/observer.app.src | 5 | ||||
-rw-r--r-- | lib/observer/src/observer.erl | 7 | ||||
-rw-r--r-- | lib/observer/src/observer_lib.erl | 2 | ||||
-rw-r--r-- | lib/observer/src/observer_procinfo.erl | 5 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_table.erl | 4 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_wx.erl | 6 | ||||
-rw-r--r-- | lib/observer/src/observer_wx.erl | 22 | ||||
-rw-r--r-- | lib/observer/test/crashdump_helper.erl | 4 | ||||
-rw-r--r-- | lib/observer/test/crashdump_viewer_SUITE.erl | 109 | ||||
-rw-r--r-- | lib/observer/test/observer_SUITE.erl | 248 | ||||
-rw-r--r-- | lib/observer/vsn.mk | 2 |
18 files changed, 545 insertions, 75 deletions
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml index 9de00b8c16..11729078c2 100644 --- a/lib/observer/doc/src/notes.xml +++ b/lib/observer/doc/src/notes.xml @@ -31,6 +31,122 @@ <p>This document describes the changes made to the Observer application.</p> +<section><title>Observer 2.0.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A note saying only R15B nodes can be observed is removed + from the user guide.</p> + <p> + Own Id: OTP-12078</p> + </item> + </list> + </section> + +</section> + +<section><title>Observer 2.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed statusbar on Windows</p> + <p> + Own Id: OTP-12162</p> + </item> + </list> + </section> + +</section> + +<section><title>Observer 2.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + crashdump_viewer would crash if the owner of a timer was + specified as the process' registered name. This has been + corrected.</p> + <p> + Own Id: OTP-11919</p> + </item> + <item> + <p> + Fix crash and minor updates.</p> + <p> + Own Id: OTP-11949</p> + </item> + </list> + </section> + +</section> + +<section><title>Observer 2.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + etop trace handler now works in smp environment (Thanks + to Péter Gömöri)</p> + <p> + Own Id: OTP-11633</p> + </item> + <item> + <p> + Application upgrade (appup) files are corrected for the + following applications: </p> + <p> + <c>asn1, common_test, compiler, crypto, debugger, + dialyzer, edoc, eldap, erl_docgen, et, eunit, gs, hipe, + inets, observer, odbc, os_mon, otp_mibs, parsetools, + percept, public_key, reltool, runtime_tools, ssh, + syntax_tools, test_server, tools, typer, webtool, wx, + xmerl</c></p> + <p> + A new test utility for testing appup files is added to + test_server. This is now used by most applications in + OTP.</p> + <p> + (Thanks to Tobias Schlager)</p> + <p> + Own Id: OTP-11744</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Removed gs based applications and gs based backends. The + <c>observer</c> application replaces the removed + applications.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-10915</p> + </item> + <item> + <p> + The <c>crashdump_viewer</c> is re-written using + <c>wx</c>. The old <c>webtool</c> interface for + <c>crashdump_viewer</c> does no longer exist.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-11179</p> + </item> + </list> + </section> + +</section> + <section><title>Observer 1.3.1.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml index 3aeaf1997a..62f99c5210 100644 --- a/lib/observer/doc/src/observer_ug.xml +++ b/lib/observer/doc/src/observer_ug.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2011</year><year>2013</year> + <year>2011</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -54,9 +54,6 @@ impact only the active viewer is updated and the other views will be updated when activated. </p> - <note> - <p>Only R15B nodes can be observed.</p> - </note> <p> In general the mouse buttons behaves as expected, use left click to select objects, right click to pop up a menu with most used diff --git a/lib/observer/src/cdv_dist_cb.erl b/lib/observer/src/cdv_dist_cb.erl index 3860324d6f..f7e6c9aded 100644 --- a/lib/observer/src/cdv_dist_cb.erl +++ b/lib/observer/src/cdv_dist_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -22,7 +22,8 @@ get_info/1, get_detail_cols/1, get_details/1, - detail_pages/0]). + detail_pages/0, + format/1]). -include_lib("wx/include/wx.hrl"). -include("crashdump_viewer.hrl"). @@ -75,6 +76,11 @@ init_gen_page(Parent, Info) -> Fields = info_fields(), cdv_info_wx:start_link(Parent,{Fields,Info,[]}). +format({creations,Creations}) -> + string:join([integer_to_list(C) || C <- Creations],","); +format(D) -> + D. + %%%----------------------------------------------------------------- %%% Internal info_fields() -> @@ -83,7 +89,7 @@ info_fields() -> {"Type", conn_type}, {"Channel", channel}, {"Controller", {click,controller}}, - {"Creation", creation}, + {"Creation", {{format,fun format/1},creation}}, {"Extra Info", error}]}, {scroll_boxes, [{"Remote Links",1,{click,remote_links}}, diff --git a/lib/observer/src/cdv_timer_cb.erl b/lib/observer/src/cdv_timer_cb.erl index 9cdbfa05a9..d44592cf18 100644 --- a/lib/observer/src/cdv_timer_cb.erl +++ b/lib/observer/src/cdv_timer_cb.erl @@ -27,18 +27,21 @@ %% Defines -define(COL_OWNER, 0). --define(COL_MSG, ?COL_OWNER+1). +-define(COL_NAME, ?COL_OWNER+1). +-define(COL_MSG, ?COL_NAME+1). -define(COL_TIME, ?COL_MSG+1). %% Callbacks for cdv_virtual_list_wx col_to_elem(id) -> col_to_elem(?COL_OWNER); col_to_elem(?COL_OWNER) -> #timer.pid; +col_to_elem(?COL_NAME) -> #timer.name; col_to_elem(?COL_MSG) -> #timer.msg; col_to_elem(?COL_TIME) -> #timer.time. col_spec() -> [{"Owner", ?wxLIST_FORMAT_LEFT, 110}, - {"Message", ?wxLIST_FORMAT_LEFT, 400}, + {"Owner name", ?wxLIST_FORMAT_LEFT, 150}, + {"Message", ?wxLIST_FORMAT_LEFT, 300}, {"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 80}]. get_info(Owner) -> diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl index c5a7d9a2e5..bfe115a42e 100644 --- a/lib/observer/src/cdv_virtual_list_wx.erl +++ b/lib/observer/src/cdv_virtual_list_wx.erl @@ -269,7 +269,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click, MenuId = ?ID_DETAILS + Col, ColText = call(Holder, {get_row, self(), Row, Col}), case ColText of - "[]" -> []; + Empty when Empty=="[]"; Empty=="" -> []; _ -> What = case catch list_to_integer(ColText) of @@ -284,8 +284,13 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click, end end, MenuCols), - wxWindow:popupMenu(Panel, Menu), - wxMenu:destroy(Menu), + case MenuItems of + [] -> + wxMenu:destroy(Menu); + _ -> + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu) + end, {noreply,State#state{menu_items=MenuItems}}; handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index a17efbccb0..99329b94e2 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2013. All Rights Reserved. +%% Copyright Ericsson AB 2003-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -298,6 +298,7 @@ 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]), {ok, #state{}}. %%-------------------------------------------------------------------- @@ -978,9 +979,20 @@ count() -> %%----------------------------------------------------------------- %% Page with all processes procs_summary(File,WS) -> - ParseFun = fun(Fd,Pid) -> + ParseFun = fun(Fd,Pid0) -> + Pid = list_to_pid(Pid0), Proc = get_procinfo(Fd,fun main_procinfo/5, - #proc{pid=list_to_pid(Pid)},WS), + #proc{pid=Pid},WS), + case Proc#proc.name of + undefined -> + true; + Name -> + %% Registered process - store to allow + %% lookup for timers connected to + %% registered name instead of pid. + ets:insert(cdv_reg_proc_table,{Name,Pid}), + ets:insert(cdv_reg_proc_table,{Pid0,Name}) + end, case Proc#proc.memory of undefined -> Proc#proc{memory=Proc#proc.stack_heap}; _ -> Proc @@ -1495,8 +1507,28 @@ get_internal_ets_tables(File,WS) -> %%----------------------------------------------------------------- %% Page with list of all timers get_timers(File,Pid) -> - ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=list_to_pid(Id)}) end, - lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers"). + ParseFun = fun(Fd,Id) -> get_timerinfo(Fd,Id) end, + T1 = lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers"), + T2 = case ets:lookup(cdv_reg_proc_table,Pid) of + [{_,Name}] -> + lookup_and_parse_index(File,{?timer,Name},ParseFun,"timers"); + _ -> + [] + end, + T1 ++ T2. + +get_timerinfo(Fd,Id) -> + case catch list_to_pid(Id) of + Pid when is_pid(Pid) -> + get_timerinfo_1(Fd,#timer{pid=Pid}); + _ -> + case ets:lookup(cdv_reg_proc_table,Id) of + [{_,Pid}] when is_pid(Pid) -> + get_timerinfo_1(Fd,#timer{pid=Pid,name=Id}); + [] -> + get_timerinfo_1(Fd,#timer{name=Id}) + end + end. get_timerinfo_1(Fd,Timer) -> case line_head(Fd) of @@ -1576,7 +1608,13 @@ get_nodeinfo(Fd,Nod) -> "Controller" -> get_nodeinfo(Fd,Nod#nod{controller=val(Fd)}); "Creation" -> - get_nodeinfo(Fd,Nod#nod{creation=list_to_integer(val(Fd))}); + %% 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)," ")), + get_nodeinfo(Fd,Nod#nod{creation={creations,Creations}}); "Remote link" -> Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>" {Local,Remote} = split(Procs), @@ -2559,11 +2597,11 @@ progress_pmap(Report,File,Fun,List) -> {L1,L2} = if length(L)>=NPerProc -> lists:split(NPerProc,L); true -> {L,[]} % last chunk end, - P = spawn( + {P,_Ref} = + spawn_monitor( fun() -> progress_map(Collector,ReportInterval,File,Fun,L1) end), - erlang:monitor(process,P), {L2,[P|Ps]} end, {List,[]}, diff --git a/lib/observer/src/crashdump_viewer.hrl b/lib/observer/src/crashdump_viewer.hrl index ae288ed573..0e2eba6dee 100644 --- a/lib/observer/src/crashdump_viewer.hrl +++ b/lib/observer/src/crashdump_viewer.hrl @@ -108,6 +108,7 @@ -record(timer, {pid, + name, msg, time}). diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src index f14f0ee849..97a54cd6f9 100644 --- a/lib/observer/src/observer.app.src +++ b/lib/observer/src/observer.app.src @@ -60,6 +60,9 @@ ttb_et]}, {registered, []}, {applications, [kernel, stdlib]}, - {env, []}]}. + {env, []}, + {runtime_dependencies, ["wx-1.2","stdlib-2.0","runtime_tools-1.8.14", + "kernel-3.0","inets-5.10","et-1.5", + "erts-6.0"]}]}. diff --git a/lib/observer/src/observer.erl b/lib/observer/src/observer.erl index 098100e8ee..a30ceecc63 100644 --- a/lib/observer/src/observer.erl +++ b/lib/observer/src/observer.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -18,8 +18,11 @@ -module(observer). --export([start/0]). +-export([start/0, stop/0]). start() -> observer_wx:start(). + +stop() -> + observer_wx:stop(). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index cedaf7d2b8..34c7b127ff 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -249,6 +249,8 @@ to_str({func, {F,A}}) when is_atom(F), is_integer(A) -> lists:concat([F, "/", A]); to_str({func, {F,'_'}}) when is_atom(F) -> atom_to_list(F); +to_str({{format,Fun},Value}) when is_function(Fun) -> + Fun(Value); to_str({A, B}) when is_atom(A), is_atom(B) -> lists:concat([A, ":", B]); to_str({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) -> diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 3ffa5fc77d..8e8a37fc93 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -34,6 +34,7 @@ -record(state, {parent, frame, + notebook, pid, pages=[], expand_table, @@ -76,6 +77,7 @@ init([Pid, ParentFrame, Parent]) -> {Frame, #state{parent=Parent, pid=Pid, frame=Frame, + notebook=Notebook, pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage], expand_table=Table }} @@ -158,6 +160,9 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State) handle_event(Event, _State) -> error({unhandled_event, Event}). +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]), {noreply, State}. diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 59fe5b5670..7757dfea53 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -409,7 +409,7 @@ handle_info({refresh, Min, Min}, State = #state{grid=Grid}) -> wxListCtrl:refreshItem(Grid, Min), %% Avoid assert in wx below if Max is 0 {noreply, State}; handle_info({refresh, Min, Max}, State = #state{grid=Grid}) -> - wxListCtrl:refreshItems(Grid, Min, Max), + Max > 0 andalso wxListCtrl:refreshItems(Grid, Min, Max), {noreply, State}; handle_info(refresh_interval, State = #state{pid=Pid}) -> diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index b276965f83..da4cb8e041 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -132,8 +132,8 @@ handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> observer_lib:set_listctrl_col_size(Grid, W), {noreply, State}; -handle_event(#wx{obj=Grid, event=#wxList{type=command_list_item_activated, - itemIndex=Index}}, +handle_event(#wx{event=#wxList{type=command_list_item_activated, + itemIndex=Index}}, State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs}) -> Table = lists:nth(Index+1, Tabs), case Table#tab.protection of diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index ecb8e132fe..c86f5ea916 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -19,7 +19,7 @@ -behaviour(wx_object). --export([start/0]). +-export([start/0, stop/0]). -export([create_menus/2, get_attrib/1, get_tracer/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]). @@ -69,6 +69,9 @@ start() -> _Obj -> ok end. +stop() -> + wx_object:call(observer, stop). + create_menus(Object, Menus) when is_list(Menus) -> wx_object:call(Object, {create_menus, Menus}). @@ -109,9 +112,6 @@ setup(#state{frame = Frame} = State) -> observer_lib:create_menus(DefMenus, MenuBar, default), wxFrame:setMenuBar(Frame, MenuBar), - StatusBar = wxFrame:createStatusBar(Frame, []), - wxFrame:setTitle(Frame, atom_to_list(node())), - wxStatusBar:setStatusText(StatusBar, atom_to_list(node())), %% Setup panels Panel = wxPanel:new(Frame, []), @@ -127,6 +127,11 @@ setup(#state{frame = Frame} = State) -> wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), wxPanel:setSizer(Panel, MainSizer), + StatusBar = wxStatusBar:new(Frame), + wxFrame:setStatusBar(Frame, StatusBar), + wxFrame:setTitle(Frame, atom_to_list(node())), + wxStatusBar:setStatusText(StatusBar, atom_to_list(node())), + wxNotebook:connect(Notebook, command_notebook_page_changing), wxFrame:connect(Frame, close_window, [{skip, true}]), wxMenu:connect(Frame, command_menu_selected), @@ -331,6 +336,10 @@ handle_call({get_attrib, Attrib}, _From, State) -> handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) -> {reply, TraceP, State}; +handle_call(stop, _, State = #state{frame = Frame}) -> + wxFrame:destroy(Frame), + {stop, normal, ok, State}; + handle_call(_Msg, _From, State) -> {reply, ok, State}. @@ -367,6 +376,10 @@ handle_info({open_link, Pid0}, State = #state{pro_panel=ProcViewer, frame=Frame} end, {noreply, State}; +handle_info({get_debug_info, From}, State = #state{notebook=Notebook, active_tab=Pid}) -> + From ! {observer_debug, wx:get_env(), Notebook, Pid}, + {noreply, State}; + handle_info({'EXIT', Pid, _Reason}, State) -> io:format("Child (~s) crashed exiting: ~p ~p~n", [pid2panel(Pid, State), Pid,_Reason]), @@ -377,6 +390,7 @@ handle_info(_Info, State) -> terminate(_Reason, #state{frame = Frame}) -> wxFrame:destroy(Frame), + wx:destroy(), ok. code_change(_, _, State) -> diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl index 40dbe28d46..0eb4a92c53 100644 --- a/lib/observer/test/crashdump_helper.erl +++ b/lib/observer/test/crashdump_helper.erl @@ -35,7 +35,9 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) -> register(aaaaaaaa,self()), process_flag(save_calls,3), ets:new(cdv_test_ordset_table,[ordered_set]), - erlang:send_after(1000000,self(),cdv_test_timer_message), + erlang:send_after(1000000,self(),cdv_test_timer_message1), + erlang:send_after(1000000,aaaaaaaa,cdv_test_timer_message2), + erlang:send_after(1000000,noexistproc,cdv_test_timer_message3), Port = hd(erlang:ports()), Fun = fun() -> ok end, Ref = make_ref(), diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl index 7a582436b4..03ab0c20e1 100644 --- a/lib/observer/test/crashdump_viewer_SUITE.erl +++ b/lib/observer/test/crashdump_viewer_SUITE.erl @@ -120,27 +120,63 @@ start_stop(Config) when is_list(Config) -> Dump = hd(?config(dumps,Config)), timer:sleep(1000), - ProcsBefore = length(processes()), + ProcsBefore = processes(), + NumProcsBefore = length(ProcsBefore), ok = crashdump_viewer:start(Dump), - true = is_pid(whereis(crashdump_viewer_server)), - true = is_pid(whereis(cdv_wx)), - true = is_pid(whereis(cdv_proc_cb)), - true = is_pid(whereis(cdv_port_cb)), - true = is_pid(whereis(cdv_ets_cb)), - true = is_pid(whereis(cdv_timer_cb)), - true = is_pid(whereis(cdv_fun_cb)), - true = is_pid(whereis(cdv_atom_cb)), - true = is_pid(whereis(cdv_dist_cb)), - true = is_pid(whereis(cdv_mod_cb)), + ExpectedRegistered = [crashdump_viewer_server, + cdv_wx, + cdv_proc_cb, + cdv_proc_cb__holder, + cdv_port_cb, + cdv_port_cb__holder, + cdv_ets_cb, + cdv_ets_cb__holder, + cdv_timer_cb, + cdv_timer_cb__holder, + cdv_fun_cb, + cdv_fun_cb__holder, + cdv_atom_cb, + cdv_atom_cb__holder, + cdv_dist_cb, + cdv_dist_cb__holder, + cdv_mod_cb, + cdv_mod_cb__holder], + Regs=[begin + P=whereis(N), + {P,N,erlang:monitor(process,P)} + end || N <- ExpectedRegistered], + ct:log("CDV procs: ~n~p~n",[Regs]), + [true=is_pid(P) || {P,_,_} <- Regs], timer:sleep(5000), % give some time to live ok = crashdump_viewer:stop(), - timer:sleep(1000), % give some time to stop - undefined = whereis(crashdump_viewer_server), - undefined = whereis(cdv_wx), - ProcsAfter=length(processes()), - ProcsAfter=ProcsBefore, + recv_downs(Regs), + timer:sleep(2000), + ProcsAfter = processes(), + NumProcsAfter = length(ProcsAfter), + if NumProcsAfter=/=NumProcsBefore -> + ct:log("Before but not after:~n~p~n", + [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]), + ct:log("After but not before:~n~p~n", + [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]), + ct:fail("leaking processes"); + true -> + ok + end, ok. +recv_downs([]) -> + ok; +recv_downs(Regs) -> + receive + {'DOWN',Ref,process,_Pid,_} -> + ct:log("Got 'DOWN' for process ~n~p~n",[_Pid]), + recv_downs(lists:keydelete(Ref,3,Regs)) + after 30000 -> + ct:log("Timeout waiting for down:~n~p~n", + [[{Reg,process_info(P)} || {P,_,_}=Reg <- Regs]]), + ct:log("Message queue:~n~p~n",[process_info(self(),messages)]) + end. + %% Try to load nonexisting file non_existing(Config) when is_list(Config) -> ExpectedReason = "non-existing-file is not an Erlang crash dump\n", @@ -265,10 +301,12 @@ wait_for_progress_done() -> %%%----------------------------------------------------------------- %%% General check of what is displayed for a dump browse_file(File) -> - io:format("Browsing file: ~s~n",[File]), + io:format("~nBrowsing file: ~s",[File]), ok = start_backend(File), + io:format(" backend started",[]), + {ok,_GI=#general_info{},_GenTW} = crashdump_viewer:general_info(), {ok,Procs,_ProcsTW} = crashdump_viewer:processes(), {ok,Ports,_PortsTW} = crashdump_viewer:ports(), @@ -285,10 +323,16 @@ browse_file(File) -> {ok,_HashTabs,_HashTabsTW} = crashdump_viewer:hash_tables(), {ok,_IndexTabs,_IndexTabsTW} = crashdump_viewer:index_tables(), + io:format(" info read",[]), + lookat_all_pids(Procs), + io:format(" pids ok",[]), lookat_all_ports(Ports), + io:format(" ports ok",[]), lookat_all_mods(Mods), + io:format(" mods ok",[]), lookat_all_nodes(Nodes), + io:format(" nodes ok",[]), Procs. % used as second arg to special/2 @@ -303,6 +347,7 @@ special(File,Procs) -> [#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, @@ -314,6 +359,7 @@ special(File,Procs) -> ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), {ok,<<_:SSize/binary>>} = crashdump_viewer:expand_binary({SOffset,SSize,SPos}), + io:format(" expand binary ok",[]), ['#CDVPid',X1,Y1,Z1] = proplists:get_value(ext_pid,Dict), ChannelStr1 = integer_to_list(X1), @@ -323,22 +369,34 @@ special(File,Procs) -> integer_to_list(Z1) ++ ">", {error,{other_node,ChannelStr1}} = crashdump_viewer:proc_details(ExtPid), + io:format(" process details external ok",[]), ['#CDVPort',X2,Y2] = proplists:get_value(port,Dict), ChannelStr2 = integer_to_list(X2), Port = "#Port<"++ChannelStr2++"."++integer_to_list(Y2)++">", {ok,_PortDetails=#port{},[]} = crashdump_viewer:port(Port), + io:format(" port details ok",[]), ['#CDVPort',X3,Y3] = proplists:get_value(ext_port,Dict), ChannelStr3 = integer_to_list(X3), ExtPort = "#Port<"++ChannelStr3++"."++integer_to_list(Y3)++">", {error,{other_node,ChannelStr3}} = crashdump_viewer:port(ExtPort), + io:format(" port details external ok",[]), {ok,[_Ets=#ets_table{}],[]} = crashdump_viewer:ets_tables(Pid), - {ok,[_Timer=#timer{}],[]} = crashdump_viewer:timers(Pid), + io:format(" ets tables ok",[]), + + {ok,[#timer{pid=Pid0,name=undefined}, + #timer{pid=Pid0,name="aaaaaaaa"}],[]} = + crashdump_viewer:timers(Pid), + {ok,AllTimers,_TimersTW} = crashdump_viewer:timers(all), + #timer{name="noexistproc"} = + lists:keyfind(undefined,#timer.pid,AllTimers), + io:format(" timers ok:",[]), {ok,Mod1=#loaded_mod{},[]} = crashdump_viewer:loaded_mod_details(atom_to_list(?helper_mod)), + io:format(" modules ok",[]), #loaded_mod{current_size=CS, old_size=OS, old_attrib=A,old_comp_info=C}=Mod1, true = is_integer(CS), @@ -347,6 +405,7 @@ special(File,Procs) -> true = (C=/=undefined), {ok,Mod2=#loaded_mod{},[]} = crashdump_viewer:loaded_mod_details("application"), + io:format(" module details ok",[]), #loaded_mod{old_size="No old code exists", old_attrib=undefined, old_comp_info=undefined}=Mod2, @@ -428,8 +487,10 @@ do_create_dumps(DataDir,Rel) -> end. -%% Create a dump which has two visible nodes, one hidden and one +%% Create a dump which has three visible nodes, one hidden and one %% not connected node, and with monitors and links between nodes. +%% One of the visible nodes is stopped and started again in order to +%% get multiple creations. full_dist_dump(DataDir,Rel) -> Opt = rel_opt(Rel), Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", @@ -450,6 +511,15 @@ full_dist_dump(DataDir,Rel) -> get_response(P4), get_response(P1), + %% start, stop and start a node in order to get multiple 'creations' + {ok,N5} = ?t:start_node(n5,peer,Opt ++ PzOpt), + P51 = rpc:call(N5,?helper_mod,remote_proc,[P1,Creator]), + get_response(P51), + ?t:stop_node(N5), + {ok,N5} = ?t:start_node(n5,peer,Opt ++ PzOpt), + P52 = rpc:call(N5,?helper_mod,remote_proc,[P1,Creator]), + get_response(P52), + {aaaaaaaa,N1} ! {hello,from,other,node}, % distribution message ?t:stop_node(N3), @@ -458,6 +528,7 @@ full_dist_dump(DataDir,Rel) -> ?t:stop_node(N2), ?t:stop_node(N4), + ?t:stop_node(N5), CD. get_response(P) -> diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl index c076c5e81e..5cf719acb1 100644 --- a/lib/observer/test/observer_SUITE.erl +++ b/lib/observer/test/observer_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2013. All Rights Reserved. +%% Copyright Ericsson AB 2006-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -19,34 +19,35 @@ -module(observer_SUITE). -include_lib("test_server/include/test_server.hrl"). +-include_lib("wx/include/wx.hrl"). +-include_lib("observer/src/observer_tv.hrl"). %% Test server specific exports --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2]). --export([init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0,groups/0]). +-export([init_per_testcase/2, end_per_testcase/2, + init_per_group/2, end_per_group/2, + init_per_suite/1, end_per_suite/1 + ]). %% Test cases --export([app_file/1, appup_file/1]). +-export([app_file/1, appup_file/1, + basic/1, process_win/1, table_win/1 + ]). %% Default timetrap timeout (set in init_per_testcase) -define(default_timeout, ?t:minutes(1)). -init_per_testcase(_Case, Config) -> - Dog = ?t:timetrap(?default_timeout), - [{watchdog, Dog} | Config]. - -end_per_testcase(_Case, Config) -> - Dog = ?config(watchdog, Config), - ?t:timetrap_cancel(Dog), - ok. - suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> - [app_file, appup_file]. +all() -> + [app_file, appup_file, {group, gui}]. -groups() -> - []. +groups() -> + [{gui, [], + [basic + , process_win, table_win + ] + }]. init_per_suite(Config) -> Config. @@ -54,12 +55,40 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -init_per_group(_GroupName, Config) -> - Config. +init_per_testcase(_Case, Config) -> + Dog = ?t:timetrap(?default_timeout), + [{watchdog, Dog} | Config]. -end_per_group(_GroupName, Config) -> - Config. +end_per_testcase(_Case, Config) -> + Dog = ?config(watchdog, Config), + ?t:timetrap_cancel(Dog), + ok. +init_per_group(gui, Config) -> + try + case os:type() of + {unix,darwin} -> + exit("Can not test on MacOSX"); + {unix, _} -> + io:format("DISPLAY ~s~n", [os:getenv("DISPLAY")]), + case ct:get_config(xserver, none) of + none -> ignore; + Server -> os:putenv("DISPLAY", Server) + end; + _ -> ignore + end, + wx:new(), + wx:destroy(), + Config + catch + _:undef -> + {skipped, "No wx compiled for this platform"}; + _:Reason -> + SkipReason = io_lib:format("Start wx failed: ~p", [Reason]), + {skipped, lists:flatten(SkipReason)} + end. +end_per_group(_, _) -> + ok. app_file(suite) -> []; @@ -72,3 +101,178 @@ app_file(Config) when is_list(Config) -> %% Testing .appup file appup_file(Config) when is_list(Config) -> ok = ?t:appup_test(observer). + +-define(DBG(Foo), io:format("~p: ~p~n",[?LINE, catch Foo])). + +basic(suite) -> []; +basic(doc) -> [""]; +basic(Config) when is_list(Config) -> + timer:send_after(100, "foobar"), %% Otherwise the timer sever gets added to procs + ProcsBefore = processes(), + NumProcsBefore = length(ProcsBefore), + + ok = observer:start(), + Notebook = setup_whitebox_testing(), + + io:format("Notebook ~p~n",[Notebook]), + Count = wxNotebook:getPageCount(Notebook), + true = Count >= 6, + 0 = wxNotebook:getSelection(Notebook), + timer:sleep(500), + Check = fun(N, TestMore) -> + TestMore andalso + test_page(wxNotebook:getPageText(Notebook, N), + wxNotebook:getCurrentPage(Notebook)), + timer:sleep(200), + ok = wxNotebook:advanceSelection(Notebook) + end, + %% Just verify that we can toogle trough all pages + [_|_] = [Check(N, false) || N <- lists:seq(1, Count)], + %% Cause it to resize + Frame = get_top_level_parent(Notebook), + {W,H} = wxWindow:getSize(Frame), + wxWindow:setSize(Frame, W+10, H+10), + [_|_] = [Check(N, true) || N <- lists:seq(0, Count-1)], + + ok = observer:stop(), + timer:sleep(2000), %% stop is async + ProcsAfter = processes(), + NumProcsAfter = length(ProcsAfter), + if NumProcsAfter=/=NumProcsBefore -> + ct:log("Before but not after:~n~p~n", + [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]), + ct:log("After but not before:~n~p~n", + [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]), + ct:fail("leaking processes"); + true -> + ok + end, + ok. + +test_page("Load Charts" ++ _, _Window) -> + %% Just let it display some info and hopefully it doesn't crash + timer:sleep(2000), + ok; +test_page("Applications" ++ _, _Window) -> + ok = application:start(mnesia), + timer:sleep(1000), %% Give it time to refresh + Active = get_active(), + FakeEv = #wx{event=#wxCommand{type=command_listbox_selected, cmdString="mnesia"}}, + Active ! FakeEv, + timer:sleep(1000), %% Give it time to refresh + ok = application:stop(mnesia), + timer:sleep(1000), %% Give it time to refresh + ok; + +test_page("Processes" ++ _, _Window) -> + timer:sleep(500), %% Give it time to refresh + Active = get_active(), + ChangeSort = fun(N) -> + FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}}, + Active ! FakeEv, + timer:sleep(200) + end, + [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]], + Focus = #wx{event=#wxList{type=command_list_item_focused, itemIndex=2}}, + Active ! Focus, + Activate = #wx{event=#wxList{type=command_list_item_activated}}, + Active ! Activate, + timer:sleep(1000), %% Give it time to refresh + ok; + +test_page(_Title = "Table" ++ _, _Window) -> + Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)], + Table = lists:nth(3, Tables), + ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]), + + Active = get_active(), + Active ! refresh_interval, + ChangeSort = fun(N) -> + FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}}, + Active ! FakeEv, + timer:sleep(200) + end, + [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]], + timer:sleep(1000), + Focus = #wx{event=#wxList{type=command_list_item_selected, itemIndex=2}}, + Active ! Focus, + Activate = #wx{event=#wxList{type=command_list_item_activated, itemIndex=2}}, + Active ! Activate, + + Info = 407, %% whitebox... + Active ! #wx{id=Info}, + timer:sleep(1000), + ok; + +test_page(Title, Window) -> + io:format("Page ~p: ~p~n", [Title, Window]), + %% Just let it display some info and hopefully it doesn't crash + timer:sleep(1000), + ok. + + +process_win(suite) -> []; +process_win(doc) -> [""]; +process_win(Config) when is_list(Config) -> + ok = observer:start(), + ObserverNB = setup_whitebox_testing(), + Parent = get_top_level_parent(ObserverNB), + Frame = observer_procinfo:start(self(), Parent, self()), + PIPid = wx_object:get_pid(Frame), + PIPid ! {get_debug_info, self()}, + Notebook = receive {procinfo_debug, NB} -> NB end, + Count = wxNotebook:getPageCount(Notebook), + Check = fun(_N) -> + ok = wxNotebook:advanceSelection(Notebook), + timer:sleep(400) + end, + [_|_] = [Check(N) || N <- lists:seq(1, Count)], + PIPid ! #wx{event=#wxClose{type=close_window}}, + observer:stop(), + ok. + +table_win(suite) -> []; +table_win(doc) -> [""]; +table_win(Config) when is_list(Config) -> + Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)], + Table = lists:nth(3, Tables), + ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]), + ok = observer:start(), + Notebook = setup_whitebox_testing(), + Parent = get_top_level_parent(Notebook), + TObj = observer_tv_table:start_link(Parent, [{node,node()}, {type,ets}, {table,#tab{name=foo, id=Table}}]), + %% Modal can not test edit.. + %% TPid = wx_object:get_pid(TObj), + %% TPid ! #wx{event=#wxList{type=command_list_item_activated, itemIndex=12}}, + timer:sleep(3000), + wx_object:get_pid(TObj) ! #wx{event=#wxClose{type=close_window}}, + observer:stop(), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_top_level_parent(Window) -> + Parent = wxWindow:getParent(Window), + case wx:is_null(Parent) of + true -> Window; + false -> get_top_level_parent(Parent) + end. + +setup_whitebox_testing() -> + %% So that if we die observer exists + link(whereis(observer)), + {Env, Notebook, _Active} = get_observer_debug(), + wx:set_env(Env), + Notebook. + +get_active() -> + {_, _, Active} = get_observer_debug(), + Active. + +get_observer_debug() -> + observer ! {get_debug_info, self()}, + receive + {observer_debug, Env, Notebook, Active} -> + {Env, Notebook, Active} + end. diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index f48809a839..c8a6023b4f 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 1.3.1.2 +OBSERVER_VSN = 2.0.3 |