diff options
Diffstat (limited to 'lib/observer/src')
-rw-r--r-- | lib/observer/src/Makefile | 18 | ||||
-rw-r--r-- | lib/observer/src/observer_defs.hrl | 55 | ||||
-rw-r--r-- | lib/observer/src/observer_pro_wx.erl | 944 | ||||
-rw-r--r-- | lib/observer/src/observer_procinfo.erl | 511 | ||||
-rw-r--r-- | lib/observer/src/observer_sys.erl | 131 | ||||
-rw-r--r-- | lib/observer/src/observer_sys_wx.erl | 313 | ||||
-rw-r--r-- | lib/observer/src/observer_trace_wx.erl | 487 | ||||
-rw-r--r-- | lib/observer/src/observer_traceoptions_wx.erl | 1066 | ||||
-rw-r--r-- | lib/observer/src/observer_wx.erl | 506 |
9 files changed, 4026 insertions, 5 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 3875b62101..514c6e441c 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -1,19 +1,19 @@ # # %CopyrightBegin% -# +# # Copyright Ericsson AB 2002-2011. All Rights Reserved. -# +# # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in # compliance with the License. You should have received a copy of the # Erlang Public License along with this software. If not, it can be # retrieved online at http://www.erlang.org/. -# +# # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. -# +# # %CopyrightEnd% # include $(ERL_TOP)/make/target.mk @@ -41,8 +41,16 @@ MODULES= \ etop_gui \ etop_tr \ etop_txt \ + observer_wx \ + observer_pro_wx \ + observer_procinfo \ + observer_sys \ + observer_sys_wx \ + observer_trace_wx \ + observer_traceoptions_wx \ ttb \ ttb_et + HRL_FILES= \ ../include/etop.hrl INTERNAL_HRL_FILES= \ @@ -109,7 +117,7 @@ docs: # ---------------------------------------------------- # Release Target -# ---------------------------------------------------- +# ---------------------------------------------------- include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl new file mode 100644 index 0000000000..7990cc248a --- /dev/null +++ b/lib/observer/src/observer_defs.hrl @@ -0,0 +1,55 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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% + +%% -define(OBS, observer_wx). +%% -define(OBS_SYS_LOGIC, observer_sys). +%% -define(OBS_SYS_WX, observer_sys_wx). +%% -define(OBS_PRO_WX, observer_pro_wx). + + +-record(trace_options, {send = false, + treceive = false, + functions = false, + events = false, + on_1st_spawn = false, + on_all_spawn = false, + on_1st_link = false, + on_all_link = false, + main_window = true}). + +-record(match_spec, {alias, + term_ms = [], + str_ms = [], + fun2ms}). + +-record(traced_func, {func_name, %atom + arity, %integer + match_spec = #match_spec{}}). + +-record(on_spawn, {checkbox, all_spawn, first_spawn}). + +-record(on_link, {checkbox, all_link, first_link}). + +-record(pid, {window, traced}). + +-record(create_menu, + {id, + text, + type = append, + check = false + }). diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl new file mode 100644 index 0000000000..7080da8231 --- /dev/null +++ b/lib/observer/src/observer_pro_wx.erl @@ -0,0 +1,944 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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_pro_wx). + +-behaviour(wx_object). + +-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_cast/2, to_str/1]). +-export([get_row/4, get_attr/3]). + +-include_lib("wx/include/wx.hrl"). +-include_lib("runtime_tools/include/observer_backend.hrl"). +-include("observer_defs.hrl"). + +%% Defines +-define(COL_PID, 0). +-define(COL_NAME, 1). +-define(COL_TIME, 2). +-define(COL_REDS, 3). +-define(COL_MEM, 4). +-define(COL_MSG, 5). +-define(COL_FUN, 6). + +-define(ID_KILL, 201). +-define(ID_PROC, 202). +-define(ID_REFRESH, 203). +-define(ID_REFRESH_INTERVAL, 204). +-define(ID_DUMP_TO_FILE, 205). +-define(ID_TRACEMENU, 206). +-define(ID_TRACE_ALL_MENU, 207). +-define(ID_TRACE_NEW_MENU, 208). +-define(ID_NO_OF_LINES, 209). +-define(ID_ACCUMULATE, 210). + +-define(START_LINES, 50). %% hardcoded startvalue representing the number of visible lines + +%% Records +-record(attrs, {even, odd, deleted, changed, searched}). + +-record(holder, {parent, + info, + attrs}). + + +-record(pro_wx_state, {parent, + etop_monitor, + holder_monitor, + grid, + panel, + popup_menu, + parent_notebook, + trace_options = #trace_options{}, + match_specs = [], + refr_timer = false, + tracemenu_opened, + procinfo_menu_pids = [], + selected_pids = [], + last_selected, + sort_dir = decr, % decr::atom | incr::incr + holder}). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +init([Notebook, Parent]) -> + {EtopMonitor, Config} = etop:start(observer), + SortDir = decr, + Attrs = create_attrs(), + Self = self(), + change_lines(?START_LINES), + Info = etop:update(Config, SortDir), + {Holder, HolderMon} = spawn_monitor(fun() -> + init_table_holder(Self, + Info, + Attrs) + end), + Count = length(Info#etop_info.procinfo), + {ProPanel, State} = setup(Notebook, Parent, Holder, Count), + refresh_grid(Holder, SortDir), + MatchSpecs = generate_matchspecs(), + {ProPanel, State#pro_wx_state{etop_monitor = EtopMonitor, + holder_monitor = HolderMon, + sort_dir = SortDir, + match_specs = MatchSpecs}}. + +setup(Notebook, Parent, Holder, Count) -> + ProPanel = wxPanel:new(Notebook, []), + + Grid = create_list_box(ProPanel, Holder, Count), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, + {border,4}]), + + wxWindow:setSizer(ProPanel, Sizer), + + Popup = create_popup_menu(ProPanel), + + State = #pro_wx_state{parent = Parent, + grid = Grid, + panel = ProPanel, + popup_menu = Popup, + parent_notebook = Notebook, + tracemenu_opened = false, + holder = Holder}, + {ProPanel, State}. + +generate_matchspecs() -> + try + StrMs1 = "[{'_', [], [{return_trace}]}].", + StrMs2 = "[{'_', [], [{exception_trace}]}].", + StrMs3 = "[{'_', [], [{message, {caller}}]}].", + StrMs4 = "[{'_', [], [{message, {process_dump}}]}].", + + {ok, Tokens1, _} = erl_scan:string(StrMs1), + {ok, Tokens2, _} = erl_scan:string(StrMs2), + {ok, Tokens3, _} = erl_scan:string(StrMs3), + {ok, Tokens4, _} = erl_scan:string(StrMs4), + {ok, Term1} = erl_parse:parse_term(Tokens1), + {ok, Term2} = erl_parse:parse_term(Tokens2), + {ok, Term3} = erl_parse:parse_term(Tokens3), + {ok, Term4} = erl_parse:parse_term(Tokens4), + + [#match_spec{term_ms = Term1, str_ms = StrMs1}, + #match_spec{term_ms = Term2, str_ms = StrMs2}, + #match_spec{term_ms = Term3, str_ms = StrMs3}, + #match_spec{term_ms = Term4, str_ms = StrMs4}] + catch + _:_ -> + [] + end. + +%% UI-creation + +create_pro_menu(Parent) -> + MenuEntries = [{"View", + [#create_menu{id = ?ID_REFRESH, text = "Refresh"}, + #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval"}]}, + {"Options", + [#create_menu{id = ?ID_DUMP_TO_FILE, text = "Dump to file"}, + #create_menu{id = ?ID_ACCUMULATE, text = "Accumulate", type = check}, + #create_menu{id = ?ID_NO_OF_LINES, text = "Number of lines"}]}, + {"Trace", + [#create_menu{id = ?ID_TRACEMENU, text = "Trace selected processes"}, + #create_menu{id = ?ID_TRACE_NEW_MENU, text = "Trace new processes"}, + #create_menu{id = ?ID_TRACE_ALL_MENU, text = "Trace all processes"}]} + ], + observer_wx:create_menus(Parent, MenuEntries). + +create_popup_menu(ParentFrame) -> + MiniFrame = wxMiniFrame:new(ParentFrame, ?wxID_ANY, "Options", [{style, ?wxFRAME_FLOAT_ON_PARENT}]), + Panel = wxPanel:new(MiniFrame), + Sizer = wxBoxSizer:new(?wxVERTICAL), + TraceBtn = wxButton:new(Panel, ?ID_TRACEMENU, [{label, "Trace selected"}, + {style, ?wxNO_BORDER}]), + ProcBtn = wxButton:new(Panel, ?ID_PROC, [{label, "Process info"}, + {style, ?wxNO_BORDER}]), + KillBtn = wxButton:new(Panel, ?ID_KILL, [{label, "Kill process"}, + {style, ?wxNO_BORDER}]), + + wxButton:connect(TraceBtn, command_button_clicked), + wxButton:connect(ProcBtn, command_button_clicked), + wxButton:connect(KillBtn, command_button_clicked), + wxSizer:add(Sizer, TraceBtn, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(Sizer, ProcBtn, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(Sizer, KillBtn, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(Panel, Sizer), + wxSizer:setSizeHints(Sizer, MiniFrame), + MiniFrame. + +create_list_box(Panel, Holder, Count) -> + Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL, + ListCtrl = wxListCtrl:new(Panel, [{style, Style}, + {onGetItemText, + fun(_, Item, Col) -> get_row(Holder, Item, Col) end}, + {onGetItemAttr, + fun(_, Item) -> get_attr(Holder, Item) end} + ]), + Li = wxListItem:new(), + AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(ListCtrl, Col, Li), + wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize), + Col + 1 + end, + ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE, 120}, + {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 200}, + {"Time", ?wxLIST_FORMAT_CENTRE, 50}, + {"Reds", ?wxLIST_FORMAT_CENTRE, 50}, + {"Memory", ?wxLIST_FORMAT_CENTRE, 50}, + {"MsgQ", ?wxLIST_FORMAT_LEFT, 50}, + {"Current Function", ?wxLIST_FORMAT_LEFT, 200}], + lists:foldl(AddListEntry, 0, ListItems), + wxListItem:destroy(Li), + + wxListCtrl:connect(ListCtrl, size, [{skip, true}]), + wxListCtrl:connect(ListCtrl, command_list_item_activated), + wxListCtrl:connect(ListCtrl, command_list_item_right_click), + wxListCtrl:connect(ListCtrl, command_list_col_click), + wxListCtrl:connect(ListCtrl, command_list_item_selected), + wxListCtrl:setItemCount(ListCtrl, Count), + ListCtrl. + +change_node(Node) -> + etop_server ! {config, {node, Node}}. + +get_node() -> + etop_server ! {get_opt, node, self()}, + receive + {node, Node} -> + Node + end. + +change_accum(Bool) -> + etop_server ! {config, {accumulate, Bool}}. + +change_lines(Int) when is_integer(Int) -> + etop_server ! {config, {lines, Int}}. + +get_lines() -> + etop_server ! {get_opt, lines, self()}, + receive + {lines, Lines} -> + Lines + end. + +change_intv(NewIntv) -> + etop_server ! {config, {interval, NewIntv}}. + +get_intv() -> + etop_server ! {get_opt, intv, self()}, + receive + {intv, Intv} -> + Intv + end. + +get_sort() -> + etop_server ! {get_opt, sort, self()}, + receive {sort, Sort} -> + Sort + end. + +refresh_grid(Holder, Dir) -> + etop_server ! {update, Holder, Dir}. + +change_sort(Col, Dir) -> + case get_sort() =:= map_sort_order(Col) of + true when Dir =:= incr-> + decr; + true when Dir =:= decr -> + incr; + false -> + change_sort(Col), + Dir + end. +change_sort(?COL_PID) -> + etop_server ! {config, {sort, pid}}; +change_sort(?COL_NAME) -> + etop_server ! {config, {sort, name}}; +change_sort(?COL_TIME) -> + etop_server ! {config, {sort, runtime}}; +change_sort(?COL_REDS) -> + etop_server ! {config, {sort, reductions}}; +change_sort(?COL_MEM) -> + etop_server ! {config, {sort, memory}}; +change_sort(?COL_MSG) -> + etop_server ! {config, {sort, msg_q}}; +change_sort(?COL_FUN) -> + etop_server ! {config, {sort, cf}}. + + +map_sort_order(Col) -> + case Col of + ?COL_PID -> pid; + ?COL_NAME -> name; + ?COL_TIME -> runtime; + ?COL_REDS -> reductions; + ?COL_MEM -> memory; + ?COL_MSG -> msg_q; + ?COL_FUN -> cf + end. + +to_str(Value) when is_atom(Value) -> + atom_to_list(Value); +to_str({A, B}) -> + lists:concat([A, ":", B]); +to_str({M,F,A}) -> + lists:concat([M, ":", F, "/", A]); +to_str(Value) when is_list(Value) -> + case lists:all(fun(X) -> is_integer(X) end, Value) of + true -> Value; + false -> + lists:foldl(fun(X, Acc) -> + to_str(X) ++ " " ++ Acc end, + "", Value) + end; +to_str(Port) when is_port(Port) -> + erlang:port_to_list(Port); +to_str(Pid) when is_pid(Pid) -> + pid_to_list(Pid); +to_str(No) when is_integer(No) -> + integer_to_list(No); +to_str(ShouldNotGetHere) -> + erlang:error({?MODULE, to_str, ShouldNotGetHere}). + +clear_all(Grid) -> + lists:foreach(fun(I) -> + wxListCtrl:setItemState(Grid, I, 0, ?wxLIST_STATE_SELECTED), + wxListCtrl:setItemState(Grid, I, 0, ?wxLIST_STATE_FOCUSED) + end, + lists:seq(0, wxListCtrl:getItemCount(Grid))). + +set_selected_items(Grid, Holder, Pids) -> + Count = wxListCtrl:getItemCount(Grid), + set_selected_items(Grid, Holder, 0, Pids, Count, []). + +set_selected_items(_, _, Index, Pids, Max, Acc) when Pids =:= []; Index =:= Max -> + Acc; +set_selected_items(Grid, Holder, Index, Pids, Max, Acc) -> + {ok, Pid} = get_row(Holder, Index, pid), + case lists:member(Pid, Pids) of + true -> + wxListCtrl:setItemState(Grid, Index, + ?wxLIST_STATE_SELECTED, + ?wxLIST_STATE_SELECTED), + set_selected_items(Grid, Holder, Index+1, lists:delete(Pid, Pids), Max, [Pid | Acc]); + false -> + set_selected_items(Grid, Holder, Index+1, Pids, Max, Acc) + end. + +get_selected_items(Grid) -> + get_selected_items(Grid, -1, []). + +get_selected_items(Grid, Index, ItemAcc) -> + Item = wxListCtrl:getNextItem(Grid, Index, + [{geometry, ?wxLIST_NEXT_ALL}, {state, ?wxLIST_STATE_SELECTED}]), + case Item of + -1 -> + lists:reverse(ItemAcc); + _ -> + get_selected_items(Grid, Item, [Item+1 | ItemAcc]) + end. + +interval_dialog(ParentFrame, ParentPid, Enabled, Value, Min, Max) -> + Dialog = wxDialog:new(ParentFrame, ?wxID_ANY, "Update Interval", + [{style, ?wxDEFAULT_DIALOG_STYLE bor + ?wxRESIZE_BORDER}]), + Panel = wxPanel:new(Dialog), + Check = wxCheckBox:new(Panel, ?wxID_ANY, "Periodical refresh"), + wxCheckBox:setValue(Check, Enabled), + Style = ?wxSL_HORIZONTAL bor ?wxSL_AUTOTICKS bor ?wxSL_LABELS, + Slider = wxSlider:new(Panel, ?wxID_ANY, Value, Min, Max, + [{style, Style}, {size, {200, -1}}]), + wxWindow:enable(Slider, [{enable, Enabled}]), + InnerSizer = wxBoxSizer:new(?wxVERTICAL), + + OKBtn = wxButton:new(Dialog, ?wxID_OK), + CancelBtn = wxButton:new(Dialog, ?wxID_CANCEL), + Buttons = wxStdDialogButtonSizer:new(), + wxStdDialogButtonSizer:addButton(Buttons, OKBtn), + wxStdDialogButtonSizer:addButton(Buttons, CancelBtn), + + Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 2}], + wxSizer:add(InnerSizer, Check, Flags), + wxSizer:add(InnerSizer, Slider, Flags), + wxPanel:setSizer(Panel, InnerSizer), + TopSizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(TopSizer, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + wxSizer:add(TopSizer, Buttons, [{flag, ?wxEXPAND}]), + wxStdDialogButtonSizer:realize(Buttons), + wxWindow:setSizerAndFit(Dialog, TopSizer), + wxSizer:setSizeHints(TopSizer, Dialog), + wxCheckBox:connect(Check, command_checkbox_clicked, + [{callback, fun(#wx{event=#wxCommand{type = command_checkbox_clicked, + commandInt=Enable0}},_) -> + Enable = Enable0 > 0, + wxWindow:enable(Slider, [{enable, Enable}]) + end}]), + + wxButton:connect(OKBtn, command_button_clicked, + [{callback, + fun(#wx{id = ?wxID_OK, + event = #wxCommand{type = command_button_clicked}},_) -> + ParentPid ! {wxCheckBox:isChecked(Check), wxSlider:getValue(Slider)}, + wxDialog:destroy(Dialog) + end}]), + wxButton:connect(CancelBtn, command_button_clicked, + [{callback, + fun(#wx{id = ?wxID_CANCEL, + event = #wxCommand{type = command_button_clicked}},_) -> + ParentPid ! cancel, + wxDialog:destroy(Dialog) + end}]), + + wxDialog:show(Dialog). + + + +line_dialog(ParentFrame, OldLines, Holder, Dir) -> + Dialog = wxDialog:new(ParentFrame, ?wxID_ANY, "Enter number of lines", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), + Panel = wxPanel:new(Dialog), + TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY, [{value, OldLines}]), + InnerSz = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(InnerSz, TxtCtrl, [{flag, ?wxEXPAND bor ?wxALL}]), + wxPanel:setSizer(Panel, InnerSz), + + OKBtn = wxButton:new(Dialog, ?wxID_OK), + CancelBtn = wxButton:new(Dialog, ?wxID_CANCEL), + + Buttons = wxStdDialogButtonSizer:new(), + wxStdDialogButtonSizer:addButton(Buttons, OKBtn), + wxStdDialogButtonSizer:addButton(Buttons, CancelBtn), + + TopSz = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(TopSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + wxSizer:add(TopSz, Buttons, [{flag, ?wxEXPAND}]), + wxStdDialogButtonSizer:realize(Buttons), + wxWindow:setSizerAndFit(Dialog, TopSz), + wxSizer:setSizeHints(TopSz, Dialog), + + wxButton:connect(OKBtn, command_button_clicked, + [{callback, + fun(#wx{id = ?wxID_OK, + event = #wxCommand{type = command_button_clicked}},_) -> + try + NewLines = list_to_integer(wxTextCtrl:getValue(TxtCtrl)), + case NewLines >= 0 of + true -> + change_lines(NewLines), + refresh_grid(Holder, Dir); + false -> + observer_wx:create_txt_dialog(Panel, + "Invalid input", + "Error", + ?wxICON_ERROR) + end + catch error:badarg -> + observer_wx:create_txt_dialog(Panel, + "Invalid input", + "Error", + ?wxICON_ERROR) + end, + wxDialog:destroy(Dialog) + end}]), + wxButton:connect(CancelBtn, command_button_clicked, + [{callback, + fun(#wx{id = ?wxID_CANCEL, + event = #wxCommand{type = command_button_clicked}},_) -> + wxDialog:destroy(Dialog) + end}]), + wxDialog:show(Dialog). + + +create_attrs() -> + Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + Text = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT), + #attrs{even = wx:typeCast(wx:null(), wxListItemAttr), + odd = wxListItemAttr:new(Text, {240,240,255}, Font), + searched = wxListItemAttr:new(Text, {235,215,90}, Font) + }. + +dump_to_file(Parent, FileName, Holder) -> + case file:open(FileName, [write]) of + {ok, Fd} -> + Holder ! {dump, Fd}; + {error, Reason} -> + FailMsg = file:format_error(Reason), + MD = wxMessageDialog:new(Parent, FailMsg), + wxDialog:showModal(MD), + wxDialog:destroy(MD) + end. + +start_procinfo(Node, Pid, Frame, Opened) -> + case lists:member(Pid, Opened) of + true -> + Opened; + false -> + observer_procinfo:start(Node, Pid, Frame, self()), + [Pid | Opened] + end. + + + +get_selected_pids(Holder, Indices) -> + Ref = erlang:monitor(process, Holder), + Holder ! {get_pids, self(), Indices}, + receive + {'DOWN', Ref, _, _, _} -> []; + {Holder, Res} -> + erlang:demonitor(Ref), + Res + end. + +get_row(Holder, Row, Column) -> + Ref = erlang:monitor(process, Holder), + Holder ! {get_row, self(), Row, Column}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Holder, Res} -> + erlang:demonitor(Ref), + Res + end. + + +get_attr(Holder, Item) -> + Ref = erlang:monitor(process, Holder), + Holder ! {get_attr, self(), Item}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Holder, Res} -> + erlang:demonitor(Ref), + Res + end. + + +%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_info({'DOWN', Ref, _, _, _}, + #pro_wx_state{etop_monitor = EtopMon} = State) when Ref =:= EtopMon -> + io:format("Etop died~n"), + {stop, shutdown, State}; + +handle_info({'DOWN', Ref, _, _, _}, + #pro_wx_state{holder_monitor = HMonitor} = State) when Ref =:= HMonitor -> + io:format("Holder died~n"), + {stop, shutdown, State}; + +handle_info({holder_updated, Count}, #pro_wx_state{grid = Grid, + holder = Holder, + selected_pids = Pids} = State) -> + Pids2 = wx:batch(fun() -> + wxListCtrl:setItemCount(Grid, Count), + clear_all(Grid), + Pids2 = set_selected_items(Grid, Holder, Pids), + wxListCtrl:refreshItems(Grid, 0, Count), + Pids2 + end), + {noreply, State#pro_wx_state{selected_pids = Pids2}}; + +handle_info(refresh_interval, #pro_wx_state{sort_dir = Dir, + holder = Holder} = State) -> + refresh_grid(Holder, Dir), + {noreply, State}; + +handle_info({tracemenu_closed, TraceOpts, MatchSpecs}, State) -> + {noreply, State#pro_wx_state{tracemenu_opened = false, + trace_options = TraceOpts, + match_specs = MatchSpecs}}; + +handle_info({procinfo_menu_closed, Pid}, + #pro_wx_state{procinfo_menu_pids = Opened} = State) -> + NewPids = lists:delete(Pid, Opened), + {noreply, State#pro_wx_state{procinfo_menu_pids = NewPids}}; + +handle_info({active, Node}, #pro_wx_state{holder = Holder, + sort_dir = Dir, + refr_timer = Timer0, + parent = Parent} = State) -> + create_pro_menu(Parent), + change_node(Node), + refresh_grid(Holder, Dir), + Timer = case Timer0 of + true -> + Intv = get_intv(), + {ok, Ref} = timer:send_interval(Intv, refresh_interval), + Ref; + false -> + false + end, + {noreply, State#pro_wx_state{refr_timer = Timer}}; + +handle_info(not_active, #pro_wx_state{refr_timer = Timer0} = State) -> + Timer = case Timer0 of + false -> false; + true -> true; + Timer0 -> + timer:cancel(Timer0), + true + end, + {noreply, State#pro_wx_state{refr_timer=Timer, + selected_pids = [], + last_selected = undefined}}; + +handle_info({node, Node}, #pro_wx_state{holder = Holder, sort_dir = Dir} = State) -> + change_node(Node), + refresh_grid(Holder, Dir), + {noreply, State#pro_wx_state{selected_pids = [], + last_selected = undefined}}; + +handle_info(Info, State) -> + io:format("~p, ~p, Handled unexpected info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +terminate(Reason, #pro_wx_state{holder = Holder}) -> + io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), + Holder ! stop, + etop:stop(), + ok. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + + +handle_call(Msg, _From, State) -> + io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + + +handle_cast(Msg, State) -> + io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), + {noreply, State}. + +%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{id = ?ID_DUMP_TO_FILE}, + #pro_wx_state{panel = Panel, + holder = Holder} = State) -> + FD = wxFileDialog:new(Panel, + [{style,?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), + case wxFileDialog:showModal(FD) of + ?wxID_OK -> + Path = wxFileDialog:getPath(FD), + wxDialog:destroy(FD), + dump_to_file(Panel, Path, Holder); + _ -> + wxDialog:destroy(FD) + end, + {noreply, State}; + +handle_event(#wx{id = ?ID_ACCUMULATE, event = #wxCommand{type = command_menu_selected, + commandInt = CmdInt}}, + #pro_wx_state{holder = Holder, + sort_dir = Dir} = State) when CmdInt =:= 1-> + change_accum(true), + refresh_grid(Holder, Dir), + {noreply, State}; + +handle_event(#wx{id = ?ID_ACCUMULATE, event = #wxCommand{type = command_menu_selected, + commandInt = CmdInt}}, + #pro_wx_state{holder = Holder, + sort_dir = Dir} = State) when CmdInt =:= 0 -> + change_accum(false), + refresh_grid(Holder, Dir), + {noreply, State}; + +handle_event(#wx{id = ?ID_NO_OF_LINES, event = #wxCommand{type = command_menu_selected}}, + #pro_wx_state{panel = Panel, + sort_dir = Dir, + holder = Holder} = State) -> + OldLines = integer_to_list(get_lines()), + line_dialog(Panel, OldLines, Holder, Dir), + {noreply, State}; + +handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_selected}}, + #pro_wx_state{sort_dir = Dir, holder = Holder} = State) -> + refresh_grid(Holder, Dir), + {noreply, State}; + +handle_event(#wx{id = ?ID_REFRESH_INTERVAL}, + #pro_wx_state{panel = Panel, refr_timer=Timer0} = State) -> + Intv0 = get_intv() div 1000, + interval_dialog(Panel, self(), Timer0 /= false, Intv0, 1, 5*60), + receive + cancel -> + {noreply, State}; + {true, Intv} -> + case Timer0 of + false -> ok; + _ -> timer:cancel(Timer0) + end, + change_intv(Intv), + {ok, Timer} = timer:send_interval(Intv * 1000, refresh_interval), + {noreply, State#pro_wx_state{refr_timer=Timer}}; + {false, _} -> + case Timer0 of + false -> ok; + _ -> timer:cancel(Timer0) + end, + {noreply, State#pro_wx_state{refr_timer=false}} + end; + +handle_event(#wx{id = ?ID_KILL}, #pro_wx_state{popup_menu = Pop, + selected_pids = Pids, + last_selected = ToKill} = State) -> + + wxWindow:show(Pop, [{show, false}]), + exit(ToKill, kill), + Pids2 = lists:delete(ToKill, Pids), + {noreply, State#pro_wx_state{selected_pids = Pids2, last_selected = undefined}}; + + +handle_event(#wx{id = ?ID_PROC}, + #pro_wx_state{panel = Panel, + popup_menu = Pop, + last_selected = Pid, + procinfo_menu_pids = Opened} = State) -> + wxWindow:show(Pop, [{show, false}]), + Node = get_node(), + Opened2 = start_procinfo(Node, Pid, Panel, Opened), + {noreply, State#pro_wx_state{procinfo_menu_pids = Opened2}}; + +handle_event(#wx{id = ?ID_TRACEMENU}, + #pro_wx_state{popup_menu = Pop, + trace_options = Options, + match_specs = MatchSpecs, + selected_pids = Pids, + tracemenu_opened = false, + panel = Panel} = State) -> + wxWindow:show(Pop, [{show, false}]), + case Pids of + [] -> + observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION), + {noreply, State}; + Pids -> + Node = get_node(), + observer_trace_wx:start(Node, + Pids, + Options, + MatchSpecs, + Panel, + self()), + {noreply, State#pro_wx_state{tracemenu_opened = true}} + end; + +handle_event(#wx{id = ?ID_TRACE_ALL_MENU, event = #wxCommand{type = command_menu_selected}}, + #pro_wx_state{trace_options = Options, + match_specs = MatchSpecs, + tracemenu_opened = false, + panel = Panel} = State) -> + Node = get_node(), + observer_trace_wx:start(Node, + all, + Options, + MatchSpecs, + Panel, + self()), + {noreply, State#pro_wx_state{tracemenu_opened = true}}; + + +handle_event(#wx{id = ?ID_TRACE_NEW_MENU, event = #wxCommand{type = command_menu_selected}}, + #pro_wx_state{trace_options = Options, + match_specs = MatchSpecs, + tracemenu_opened = false, + panel = Panel} = State) -> + Node = get_node(), + observer_trace_wx:start(Node, + new, + Options, + MatchSpecs, + Panel, + self()), + {noreply, State#pro_wx_state{tracemenu_opened = true}}; + +handle_event(#wx{event=#wxSize{size={W,_}}}, + #pro_wx_state{grid=Grid} = State) -> + wx:batch(fun() -> + Cols = wxListCtrl:getColumnCount(Grid), + Last = lists:foldl(fun(I, Last) -> + Last - wxListCtrl:getColumnWidth(Grid, I) + end, W-2, lists:seq(0, Cols - 2)), + Size = max(200, Last), + wxListCtrl:setColumnWidth(Grid, Cols-1, Size) + end), + {noreply, State}; + +handle_event(#wx{event = #wxList{type = command_list_item_right_click, + itemIndex = Row}}, + #pro_wx_state{popup_menu = Popup, + holder = Holder} = State) -> + + case get_row(Holder, Row, pid) of + {error, undefined} -> + wxWindow:show(Popup, [{show, false}]), + undefined; + {ok, _} -> + wxWindow:move(Popup, wx_misc:getMousePosition()), + wxWindow:show(Popup) + end, + {noreply, State}; + +handle_event(#wx{event = #wxList{type = command_list_item_selected, + itemIndex = Row}}, + #pro_wx_state{grid = Grid, + popup_menu = Pop, + holder = Holder} = State) -> + + NewPid = case get_row(Holder, Row, pid) of + {error, undefined} -> + undefined; + {ok, P} when is_pid(P) -> + P + end, + wxWindow:show(Pop, [{show, false}]), + Pids = get_selected_pids(Holder, get_selected_items(Grid)), + {noreply, State#pro_wx_state{selected_pids = Pids, + last_selected = NewPid}}; + +handle_event(#wx{event = #wxList{type = command_list_col_click, col = Col}}, + #pro_wx_state{sort_dir = OldDir, + holder = Holder} = State) -> + NewDir = change_sort(Col, OldDir), + refresh_grid(Holder, NewDir), + {noreply, State#pro_wx_state{sort_dir = NewDir}}; + +handle_event(#wx{event = #wxList{type = command_list_item_activated}}, + #pro_wx_state{panel = Panel, + procinfo_menu_pids= Opened, + last_selected = Pid} = State) when Pid =/= undefined -> + Node = get_node(), + Opened2 = start_procinfo(Node, Pid, Panel, Opened), + {noreply, State#pro_wx_state{procinfo_menu_pids = Opened2}}; + +handle_event(Event, State) -> + io:format("~p~p, handle event ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + + + + + + + + + + + + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +init_table_holder(Parent, Info, Attrs) -> + table_holder(#holder{parent = Parent, + info = Info, + attrs = Attrs}). + + +table_holder(#holder{parent = Parent, + info = Info, + attrs = Attrs} = S0) -> + receive + {get_row, From, Row, Col} -> + get_row(From, Row, Col, Info#etop_info.procinfo), + table_holder(S0); + {get_attr, From, Row} -> + get_attr(From, Row, Attrs), + table_holder(S0); + {get_pids, From, Indices} -> + get_pids(From, Indices, Info#etop_info.procinfo), + table_holder(S0); + {update, #etop_info{procinfo = ProcInfo} = NewInfo} -> + Parent ! {holder_updated, length(ProcInfo)}, + table_holder(S0#holder{info = NewInfo}); + {dump, Fd} -> + etop_server ! {observer_dump, Fd, Info}, + table_holder(S0); + stop -> + ok; + What -> + io:format("Table holder got ~p~n",[What]), + table_holder(S0) + end. + + +get_procinfo_data(?COL_PID, #etop_proc_info{pid = Pid}) -> + Pid; +get_procinfo_data(?COL_NAME, #etop_proc_info{name = Name}) -> + Name; +get_procinfo_data(?COL_MEM, #etop_proc_info{mem = Mem}) -> + Mem; +get_procinfo_data(?COL_TIME, #etop_proc_info{runtime = RT}) -> + RT; +get_procinfo_data(?COL_REDS, #etop_proc_info{reds = Reds}) -> + Reds; +get_procinfo_data(?COL_FUN, #etop_proc_info{cf = CF}) -> + CF; +get_procinfo_data(?COL_MSG, #etop_proc_info{mq = MQ}) -> + MQ. + +get_pids(From, Indices, ProcInfo) -> + From ! {self(), + [X#etop_proc_info.pid || X <- + [lists:nth(I, ProcInfo) || I <- Indices]]}. + +get_row(From, Row, pid, Info) -> + Pid = case Row =:= -1 of + true -> + {error, undefined}; + false -> + {ok, get_procinfo_data(?COL_PID, lists:nth(Row+1, Info))} + end, + From ! {self(), Pid}; +get_row(From, Row, Col, Info) -> + Data = case Row+1 > length(Info) of + true -> + null; + false -> + ProcInfo = lists:nth(Row+1, Info), + get_procinfo_data(Col, ProcInfo) + end, + From ! {self(), io_lib:format("~p", [Data])}. + +get_attr(From, Row, Attrs) -> + Attribute = case Row rem 2 =:= 0 of + true -> + Attrs#attrs.even; + false -> + Attrs#attrs.odd + end, + From ! {self(), Attribute}. diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl new file mode 100644 index 0000000000..b01b91c0b2 --- /dev/null +++ b/lib/observer/src/observer_procinfo.erl @@ -0,0 +1,511 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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_procinfo). + +-behaviour(wx_object). + +-export([start/4]). + +-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, + handle_call/3, handle_info/2]). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-define(CLOSE, 601). +-define(REFRESH, 602). +-define(SELECT_ALL, 603). +-define(ID_NOTEBOOK, 604). + +-record(procinfo_state, {parent, + frame, + node, + pid, + module, + procinfo_stc, + modinfo_stc, + modcode_stc, + checklistbox, + current_view, % proc_info::atom | module_info::atom | module_code::atom + itemlist = [{backtrace, false}, + {binary, false}, + {catchlevel, false}, + {current_function, false}, + {dictionary, false}, + {error_handler, true}, + {garbage_collection, true}, + {group_leader, true}, + {heap_size, true}, + {initial_call, false}, + {last_calls, false}, + {links, true}, + {memory, false}, + {message_queue_len, true}, + {messages, false}, + {monitored_by, false}, + {monitors, false}, + {priority, true}, + {reductions, false}, + {registered_name, false}, + {sequential_trace_token, false}, + {stack_size, false}, + {status, false}, + {suspending, false}, + {total_heap_size, false}, + {trace, false}, + {trap_exit,true}] + }). + + + + +start(Node, Process, ParentFrame, Parent) -> + wx_object:start(?MODULE, [Node, Process, ParentFrame, Parent], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([Node, Process, ParentFrame, Parent]) -> + try + State = #procinfo_state{parent = Parent, + node = Node, + pid = Process, + current_view = proc_info + }, + ItemList = State#procinfo_state.itemlist, + Name = case observer_wx:try_rpc(Node, erlang, process_info, [Process, registered_name]) of + [] -> + undefined; + {registered_name, M} -> + M + end, + {initial_call, {Module, _, _}} = observer_wx:try_rpc(Node, erlang, process_info, [Process, initial_call]), + {Frame, ProcStc, CheckListBox, ModInfoStc, ModCodeStc} = setup(ParentFrame, Node, Process, ItemList, Module, Name), + {Frame, State#procinfo_state{frame = Frame, + module = Module, + procinfo_stc = ProcStc, + modinfo_stc = ModInfoStc, + modcode_stc = ModCodeStc, + checklistbox = CheckListBox}} + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(ParentFrame, Node), + {stop, badrpc, #procinfo_state{parent = Parent, + pid = Process}} + end. + + +setup(ParentFrame, Node, Pid, ItemList, Module, Name) -> + Title = case Name of + undefined -> + atom_to_list(Node) ++ ":" ++ atom_to_list(Module); + Name -> + atom_to_list(Node) ++ ":" ++ atom_to_list(Name) + end, + Frame = wxFrame:new(ParentFrame, ?wxID_ANY, Title, + [{style, ?wxDEFAULT_FRAME_STYLE}, + {size, {900,900}}]), + Panel = wxPanel:new(Frame, []), + MainSz = wxBoxSizer:new(?wxHORIZONTAL), + Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), + wxNotebook:connect(Notebook, command_notebook_page_changed), + {CodePanel, CheckListBox, CodeStc} = create_procinfo_page(Notebook, Node, Pid, ItemList), + {ModInfoPanel, ModInfoStc} = create_page(Notebook, Node, Module, module_info), + {ModCodePanel, ModCodeStc} = create_page(Notebook, Node, Module, module_code), + wxNotebook:addPage(Notebook, CodePanel, "Process information", []), + wxNotebook:addPage(Notebook, ModInfoPanel, "Module information", []), + wxNotebook:addPage(Notebook, ModCodePanel, "Module code", []), + MenuBar = wxMenuBar:new(), + create_menus(MenuBar), + wxWindow:setSizer(Panel, MainSz), + wxSizer:add(MainSz, Notebook, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxFrame:setMenuBar(Frame, MenuBar), + wxFrame:show(Frame), + wxFrame:connect(Frame, close_window), + wxMenu:connect(Frame, command_menu_selected), + {Frame, CodeStc, CheckListBox, ModInfoStc, ModCodeStc}. + + +create_procinfo_page(Notebook, Node, Pid, ItemList) -> + Panel = wxPanel:new(Notebook), + MainSz = wxBoxSizer:new(?wxHORIZONTAL), + CheckSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "View"}]), + BtnSz = wxBoxSizer:new(?wxHORIZONTAL), + + Stc = create_styled_txtctrl(Panel, proc_info), + Txt = get_formatted_values(Node, Pid, ItemList), + set_text(Stc, Txt, text), + Choices = [atom_to_list(Tag) || {Tag, _} <- ItemList], + CheckListBox = wxCheckListBox:new(Panel, ?wxID_ANY, [{choices, Choices}, + {style, ?wxLB_EXTENDED}, + {style, ?wxLB_SORT}, + {style, ?wxLB_NEEDED_SB}]), + check_boxes(CheckListBox, ItemList), + wxCheckListBox:connect(CheckListBox, command_checklistbox_toggled), + + SelAllBtn = wxButton:new(Panel, ?SELECT_ALL, [{label, "Select all"}]), + DeSelAllBtn = wxButton:new(Panel, ?SELECT_ALL, [{label, "Deselect all"}]), + + wxButton:connect(SelAllBtn, command_button_clicked, [{userData, true}]), + wxButton:connect(DeSelAllBtn, command_button_clicked, [{userData, false}]), + wxWindow:setSizer(Panel, MainSz), + wxSizer:add(MainSz, Stc, [{proportion, 1}, {flag, ?wxEXPAND}]), + wxSizer:add(CheckSz, CheckListBox, [{proportion, 1}]), + wxSizer:add(BtnSz, SelAllBtn), + wxSizer:add(BtnSz, DeSelAllBtn), + wxSizer:add(CheckSz, BtnSz), + wxSizer:add(MainSz, CheckSz, [{flag, ?wxEXPAND}]), + {Panel, CheckListBox, Stc}. + +create_page(Notebook, Node, Module, What) -> + Panel = wxPanel:new(Notebook, []), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Stc = create_styled_txtctrl(Panel, What), + {Sort, Txt} = case What of + module_info -> + {text, get_formatted_modinfo(Node, Module)}; + module_code -> + case get_src_file(Node, Module) of + {ok, File} -> + {file, File}; + error-> + {text, "Error! Could not read sourcefile"} + end + end, + set_text(Stc, Txt, Sort), + wxWindow:setSizer(Panel, Sizer), + wxSizer:add(Sizer, Stc, [{flag, ?wxEXPAND}, {proportion, 1}]), + {Panel, Stc}. + +create_menus(MenuBar) -> + observer_wx:create_menu( + [ + {"File", [#create_menu{id = ?CLOSE, text = "Close"}]}, + {"View", [#create_menu{id = ?REFRESH, text = "Refresh"}]} + ], + MenuBar). + +check_boxes(CheckListBox, Bool, all) -> + lists:foreach(fun(Index) -> + wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]) + end, + lists:seq(0, wxControlWithItems:getCount(CheckListBox))). +check_boxes(CheckListBox, ItemList) -> + lists:foldl(fun({_, Bool}, Index) -> + wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]), + Index+1 + end, + 0, ItemList). + +create_styled_txtctrl(Parent, View) -> + FixedFont = wxFont:new(11, ?wxFONTFAMILY_TELETYPE, ?wxFONTSTYLE_NORMAL, ?wxNORMAL,[]), + Stc = wxStyledTextCtrl:new(Parent), + wxStyledTextCtrl:styleClearAll(Stc), + wxStyledTextCtrl:styleSetFont(Stc, ?wxSTC_STYLE_DEFAULT, FixedFont), + wxStyledTextCtrl:setLexer(Stc, ?wxSTC_LEX_ERLANG), + wxStyledTextCtrl:setMarginType(Stc, 2, ?wxSTC_MARGIN_NUMBER), + W = wxStyledTextCtrl:textWidth(Stc, ?wxSTC_STYLE_LINENUMBER, "9"), + wxStyledTextCtrl:setMarginWidth(Stc, 2, W*3), + + wxStyledTextCtrl:setSelectionMode(Stc, ?wxSTC_SEL_LINES), + wxStyledTextCtrl:setUseHorizontalScrollBar(Stc, false), + + Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}}, + {?wxSTC_ERLANG_COMMENT, {160,53,35}}, + {?wxSTC_ERLANG_VARIABLE, {150,100,40}}, + {?wxSTC_ERLANG_NUMBER, {5,5,100}}, + {?wxSTC_ERLANG_KEYWORD, {130,40,172}}, + {?wxSTC_ERLANG_STRING, {170,45,132}}, + {?wxSTC_ERLANG_OPERATOR, {30,0,0}}, + {?wxSTC_ERLANG_ATOM, {0,0,0}}, + {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}}, + {?wxSTC_ERLANG_CHARACTER,{236,155,172}}, + {?wxSTC_ERLANG_MACRO, {40,144,170}}, + {?wxSTC_ERLANG_RECORD, {40,100,20}}, + {?wxSTC_ERLANG_SEPARATOR,{0,0,0}}, + {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}], + SetStyle = fun({Style, Color}) -> + wxStyledTextCtrl:styleSetFont(Stc, Style, FixedFont), + wxStyledTextCtrl:styleSetForeground(Stc, Style, Color) + end, + [SetStyle(Style) || Style <- Styles], + + KeyWords = case View of + proc_info -> + get_procinfo_keywords(); + module_info -> + get_modinfo_keywords(); + module_code -> + get_erl_keywords() + end, + wxStyledTextCtrl:setKeyWords(Stc, 0, KeyWords), + Stc. + +get_erl_keywords() -> + L = ["after","begin","case","try","cond","catch","andalso","orelse", + "end","fun","if","let","of","query","receive","when","bnot","not", + "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"], + lists:flatten([K ++ " "|| K <- L] ++ [0]). +get_procinfo_keywords() -> + L = ["backtrace","binary","catchlevel","current_function","dictionary", + "error_handler","garbage_collection","group_leader", "heap_size", + "initial_call","last_calls","links","memory","message_queue_len", + "messages","monitored_by","monitors", "priority","reductions", + "registered_name", "sequential_trace_token","stack_size","status", + "suspending", "total_heap_size","trace","trap_exit"], + lists:flatten([K ++ " "|| K <- L] ++ [0]). +get_modinfo_keywords() -> + L = ["exports", "imports", "attributes", "compile"], + lists:flatten([K ++ " "|| K <- L] ++ [0]). + +get_formatted_values(Node, Process, ItemList) -> + TagList = [Tag || {Tag, Bool} <- ItemList, Bool =:= true], + Values = observer_wx:try_rpc(Node, erlang, process_info, [Process, TagList]), + lists:flatten(format_value(Values, [])). + +format_value([], Acc) -> + lists:reverse(Acc); +format_value([{backtrace, Bin} | T], Acc) -> + format_value(T, [io_lib:format("{backtrace,~s}~n", [binary_to_list(Bin)]) | Acc]); +format_value([H|T], Acc) -> + format_value(T, [io_lib:format("~p~n", [H]) | Acc]). + +get_formatted_modinfo(Node, Module) -> + Info = observer_wx:try_rpc(Node, Module, module_info, []), + lists:flatten([io_lib:format("~p~n", [I]) || I <- Info]). +get_src_remote(Node, Module) -> + case observer_wx:try_rpc(Node, filename, find_src, [Module]) of + {error, _} -> + error; + {SrcFile, _} -> + case observer_wx:try_rpc(Node, file, read_file_info, [SrcFile ++ ".erl"]) of + {error, _} -> + error; + {ok, _} -> + {ok, SrcFile ++ ".erl"} + end + end. + +get_src_local(Module) -> + case filename:find_src(Module) of + {error, _} -> + error; + {SrcFile, _} -> + case file:read_file_info(SrcFile ++ ".erl") of + {error, _} -> + error; + {ok, _} -> + {ok, SrcFile ++ ".erl"} + end + end. + +get_src_file(Node, Module) -> + case get_src_remote(Node, Module) of + {ok, SrcFile} -> + {ok, SrcFile}; + error -> + get_src_local(Module) + end. + + +set_text(Stc, Text, text) -> + wxStyledTextCtrl:setReadOnly(Stc, false), + wxStyledTextCtrl:setText(Stc, Text), + wxStyledTextCtrl:setReadOnly(Stc, true); +set_text(Stc, File, file) -> + wxStyledTextCtrl:setReadOnly(Stc, false), + wxStyledTextCtrl:loadFile(Stc, File), + wxStyledTextCtrl:setReadOnly(Stc, true). + +update_procinfo_page(Stc, Node, Process, ItemList) -> + Txt = get_formatted_values(Node, Process, ItemList), + set_text(Stc, Txt, text). +update_modinfo_page(Stc, Node, Module) -> + Txt = get_formatted_modinfo(Node, Module), + set_text(Stc, Txt, text). +update_modcode_page(Stc, Node, Module) -> + case get_src_file(Node, Module) of + {ok, File} -> + set_text(Stc, File, file); + error -> + set_text(Stc, "Error! Could not read sourcefile", text) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handle_event(#wx{event = #wxClose{type = close_window}}, + State) -> + {stop, shutdown, State}; + +handle_event(#wx{id = ?CLOSE, + event = #wxCommand{type = command_menu_selected}}, + State) -> + {stop, shutdown, State}; + +handle_event(#wx{id = ?REFRESH, + event = #wxCommand{type = command_menu_selected}}, + #procinfo_state{current_view = Current, + frame = Frame, + node = Node, + pid = Pid, + procinfo_stc = Stc, + itemlist = ItemList} = State) when Current =:= proc_info -> + try + update_procinfo_page(Stc, Node, Pid, ItemList), + {noreply, State} + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Frame, Node), + {stop, badrpc, State} + end; + +handle_event(#wx{id = ?REFRESH, + event = #wxCommand{type = command_menu_selected}}, + #procinfo_state{current_view = Current, + frame = Frame, + node = Node, + modinfo_stc = Stc, + module = Module} = State) when Current =:= module_info -> + try + update_modinfo_page(Stc, Node, Module), + {noreply, State} + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Frame, Node), + {stop, badrpc, State} + end; + +handle_event(#wx{id = ?REFRESH, + event = #wxCommand{type = command_menu_selected}}, + #procinfo_state{current_view = Current, + modcode_stc = Stc, + frame = Frame, + node = Node, + module = Module} = State) when Current =:= module_code -> + try + update_modcode_page(Stc, Node, Module), + {noreply, State} + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Frame, Node), + {stop, badrpc, State} + end; + + +handle_event(#wx{obj = Notebook, id = ?ID_NOTEBOOK, + event = #wxNotebook{type = command_notebook_page_changed}}, + #procinfo_state{frame = Frame, + module = Module, + procinfo_stc = ProcStc, + modcode_stc = CodeStc, + modinfo_stc = ModInfoStc, + node = Node, + pid = Pid, + itemlist = ItemList} = State) -> + try + Current = case observer_wx:check_page_title(Notebook) of + "Process information" -> + update_procinfo_page(ProcStc, Node, Pid, ItemList), + proc_info; + "Module information" -> + update_modinfo_page(ModInfoStc, Node, Module), + module_info; + "Module code" -> + update_modcode_page(CodeStc, Node, Module), + module_code + end, + {noreply, State#procinfo_state{current_view = Current}} + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Frame, Node), + {stop, badrpc, State} + end; + +handle_event(#wx{event = #wxCommand{type = command_checklistbox_toggled, + commandInt = Index}, + obj = CheckListbox}, + #procinfo_state{frame = Frame, + node = Node, + pid = Process, + procinfo_stc = Stc, + itemlist = ItemList} = State) -> + try + {Tag, _} = lists:nth(Index+1, ItemList), + ItemList2 = case wxCheckListBox:isChecked(CheckListbox, Index) of + true -> + lists:keyreplace(Tag, 1, ItemList, {Tag, true}); + false -> + lists:keyreplace(Tag, 1, ItemList, {Tag, false}) + end, + Txt = get_formatted_values(Node, Process, ItemList2), + set_text(Stc, Txt, text), + {noreply, State#procinfo_state{itemlist = ItemList2}} + + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Frame, Node), + {stop, badrpc, State} + end; + +handle_event(#wx{id = ?SELECT_ALL, + event = #wxCommand{type = command_button_clicked}, + userData = Bool}, + #procinfo_state{frame = Frame, + node = Node, + pid = Process, + itemlist = ItemList, + procinfo_stc = Stc, + checklistbox = CheckListBox} = State) -> + try + check_boxes(CheckListBox, Bool, all), + ItemList2 = lists:keymap(fun(_) -> + Bool + end, + 2, ItemList), + Txt = get_formatted_values(Node, Process, ItemList2), + set_text(Stc, Txt, text), + {noreply, State#procinfo_state{itemlist = ItemList2}} + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Frame, Node), + {stop, badrpc, State} + end; + +handle_event(Event, State) -> + io:format("~p: ~p, Handle event: ~p~n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_info(Info, State) -> + io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +handle_call(Call, _From, State) -> + io:format("~p ~p: Got call ~p~n",[?MODULE, ?LINE, Call]), + {reply, ok, State}. + +handle_cast(Cast, State) -> + io:format("~p ~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]), + {noreply, State}. + +terminate(Reason, #procinfo_state{parent = Parent, + pid = Pid, + frame = Frame}) -> + io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), + Parent ! {procinfo_menu_closed, Pid}, + case Frame of + undefined -> + ok; + _ -> + wxFrame:destroy(Frame) + end, + ok. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. diff --git a/lib/observer/src/observer_sys.erl b/lib/observer/src/observer_sys.erl new file mode 100644 index 0000000000..8db7bb0e46 --- /dev/null +++ b/lib/observer/src/observer_sys.erl @@ -0,0 +1,131 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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_sys). + +-export([node_info/0, node_name_str/1, no_procs_str/1, no_cpu_str/1, + no_cpu_available_str/1, no_cpu_online_str/1, tot_alloc_str/1, + proc_used_str/1, proc_alloc_str/1, atom_used_str/1, atom_alloc_str/1, + binary_alloc_str/1, code_alloc_str/1, ets_alloc_str/1]). + +-record(node_info, {node_name, + no_procs, % number of processes + no_cpu, % number of logical cpu's + no_cpu_available, %number of logical cpu's available + no_cpu_online, % number of logical cpu's online + tot_alloc, % total memory allocated + proc_used, % memory used by processes + proc_alloc, % memory alloc by processes, + atom_used, % memory used by atoms + atom_alloc, % memory allocated by atoms + binary_alloc, % memory allocated for binaries + code_alloc, % memory allocated by code + ets_alloc}).% memory allocated by ets + + +%%%%%%%%%%%%%%%%%%%%%%% functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +node_info() -> + #node_info{node_name = node_name(), + no_procs = process_count(), + no_cpu = logical_cpus(), + no_cpu_available = logical_cpus_available(), + no_cpu_online = logical_cpus_online(), + tot_alloc = total_alloc(), + proc_used = processes_used(), + proc_alloc = processes_alloc(), + atom_used = atom_used(), + atom_alloc = atom_alloc(), + binary_alloc = binary_alloc(), + code_alloc = code_alloc(), + ets_alloc = ets_alloc() + }. + +node_name() -> + node(). + +process_count() -> + erlang:system_info(process_count). + +logical_cpus() -> + erlang:system_info(logical_processors). % detected number of logical cpus configured on system + +logical_cpus_available() -> % detected number of logical cpus available to erlang runtime system + erlang:system_info(logical_processors_available). + +logical_cpus_online() -> % detected number of logical cpus online on system + erlang:system_info(logical_processors_online). + +total_alloc() -> + erlang:memory(total). % total amount of memory currently allocated + +processes_used() -> % amount of memory currently used by the erlang processes + erlang:memory(processes_used). + +processes_alloc() -> % allocated by erlang processes + erlang:memory(processes). + +atom_used() -> % amount of memory used for atoms + erlang:memory(atom_used). + +atom_alloc() -> % amount allocated for atoms + erlang:memory(atom). + +binary_alloc() -> % amount allocated for binaries + erlang:memory(binary). + +code_alloc() -> % amount allocated for code + erlang:memory(code). + +ets_alloc() -> % amount allocated for ets tables + erlang:memory(ets). + + +%% formatting functions, from the record-value to string +node_name_str(#node_info{node_name = ToReturn}) -> + erlang:atom_to_list(ToReturn). +no_procs_str(#node_info{no_procs = ToReturn}) -> + erlang:integer_to_list(ToReturn). +no_cpu_str(#node_info{no_cpu = ToReturn}) -> + erlang:integer_to_list(ToReturn). +no_cpu_available_str(#node_info{no_cpu_available = ToReturn}) -> + erlang:integer_to_list(ToReturn). +no_cpu_online_str(#node_info{no_cpu_online = ToReturn}) -> + erlang:integer_to_list(ToReturn). +tot_alloc_str(#node_info{tot_alloc = ToReturn}) -> + erlang:integer_to_list(ToReturn). + +proc_used_str(#node_info{proc_used = ToReturn}) -> + erlang:integer_to_list(ToReturn). + +proc_alloc_str(#node_info{proc_alloc = ToReturn}) -> + erlang:integer_to_list(ToReturn). + +atom_used_str(#node_info{atom_used = ToReturn}) -> + erlang:integer_to_list(ToReturn). + +atom_alloc_str(#node_info{atom_alloc = ToReturn}) -> + erlang:integer_to_list(ToReturn). + +binary_alloc_str(#node_info{binary_alloc = ToReturn}) -> + erlang:integer_to_list(ToReturn). + +code_alloc_str(#node_info{code_alloc = ToReturn}) -> + erlang:integer_to_list(ToReturn). + +ets_alloc_str(#node_info{ets_alloc = ToReturn}) -> + erlang:integer_to_list(ToReturn). diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl new file mode 100644 index 0000000000..5116891e91 --- /dev/null +++ b/lib/observer/src/observer_sys_wx.erl @@ -0,0 +1,313 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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_sys_wx). + +-behaviour(wx_object). + +-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_cast/2]). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-define(ID_REFRESH, 101). +-define(ID_REFRESH_INTERVAL, 102). + +%% Records +-record(sys_wx_state, + {parent, + panel, + menubar, + parent_notebook, + no_procs, + no_cpu, + no_cpu_available, + no_cpu_online, + tot_alloc, + proc_used, + proc_alloc, + atom_used, + atom_alloc, + binary_alloc, + code_alloc, + ets_alloc, + node_label, + node, + refr_timer = false, + refr_intv = 30}). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([Notebook, Parent]) -> + SysPanel = wxPanel:new(Notebook, []), + + %% Setup sizers + SysSizer = wxBoxSizer:new(?wxVERTICAL), + + SysNodeSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, SysPanel, [{label, "Node:"}]), + + SysLoadSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, SysPanel, [{label, "Load:"}]), + SysLeftLoadSizer = wxBoxSizer:new(?wxVERTICAL), + SysMidLoadSizer = wxBoxSizer:new(?wxHORIZONTAL), + SysRightLoadSizer = wxBoxSizer:new(?wxVERTICAL), + + SysMemSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, SysPanel, [{label, "Memory:"}]), + SysLeftMemSizer = wxBoxSizer:new(?wxVERTICAL), + SysMidMemSizer = wxBoxSizer:new(?wxHORIZONTAL), + SysRightMemSizer = wxBoxSizer:new(?wxVERTICAL), + + wxSizer:add(SysSizer, SysNodeSizer, [{flag, ?wxEXPAND}]), + wxSizer:add(SysSizer, SysLoadSizer, [{flag, ?wxEXPAND}]), + wxSizer:add(SysSizer, SysMemSizer, [{flag, ?wxEXPAND}]), + wxSizer:add(SysLoadSizer, SysLeftLoadSizer), + wxSizer:add(SysLoadSizer, SysMidLoadSizer), + wxSizer:add(SysLoadSizer, SysRightLoadSizer), + + wxSizer:add(SysMemSizer, SysLeftMemSizer), + wxSizer:add(SysMemSizer, SysMidMemSizer), + wxSizer:add(SysMemSizer, SysRightMemSizer), + + wxSizer:addSpacer(SysMidLoadSizer, 90), + wxSizer:addSpacer(SysMidMemSizer, 70), + + %% Create labels + NodeInfo = get_syspage_info(node()), + NodeLabel = create_info_label(SysPanel, SysNodeSizer, observer_sys:node_name_str(NodeInfo)), + + create_info_label(SysPanel, SysLeftLoadSizer, "logical CPU's:"), + create_info_label(SysPanel, SysLeftLoadSizer, "logical CPU's available:"), + create_info_label(SysPanel, SysLeftLoadSizer, "logical CPU's online:"), + create_info_label(SysPanel, SysLeftLoadSizer, "existing processes:"), + NoCpuTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_cpu_str(NodeInfo)), + NoCpuAvTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_cpu_available_str(NodeInfo)), + NoCpuOnTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_cpu_online_str(NodeInfo)), + NoProcsTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_procs_str(NodeInfo)), + + create_info_label(SysPanel, SysLeftMemSizer, "total allocated:"), + create_info_label(SysPanel, SysLeftMemSizer, "used by processes:"), + create_info_label(SysPanel, SysLeftMemSizer, "allocated for processes:"), + create_info_label(SysPanel, SysLeftMemSizer, "used by atoms:"), + create_info_label(SysPanel, SysLeftMemSizer, "allocated for atoms:"), + create_info_label(SysPanel, SysLeftMemSizer, "allocated for binaries:"), + create_info_label(SysPanel, SysLeftMemSizer, "allocated for code"), + create_info_label(SysPanel, SysLeftMemSizer, "allocated for ETS:"), + TotAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:tot_alloc_str(NodeInfo)), + ProcUsedTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:proc_used_str(NodeInfo)), + ProcAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:proc_alloc_str(NodeInfo)), + AtomUsedTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:atom_used_str(NodeInfo)), + AtomAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:atom_alloc_str(NodeInfo)), + BinaryAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:binary_alloc_str(NodeInfo)), + CodeAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:code_alloc_str(NodeInfo)), + EtsAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:ets_alloc_str(NodeInfo)), + + %% Create StateRecord + SysPanelState = #sys_wx_state{ + parent = Parent, + panel = SysPanel, + parent_notebook = Notebook, + node_label = NodeLabel, + no_procs = NoProcsTxt, + no_cpu = NoCpuTxt, + no_cpu_available = NoCpuAvTxt, + no_cpu_online= NoCpuOnTxt, + tot_alloc = TotAllocTxt, + proc_used = ProcUsedTxt, + proc_alloc = ProcAllocTxt, + atom_used = AtomUsedTxt, + atom_alloc = AtomAllocTxt, + binary_alloc = BinaryAllocTxt, + code_alloc = CodeAllocTxt, + ets_alloc = EtsAllocTxt, + node = node()}, + + wxPanel:setSizer(SysPanel, SysSizer), + {SysPanel, SysPanelState}. + +get_syspage_info(Node) -> + observer_wx:try_rpc(Node, observer_sys, node_info, []). + +create_info_label(Panel, Sizer, Msg) -> + WxText = wxStaticText:new(Panel, ?wxID_ANY, Msg), + wxSizer:add(Sizer, WxText), + WxText. + +create_sys_menu(Parent) -> + View = {"View", [#create_menu{id = ?ID_REFRESH, text = "Refresh"}, + #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh interval"}]}, + observer_wx:create_menus(Parent, [View]). + +update_syspage(#sys_wx_state{node = Node} = State) -> + Info = get_syspage_info(Node), + update_info_label(node_label, Info, State#sys_wx_state.node_label), + update_info_label(no_procs, Info, State#sys_wx_state.no_procs), + update_info_label(no_cpu, Info, State#sys_wx_state.no_cpu), + update_info_label(no_cpu_available, Info, State#sys_wx_state.no_cpu_available), + update_info_label(no_cpu_online, Info, State#sys_wx_state.no_cpu_online), + update_info_label(tot_alloc, Info, State#sys_wx_state.tot_alloc), + update_info_label(proc_used, Info, State#sys_wx_state.proc_used), + update_info_label(proc_alloc, Info, State#sys_wx_state.proc_alloc), + update_info_label(atom_used, Info, State#sys_wx_state.atom_used), + update_info_label(atom_alloc, Info, State#sys_wx_state.atom_alloc), + update_info_label(binary_alloc, Info, State#sys_wx_state.binary_alloc), + update_info_label(code_alloc, Info, State#sys_wx_state.code_alloc), + update_info_label(ets_alloc, Info, State#sys_wx_state.ets_alloc). + +update_info_label(node_label, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:node_name_str(Info)); +update_info_label(no_procs, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:no_procs_str(Info)); +update_info_label(no_cpu, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:no_cpu_str(Info)); +update_info_label(no_cpu_available, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:no_cpu_available_str(Info)); +update_info_label(no_cpu_online, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:no_cpu_online_str(Info)); +update_info_label(tot_alloc, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:tot_alloc_str(Info)); +update_info_label(proc_used, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:proc_used_str(Info)); +update_info_label(proc_alloc, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:proc_alloc_str(Info)); +update_info_label(atom_used, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:atom_used_str(Info)); +update_info_label(atom_alloc, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:atom_alloc_str(Info)); +update_info_label(binary_alloc, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:binary_alloc_str(Info)); +update_info_label(code_alloc, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:code_alloc_str(Info)); +update_info_label(ets_alloc, Info, WxTxt) -> + wxStaticText:setLabel(WxTxt, observer_sys:ets_alloc_str(Info)). + + +%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_info(refresh_interval, #sys_wx_state{panel = Panel, + node = Node} = State) -> + try + update_syspage(State) + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Panel, Node) + end, + {noreply, State}; + +handle_info({node, Node}, #sys_wx_state{panel = Panel} = State) -> + UpdState = State#sys_wx_state{node = Node}, + try + update_syspage(UpdState), + {noreply, UpdState} + + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Panel, Node), + {noreply, State} + end; + +handle_info({active, Node}, + #sys_wx_state{parent = Parent, + panel = Panel, + refr_timer = Timer0, + refr_intv = Intv} = State) -> + UpdState = State#sys_wx_state{node = Node}, + create_sys_menu(Parent), + try + update_syspage(UpdState), + Timer = case Timer0 of + true -> + {ok, Ref} = timer:send_interval(Intv*1000, refresh_interval), + Ref; + false -> + false + end, + {noreply, UpdState#sys_wx_state{refr_timer = Timer}} + + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Panel, Node), + {noreply, State} + end; + + +handle_info(not_active, #sys_wx_state{refr_timer = Timer0} = State) -> + Timer = case Timer0 of + false -> false; + true -> true; + Timer0 -> + timer:cancel(Timer0), + true + end, + {noreply, State#sys_wx_state{refr_timer = Timer}}; + +handle_info(Info, State) -> + io:format("~p, ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +terminate(Reason, _State) -> + io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), + ok. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +handle_call(Msg, _From, State) -> + io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast(Msg, State) -> + io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]), + {noreply, State}. + +handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_selected}}, + #sys_wx_state{node = Node, panel = Panel} = State) -> + try + update_syspage(State) + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Panel, Node) + end, + {noreply, State}; + +handle_event(#wx{id = ?ID_REFRESH_INTERVAL, + event = #wxCommand{type = command_menu_selected}}, + #sys_wx_state{refr_timer = Timer0, + refr_intv = Intv0, + parent_notebook = Notebook} = State) -> + Parent = observer_tv_wx:get_wx_parent(Notebook), + case observer_tv_wx:interval_dialog(Parent, Timer0 /= false, Intv0, 1, 5*60) of + cancel -> + {noreply, State}; + {true, Intv} -> + case Timer0 of + false -> ok; + _ -> timer:cancel(Timer0) + end, + {ok, Timer} = timer:send_interval(Intv * 1000, refresh_interval), + {noreply, State#sys_wx_state{refr_timer=Timer, refr_intv=Intv}}; + {false, _} -> + case Timer0 of + false -> ok; + _ -> timer:cancel(Timer0) + end, + {noreply, State#sys_wx_state{refr_timer=false}} + end; + +handle_event(Event, State) -> + io:format("handle event ~p\n", [Event]), + {noreply, State}. diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl new file mode 100644 index 0000000000..8e08b57b92 --- /dev/null +++ b/lib/observer/src/observer_trace_wx.erl @@ -0,0 +1,487 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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_trace_wx). + +-export([start/6]). +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_cast/2]). + +-behaviour(wx_object). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-define(OPTIONS, 301). +-define(SAVE_BUFFER, 302). +-define(CLOSE, 303). +-define(CLEAR, 304). +-define(SAVE_TRACEOPTS, 305). +-define(LOAD_TRACEOPTS, 306). + + +-record(state, { + parent, + frame, + text_ctrl, + trace_options, + toggle_button, + node, + traceoptions_open, + traced_procs, + traced_funcs = dict:new(), % Key =:= Module::atom, Value =:= [ #traced_func ] + match_specs = []}). % [ #match_spec{} ] + + +start(Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid) -> + wx_object:start(?MODULE, [Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid], []). + +init([Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid]) -> + State = + wx:batch(fun() -> + create_window(ParentFrame, TraceOpts) + end), + + Frame = State#state.frame, + TraceOpts2 = State#state.trace_options, + TracedFuncs = State#state.traced_funcs, + + wx_object:start(observer_traceoptions_wx, + [Frame, self(), Node, TraceOpts2, TracedFuncs, MatchSpecs], + []), + + {Frame, State#state{parent = ParentPid, + node = Node, + traced_procs = TracedProcs, + match_specs = MatchSpecs, + traceoptions_open = true}}. + + +create_window(ParentFrame, TraceOpts) -> + %% Create the window + Frame = wxFrame:new(ParentFrame, ?wxID_ANY, "Tracer", + [{style, ?wxDEFAULT_FRAME_STYLE}, + {size, {900, 900}}]), + wxFrame:connect(Frame, close_window,[{skip,true}]), + Panel = wxPanel:new(Frame, []), + Sizer = wxBoxSizer:new(?wxVERTICAL), + + %% Menues + MenuBar = wxMenuBar:new(), + create_menues(MenuBar), + wxFrame:setMenuBar(Frame, MenuBar), + wxMenu:connect(Frame, command_menu_selected, []), + + %% Buttons + ToggleButton = wxToggleButton:new(Panel, ?wxID_ANY, "Start Trace", []), + wxSizer:add(Sizer, ToggleButton, [{flag, ?wxALL}, + {border, 5}]), + wxMenu:connect(ToggleButton, command_togglebutton_clicked, []), + + TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY, + [{style,?wxTE_READONLY bor + ?wxTE_MULTILINE}, + {size, {400, 300}}]), + + wxSizer:add(Sizer, TxtCtrl, [{proportion, 1}, + {flag, ?wxEXPAND}]), + + %% Display window + wxWindow:setSizer(Panel, Sizer), + wxFrame:show(Frame), + #state{frame = Frame, + text_ctrl = TxtCtrl, + toggle_button = ToggleButton, + trace_options = TraceOpts#trace_options{main_window = false}}. + +create_menues(MenuBar) -> + observer_wx:create_menu( + [ + {"File", [ + #create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"}, + #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}, + separator, + #create_menu{id = ?SAVE_BUFFER, text = "Save buffer"}, + separator, + #create_menu{id = ?CLOSE, text = "Close"} + ]}, + {"View", [ + #create_menu{id = ?CLEAR, text = "Clear buffer"} + ]}, + {"Options", [ + #create_menu{id = ?OPTIONS, text = "Trace options"} + ]} + ], + MenuBar). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %Main window + +handle_event(#wx{id = ?CLOSE, event = #wxCommand{type = command_menu_selected}}, + #state{parent = Parent, + trace_options = TraceOpts, + match_specs = MatchSpecs} = State) -> + Parent ! {tracemenu_closed, TraceOpts, MatchSpecs}, + {stop, shutdown, State}; + +handle_event(#wx{id = ?OPTIONS, event = #wxCommand{type = command_menu_selected}}, + #state{frame = Frame, trace_options = TraceOpts, + traced_funcs = TracedFuncs, + node = Node, + match_specs = MatchSpecs, + traceoptions_open = false} = State) -> + + wx_object:start(observer_traceoptions_wx, + [Frame, self(), Node, TraceOpts, TracedFuncs, MatchSpecs], + []), + + {noreply, State#state{traceoptions_open = true}}; + +handle_event(#wx{id = ?CLEAR, event = #wxCommand{type = command_menu_selected}}, + #state{text_ctrl = TxtCtrl} = State) -> + wxTextCtrl:clear(TxtCtrl), + {noreply, State}; + +handle_event(#wx{id = ?SAVE_BUFFER, event = #wxCommand{type = command_menu_selected}}, + #state{frame = Frame, text_ctrl = TxtCtrl} = State) -> + Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), + case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Path = wxFileDialog:getPath(Dialog), + wxDialog:destroy(Dialog), + case filelib:is_file(Path) of + true -> + observer_wx:create_txt_dialog(Frame, "File already exists: " ++ Path ++ "\n", + "Error", ?wxICON_ERROR); + false -> + wxTextCtrl:saveFile(TxtCtrl, [{file, Path}]) + end; + _ -> + wxDialog:destroy(Dialog), + ok + end, + {noreply, State}; + +handle_event(#wx{id = ?SAVE_TRACEOPTS, + event = #wxCommand{type = command_menu_selected}}, + #state{frame = Frame, + trace_options = TraceOpts, + match_specs = MatchSpecs} = State) -> + Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), + case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Path = wxFileDialog:getPath(Dialog), + write_file(Frame, Path, TraceOpts, MatchSpecs); + _ -> + ok + end, + wxDialog:destroy(Dialog), + {noreply, State}; + +handle_event(#wx{id = ?LOAD_TRACEOPTS, + event = #wxCommand{type = command_menu_selected}}, + #state{frame = Frame} = State) -> + Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_FILE_MUST_EXIST}]), + State2 = case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Path = wxFileDialog:getPath(Dialog), + read_settings(Path, State); + _ -> + State + end, + wxDialog:destroy(Dialog), + {noreply, State2}; + + +handle_event(#wx{event = #wxClose{type = close_window}}, + #state{parent = Parent, + trace_options = TraceOpts, + match_specs = MatchSpecs} = State) -> + Parent ! {tracemenu_closed, TraceOpts, MatchSpecs}, + {stop, shutdown, State}; + +handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, + #state{node = Node, + traced_procs = TracedProcs, + traced_funcs = TracedDict, + trace_options = TraceOpts, + text_ctrl = TextCtrl, + toggle_button = ToggleBtn} = State) -> + + start_trace(Node, TracedProcs, TracedDict, TraceOpts), + wxTextCtrl:appendText(TextCtrl, "Start Trace:\n"), + wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), + {noreply, State}; + +handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, %%Stop tracing + #state{text_ctrl = TxtCtrl, + toggle_button = ToggleBtn} = State) -> + dbg:stop_clear(), + wxTextCtrl:appendText(TxtCtrl, "Stop Trace.\n"), + wxToggleButton:setLabel(ToggleBtn, "Start Trace"), + {noreply, State}; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{event = What}, State) -> + io:format("~p~p: Unhandled event: ~p ~n", [?MODULE, self(), What]), + {noreply, State}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_info({updated_traceopts, + TraceOpts, + MatchSpecs, + TracedFuncs}, State) -> + {noreply, State#state{trace_options = TraceOpts, + match_specs = MatchSpecs, + traced_funcs = TracedFuncs, + traceoptions_open = false}}; + +handle_info(traceopts_closed, State) -> + {noreply, State#state{traceoptions_open = false}}; + +handle_info(Tuple, #state{text_ctrl = TxtCtrl} = State) when is_tuple(Tuple) -> + Text = textformat(Tuple), + wxTextCtrl:appendText(TxtCtrl, lists:flatten(Text)), + {noreply, State}; + +handle_info(Any, State) -> + io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]), + {noreply, State}. + + +terminate(Reason, #state{node = Node, + frame = Frame}) -> + try + case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of + undefined -> fine; + Pid -> exit(Pid, kill) + end, + io:format("~p terminating tracemenu. Reason: ~p~n", [?MODULE, Reason]), + wxFrame:destroy(Frame), + ok + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(Frame, Node), + wxFrame:destroy(Frame) + end. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +handle_call(Msg, _From, State) -> + io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast(Msg, State) -> + io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), + {noreply, State}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_trace(Node, TracedProcs, TracedDict, + #trace_options{send = Send, treceive = Receive, functions = Functions, + events = Events, on_1st_spawn = On1Spawn, + on_all_spawn = AllSpawn, on_1st_link = On1Link, + on_all_link = AllLink}) -> + dbg:stop_clear(), + MyPid = self(), + HandlerFun = fun(NewMsg, _) -> + MyPid ! NewMsg + end, + dbg:tracer(process, {HandlerFun, []}), + + case Node =:= node() of + true -> + ok; + false -> + dbg:n(Node) + end, + + Recs = [{Send, send}, + {Receive, 'receive'}, + {Functions, call}, + {Events, procs}, + {On1Spawn, set_on_first_spawn}, + {AllSpawn, set_on_spawn}, + {On1Link, set_on_first_link}, + {AllLink, set_on_link}], + Flags = [Assoc || {true, Assoc} <- Recs], + + case TracedProcs of + all -> + dbg:p(all, Flags); + new -> + dbg:p(new, Flags); + _Pids -> + lists:foreach(fun(Pid) -> dbg:p(Pid, Flags) end, TracedProcs) + end, + + case Functions of + true -> + trace_functions(TracedDict); + false -> + ok + end. + +textformat({died, Pid}) -> + io_lib:format("~w Process died.~n",[Pid]); +textformat({shell_died, Old, New}) -> + io_lib:format("~w Shell Process died. Restarted as ~w~n~n",[Old,New]); +textformat({trace, From, 'receive', Msg}) -> + io_lib:format("~w: rec ~s~n", [From, + tuple_space(Msg)]); +textformat({trace, From, send, Msg, To}) -> + io_lib:format("~w: ! To: ~w Msg: ~s~n", [From, + To, + tuple_space(Msg)]); +textformat({trace, From, call, Func}) -> + io_lib:format("~w: call ~s~n",[From, ffunc(Func)]); +textformat({trace, From, spawn, Data}) -> + io_lib:format("~w: spawn ~p~n", [From, Data]); +textformat({trace, From, link, Data}) -> + io_lib:format("~w: link ~p~n", [From, Data]); +textformat({trace, From, unlink, Data}) -> + io_lib:format("~w: U-lnk ~p~n", [From, Data]); + +textformat({trace, From, Op, Data}) -> + io_lib:format("~w: ~w ~p~n", [From, Op, Data]); + +textformat({print, Format, Args}) -> + io_lib:format(Format, Args); +textformat(Other) -> + io_lib:format("~p~n",[Other]). + + +tuple_space(X) when is_tuple(X) -> print(size(X), X, "}"); +tuple_space(X) -> io_lib:format("~p",[X]). + + +ffunc({M,F, Argl}) -> + io_lib:format("~w:~w(~s)", [M, F, fargs(Argl)]); +ffunc(X) -> tuple_space(X). + +fargs([]) -> []; +fargs([A]) -> tuple_space(A); %% last arg +fargs([A|Args]) -> [tuple_space(A),", "|fargs(Args)]. + +print(0 , _X, Buff) -> ["{"|Buff]; +print(1 , X, Buff) -> + Str = tuple_space(element(1, X)), + ["{",Str|Buff]; +print(Num, X, Buff) -> + Str = tuple_space(element(Num, X)), + print(Num-1, X, [", ",Str|Buff]). + +trace_functions(TracedDict) -> + Trace = fun(KeyAtom, RecordList, acc_in) -> + + lists:foreach(fun(#traced_func{func_name = Function, + arity = Arity, + match_spec = #match_spec{term_ms = MS}}) -> + dbg:tpl({KeyAtom, Function, Arity}, MS) + end, + RecordList), + acc_in + end, + dict:fold(Trace, acc_in, TracedDict). + + +write_file(Frame, Filename, #trace_options{send = Send, + treceive = Receive, + functions = Functions, + events = Events, + on_1st_spawn = On1stSpawn, + on_all_spawn = OnAllSpawn, + on_1st_link = On1stLink, + on_all_link = OnAllLink}, + MatchSpecs) -> + + FormattedMatchSpecs = lists:flatten(lists:foldl( + fun(#match_spec{alias = A, term_ms = T, fun2ms = F}, Acc) -> + [io_lib:format("{alias, ~p, term_ms, ~p, fun2ms, ~p}.\n", + [A, T, F]) | Acc] + end, [], MatchSpecs)), + + Binary = + list_to_binary("%%%\n%%% This file is generated by Observer\n" + "%%%\n%%% DO NOT EDIT!\n%%%\n" + "{send, " ++ atom_to_list(Send) ++ "}.\n" + "{treceive, " ++ atom_to_list(Receive) ++ "}.\n" + "{functions, " ++ atom_to_list(Functions) ++ "}.\n" + "{events, " ++ atom_to_list(Events) ++ "}.\n" + "{on_1st_spawn, " ++ atom_to_list(On1stSpawn) ++ "}.\n" + "{on_all_spawn, " ++ atom_to_list(OnAllSpawn) ++ "}.\n" + "{on_1st_link, " ++ atom_to_list(On1stLink) ++ "}.\n" + "{on_all_link, " ++ atom_to_list(OnAllLink) ++ "}.\n" + ++ FormattedMatchSpecs), + + case file:write_file(Filename, Binary) of + ok -> + success; + {error, Reason} -> + FailMsg = file:format_error(Reason), + observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR) + end. + + +read_settings(Filename, #state{frame = Frame} = State) -> + case file:consult(Filename) of + {ok, Terms} -> + {TraceOpts, MatchSpecs} = parse_settings(Terms, {#trace_options{}, []}), + State#state{trace_options = TraceOpts, match_specs = MatchSpecs}; + {error, _} -> + observer_wx:create_txt_dialog(Frame, "Could not load settings", "Error", ?wxICON_ERROR), + State + end. + + +parse_settings([], {TraceOpts, MatchSpecs}) -> + {TraceOpts, MatchSpecs}; +parse_settings([{send, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{send = Bool}, MS}); +parse_settings([{treceive, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{treceive = Bool}, MS}); +parse_settings([{functions, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{functions = Bool}, MS}); +parse_settings([{events, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{events = Bool}, MS}); +parse_settings([{on_1st_spawn, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{on_1st_spawn = Bool}, MS}); +parse_settings([{on_all_spawn, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{on_all_spawn = Bool}, MS}); +parse_settings([{on_1st_link, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{on_1st_link = Bool}, MS}); +parse_settings([{on_all_link, Bool} | T], {Opts, MS}) -> + parse_settings(T, {Opts#trace_options{on_all_link = Bool}, MS}); +parse_settings([{alias, A, term_ms, TermMS, fun2ms, F} | T], {Opts, MatchSpecs}) -> + Alias = case A of + undefined -> A; + _ -> lists:flatten(io_lib:format("~s", [A])) + end, + Fun2MS = case F of + undefined -> F; + _ -> lists:flatten(io_lib:format("~s", [F])) + end, + parse_settings(T, {Opts, [#match_spec{alias = Alias, + term_ms = TermMS, + str_ms = lists:flatten(io_lib:format("~p", [TermMS])), + fun2ms = Fun2MS} + | MatchSpecs]}). diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl new file mode 100644 index 0000000000..7244efdc50 --- /dev/null +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -0,0 +1,1066 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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_traceoptions_wx). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-export([start/6]). +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_cast/2]). + +-behaviour(wx_object). + +-record(traceopts_state, { + parent, + frame, + tree, + boxes, + functionpage_listbox, + matchpage_styled_txtctrl, + matchpage_listbox, + popup_open = false, + module_popup_dialog, + module_popup_checklistbox, + matchspec_popup_dialog, + matchspec_popup_listbox, + matchspec_popup_styled_txtctrl, + match_specs, % [ #match_spec{} ] + checked_funcs = [], + traced_funcs, % Key =:= Module::atom, Value =:= [ #traced_func{} ] + trace_options}). + + +-record(boxes, {send, 'receive', functions, events, + on_spawn, on_link, all_spawn, all_link}). + +-define(TRACEOPTS_FRAME, 501). + +-define(MATCHPAGE_ADDFUN, 502). +-define(MATCHPAGE_ADDMS, 503). +-define(MATCHPAGE_ADDMS_ALIAS, 504). +-define(MATCHPAGE_LISTBOX, 505). + +-define(MATCH_POPUP_DIALOG, 506). + +-define(MODULEPOPUP_SELECT, 507). +-define(MODULEPOPUP_SELALL, 508). +-define(MODULEPOPUP_CHECKLISTBOX, 509). +-define(MODULEPOPUP_TXTCTRL, 510). +-define(MODULEPOPUP_DIALOG, 511). + +-define(FUNCTIONPAGE_LISTBOX, 512). +-define(FUNCTIONPAGE_TXTCTRL, 513). + + +start(ParentFrame, ParentPid, Node, TraceOpts, TracedFuncs, MatchSpecs) -> + wx_object:start(?MODULE, [ParentFrame, ParentPid, Node, TraceOpts, + TracedFuncs, MatchSpecs], []). + +init([ParentFrame, ParentPid, Node, TraceOpts, TracedFuncs, MatchSpecs]) -> + try + {Frame, Tree, Boxes, ModuleListBox, MatchTxtCtrl, MatchListBox} + = setup(ParentFrame, Node, TraceOpts, TracedFuncs, MatchSpecs), + + {Frame, + #traceopts_state{parent = ParentPid, + frame = Frame, + tree = Tree, + functionpage_listbox = ModuleListBox, + matchpage_styled_txtctrl = MatchTxtCtrl, + matchpage_listbox = MatchListBox, + boxes = Boxes, + match_specs = MatchSpecs, + traced_funcs = TracedFuncs, + trace_options = TraceOpts}} + + catch error:{badrpc, _} -> + observer_wx:return_to_localnode(ParentFrame, Node), + {stop, badrpc, #traceopts_state{}} + end. + + +setup(ParentFrame, Node, TraceOpts, TracedFuncs, MatchSpecs) -> + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Setup main window + + Frame = wxFrame:new(ParentFrame, ?TRACEOPTS_FRAME, "Trace options", + [{style, ?wxRESIZE_BORDER bor ?wxCLOSE_BOX}, + {size, {400, 500}}]), + Panel = wxPanel:new(Frame, []), + MainSz = wxBoxSizer:new(?wxVERTICAL), + Notebook = wxNotebook:new(Panel, ?wxID_ANY), + Modules = get_modules(Node), + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Setup tracing page + + OptPanel = wxPanel:new(Notebook), + OptMainSz = wxBoxSizer:new(?wxVERTICAL), + TopSz = wxBoxSizer:new(?wxHORIZONTAL), + TopLeftSz = wxStaticBoxSizer:new(?wxVERTICAL, OptPanel, + [{label, "Tracing options"}]), + TopRightSz = wxStaticBoxSizer:new(?wxVERTICAL, OptPanel, + [{label, "Inheritance options:"}]), + + SendBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace send", []), + check_box(SendBox, TraceOpts#trace_options.send), + RecBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace receive", []), + check_box(RecBox, TraceOpts#trace_options.treceive), + FuncBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace functions", []), + check_box(FuncBox, TraceOpts#trace_options.functions), + EventBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace events", []), + check_box(EventBox, TraceOpts#trace_options.events), + + {SpawnBox, SpwnAllRadio, SpwnFirstRadio} = + optionpage_top_right(OptPanel, TopRightSz, [{flag, ?wxBOTTOM},{border, 5}], "spawn"), + {LinkBox, LinkAllRadio, LinkFirstRadio} = + optionpage_top_right(OptPanel, TopRightSz, [{flag, ?wxBOTTOM},{border, 5}], "link"), + SpawnBool = TraceOpts#trace_options.on_all_spawn or TraceOpts#trace_options.on_1st_spawn, + LinkBool = TraceOpts#trace_options.on_all_link or TraceOpts#trace_options.on_1st_link, + check_box(SpawnBox, SpawnBool), + check_box(LinkBox, LinkBool), + enable({SpawnBox, SpwnAllRadio, SpwnFirstRadio}), + enable({LinkBox, LinkAllRadio, LinkFirstRadio}), + wxRadioButton:setValue(SpwnAllRadio, TraceOpts#trace_options.on_all_spawn), + wxRadioButton:setValue(SpwnFirstRadio, TraceOpts#trace_options.on_1st_spawn), + wxRadioButton:setValue(LinkAllRadio, TraceOpts#trace_options.on_all_link), + wxRadioButton:setValue(LinkFirstRadio, TraceOpts#trace_options.on_1st_link), + + wxSizer:add(TopLeftSz, SendBox, []), + wxSizer:add(TopLeftSz, RecBox, []), + wxSizer:add(TopLeftSz, FuncBox, []), + wxSizer:add(TopLeftSz, EventBox, []), + wxSizer:add(TopLeftSz, 150, -1), + + wxSizer:add(TopSz, TopLeftSz, [{flag, ?wxEXPAND}]), + wxSizer:add(TopSz, TopRightSz,[{flag, ?wxEXPAND}]), + wxSizer:add(OptMainSz, TopSz, []), + wxWindow:setSizer(OptPanel, OptMainSz), + wxNotebook:addPage(Notebook, OptPanel, "Tracing"), + +%%%%%%%%%%%%%%%%%%%%%%%% Setup functions page + + FuncPanel = wxPanel:new(Notebook), + FuncMainSz = wxBoxSizer:new(?wxVERTICAL), + ModuleSz = wxStaticBoxSizer:new(?wxVERTICAL, FuncPanel, [{label, "Select module"}]), + TreeSz = wxStaticBoxSizer:new(?wxVERTICAL, FuncPanel, [{label, "Selected functions"}]), + + AllModules = atomlist_to_stringlist(Modules), + ModuleTxtCtrl = wxTextCtrl:new(FuncPanel, ?FUNCTIONPAGE_TXTCTRL), + ModuleListBox = wxListBox:new(FuncPanel, ?FUNCTIONPAGE_LISTBOX, [{choices, AllModules}, {style, ?wxLB_SINGLE}]), + TreeCtrl = wxTreeCtrl:new(FuncPanel), + wxTreeCtrl:addRoot(TreeCtrl, atom_to_list(Node)), + update_tree(TreeCtrl, TracedFuncs), + + wxTextCtrl:connect(ModuleTxtCtrl, command_text_updated, + [{userData, AllModules}]), + wxListBox:connect(ModuleListBox, command_listbox_doubleclicked), + wxTreeCtrl:connect(TreeCtrl, command_tree_item_activated), + + wxSizer:add(ModuleSz, ModuleTxtCtrl, [{flag, ?wxEXPAND}]), + wxSizer:add(ModuleSz, ModuleListBox, [{flag, ?wxEXPAND}]), + wxSizer:add(TreeSz, TreeCtrl, [{flag, ?wxEXPAND},{proportion, 1}]), + wxSizer:add(FuncMainSz, ModuleSz, [{flag, ?wxEXPAND}]), + wxSizer:add(FuncMainSz, TreeSz, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxWindow:setSizer(FuncPanel, FuncMainSz), + wxNotebook:addPage(Notebook, FuncPanel, "Functions"), + + +%%%%%%%%%%%%%%%%%%% Setup match specification page + + {MatchPanel, _, MatchTxtCtrl, MatchListBox} = create_matchspec_page(Notebook, MatchSpecs, matchpage), + wxNotebook:addPage(Notebook, MatchPanel, "Match Specs"), + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Setup Dialog + + wxSizer:add(MainSz, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), + OKBtn = wxButton:new(Panel, ?wxID_OK, []), + CancelBtn = wxButton:new(Panel, ?wxID_CANCEL, []), + DialogBtnSz = wxStdDialogButtonSizer:new(), + wxStdDialogButtonSizer:addButton(DialogBtnSz, OKBtn), + wxStdDialogButtonSizer:addButton(DialogBtnSz, CancelBtn), + wxStdDialogButtonSizer:realize(DialogBtnSz), + wxSizer:add(MainSz, DialogBtnSz), + wxWindow:setSizer(Panel, MainSz), + + Boxes = #boxes{send = SendBox, + 'receive' = RecBox, + functions = FuncBox, + events = EventBox, + on_spawn = #on_spawn{checkbox = SpawnBox, + all_spawn = SpwnAllRadio, + first_spawn = SpwnFirstRadio}, + all_spawn = SpwnAllRadio, + on_link = #on_link{checkbox = LinkBox, + all_link = LinkAllRadio, + first_link = LinkFirstRadio}, + all_link = LinkAllRadio}, + + + wxButton:connect(OKBtn, command_button_clicked, [{userData, trace_options}]), + wxButton:connect(CancelBtn, command_button_clicked, [{userData, trace_options}]), + wxFrame:connect(Frame, close_window, []), + wxFrame:show(Frame), + {Frame, TreeCtrl, Boxes, ModuleListBox, MatchTxtCtrl, MatchListBox}. + + +filter_listbox_data(Input, Data, ListBox) -> + FilteredData = [X || X <- Data, re:run(X, Input) =/= nomatch], + wxListBox:clear(ListBox), + wxListBox:appendStrings(ListBox, FilteredData), + FilteredData. + +get_modules(Node) -> + lists:sort([Module || {Module, _} <- observer_wx:try_rpc(Node, code, all_loaded, [])]). + +optionpage_top_right(Panel, TopRightSz, Options, Text) -> + Sizer = wxBoxSizer:new(?wxVERTICAL), + ChkBox = wxCheckBox:new(Panel, ?wxID_ANY, "Inherit on " ++ Text, []), + RadioSz = wxBoxSizer:new(?wxVERTICAL), + Radio1 = wxRadioButton:new(Panel, ?wxID_ANY, "All " ++ Text, [{style, ?wxRB_GROUP}]), + Radio2 = wxRadioButton:new(Panel, ?wxID_ANY, "First " ++ Text ++ " only", []), + wxSizer:add(Sizer, ChkBox, []), + wxSizer:add(RadioSz, Radio1, []), + wxSizer:add(RadioSz, Radio2, []), + wxSizer:add(Sizer, RadioSz, [{flag, ?wxLEFT},{border, 20}]), + wxSizer:add(TopRightSz, Sizer, Options), + wxCheckBox:connect(ChkBox, command_checkbox_clicked, [{userData, {ChkBox, Radio1, Radio2}}]), + {ChkBox, Radio1, Radio2}. + + +read_trace_boxes(ChkBoxes = #boxes{on_spawn = OnSpawn, on_link = OnLink}, Options) -> + {On1stSpawn2, OnAllSpawn2} = + case wxCheckBox:isChecked(OnSpawn#on_spawn.checkbox) of + true -> + OnAllSpawn = wxRadioButton:getValue(OnSpawn#on_spawn.all_spawn), + On1stSpawn = wxRadioButton:getValue(OnSpawn#on_spawn.first_spawn), + {On1stSpawn, OnAllSpawn}; + false -> + {false, false} + end, + {On1stLink2, OnAllLink2} = + case wxCheckBox:isChecked(OnLink#on_link.checkbox) of + true -> + OnAllLink = wxRadioButton:getValue(OnLink#on_link.all_link), + On1stLink = wxRadioButton:getValue(OnLink#on_link.first_link), + {On1stLink, OnAllLink}; + false -> + {false, false} + end, + Options#trace_options{send = wxCheckBox:isChecked(ChkBoxes#boxes.send), + treceive = wxCheckBox:isChecked(ChkBoxes#boxes.'receive'), + functions = wxCheckBox:isChecked(ChkBoxes#boxes.functions), + events = wxCheckBox:isChecked(ChkBoxes#boxes.events), + on_all_spawn = OnAllSpawn2, + on_1st_spawn = On1stSpawn2, + on_all_link = OnAllLink2, + on_1st_link = On1stLink2}. + + +create_styled_txtctrl(Parent) -> + FixedFont = wxFont:new(12, ?wxFONTFAMILY_TELETYPE, ?wxFONTSTYLE_NORMAL, ?wxNORMAL,[]), + Ed = wxStyledTextCtrl:new(Parent), + wxStyledTextCtrl:styleClearAll(Ed), + wxStyledTextCtrl:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont), + wxStyledTextCtrl:setLexer(Ed, ?wxSTC_LEX_ERLANG), + wxStyledTextCtrl:setMarginType(Ed, 1, ?wxSTC_MARGIN_NUMBER), + wxStyledTextCtrl:setSelectionMode(Ed, ?wxSTC_SEL_LINES), + wxStyledTextCtrl:setUseHorizontalScrollBar(Ed, false), + + Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}}, + {?wxSTC_ERLANG_COMMENT, {160,53,35}}, + {?wxSTC_ERLANG_VARIABLE, {150,100,40}}, + {?wxSTC_ERLANG_NUMBER, {5,5,100}}, + {?wxSTC_ERLANG_KEYWORD, {130,40,172}}, + {?wxSTC_ERLANG_STRING, {170,45,132}}, + {?wxSTC_ERLANG_OPERATOR, {30,0,0}}, + {?wxSTC_ERLANG_ATOM, {0,0,0}}, + {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}}, + {?wxSTC_ERLANG_CHARACTER,{236,155,172}}, + {?wxSTC_ERLANG_MACRO, {40,144,170}}, + {?wxSTC_ERLANG_RECORD, {40,100,20}}, + {?wxSTC_ERLANG_SEPARATOR,{0,0,0}}, + {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}], + SetStyle = fun({Style, Color}) -> + wxStyledTextCtrl:styleSetFont(Ed, Style, FixedFont), + wxStyledTextCtrl:styleSetForeground(Ed, Style, Color) + end, + [SetStyle(Style) || Style <- Styles], + wxStyledTextCtrl:setKeyWords(Ed, 0, keyWords()), + Ed. + + +keyWords() -> + L = ["after","begin","case","try","cond","catch","andalso","orelse", + "end","fun","if","let","of","query","receive","when","bnot","not", + "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"], + lists:flatten([K ++ " " || K <- L] ++ [0]). + + +enable({CheckBox, AllRadio, FirstRadio}) -> + case wxCheckBox:isChecked(CheckBox) of + false -> + wxWindow:disable(AllRadio), + wxWindow:disable(FirstRadio); + true -> + wxWindow:enable(AllRadio), + wxWindow:enable(FirstRadio) + end. + + +check_box(ChkBox, Bool) -> + case Bool of + true -> + wxCheckBox:set3StateValue(ChkBox, ?wxCHK_CHECKED); + false -> + ignore + end. + +parse_record_function_names(RecordList) -> + StrList = [atom_to_list(FName) ++ "/" ++ integer_to_list(Arity) + || #traced_func{func_name = FName, arity = Arity} <- RecordList], + parse_function_names(StrList, []). + +parse_function_names(Choices) -> + StrList = [atom_to_list(Name) ++ "/" ++ integer_to_list(Arity) || {Name, Arity} <- Choices], + parse_function_names(StrList, []). + +parse_function_names([], Acc) -> + lists:reverse(Acc); +parse_function_names([H|T], Acc) -> + IsFun = re:run(H, ".*-fun-\\d*?-"), + IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-"), + IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-"), + Parsed = + if IsFun =/= nomatch -> "Fun: " ++ H; + IsLc =/= nomatch -> "List comprehension: " ++ H; + IsLbc =/= nomatch -> "Bit comprehension: " ++ H; + true -> + H + end, + parse_function_names(T, [Parsed | Acc]). + +show_ms_in_savedlistbox(MatchSpecList) -> + MsOrAlias = fun(#match_spec{alias = A, str_ms = M, fun2ms = F}) -> + case A of + undefined -> + if + F =:= undefined -> M; + true -> F + end; + _ -> + A + end + end, + [MsOrAlias(X) || X <- MatchSpecList]. + + +find_and_format_ms(Selection, [ #match_spec{str_ms = Spec, alias = Alias, fun2ms = Fun} | T ]) -> + case ((Selection =:= Spec) or (Selection =:= Alias)) or (Selection =:= Fun) of + true -> + if Selection =:= Alias -> + Spec; + true -> + Selection + end; + false -> + find_and_format_ms(Selection, T) + end. + +find_ms(_, []) -> + {nomatch, #match_spec{}}; +find_ms(Str, [ #match_spec{str_ms = Spec, alias = Alias, fun2ms = Fun} = MS | T ]) -> + case ((Str =:= Spec) or (Str =:= Alias)) or (Str =:= Fun) of + true -> + {match, MS}; + false -> + find_ms(Str, T) + end. + +apply_matchspec(MatchSpec, TracedDict, root) -> + UpdateMS = fun(_Key, RecordList) -> + [X#traced_func{match_spec = MatchSpec} || X <- RecordList] + end, + {ok, dict:map(UpdateMS, TracedDict)}; +apply_matchspec(MatchSpec, TracedDict, {module, Module}) -> + RecordList = dict:fetch(Module, TracedDict), + RecordList2 = [X#traced_func{match_spec = MatchSpec} || X <- RecordList], + {ok, dict:store(Module, RecordList2, TracedDict)}; +apply_matchspec(MatchSpec, TracedDict, {function, Module, TracedFuncRec}) -> + RecordList = dict:fetch(Module, TracedDict), + NewFunc = TracedFuncRec#traced_func{match_spec = MatchSpec}, + RecordList2 = [NewFunc | [X || X <- RecordList, X =/= TracedFuncRec]], + {NewFunc, dict:store(Module, RecordList2, TracedDict)}. + +create_matchspec_page(Parent, MatchSpecs, UserData) -> + Panel = wxPanel:new(Parent), + MainSz = wxBoxSizer:new(?wxVERTICAL), + TxtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Match specification:"}]), + BtnSz = wxBoxSizer:new(?wxHORIZONTAL), + SavedSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Saved match specifications:"}]), + + TxtCtrl = create_styled_txtctrl(Panel), + wxSizer:add(TxtSz, TxtCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), + + AddMsBtn = wxButton:new(Panel, ?MATCHPAGE_ADDMS, [{label, "Add"}]), + AddMsAliasBtn = wxButton:new(Panel, ?MATCHPAGE_ADDMS_ALIAS, [{label, "Add with alias"}]), + Fun2MSBtn = wxButton:new(Panel, ?MATCHPAGE_ADDFUN, [{label, "Add fun"}]), + wxSizer:add(BtnSz, AddMsBtn), + wxSizer:add(BtnSz, AddMsAliasBtn), + wxSizer:add(BtnSz, Fun2MSBtn), + + Choices = show_ms_in_savedlistbox(MatchSpecs), + SavedMSListBox = wxListBox:new(Panel, ?MATCHPAGE_LISTBOX, [{choices, Choices}]), + wxSizer:add(SavedSz, SavedMSListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), + + wxButton:connect(AddMsBtn, command_button_clicked, [{userData, UserData}]), + wxButton:connect(AddMsAliasBtn, command_button_clicked, [{userData, UserData}] ), + wxButton:connect(Fun2MSBtn, command_button_clicked, [{userData, UserData}] ), + wxListBox:connect(SavedMSListBox, command_listbox_selected, [{userData, UserData}] ), + wxSizer:add(MainSz, TxtSz, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(MainSz, BtnSz), + wxSizer:add(MainSz, SavedSz, [{flag, ?wxEXPAND}, {proportion, 1}]), + + wxWindow:setSizer(Panel, MainSz), + {Panel, MainSz, TxtCtrl, SavedMSListBox}. + + + +update_tree(Tree, Dict) -> + RootId = wxTreeCtrl:getRootItem(Tree), + wxTreeCtrl:deleteChildren(Tree, RootId), + + FillTree = fun(KeyAtom, RecordList, acc_in) -> + ParsedList = parse_record_function_names(RecordList), + Module = wxTreeCtrl:appendItem(Tree, RootId, atom_to_list(KeyAtom)), + lists:foldl(fun(TracedFuncRecord, N) -> + FNameStr = lists:nth(N, ParsedList), + wxTreeCtrl:appendItem(Tree, Module, FNameStr, + [{data, TracedFuncRecord}]), + N+1 + end, + 1, RecordList), + wxTreeCtrl:sortChildren(Tree, Module), + acc_in + end, + dict:fold(FillTree, acc_in, Dict), + wxTreeCtrl:sortChildren(Tree, RootId), + wxTreeCtrl:expand(Tree, RootId). + + + + +create_module_popup(Parent, ModuleName, TracedDict) -> + Module = list_to_atom(ModuleName), + Value = dict:find(Module, TracedDict), + TracedModRecs = + case Value of + {ok, V} -> + V; + error -> + [] + end, + Functions = Module:module_info(functions), + Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, not(erl_internal:guard_bif(Name, Arity))]), + ParsedChoices = parse_function_names(Choices), + + Dialog = wxDialog:new(Parent, ?MODULEPOPUP_DIALOG, ModuleName, + [{style, ?wxDEFAULT_FRAME_STYLE}]), + Panel = wxPanel:new(Dialog), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + SelBtnSz = wxBoxSizer:new(?wxHORIZONTAL), + TxtCtrl = wxTextCtrl:new(Panel, ?MODULEPOPUP_TXTCTRL), + SelBtn = wxButton:new(Panel, ?MODULEPOPUP_SELECT, [{label, "Select"}]), + DeSelBtn = wxButton:new(Panel, ?MODULEPOPUP_SELECT, [{label, "Deselect"}]), + SelAllBtn = wxButton:new(Panel, ?MODULEPOPUP_SELALL, [{label, "Select all"}]), + DeSelAllBtn = wxButton:new(Panel, ?MODULEPOPUP_SELALL, [{label, "Deselect all"}]), + CheckListBox = wxCheckListBox:new(Panel, ?MODULEPOPUP_CHECKLISTBOX, [{choices, ParsedChoices}, {style, ?wxLB_EXTENDED}]), + Indices = find_index(TracedModRecs, Choices), + lists:foreach(fun(X) -> wxCheckListBox:check(CheckListBox, X) end, Indices), + Selections = [wxControlWithItems:getString(CheckListBox, I) || I <- Indices], + + OKBtn = wxButton:new(Panel, ?wxID_OK, []), + CancelBtn = wxButton:new(Panel, ?wxID_CANCEL, []), + DialogBtnSz = wxStdDialogButtonSizer:new(), + wxStdDialogButtonSizer:addButton(DialogBtnSz, OKBtn), + wxStdDialogButtonSizer:addButton(DialogBtnSz, CancelBtn), + wxStdDialogButtonSizer:realize(DialogBtnSz), + + wxSizer:add(SelBtnSz, SelBtn), + wxSizer:add(SelBtnSz, DeSelBtn), + wxSizer:add(SelBtnSz, SelAllBtn), + wxSizer:add(SelBtnSz, DeSelAllBtn), + wxSizer:add(MainSz, TxtCtrl, [{flag, ?wxEXPAND}]), + wxSizer:add(MainSz, CheckListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(MainSz, SelBtnSz, [{flag, ?wxEXPAND}]), + wxSizer:add(MainSz, DialogBtnSz), + wxWindow:setSizer(Panel, MainSz), + + wxButton:connect(SelBtn, command_button_clicked, [{userData, true}]), + wxButton:connect(DeSelBtn, command_button_clicked, [{userData, false}]), + wxButton:connect(SelAllBtn, command_button_clicked, [{userData, true}]), + wxButton:connect(DeSelAllBtn, command_button_clicked, [{userData, false}]), + wxButton:connect(OKBtn, command_button_clicked, [{userData, {module_popup, Module, ParsedChoices, Choices}}]), + wxButton:connect(CancelBtn, command_button_clicked, [{userData, module_popup}]), + wxTextCtrl:connect(TxtCtrl, command_text_updated, [{userData, ParsedChoices}]), + wxCheckListBox:connect(CheckListBox, command_checklistbox_toggled), + wxDialog:connect(Dialog, close_window), + wxDialog:show(Dialog), + {Dialog, CheckListBox, Selections}. + +get_selections(Selections, FunctionList) -> + get_selections(Selections, FunctionList, []). +get_selections([], _, Acc) -> + Acc; +get_selections([Int|T], FuncList, Acc) -> + get_selections(T, FuncList, [lists:nth(Int, FuncList) | Acc]). + +find_index(Selections, FunctionList) -> + find_index(Selections, FunctionList, 1, []). +find_index(Selections, FunctionList, N, Acc) when N > length(FunctionList); Selections =:= [] -> + Acc; + +find_index([#traced_func{func_name = Name, arity = Arity} |STail] = Selections, + FunctionList, N, Acc) -> + {Fname, A} = lists:nth(N, FunctionList), + case (Fname =:= Name) and (A =:= Arity) of + true -> + find_index(STail, FunctionList, 1, [N-1|Acc]); + false -> + find_index(Selections, FunctionList, N+1, Acc) + end; + +find_index([Sel|STail] = Selections, FunctionList, N, Acc) when is_list(Sel) -> + case lists:nth(N, FunctionList) =:= Sel of + true -> + find_index(STail, FunctionList, 1, [N-1|Acc]); + false -> + find_index(Selections, FunctionList, N+1, Acc) + end. + +atomlist_to_stringlist(Modules) -> + [atom_to_list(X) || X <- Modules]. + +ensure_last_is_dot([]) -> + "."; +ensure_last_is_dot(String) -> + case lists:last(String) =:= $. of + true -> + String; + false -> + String ++ "." + end. + +check_correct_MS(String) -> + Tokens = try_scan(String), + case try_parse(Tokens) of + {ok, Term} -> + case erlang:match_spec_test([], Term, trace) of + {ok, _, _, _} -> + {true, Term}; + {error, List} -> + Reason = unparse_error_msg(List, []), + {false, Reason} + end; + error -> + {false, "Invalid term"} + end. + +unparse_error_msg([], Acc) -> + lists:reverse(Acc); +unparse_error_msg([{_, Reason} | T], Acc) -> + unparse_error_msg(T, [Reason ++ "\n" | Acc]). + +try_scan(String) -> + try + erl_scan:string(String) of + {ok, T, _} -> + T; + _ -> + error + catch + _:_ -> error + end. + +try_parse(Tokens) -> + try + erl_parse:parse_term(Tokens) of + {ok, Term} -> + {ok, Term}; + _ -> + error + catch + _:_ -> + error + end. + +update_matchspec_listbox(Str, {PopupBox, PageBox}, From) -> + case From of + matchpopup -> + wxControlWithItems:append(PageBox, Str), + wxControlWithItems:append(PopupBox, Str); + matchpage -> + wxControlWithItems:append(PageBox, Str) + end. + + +dbg_from_string(Str0) -> + Str = unicode:characters_to_list(Str0), + case erl_scan:string(Str) of + {ok, Tokens,_} -> + case erl_parse:parse_exprs(Tokens) of + {ok,[{'fun',_,{clauses, Cl}}]} -> + case ms_transform: + transform_from_shell(dbg,Cl,orddict:new()) of + {error, [{_,[{Line,ms_transform,Info}]}],_} -> + {error,{Line,ms_transform,Info}}; + {error, _} = ET1 -> + ET1; + Else -> + {ok, Else, "[" ++ lists:flatten(io_lib:format("~p", Else)) ++ "]"} + end; + {ok,_} -> + {error, {1,ms_transform,1}}; + {error,Reason} -> + {error,Reason} + end; + {error,Reason2,_} -> + {error,Reason2} + end. + +get_correct_matchspec_components(From, State) -> + case From of + matchpage -> + {State#traceopts_state.matchpage_styled_txtctrl, + State#traceopts_state.frame}; + matchpopup -> + {State#traceopts_state.matchspec_popup_styled_txtctrl, + State#traceopts_state.matchspec_popup_dialog} + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %Trace option window + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% All pages + +handle_event(#wx{id = ?wxID_OK, + event = #wxCommand{type = command_button_clicked}, + userData = trace_options}, + #traceopts_state{boxes = Boxes, + trace_options = TraceOpts, + match_specs = MatchSpecs, + traced_funcs = TracedFuncs, + parent = Parent} = State) -> + UpdTraceOpts = wx:batch(fun() -> + read_trace_boxes(Boxes, TraceOpts) + end), + Parent ! {updated_traceopts, + UpdTraceOpts, + MatchSpecs, + TracedFuncs}, + {stop, shutdown, State}; + +handle_event(#wx{id = ?wxID_CANCEL, + event = #wxCommand{type = command_button_clicked}, + userData = trace_options}, + #traceopts_state{parent = Parent} = State) -> + Parent ! traceopts_closed, + {stop, shutdown, State}; + +handle_event(#wx{id = ?TRACEOPTS_FRAME, + event = #wxClose{type = close_window}}, + #traceopts_state{parent = Parent} = State) -> + Parent ! traceopts_closed, + {stop, shutdown, State}; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Page - Tracing + +handle_event(#wx{event = #wxCommand{type = command_checkbox_clicked}, userData = Boxgroup}, + State) -> + enable(Boxgroup), + {noreply, State}; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Page - Functions + +handle_event(#wx{id = ?FUNCTIONPAGE_LISTBOX, + event = #wxCommand{type = command_listbox_doubleclicked, + cmdString = ChosenModule}}, + #traceopts_state{frame = Frame, + traced_funcs = TracedDict, + popup_open = false} = State) -> + {Dialog, CheckListBox, CheckedFuncs} = create_module_popup(Frame, ChosenModule, TracedDict), + {noreply, State#traceopts_state{popup_open = true, + module_popup_dialog = Dialog, + module_popup_checklistbox = CheckListBox, + checked_funcs = CheckedFuncs}}; + +handle_event(#wx{id = ?FUNCTIONPAGE_TXTCTRL, + event = #wxCommand{type = command_text_updated, + cmdString = Input}, + userData = Data}, + #traceopts_state{functionpage_listbox = ListBox} = State) -> + filter_listbox_data(Input, Data, ListBox), + {noreply, State}; + +handle_event(#wx{event = #wxTree{type = command_tree_item_activated, + item = Item}}, + #traceopts_state{frame = Frame, + match_specs = MatchSpecs, + popup_open = false} = State) -> + + Dialog = wxDialog:new(Frame, ?MATCH_POPUP_DIALOG, "Match specification", + [{style, ?wxDEFAULT_FRAME_STYLE}]), + {MatchPanel, MatchSz, StyledTxtCtrl, ListBox} = create_matchspec_page(Dialog, MatchSpecs, matchpopup), + ApplyBtn = wxButton:new(MatchPanel, ?wxID_APPLY), + CancelBtn = wxButton:new(MatchPanel, ?wxID_CANCEL, []), + wxButton:connect(ApplyBtn, command_button_clicked, [{userData, Item}]), + wxButton:connect(CancelBtn, command_button_clicked, [{userData, matchspec_popup}]), + DialogBtnSz = wxStdDialogButtonSizer:new(), + wxStdDialogButtonSizer:addButton(DialogBtnSz, ApplyBtn), + wxStdDialogButtonSizer:addButton(DialogBtnSz, CancelBtn), + wxStdDialogButtonSizer:realize(DialogBtnSz), + wxSizer:add(MatchSz, DialogBtnSz), + + wxDialog:connect(Dialog, close_window), + wxDialog:show(Dialog), + {noreply, State#traceopts_state{matchspec_popup_dialog = Dialog, + matchspec_popup_listbox = ListBox, + matchspec_popup_styled_txtctrl = StyledTxtCtrl, + popup_open = true}}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Page - Match specs + +handle_event(#wx{event = #wxCommand{type = command_listbox_selected, + cmdString = Txt}}, + State) when Txt =:= [] -> + {noreply, State}; + +handle_event(#wx{id = ?MATCHPAGE_LISTBOX, + event = #wxCommand{type = command_listbox_selected, + cmdString = SavedTxt}, + userData = From}, + #traceopts_state{match_specs = MatchSpecs} = State) -> + {StyledTxtCtrl, _} = get_correct_matchspec_components(From, State), + MsOrFun = find_and_format_ms(SavedTxt, MatchSpecs), + wxStyledTextCtrl:setText(StyledTxtCtrl, MsOrFun), + {noreply, State}; + +handle_event(#wx{id = ?MATCHPAGE_ADDFUN, + event = #wxCommand{type = command_button_clicked}, + userData = From}, + #traceopts_state{match_specs = MatchSpecs, + matchpage_listbox = PageListBox, + matchspec_popup_listbox = PopupListBox} = State) -> + + {StyledTxtCtrl, Frame} = get_correct_matchspec_components(From, State), + StrFun = ensure_last_is_dot(wxStyledTextCtrl:getText(StyledTxtCtrl)), + + MatchSpecs2 = case dbg_from_string(StrFun) of + {ok, TermMS, StrMS} -> + FunMS = #match_spec{str_ms = StrMS, term_ms = TermMS, fun2ms = StrFun}, + case lists:member(FunMS, MatchSpecs) of + true -> + observer_wx:create_txt_dialog(Frame, StrFun ++ "\nalready exists", + "Error", ?wxICON_ERROR), + MatchSpecs; + false -> + wxStyledTextCtrl:setText(StyledTxtCtrl, StrMS), + update_matchspec_listbox(StrFun, {PopupListBox, PageListBox}, From), + lists:reverse([FunMS | MatchSpecs]) + end; + {error, {_, Module, What}} -> + FailMsg = Module:format_error(What), + observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR), + MatchSpecs + end, + {noreply, State#traceopts_state{match_specs = MatchSpecs2}}; + +handle_event(#wx{id = ?MATCHPAGE_ADDMS, + event = #wxCommand{type = command_button_clicked}, + userData = From}, + #traceopts_state{match_specs = MatchSpecs, + matchpage_listbox = PageListBox, + matchspec_popup_listbox = PopupListBox} = State) -> + + {StyledTxtCtrl, Frame} = get_correct_matchspec_components(From, State), + StrMS = ensure_last_is_dot(wxStyledTextCtrl:getText(StyledTxtCtrl)), + MatchSpecs2 = case check_correct_MS(StrMS) of + {true, TermMS} -> + MS = #match_spec{str_ms = StrMS, term_ms = TermMS}, + case lists:member(MS, MatchSpecs) of + true -> + observer_wx:create_txt_dialog(Frame, StrMS ++ "\nalready exists", + "Error", ?wxICON_ERROR), + MatchSpecs; + false -> + update_matchspec_listbox(StrMS, {PopupListBox, PageListBox}, From), + lists:reverse([MS | MatchSpecs]) + end; + {false, Reason} -> + observer_wx:create_txt_dialog(Frame, Reason, "Error", ?wxICON_ERROR), + MatchSpecs + end, + {noreply, State#traceopts_state{match_specs = MatchSpecs2}}; + + +handle_event(#wx{id = ?MATCHPAGE_ADDMS_ALIAS, + event = #wxCommand{type = command_button_clicked}, + userData = From}, + #traceopts_state{match_specs = MatchSpecs, + matchpage_listbox = PageListBox, + matchspec_popup_listbox = PopupListBox} = State) -> + + {StyledTxtCtrl, Frame} = get_correct_matchspec_components(From, State), + StrMS = ensure_last_is_dot(wxStyledTextCtrl:getText(StyledTxtCtrl)), + + MatchSpecs2 = case check_correct_MS(StrMS) of + {true, TermMS} -> + Dialog = wxTextEntryDialog:new(Frame, "Enter ms alias: "), + Alias = case wxDialog:showModal(Dialog) of + ?wxID_OK -> + wxTextEntryDialog:getValue(Dialog); + ?wxID_CANCEL -> + "" + end, + wxDialog:destroy(Dialog), + + case Alias of + "" -> + observer_wx:create_txt_dialog(Frame, "Bad alias", "Syntax error", + ?wxICON_ERROR), + MatchSpecs; + + _ -> + MS = #match_spec{alias = Alias, str_ms = StrMS, + term_ms = TermMS}, + {OccupiedAlias, _} = find_ms(Alias, MatchSpecs), + + if + OccupiedAlias =:= match -> + observer_wx:create_txt_dialog(Frame, "Alias " ++ Alias ++ " already exists", + "Error", ?wxICON_ERROR), + MatchSpecs; + true -> + update_matchspec_listbox(Alias, {PopupListBox, PageListBox}, From), + lists:reverse([MS | MatchSpecs]) + end + end; + {false, Reason} -> + observer_wx:create_txt_dialog(Frame, Reason, "Error", ?wxICON_ERROR), + MatchSpecs + end, + {noreply, State#traceopts_state{match_specs = MatchSpecs2}}; + + +handle_event(#wx{id = ?wxID_APPLY, + event = #wxCommand{type = command_button_clicked}, + userData = Item}, + #traceopts_state{matchspec_popup_dialog = Dialog, + matchspec_popup_listbox = ListBox, + tree = Tree, + match_specs = MatchSpecs, + traced_funcs = TracedDict} = State) -> + IntSelection = wxListBox:getSelection(ListBox), + StrSelection = + case IntSelection >= 0 of + true -> + wxControlWithItems:getString(ListBox, IntSelection); + false -> + [] + end, + {_, MS} = find_ms(StrSelection, MatchSpecs), + RootId = wxTreeCtrl:getRootItem(Tree), + ItemParent = wxTreeCtrl:getItemParent(Tree, Item), + + TracedDict2 = + if (Item =:= RootId) -> + {ok, NewDict} = apply_matchspec(MS, TracedDict, root), + NewDict; + (ItemParent =:= RootId) -> + Module = list_to_atom(wxTreeCtrl:getItemText(Tree, Item)), + {ok, NewDict} = apply_matchspec(MS, TracedDict, {module, Module}), + NewDict; + true -> + TracedFuncRec = wxTreeCtrl:getItemData(Tree, Item), + Module = list_to_atom(wxTreeCtrl:getItemText(Tree, ItemParent)), + {NewTracedFuncRecord, NewDict} = + apply_matchspec(MS, + TracedDict, + {function, + Module, + TracedFuncRec}), + wxTreeCtrl:setItemData(Tree, Item, NewTracedFuncRecord), + NewDict + end, + wxDialog:destroy(Dialog), + {noreply, State#traceopts_state{traced_funcs = TracedDict2, + popup_open = false}}; + +handle_event(#wx{id = ?wxID_CANCEL, + event = #wxCommand{type = command_button_clicked}, + userData = matchspec_popup}, + #traceopts_state{matchspec_popup_dialog = Dialog} = State) -> + wxDialog:destroy(Dialog), + {noreply, State#traceopts_state{popup_open = false}}; + +handle_event(#wx{id = ?MATCH_POPUP_DIALOG, + event = #wxClose{type = close_window}}, + #traceopts_state{matchspec_popup_dialog = Dialog} = State) -> + wxDialog:destroy(Dialog), + {noreply, State#traceopts_state{popup_open = false}}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %Module Popup + +handle_event(#wx{id = ?wxID_OK, + event = #wxCommand{type = command_button_clicked}, + userData = {module_popup, Module, + ParsedChoices, Choices}}, + #traceopts_state{ + module_popup_dialog = Dialog, + traced_funcs = TracedDict, + tree = Tree, + checked_funcs = CheckedFuncs} = State) -> + + Indices = [I+1 || I <- find_index(CheckedFuncs, ParsedChoices)], + Selections = get_selections(Indices, Choices), + TracedDict2 = case Selections of + [] -> + dict:erase(Module, TracedDict); + _ -> + Traced = [#traced_func{arity = Arity, + func_name = Function} + || {Function, Arity} <- Selections], + dict:store(Module, Traced, TracedDict) + end, + + update_tree(Tree, TracedDict2), + wxDialog:destroy(Dialog), + {noreply, State#traceopts_state{traced_funcs = TracedDict2, + checked_funcs = [], + popup_open = false}}; + +handle_event(#wx{id = ?wxID_CANCEL, + event = #wxCommand{type = command_button_clicked}, + userData = module_popup}, + #traceopts_state{module_popup_dialog = Dialog} = State) -> + wxDialog:destroy(Dialog), + {noreply, State#traceopts_state{popup_open = false, + checked_funcs = []}}; + +handle_event(#wx{id = ?MODULEPOPUP_SELECT, + event = #wxCommand{type = command_button_clicked}, + userData = Bool}, + #traceopts_state{module_popup_checklistbox = CheckListBox, + checked_funcs = CheckedFuncs} = State) -> + {_, Selections} = wxListBox:getSelections(CheckListBox), + lists:foreach(fun(Index) -> wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]) end, Selections), + StrSelections = [wxControlWithItems:getString(CheckListBox, N) || N <- Selections], + CheckedFuncs2 = case Bool of + true -> + [X || X <- StrSelections, + not(lists:member(X, CheckedFuncs))] ++ CheckedFuncs; + false -> + CheckedFuncs -- StrSelections + end, + {noreply, State#traceopts_state{checked_funcs = CheckedFuncs2}}; + +handle_event(#wx{id = ?MODULEPOPUP_SELALL, + event = #wxCommand{type = command_button_clicked}, + userData = Bool}, + #traceopts_state{module_popup_checklistbox = CheckListBox} = State) -> + lists:foreach(fun(Index) -> + wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]) + end, + lists:seq(0, wxControlWithItems:getCount(CheckListBox))), + CheckedFuncs = case Bool of + true -> + [wxControlWithItems:getString(CheckListBox, N) + || N <- lists:seq(0, wxControlWithItems:getCount(CheckListBox))]; + false -> + [] + end, + {noreply, State#traceopts_state{checked_funcs = CheckedFuncs}}; + +handle_event(#wx{id = ?MODULEPOPUP_CHECKLISTBOX, + obj = CheckListBox, + event = #wxCommand{type = command_checklistbox_toggled, + commandInt = Index}}, + #traceopts_state{checked_funcs = CheckedFuncs} = State) -> + + UpdCheckedFuncs = case + wxCheckListBox:isChecked(CheckListBox, Index) of + true -> + [wxControlWithItems:getString(CheckListBox, Index) | CheckedFuncs]; + false -> + lists:delete(wxControlWithItems:getString(CheckListBox, Index), CheckedFuncs) + end, + {noreply, State#traceopts_state{checked_funcs = UpdCheckedFuncs}}; + +handle_event(#wx{id = ?MODULEPOPUP_TXTCTRL, + event = #wxCommand{type = command_text_updated, + cmdString = Input}, + userData = Data}, + #traceopts_state{module_popup_checklistbox = CListBox, + checked_funcs = CheckedFuncs} = State) -> + FilteredData = filter_listbox_data(Input, Data, CListBox), + lists:foreach(fun(Index) -> + wxCheckListBox:check(CListBox, Index, [{check, true}]) + end, + [wxControlWithItems:findString(CListBox, X) || X <- CheckedFuncs, lists:member(X, FilteredData)]), + {noreply, State}; + +handle_event(#wx{id = ?MODULEPOPUP_DIALOG, + event = #wxClose{type = close_window}}, + #traceopts_state{module_popup_dialog = Dialog} = State) -> + wxDialog:destroy(Dialog), + {noreply, State#traceopts_state{popup_open = false, + checked_funcs = []}}; + +handle_event(#wx{event = What}, State) -> + io:format("~p~p: Unhandled event: ~p ~n", [?MODULE, self(), What]), + {noreply, State}. + + + +terminate(Reason, #traceopts_state{frame = Frame}) -> + io:format("~p terminating traceopts. Reason: ~p~n", [?MODULE, Reason]), + wxFrame:destroy(Frame), + ok. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +handle_info(Any, State) -> + io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]), + {noreply, State}. + +handle_call(Msg, _From, State) -> + io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast(Msg, State) -> + io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), + {noreply, State}. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl new file mode 100644 index 0000000000..b4a85cd43b --- /dev/null +++ b/lib/observer/src/observer_wx.erl @@ -0,0 +1,506 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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_wx). + +-behaviour(wx_object). + +-export([start/0]). +-export([create_menus/2, create_menu/2, create_txt_dialog/4, try_rpc/4, + return_to_localnode/2]). + +-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, + handle_call/3, handle_info/2, check_page_title/1]). + +%% Includes +-include_lib("wx/include/wx.hrl"). + +-include("observer_defs.hrl"). + +%% Defines + +-define(ID_PING, 1). +-define(ID_CONNECT, 2). +-define(ID_NOTEBOOK, 3). + +-define(FIRST_NODES_MENU_ID, 1000). +-define(LAST_NODES_MENU_ID, 2000). + +%% Records +-record(state, + {frame, + menubar, + status_bar, + notebook, + main_panel, + pro_panel, + tv_panel, + sys_panel, + active_tab, + node, + nodes + }). + +start() -> + wx_object:start(?MODULE, [], []). + +create_menus(Object, Menus) when is_list(Menus) -> + wx_object:call(Object, {create_menus, Menus}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init(_Args) -> + wx:new(), + Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer", [{size, {1000, 500}}, + {style, ?wxDEFAULT_FRAME_STYLE}]), + IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"), + Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), + wxFrame:setIcon(Frame, Icon), + wxIcon:destroy(Icon), + + State = #state{frame = Frame}, + UpdState = setup(State), + wxFrame:show(Frame), + net_kernel:monitor_nodes(true), + {Frame, UpdState}. + +setup(#state{frame = Frame} = State) -> + %% Setup Menubar & Menus + MenuBar = wxMenuBar:new(), + + {Nodes, NodeMenus} = get_nodes(), + DefMenus = default_menus(NodeMenus), + create_menu(DefMenus, MenuBar), + + 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, []), + Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), + + %% Setup sizer + MainSizer = wxBoxSizer:new(?wxVERTICAL), + + %% System Panel + SysPanel = observer_sys_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, SysPanel, "System", []), + + %% Process Panel + ProPanel = observer_pro_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, ProPanel, "Processes", []), + + %% Table Viewer Panel + TVPanel = observer_tv_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []), + + wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), + wxPanel:setSizer(Panel, MainSizer), + + wxNotebook:connect(Notebook, command_notebook_page_changed), + wxFrame:connect(Frame, close_window, [{skip, true}]), + wxMenu:connect(Frame, command_menu_selected, [{skip, true}]), + + SysPid = wx_object:get_pid(SysPanel), + SysPid ! {active, node()}, + UpdState = State#state{main_panel = Panel, + notebook = Notebook, + menubar = MenuBar, + status_bar = StatusBar, + sys_panel = SysPanel, + pro_panel = ProPanel, + tv_panel = TVPanel, + active_tab = SysPid, + node = node(), + nodes = Nodes + }, + UpdState. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%Callbacks +handle_event(#wx{obj = Notebook, id = ?ID_NOTEBOOK, + event = #wxNotebook{type = command_notebook_page_changed}}, + #state{active_tab=Previous, node=Node, notebook = Notebook} = State) -> + io:format("Command notebook changed ~n"), + Pid = get_active_pid(State), + Previous ! not_active, + Pid ! {active, Node}, + {noreply, State#state{active_tab=Pid}}; + +handle_event(#wx{event = #wxClose{}}, State) -> + {stop, normal, State}; + +handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) -> + io:format("~p ~p, you clicked close", [?MODULE, ?LINE]), + {stop, normal, State}; + +handle_event(#wx{id = ?wxID_HELP, event = #wxCommand{type = command_menu_selected}}, State) -> + io:format("~p ~p, you clicked help", [?MODULE, ?LINE]), + {noreply, State}; + +handle_event(#wx{id = ?ID_CONNECT, event = #wxCommand{type = command_menu_selected}}, + #state{frame = Frame} = State) -> + UpdState = case create_connect_dialog(connect, State) of + cancel -> + State; + {value, [], _, _} -> + create_txt_dialog(Frame, "Node must have a name", + "Error", ?wxICON_ERROR), + State; + {value, NodeName, LongOrShort, Cookie} -> %Shortname, + try + case connect(list_to_atom(NodeName), LongOrShort, list_to_atom(Cookie)) of + {ok, set_cookie} -> + change_node_view(node(), State); + {error, set_cookie} -> + create_txt_dialog(Frame, "Could not set cookie", + "Error", ?wxICON_ERROR), + State; + {error, net_kernel, _Reason} -> + create_txt_dialog(Frame, "Could not enable node", + "Error", ?wxICON_ERROR), + State + end + catch _:_ -> + create_txt_dialog(Frame, "Could not enable node", + "Error", ?wxICON_ERROR), + State + end + end, + {noreply, UpdState}; + +handle_event(#wx{id = ?ID_PING, event = #wxCommand{type = command_menu_selected}}, + #state{frame = Frame} = State) -> + UpdState = case create_connect_dialog(ping, State) of + cancel -> + State; + {value, Value} when is_list(Value) -> + try + + Node = list_to_atom(Value), + case net_adm:ping(Node) of + pang -> + create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION), + State; + pong -> + change_node_view(Node, State) + end + + catch _:_ -> + create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION), + State + end + 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 -> + + Node = lists:nth(Id - ?FIRST_NODES_MENU_ID, State#state.nodes), + UpdState = change_node_view(Node, State), + {noreply, UpdState}; + +handle_event(Event, State) -> + Pid = get_active_pid(State), + Pid ! Event, + {noreply, State}. + +handle_cast(Cast, State) -> + io:format("~p:~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]), + {noreply, State}. + +handle_call({create_menus, TabMenus}, _From, State = #state{menubar=MenuBar}) -> + wx:batch(fun() -> + {_Nodes, NodeMenus} = get_nodes(), + DefMenus = default_menus(NodeMenus), + Menus = merge_menus(DefMenus, TabMenus), + clean_menus(MenuBar), + create_menu(Menus, MenuBar) + end), + {reply, ok, State}; + +handle_call(Msg, _From, State) -> + io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_info({nodeup, _Node}, State) -> + State2 = update_node_list(State), + {noreply, State2}; + +handle_info({nodedown, Node}, + #state{frame = Frame} = State) -> + State2 = case Node =:= State#state.node of + true -> + change_node_view(node(), State); + false -> + State + end, + State3 = update_node_list(State2), + Msg = ["Node down: " | atom_to_list(Node)], + create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION), + {noreply, State3}; + +handle_info(Info, State) -> + io:format("~p, ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +terminate(Reason, #state{frame = Frame}) -> + wxFrame:destroy(Frame), + io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), + ok. + +code_change(_, _, State) -> + {stop, not_yet_implemented, State}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +try_rpc(Node, Mod, Func, Args) -> + case + rpc:call(Node, Mod, Func, Args) of + {badrpc, Reason} -> + error_logger:error_report([{node, Node}, + {call, {Mod, Func, Args}}, + {reason, {badrpc, Reason}}]), + error({badrpc, Reason}); + Res -> + Res + end. + +return_to_localnode(Frame, Node) -> + case node() =/= Node of + true -> + create_txt_dialog(Frame, "Error occured on remote node", + "Error", ?wxICON_ERROR), + disconnect_node(Node); + false -> + ok + end. + +create_txt_dialog(Frame, Msg, Title, Style) -> + MD = wxMessageDialog:new(Frame, Msg, [{style, Style}]), + wxMessageDialog:setTitle(MD, Title), + wxDialog:showModal(MD), + wxDialog:destroy(MD). + +connect(NodeName, 0, Cookie) -> + connect2(NodeName, shortnames, Cookie); +connect(NodeName, 1, Cookie) -> + connect2(NodeName, longnames, Cookie). + +connect2(NodeName, Opts, Cookie) -> + case net_kernel:start([NodeName, Opts]) of + {ok, _} -> + case is_alive() of + true -> + erlang:set_cookie(node(), Cookie), + {ok, set_cookie}; + false -> + {error, set_cookie} + end; + {error, Reason} -> + {error, net_kernel, Reason} + end. + +change_node_view(Node, State = #state{pro_panel=Pro, sys_panel=Sys, tv_panel=Tv}) -> + lists:foreach(fun(Pid) -> wx_object:get_pid(Pid) ! {node, Node} end, + [Pro, Sys, Tv]), + StatusText = ["Observer - " | atom_to_list(Node)], + wxFrame:setTitle(State#state.frame, StatusText), + wxStatusBar:setStatusText(State#state.status_bar, StatusText), + State#state{node = Node}. + +check_page_title(Notebook) -> + Selection = wxNotebook:getSelection(Notebook), + wxNotebook:getPageText(Notebook, Selection). + +get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, tv_panel=Tv}) -> + Panel = case check_page_title(Notebook) of + "Processes" -> Pro; + "System" -> Sys; + "Table Viewer" -> Tv + end, + wx_object:get_pid(Panel). + +create_connect_dialog(ping, #state{frame = Frame}) -> + Dialog = wxTextEntryDialog:new(Frame, "Connect to node"), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Value = wxTextEntryDialog:getValue(Dialog), + wxDialog:destroy(Dialog), + {value, Value}; + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + cancel + end; +create_connect_dialog(connect, #state{frame = Frame}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, "Distribute node "), + + VSizer = wxBoxSizer:new(?wxVERTICAL), + RadioBoxSizer = wxBoxSizer:new(?wxHORIZONTAL), + + Choices = ["Short name", "Long name"], + RadioBox = wxRadioBox:new(Dialog, 1, "", + ?wxDefaultPosition, + ?wxDefaultSize, + Choices, + [{majorDim, 2}, + {style, ?wxHORIZONTAL}]), + + NameText = wxStaticText:new(Dialog, ?wxID_ANY, "Node name: "), + NameCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {200, 25}}, {style, ?wxDEFAULT}]), + wxTextCtrl:setValue(NameCtrl, "observer"), + CookieText = wxStaticText:new(Dialog, ?wxID_ANY, "Secret cookie: "), + CookieCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {200, 25}}, {style, ?wxDEFAULT}]), + + BtnSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxID_DEFAULT), + Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}], + wxSizer:add(RadioBoxSizer, RadioBox, Flags), + + wxSizer:add(VSizer, RadioBoxSizer, Flags), + wxSizer:addSpacer(VSizer, 10), + wxSizer:add(VSizer, NameText), + wxSizer:add(VSizer, NameCtrl, Flags), + wxSizer:addSpacer(VSizer, 10), + wxSizer:add(VSizer, CookieText), + wxSizer:add(VSizer, CookieCtrl, Flags), + wxSizer:addSpacer(VSizer, 10), + wxSizer:add(VSizer, BtnSizer, [{flag, ?wxALIGN_LEFT}]), + + wxWindow:setSizer(Dialog, VSizer), + CookiePath = filename:join(os:getenv("HOME"), ".erlang.cookie"), + DefaultCookie = case filelib:is_file(CookiePath) of + true -> + {ok, IoDevice} = file:open(CookiePath, read), + case file:read_line(IoDevice) of + {ok, Cookie} -> + Cookie; + _ -> + "" + end; + false -> + "" + end, + wxTextCtrl:setValue(CookieCtrl, DefaultCookie), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + NameValue = wxTextCtrl:getValue(NameCtrl), + NameLngthValue = wxRadioBox:getSelection(RadioBox), + CookieValue = wxTextCtrl:getValue(CookieCtrl), + wxDialog:destroy(Dialog), + {value, NameValue, NameLngthValue, CookieValue}; + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + cancel + end. + +default_menus(NodesMenuItems) -> + FileMenu = {"File", [#create_menu{id = ?wxID_EXIT, text = "Quit"}]}, + HelpMenu = {"Help", [#create_menu{id = ?wxID_HELP, text = "Help"}]}, + NodeMenu = case erlang:is_alive() of + true -> + {"Nodes", NodesMenuItems ++ + [#create_menu{id = ?ID_PING, text = "Connect Node"}]}; + false -> + {"Nodes", NodesMenuItems ++ + [#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}]} + end, + [FileMenu, NodeMenu, HelpMenu]. + +clean_menus(MenuBar) -> + Count = wxMenuBar:getMenuCount(MenuBar), + remove_menu_item(MenuBar, Count). + +remove_menu_item(MenuBar, Item) when Item > -1 -> + Menu = wxMenuBar:getMenu(MenuBar, Item), + wxMenuBar:remove(MenuBar, Item), + wxMenu:destroy(Menu), + remove_menu_item(MenuBar, Item-1); +remove_menu_item(_, _) -> + ok. + +merge_menus([{Label, Items}|Default], [{Label, TabItems}|TabMenus]) -> + [{Label, TabItems ++ Items} | merge_menus(Default, TabMenus)]; +merge_menus([Menu = {"File", _}|Default], TabMenus) -> + [Menu | merge_menus(Default, TabMenus)]; +merge_menus(Default = [{"Nodes", _}|_], TabMenus) -> + TabMenus ++ Default. + +create_menu(Menus, MenuBar) -> + Add = fun({Name, MenuItems}) -> + Menu = wxMenu:new(), + lists:foreach(fun(Record) -> + create_menu_item(Record, Menu) + end, + MenuItems), + wxMenuBar:append(MenuBar, Menu, Name) + end, + wx:foreach(Add, Menus), + ok. + +create_menu_item(#create_menu{id = Id, text = Text, type = Type, check = Check}, Menu) -> + case Type of + append -> + wxMenu:append(Menu, Id, Text); + check -> + wxMenu:appendCheckItem(Menu, Id, Text), + wxMenu:check(Menu, Id, Check); + radio -> + wxMenu:appendRadioItem(Menu, Id, Text), + wxMenu:check(Menu, Id, Check); + separator -> + wxMenu:appendSeparator(Menu) + end; +create_menu_item(separator, Menu) -> + wxMenu:appendSeparator(Menu). + + +get_nodes() -> + Nodes = [node()| nodes()], + {_, Menues} = + lists:foldl(fun(Node, {Id, Acc}) when Id < ?LAST_NODES_MENU_ID -> + {Id + 1, [#create_menu{id = Id + ?FIRST_NODES_MENU_ID, + text = atom_to_list(Node)} | Acc]} + end, + {1, []}, + Nodes), + {Nodes, lists:reverse(Menues)}. + +update_node_list(State = #state{menubar=MenuBar}) -> + {Nodes, NodesMenuItems} = get_nodes(), + NodeMenuId = wxMenuBar:findMenu(MenuBar, "Nodes"), + NodeMenu = wxMenuBar:getMenu(MenuBar, NodeMenuId), + wx:foreach(fun(Item) -> + wxMenu:'Destroy'(NodeMenu, Item) + end, + wxMenu:getMenuItems(NodeMenu)), + + wx:foreach(fun(Record) -> + create_menu_item(Record, NodeMenu) + end, NodesMenuItems), + + case erlang:is_alive() of + true -> + create_menu_item(#create_menu{id = ?ID_PING, text = "Connect node"}, NodeMenu); + false -> + create_menu_item(#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}, NodeMenu) + end, + State#state{nodes = Nodes}. |