diff options
Diffstat (limited to 'lib/observer/src')
29 files changed, 1160 insertions, 307 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index c120865213..8c6606d0a6 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -51,6 +51,7 @@ MODULES= \ cdv_multi_wx \ cdv_port_cb \ cdv_proc_cb \ + cdv_sched_cb \ cdv_table_wx \ cdv_term_cb \ cdv_timer_cb \ @@ -61,6 +62,7 @@ MODULES= \ etop_txt \ observer \ observer_app_wx \ + observer_alloc_wx \ observer_html_lib \ observer_lib \ observer_perf_wx \ diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl index d5fbceff1e..8b427e92b7 100644 --- a/lib/observer/src/cdv_bin_cb.erl +++ b/lib/observer/src/cdv_bin_cb.erl @@ -17,14 +17,14 @@ %% %CopyrightEnd% -module(cdv_bin_cb). --export([get_details/1, +-export([get_details/2, detail_pages/0]). %% Callbacks for cdv_detail_wx -get_details({Type, {T,Key}}) -> +get_details({Type, {T,Key}}, _) -> [{Key,Term}] = ets:lookup(T,Key), {ok,{"Expanded Binary", {Type, Term}, []}}; -get_details({cdv, Id}) -> +get_details({cdv, Id}, _) -> {ok,Bin} = crashdump_viewer:expand_binary(Id), {ok,{"Expanded Binary", {cvd, Bin}, []}}. diff --git a/lib/observer/src/cdv_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl index dc93507a36..ec0d877d87 100644 --- a/lib/observer/src/cdv_detail_wx.erl +++ b/lib/observer/src/cdv_detail_wx.erl @@ -19,7 +19,7 @@ -behaviour(wx_object). --export([start_link/3]). +-export([start_link/4]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, handle_call/3, handle_info/2]). @@ -38,13 +38,13 @@ -define(ID_NOTEBOOK, 604). %% Detail view -start_link(Id, ParentFrame, Callback) -> - wx_object:start_link(?MODULE, [Id, ParentFrame, Callback, self()], []). +start_link(Id, Data, ParentFrame, Callback) -> + wx_object:start_link(?MODULE, [Id, Data, ParentFrame, Callback, self()], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Id, ParentFrame, Callback, Parent]) -> - case Callback:get_details(Id) of +init([Id, Data, ParentFrame, Callback, Parent]) -> + case Callback:get_details(Id, Data) of {ok,Details} -> init(Id,ParentFrame,Callback,Parent,Details); {yes_no, Info, Fun} -> diff --git a/lib/observer/src/cdv_dist_cb.erl b/lib/observer/src/cdv_dist_cb.erl index f7e6c9aded..f45fb1f524 100644 --- a/lib/observer/src/cdv_dist_cb.erl +++ b/lib/observer/src/cdv_dist_cb.erl @@ -21,7 +21,7 @@ col_spec/0, get_info/1, get_detail_cols/1, - get_details/1, + get_details/2, detail_pages/0, format/1]). @@ -55,10 +55,10 @@ get_info(_) -> {Info,TW}. get_detail_cols(_) -> - {[?COL_CH,?COL_CTRL],true}. + {[{node, ?COL_CH},{port,?COL_CTRL}],true}. %% Callbacks for cdv_detail_wx -get_details(Id) -> +get_details(Id, _) -> case crashdump_viewer:node_info(Id) of {ok,Info,TW} -> Proplist = crashdump_viewer:to_proplist(record_info(fields,nod),Info), diff --git a/lib/observer/src/cdv_ets_cb.erl b/lib/observer/src/cdv_ets_cb.erl index 2a5c170e58..371c7f0b32 100644 --- a/lib/observer/src/cdv_ets_cb.erl +++ b/lib/observer/src/cdv_ets_cb.erl @@ -20,7 +20,10 @@ -export([col_to_elem/1, col_spec/0, get_info/1, - get_detail_cols/1]). + get_details/2, + get_detail_cols/1, + detail_pages/0 + ]). -include_lib("wx/include/wx.hrl"). -include("crashdump_viewer.hrl"). @@ -41,7 +44,7 @@ col_to_elem(?COL_ID) -> #ets_table.id; col_to_elem(?COL_NAME) -> #ets_table.name; col_to_elem(?COL_SLOT) -> #ets_table.slot; col_to_elem(?COL_OWNER) -> #ets_table.pid; -col_to_elem(?COL_TYPE) -> #ets_table.type; +col_to_elem(?COL_TYPE) -> #ets_table.data_type; col_to_elem(?COL_BUCK) -> #ets_table.buckets; col_to_elem(?COL_OBJ) -> #ets_table.size; col_to_elem(?COL_MEM) -> #ets_table.memory. @@ -50,18 +53,68 @@ col_spec() -> [{"Id", ?wxLIST_FORMAT_LEFT, 200}, {"Name", ?wxLIST_FORMAT_LEFT, 200}, {"Slot", ?wxLIST_FORMAT_RIGHT, 50}, - {"Owner", ?wxLIST_FORMAT_CENTRE, 90}, - {"Buckets", ?wxLIST_FORMAT_RIGHT, 50}, - {"Objects", ?wxLIST_FORMAT_RIGHT, 50}, - {"Memory", ?wxLIST_FORMAT_RIGHT, 80}, - {"Type", ?wxLIST_FORMAT_LEFT, 50} + {"Owner", ?wxLIST_FORMAT_CENTRE, 120}, + {"Objects", ?wxLIST_FORMAT_RIGHT, 80}, + {"Memory", ?wxLIST_FORMAT_RIGHT, 80} +% {"Type", ?wxLIST_FORMAT_LEFT, 50} ]. get_info(Owner) -> {ok,Info,TW} = crashdump_viewer:ets_tables(Owner), {Info,TW}. +%% Callbacks for cdv_detail_wx +get_details(_Id, not_found) -> + Info = "The table you are searching for could not be found.", + {info,Info}; +get_details(Id, Data) -> + Proplist = crashdump_viewer:to_proplist(record_info(fields,ets_table),Data), + {ok,{"Table:" ++ Id,Proplist,""}}. + get_detail_cols(all) -> - {[?COL_OWNER],false}; -get_detail_cols(_) -> - {[],false}. + {[{ets, ?COL_ID}, {process, ?COL_OWNER}],true}; +get_detail_cols(_W) -> + {[],true}. + + +%%%%%%%%%%%%%%%%%%%%%%%% + +detail_pages() -> + [{"Table Information", fun init_gen_page/2}]. + +init_gen_page(Parent, Info0) -> + Fields = info_fields(), + Details = proplists:get_value(details, Info0), + Info = if is_map(Details) -> Info0 ++ maps:to_list(Details); + true -> Info0 + end, + cdv_info_wx:start_link(Parent,{Fields,Info,[]}). + +%%% Internal +info_fields() -> + [{"Overview", + [{"Id", id}, + {"Name", name}, + {"Slot", slot}, + {"Owner", owner}, + {"Data Structure", data_type} + ]}, + {"Settings", + [{"Type", type}, + {"Protection", protection}, + {"Compressed", compressed}, + {"Fixed", fixed}, + {"Lock write concurrency", write_c}, + {"Lock read concurrency", read_c} + ]}, + {"Memory Usage", + [{"Buckets", buckets}, + {"Size", size}, + {"Memory", memory}, + {"Min Chain Length", chain_min}, + {"Avg Chain Length", chain_avg}, + {"Max Chain Length", chain_max}, + {"Chain Length Std Dev", chain_stddev}, + {"Chain Length Expected Std Dev", chain_exp_stddev} + ]} + ]. diff --git a/lib/observer/src/cdv_fun_cb.erl b/lib/observer/src/cdv_fun_cb.erl index 689ef0e3bb..067377254a 100644 --- a/lib/observer/src/cdv_fun_cb.erl +++ b/lib/observer/src/cdv_fun_cb.erl @@ -55,4 +55,4 @@ get_info(_) -> {Info,TW}. get_detail_cols(_) -> - {[?COL_MOD],false}. + {[{module, ?COL_MOD}],false}. diff --git a/lib/observer/src/cdv_gen_cb.erl b/lib/observer/src/cdv_gen_cb.erl index 6be717d76d..aa5e7c5182 100644 --- a/lib/observer/src/cdv_gen_cb.erl +++ b/lib/observer/src/cdv_gen_cb.erl @@ -42,4 +42,6 @@ info_fields() -> {"Processes",num_procs}, {"ETS tables",num_ets}, {"Timers",num_timers}, - {"Funs",num_fun}]}]. + {"Funs",num_fun}, + {"Calling Thread", thread} + ]}]. diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl index b79c647f63..6d19589f5d 100644 --- a/lib/observer/src/cdv_html_wx.erl +++ b/lib/observer/src/cdv_html_wx.erl @@ -126,7 +126,7 @@ expand(Id,Callback,#state{expand_wins=Opened0}=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), wx_object:get_pid(EW) ! active, [{Id,EW}|Opened0]; {_,EW} -> diff --git a/lib/observer/src/cdv_mod_cb.erl b/lib/observer/src/cdv_mod_cb.erl index e829ff4fca..8d33f9da9d 100644 --- a/lib/observer/src/cdv_mod_cb.erl +++ b/lib/observer/src/cdv_mod_cb.erl @@ -21,7 +21,7 @@ col_spec/0, get_info/1, get_detail_cols/1, - get_details/1, + get_details/2, detail_pages/0, format/1]). @@ -49,10 +49,10 @@ get_info(_) -> {Info,TW}. get_detail_cols(_) -> - {[?COL_ID],true}. + {[{module, ?COL_ID}],true}. %% Callbacks for cdv_detail_wx -get_details(Id) -> +get_details(Id, _) -> {ok,Info,TW} = crashdump_viewer:loaded_mod_details(Id), Proplist = crashdump_viewer:to_proplist(record_info(fields,loaded_mod),Info), Title = io_lib:format("~s",[Info#loaded_mod.mod]), diff --git a/lib/observer/src/cdv_port_cb.erl b/lib/observer/src/cdv_port_cb.erl index 08488d3e34..409431218b 100644 --- a/lib/observer/src/cdv_port_cb.erl +++ b/lib/observer/src/cdv_port_cb.erl @@ -21,7 +21,7 @@ col_spec/0, get_info/1, get_detail_cols/1, - get_details/1, + get_details/2, detail_pages/0, format/1]). @@ -57,10 +57,10 @@ get_info(_) -> {Info,TW}. get_detail_cols(_) -> - {[?COL_ID,?COL_CONN],true}. + {[{port, ?COL_ID},{process, ?COL_CONN}],true}. %% Callbacks for cdv_detail_wx -get_details(Id) -> +get_details(Id, _Data) -> case crashdump_viewer:port(Id) of {ok,Info,TW} -> Proplist = @@ -70,7 +70,7 @@ get_details(Id) -> Info = "The port you are searching for was residing on " "a remote node. No port information is available. " "Show information about the remote node?", - Fun = fun() -> cdv_virtual_list_wx:start_detail_win(NodeId) end, + Fun = fun() -> cdv_virtual_list_wx:start_detail_win(NodeId, node) end, {yes_no, Info, Fun}; {error,not_found} -> Info = "The port you are searching for could not be found.", diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl index dfc2df9c4c..0af6a9c235 100644 --- a/lib/observer/src/cdv_proc_cb.erl +++ b/lib/observer/src/cdv_proc_cb.erl @@ -21,7 +21,7 @@ col_spec/0, get_info/1, get_detail_cols/1, - get_details/1, + get_details/2, detail_pages/0]). -include_lib("wx/include/wx.hrl"). @@ -57,10 +57,10 @@ get_info(_) -> {Info,TW}. get_detail_cols(_) -> - {[?COL_ID],true}. + {[{process, ?COL_ID}],true}. %% Callbacks for cdv_detail_wx -get_details(Id) -> +get_details(Id, _) -> case crashdump_viewer:proc_details(Id) of {ok,Info,TW} -> %% The following table is used by observer_html_lib @@ -76,7 +76,7 @@ get_details(Id) -> Info = "The process you are searching for was residing on " "a remote node. No process information is available. " "Show information about the remote node?", - Fun = fun() -> cdv_virtual_list_wx:start_detail_win(NodeId) end, + Fun = fun() -> cdv_virtual_list_wx:start_detail_win(NodeId, port) end, {yes_no, Info, Fun}; {error,not_found} -> Info = "The process you are searching for could not be found.", @@ -126,10 +126,13 @@ info_fields() -> {dynamic, current_func}, {"Registered Name", name}, {"Status", state}, + {"Internal State", int_state}, {"Started", start_time}, {"Parent", {click,parent}}, {"Message Queue Len",msg_q_len}, + {"Run queue", run_queue}, {"Reductions", reds}, + {"Program counter", prog_count}, {"Continuation pointer",cp}, {"Arity",arity}]}, diff --git a/lib/observer/src/cdv_sched_cb.erl b/lib/observer/src/cdv_sched_cb.erl new file mode 100644 index 0000000000..6ef4886c5e --- /dev/null +++ b/lib/observer/src/cdv_sched_cb.erl @@ -0,0 +1,117 @@ +%% +%% %CopyrightBegin% +%% +%% 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(cdv_sched_cb). + +-export([col_to_elem/1, + col_spec/0, + get_info/1, + get_details/2, + get_detail_cols/1, + detail_pages/0 + ]). + +-include_lib("wx/include/wx.hrl"). +-include("crashdump_viewer.hrl"). + +%% Columns +-define(COL_ID, 0). +-define(COL_PROC, ?COL_ID+1). +-define(COL_PORT, ?COL_PROC+1). +-define(COL_RQL, ?COL_PORT+1). +-define(COL_PQL, ?COL_RQL+1). + +%% Callbacks for cdv_virtual_list_wx +col_to_elem(id) -> col_to_elem(?COL_ID); +col_to_elem(?COL_ID) -> #sched.name; +col_to_elem(?COL_PROC) -> #sched.process; +col_to_elem(?COL_PORT) -> #sched.port; +col_to_elem(?COL_RQL) -> #sched.run_q; +col_to_elem(?COL_PQL) -> #sched.port_q. + +col_spec() -> + [{"Id", ?wxLIST_FORMAT_RIGHT, 50}, + {"Current Process", ?wxLIST_FORMAT_CENTER, 130}, + {"Current Port", ?wxLIST_FORMAT_CENTER, 130}, + {"Run Queue Length", ?wxLIST_FORMAT_RIGHT, 180}, + {"Port Queue Length", ?wxLIST_FORMAT_RIGHT, 180}]. + +get_info(_) -> + {ok,Info,TW} = crashdump_viewer:schedulers(), + {Info,TW}. + +get_details(_Id, not_found) -> + Info = "The scheduler you are searching for could not be found.", + {info,Info}; +get_details(Id, Data) -> + Proplist = crashdump_viewer:to_proplist(record_info(fields,sched),Data), + {ok,{"Scheduler: " ++ Id,Proplist,""}}. + +get_detail_cols(all) -> + {[{sched, ?COL_ID}, {process, ?COL_PROC}, {process, ?COL_PORT}],true}; +get_detail_cols(_) -> + {[],false}. + +%%%%%%%%%%%%%%%%%%%%%%%% + +detail_pages() -> + [{"Scheduler Information", fun init_gen_page/2}]. + +init_gen_page(Parent, Info0) -> + Fields = info_fields(), + Details = proplists:get_value(details, Info0), + Info = if is_map(Details) -> Info0 ++ maps:to_list(Details); + true -> Info0 + end, + cdv_info_wx:start_link(Parent,{Fields,Info,[]}). + +%%% Internal +info_fields() -> + [{"Scheduler Overview", + [{"Id", id}, + {"Current Process",process}, + {"Current Port", port}, + {"Sleep Info Flags", sleep_info}, + {"Sleep Aux Work", sleep_aux} + ]}, + {"Run Queues", + [{"Flags", runq_flags}, + {"Priority Max Length", runq_max}, + {"Priority High Length", runq_high}, + {"Priority Normal Length", runq_norm}, + {"Priority Low Length", runq_low}, + {"Port Length", port_q} + ]}, + {"Current Process", + [{"State", currp_state}, + {"Internal State", currp_int_state}, + {"Program Counter", currp_prg_cnt}, + {"CP", currp_cp}, + {"Stack", {currp_stack, 0}}, + {" ", {currp_stack, 1}}, + {" ", {currp_stack, 2}}, + {" ", {currp_stack, 3}}, + {" ", {currp_stack, 4}}, + {" ", {currp_stack, 5}}, + {" ", {currp_stack, 6}}, + {" ", {currp_stack, 7}}, + {" ", {currp_stack, 8}}, + {" ", {currp_stack, 9}}, + {" ", {currp_stack, 10}}, + {" ", {currp_stack, 11}} + ]} + ]. diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl index 4451045012..6db6d54514 100644 --- a/lib/observer/src/cdv_term_cb.erl +++ b/lib/observer/src/cdv_term_cb.erl @@ -17,11 +17,11 @@ %% %CopyrightEnd% -module(cdv_term_cb). --export([get_details/1, +-export([get_details/2, detail_pages/0]). %% Callbacks for cdv_detail_wx -get_details({Type, {T,Key}}) -> +get_details({Type, {T,Key}}, _) -> [{Key,Term}] = ets:lookup(T,Key), {ok,{"Expanded Term", {Type,[Term, T]}, []}}. diff --git a/lib/observer/src/cdv_timer_cb.erl b/lib/observer/src/cdv_timer_cb.erl index d44592cf18..b4564941ea 100644 --- a/lib/observer/src/cdv_timer_cb.erl +++ b/lib/observer/src/cdv_timer_cb.erl @@ -49,6 +49,6 @@ get_info(Owner) -> {Info,TW}. get_detail_cols(all) -> - {[?COL_OWNER],false}; + {[{process, ?COL_OWNER}],false}; get_detail_cols(_) -> {[],false}. diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl index bfe115a42e..c0bc7018cb 100644 --- a/lib/observer/src/cdv_virtual_list_wx.erl +++ b/lib/observer/src/cdv_virtual_list_wx.erl @@ -19,7 +19,8 @@ -behaviour(wx_object). --export([start_link/2, start_link/3, start_detail_win/1]). +-export([start_link/2, start_link/3, + start_detail_win/1, start_detail_win/2]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -65,22 +66,31 @@ start_link(ParentWin, Callback, Owner) -> wx_object:start_link(?MODULE, [ParentWin, Callback, Owner], []). start_detail_win(Id) -> - Callback = - case Id of - "<"++_ -> - cdv_proc_cb; - "#Port"++_ -> - cdv_port_cb; - _ -> - case catch list_to_integer(Id) of - NodeId when is_integer(NodeId) -> - cdv_dist_cb; - _ -> - cdv_mod_cb - end - end, - start_detail_win(Callback,Id). -start_detail_win(Callback,Id) -> + case Id of + "<"++_ -> + start_detail_win(Id, process); + "#Port"++_ -> + start_detail_win(Id, port); + _ -> + io:format("cdv: unknown identifier: ~p~n",[Id]), + ignore + end. + +start_detail_win(Id, process) -> + start_detail_win_2(cdv_proc_cb, Id); +start_detail_win(Id, port) -> + start_detail_win_2(cdv_port_cb, Id); +start_detail_win(Id, node) -> + start_detail_win_2(cdv_dist_cb, Id); +start_detail_win(Id, module) -> + start_detail_win_2(cdv_mod_cb, Id); +start_detail_win(Id, ets) -> + start_detail_win_2(cdv_ets_cb, Id); +start_detail_win(Id, sched) -> + start_detail_win_2(cdv_sched_cb, Id). + + +start_detail_win_2(Callback,Id) -> wx_object:cast(Callback,{start_detail_win,Id}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -158,15 +168,14 @@ create_list_box(Panel, Holder, Callback, Owner) -> do_start_detail_win(undefined, State) -> State; do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened, - callback=Callback}=State) -> + holder=Holder,callback=Callback}=State) -> NewOpened = case lists:keyfind(Id, 1, Opened) of false -> - case cdv_detail_wx:start_link(Id, Panel, Callback) of - {error, _} -> - Opened; - IW -> - [{Id, IW} | Opened] + Data = call(Holder, {get_data, self(), Id}), + case cdv_detail_wx:start_link(Id, Data, Panel, Callback) of + {error, _} -> Opened; + IW -> [{Id, IW} | Opened] end; {_, IW} -> wxFrame:raise(IW), @@ -247,8 +256,8 @@ handle_event(#wx{id=MenuId, event=#wxCommand{type = command_menu_selected}}, #state{menu_items=MenuItems} = State) -> case lists:keyfind(MenuId,1,MenuItems) of - {MenuId,Id} -> - start_detail_win(Id); + {MenuId,Type,Id} -> + start_detail_win(Id, Type); false -> ok end, @@ -265,7 +274,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click, Menu = wxMenu:new(), MenuItems = lists:flatmap( - fun(Col) -> + fun({Type, Col}) -> MenuId = ?ID_DETAILS + Col, ColText = call(Holder, {get_row, self(), Row, Col}), case ColText of @@ -273,14 +282,15 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click, _ -> What = case catch list_to_integer(ColText) of - NodeId when is_integer(NodeId) -> + NodeId when is_integer(NodeId), + Type =:= node -> "node " ++ ColText; _ -> ColText end, Text = "Properties for " ++ What, wxMenu:append(Menu, MenuId, Text), - [{MenuId,ColText}] + [{MenuId,Type,ColText}] end end, MenuCols), @@ -300,9 +310,14 @@ handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Row}}, - #state{holder=Holder} = State) -> - Id = call(Holder, {get_row, self(), Row, id}), - start_detail_win(Id), + #state{holder=Holder, menu_cols=MenuCols} = State) -> + case MenuCols of + [{Type, _}|_] -> + Id = call(Holder, {get_row, self(), Row, id}), + start_detail_win(Id, Type); + _ -> + ignore + end, {noreply, State}; handle_event(Event, State) -> @@ -346,7 +361,7 @@ init_table_holder(Parent, Attrs, Callback, InfoList0) -> attrs=Attrs, callback=Callback}). -table_holder(#holder{callback=Callback, attrs=Attrs}=S0) -> +table_holder(#holder{callback=Callback, attrs=Attrs, info=Info}=S0) -> receive _M={get_row, From, Row, Col} -> %% erlang:display(_M), @@ -360,6 +375,9 @@ table_holder(#holder{callback=Callback, attrs=Attrs}=S0) -> %% erlang:display(_M), State = change_sort(Callback:col_to_elem(Col), S0), table_holder(State); + _M={get_data, From, Id} -> + search_id(From, Id, Callback, Info), + table_holder(S0); stop -> ok; What -> @@ -367,6 +385,21 @@ table_holder(#holder{callback=Callback, attrs=Attrs}=S0) -> table_holder(S0) end. +search_id(From, Id, Callback, Info) -> + Find = fun(_, RowInfo, _) -> + search_id(Callback, RowInfo, Id) + end, + Res = try array:foldl(Find, not_found, Info) + catch Data -> Data end, + From ! {self(), Res}, + ok. + +search_id(Callback, RowInfo, Id) -> + case observer_lib:to_str(get_cell_data(Callback, id, RowInfo)) of + Id -> throw(RowInfo); + _Str -> not_found + end. + change_sort(Col, S0=#holder{parent=Parent, info=Info0, sort=Sort0}) -> NRows = array:size(Info0), InfoList0 = array:to_list(Info0), diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl index 26df60b0a6..ec0c652a27 100644 --- a/lib/observer/src/cdv_wx.erl +++ b/lib/observer/src/cdv_wx.erl @@ -44,6 +44,7 @@ -define(PORT_STR, "Ports"). -define(ETS_STR, "ETS Tables"). -define(TIMER_STR, "Timers"). +-define(SCHEDULER_STR, "Schedulers"). -define(FUN_STR, "Funs"). -define(ATOM_STR, "Atoms"). -define(DIST_STR, "Nodes"). @@ -66,6 +67,7 @@ port_panel, ets_panel, timer_panel, + sched_panel, fun_panel, atom_panel, dist_panel, @@ -171,6 +173,9 @@ setup(#state{frame=Frame, notebook=Notebook}=State) -> %% Timer Panel TimerPanel = add_page(Notebook, ?TIMER_STR, cdv_virtual_list_wx,cdv_timer_cb), + %% Scheduler Panel + SchedPanel = add_page(Notebook, ?SCHEDULER_STR, cdv_virtual_list_wx, cdv_sched_cb), + %% Fun Panel FunPanel = add_page(Notebook, ?FUN_STR, cdv_virtual_list_wx, cdv_fun_cb), @@ -202,6 +207,7 @@ setup(#state{frame=Frame, notebook=Notebook}=State) -> port_panel = PortPanel, ets_panel = EtsPanel, timer_panel = TimerPanel, + sched_panel = SchedPanel, fun_panel = FunPanel, atom_panel = AtomPanel, dist_panel = DistPanel, @@ -335,7 +341,8 @@ check_page_title(Notebook) -> get_active_pid(#state{notebook=Notebook, gen_panel=Gen, pro_panel=Pro, port_panel=Ports, ets_panel=Ets, timer_panel=Timers, fun_panel=Funs, atom_panel=Atoms, dist_panel=Dist, - mod_panel=Mods, mem_panel=Mem, int_panel=Int + mod_panel=Mods, mem_panel=Mem, int_panel=Int, + sched_panel=Sched }) -> Panel = case check_page_title(Notebook) of ?GEN_STR -> Gen; @@ -343,6 +350,7 @@ get_active_pid(#state{notebook=Notebook, gen_panel=Gen, pro_panel=Pro, ?PORT_STR -> Ports; ?ETS_STR -> Ets; ?TIMER_STR -> Timers; + ?SCHEDULER_STR -> Sched; ?FUN_STR -> Funs; ?ATOM_STR -> Atoms; ?DIST_STR -> Dist; diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index 99329b94e2..007fc74279 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -63,6 +63,7 @@ allocator_info/0, hash_tables/0, index_tables/0, + schedulers/0, expand_binary/1]). %% Library function @@ -114,6 +115,7 @@ -define(proc_heap,proc_heap). -define(proc_messages,proc_messages). -define(proc_stack,proc_stack). +-define(scheduler,scheduler). -define(timer,timer). -define(visible_node,visible_node). @@ -267,6 +269,8 @@ hash_tables() -> call(hash_tables). index_tables() -> call(index_tables). +schedulers() -> + call(schedulers). %%%----------------------------------------------------------------- %%% Called when a link to a process (Pid) is clicked. @@ -320,6 +324,8 @@ handle_call(general_info,_From,State=#state{file=File}) -> "Some information might be missing."]; false -> [] end, + ets:insert(cdv_reg_proc_table, + {cdv_dump_node_name,GenInfo#general_info.node_name}), {reply,{ok,GenInfo,TW},State#state{wordsize=WS, num_atoms=NumAtoms}}; handle_call({expand_binary,{Offset,Size,Pos}},_From,State=#state{file=File}) -> Fd = open(File), @@ -429,7 +435,11 @@ handle_call(hash_tables,_From,State=#state{file=File}) -> handle_call(index_tables,_From,State=#state{file=File}) -> IndexTables=index_tables(File), TW = truncated_warning([?hash_table,?index_table]), - {reply,{ok,IndexTables,TW},State}. + {reply,{ok,IndexTables,TW},State}; +handle_call(schedulers,_From,State=#state{file=File}) -> + Schedulers=schedulers(File), + TW = truncated_warning([?scheduler]), + {reply,{ok,Schedulers,TW},State}. @@ -675,9 +685,11 @@ skip(Fd,<<>>) -> val(Fd) -> + val(Fd, "-1"). +val(Fd, NoExist) -> case get_rest_of_line(Fd) of - {eof,[]} -> "-1"; - [] -> "-1"; + {eof,[]} -> NoExist; + [] -> NoExist; {eof,Val} -> Val; Val -> Val end. @@ -926,7 +938,7 @@ general_info(File) -> N; [] -> case lookup_index(?no_distribution) of - [_] -> "nonode@nohost"; + [_] -> "'nonode@nohost'"; [] -> "unknown" end end, @@ -965,6 +977,8 @@ get_general_info(Fd,GenInfo) -> get_general_info(Fd,GenInfo#general_info{taints=Val}); "Atoms" -> get_general_info(Fd,GenInfo#general_info{num_atoms=val(Fd)}); + "Calling Thread" -> + get_general_info(Fd,GenInfo#general_info{thread=val(Fd)}); "=" ++ _next_tag -> GenInfo; Other -> @@ -1131,6 +1145,10 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) -> "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); + "Internal State" -> + get_procinfo(Fd,Fun,Proc#proc{int_state=val(Fd)},WS); "=" ++ _next_tag -> Proc; Other -> @@ -1165,6 +1183,19 @@ parse_pid(Str) -> {Pid,Rest} = parse_link(Str,[]), {{Pid,Pid},Rest}. +parse_monitor("{"++Str) -> + %% Named process + {Name,Node,Rest1} = parse_name_node(Str,[]), + Pid = get_pid_from_name(Name,Node), + case parse_link(string:strip(Rest1,left,$,),[]) of + {Ref,"}"++Rest2} -> + %% Bug in break.c - prints an extra "}" for remote + %% nodes... thus the strip + {{Pid,"{"++Name++","++Node++"} ("++Ref++")"}, + string:strip(Rest2,left,$})}; + {Ref,[]} -> + {{Pid,"{"++Name++","++Node++"} ("++Ref++")"},[]} + end; parse_monitor(Str) -> case parse_link(Str,[]) of {Pid,","++Rest1} -> @@ -1186,23 +1217,58 @@ parse_link([],Acc) -> %% truncated {lists:reverse(Acc),[]}. +parse_name_node(","++Rest,Name) -> + parse_name_node(Rest,Name,[]); +parse_name_node([H|T],Name) -> + parse_name_node(T,[H|Name]); +parse_name_node([],Name) -> + %% truncated + {lists:reverse(Name),[],[]}. + +parse_name_node("}"++Rest,Name,Node) -> + {lists:reverse(Name),lists:reverse(Node),Rest}; +parse_name_node([H|T],Name,Node) -> + parse_name_node(T,Name,[H|Node]); +parse_name_node([],Name,Node) -> + %% truncated + {lists:reverse(Name),lists:reverse(Node),[]}. + +get_pid_from_name(Name,Node) -> + case ets:lookup(cdv_reg_proc_table,cdv_dump_node_name) of + [{_,Node}] -> + case ets:lookup(cdv_reg_proc_table,Name) of + [{_,Pid}] when is_pid(Pid) -> + pid_to_list(Pid); + _ -> + "<unkonwn_pid>" + end; + _ -> + "<unknown_pid_other_node>" + end. + maybe_other_node(Id) -> Channel = case split($.,Id) of {"<" ++ N, _Rest} -> N; {"#Port<" ++ N, _Rest} -> - N + N; + {_, []} -> + not_found end, + maybe_other_node2(Channel). + +maybe_other_node2(not_found) -> not_found; +maybe_other_node2(Channel) -> Ms = ets:fun2ms( - fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel -> + fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel -> {"Visible Node",Start}; ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel -> {"Hidden Node",Start}; - ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel -> + ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel -> {"Not Connected Node",Start} end), - + case ets:select(cdv_dump_index_table,Ms) of [] -> not_found; @@ -1457,7 +1523,7 @@ get_ets_tables(File,Pid,WS) -> end, lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets"). -get_etsinfo(Fd,EtsTable,WS) -> +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); @@ -1467,7 +1533,7 @@ get_etsinfo(Fd,EtsTable,WS) -> get_etsinfo(Fd,EtsTable#ets_table{name=val(Fd)},WS); "Ordered set (AVL tree), Elements" -> skip_rest_of_line(Fd), - get_etsinfo(Fd,EtsTable#ets_table{type="tree",buckets="-"},WS); + 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. @@ -1482,9 +1548,42 @@ get_etsinfo(Fd,EtsTable,WS) -> -1 -> -1; % probably truncated _ -> Words * WS end, - get_etsinfo(Fd,EtsTable#ets_table{memory=Bytes},WS); + get_etsinfo(Fd,EtsTable#ets_table{memory={bytes,Bytes}},WS); "=" ++ _next_tag -> EtsTable; + "Chain Length Min" -> + Val = val(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, + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_avg=>Val}},WS); + "Chain Length Max" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_max=>Val}},WS); + "Chain Length Std Dev" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_stddev=>Val}},WS); + "Chain Length Expected Std Dev" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_exp_stddev=>Val}},WS); + "Fixed" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{fixed=>Val}},WS); + "Type" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{data_type=>Val}},WS); + "Protection" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{protection=>Val}},WS); + "Compressed" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{compressed=>Val}},WS); + "Write Concurrency" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{write_c=>Val}},WS); + "Read Concurrency" -> + Val = val(Fd), + get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{read_c=>Val}},WS); Other -> unexpected(Fd,Other,"ETS info"), EtsTable @@ -2224,6 +2323,89 @@ get_indextableinfo1(Fd,IndexTable) -> IndexTable end. + +%%----------------------------------------------------------------- +%% Page with scheduler table information +schedulers(File) -> + case lookup_index(?scheduler) of + [] -> + []; + Schedulers -> + Fd = open(File), + R = lists:map(fun({Name,Start}) -> + get_schedulerinfo(Fd,Name,Start) + end, + Schedulers), + close(Fd), + R + end. + +get_schedulerinfo(Fd,Name,Start) -> + pos_bof(Fd,Start), + get_schedulerinfo1(Fd,#sched{name=Name}). + +get_schedulerinfo1(Fd,Sched=#sched{details=Ds}) -> + case line_head(Fd) of + "Current Process" -> + get_schedulerinfo1(Fd,Sched#sched{process=val(Fd, "None")}); + "Current Port" -> + get_schedulerinfo1(Fd,Sched#sched{port=val(Fd, "None")}); + "Run Queue Max Length" -> + RQMax = list_to_integer(val(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)), + 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)), + 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)), + 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)), + get_schedulerinfo1(Fd,Sched#sched{port_q=RQ}); + + "Scheduler Sleep Info Flags" -> + get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_info=>val(Fd, "None")}}); + "Scheduler Sleep Info Aux Work" -> + get_schedulerinfo1(Fd,Sched#sched{details=Ds#{sleep_aux=>val(Fd, "None")}}); + + "Run Queue Flags" -> + get_schedulerinfo1(Fd,Sched#sched{details=Ds#{runq_flags=>val(Fd, "None")}}); + + "Current Process State" -> + get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_state=>val(Fd)}}); + "Current Process Internal State" -> + get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_int_state=>val(Fd)}}); + "Current Process Program counter" -> + get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_prg_cnt=>val(Fd)}}); + "Current Process CP" -> + get_schedulerinfo1(Fd,Sched#sched{details=Ds#{currp_cp=>val(Fd)}}); + "Current Process Limited Stack Trace" -> + %% If there shall be last in scheduler information block + Sched#sched{details=get_limited_stack(Fd, 0, Ds)}; + "=" ++ _next_tag -> + Sched; + Other -> + unexpected(Fd,Other,"scheduler information"), + Sched + end. + +get_limited_stack(Fd, N, Ds) -> + case val(Fd) of + Addr = "0x" ++ _ -> + get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Addr}); + "=" ++ _next_tag -> + Ds; + Line -> + get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Line}) + end. + %%%----------------------------------------------------------------- %%% Parse memory in crashdump version 0.1 and newer %%% @@ -2526,6 +2708,7 @@ tag_to_atom("proc_dictionary") -> ?proc_dictionary; tag_to_atom("proc_heap") -> ?proc_heap; tag_to_atom("proc_messages") -> ?proc_messages; tag_to_atom("proc_stack") -> ?proc_stack; +tag_to_atom("scheduler") -> ?scheduler; tag_to_atom("timer") -> ?timer; tag_to_atom("visible_node") -> ?visible_node; tag_to_atom(UnknownTag) -> diff --git a/lib/observer/src/crashdump_viewer.hrl b/lib/observer/src/crashdump_viewer.hrl index 0e2eba6dee..9515e74114 100644 --- a/lib/observer/src/crashdump_viewer.hrl +++ b/lib/observer/src/crashdump_viewer.hrl @@ -36,7 +36,9 @@ num_fun, mem_tot, mem_max, - instr_info}). + instr_info, + thread + }). -record(proc, %% Initial data according to the follwoing: @@ -85,7 +87,10 @@ old_heap_top, old_heap_end, memory, - stack_dump}). + stack_dump, + run_queue=?unknown, + int_state + }). -record(port, {id, @@ -96,15 +101,28 @@ monitors, controls}). +-record(sched, + {name, + process, + port, + run_q=0, + port_q=0, + details=#{} + }). + + + -record(ets_table, {pid, slot, id, name, - type="hash", - buckets, + data_type="hash", + buckets="-", size, - memory}). + memory, + details= #{} + }). -record(timer, {pid, diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src index 97a54cd6f9..c12353f9e1 100644 --- a/lib/observer/src/observer.app.src +++ b/lib/observer/src/observer.app.src @@ -37,6 +37,7 @@ cdv_proc_cb, cdv_table_wx, cdv_term_cb, + cdv_sched_cb, cdv_timer_cb, cdv_virtual_list_wx, cdv_wx, @@ -44,6 +45,7 @@ etop_tr, etop_txt, observer, + observer_alloc_wx, observer_app_wx, observer_html_lib, observer_lib, @@ -63,6 +65,6 @@ {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"]}]}. + "erts-7.0"]}]}. diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl new file mode 100644 index 0000000000..0c4bc9ee4b --- /dev/null +++ b/lib/observer/src/observer_alloc_wx.erl @@ -0,0 +1,256 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(observer_alloc_wx). + +-export([start_link/2]). + +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_sync_event/3, handle_cast/2]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-record(state, + { + offset = 0.0, + active = false, + parent, + windows, + data = {0, queue:new()}, + panel, + paint, + appmon, + async + }). + +-define(ALLOC_W, 1). +-define(UTIL_W, 2). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +init([Notebook, Parent]) -> + try + Panel = wxPanel:new(Notebook), + Main = wxBoxSizer:new(?wxVERTICAL), + Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxCLIP_CHILDREN, + Carrier = wxPanel:new(Panel, [{winid, ?ALLOC_W}, {style,Style}]), + Utilz = wxPanel:new(Panel, [{winid, ?UTIL_W}, {style,Style}]), + BorderFlags = ?wxLEFT bor ?wxRIGHT, + wxSizer:add(Main, Carrier, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP}, + {proportion, 1}, {border, 5}]), + + wxSizer:add(Main, Utilz, [{flag, ?wxEXPAND bor BorderFlags}, + {proportion, 1}, {border, 5}]), + + MemWin = {MemPanel,_} = create_mem_info(Panel), + wxSizer:add(Main, MemPanel, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM}, + {proportion, 1}, {border, 5}]), + wxWindow:setSizer(Panel, Main), + + PaintInfo = observer_perf_wx:setup_graph_drawing([Carrier, Utilz]), + {Panel, #state{parent=Parent, + panel =Panel, + windows = {Carrier, Utilz, MemWin}, + paint=PaintInfo} + } + catch _:Err -> + io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]), + {stop, Err} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, + State = #state{}) -> + {noreply, State}; + +handle_event(Event, _State) -> + error({unhandled_event, Event}). + +%%%%%%%%%% +handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_, + #state{active=Active, offset=Offset, paint=Paint, + windows=Windows, data=Data}) -> + %% Sigh workaround bug on MacOSX (Id in paint event is always 0) + Id = if Panel =:= element(?ALLOC_W, Windows) -> alloc; + Panel =:= element(?UTIL_W, Windows) -> utilz + end, + observer_perf_wx:refresh_panel(Panel, Id, Offset, Data, Active, Paint), + ok. +%%%%%%%%%% +handle_call(Event, From, _State) -> + error({unhandled_call, Event, From}). + +handle_cast(Event, _State) -> + error({unhandled_cast, Event}). +%%%%%%%%%% + +handle_info({Key, {promise_reply, {badrpc, _}}}, #state{async=Key} = State) -> + {noreply, State#state{active=false, appmon=undefined}}; + +handle_info({Key, {promise_reply, SysInfo}}, #state{async=Key, data=Data} = State) -> + Info = alloc_info(SysInfo), + update_alloc(State, Info), + {noreply, State#state{offset=0.0, data = add_data(Info, Data), async=undefined}}; + +handle_info({refresh, Seq, Freq, Node}, #state{panel=Panel, appmon=Node, async=Key} = State) -> + wxWindow:refresh(Panel), + Next = Seq+1, + if + Next > Freq, Key =:= undefined -> + erlang:send_after(trunc(1000 / Freq), self(), {refresh, 1, Freq, Node}), + Req = rpc:async_call(Node, observer_backend, sys_info, []), + {noreply, State#state{offset=Seq/Freq, async=Req}}; + true -> + erlang:send_after(trunc(1000 / Freq), self(), {refresh, Next, Freq, Node}), + {noreply, State#state{offset=Seq/Freq}} + end; +handle_info({refresh, _Seq, _Freq, _Node}, State) -> + {noreply, State}; + +handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Old}) -> + create_menus(Parent, []), + try + Node = Old, + wxWindow:refresh(Panel), + {noreply, State#state{active=true}} + catch _:_ -> + SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []), + Info = alloc_info(SysInfo), + Freq = 6, + erlang:send_after(trunc(1000 / Freq), self(), {refresh, 1, Freq, Node}), + wxWindow:refresh(Panel), + {noreply, State#state{active=true, appmon=Node, offset=0.0, + data = add_data(Info, {0, queue:new()})}} + end; + +handle_info(not_active, State = #state{appmon=_Pid}) -> + {noreply, State#state{active=false}}; + +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]), + {noreply, State}. + +terminate(_Event, #state{}) -> + ok. +code_change(_, _, State) -> + State. + +%%%%%%%%%% + +add_data(Stats, {N, Q}) when N > 60 -> + {N, queue:drop(queue:in(Stats, Q))}; +add_data(Stats, {N, Q}) -> + {N+1, queue:in(Stats, Q)}. + +update_alloc(#state{windows={_, _, {_, Grid}}}, Fields) -> + Max = wxListCtrl:getItemCount(Grid), + Update = fun({Name, BS, CS}, Row) -> + (Row >= Max) andalso wxListCtrl:insertItem(Grid, Row, ""), + wxListCtrl:setItem(Grid, Row, 0, observer_lib:to_str(Name)), + wxListCtrl:setItem(Grid, Row, 1, observer_lib:to_str(BS div 1024)), + wxListCtrl:setItem(Grid, Row, 2, observer_lib:to_str(CS div 1024)), + Row + 1 + end, + lists:foldl(Update, 0, Fields), + Fields. + +alloc_info(SysInfo) -> + AllocInfo = proplists:get_value(alloc_info, SysInfo, []), + alloc_info(AllocInfo, [], 0, 0, true). + +alloc_info([{Type,Instances}|Allocators],TypeAcc,TotalBS,TotalCS,IncludeTotal) -> + {BS,CS,NewTotalBS,NewTotalCS,NewIncludeTotal} = + sum_alloc_instances(Instances,0,0,TotalBS,TotalCS), + alloc_info(Allocators,[{Type,BS,CS}|TypeAcc],NewTotalBS,NewTotalCS, + IncludeTotal andalso NewIncludeTotal); +alloc_info([],TypeAcc,TotalBS,TotalCS,IncludeTotal) -> + Types = [X || X={_,BS,CS} <- TypeAcc, (BS>0 orelse CS>0)], + case IncludeTotal of + true -> + [{total,TotalBS,TotalCS} | lists:reverse(Types)]; + false -> + lists:reverse(Types) + end. + +sum_alloc_instances(false,BS,CS,TotalBS,TotalCS) -> + {BS,CS,TotalBS,TotalCS,false}; +sum_alloc_instances([{_,_,Data}|Instances],BS,CS,TotalBS,TotalCS) -> + {NewBS,NewCS,NewTotalBS,NewTotalCS} = + sum_alloc_one_instance(Data,BS,CS,TotalBS,TotalCS), + sum_alloc_instances(Instances,NewBS,NewCS,NewTotalBS,NewTotalCS); +sum_alloc_instances([],BS,CS,TotalBS,TotalCS) -> + {BS,CS,TotalBS,TotalCS,true}. + +sum_alloc_one_instance([{sbmbcs,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}| + Rest],OldBS,OldCS,TotalBS,TotalCS) -> + sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS,TotalCS); +sum_alloc_one_instance([{_,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}| + Rest],OldBS,OldCS,TotalBS,TotalCS) -> + sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS); +sum_alloc_one_instance([{_,[{blocks_size,BS},{carriers_size,CS}]}| + Rest],OldBS,OldCS,TotalBS,TotalCS) -> + sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS); +sum_alloc_one_instance([_|Rest],BS,CS,TotalBS,TotalCS) -> + sum_alloc_one_instance(Rest,BS,CS,TotalBS,TotalCS); +sum_alloc_one_instance([],BS,CS,TotalBS,TotalCS) -> + {BS,CS,TotalBS,TotalCS}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_mem_info(Parent) -> + Panel = wxPanel:new(Parent), + wxWindow:setBackgroundColour(Panel, {255,255,255}), + Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES bor ?wxLC_VRULES, + Grid = wxListCtrl:new(Panel, [{style, Style}]), + Li = wxListItem:new(), + AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(Grid, Col, Li), + wxListCtrl:setColumnWidth(Grid, Col, DefSize), + Col + 1 + end, + ListItems = [{"Allocator Type", ?wxLIST_FORMAT_LEFT, 200}, + {"Block size (kB)", ?wxLIST_FORMAT_RIGHT, 150}, + {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}], + lists:foldl(AddListEntry, 0, ListItems), + wxListItem:destroy(Li), + + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}, + {border, 5}, {proportion, 1}]), + wxWindow:setSizerAndFit(Panel, Sizer), + {Panel, Grid}. + + +create_menus(Parent, _) -> + MenuEntries = + [{"File", + [ + ]} + ], + observer_wx:create_menus(Parent, MenuEntries). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl index c279218707..df0bc05312 100644 --- a/lib/observer/src/observer_html_lib.erl +++ b/lib/observer/src/observer_html_lib.erl @@ -60,7 +60,8 @@ expandable_term_body(Heading,[],_Tab) -> "StackDump" -> "No stack dump was found"; "Dictionary" -> "No dictionary was found"; "ProcState" -> "Information could not be retrieved," - " system messages may not be handled by this process." + " system messages may not be handled by this process."; + "SaslLog" -> "No log entry was found" end]; expandable_term_body(Heading,Expanded,Tab) -> Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", @@ -102,7 +103,10 @@ expandable_term_body(Heading,Expanded,Tab) -> element(1, lists:mapfoldl(fun(Entry, Even) -> {proc_state(Tab, Entry,Even), not Even} - end, true, Expanded))]); + end, true, Expanded))]); + "SaslLog" -> + table(Attr, + [tr("BGCOLOR=white",[td("ALIGN=left", pre(href_proc_port(Expanded)))])]) ; _ -> table(Attr, [tr( @@ -151,7 +155,7 @@ all_or_expand(_Tab,Term,Str,false) href_proc_port(lists:flatten(Str)); all_or_expand(Tab,Term,Preview,true) when not is_binary(Term) -> - Key = {Key1,Key2,Key3} = now(), + Key = {Key1,Key2,Key3} = {erlang:unique_integer([positive]),1,2}, ets:insert(Tab,{Key,Term}), [href_proc_port(lists:flatten(Preview), false), $\n, href("TARGET=\"expanded\"", diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 9592ab5977..40a3eb8831 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -173,12 +173,17 @@ fill_info([{Str,SubStructure}|Rest], Data) when is_list(SubStructure) -> [{Str, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; fill_info([{Str,Attrib,SubStructure}|Rest], Data) -> [{Str, Attrib, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; +fill_info([{Str, Key = {K,N}}|Rest], Data) when is_atom(K), is_integer(N) -> + case get_value(Key, Data) of + undefined -> [undefined | fill_info(Rest, Data)]; + Value -> [{Str, Value} | fill_info(Rest, Data)] + end; fill_info([], _) -> []. -get_value(Key, Data) when is_atom(Key) -> - proplists:get_value(Key,Data); get_value(Fun, Data) when is_function(Fun) -> - Fun(Data). + Fun(Data); +get_value(Key, Data) -> + proplists:get_value(Key,Data). update_info([Fields|Fs], [{_Header, SubStructure}| Rest]) -> update_info2(Fields, SubStructure), @@ -269,6 +274,8 @@ to_str(Pid) when is_pid(Pid) -> pid_to_list(Pid); 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(Term) -> io_lib:format("~w", [Term]). diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl index 8173349ed7..4df9218087 100644 --- a/lib/observer/src/observer_perf_wx.erl +++ b/lib/observer/src/observer_perf_wx.erl @@ -24,7 +24,8 @@ handle_event/2, handle_sync_event/3, handle_cast/2]). %% Drawing wrappers for DC and GC areas --export([haveGC/0, +-export([setup_graph_drawing/1, refresh_panel/6, + haveGC/0, setPen/2, setFont/3, setBrush/2, strokeLine/5, strokeLines/2, drawRoundedRectangle/6, drawText/4, getTextExtent/2]). @@ -42,13 +43,12 @@ data = {0, queue:new()}, panel, paint, - appmon, - usegc = false + appmon }). -define(wxGC, wxGraphicsContext). --record(paint, {font, small, pen, pen2, pens}). +-record(paint, {font, small, pen, pen2, pens, usegc = false}). -define(RQ_W, 1). -define(MEM_W, 2). @@ -63,14 +63,11 @@ init([Notebook, Parent]) -> Main = wxBoxSizer:new(?wxVERTICAL), Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxCLIP_CHILDREN, CPU = wxPanel:new(Panel, [{winid, ?RQ_W}, {style,Style}]), - wxWindow:setBackgroundColour(CPU, ?wxWHITE), wxSizer:add(Main, CPU, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), MemIO = wxBoxSizer:new(?wxHORIZONTAL), MEM = wxPanel:new(Panel, [{winid, ?MEM_W}, {style,Style}]), - wxWindow:setBackgroundColour(MEM, ?wxWHITE), IO = wxPanel:new(Panel, [{winid, ?IO_W}, {style,Style}]), - wxWindow:setBackgroundColour(IO, ?wxWHITE), wxSizer:add(MemIO, MEM, [{flag, ?wxEXPAND bor ?wxLEFT}, {proportion, 1}, {border, 5}]), wxSizer:add(MemIO, IO, [{flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}, @@ -79,53 +76,56 @@ init([Notebook, Parent]) -> {proportion, 1}, {border, 5}]), wxWindow:setSizer(Panel, Main), - wxPanel:connect(CPU, paint, [callback]), - wxPanel:connect(IO, paint, [callback]), - wxPanel:connect(MEM, paint, [callback]), - case os:type() of - {win32, _} -> %% Ignore erase on windows - wxPanel:connect(CPU, erase_background, [{callback, fun(_,_) -> ok end}]), - wxPanel:connect(IO, erase_background, [{callback, fun(_,_) -> ok end}]), - wxPanel:connect(MEM, erase_background, [{callback, fun(_,_) -> ok end}]); - _ -> ok - end, + PaintInfo = setup_graph_drawing([CPU, MEM, IO]), + process_flag(trap_exit, true), + {Panel, #state{parent=Parent, + panel =Panel, + windows = {CPU, MEM, IO}, + paint=PaintInfo + }} + catch _:Err -> + io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]), + {stop, Err} + end. + +setup_graph_drawing(Panels) -> + IsWindows = element(1, os:type()) =:= win32, + IgnoreCB = {callback, fun(_,_) -> ok end}, + Do = fun(Panel) -> + wxWindow:setBackgroundColour(Panel, ?wxWHITE), + wxPanel:connect(Panel, paint, [callback]), + IsWindows andalso + wxPanel:connect(Panel, erase_background, [IgnoreCB]) + end, + _ = [Do(Panel) || Panel <- Panels], UseGC = haveGC(), Version28 = ?wxMAJOR_VERSION =:= 2 andalso ?wxMINOR_VERSION =:= 8, {Font, SmallFont} - = case os:type() of - {unix, _} when UseGC, Version28 -> + = if UseGC, Version28 -> %% Def font is really small when using Graphics contexts in 2.8 %% Hardcode it F = wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD), SF = wxFont:new(10, ?wxFONTFAMILY_DECORATIVE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), {F, SF}; - _ -> + true -> DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), DefSize = wxFont:getPointSize(DefFont), DefFamily = wxFont:getFamily(DefFont), - F = wxFont:new(DefSize, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD), - SF = wxFont:new(DefSize-1, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), + F = wxFont:new(DefSize-1, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD), + SF = wxFont:new(DefSize-2, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), {F, SF} end, BlackPen = wxPen:new({0,0,0}, [{width, 2}]), - Pens = [wxPen:new(Col, [{width, 2}]) || Col <- tuple_to_list(colors())], - process_flag(trap_exit, true), - {Panel, #state{parent=Parent, - panel =Panel, - windows = {CPU, MEM, IO}, - usegc=UseGC, - paint=#paint{font = Font, - small = SmallFont, - pen = ?wxGREY_PEN, - pen2 = BlackPen, - pens = list_to_tuple(Pens) - } - }} - catch _:Err -> - io:format("~p crashed ~p: ~p~n",[?MODULE, Err, erlang:get_stacktrace()]), - {stop, Err} - end. + Pens = [wxPen:new(Col, [{width, 3}]) || Col <- tuple_to_list(colors())], + #paint{usegc = UseGC, + font = Font, + small = SmallFont, + pen = ?wxGREY_PEN, + pen2 = BlackPen, + pens = list_to_tuple(Pens) + }. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -139,21 +139,25 @@ handle_event(Event, _State) -> %%%%%%%%%% handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_, #state{active=Active, offset=Offset, paint=Paint, - windows=Windows, data=Data, usegc=UseGC}) -> - %% PaintDC must be created in a callback to work on windows. + windows=Windows, data=Data}) -> %% Sigh workaround bug on MacOSX (Id in paint event is always 0) %% Panel = element(Id, Windows), - Id = if Panel =:= element(?RQ_W, Windows) -> ?RQ_W; - Panel =:= element(?MEM_W, Windows) -> ?MEM_W; - Panel =:= element(?IO_W, Windows) -> ?IO_W + Id = if Panel =:= element(?RQ_W, Windows) -> runq; + Panel =:= element(?MEM_W, Windows) -> memory; + Panel =:= element(?IO_W, Windows) -> io end, - IsWindows = element(1, os:type()) =:= win32, - DC = if IsWindows -> + refresh_panel(Panel, Id, Offset, Data, Active, Paint), + ok. + +refresh_panel(Panel, Id, Offset, Data, Active, #paint{usegc=UseGC} = Paint) -> + %% PaintDC must be created in a callback to work on windows. + IsWindows = element(1, os:type()) =:= win32, + DC = if IsWindows -> %% Ugly hack to aviod flickering on windows, works on windows only %% But the other platforms are doublebuffered by default wx:typeCast(wxBufferedPaintDC:new(Panel), wxPaintDC); - true -> + true -> wxPaintDC:new(Panel) end, IsWindows andalso wxDC:clear(DC), @@ -167,8 +171,9 @@ handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_, io:format("Internal error ~p ~p~n",[Err, erlang:get_stacktrace()]) end, UseGC andalso ?wxGC:destroy(GC), - wxPaintDC:destroy(DC), - ok. + wxPaintDC:destroy(DC). + + %%%%%%%%%% handle_call(Event, From, _State) -> error({unhandled_call, Event, From}). @@ -247,10 +252,10 @@ create_menus(Parent, _) -> observer_wx:create_menus(Parent, MenuEntries). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -collect_data(?RQ_W, {N, Q}) -> +collect_data(runq, {N, Q}) -> case queue:to_list(Q) of - [] -> {0, 0, []}; - [_] -> {0, 0, []}; + [] -> {0, 0, [], []}; + [_] -> {0, 0, [], []}; [{stats, _Ver, Init0, _IO, _Mem}|Data0] -> Init = lists:sort(Init0), [_|Data=[First|_]] = lists:foldl(fun({stats, _, T0, _, _}, [Prev|Acc]) -> @@ -258,25 +263,46 @@ collect_data(?RQ_W, {N, Q}) -> Delta = calc_delta(TN, Prev), [TN, list_to_tuple(Delta)|Acc] end, [Init], Data0), - {N, lmax(Data), lists:reverse([First|Data])} + NoGraphs = tuple_size(First), + {N, lmax(Data), lists:reverse([First|Data]), lists:seq(1, NoGraphs)} end; -collect_data(?MEM_W, {N, Q}) -> +collect_data(memory, {N, Q}) -> MemT = mem_types(), Data = [list_to_tuple([Value || {Type,Value} <- MemInfo, lists:member(Type, MemT)]) || {stats, _Ver, _RQ, _IO, MemInfo} <- queue:to_list(Q)], - {N, lmax(Data), Data}; -collect_data(?IO_W, {N, Q}) -> + {N, lmax(Data), Data, MemT}; +collect_data(io, {N, Q}) -> case queue:to_list(Q) of - [] -> {0, 0, []}; - [_] -> {0, 0, []}; + [] -> {0, 0, [], []}; + [_] -> {0, 0, [], []}; [{stats, _Ver, _RQ, {{_,In0}, {_,Out0}}, _Mem}|Data0] -> [_,_|Data=[First|_]] = lists:foldl(fun({stats, _, _, {{_,In}, {_,Out}}, _}, [PIn,Pout|Acc]) -> [In,Out,{In-PIn,Out-Pout}|Acc] end, [In0,Out0], Data0), - {N, lmax(Data), lists:reverse([First|Data])} - end. + {N, lmax(Data), lists:reverse([First|Data]), [input, output]} + end; +collect_data(alloc, {N, Q}) -> + List = queue:to_list(Q), + Data = [list_to_tuple([Carrier || {_Type,_Block,Carrier} <- MemInfo]) + || MemInfo <- List], + Info = case List of %% Varies depending on erlang build config/platform + [MInfo|_] -> [Type || {Type, _, _} <- MInfo]; + _ -> [] + end, + {N, lmax(Data), Data, Info}; + +collect_data(utilz, {N, Q}) -> + List = queue:to_list(Q), + Data = [list_to_tuple([round(100*Block/Carrier) || {_Type,Block,Carrier} <- MemInfo]) + || MemInfo <- List], + Info = case List of %% Varies depending on erlang build config/platform + [MInfo|_] -> [Type || {Type, _, _} <- MInfo]; + _ -> [] + end, + {N, lmax(Data), Data, Info}. + mem_types() -> [total, processes, atom, binary, code, ets]. @@ -299,14 +325,14 @@ draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active) %% This can be optimized a lot by collecting data once %% and draw to memory and then blit memory and only draw new entries in new memory %% area. Hmm now rewritten to use ?wxGC I don't now if it is feasable. - {Len, Max0, Hs} = collect_data(Id, Data), - Max = calc_max(Max0), - NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end, + {Len, Max0, Hs, Info} = collect_data(Id, Data), + {Max,_,_} = MaxDisp = calc_max(Id, Max0), Size = wxWindow:getClientSize(Panel), - {X0,Y0,WS,HS} = draw_borders(Id, NoGraphs, DC, Size, Max, Paint), + {X0,Y0,WS,HS, DrawBs} = draw_borders(Id, Info, DC, Size, MaxDisp, Paint), Last = 60*WS+X0-1, Start = max(61-Len, 0)*WS+X0 - Offset*WS, Samples = length(Hs), + NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end, case Active andalso Samples > 1 andalso NoGraphs > 0 of true -> Draw = fun(N) -> @@ -315,14 +341,16 @@ draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active) strokeLines(DC, Lines), N+1 end, - [Draw(I) || I <- lists:seq(NoGraphs, 1, -1)]; + [Draw(I) || I <- lists:seq(NoGraphs, 1, -1)], + DrawBs(); false -> - Info = case Active andalso Samples =< 1 of - true -> "Waiting on data"; + DrawBs(), + Text = case Active andalso Samples =< 1 of + true -> "Waiting for data"; false -> "Information not available" end, setFont(DC, Small, {0,0,0}), - drawText(DC, Info, X0 + 100, element(2,Size) div 2) + drawText(DC, Text, X0 + 100, element(2,Size) div 2) end, ok. @@ -397,9 +425,8 @@ spline_tan(Y0, Y1, Y2, Y3) -> -define(BW, 5). -define(BH, 5). -draw_borders(Type, NoGraphs, DC, {W,H}, Max, +draw_borders(Type, Info, DC, {W,H}, {Max, Unit, MaxUnit}, #paint{pen=Pen, pen2=Pen2, font=Font, small=Small}) -> - {Unit, MaxUnit} = bytes(Type, Max), Str1 = observer_lib:to_str(MaxUnit), Str2 = observer_lib:to_str(MaxUnit div 2), Str3 = observer_lib:to_str(0), @@ -410,10 +437,10 @@ draw_borders(Type, NoGraphs, DC, {W,H}, Max, GraphX0 = ?BW+TW+?BW, GraphX1 = W-?BW*4, - TopTextX = ?BW+TW+?BW, - MaxTextY = ?BH+TH+?BH, + TopTextX = ?BW*3+TW, + MaxTextY = TH+?BH, BottomTextY = H-?BH-TH, - SecondsY = BottomTextY - ?BH - TH, + SecondsY = BottomTextY - TH, GraphY0 = MaxTextY + (TH / 2), GraphY1 = SecondsY - ?BH, GraphW = GraphX1-GraphX0-1, @@ -447,17 +474,7 @@ draw_borders(Type, NoGraphs, DC, {W,H}, Max, strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50), strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75), - setPen(DC, Pen2), - strokeLines(DC, [{GraphX0, GraphY0-1}, {GraphX0, GraphY1+1}, - {GraphX1, GraphY1+1}, {GraphX1, GraphY0-1}, - {GraphX0, GraphY0-1}]), - setFont(DC, Font, {0,0,0}), - case Type of - ?RQ_W -> drawText(DC, "Scheduler Utilization (%) ", TopTextX,?BH); - ?MEM_W -> drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH); - ?IO_W -> drawText(DC, "IO Usage " ++ Unit, TopTextX,?BH) - end, Text = fun(X,Y, Str, PenId) -> if PenId == 0 -> @@ -468,32 +485,65 @@ draw_borders(Type, NoGraphs, DC, {W,H}, Max, end, drawText(DC, Str, X, Y), {StrW, _} = getTextExtent(DC, Str), - StrW + X + SpaceW + StrW + X + ?BW*2 end, + case Type of - ?RQ_W -> - TN0 = Text(?BW, BottomTextY, "Scheduler: ", 0), + runq -> + drawText(DC, "Scheduler Utilization (%) ", TopTextX, ?BH), + TN0 = Text(TopTextX, BottomTextY, "Scheduler: ", 0), lists:foldl(fun(Id, Pos0) -> Text(Pos0, BottomTextY, integer_to_list(Id), Id) - end, TN0, lists:seq(1, NoGraphs)); - ?MEM_W -> + end, TN0, Info); + memory -> + drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH), + lists:foldl(fun(MType, {PenId, Pos0}) -> + Str = to_string(MType), + Pos = Text(Pos0, BottomTextY, Str, PenId), + {PenId+1, Pos} + end, {1, TopTextX}, Info); + io -> + drawText(DC, "IO Usage " ++ Unit, TopTextX,?BH), + lists:foldl(fun(MType, {PenId, Pos0}) -> + Str = to_string(MType), + Pos = Text(Pos0, BottomTextY, Str, PenId), + {PenId+1, Pos} + end, {1, TopTextX}, Info); + alloc -> + drawText(DC, "Carrier Size " ++ Unit, TopTextX,?BH); + utilz -> + drawText(DC, "Carrier Utilization (%)" ++ Unit, TopTextX,?BH), lists:foldl(fun(MType, {PenId, Pos0}) -> - Str = uppercase(atom_to_list(MType)), + Str = to_string(MType), Pos = Text(Pos0, BottomTextY, Str, PenId), {PenId+1, Pos} - end, {1, ?BW}, mem_types()); - ?IO_W -> - TN0 = Text(?BW, BottomTextY, "Input", 1), - Text(TN0, BottomTextY, "Output", 2) + end, {1, TopTextX}, Info) end, - {GraphX0+1, GraphY1, ScaleW, ScaleH}. + DrawBorder = fun() -> + setPen(DC, Pen2), + strokeLines(DC, [{GraphX0, GraphY0-1}, {GraphX0, GraphY1+1}, + {GraphX1, GraphY1+1}, {GraphX1, GraphY0-1}, + {GraphX0, GraphY0-1}]) + end, + {GraphX0+1, GraphY1, ScaleW, ScaleH, DrawBorder}. + +to_string(Atom) -> + Name = atom_to_list(Atom), + case lists:reverse(Name) of + "colla_" ++ Rev -> + uppercase(lists:reverse(Rev)); + _ -> + uppercase(Name) + end. uppercase([C|Rest]) -> [C-$a+$A|Rest]. -calc_max(Max) when Max < 10 -> 10; -calc_max(Max) -> calc_max1(Max). +calc_max(Type, Max) -> + bytes(Type, Max). +calc_max1(Max) when Max < 10 -> + 10; calc_max1(Max) -> case Max div 10 of X when X < 10 -> @@ -506,23 +556,36 @@ calc_max1(Max) -> 10*calc_max1(X) end. -bytes(?RQ_W, Val) -> {"", Val}; +bytes(runq, Val) -> + Upper = calc_max1(Val), + {Upper, "", Upper}; +bytes(utilz, Val) -> + Upper = calc_max1(Val), + {Upper, "", Upper}; bytes(_, B) -> KB = B div 1024, MB = KB div 1024, GB = MB div 1024, if - GB > 10 -> {"(GB)", GB}; - MB > 10 -> {"(MB)", MB}; - KB > 0 -> {"(KB)", KB}; - true -> {"(B)", B} + GB > 10 -> + Upper = calc_max1(GB), + {Upper*1024*1024*1024, "(GB)", Upper}; + MB > 10 -> + Upper = calc_max1(MB), + {Upper*1024*1024, "(MB)", Upper}; + KB > 0 -> + Upper = calc_max1(KB), + {Upper*1024, "(KB)", Upper}; + true -> + Upper = calc_max1(B), + {Upper, "(B)", Upper} end. colors() -> - {{200, 50, 50}, {50, 200, 50}, {50, 50, 200}, - {255, 110, 0}, {50, 200, 200}, {200, 50, 200}, - {240, 200, 80}, {140, 2, 140}, - {100, 200, 240}, {100, 240, 100} + {{240, 100, 100}, {100, 240, 100}, {100, 100, 240}, + {220, 220, 80}, {100, 240, 240}, {240, 100, 240}, + {100, 25, 25}, {25, 100, 25}, {25, 25, 100}, + {120, 120, 0}, {25, 100, 100}, {100, 50, 100} }. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 0be8c18893..026693ff56 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -578,7 +578,7 @@ get_row(From, Row, pid, Info) -> end, From ! {self(), Pid}; get_row(From, Row, Col, Info) -> - Data = case Row > array:size(Info) of + Data = case Row >= array:size(Info) of true -> ""; false -> diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 8e8a37fc93..d724cd9e96 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -43,6 +43,8 @@ -record(worker, {panel, callback}). +-record(io, {rdata=""}). + start(Process, ParentFrame, Parent) -> wx_object:start_link(?MODULE, [Process, ParentFrame, Parent], []). @@ -69,6 +71,10 @@ init([Pid, ParentFrame, Parent]) -> DictPage = init_panel(Notebook, "Dictionary", [Pid,Table], fun init_dict_page/3), StackPage = init_panel(Notebook, "Stack Trace", [Pid], fun init_stack_page/2), StatePage = init_panel(Notebook, "State", [Pid,Table], fun init_state_page/3), + Ps = case gen_server:call(observer, log_status) of + true -> [init_panel(Notebook, "Log", [Pid,Table], fun init_log_page/3)]; + false -> [] + end, wxFrame:connect(Frame, close_window), wxMenu:connect(Frame, command_menu_selected), @@ -78,7 +84,7 @@ init([Pid, ParentFrame, Parent]) -> pid=Pid, frame=Frame, notebook=Notebook, - pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage], + pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage|Ps], expand_table=Table }} catch error:{badrpc, _} -> @@ -144,7 +150,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), [{Id,Win}|Opened0]; {_,Win} -> wxFrame:raise(Win), @@ -327,6 +333,26 @@ fetch_state_info2(Pid, M) -> {badrpc,{'EXIT',{timeout, _}}} -> [] end. +init_log_page(Parent, Pid, Table) -> + Win = observer_lib:html_window(Parent), + Update = fun() -> + Fd = spawn_link(fun() -> io_server() end), + rpc:call(node(Pid), rb, rescan, [[{start_log, Fd}]]), + rpc:call(node(Pid), rb, grep, [local_pid_str(Pid)]), + Logs = io_get_data(Fd), + %% Replace remote local pid notation to global notation + Pref = global_pid_node_pref(Pid), + ExpPid = re:replace(Logs,"<0\.","<" ++ Pref ++ ".",[global, {return, list}]), + %% Try to keep same look by removing blanks at right of rewritten PID + NbBlanks = length(Pref) - 1, + Re = "(<" ++ Pref ++ "\.[^>]{1,}>)[ ]{"++ integer_to_list(NbBlanks) ++ "}", + Look = re:replace(ExpPid, Re, "\\1", [global, {return, list}]), + Html = observer_html_lib:expandable_term("SaslLog", Look, Table), + wxHtmlWindow:setPage(Win, Html) + end, + Update(), + {Win, Update}. + create_menus(MenuBar) -> Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}, {"View", [#create_menu{id=?REFRESH, text="Refresh\tCtrl-R"}]}], @@ -409,3 +435,51 @@ filter_monitor_info() -> Ms = proplists:get_value(monitors, Data), [Pid || {process, Pid} <- Ms] end. + +local_pid_str(Pid) -> + %% observer can observe remote nodes + %% There is no function to get the local + %% pid from the remote pid ... + %% So grep will fail to find remote pid in remote local log. + %% i.e. <4589.42.1> will not be found, but <0.42.1> will + %% Let's replace first integer by zero + "<0" ++ re:replace(pid_to_list(Pid),"\<([0-9]{1,})","",[{return, list}]). + +global_pid_node_pref(Pid) -> + %% Global PID node prefix : X of <X.Y.Z> + string:strip(string:sub_word(pid_to_list(Pid),1,$.),left,$<). + + +io_get_data(Pid) -> + Pid ! {self(), get_data_and_close}, + receive + {Pid, data, Data} -> lists:flatten(Data) + end. + +io_server() -> + io_server(#io{}). + +io_server(State) -> + receive + {io_request, From, ReplyAs, Request} -> + {_, Reply, NewState} = io_request(Request,State), + From ! {io_reply, ReplyAs, Reply}, + io_server(NewState); + {Pid, get_data_and_close} -> + Pid ! {self(), data, lists:reverse(State#io.rdata)}, + normal; + _Unknown -> + io_server(State) + end. + +io_request({put_chars, _Encoding, Chars}, State = #io{rdata=Data}) -> + {ok, ok, State#io{rdata=[Chars|Data]}}; +io_request({put_chars, Encoding, Module, Function, Args}, State) -> + try + io_request({put_chars, Encoding, apply(Module, Function, Args)}, State) + catch _:_ -> + {error, {error, Function}, State} + end; +io_request(_Req, State) -> + %% io:format("~p: Unknown req: ~p ~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 f989f9cf97..ea89590e84 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -37,7 +37,6 @@ parent_notebook, panel, sizer, menubar, - alloc, fields, timer}). @@ -48,7 +47,6 @@ start_link(Notebook, Parent) -> init([Notebook, Parent]) -> SysInfo = observer_backend:sys_info(), - AllocInfo = proplists:get_value(alloc_info, SysInfo, []), {Info, Stat} = info_fields(), Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxVERTICAL), @@ -60,16 +58,13 @@ init([Notebook, Parent]) -> wxSizer:add(TopSizer, FPanel0, [{flag, ?wxEXPAND}, {proportion, 1}]), wxSizer:add(TopSizer, FPanel1, [{flag, ?wxEXPAND}, {proportion, 1}]), BorderFlags = ?wxLEFT bor ?wxRIGHT, - {MemPanel, MemoryInfo} = create_mem_info(Panel, AllocInfo), wxSizer:add(Sizer, TopSizer, [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP}, {proportion, 0}, {border, 5}]), - wxSizer:add(Sizer, MemPanel, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM}, - {proportion, 1}, {border, 5}]), wxPanel:setSizer(Panel, Sizer), Timer = observer_lib:start_timer(10), {Panel, #sys_wx_state{parent=Parent, parent_notebook=Notebook, - panel=Panel, sizer=Sizer, alloc=MemoryInfo, + panel=Panel, sizer=Sizer, timer=Timer, fields=Fields0 ++ Fields1}}. create_sys_menu(Parent) -> @@ -77,91 +72,13 @@ create_sys_menu(Parent) -> #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh interval"}]}, observer_wx:create_menus(Parent, [View]). -update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer, alloc=AllocCtrl}) -> +update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) -> SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []), - AllocInfo = proplists:get_value(alloc_info, SysInfo, []), {Info, Stat} = info_fields(), observer_lib:update_info(Fields, observer_lib:fill_info(Info, SysInfo) ++ observer_lib:fill_info(Stat, SysInfo)), - update_alloc(AllocCtrl, AllocInfo), wxSizer:layout(Sizer). -create_mem_info(Parent, Fields) -> - Panel = wxPanel:new(Parent), - wxWindow:setBackgroundColour(Panel, {255,255,255}), - Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES bor ?wxLC_VRULES, - Grid = wxListCtrl:new(Panel, [{style, Style}]), - Li = wxListItem:new(), - AddListEntry = fun({Name, Align, DefSize}, Col) -> - wxListItem:setText(Li, Name), - wxListItem:setAlign(Li, Align), - wxListCtrl:insertColumn(Grid, Col, Li), - wxListCtrl:setColumnWidth(Grid, Col, DefSize), - Col + 1 - end, - ListItems = [{"Allocator Type", ?wxLIST_FORMAT_LEFT, 200}, - {"Block size (kB)", ?wxLIST_FORMAT_RIGHT, 150}, - {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}], - lists:foldl(AddListEntry, 0, ListItems), - wxListItem:destroy(Li), - update_alloc(Grid, Fields), - - Sizer = wxBoxSizer:new(?wxVERTICAL), - wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}, - {border, 5}, {proportion, 1}]), - wxWindow:setSizerAndFit(Panel, Sizer), - {Panel, Grid}. - -update_alloc(Grid, AllocInfo) -> - Fields = alloc_info(AllocInfo, [], 0, 0, true), - wxListCtrl:deleteAllItems(Grid), - Update = fun({Name, BS, CS}, Row) -> - wxListCtrl:insertItem(Grid, Row, ""), - wxListCtrl:setItem(Grid, Row, 0, observer_lib:to_str(Name)), - wxListCtrl:setItem(Grid, Row, 1, observer_lib:to_str(BS div 1024)), - wxListCtrl:setItem(Grid, Row, 2, observer_lib:to_str(CS div 1024)), - Row + 1 - end, - lists:foldl(Update, 0, Fields), - Fields. - -alloc_info([{Type,Instances}|Allocators],TypeAcc,TotalBS,TotalCS,IncludeTotal) -> - {BS,CS,NewTotalBS,NewTotalCS,NewIncludeTotal} = - sum_alloc_instances(Instances,0,0,TotalBS,TotalCS), - alloc_info(Allocators,[{Type,BS,CS}|TypeAcc],NewTotalBS,NewTotalCS, - IncludeTotal andalso NewIncludeTotal); -alloc_info([],TypeAcc,TotalBS,TotalCS,IncludeTotal) -> - Types = [X || X={_,BS,CS} <- TypeAcc, (BS>0 orelse CS>0)], - case IncludeTotal of - true -> - [{total,TotalBS,TotalCS} | lists:reverse(Types)]; - false -> - lists:reverse(Types) - end. - -sum_alloc_instances(false,BS,CS,TotalBS,TotalCS) -> - {BS,CS,TotalBS,TotalCS,false}; -sum_alloc_instances([{_,_,Data}|Instances],BS,CS,TotalBS,TotalCS) -> - {NewBS,NewCS,NewTotalBS,NewTotalCS} = - sum_alloc_one_instance(Data,BS,CS,TotalBS,TotalCS), - sum_alloc_instances(Instances,NewBS,NewCS,NewTotalBS,NewTotalCS); -sum_alloc_instances([],BS,CS,TotalBS,TotalCS) -> - {BS,CS,TotalBS,TotalCS,true}. - -sum_alloc_one_instance([{sbmbcs,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}| - Rest],OldBS,OldCS,TotalBS,TotalCS) -> - sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS,TotalCS); -sum_alloc_one_instance([{_,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}| - Rest],OldBS,OldCS,TotalBS,TotalCS) -> - sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS); -sum_alloc_one_instance([{_,[{blocks_size,BS},{carriers_size,CS}]}| - Rest],OldBS,OldCS,TotalBS,TotalCS) -> - sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS); -sum_alloc_one_instance([_|Rest],BS,CS,TotalBS,TotalCS) -> - sum_alloc_one_instance(Rest,BS,CS,TotalBS,TotalCS); -sum_alloc_one_instance([],BS,CS,TotalBS,TotalCS) -> - {BS,CS,TotalBS,TotalCS}. - info_fields() -> Info = [{"System and Architecture", [{"System Version", otp_release}, diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index da4cb8e041..acfa6a2a99 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -176,10 +176,16 @@ handle_call(Event, From, _State) -> handle_cast(Event, _State) -> error({unhandled_cast, Event}). -handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) -> - Tables = get_tables(Node, Opt), - Tabs = update_grid(Grid, Opt, Tables), - {noreply, State#state{tabs=Tabs}}; +handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt, + tabs=OldTabs}) -> + case get_tables(Node, Opt) of + OldTabs -> + %% no change + {noreply, State}; + Tables -> + Tabs = update_grid(Grid, Opt, Tables), + {noreply, State#state{tabs=Tabs}} + end; handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, timer=Timer0}) -> diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 15df804975..cf602569aa 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -37,11 +37,13 @@ -define(ID_CONNECT, 2). -define(ID_NOTEBOOK, 3). -define(ID_CDV, 4). +-define(ID_LOGVIEW, 5). -define(FIRST_NODES_MENU_ID, 1000). -define(LAST_NODES_MENU_ID, 2000). -define(TRACE_STR, "Trace Overview"). +-define(ALLOC_STR, "Memory Allocators"). %% Records -record(state, @@ -57,10 +59,12 @@ trace_panel, app_panel, perf_panel, + allc_panel, active_tab, node, nodes, - prev_node="" + prev_node="", + log = false }). start() -> @@ -147,6 +151,10 @@ setup(#state{frame = Frame} = State) -> PerfPanel = observer_perf_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, PerfPanel, "Load Charts", []), + %% Memory Allocator Viewer Panel + AllcPanel = observer_alloc_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, AllcPanel, ?ALLOC_STR, []), + %% App Viewer Panel AppPanel = observer_app_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, AppPanel, "Applications", []), @@ -182,6 +190,7 @@ setup(#state{frame = Frame} = State) -> trace_panel = TracePanel, app_panel = AppPanel, perf_panel = PerfPanel, + allc_panel = AllcPanel, active_tab = SysPid, node = node(), nodes = Nodes @@ -215,14 +224,17 @@ handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}}, {noreply, State#state{active_tab=Pid}} end; -handle_event(#wx{event = #wxClose{}}, State) -> - {stop, normal, State}; - handle_event(#wx{id = ?ID_CDV, event = #wxCommand{type = command_menu_selected}}, State) -> spawn(crashdump_viewer, start, []), {noreply, State}; -handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) -> +handle_event(#wx{event = #wxClose{}}, #state{log=LogOn} = State) -> + LogOn andalso rpc:block_call(State#state.node, rb, stop, []), + {stop, normal, State}; + +handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, + #state{log=LogOn} = State) -> + LogOn andalso rpc:block_call(State#state.node, rb, stop, []), {stop, normal, State}; handle_event(#wx{id = ?wxID_HELP, event = #wxCommand{type = command_menu_selected}}, State) -> @@ -300,12 +312,42 @@ handle_event(#wx{id = ?ID_PING, event = #wxCommand{type = command_menu_selected} end, {noreply, UpdState}; -handle_event(#wx{id = Id, event = #wxCommand{type = command_menu_selected}}, State) - when Id > ?FIRST_NODES_MENU_ID, Id < ?LAST_NODES_MENU_ID -> +handle_event(#wx{id = ?ID_LOGVIEW, event = #wxCommand{type = command_menu_selected}}, + #state{frame = Frame, log = PrevLog, node = Node} = State) -> + try + ok = ensure_sasl_started(Node), + ok = ensure_mf_h_handler_used(Node), + ok = ensure_rb_mode(Node, PrevLog), + case PrevLog of + false -> + rpc:block_call(Node, rb, start, []), + set_status("Observer - " ++ atom_to_list(Node) ++ " (rb_server started)"), + {noreply, State#state{log=true}}; + true -> + rpc:block_call(Node, rb, stop, []), + set_status("Observer - " ++ atom_to_list(Node) ++ " (rb_server stopped)"), + {noreply, State#state{log=false}} + end + catch + throw:Reason -> + create_txt_dialog(Frame, Reason, "Log view status", ?wxICON_ERROR), + {noreply, State} + end; - Node = lists:nth(Id - ?FIRST_NODES_MENU_ID, State#state.nodes), - UpdState = change_node_view(Node, State), - {noreply, UpdState}; +handle_event(#wx{id = Id, event = #wxCommand{type = command_menu_selected}}, + #state{nodes= Ns , node = PrevNode, log = PrevLog} = State) + when Id > ?FIRST_NODES_MENU_ID, Id < ?LAST_NODES_MENU_ID -> + Node = lists:nth(Id - ?FIRST_NODES_MENU_ID, Ns), + %% Close rb_server only if another node than current one selected + LState = case PrevLog of + true -> case Node == PrevNode of + false -> rpc:block_call(PrevNode, rb, stop, []), + State#state{log=false} ; + true -> State + end; + false -> State + end, + {noreply, change_node_view(Node, LState)}; handle_event(Event, State) -> Pid = get_active_pid(State), @@ -340,6 +382,9 @@ handle_call(stop, _, State = #state{frame = Frame}) -> wxFrame:destroy(Frame), {stop, normal, ok, State}; +handle_call(log_status, _From, State) -> + {reply, State#state.log, State}; + handle_call(_Msg, _From, State) -> {reply, ok, State}. @@ -467,7 +512,7 @@ check_page_title(Notebook) -> get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, tv_panel=Tv, trace_panel=Trace, app_panel=App, - perf_panel=Perf + perf_panel=Perf, allc_panel=Alloc }) -> Panel = case check_page_title(Notebook) of "Processes" -> Pro; @@ -475,13 +520,14 @@ get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, "Table Viewer" -> Tv; ?TRACE_STR -> Trace; "Load Charts" -> Perf; - "Applications" -> App + "Applications" -> App; + ?ALLOC_STR -> Alloc end, wx_object:get_pid(Panel). pid2panel(Pid, #state{pro_panel=Pro, sys_panel=Sys, tv_panel=Tv, trace_panel=Trace, app_panel=App, - perf_panel=Perf}) -> + perf_panel=Perf, allc_panel=Alloc}) -> case Pid of Pro -> "Processes"; Sys -> "System"; @@ -489,6 +535,7 @@ pid2panel(Pid, #state{pro_panel=Pro, sys_panel=Sys, Trace -> ?TRACE_STR; Perf -> "Load Charts"; App -> "Applications"; + Alloc -> ?ALLOC_STR; _ -> "unknown" end. @@ -568,17 +615,19 @@ default_menus(NodesMenuItems) -> false -> {"Nodes", NodesMenuItems ++ [#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}]} end, + LogMenu = {"Log", [#create_menu{id = ?ID_LOGVIEW, text = "Toggle log view"}]}, case os:type() =:= {unix, darwin} of false -> FileMenu = {"File", [CDV, Quit]}, HelpMenu = {"Help", [About,Help]}, - [FileMenu, NodeMenu, HelpMenu]; + [FileMenu, NodeMenu, LogMenu, HelpMenu]; true -> %% On Mac quit and about will be moved to the "default' place %% automagicly, so just add them to a menu that always exist. %% But not to the help menu for some reason + {Tag, Menus} = FileMenu, - [{Tag, Menus ++ [About]}, NodeMenu, {"&Help", [Help]}] + [{Tag, Menus ++ [Quit,About]}, NodeMenu, LogMenu, {"&Help", [Help]}] end. clean_menus(Menus, MenuBar) -> @@ -657,3 +706,59 @@ update_node_list(State = #state{menubar=MenuBar}) -> end, observer_lib:create_menu_item(Dist, NodeMenu, Index), State#state{nodes = Nodes}. + +ensure_sasl_started(Node) -> + %% is sasl started ? + Apps = rpc:block_call(Node, application, which_applications, []), + case lists:keyfind(sasl, 1, Apps) of + false -> throw("Error: sasl application not started."), + error; + {sasl, _, _} -> ok + end. + +ensure_mf_h_handler_used(Node) -> + %% is log_mf_h used ? + Handlers = rpc:block_call(Node, gen_event, which_handlers, [error_logger]), + case lists:any(fun(L)-> L == log_mf_h end, Handlers) of + false -> throw("Error: log_mf_h handler not used in sasl."), + error; + true -> ok + end. + +ensure_rb_mode(Node, PrevLog) -> + ok = ensure_rb_module_loaded(Node), + ok = is_rb_compatible(Node), + ok = is_rb_server_running(Node, PrevLog), + ok. + + +ensure_rb_module_loaded(Node) -> + %% Need to ensure that module is loaded in order to detect exported + %% functions on interactive nodes + case rpc:block_call(Node, code, ensure_loaded, [rb]) of + {badrpc, Reason} -> + throw("Error: badrpc - " ++ io_lib:format("~tp",[Reason])); + {error, Reason} -> + throw("Error: rb module load error - " ++ io_lib:format("~tp",[Reason])); + {module,rb} -> + ok + end. + +is_rb_compatible(Node) -> + %% Simply test that rb:log_list/0 is exported + case rpc:block_call(Node, erlang, function_exported, [rb, log_list, 0]) of + false -> throw("Error: Node's Erlang release must be at least R16B02."); + true -> ok + end. + +is_rb_server_running(Node, LogState) -> + %% If already started, somebody else may use it. + %% We can not use it too, as far log file would be overriden. Not fair. + case rpc:block_call(Node, erlang, whereis, [rb_server]) of + Pid when is_pid(Pid), (LogState == false) -> + throw("Error: rb_server is already started and maybe used by someone."); + Pid when is_pid(Pid) -> + ok; + undefined -> + ok + end. diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl index 61fd6d1787..a2db40aa2f 100644 --- a/lib/observer/src/ttb.erl +++ b/lib/observer/src/ttb.erl @@ -849,7 +849,7 @@ get_nodes() -> receive {?MODULE,Nodes} -> Nodes end. ts() -> - {{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(now()), + {{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(erlang:timestamp()), io_lib:format("-~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", [Y,M,D,H,Min,S]). |