From 5e1fafb077740c9d919bc532b2c392f4f20bbf1b Mon Sep 17 00:00:00 2001 From: Magnus Eriksson Date: Thu, 29 Sep 2011 13:20:42 +0200 Subject: [observer] Started on a wx gui --- lib/observer/priv/erlang_observer.png | Bin 0 -> 4351 bytes lib/observer/src/Makefile | 18 +- lib/observer/src/observer_defs.hrl | 55 ++ lib/observer/src/observer_pro_wx.erl | 944 ++++++++++++++++++++++ lib/observer/src/observer_procinfo.erl | 511 ++++++++++++ lib/observer/src/observer_sys.erl | 131 +++ lib/observer/src/observer_sys_wx.erl | 313 ++++++++ lib/observer/src/observer_trace_wx.erl | 487 +++++++++++ lib/observer/src/observer_traceoptions_wx.erl | 1066 +++++++++++++++++++++++++ lib/observer/src/observer_wx.erl | 506 ++++++++++++ 10 files changed, 4026 insertions(+), 5 deletions(-) create mode 100644 lib/observer/priv/erlang_observer.png create mode 100644 lib/observer/src/observer_defs.hrl create mode 100644 lib/observer/src/observer_pro_wx.erl create mode 100644 lib/observer/src/observer_procinfo.erl create mode 100644 lib/observer/src/observer_sys.erl create mode 100644 lib/observer/src/observer_sys_wx.erl create mode 100644 lib/observer/src/observer_trace_wx.erl create mode 100644 lib/observer/src/observer_traceoptions_wx.erl create mode 100644 lib/observer/src/observer_wx.erl (limited to 'lib/observer') diff --git a/lib/observer/priv/erlang_observer.png b/lib/observer/priv/erlang_observer.png new file mode 100644 index 0000000000..78e70461b1 Binary files /dev/null and b/lib/observer/priv/erlang_observer.png differ 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}. -- cgit v1.2.3 From 41dc04f0b76474596076bdc909760228fffc6a73 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Thu, 29 Sep 2011 14:18:52 +0200 Subject: [observer] Add a [d]ets viewer, tv replacement --- lib/observer/src/Makefile | 2 + lib/observer/src/observer_tv.hrl | 34 ++ lib/observer/src/observer_tv_table.erl | 806 +++++++++++++++++++++++++++++++++ lib/observer/src/observer_tv_wx.erl | 600 ++++++++++++++++++++++++ 4 files changed, 1442 insertions(+) create mode 100644 lib/observer/src/observer_tv.hrl create mode 100644 lib/observer/src/observer_tv_table.erl create mode 100644 lib/observer/src/observer_tv_wx.erl (limited to 'lib/observer') diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 514c6e441c..92319434c0 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -48,6 +48,8 @@ MODULES= \ observer_sys_wx \ observer_trace_wx \ observer_traceoptions_wx \ + observer_tv_table \ + observer_tv_wx \ ttb \ ttb_et diff --git a/lib/observer/src/observer_tv.hrl b/lib/observer/src/observer_tv.hrl new file mode 100644 index 0000000000..05e4f928d0 --- /dev/null +++ b/lib/observer/src/observer_tv.hrl @@ -0,0 +1,34 @@ +%% +%% %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% + +-record(tab, {name, + id = ignore, + size, + memory=0, %% In bytes + owner, + reg_name, + protection = public, + type=set, + keypos=1, + heir=none, + compressed=false, + fixed=false, + %% Mnesia Info + storage, + index + }). diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl new file mode 100644 index 0000000000..3228f7e571 --- /dev/null +++ b/lib/observer/src/observer_tv_table.erl @@ -0,0 +1,806 @@ +%% +%% %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_tv_table). + +-export([start_link/2]). + +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_sync_event/3, handle_cast/2]). + +-export([get_table/3]). + +-import(observer_pro_wx, [to_str/1]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_tv.hrl"). + +-define(ID_TABLE_INFO, 400). +-define(ID_REFRESH, 401). +-define(ID_REFRESH_INTERVAL, 402). +-define(ID_EDIT, 403). +-define(ID_DELETE, 404). +-define(ID_SEARCH, 405). + +-define(SEARCH_ENTRY, 420). +-define(GOTO_ENTRY, 421). + +-define(DEFAULT_COL_WIDTH, 100). + +-record(state, + { + parent, + frame, + grid, + status, + sizer, + search, + selected, + node=node(), + columns, + pid, + source, + tab, + attrs + }). + +-record(opt, + { + sort_key=2, + sort_incr=true + }). + +-record(attrs, {even, odd, deleted, changed, searched}). + +-record(search, + {enable=true, % Subwindow is enabled + win, % Sash Sub window obj + name, % name + + search, % Search input ctrl + goto, % Goto input ctrl + radio, % Radio buttons + + find % Search string + }). + +-record(find, {start, % start pos + strlen, % Found + found % false + }). + +start_link(Parent, Opts) -> + wx_object:start_link(?MODULE, [Parent, Opts], []). + +init([Parent, Opts]) -> + Source = proplists:get_value(type, Opts), + Table = proplists:get_value(table, Opts), + Node = proplists:get_value(node, Opts), + Title0 = atom_to_list(Table#tab.name) ++ " @ " ++ atom_to_list(Node), + Title = case Source of + ets -> "TV Ets: " ++ Title0; + mnesia -> "TV Mnesia: " ++ Title0 + end, + Frame = wxFrame:new(Parent, ?wxID_ANY, Title, [{size, {800, 300}}]), + 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), + MenuBar = wxMenuBar:new(), + create_menus(MenuBar), + wxFrame:setMenuBar(Frame, MenuBar), + %% wxFrame:setAcceleratorTable(Frame, AccelTable), + wxMenu:connect(Frame, command_menu_selected), + + StatusBar = wxFrame:createStatusBar(Frame, []), + try + TabId = table_id(Table), + ColumnNames = column_names(Node, Source, TabId), + KeyPos = key_pos(Node, Source, TabId), + + Attrs = create_attrs(), + + Self = self(), + Holder = spawn_link(fun() -> + init_table_holder(Self, Table, Source, + length(ColumnNames), Node, Attrs) + end), + + Panel = wxPanel:new(Frame), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Grid = wxListCtrl:new(Panel, [{style, Style}, + {onGetItemText, + fun(_, Item,Col) -> get_row(Holder, Item, Col+1) end}, + {onGetItemAttr, + fun(_, Item) -> get_attr(Holder, Item) end} + ]), + wxListCtrl:connect(Grid, command_list_item_activated), + wxListCtrl:connect(Grid, command_list_item_selected), + wxListCtrl:connect(Grid, command_list_col_click), + wxListCtrl:connect(Grid, size, [{skip, true}]), + wxWindow:setFocus(Grid), + + Search = search_area(Panel), + wxSizer:add(Sizer, Grid, + [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), + wxSizer:add(Sizer, Search#search.win, + [{flag,?wxEXPAND bor ?wxLEFT bor ?wxRIGHT bor + ?wxRESERVE_SPACE_EVEN_IF_HIDDEN}, + {border, 5}]), + wxWindow:setSizer(Panel, Sizer), + wxSizer:hide(Sizer, Search#search.win), + + Cols = add_columns(Grid, 0, ColumnNames), + wxFrame:show(Frame), + {Panel, #state{frame=Frame, grid=Grid, status=StatusBar, search=Search, + sizer = Sizer, + parent=Parent, columns=Cols, + pid=Holder, source=Source, tab=Table#tab{keypos=KeyPos}, + attrs=Attrs}} + catch node_or_table_down -> + wxFrame:destroy(Frame), + stop + end. + +add_columns(Grid, Start, ColumnNames) -> + Li = wxListItem:new(), + AddListEntry = fun(Name, Col) -> + wxListItem:setText(Li, to_str(Name)), + wxListItem:setAlign(Li, ?wxLIST_FORMAT_LEFT), + wxListCtrl:insertColumn(Grid, Col, Li), + wxListCtrl:setColumnWidth(Grid, Col, ?DEFAULT_COL_WIDTH), + Col + 1 + end, + Cols = lists:foldl(AddListEntry, Start, ColumnNames), + wxListItem:destroy(Li), + Cols. + +create_menus(MB) -> + File = wxMenu:new(), + wxMenu:append(File, ?ID_TABLE_INFO, "Table Information\tCtrl-I"), + wxMenu:append(File, ?wxID_CLOSE, "Close"), + wxMenuBar:append(MB, File, "File"), + Edit = wxMenu:new(), + wxMenu:append(Edit, ?ID_EDIT, "Edit Object"), + wxMenu:append(Edit, ?ID_DELETE, "Delete Object\tCtrl-D"), + wxMenu:appendSeparator(Edit), + wxMenu:append(Edit, ?ID_SEARCH, "Search\tCtrl-S"), + wxMenu:appendSeparator(Edit), + wxMenu:append(Edit, ?ID_REFRESH, "Refresh\tCtrl-R"), + wxMenu:append(Edit, ?ID_REFRESH_INTERVAL, "Refresh interval..."), + wxMenuBar:append(MB, Edit, "Edit"), + Help = wxMenu:new(), + wxMenu:append(Help, ?wxID_HELP, "Help"), + wxMenuBar:append(MB, Help, "Help"), + ok. + +search_area(Parent) -> + HSz = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Find:"), + [{flag,?wxALIGN_CENTER_VERTICAL}]), + TC1 = wxTextCtrl:new(Parent, ?SEARCH_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), + wxSizer:add(HSz, TC1, [{proportion,3}, {flag, ?wxEXPAND}]), + Nbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Next"), + wxRadioButton:setValue(Nbtn, true), + wxSizer:add(HSz,Nbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + Pbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Previous"), + wxSizer:add(HSz,Pbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + Cbtn = wxCheckBox:new(Parent, ?wxID_ANY, "Match Case"), + wxSizer:add(HSz,Cbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + wxSizer:add(HSz, 15,15, [{proportion,1}, {flag, ?wxEXPAND}]), + wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Goto Entry:"), + [{flag,?wxALIGN_CENTER_VERTICAL}]), + TC2 = wxTextCtrl:new(Parent, ?GOTO_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), + wxSizer:add(HSz, TC2, [{proportion,0}, {flag, ?wxEXPAND}]), + wxTextCtrl:connect(TC1, command_text_updated), + wxTextCtrl:connect(TC1, command_text_enter), + wxTextCtrl:connect(TC1, kill_focus), + wxTextCtrl:connect(TC2, command_text_enter), + wxWindow:connect(Parent, command_button_clicked), + + #search{name='Search Area', win=HSz, + search=TC1,goto=TC2,radio={Nbtn,Pbtn,Cbtn}}. + +edit(Index, #state{pid=Pid, frame=Frame}) -> + Str = get_row(Pid, Index, all), + Dialog = wxTextEntryDialog:new(Frame, "Edit object:", [{value, Str}]), + case wxTextEntryDialog:showModal(Dialog) of + ?wxID_OK -> + New = wxTextEntryDialog:getValue(Dialog), + wxTextEntryDialog:destroy(Dialog), + case Str =:= New of + true -> ok; + false -> + complete_edit(Index, New, Pid) + end; + ?wxID_CANCEL -> + wxTextEntryDialog:destroy(Dialog) + end. + +complete_edit(Row, New0, Pid) -> + New = case lists:reverse(New0) of + [$.|_] -> New0; + _ -> New0 ++ "." + end, + try + {ok, Tokens, _} = erl_scan:string(New), + {ok, Term} = erl_parse:parse_term(Tokens), + Pid ! {edit, Row, Term} + catch _:{badmatch, {error, {_, _, Err}}} -> + self() ! {error, ["Parse error: ", Err]}; + _Err -> + self() ! {error, ["Syntax error in: ", New]} + end. + +handle_event(#wx{id=?ID_REFRESH},State = #state{pid=Pid}) -> + Pid ! refresh, + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, + State = #state{pid=Pid}) -> + Pid ! {sort, Col+1}, + {noreply, State}; + +handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> + 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(?DEFAULT_COL_WIDTH, Last), + wxListCtrl:setColumnWidth(Grid, Cols-1, Size) + end), + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, + State = #state{pid=Pid, grid=Grid, status=StatusBar}) -> + N = wxListCtrl:getItemCount(Grid), + Str = get_row(Pid, Index, all), + wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w: ~s",[N, Str])), + {noreply, State#state{selected=Index}}; + +handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Index}}, + State) -> + edit(Index, State), + {noreply, State}; + +handle_event(#wx{id=?ID_EDIT}, State = #state{selected=undefined}) -> + {noreply, State}; +handle_event(#wx{id=?ID_EDIT}, State = #state{selected=Index}) -> + edit(Index, State), + {noreply, State}; + +handle_event(#wx{id=?ID_DELETE}, State = #state{selected=undefined}) -> + {noreply, State}; +handle_event(#wx{id=?ID_DELETE}, + State = #state{pid=Pid, status=StatusBar, selected=Index}) -> + Str = get_row(Pid, Index, all), + Pid ! {delete, Index}, + wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~s",[Str])), + {noreply, State}; + +handle_event(#wx{id=?wxID_CLOSE}, State) -> + {stop, normal, State}; + +handle_event(Help = #wx{id=?wxID_HELP}, State = #state{parent=Parent}) -> + Parent ! Help, + {noreply, State}; + +handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, + State = #state{grid=Grid}) -> + try + Row0 = list_to_integer(Str), + Row1 = min(0, Row0), + Row = max(wxListCtrl:getItemCount(Grid)-1,Row1), + wxListCtrl:ensureVisible(Grid, Row), + ok + catch _:_ -> ok + end, + {noreply, State}; + +%% Search functionality +handle_event(#wx{id=?ID_SEARCH}, + State = #state{sizer=Sz, search=Search}) -> + wxSizer:show(Sz, Search#search.win), + wxWindow:setFocus(Search#search.search), + wxSizer:layout(Sz), + {noreply, State}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxFocus{}}, + State = #state{search=Search, pid=Pid}) -> + Pid ! {mark_search_hit, false}, + {noreply, State#state{search=Search#search{find=undefined}}}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=""}}, + State = #state{search=Search, pid=Pid}) -> + Pid ! {mark_search_hit, false}, + {noreply, State#state{search=Search#search{find=undefined}}}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdString=Str}}, + State = #state{grid=Grid, pid=Pid, status=SB, + search=Search=#search{radio={Next0, _, Case0}, + find=Find}}) + when Find =/= undefined -> + Dir = wxRadioButton:getValue(Next0) xor wx_misc:getKeyState(?WXK_SHIFT), + Case = wxCheckBox:getValue(Case0), + Pos = if Find#find.found, Dir -> %% Forward Continuation + Find#find.start+1; + Find#find.found -> %% Backward Continuation + Find#find.start-1; + Dir -> %% Forward wrap + 0; + true -> %% Backward wrap + wxListCtrl:getItemCount(Grid)-1 + end, + Pid ! {mark_search_hit, false}, + case search(Pid, Str, Pos, Dir, Case) of + false -> + wxStatusBar:setStatusText(SB, "Not found"), + Pid ! {mark_search_hit, Find#find.start}, + wxListCtrl:refreshItem(Grid, Find#find.start), + {noreply, State#state{search=Search#search{find=#find{found=false}}}}; + Row -> + wxListCtrl:ensureVisible(Grid, Row), + wxListCtrl:refreshItem(Grid, Row), + Status = "Found: (Hit Enter for next, Shift-Enter for previous)", + wxStatusBar:setStatusText(SB, Status), + {noreply, State#state{search=Search#search{find=#find{start=Row, found=true}}}} + end; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}}, + State = #state{grid=Grid, pid=Pid, status=SB, + search=Search=#search{radio={Next0, _, Case0}, + find=Find}}) -> + try + Dir = wxRadioButton:getValue(Next0), + Case = wxCheckBox:getValue(Case0), + Start = case Dir of + true -> 0; + false -> wxListCtrl:getItemCount(Grid)-1 + end, + Cont = case Find of + undefined -> + #find{start=Start, strlen=length(Str)}; + #find{strlen=Old} when Old < length(Str) -> + Find#find{start=Start, strlen=length(Str)}; + _ -> + Find#find{strlen=length(Str)} + end, + + Pid ! {mark_search_hit, false}, + case search(Pid, Str, Cont#find.start, Dir, Case) of + false -> + wxStatusBar:setStatusText(SB, "Not found"), + {noreply, State}; + Row -> + wxListCtrl:ensureVisible(Grid, Row), + wxListCtrl:refreshItem(Grid, Row), + Status = "Found: (Hit Enter for next, Shift-Enter for previous)", + wxStatusBar:setStatusText(SB, Status), + {noreply, State#state{search=Search#search{find=#find{start=Row, found=true}}}} + end + catch _:_ -> {noreply, State} + end; + +handle_event(#wx{id=?ID_TABLE_INFO}, + State = #state{frame=Frame, node=Node, source=Source, tab=Table}) -> + observer_tv_wx:display_table_info(Frame, Node, Source, Table), + {noreply, State}; + +handle_event(Event, State) -> + io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_sync_event(Event, _Obj, _State) -> + io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), + ok. + +handle_call(Event, From, State) -> + io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), + {noreply, State}. + +handle_cast(Event, State) -> + io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_info({no_rows, N}, State = #state{grid=Grid, status=StatusBar}) -> + wxListCtrl:setItemCount(Grid, N), + wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w",[N])), + {noreply, State}; +handle_info({new_cols, New}, State = #state{grid=Grid, columns=Cols0}) -> + Cols = add_columns(Grid, Cols0, New), + {noreply, State#state{columns=Cols}}; +handle_info({refresh, Min, Max}, State = #state{grid=Grid}) -> + wxListCtrl:refreshItems(Grid, Min, Max), + {noreply, State}; +handle_info({error, Error}, State = #state{frame=Frame}) -> + Dlg = wxMessageDialog:new(Frame, Error), + wxMessageDialog:showModal(Dlg), + wxMessageDialog:destroy(Dlg), + {noreply, State}; + +handle_info(Event, State) -> + io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +terminate(_Event, #state{pid=Pid, attrs=Attrs}) -> + %% ListItemAttr are not auto deleted + #attrs{odd=Odd, deleted=D, changed=Ch, searched=S} = Attrs, + wxListItemAttr:destroy(Odd), + wxListItemAttr:destroy(D), + wxListItemAttr:destroy(Ch), + wxListItemAttr:destroy(S), + unlink(Pid), + exit(Pid, window_closed), + ok. + +code_change(_, _, State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Table holder needs to be in a separate process otherwise +%% the callback get_row/3 may deadlock if the process do +%% wx calls when callback is invoked. +get_row(Table, Item, Column) -> + Ref = erlang:monitor(process, Table), + Table ! {get_row, self(), Item, Column}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Table, Res} -> + erlang:demonitor(Ref), + Res + end. + +get_attr(Table, Item) -> + Ref = erlang:monitor(process, Table), + Table ! {get_attr, self(), Item}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Table, Res} -> + erlang:demonitor(Ref), + Res + end. + +search(Table, Str, Row, Dir, Case) -> + Ref = erlang:monitor(process, Table), + Table ! {search, [Str, Row, Dir, Case]}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Table, Res} -> + erlang:demonitor(Ref), + Res + end. + +-record(holder, {node, parent, pid, + table=[], n=0, columns, + temp=[], + search, + source, tabid, + sort, + key, + type, + attrs + }). + +init_table_holder(Parent, Table, MnesiaOrEts, Cols, Node, Attrs) -> + TabId = case Table#tab.id of + ignore -> Table#tab.name; + Id -> Id + end, + self() ! refresh, + table_holder(#holder{node=Node, parent=Parent, + source=MnesiaOrEts, tabid=TabId, columns=Cols, + sort=#opt{sort_key=Table#tab.keypos, sort_incr=true}, + type=Table#tab.type, key=Table#tab.keypos, + attrs=Attrs}). + +table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) -> + receive + {get_attr, From, Row} -> + get_attr(From, Row, S0), + table_holder(S0); + {get_row, From, Row, Col} -> + get_row(From, Row, Col, Table), + table_holder(S0); + {Pid, Data} -> + S1 = handle_new_data_chunk(Data, S0), + table_holder(S1); + {sort, Col} -> + table_holder(sort(Col, S0)); + {search, Data} -> + table_holder(search(Data, S0)); + {mark_search_hit, Row} -> + Old = S0#holder.search, + is_integer(Old) andalso (Parent ! {refresh, Old, Old}), + table_holder(S0#holder{search=Row}); + refresh when is_pid(Pid) -> + %% Already getting the table... + %% io:format("ignoring refresh", []), + table_holder(S0); + refresh -> + GetTab = rpc:call(S0#holder.node, ?MODULE, get_table, + [self(), S0#holder.tabid, S0#holder.source]), + table_holder(S0#holder{pid=GetTab}); + {delete, Row} -> + delete_row(Row, S0), + table_holder(S0); + {edit, Row, Term} -> + edit_row(Row, Term, S0), + table_holder(S0); + What -> + io:format("Table holder got ~p~n",[What]), + table_holder(S0) + end. + +handle_new_data_chunk(Data, S0 = #holder{columns=Cols, parent=Parent}) -> + S1 = #holder{columns=NewCols} = handle_new_data_chunk2(Data, S0), + case NewCols =:= Cols of + true -> S1; + false -> + Parent ! {new_cols, lists:seq(Cols+1, NewCols)}, + S1 + end. + +handle_new_data_chunk2('$end_of_table', + S0 = #holder{parent=Parent, sort=Opt, + key=Key, + table=Old, temp=New}) -> + Table = merge(Old, New, Key), + N = length(Table), + Parent ! {no_rows, N}, + sort(Opt#opt.sort_key, S0#holder{n=N, pid=undefine, + sort=Opt#opt{sort_key = undefined}, + table=Table, temp=[]}); +handle_new_data_chunk2(Data, S0 = #holder{columns=Cols0, source=ets, temp=Tab0}) -> + {Tab, Cols} = parse_ets_data(Data, Cols0, Tab0), + S0#holder{columns=Cols, temp=Tab}; +handle_new_data_chunk2(Data, S0 = #holder{source=mnesia, temp=Tab}) -> + S0#holder{temp=(Data ++ Tab)}. + +parse_ets_data([[Rec]|Rs], C, Tab) -> + parse_ets_data(Rs, max(tuple_size(Rec), C), [Rec|Tab]); +parse_ets_data([Recs|Rs], C0, Tab0) -> + {Tab, Cols} = parse_ets_data(Recs, C0, Tab0), + parse_ets_data(Rs, Cols, Tab); +parse_ets_data([], Cols, Tab) -> + {Tab, Cols}. + +sort(Col, S=#holder{n=N, parent=Parent, sort=Opt0, table=Table0}) -> + {Opt, Table} = sort(Col, Opt0, Table0), + Parent ! {refresh, 0, N}, + S#holder{sort=Opt, table=Table}. + +sort(Col, Opt = #opt{sort_key=Col, sort_incr=Bool}, Table) -> + {Opt#opt{sort_incr=not Bool}, lists:reverse(Table)}; +sort(Col, #opt{sort_incr=true}, Table) -> + {#opt{sort_key=Col}, keysort(Col, Table)}; +sort(Col, #opt{sort_incr=false}, Table) -> + {#opt{sort_key=Col}, lists:reverse(keysort(Col, Table))}. + +keysort(Col, Table) -> + Sort = fun([A0|_], [B0|_]) -> + A = try element(Col, A0) catch _:_ -> [] end, + B = try element(Col, B0) catch _:_ -> [] end, + case A == B of + true -> A0 =< B0; + false -> A < B + end; + (A0, B0) when is_tuple(A0), is_tuple(B0) -> + A = try element(Col, A0) catch _:_ -> [] end, + B = try element(Col, B0) catch _:_ -> [] end, + case A == B of + true -> A0 =< B0; + false -> A < B + end + end, + lists:sort(Sort, Table). + +search([Str, Row, Dir0, CaseSens], + S=#holder{parent=Parent, table=Table}) -> + Opt = case CaseSens of + true -> []; + false -> [caseless] + end, + {ok, Re} = re:compile(Str, Opt), + Dir = case Dir0 of + true -> 1; + false -> -1 + end, + Res = search(Row, Dir, Re, Table), + Parent ! {self(), Res}, + S#holder{search=Res}. + +search(Row, Dir, Re, Table) -> + Res = try lists:nth(Row+1, Table) of + Term -> + Str = io_lib:format("~w", [Term]), + re:run(Str, Re) + catch _:_ -> no_more + end, + case Res of + nomatch -> search(Row+Dir, Dir, Re, Table); + no_more -> false; + {match,_} -> Row + end. + +get_row(From, Row, Col, Table) -> + case lists:nth(Row+1, Table) of + [Object|_] when Col =:= all -> + From ! {self(), io_lib:format("~w", [Object])}; + [Object|_] when tuple_size(Object) >= Col -> + From ! {self(), io_lib:format("~w", [element(Col, Object)])}; + _ -> + From ! {self(), ""} + end. + +get_attr(From, Row, #holder{attrs=Attrs, search=Row}) -> + What = Attrs#attrs.searched, + From ! {self(), What}; +get_attr(From, Row, #holder{table=Table, attrs=Attrs}) -> + What = case lists:nth(Row+1, Table) of + [_|deleted] -> Attrs#attrs.deleted; + [_|changed] -> Attrs#attrs.changed; + [_|new] -> Attrs#attrs.changed; + _ when (Row rem 2) > 0 -> + Attrs#attrs.odd; + _ -> + Attrs#attrs.even + end, + From ! {self(), What}. + +merge([], New, _Key) -> + [[N] || N <- New]; %% First time +merge(Old, New, Key) -> + merge2(keysort(Key, Old), keysort(Key, New), Key). + +merge2([[Obj|_]|Old], [Obj|New], Key) -> + [[Obj]|merge2(Old, New, Key)]; +merge2([[A|_]|Old], [B|New], Key) + when element(Key, A) == element(Key, B) -> + [[B|changed]|merge2(Old, New, Key)]; +merge2([[A|_]|Old], New = [B|_], Key) + when element(Key, A) < element(Key, B) -> + [[A|deleted]|merge2(Old, New, Key)]; +merge2(Old = [[A|_]|_], [B|New], Key) + when element(Key, A) > element(Key, B) -> + [[B|new]|merge2(Old, New, Key)]; +merge2([], New, _Key) -> + [[N|new] || N <- New]; +merge2(Old, [], _Key) -> + [[O|deleted] || [O|_] <- Old]. + + +delete_row(Row, S0 = #holder{parent=Parent}) -> + case delete(Row, S0) of + ok -> + self() ! refresh; + {error, Err} -> + Parent ! {error, "Could not delete object: " ++ Err} + end. + + +delete(Row, #holder{tabid=Id, table=Table, + source=Source, node=Node}) -> + [Object|_] = lists:nth(Row+1, Table), + try + case Source of + ets -> + true = rpc:call(Node, ets, delete_object, [Id, Object]); + mnesia -> + ok = rpc:call(Node, mnesia, dirty_delete_object, [Id, Object]) + end, + ok + catch _:_Error -> + {error, "node or table is not available"} + end. + +edit_row(Row, Term, S0 = #holder{parent=Parent}) -> + case delete(Row, S0) of + ok -> + case insert(Term, S0) of + ok -> self() ! refresh; + Err -> Parent ! {error, Err} + end; + {error, Err} -> + Parent ! {error, "Could not edit object: " ++ Err} + end. + +insert(Object, #holder{tabid=Id, source=Source, node=Node}) -> + try + case Source of + ets -> + true = rpc:call(Node, ets, insert, [Id, Object]); + mnesia -> + ok = rpc:call(Node, mnesia, dirty_write, [Id, Object]) + end, + ok + catch _:_Error -> + {error, "node or table is not available"} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +get_table(Parent, Table, Module) -> + spawn(fun() -> + link(Parent), + get_table2(Parent, Table, Module) + end). + +get_table2(Parent, Table, Type) -> + Size = case Type of + ets -> ets:info(Table, size); + mnesia -> mnesia:table_info(Table, size) + end, + case Size > 0 of + false -> + Parent ! {self(), '$end_of_table'}, + normal; + true when Type =:= ets -> + Mem = ets:info(Table, memory), + Average = Mem div Size, + NoElements = max(10, 20000 div Average), + get_ets_loop(Parent, ets:match(Table, '$1', NoElements)); + true -> + Mem = mnesia:table_info(Table, memory), + Average = Mem div Size, + NoElements = max(10, 20000 div Average), + Ms = [{'$1', [], ['$1']}], + Get = fun() -> + get_mnesia_loop(Parent, mnesia:select(Table, Ms, NoElements, read)) + end, + %% Not a transaction, we don't want to grab locks when inspecting the table + mnesia:async_dirty(Get) + end. + +get_ets_loop(Parent, '$end_of_table') -> + Parent ! {self(), '$end_of_table'}; +get_ets_loop(Parent, {Match, Cont}) -> + Parent ! {self(), Match}, + get_ets_loop(Parent, ets:match(Cont)). + +get_mnesia_loop(Parent, '$end_of_table') -> + Parent ! {self(), '$end_of_table'}; +get_mnesia_loop(Parent, {Match, Cont}) -> + Parent ! {self(), Match}, + get_ets_loop(Parent, mnesia:select(Cont)). + +column_names(Node, Type, Table) -> + case Type of + ets -> [1, 2]; + mnesia -> + Attrs = rpc:call(Node, mnesia, table_info, [Table, attributes]), + is_list(Attrs) orelse throw(node_or_table_down), + ["Record Name"|Attrs] + end. + +table_id(#tab{id=ignore, name=Name}) -> Name; +table_id(#tab{id=Id}) -> Id. + +key_pos(_, mnesia, _) -> 2; +key_pos(Node, ets, TabId) -> + KeyPos = rpc:call(Node, ets, info, [TabId, keypos]), + is_integer(KeyPos) orelse throw(node_or_table_down), + KeyPos. + +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), + deleted = wxListItemAttr:new({240,30,30}, {100,100,100}, Font), + changed = wxListItemAttr:new(Text, {255,215,0}, Font), + searched = wxListItemAttr:new(Text, {235,215,90}, Font) + }. diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl new file mode 100644 index 0000000000..10b29cb1b7 --- /dev/null +++ b/lib/observer/src/observer_tv_wx.erl @@ -0,0 +1,600 @@ +%% +%% %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_tv_wx). + +-export([start_link/2, display_table_info/4]). + +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_sync_event/3, handle_cast/2]). + +-export([get_table_list/1]). %% RPC called move to runtime tools? + +-export([get_wx_parent/1, interval_dialog/5]). + +-import(observer_pro_wx, [to_str/1]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). +-include("observer_tv.hrl"). + +-define(GRID, 500). +-define(ID_REFRESH, 401). +-define(ID_REFRESH_INTERVAL, 402). +-define(ID_ETS, 403). +-define(ID_MNESIA, 404). +-define(ID_UNREADABLE, 405). +-define(ID_SYSTEM_TABLES, 406). +-define(ID_TABLE_INFO, 407). + +-record(opt, {type=ets, + sys_hidden=true, + unread_hidden=true, + sort_key=2, + sort_incr=true + }). + +-record(state, + { + parent, + grid, + node=node(), + opt=#opt{}, + selected, + tabs, + refr_timer=false, + refr_intv=30 + }). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +init([Notebook, Parent]) -> + Panel = wxPanel:new(Notebook), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), + wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, {border, 5}]), + wxWindow:setSizer(Panel, Sizer), + Li = wxListItem:new(), + AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(Grid, Col, Li), + wxListCtrl:setColumnWidth(Grid, Col, DefSize), + Col + 1 + end, + ListItems = [{"Table Name", ?wxLIST_FORMAT_LEFT, 200}, + {"Table Id", ?wxLIST_FORMAT_RIGHT, 100}, + {"Objects", ?wxLIST_FORMAT_RIGHT, 100}, + {"Size (kB)", ?wxLIST_FORMAT_RIGHT, 100}, + {"Owner Pid", ?wxLIST_FORMAT_CENTER, 150}, + {"Owner Name", ?wxLIST_FORMAT_LEFT, 200} + ], + lists:foldl(AddListEntry, 0, ListItems), + wxListItem:destroy(Li), + + wxListCtrl:connect(Grid, command_list_item_activated), + wxListCtrl:connect(Grid, command_list_item_selected), + wxListCtrl:connect(Grid, command_list_col_click), + wxListCtrl:connect(Grid, size, [{skip, true}]), + + wxWindow:setFocus(Grid), + {Panel, #state{grid=Grid, parent=Parent}}. + +handle_event(#wx{id=?ID_REFRESH}, + State = #state{node=Node, grid=Grid, opt=Opt}) -> + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + {noreply, State#state{tabs=Tabs}}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, + State = #state{node=Node, grid=Grid, + opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> + Opt = case Col+2 of + Key -> Opt0#opt{sort_incr=not Bool}; + NewKey -> Opt0#opt{sort_key=NewKey} + end, + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + {noreply, State#state{opt=Opt, tabs=Tabs}}; + +handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0}) + when Id >= ?ID_ETS, Id =< ?ID_SYSTEM_TABLES -> + Opt = case Id of + ?ID_ETS -> Opt0#opt{type=ets}; + ?ID_MNESIA -> Opt0#opt{type=mnesia}; + ?ID_UNREADABLE -> Opt0#opt{unread_hidden= not Opt0#opt.unread_hidden}; + ?ID_SYSTEM_TABLES -> Opt0#opt{sys_hidden= not Opt0#opt.sys_hidden} + end, + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + {noreply, State#state{opt=Opt, tabs=Tabs}}; + +handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> + 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{obj=Grid, event=#wxList{type=command_list_item_activated, + itemIndex=Index}}, + State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs}) -> + Table = lists:nth(Index+1, Tabs), + case Table#tab.protection of + private -> + self() ! {error, "Table has 'private' protection and can not be read"}; + _ -> + observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]) + end, + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, + State) -> + {noreply, State#state{selected=Index}}; + +handle_event(#wx{id=?ID_TABLE_INFO}, + State = #state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs, selected=Sel}) -> + case Sel of + undefined -> + {noreply, State}; + R when is_integer(R) -> + Table = lists:nth(Sel+1, Tabs), + Parent = get_wx_parent(Grid), + display_table_info(Parent, Node, Type, Table), + {noreply, State} + end; + +handle_event(#wx{id=?ID_REFRESH_INTERVAL}, + State = #state{grid=Grid, refr_timer=Timer0, refr_intv=Intv0}) -> + Parent = get_wx_parent(Grid), + case interval_dialog(Parent, Timer0 /= false, Intv0, 10, 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#state{refr_timer=Timer, refr_intv=Intv}}; + {false, _} -> + case Timer0 of + false -> ok; + _ -> timer:cancel(Timer0) + end, + {noreply, State#state{refr_timer=false}} + end; + +handle_event(Event, State) -> + io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_sync_event(Event, _Obj, _State) -> + io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), + ok. + +handle_call(Event, From, State) -> + io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), + {noreply, State}. + +handle_cast(Event, State) -> + io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) -> + io:format("refresh interval ~p~n", [time()]), + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + {noreply, State#state{tabs=Tabs}}; + +handle_info({active, Node}, + State = #state{parent=Parent, grid=Grid, opt=Opt, + refr_timer = Refr, refr_intv=Intv}) -> + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + create_menus(Parent, Opt), + Timer = case Refr of + true -> + {ok, Ref} = timer:send_interval(Intv*1000, refresh_interval), + Ref; + false -> + false + end, + {noreply, State#state{node=Node, tabs=Tabs, refr_timer=Timer}}; + +handle_info(not_active, State = #state{refr_timer = Timer0}) -> + Timer = case Timer0 of + false -> false; + true -> true; + Timer0 -> + timer:cancel(Timer0), + true + end, + {noreply, State#state{refr_timer=Timer}}; + +handle_info({node, Node}, State = #state{grid=Grid, opt=Opt}) -> + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + {noreply, State#state{node=Node, tabs=Tabs}}; + +handle_info({error, Error}, State) -> + handle_error(Error), + {noreply, State}; + +handle_info(Event, State) -> + io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +terminate(Event, _State) -> + io:format("~p:~p, terminate ~p\n", [?MODULE, ?LINE, Event]), + ok. + +code_change(_, _, State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_menus(Parent, #opt{sys_hidden=Sys, unread_hidden=UnR, type=Type}) -> + MenuEntries = [{"View", + [#create_menu{id = ?ID_TABLE_INFO, text = "Table information\tCtrl-I"}, + separator, + #create_menu{id = ?ID_ETS, text = "&Ets Tables", + type=radio, check=Type==ets}, + #create_menu{id = ?ID_MNESIA, text = "&Mnesia Tables", + type=radio, check=Type==mnesia}, + separator, + #create_menu{id = ?ID_UNREADABLE, text = "View &Unreadable Tables", + type=check, check=not UnR}, + #create_menu{id = ?ID_SYSTEM_TABLES, text = "View &System Tables", + type=check, check=not Sys}, + separator, + #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, + #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."} + ]}], + observer_wx:create_menus(Parent, MenuEntries). + +get_tables(Node, Opt) -> + case rpc:call(Node, ?MODULE, get_table_list, [Opt]) of + {badrpc, Error} -> + self() ! {error, Error}, + []; + {error, Error} -> + self() ! {error, Error}, + []; + Result -> + Result + end. + +get_table_list(#opt{type=ets, unread_hidden=HideUnread, sys_hidden=HideSys}) -> + Info = fun(Id, Acc) -> + try + TabId = case ets:info(Id, named_table) of + true -> ignore; + false -> Id + end, + Name = ets:info(Id, name), + Protection = ets:info(Id, protection), + ignore(HideUnread andalso Protection == private, unreadable), + Owner = ets:info(Id, owner), + RegName = case catch process_info(Owner, registered_name) of + [] -> ignore; + {registered_name, ProcName} -> ProcName + end, + ignore(HideSys andalso ordsets:is_element(RegName, sys_processes()), system_tab), + ignore(HideSys andalso ordsets:is_element(Name, sys_tables()), system_tab), + ignore((RegName == mnesia_monitor) + andalso Name /= schema + andalso is_atom((catch mnesia:table_info(Name, where_to_read))), mnesia_tab), + Memory = ets:info(Id, memory) * erlang:system_info(wordsize), + Tab = #tab{name = Name, + id = TabId, + protection = Protection, + owner = Owner, + size = ets:info(Id, size), + reg_name = RegName, + type = ets:info(Id, type), + keypos = ets:info(Id, keypos), + heir = ets:info(Id, heir), + memory = Memory, + compressed = ets:info(Id, compressed), + fixed = ets:info(Id, fixed) + }, + [Tab|Acc] + catch _:_What -> + %% io:format("Skipped ~p: ~p ~n",[Id, _What]), + Acc + end + end, + lists:foldl(Info, [], ets:all()); +get_table_list(#opt{type=mnesia, sys_hidden=HideSys}) -> + Owner = ets:info(schema, owner), + Owner /= undefined orelse + throw({error, "Mnesia is not running on: " ++ atom_to_list(node())}), + {registered_name, RegName} = process_info(Owner, registered_name), + Info = fun(Id, Acc) -> + try + Name = Id, + ignore(HideSys andalso ordsets:is_element(Name, mnesia_tables()), system_tab), + ignore(Name =:= schema, mnesia_tab), + Storage = mnesia:table_info(Id, storage_type), + Tab0 = #tab{name = Name, + owner = Owner, + size = mnesia:table_info(Id, size), + reg_name = RegName, + type = mnesia:table_info(Id, type), + keypos = 2, + memory = mnesia:table_info(Id, memory) * erlang:system_info(wordsize), + storage = Storage, + index = mnesia:table_info(Id, index) + }, + Tab = if Storage == disc_only_copies -> + Tab0#tab{fixed = element(2, dets:info(Id, safe_fixed)) /= []}; + (Storage == ram_copies) orelse + (Storage == disc_copies) -> + Tab0#tab{fixed = ets:info(Id, fixed), + compressed = ets:info(Id, compressed)}; + true -> Tab0 + end, + [Tab|Acc] + catch _:_What -> + %% io:format("Skipped ~p: ~p ~n",[Id, _What]), + Acc + end + end, + lists:foldl(Info, [], mnesia:system_info(tables)). + +display_table_info(Parent, Node, Source, Table) -> + Title = "Table Info: " ++ atom_to_list(Table#tab.name), + Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, + [{style, ?wxCAPTION bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), + + IdInfo = {"Identification and Owner", + [{"Name", Table#tab.name}, + {"Id", case Table#tab.id of + ignore -> Table#tab.name; + Id -> Id + end}, + {"Named table", Table#tab.id == ignore}, + {"Owner", Table#tab.owner}, + {"Owner Name", case Table#tab.reg_name of + ignore -> "-"; + Id -> Id + end}, + {"Heir", Table#tab.heir}, + {"Node", Node}]}, + MnesiaSettings = case Source of + ets -> []; + mnesia -> + [{"Local storage type", case Table#tab.storage of + unknown -> "Not available"; + ST -> ST + end}, + {"Index positions", list_to_strings(Table#tab.index)}] + end, + Settings = {"Settings", + [{"Source", Source}, + {"Key Position", Table#tab.keypos}, + {"Table Type", Table#tab.type}, + {"Protection Mode", Table#tab.protection}, + {"Fixed", Table#tab.fixed} + | MnesiaSettings ]}, + Memory = {"Memory Usage", + [{"Number of objects", Table#tab.size}, + {"Memory allocated", integer_to_list(Table#tab.memory div 1024) ++ "kB"}, + {"Compressed", Table#tab.compressed}]}, + + Sizer = display_info_wx(Frame, [IdInfo, Settings, Memory]), + wxSizer:setSizeHints(Sizer, Frame), + wxFrame:center(Frame), + wxFrame:show(Frame). + +list_to_strings([]) -> "None"; +list_to_strings([A]) -> integer_to_list(A); +list_to_strings([A,B]) -> + integer_to_list(A) ++ " ," ++ list_to_strings(B). + +get_wx_parent(Window) -> + Parent = wxWindow:getParent(Window), + case wx:is_null(Parent) of + true -> Window; + false -> get_wx_parent(Parent) + end. + +display_info_wx(Frame, Info) -> + Panel = wxPanel:new(Frame), + wxWindow:setBackgroundColour(Panel, {255,255,255}), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:addSpacer(Sizer, 5), + Add = fun(BoxInfo) -> + Box = create_box(Panel, BoxInfo), + wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}]) + end, + [Add(I) || I <- Info], + wxSizer:addSpacer(Sizer, 5), + wxWindow:setSizerAndFit(Panel, Sizer), + Sizer. + +create_box(Panel, {Title, Info}) -> + Box = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, Title}]), + Left = wxBoxSizer:new(?wxVERTICAL), + Right = wxBoxSizer:new(?wxVERTICAL), + Expand = [{flag, ?wxEXPAND}], + AddRow = fun({Desc, Value}) -> + wxSizer:add(Left, wxStaticText:new(Panel, ?wxID_ANY, Desc ++ ":"), Expand), + wxSizer:add(Right, wxStaticText:new(Panel, ?wxID_ANY, to_str(Value)), Expand) + end, + [AddRow(Entry) || Entry <- Info], + wxSizer:add(Box, Left), + wxSizer:addSpacer(Box, 10), + wxSizer:add(Box, Right), + wxSizer:addSpacer(Box, 30), + Box. + +sys_tables() -> + [ac_tab, asn1, + cdv_dump_index_table, cdv_menu_table, cdv_decode_heap_table, + cell_id, cell_pos, clist, + cover_internal_data_table, cover_collected_remote_data_table, cover_binary_code_table, + code, code_names, cookies, + corba_policy, corba_policy_associations, + dets, dets_owners, dets_registry, + disk_log_names, disk_log_pids, + eprof, erl_atom_cache, erl_epmd_nodes, + etop_accum_tab, etop_tr, + ets_coverage_data, + file_io_servers, + gs_mapping, gs_names, gstk_db, + gstk_grid_cellid, gstk_grid_cellpos, gstk_grid_id, + httpd, + id, + ign_req_index, ign_requests, + index, + inet_cache, inet_db, inet_hosts, + 'InitialReferences', + int_db, + interpreter_includedirs_macros, + ir_WstringDef, + lmcounter, locks, +% mnesia_decision, + mnesia_gvar, mnesia_stats, +% mnesia_transient_decision, + pg2_table, + queue, + schema, + shell_records, + snmp_agent_table, snmp_local_db2, snmp_mib_data, snmp_note_store, snmp_symbolic_ets, + tkFun, tkLink, tkPriv, + ttb, ttb_history_table, + udp_fds, udp_pids + ]. + +sys_processes() -> + [auth, code_server, global_name_server, inet_db, + mnesia_recover, net_kernel, timer_server, wxe_master]. + +mnesia_tables() -> + [ir_AliasDef, ir_ArrayDef, ir_AttributeDef, ir_ConstantDef, + ir_Contained, ir_Container, ir_EnumDef, ir_ExceptionDef, + ir_IDLType, ir_IRObject, ir_InterfaceDef, ir_ModuleDef, + ir_ORB, ir_OperationDef, ir_PrimitiveDef, ir_Repository, + ir_SequenceDef, ir_StringDef, ir_StructDef, ir_TypedefDef, + ir_UnionDef, logTable, logTransferTable, mesh_meas, + mesh_type, mnesia_clist, orber_CosNaming, + orber_objkeys, user + ]. + +handle_error(Foo) -> + try + Str = io_lib:format("ERROR: ~s~n",[Foo]), + display_info(Str) + catch _:_ -> + display_info(io_lib:format("ERROR: ~p~n",[Foo])) + end, + ok. + +display_info(Str) -> + Dlg = wxMessageDialog:new(wx:null(), Str), + wxMessageDialog:showModal(Dlg), + wxMessageDialog:destroy(Dlg), + ok. + +interval_dialog(Parent, Enabled, Value, Min, Max) -> + Dialog = wxDialog:new(Parent, ?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), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + 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}]), + wxWindow:setSizerAndFit(Dialog, TopSizer), + wxSizer:setSizeHints(TopSizer, Dialog), + wxCheckBox:connect(Check, command_checkbox_clicked, + [{callback, fun(#wx{event=#wxCommand{commandInt=Enable0}},_) -> + Enable = Enable0 > 0, + wxWindow:enable(Slider, [{enable, Enable}]) + end}]), + Res = case wxDialog:showModal(Dialog) of + ?wxID_OK -> + {wxCheckBox:isChecked(Check), wxSlider:getValue(Slider)}; + ?wxID_CANCEL -> + cancel + end, + wxDialog:destroy(Dialog), + Res. + + +update_grid(Grid, Opt, Tables) -> + wx:batch(fun() -> update_grid2(Grid, Opt, Tables) end). +update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> + wxListCtrl:deleteAllItems(Grid), + Update = + fun(#tab{name = Name, id = Id, owner = Owner, size = Size, memory = Memory, + protection = Protection, reg_name = RegName}, Row) -> + _Item = wxListCtrl:insertItem(Grid, Row, ""), + if (Row rem 2) =:= 0 -> + wxListCtrl:setItemBackgroundColour(Grid, Row, {240,240,255}); + true -> ignore + end, + if Protection == private -> + wxListCtrl:setItemTextColour(Grid, Row, {200,130,50}); + true -> ignore + end, + + lists:foreach(fun({_, ignore}) -> ignore; + ({Col, Val}) -> + wxListCtrl:setItem(Grid, Row, Col, to_str(Val)) + end, + [{0,Name}, {1,Id}, {2,Size}, {3, Memory div 1024}, + {4,Owner}, {5,RegName}]), + %% wxListCtrl:setItemData(Grid, Item, Item), + Row + 1 + end, + ProcInfo = case Dir of + false -> lists:reverse(lists:keysort(Sort, Tables)); + true -> lists:keysort(Sort, Tables) + end, + lists:foldl(Update, 0, ProcInfo), + ProcInfo. + +ignore(true, Reason) -> throw(Reason); +ignore(_,_ ) -> ok. -- cgit v1.2.3 From ffcacd111c61019a502a895b1edcfbc1578ddfa7 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Mon, 3 Oct 2011 14:12:32 +0200 Subject: [observer] Clean up code system tab, timer handling and "etop" code More info in system tab, same timer handling in all tabs. Remove dependency off etop process, do the roughly the same functionality on our own. --- lib/observer/src/Makefile | 3 +- lib/observer/src/observer.erl | 25 ++ lib/observer/src/observer_defs.hrl | 6 - lib/observer/src/observer_lib.erl | 199 ++++++++++ lib/observer/src/observer_pro_wx.erl | 661 ++++++++++----------------------- lib/observer/src/observer_sys.erl | 131 ------- lib/observer/src/observer_sys_wx.erl | 324 +++++++--------- lib/observer/src/observer_tv_table.erl | 18 +- lib/observer/src/observer_tv_wx.erl | 164 ++------ lib/observer/src/observer_wx.erl | 1 + 10 files changed, 586 insertions(+), 946 deletions(-) create mode 100644 lib/observer/src/observer.erl create mode 100644 lib/observer/src/observer_lib.erl delete mode 100644 lib/observer/src/observer_sys.erl (limited to 'lib/observer') diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 92319434c0..9ea4118809 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -41,10 +41,11 @@ MODULES= \ etop_gui \ etop_tr \ etop_txt \ + observer \ + observer_lib \ observer_wx \ observer_pro_wx \ observer_procinfo \ - observer_sys \ observer_sys_wx \ observer_trace_wx \ observer_traceoptions_wx \ diff --git a/lib/observer/src/observer.erl b/lib/observer/src/observer.erl new file mode 100644 index 0000000000..098100e8ee --- /dev/null +++ b/lib/observer/src/observer.erl @@ -0,0 +1,25 @@ +%% +%% %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). + +-export([start/0]). + + +start() -> + observer_wx:start(). diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index 7990cc248a..0af15f6422 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -16,12 +16,6 @@ %% %% %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, diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl new file mode 100644 index 0000000000..cab9d5ccf2 --- /dev/null +++ b/lib/observer/src/observer_lib.erl @@ -0,0 +1,199 @@ +%% +%% %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_lib). + +-export([get_wx_parent/1, + display_info_dialog/1, + interval_dialog/4, start_timer/1, stop_timer/1, + display_info/2, update_info/2, to_str/1]). + +-include_lib("wx/include/wx.hrl"). + +get_wx_parent(Window) -> + Parent = wxWindow:getParent(Window), + case wx:is_null(Parent) of + true -> Window; + false -> get_wx_parent(Parent) + end. + +interval_dialog(Parent0, {Timer, Value}, Min, Max) -> + Parent = get_wx_parent(Parent0), + Dialog = wxDialog:new(Parent, ?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, Timer /= false), + 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, Timer /= false}]), + InnerSizer = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + 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}]), + wxWindow:setSizerAndFit(Dialog, TopSizer), + wxSizer:setSizeHints(TopSizer, Dialog), + wxCheckBox:connect(Check, command_checkbox_clicked, + [{callback, fun(#wx{event=#wxCommand{commandInt=Enable0}},_) -> + Enable = Enable0 > 0, + wxWindow:enable(Slider, [{enable, Enable}]) + end}]), + Res = case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Enabled = wxCheckBox:isChecked(Check), + setup_timer(Enabled, {Timer, wxSlider:getValue(Slider)}); + ?wxID_CANCEL -> + {Timer, Value} + end, + wxDialog:destroy(Dialog), + Res. + +stop_timer(Timer = {false, _}) -> Timer; +stop_timer(Timer = {true, _}) -> Timer; +stop_timer(Timer = {_, Intv}) -> + setup_timer(false, Timer), + {true, Intv}. +start_timer(Intv) when is_integer(Intv) -> + setup_timer(true, {true, Intv}); +start_timer(Timer) -> + setup_timer(true, Timer). + +setup_timer(false, {Timer, Value}) + when is_boolean(Timer) -> + {false, Value}; +setup_timer(true, {false, Value}) -> + {ok, Timer} = timer:send_interval(Value * 1000, refresh_interval), + {Timer, Value}; +setup_timer(Bool, {Timer, Old}) -> + timer:cancel(Timer), + setup_timer(Bool, {false, Old}). + +display_info_dialog(Str) -> + Dlg = wxMessageDialog:new(wx:null(), Str), + wxMessageDialog:showModal(Dlg), + wxMessageDialog:destroy(Dlg), + ok. + +%% display_info(Parent, [{Title, [{Label, Info}]}]) -> {Panel, Sizer, InfoFieldsToUpdate} +display_info(Frame, Info) -> + Panel = wxPanel:new(Frame), + wxWindow:setBackgroundColour(Panel, {255,255,255}), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:addSpacer(Sizer, 5), + Add = fun(BoxInfo) -> + {Box, InfoFs} = create_box(Panel, BoxInfo), + wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}]), + wxSizer:addSpacer(Sizer, 5), + InfoFs + end, + InfoFs = [Add(I) || I <- Info], + wxWindow:setSizerAndFit(Panel, Sizer), + {Panel, Sizer, InfoFs}. + +update_info([Fields|Fs], [{_Header, SubStructure}| Rest]) -> + update_info2(Fields, SubStructure), + update_info(Fs, Rest); +update_info([Fields|Fs], [{_Header, _Attrib, SubStructure}| Rest]) -> + update_info2(Fields, SubStructure), + update_info(Fs, Rest); +update_info([], []) -> + ok. + +update_info2([Field|Fs], [{_Str, Value}|Rest]) -> + wxStaticText:setLabel(Field, to_str(Value)), + update_info2(Fs, Rest); +update_info2([], []) -> ok. + + +to_str(Value) when is_atom(Value) -> + atom_to_list(Value); +to_str({bytes, B}) -> + KB = B div 1024, + MB = KB div 1024, + if + MB > 10 -> integer_to_list(MB) ++ " mB"; + KB > 0 -> integer_to_list(KB) ++ " kB"; + true -> integer_to_list(B) ++ " B " + end; +to_str({time_ms, MS}) -> + S = MS div 1000, + Min = S div 60, + Hours = Min div 60, + Days = Hours div 24, + if + Days > 0 -> integer_to_list(Days) ++ " Days"; + Hours > 0 -> integer_to_list(Hours) ++ " Hours"; + Min > 0 -> integer_to_list(Min) ++ " Mins"; + true -> integer_to_list(S) ++ " Secs" + end; + +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}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_box_info({Title, List}) when is_list(List) -> {Title, ?wxALIGN_LEFT, List}; +get_box_info({Title, left, List}) -> {Title, ?wxALIGN_LEFT, List}; +get_box_info({Title, right, List}) -> {Title, ?wxALIGN_RIGHT, List}. + +create_box(Panel, Data) -> + {Title, Align, Info} = get_box_info(Data), + Box = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, Title}]), + Left = wxBoxSizer:new(?wxVERTICAL), + Right = wxBoxSizer:new(?wxVERTICAL), + Expand = [{flag, ?wxEXPAND}], + ExpAlign = [{flag, Align}], + AddRow = fun({Desc, Value}) -> + wxSizer:add(Left, wxStaticText:new(Panel, ?wxID_ANY, Desc ++ ":"), Expand), + Field = wxStaticText:new(Panel, ?wxID_ANY, to_str(Value)), + wxSizer:add(Right, Field, ExpAlign), + Field + end, + InfoFields = [AddRow(Entry) || Entry <- Info], + wxSizer:add(Box, Left), + wxSizer:addSpacer(Box, 10), + wxSizer:add(Box, Right), + wxSizer:addSpacer(Box, 30), + {Box, InfoFields}. diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 7080da8231..c99abd371a 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -23,19 +23,19 @@ %% 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]). + handle_event/2, handle_cast/2]). -include_lib("wx/include/wx.hrl"). --include_lib("runtime_tools/include/observer_backend.hrl"). +-include("../include/etop.hrl"). -include("observer_defs.hrl"). +-include("etop_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_MEM, 4). -define(COL_MSG, 5). -define(COL_FUN, 6). @@ -47,66 +47,57 @@ -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 +-define(ID_ACCUMULATE, 209). %% Records -record(attrs, {even, odd, deleted, changed, searched}). +-record(sort, + { + sort_key=?COL_REDS, + sort_incr=false + }). + -record(holder, {parent, info, - attrs}). - + sort = #sort{}, + accum = [], + attrs, + node, + backend_pid + }). -record(pro_wx_state, {parent, - etop_monitor, - holder_monitor, grid, panel, popup_menu, parent_notebook, trace_options = #trace_options{}, match_specs = [], - refr_timer = false, + timer, 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), + Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end), + {ProPanel, State} = setup(Notebook, Parent, Holder), 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, State#pro_wx_state{holder = Holder, match_specs = MatchSpecs}}. + +setup(Notebook, Parent, Holder) -> ProPanel = wxPanel:new(Notebook, []), - Grid = create_list_box(ProPanel, Holder, Count), + Grid = create_list_box(ProPanel, Holder), Sizer = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, @@ -122,7 +113,9 @@ setup(Notebook, Parent, Holder, Count) -> popup_menu = Popup, parent_notebook = Notebook, tracemenu_opened = false, - holder = Holder}, + holder = Holder, + timer = {false, 10} + }, {ProPanel, State}. generate_matchspecs() -> @@ -152,14 +145,16 @@ generate_matchspecs() -> %% UI-creation -create_pro_menu(Parent) -> - MenuEntries = [{"View", - [#create_menu{id = ?ID_REFRESH, text = "Refresh"}, +create_pro_menu(Parent, Holder) -> + MenuEntries = [{"File", + [#create_menu{id = ?ID_DUMP_TO_FILE, text = "Dump to file"}]}, + {"View", + [#create_menu{id = ?ID_ACCUMULATE, text = "Accumulate", + type = check, + check = call(Holder, {get_accum, self()})}, + separator, + #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, #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"}, @@ -188,13 +183,17 @@ create_popup_menu(ParentFrame) -> wxSizer:setSizeHints(Sizer, MiniFrame), MiniFrame. -create_list_box(Panel, Holder, Count) -> +create_list_box(Panel, Holder) -> Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL, ListCtrl = wxListCtrl:new(Panel, [{style, Style}, {onGetItemText, - fun(_, Item, Col) -> get_row(Holder, Item, Col) end}, + fun(_, Row, Col) -> + call(Holder, {get_row, self(), Row, Col}) + end}, {onGetItemAttr, - fun(_, Item) -> get_attr(Holder, Item) end} + fun(_, Item) -> + call(Holder, {get_attr, self(), Item}) + end} ]), Li = wxListItem:new(), AddListEntry = fun({Name, Align, DefSize}, Col) -> @@ -207,9 +206,9 @@ create_list_box(Panel, Holder, Count) -> 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}, + {"Reds", ?wxLIST_FORMAT_RIGHT, 100}, + {"Memory", ?wxLIST_FORMAT_RIGHT, 100}, + {"MsgQ", ?wxLIST_FORMAT_RIGHT, 50}, {"Current Function", ?wxLIST_FORMAT_LEFT, 200}], lists:foldl(AddListEntry, 0, ListItems), wxListItem:destroy(Li), @@ -219,111 +218,8 @@ create_list_box(Panel, Holder, Count) -> 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), @@ -338,7 +234,7 @@ set_selected_items(Grid, Holder, Pids) -> 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), + {ok, Pid} = call(Holder, {get_row, self(), Index, pid}), case lists:member(Pid, Pids) of true -> wxListCtrl:setItemState(Grid, Index, @@ -353,8 +249,8 @@ 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}]), + Item = wxListCtrl:getNextItem(Grid, Index, [{geometry, ?wxLIST_NEXT_ALL}, + {state, ?wxLIST_STATE_SELECTED}]), case Item of -1 -> lists:reverse(ItemAcc); @@ -362,117 +258,6 @@ get_selected_items(Grid, Index, 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), @@ -484,6 +269,7 @@ create_attrs() -> dump_to_file(Parent, FileName, Holder) -> case file:open(FileName, [write]) of {ok, Fd} -> + %% Holder closes the file when it's done Holder ! {dump, Fd}; {error, Reason} -> FailMsg = file:format_error(Reason), @@ -501,67 +287,34 @@ start_procinfo(Node, Pid, Frame, Opened) -> [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) -> +call(Holder, What) -> Ref = erlang:monitor(process, Holder), - Holder ! {get_row, self(), Row, Column}, + Holder ! What, receive {'DOWN', Ref, _, _, _} -> ""; {Holder, Res} -> erlang:demonitor(Ref), Res + after 2000 -> + io:format("Hanging call ~p~n",[What]) 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:setItemCount(Grid, Count), 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), +handle_info(refresh_interval, #pro_wx_state{holder = Holder} = State) -> + Holder ! refresh, {noreply, State}; handle_info({tracemenu_closed, TraceOpts, MatchSpecs}, State) -> @@ -574,38 +327,18 @@ handle_info({procinfo_menu_closed, Pid}, 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({active, Node}, + #pro_wx_state{holder = Holder, timer = Timer, parent = Parent} = State) -> + create_pro_menu(Parent, Holder), + Holder ! {change_node, Node}, + {noreply, State#pro_wx_state{timer = observer_lib:start_timer(Timer)}}; -handle_info({node, Node}, #pro_wx_state{holder = Holder, sort_dir = Dir} = State) -> - change_node(Node), - refresh_grid(Holder, Dir), +handle_info(not_active, #pro_wx_state{timer = Timer0} = State) -> + Timer = observer_lib:stop_timer(Timer0), + {noreply, State#pro_wx_state{timer=Timer, selected_pids = [], last_selected = undefined}}; + +handle_info({node, Node}, #pro_wx_state{holder = Holder} = State) -> + Holder ! {change_node, Node}, {noreply, State#pro_wx_state{selected_pids = [], last_selected = undefined}}; @@ -649,57 +382,21 @@ handle_event(#wx{id = ?ID_DUMP_TO_FILE}, 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), +handle_event(#wx{id = ?ID_ACCUMULATE, + event = #wxCommand{type = command_menu_selected, commandInt = CmdInt}}, + #pro_wx_state{holder = Holder} = State) -> + Holder ! {accum, CmdInt =:= 1}, {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), + #pro_wx_state{holder = Holder} = State) -> + Holder ! refresh, {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; + #pro_wx_state{panel = Panel, timer=Timer0} = State) -> + Timer = observer_lib:interval_dialog(Panel, Timer0, 1, 5*60), + {noreply, State#pro_wx_state{timer=Timer}}; handle_event(#wx{id = ?ID_KILL}, #pro_wx_state{popup_menu = Pop, selected_pids = Pids, @@ -712,17 +409,19 @@ handle_event(#wx{id = ?ID_KILL}, #pro_wx_state{popup_menu = Pop, handle_event(#wx{id = ?ID_PROC}, - #pro_wx_state{panel = Panel, + #pro_wx_state{holder=Holder, + panel = Panel, popup_menu = Pop, last_selected = Pid, procinfo_menu_pids = Opened} = State) -> wxWindow:show(Pop, [{show, false}]), - Node = get_node(), + Node = call(Holder, {get_node, self()}), 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, + #pro_wx_state{holder=Holder, + popup_menu = Pop, trace_options = Options, match_specs = MatchSpecs, selected_pids = Pids, @@ -734,7 +433,7 @@ handle_event(#wx{id = ?ID_TRACEMENU}, observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION), {noreply, State}; Pids -> - Node = get_node(), + Node = call(Holder, {get_node, self()}), observer_trace_wx:start(Node, Pids, Options, @@ -745,11 +444,12 @@ handle_event(#wx{id = ?ID_TRACEMENU}, end; handle_event(#wx{id = ?ID_TRACE_ALL_MENU, event = #wxCommand{type = command_menu_selected}}, - #pro_wx_state{trace_options = Options, + #pro_wx_state{holder=Holder, + trace_options = Options, match_specs = MatchSpecs, tracemenu_opened = false, panel = Panel} = State) -> - Node = get_node(), + Node = call(Holder, {get_node, self()}), observer_trace_wx:start(Node, all, Options, @@ -760,11 +460,12 @@ handle_event(#wx{id = ?ID_TRACE_ALL_MENU, event = #wxCommand{type = command_menu handle_event(#wx{id = ?ID_TRACE_NEW_MENU, event = #wxCommand{type = command_menu_selected}}, - #pro_wx_state{trace_options = Options, + #pro_wx_state{holder=Holder, + trace_options = Options, match_specs = MatchSpecs, tracemenu_opened = false, panel = Panel} = State) -> - Node = get_node(), + Node = call(Holder, {get_node, self()}), observer_trace_wx:start(Node, new, Options, @@ -790,7 +491,7 @@ handle_event(#wx{event = #wxList{type = command_list_item_right_click, #pro_wx_state{popup_menu = Popup, holder = Holder} = State) -> - case get_row(Holder, Row, pid) of + case call(Holder, {get_row, self(), Row, pid}) of {error, undefined} -> wxWindow:show(Popup, [{show, false}]), undefined; @@ -806,29 +507,28 @@ handle_event(#wx{event = #wxList{type = command_list_item_selected, popup_menu = Pop, holder = Holder} = State) -> - NewPid = case get_row(Holder, Row, pid) of + NewPid = case call(Holder, {get_row, self(), 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)), + Pids = call(Holder, {get_pids, self(), 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}}; + #pro_wx_state{holder = Holder} = State) -> + Holder ! {change_sort, Col}, + {noreply, State}; handle_event(#wx{event = #wxList{type = command_list_item_activated}}, - #pro_wx_state{panel = Panel, + #pro_wx_state{holder=Holder, + panel = Panel, procinfo_menu_pids= Opened, last_selected = Pid} = State) when Pid =/= undefined -> - Node = get_node(), + Node = call(Holder, {get_node, self()}), Opened2 = start_procinfo(Node, Pid, Panel, Opened), {noreply, State#pro_wx_state{procinfo_menu_pids = Opened2}}; @@ -837,56 +537,59 @@ handle_event(Event, State) -> {noreply, State}. - - - - - - - - - - - - - - - - - - - - - - - %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init_table_holder(Parent, Info, Attrs) -> +init_table_holder(Parent, Attrs) -> + Backend = spawn_link(node(), observer_backend,etop_collect,[self()]), table_holder(#holder{parent = Parent, - info = Info, - attrs = Attrs}). - - -table_holder(#holder{parent = Parent, - info = Info, - attrs = Attrs} = S0) -> + info = #etop_info{procinfo=[]}, + node = node(), + backend_pid = Backend, + attrs = Attrs + }). + +table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, + node=Node, backend_pid=Backend} = S0) -> receive {get_row, From, Row, Col} -> - get_row(From, Row, Col, Info#etop_info.procinfo), + get_row(From, Row, Col, Info), table_holder(S0); {get_attr, From, Row} -> get_attr(From, Row, Attrs), table_holder(S0); + {Backend, EtopInfo = #etop_info{}} -> + State = handle_update(EtopInfo, S0), + table_holder(State#holder{backend_pid=undefined}); + refresh when is_pid(Backend)-> + table_holder(S0); %% Already updating + refresh -> + Pid = spawn_link(Node,observer_backend,etop_collect,[self()]), + table_holder(S0#holder{backend_pid=Pid}); + {change_sort, Col} -> + State = change_sort(Col, S0), + table_holder(State); {get_pids, From, Indices} -> - get_pids(From, Indices, Info#etop_info.procinfo), + get_pids(From, Indices, Info), + table_holder(S0); + {get_node, From} -> + From ! {self(), Node}, + table_holder(S0); + {change_node, NewNode} -> + case Node == NewNode of + true -> + table_holder(S0); + false -> + self() ! refresh, + table_holder(S0#holder{node=NewNode}) + end; + {accum, Bool} -> + table_holder(change_accum(Bool,S0)); + {get_accum, From} -> + From ! {self(), S0#holder.accum == true}, 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}, + etop_txt:do_update(Fd, S0#holder.info, #opts{node=Node}), + file:close(Fd), table_holder(S0); stop -> ok; @@ -895,33 +598,69 @@ table_holder(#holder{parent = Parent, 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. +change_sort(Col, S0 = #holder{parent=Parent, info=EI=#etop_info{procinfo=Data}, sort=Sort0}) -> + {Sort, ProcInfo} = sort(Col, Sort0, Data), + Parent ! {holder_updated, length(Data)}, + S0#holder{info=EI#etop_info{procinfo=ProcInfo}, sort=Sort}. + +change_accum(true, S0) -> + S0#holder{accum=true}; +change_accum(false, S0 = #holder{info=#etop_info{procinfo=Info}}) -> + self() ! refresh, + S0#holder{accum=lists:sort(Info)}. + +handle_update(EI=#etop_info{procinfo=ProcInfo0}, + S0 = #holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> + {ProcInfo1, S1} = accum(ProcInfo0, S0), + {_SO, ProcInfo} = sort(KeyField, Sort#sort{sort_key=undefined}, ProcInfo1), + Parent ! {holder_updated, length(ProcInfo)}, + S1#holder{info=EI#etop_info{procinfo=ProcInfo}}. + +accum(ProcInfo, State = #holder{accum=true}) -> + {ProcInfo, State}; +accum(ProcInfo0, State = #holder{accum=Previous}) -> + ProcInfo = lists:sort(ProcInfo0), + {accum2(ProcInfo,Previous,[]), State#holder{accum=ProcInfo}}. + +accum2([PI = #etop_proc_info{pid=Pid, reds=Reds, runtime=RT}|PIs], + [#etop_proc_info{pid=Pid, reds=OldReds, runtime=OldRT}|Old], Acc) -> + accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds, runtime=RT-OldRT}|Acc]); +accum2(PIs = [#etop_proc_info{pid=Pid}|_], [#etop_proc_info{pid=OldPid}|Old], Acc) + when Pid > OldPid -> + accum2(PIs, Old, Acc); +accum2([PI|PIs], Old, Acc) -> + accum2(PIs, Old, [PI|Acc]); +accum2([], _, Acc) -> Acc. + +sort(Col, Opt = #sort{sort_key=Col, sort_incr=Bool}, Table) -> + {Opt#sort{sort_incr=not Bool}, lists:reverse(Table)}; +sort(Col, S=#sort{sort_incr=true}, Table) -> + {S#sort{sort_key=Col}, lists:keysort(col_to_element(Col), Table)}; +sort(Col, S=#sort{sort_incr=false}, Table) -> + {S#sort{sort_key=Col}, lists:reverse(lists:keysort(col_to_element(Col), Table))}. + + + + + +get_procinfo_data(Col, Info) -> + element(col_to_element(Col), Info). +col_to_element(?COL_PID) -> #etop_proc_info.pid; +col_to_element(?COL_NAME) -> #etop_proc_info.name; +col_to_element(?COL_MEM) -> #etop_proc_info.mem; +col_to_element(?COL_TIME) -> #etop_proc_info.runtime; +col_to_element(?COL_REDS) -> #etop_proc_info.reds; +col_to_element(?COL_FUN) -> #etop_proc_info.cf; +col_to_element(?COL_MSG) -> #etop_proc_info.mq. get_pids(From, Indices, ProcInfo) -> - From ! {self(), - [X#etop_proc_info.pid || X <- - [lists:nth(I, ProcInfo) || I <- Indices]]}. + Processes = [lists:nth(I, ProcInfo) || I <- Indices], + From ! {self(), [X#etop_proc_info.pid || X <- Processes]}. 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))} + 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) -> @@ -936,9 +675,7 @@ get_row(From, Row, Col, Info) -> get_attr(From, Row, Attrs) -> Attribute = case Row rem 2 =:= 0 of - true -> - Attrs#attrs.even; - false -> - Attrs#attrs.odd + true -> Attrs#attrs.even; + false -> Attrs#attrs.odd end, From ! {self(), Attribute}. diff --git a/lib/observer/src/observer_sys.erl b/lib/observer/src/observer_sys.erl deleted file mode 100644 index 8db7bb0e46..0000000000 --- a/lib/observer/src/observer_sys.erl +++ /dev/null @@ -1,131 +0,0 @@ -%% -%% %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 index 5116891e91..5bc5fd49f6 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -24,6 +24,8 @@ -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). +-export([sys_info/0]). + -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). @@ -33,172 +35,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}). + parent_notebook, + panel, sizer, + menubar, + fields, + timer}). 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. +init(Args) -> + try + init2(Args) + catch E:R -> + io:format("~p:~p ~p~n",[E,R, erlang:get_stacktrace()]) + end. + +init2([Notebook, Parent]) -> + SysInfo = sys_info(), + {Info, Stat} = info_fields(), + Panel = wxPanel:new(Notebook), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + {FPanel0, _FSizer0, Fields0} = observer_lib:display_info(Panel, fill_info(Info, SysInfo)), + {FPanel1, _FSizer1, Fields1} = observer_lib:display_info(Panel, fill_info(Stat, SysInfo)), + wxSizer:add(Sizer, FPanel0, [{flag, ?wxEXPAND bor ?wxTOP bor ?wxBOTTOM bor ?wxLEFT}, + {proportion, 1}, {border, 5}]), + wxSizer:add(Sizer, FPanel1, [{flag, ?wxEXPAND bor ?wxTOP bor ?wxBOTTOM bor ?wxRIGHT}, + {proportion, 1}, {border, 5}]), + wxPanel:setSizer(Panel, Sizer), + Timer = observer_lib:start_timer(10), + {Panel, #sys_wx_state{parent=Parent, + parent_notebook=Notebook, + panel=Panel, sizer=Sizer, + timer=Timer, fields=Fields0 ++ Fields1}}. create_sys_menu(Parent) -> - View = {"View", [#create_menu{id = ?ID_REFRESH, text = "Refresh"}, + View = {"View", [#create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, #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)). +update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) -> + try + SysInfo = observer_wx:try_rpc(Node, ?MODULE, sys_info, []), + {Info, Stat} = info_fields(), + observer_lib:update_info(Fields, fill_info(Info, SysInfo) ++ fill_info(Stat, SysInfo)), + wxSizer:layout(Sizer) + catch E:R -> + io:format("~p:~p ~p~n",[E,R, erlang:get_stacktrace()]) + end, + ok. +info_fields() -> + Info = [{"System and Architecture", + [{"System Version", otp_release}, + {"Erts Version", version}, + {"Compiled for", system_architecture}, + {"Emulator Wordsize", wordsize_external}, + {"Process Wordsize", wordsize_internal}, + {"Smp Support", smp_support}, + {"Thread Support", threads}, + {"Async thread pool size", thread_pool_size} + ]}, + {"CPU's and Threads", + [{"System Logical CPU's", logical_processors}, + {"Erlang Logical CPU's", logical_processors_online}, + {"Used Logical CPU's", logical_processors_available} + ]} + ], + Stat = [{"Memory Usage", right, + [{"Total", total}, + {"Processes", processes}, + {"Atoms", atom}, + {"Binaries", binary}, + {"Code", code}, + {"Ets", ets} + ]}, + {"Statistics", right, + [{"Up time", uptime}, + {"Max Processes", process_limit}, + {"Processes", process_count}, + {"Run Queue", run_queue}, + {"IO Input", io_input}, + {"IO Output", io_output} + ]} + ], + {Info, Stat}. + +fill_info([{Str, Key}|Rest], Data) when is_atom(Key) -> + [{Str, proplists:get_value(Key,Data)} | fill_info(Rest, Data)]; +fill_info([{Str,SubStructure}|Rest], Data) -> + [{Str, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; +fill_info([{Str,Attrib,SubStructure}|Rest], Data) -> + [{Str, Attrib, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; +fill_info([], _) -> []. %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -216,45 +148,26 @@ handle_info({node, Node}, #sys_wx_state{panel = Panel} = State) -> 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) -> +handle_info({active, Node}, #sys_wx_state{parent = Parent, panel = Panel, + timer = Timer} = 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}} - + {noreply, UpdState#sys_wx_state{timer=observer_lib:start_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(not_active, #sys_wx_state{timer = Timer} = State) -> + {noreply, State#sys_wx_state{timer = observer_lib:stop_timer(Timer)}}; handle_info(Info, State) -> io:format("~p, ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), @@ -286,28 +199,43 @@ handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_select 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; + #sys_wx_state{timer = Timer0, parent_notebook = Notebook} = State) -> + Timer = observer_lib:interval_dialog(Notebook, Timer0, 1, 5*60), + {noreply, State#sys_wx_state{timer=Timer}}; handle_event(Event, State) -> io:format("handle event ~p\n", [Event]), {noreply, State}. + + +sys_info() -> + {{_,Input},{_,Output}} = erlang:statistics(io), + [{process_count, erlang:system_info(process_count)}, + {process_limit, erlang:system_info(process_limit)}, + {uptime, {time_ms, element(1, erlang:statistics(wall_clock))}}, + {run_queue, erlang:statistics(run_queue)}, + {io_input, {bytes, Input}}, + {io_output, {bytes, Output}}, + {logical_processors, erlang:system_info(logical_processors)}, + {logical_processors_available, erlang:system_info(logical_processors_available)}, + {logical_processors_online, erlang:system_info(logical_processors_online)}, + + {total, {bytes, erlang:memory(total)}}, + %%{processes_used, erlang:memory(processes_used)}, + {processes, {bytes, erlang:memory(processes)}}, + %%{atom_used, erlang:memory(atom_used)}, + {atom, {bytes, erlang:memory(atom)}}, + {binary, {bytes, erlang:memory(binary)}}, + {code, {bytes, erlang:memory(code)}}, + {ets, {bytes, erlang:memory(ets)}}, + + {otp_release, erlang:system_info(otp_release)}, + {version, erlang:system_info(version)}, + {system_architecture, erlang:system_info(system_architecture)}, + {kernel_poll, erlang:system_info(kernel_poll)}, + {smp_support, erlang:system_info(smp_support)}, + {threads, erlang:system_info(threads)}, + {thread_pool_size, erlang:system_info(thread_pool_size)}, + {wordsize_internal, erlang:system_info({wordsize, internal})}, + {wordsize_external, erlang:system_info({wordsize, external})} + ]. diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 3228f7e571..a2797700d8 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -26,7 +26,7 @@ -export([get_table/3]). --import(observer_pro_wx, [to_str/1]). +-import(observer_lib, [to_str/1]). -behaviour(wx_object). -include_lib("wx/include/wx.hrl"). @@ -58,7 +58,8 @@ pid, source, tab, - attrs + attrs, + timer }). -record(opt, @@ -401,6 +402,11 @@ handle_event(#wx{id=?ID_TABLE_INFO}, observer_tv_wx:display_table_info(Frame, Node, Source, Table), {noreply, State}; +handle_event(#wx{id=?ID_REFRESH_INTERVAL}, + State = #state{grid=Grid, timer=Timer0}) -> + Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), + {noreply, State#state{timer=Timer}}; + handle_event(Event, State) -> io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. @@ -586,10 +592,10 @@ sort(Col, S=#holder{n=N, parent=Parent, sort=Opt0, table=Table0}) -> sort(Col, Opt = #opt{sort_key=Col, sort_incr=Bool}, Table) -> {Opt#opt{sort_incr=not Bool}, lists:reverse(Table)}; -sort(Col, #opt{sort_incr=true}, Table) -> - {#opt{sort_key=Col}, keysort(Col, Table)}; -sort(Col, #opt{sort_incr=false}, Table) -> - {#opt{sort_key=Col}, lists:reverse(keysort(Col, Table))}. +sort(Col, S=#opt{sort_incr=true}, Table) -> + {S#opt{sort_key=Col}, keysort(Col, Table)}; +sort(Col, S=#opt{sort_incr=false}, Table) -> + {S=#opt{sort_key=Col}, lists:reverse(keysort(Col, Table))}. keysort(Col, Table) -> Sort = fun([A0|_], [B0|_]) -> diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 10b29cb1b7..230195c9de 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -25,10 +25,6 @@ -export([get_table_list/1]). %% RPC called move to runtime tools? --export([get_wx_parent/1, interval_dialog/5]). - --import(observer_pro_wx, [to_str/1]). - -behaviour(wx_object). -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). @@ -58,8 +54,7 @@ opt=#opt{}, selected, tabs, - refr_timer=false, - refr_intv=30 + timer }). start_link(Notebook, Parent) -> @@ -97,7 +92,8 @@ init([Notebook, Parent]) -> wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), - {Panel, #state{grid=Grid, parent=Parent}}. + Timer = observer_lib:start_timer(10), + {Panel, #state{grid=Grid, parent=Parent, timer=Timer}}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> @@ -164,31 +160,14 @@ handle_event(#wx{id=?ID_TABLE_INFO}, {noreply, State}; R when is_integer(R) -> Table = lists:nth(Sel+1, Tabs), - Parent = get_wx_parent(Grid), - display_table_info(Parent, Node, Type, Table), + display_table_info(Grid, Node, Type, Table), {noreply, State} end; handle_event(#wx{id=?ID_REFRESH_INTERVAL}, - State = #state{grid=Grid, refr_timer=Timer0, refr_intv=Intv0}) -> - Parent = get_wx_parent(Grid), - case interval_dialog(Parent, Timer0 /= false, Intv0, 10, 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#state{refr_timer=Timer, refr_intv=Intv}}; - {false, _} -> - case Timer0 of - false -> ok; - _ -> timer:cancel(Timer0) - end, - {noreply, State#state{refr_timer=false}} - end; + State = #state{grid=Grid, timer=Timer0}) -> + Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), + {noreply, State#state{timer=Timer}}; handle_event(Event, State) -> io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), @@ -207,36 +186,22 @@ handle_cast(Event, State) -> {noreply, State}. handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) -> - io:format("refresh interval ~p~n", [time()]), Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), {noreply, State#state{tabs=Tabs}}; -handle_info({active, Node}, - State = #state{parent=Parent, grid=Grid, opt=Opt, - refr_timer = Refr, refr_intv=Intv}) -> +handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, + timer=Timer0}) -> Tables = get_tables(Node, Opt), Tabs = update_grid(Grid, Opt, Tables), wxWindow:setFocus(Grid), create_menus(Parent, Opt), - Timer = case Refr of - true -> - {ok, Ref} = timer:send_interval(Intv*1000, refresh_interval), - Ref; - false -> - false - end, - {noreply, State#state{node=Node, tabs=Tabs, refr_timer=Timer}}; - -handle_info(not_active, State = #state{refr_timer = Timer0}) -> - Timer = case Timer0 of - false -> false; - true -> true; - Timer0 -> - timer:cancel(Timer0), - true - end, - {noreply, State#state{refr_timer=Timer}}; + Timer = observer_lib:start_timer(Timer0), + {noreply, State#state{node=Node, tabs=Tabs, timer=Timer}}; + +handle_info(not_active, State = #state{timer = Timer0}) -> + Timer = observer_lib:stop_timer(Timer0), + {noreply, State#state{timer=Timer}}; handle_info({node, Node}, State = #state{grid=Grid, opt=Opt}) -> Tables = get_tables(Node, Opt), @@ -370,7 +335,8 @@ get_table_list(#opt{type=mnesia, sys_hidden=HideSys}) -> end, lists:foldl(Info, [], mnesia:system_info(tables)). -display_table_info(Parent, Node, Source, Table) -> +display_table_info(Parent0, Node, Source, Table) -> + Parent = observer_lib:get_wx_parent(Parent0), Title = "Table Info: " ++ atom_to_list(Table#tab.name), Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, [{style, ?wxCAPTION bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), @@ -407,10 +373,10 @@ display_table_info(Parent, Node, Source, Table) -> | MnesiaSettings ]}, Memory = {"Memory Usage", [{"Number of objects", Table#tab.size}, - {"Memory allocated", integer_to_list(Table#tab.memory div 1024) ++ "kB"}, + {"Memory allocated", {bytes, Table#tab.memory}}, {"Compressed", Table#tab.compressed}]}, - Sizer = display_info_wx(Frame, [IdInfo, Settings, Memory]), + {_, Sizer, _} = observer_lib:display_info(Frame, [IdInfo,Settings,Memory]), wxSizer:setSizeHints(Sizer, Frame), wxFrame:center(Frame), wxFrame:show(Frame). @@ -420,44 +386,6 @@ list_to_strings([A]) -> integer_to_list(A); list_to_strings([A,B]) -> integer_to_list(A) ++ " ," ++ list_to_strings(B). -get_wx_parent(Window) -> - Parent = wxWindow:getParent(Window), - case wx:is_null(Parent) of - true -> Window; - false -> get_wx_parent(Parent) - end. - -display_info_wx(Frame, Info) -> - Panel = wxPanel:new(Frame), - wxWindow:setBackgroundColour(Panel, {255,255,255}), - Sizer = wxBoxSizer:new(?wxVERTICAL), - wxSizer:addSpacer(Sizer, 5), - Add = fun(BoxInfo) -> - Box = create_box(Panel, BoxInfo), - wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL}, - {border, 5}]) - end, - [Add(I) || I <- Info], - wxSizer:addSpacer(Sizer, 5), - wxWindow:setSizerAndFit(Panel, Sizer), - Sizer. - -create_box(Panel, {Title, Info}) -> - Box = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, Title}]), - Left = wxBoxSizer:new(?wxVERTICAL), - Right = wxBoxSizer:new(?wxVERTICAL), - Expand = [{flag, ?wxEXPAND}], - AddRow = fun({Desc, Value}) -> - wxSizer:add(Left, wxStaticText:new(Panel, ?wxID_ANY, Desc ++ ":"), Expand), - wxSizer:add(Right, wxStaticText:new(Panel, ?wxID_ANY, to_str(Value)), Expand) - end, - [AddRow(Entry) || Entry <- Info], - wxSizer:add(Box, Left), - wxSizer:addSpacer(Box, 10), - wxSizer:add(Box, Right), - wxSizer:addSpacer(Box, 30), - Box. - sys_tables() -> [ac_tab, asn1, cdv_dump_index_table, cdv_menu_table, cdv_decode_heap_table, @@ -512,56 +440,8 @@ mnesia_tables() -> ]. handle_error(Foo) -> - try - Str = io_lib:format("ERROR: ~s~n",[Foo]), - display_info(Str) - catch _:_ -> - display_info(io_lib:format("ERROR: ~p~n",[Foo])) - end, - ok. - -display_info(Str) -> - Dlg = wxMessageDialog:new(wx:null(), Str), - wxMessageDialog:showModal(Dlg), - wxMessageDialog:destroy(Dlg), - ok. - -interval_dialog(Parent, Enabled, Value, Min, Max) -> - Dialog = wxDialog:new(Parent, ?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), - Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), - 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}]), - wxWindow:setSizerAndFit(Dialog, TopSizer), - wxSizer:setSizeHints(TopSizer, Dialog), - wxCheckBox:connect(Check, command_checkbox_clicked, - [{callback, fun(#wx{event=#wxCommand{commandInt=Enable0}},_) -> - Enable = Enable0 > 0, - wxWindow:enable(Slider, [{enable, Enable}]) - end}]), - Res = case wxDialog:showModal(Dialog) of - ?wxID_OK -> - {wxCheckBox:isChecked(Check), wxSlider:getValue(Slider)}; - ?wxID_CANCEL -> - cancel - end, - wxDialog:destroy(Dialog), - Res. - + Str = io_lib:format("ERROR: ~s~n",[Foo]), + observer_lib:display_info_dialog(Str). update_grid(Grid, Opt, Tables) -> wx:batch(fun() -> update_grid2(Grid, Opt, Tables) end). @@ -582,7 +462,7 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> lists:foreach(fun({_, ignore}) -> ignore; ({Col, Val}) -> - wxListCtrl:setItem(Grid, Row, Col, to_str(Val)) + wxListCtrl:setItem(Grid, Row, Col, observer_lib:to_str(Val)) end, [{0,Name}, {1,Id}, {2,Size}, {3, Memory div 1024}, {4,Owner}, {5,RegName}]), diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index b4a85cd43b..ef3c6bb304 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -77,6 +77,7 @@ init(_Args) -> UpdState = setup(State), wxFrame:show(Frame), net_kernel:monitor_nodes(true), + process_flag(trap_exit, true), {Frame, UpdState}. setup(#state{frame = Frame} = State) -> -- cgit v1.2.3 From 87487a2534d3e790f65ac5b90cfc497d7d05dd86 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Fri, 7 Oct 2011 15:35:16 +0200 Subject: [observer] Misc cleanup and fixes Make sure that the menus work on MacOsX. Fix asserts on debug build on linux. Make sure epmd is started before distribution. Rewrote multi-selection code observer_pro_wx. --- lib/observer/src/Makefile | 2 +- lib/observer/src/observer_defs.hrl | 2 + lib/observer/src/observer_lib.erl | 77 +++++- lib/observer/src/observer_pro_wx.erl | 428 +++++++++++++++++---------------- lib/observer/src/observer_procinfo.erl | 9 +- lib/observer/src/observer_trace_wx.erl | 31 +-- lib/observer/src/observer_tv_table.erl | 19 +- lib/observer/src/observer_tv_wx.erl | 2 +- lib/observer/src/observer_wx.erl | 180 +++++++------- 9 files changed, 403 insertions(+), 347 deletions(-) (limited to 'lib/observer') diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 9ea4118809..95954d8587 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -65,7 +65,7 @@ EXAMPLE_FILES= multitrace.erl TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) PRIVDIR= ../priv -WEBTOOLFILES= $(PRIVDIR)/crashdump_viewer.tool +WEBTOOLFILES= $(PRIVDIR)/crashdump_viewer.tool $(PRIVDIR)/erlang_observer.png BINDIR= $(PRIVDIR)/bin ifeq ($(findstring win32,$(TARGET)),win32) WIN32_EXECUTABLES= $(BINDIR)/etop.bat $(BINDIR)/getop.bat $(BINDIR)/cdv.bat diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index 0af15f6422..567eb26e3b 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -47,3 +47,5 @@ type = append, check = false }). + +-record(attrs, {even, odd, deleted, changed, searched}). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index cab9d5ccf2..b36aaa7541 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -21,9 +21,13 @@ -export([get_wx_parent/1, display_info_dialog/1, interval_dialog/4, start_timer/1, stop_timer/1, - display_info/2, update_info/2, to_str/1]). + display_info/2, update_info/2, to_str/1, + create_menus/3, create_menu_item/3, + create_attrs/0 + ]). -include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). get_wx_parent(Window) -> Parent = wxWindow:getParent(Window), @@ -171,6 +175,77 @@ to_str(No) when is_integer(No) -> to_str(ShouldNotGetHere) -> erlang:error({?MODULE, to_str, ShouldNotGetHere}). +create_menus(Menus, MenuBar, Type) -> + Add = fun({Tag, Ms}, Index) -> + create_menu(Tag, Ms, Index, MenuBar, Type) + end, + [{First, _}|_] = Menus, + OnMac = os:type() =:= {unix, darwin}, + Index = if Type =:= default -> 0; + First =:= "File" -> 0; + OnMac -> 0; + true -> 1 + end, + wx:foldl(Add, Index, Menus), + ok. + +create_menu("File", MenuItems, Index, MenuBar, Type) -> + OnMac = os:type() =:= {unix, darwin}, + if OnMac, Type =:= default -> + Index; + not OnMac, Type =:= plugin -> + MenuId = wxMenuBar:findMenu(MenuBar, "File"), + Menu = wxMenuBar:getMenu(MenuBar, MenuId), + lists:foldl(fun(Record, N) -> + create_menu_item(Record, Menu, N) + end, 0, MenuItems), + Index + 1; + true -> + Menu = wxMenu:new(), + lists:foldl(fun(Record, N) -> + create_menu_item(Record, Menu, N) + end, 0, MenuItems), + wxMenuBar:insert(MenuBar, Index, Menu, "File"), + Index+1 + end; +create_menu(Name, MenuItems, Index, MenuBar, _Type) -> + Menu = wxMenu:new(), + lists:foldl(fun(Record, N) -> + create_menu_item(Record, Menu, N) + end, 0, MenuItems), + wxMenuBar:insert(MenuBar, Index, Menu, Name), + Index+1. + +create_menu_item(#create_menu{id = ?wxID_HELP=Id}, Menu, Index) -> + wxMenu:insert(Menu, Index, Id), + Index+1; +create_menu_item(#create_menu{id = Id, text = Text, type = Type, check = Check}, Menu, Index) -> + case Type of + append -> + wxMenu:insert(Menu, Index, Id, [{text, Text}]); + check -> + wxMenu:insertCheckItem(Menu, Index, Id, Text), + wxMenu:check(Menu, Id, Check); + radio -> + wxMenu:insertRadioItem(Menu, Index, Id, Text), + wxMenu:check(Menu, Id, Check); + separator -> + wxMenu:insertSeparator(Menu, Index) + end, + Index+1; +create_menu_item(separator, Menu, Index) -> + wxMenu:insertSeparator(Menu, Index), + Index+1. + +create_attrs() -> + Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + Text = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT), + #attrs{even = wxListItemAttr:new(Text, {255,255,255}, Font), + odd = wxListItemAttr:new(Text, {240,240,255}, Font), + deleted = wxListItemAttr:new({240,30,30}, {100,100,100}, Font), + changed = wxListItemAttr:new(Text, {255,215,0}, Font), + searched = wxListItemAttr:new(Text, {235,215,90}, Font) + }. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index c99abd371a..8e3e28caec 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -50,7 +50,6 @@ -define(ID_ACCUMULATE, 209). %% Records --record(attrs, {even, odd, deleted, changed, searched}). -record(sort, { @@ -60,44 +59,43 @@ -record(holder, {parent, info, - sort = #sort{}, - accum = [], + sort=#sort{}, + accum=[], attrs, node, backend_pid }). --record(pro_wx_state, {parent, - grid, - panel, - popup_menu, - parent_notebook, - trace_options = #trace_options{}, - match_specs = [], - timer, - tracemenu_opened, - procinfo_menu_pids = [], - selected_pids = [], - last_selected, - holder}). +-record(state, {parent, + grid, + panel, + popup_menu, + parent_notebook, + trace_options=#trace_options{}, + match_specs=[], + timer, + tracemenu_opened, + procinfo_menu_pids=[], + sel={[], []}, + holder}). start_link(Notebook, Parent) -> wx_object:start_link(?MODULE, [Notebook, Parent], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Notebook, Parent]) -> - Attrs = create_attrs(), + Attrs = observer_lib:create_attrs(), Self = self(), Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end), {ProPanel, State} = setup(Notebook, Parent, Holder), MatchSpecs = generate_matchspecs(), - {ProPanel, State#pro_wx_state{holder = Holder, match_specs = MatchSpecs}}. + {ProPanel, State#state{holder=Holder, match_specs=MatchSpecs}}. setup(Notebook, Parent, Holder) -> ProPanel = wxPanel:new(Notebook, []), - Grid = create_list_box(ProPanel, Holder), + Grid = create_list_box(ProPanel, Holder), Sizer = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, @@ -107,15 +105,15 @@ setup(Notebook, Parent, Holder) -> 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, - timer = {false, 10} - }, + State = #state{parent=Parent, + grid=Grid, + panel=ProPanel, + popup_menu=Popup, + parent_notebook=Notebook, + tracemenu_opened=false, + holder=Holder, + timer={false, 10} + }, {ProPanel, State}. generate_matchspecs() -> @@ -147,18 +145,18 @@ generate_matchspecs() -> create_pro_menu(Parent, Holder) -> MenuEntries = [{"File", - [#create_menu{id = ?ID_DUMP_TO_FILE, text = "Dump to file"}]}, + [#create_menu{id=?ID_DUMP_TO_FILE, text="Dump to file"}]}, {"View", - [#create_menu{id = ?ID_ACCUMULATE, text = "Accumulate", - type = check, - check = call(Holder, {get_accum, self()})}, + [#create_menu{id=?ID_ACCUMULATE, text="Accumulate", + type=check, + check=call(Holder, {get_accum, self()})}, separator, - #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, - #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval"}]}, + #create_menu{id=?ID_REFRESH, text="Refresh\tCtrl-R"}, + #create_menu{id=?ID_REFRESH_INTERVAL, text="Refresh Interval"}]}, {"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"}]} + [#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). @@ -213,59 +211,16 @@ create_list_box(Panel, Holder) -> lists:foldl(AddListEntry, 0, ListItems), wxListItem:destroy(Li), + wxListCtrl:setItemCount(ListCtrl, 1), 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), + %% Use focused instead of selected, selected doesn't generate events + %% for all multiple selections on Linux + wxListCtrl:connect(ListCtrl, command_list_item_focused), ListCtrl. -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} = call(Holder, {get_row, self(), 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. - -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} -> @@ -278,6 +233,8 @@ dump_to_file(Parent, FileName, Holder) -> wxDialog:destroy(MD) end. +start_procinfo(_Node, undefined, _Frame, Opened) -> + Opened; start_procinfo(Node, Pid, Frame, Opened) -> case lists:member(Pid, Opened) of true -> @@ -296,57 +253,53 @@ call(Holder, What) -> erlang:demonitor(Ref), Res after 2000 -> - io:format("Hanging call ~p~n",[What]) + io:format("Hanging call ~p~n",[What]), + "" end. %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_info({holder_updated, Count}, #pro_wx_state{grid = Grid, - holder = Holder, - selected_pids = Pids} = State) -> - Pids2 = wx:batch(fun() -> - clear_all(Grid), - Pids2 = set_selected_items(Grid, Holder, Pids), - wxListCtrl:setItemCount(Grid, Count), - wxListCtrl:refreshItems(Grid, 0, Count), - Pids2 - end), - {noreply, State#pro_wx_state{selected_pids = Pids2}}; - -handle_info(refresh_interval, #pro_wx_state{holder = Holder} = State) -> +handle_info({holder_updated, Count}, State0=#state{grid=Grid}) -> + State = update_selection(State0), + + wxListCtrl:setItemCount(Grid, Count), + wxListCtrl:refreshItems(Grid, 0, Count-1), + + {noreply, State}; + +handle_info(refresh_interval, #state{holder=Holder}=State) -> Holder ! refresh, {noreply, State}; handle_info({tracemenu_closed, TraceOpts, MatchSpecs}, State) -> - {noreply, State#pro_wx_state{tracemenu_opened = false, - trace_options = TraceOpts, - match_specs = MatchSpecs}}; + {noreply, State#state{tracemenu_opened=false, + trace_options=TraceOpts, + match_specs=MatchSpecs}}; handle_info({procinfo_menu_closed, Pid}, - #pro_wx_state{procinfo_menu_pids = Opened} = State) -> + #state{procinfo_menu_pids=Opened}=State) -> NewPids = lists:delete(Pid, Opened), - {noreply, State#pro_wx_state{procinfo_menu_pids = NewPids}}; + {noreply, State#state{procinfo_menu_pids=NewPids}}; handle_info({active, Node}, - #pro_wx_state{holder = Holder, timer = Timer, parent = Parent} = State) -> + #state{holder=Holder, timer=Timer, parent=Parent}=State) -> create_pro_menu(Parent, Holder), Holder ! {change_node, Node}, - {noreply, State#pro_wx_state{timer = observer_lib:start_timer(Timer)}}; + {noreply, State#state{timer=observer_lib:start_timer(Timer)}}; -handle_info(not_active, #pro_wx_state{timer = Timer0} = State) -> +handle_info(not_active, #state{timer=Timer0}=State) -> Timer = observer_lib:stop_timer(Timer0), - {noreply, State#pro_wx_state{timer=Timer, selected_pids = [], last_selected = undefined}}; + {noreply, State#state{timer=Timer}}; -handle_info({node, Node}, #pro_wx_state{holder = Holder} = State) -> +handle_info({node, Node}, #state{holder=Holder}=State) -> Holder ! {change_node, Node}, - {noreply, State#pro_wx_state{selected_pids = [], - last_selected = undefined}}; + {noreply, State}; 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}) -> +terminate(Reason, #state{holder=Holder}) -> io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), Holder ! stop, etop:stop(), @@ -367,9 +320,7 @@ handle_cast(Msg, State) -> %%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_event(#wx{id = ?ID_DUMP_TO_FILE}, - #pro_wx_state{panel = Panel, - holder = Holder} = State) -> +handle_event(#wx{id=?ID_DUMP_TO_FILE}, #state{panel=Panel, holder=Holder}=State) -> FD = wxFileDialog:new(Panel, [{style,?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), case wxFileDialog:showModal(FD) of @@ -382,51 +333,48 @@ handle_event(#wx{id = ?ID_DUMP_TO_FILE}, end, {noreply, State}; -handle_event(#wx{id = ?ID_ACCUMULATE, - event = #wxCommand{type = command_menu_selected, commandInt = CmdInt}}, - #pro_wx_state{holder = Holder} = State) -> +handle_event(#wx{id=?ID_ACCUMULATE, + event=#wxCommand{type=command_menu_selected, commandInt=CmdInt}}, + #state{holder=Holder}=State) -> Holder ! {accum, CmdInt =:= 1}, {noreply, State}; -handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_selected}}, - #pro_wx_state{holder = Holder} = State) -> +handle_event(#wx{id=?ID_REFRESH, event=#wxCommand{type=command_menu_selected}}, + #state{holder=Holder}=State) -> Holder ! refresh, {noreply, State}; -handle_event(#wx{id = ?ID_REFRESH_INTERVAL}, - #pro_wx_state{panel = Panel, timer=Timer0} = State) -> +handle_event(#wx{id=?ID_REFRESH_INTERVAL}, + #state{panel=Panel, timer=Timer0}=State) -> Timer = observer_lib:interval_dialog(Panel, Timer0, 1, 5*60), - {noreply, State#pro_wx_state{timer=Timer}}; - -handle_event(#wx{id = ?ID_KILL}, #pro_wx_state{popup_menu = Pop, - selected_pids = Pids, - last_selected = ToKill} = State) -> + {noreply, State#state{timer=Timer}}; +handle_event(#wx{id=?ID_KILL}, + #state{popup_menu=Pop,sel={[_|Ids], [ToKill|Pids]}}=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}}; + {noreply, State#state{sel={Ids,Pids}}}; -handle_event(#wx{id = ?ID_PROC}, - #pro_wx_state{holder=Holder, - panel = Panel, - popup_menu = Pop, - last_selected = Pid, - procinfo_menu_pids = Opened} = State) -> +handle_event(#wx{id=?ID_PROC}, + #state{panel=Panel, + popup_menu=Pop, + holder=Holder, + sel={_, [Pid|_]}, + procinfo_menu_pids=Opened}=State) -> wxWindow:show(Pop, [{show, false}]), Node = call(Holder, {get_node, self()}), 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{holder=Holder, - popup_menu = Pop, - trace_options = Options, - match_specs = MatchSpecs, - selected_pids = Pids, - tracemenu_opened = false, - panel = Panel} = State) -> + {noreply, State#state{procinfo_menu_pids=Opened2}}; + +handle_event(#wx{id=?ID_TRACEMENU}, + #state{holder=Holder, + popup_menu=Pop, + trace_options=Options, + match_specs=MatchSpecs, + sel=Pids, + tracemenu_opened=false, + panel=Panel}=State) -> wxWindow:show(Pop, [{show, false}]), case Pids of [] -> @@ -440,15 +388,15 @@ handle_event(#wx{id = ?ID_TRACEMENU}, MatchSpecs, Panel, self()), - {noreply, State#pro_wx_state{tracemenu_opened = true}} + {noreply, State#state{tracemenu_opened=true}} end; -handle_event(#wx{id = ?ID_TRACE_ALL_MENU, event = #wxCommand{type = command_menu_selected}}, - #pro_wx_state{holder=Holder, - trace_options = Options, - match_specs = MatchSpecs, - tracemenu_opened = false, - panel = Panel} = State) -> +handle_event(#wx{id=?ID_TRACE_ALL_MENU, event=#wxCommand{type=command_menu_selected}}, + #state{holder=Holder, + trace_options=Options, + match_specs=MatchSpecs, + tracemenu_opened=false, + panel=Panel}=State) -> Node = call(Holder, {get_node, self()}), observer_trace_wx:start(Node, all, @@ -456,15 +404,15 @@ handle_event(#wx{id = ?ID_TRACE_ALL_MENU, event = #wxCommand{type = command_menu MatchSpecs, Panel, self()), - {noreply, State#pro_wx_state{tracemenu_opened = true}}; + {noreply, State#state{tracemenu_opened=true}}; -handle_event(#wx{id = ?ID_TRACE_NEW_MENU, event = #wxCommand{type = command_menu_selected}}, - #pro_wx_state{holder=Holder, - trace_options = Options, - match_specs = MatchSpecs, - tracemenu_opened = false, - panel = Panel} = State) -> +handle_event(#wx{id=?ID_TRACE_NEW_MENU, event=#wxCommand{type=command_menu_selected}}, + #state{holder=Holder, + trace_options=Options, + match_specs=MatchSpecs, + tracemenu_opened=false, + panel=Panel}=State) -> Node = call(Holder, {get_node, self()}), observer_trace_wx:start(Node, new, @@ -472,10 +420,10 @@ handle_event(#wx{id = ?ID_TRACE_NEW_MENU, event = #wxCommand{type = command_menu MatchSpecs, Panel, self()), - {noreply, State#pro_wx_state{tracemenu_opened = true}}; + {noreply, State#state{tracemenu_opened=true}}; handle_event(#wx{event=#wxSize{size={W,_}}}, - #pro_wx_state{grid=Grid} = State) -> + #state{grid=Grid}=State) -> wx:batch(fun() -> Cols = wxListCtrl:getColumnCount(Grid), Last = lists:foldl(fun(I, Last) -> @@ -486,10 +434,10 @@ handle_event(#wx{event=#wxSize{size={W,_}}}, 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) -> +handle_event(#wx{event=#wxList{type=command_list_item_right_click, + itemIndex=Row}}, + #state{popup_menu=Popup, + holder=Holder}=State) -> case call(Holder, {get_row, self(), Row, pid}) of {error, undefined} -> @@ -501,55 +449,96 @@ handle_event(#wx{event = #wxList{type = command_list_item_right_click, 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 call(Holder, {get_row, self(), Row, pid}) of - {error, undefined} -> - undefined; - {ok, P} when is_pid(P) -> - P - end, - wxWindow:show(Pop, [{show, false}]), - Pids = call(Holder, {get_pids, self(), get_selected_items(Grid)}), - {noreply, State#pro_wx_state{selected_pids = Pids, - last_selected = NewPid}}; +handle_event(#wx{event=#wxList{type=command_list_item_focused, + itemIndex=Row}}, + #state{grid=Grid,popup_menu=Pop,holder=Holder} = State) -> + case Row >= 0 of + true -> + wxWindow:show(Pop, [{show, false}]), + SelIds = [Row|lists:delete(Row, get_selected_items(Grid))], + Pids = call(Holder, {get_pids, self(), SelIds}), + %% io:format("Focused ~p -> ~p~n",[State#state.sel, {SelIds, Pids}]), + {noreply, State#state{sel={SelIds, Pids}}}; + false -> + %% io:format("Focused -1~n",[]), + {noreply, State} + end; -handle_event(#wx{event = #wxList{type = command_list_col_click, col = Col}}, - #pro_wx_state{holder = Holder} = State) -> +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, + #state{holder=Holder}=State) -> Holder ! {change_sort, Col}, {noreply, State}; -handle_event(#wx{event = #wxList{type = command_list_item_activated}}, - #pro_wx_state{holder=Holder, - panel = Panel, - procinfo_menu_pids= Opened, - last_selected = Pid} = State) when Pid =/= undefined -> +handle_event(#wx{event=#wxList{type=command_list_item_activated}}, + #state{panel=Panel, procinfo_menu_pids=Opened, + holder=Holder, + sel={_, [Pid|_]}}=State) + when Pid =/= undefined -> Node = call(Holder, {get_node, self()}), - Opened2 = start_procinfo(Node, Pid, Panel, Opened), - {noreply, State#pro_wx_state{procinfo_menu_pids = Opened2}}; + Opened2=start_procinfo(Node, Pid, Panel, Opened), + {noreply, State#state{procinfo_menu_pids=Opened2}}; handle_event(Event, State) -> io:format("~p~p, handle event ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +update_selection(State=#state{holder=Holder, grid=Grid, + sel={SelIds0, SelPids0}}) -> + Sel = {SelIds,_SelPids} = call(Holder, {get_rows_from_pids, self(), SelPids0}), + set_focus(SelIds0, SelIds, Grid), + case SelIds =:= SelIds0 of + true -> ok; + false -> + wx:batch(fun() -> + [wxListCtrl:setItemState(Grid, I, 0, ?wxLIST_STATE_SELECTED) || + I <- SelIds0], + [wxListCtrl:setItemState(Grid, I, 16#FFFF, ?wxLIST_STATE_SELECTED) || + I <- SelIds] + end) + end, + %%io:format("Update ~p -> ~p~n",[{SelIds0, SelPids0}, Sel]), + State#state{sel=Sel}. + +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 | ItemAcc]) + end. + +set_focus([], [], _Grid) -> ok; +set_focus([Same|_], [Same|_], _Grid) -> ok; +set_focus([], [New|_], Grid) -> + wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED); +set_focus([Old|_], [], Grid) -> + wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED); +set_focus([Old|_], [New|_], Grid) -> + wxListCtrl:setItemState(Grid, Old, 0, ?wxLIST_STATE_FOCUSED), + wxListCtrl:setItemState(Grid, New, 16#FFFF, ?wxLIST_STATE_FOCUSED). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_table_holder(Parent, Attrs) -> Backend = spawn_link(node(), observer_backend,etop_collect,[self()]), - table_holder(#holder{parent = Parent, - info = #etop_info{procinfo=[]}, - node = node(), - backend_pid = Backend, - attrs = Attrs + table_holder(#holder{parent=Parent, + info=#etop_info{procinfo=[]}, + node=node(), + backend_pid=Backend, + attrs=Attrs }). table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, - node=Node, backend_pid=Backend} = S0) -> + node=Node, backend_pid=Backend}=S0) -> receive {get_row, From, Row, Col} -> get_row(From, Row, Col, Info), @@ -557,7 +546,7 @@ table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, {get_attr, From, Row} -> get_attr(From, Row, Attrs), table_holder(S0); - {Backend, EtopInfo = #etop_info{}} -> + {Backend, EtopInfo=#etop_info{}} -> State = handle_update(EtopInfo, S0), table_holder(State#holder{backend_pid=undefined}); refresh when is_pid(Backend)-> @@ -571,6 +560,10 @@ table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, {get_pids, From, Indices} -> get_pids(From, Indices, Info), table_holder(S0); + {get_rows_from_pids, From, Pids} -> + get_rows_from_pids(From, Pids, Info), + table_holder(S0); + {get_node, From} -> From ! {self(), Node}, table_holder(S0); @@ -598,50 +591,48 @@ table_holder(#holder{info=#etop_info{procinfo=Info}, attrs=Attrs, table_holder(S0) end. -change_sort(Col, S0 = #holder{parent=Parent, info=EI=#etop_info{procinfo=Data}, sort=Sort0}) -> - {Sort, ProcInfo} = sort(Col, Sort0, Data), +change_sort(Col, S0=#holder{parent=Parent, info=EI=#etop_info{procinfo=Data}, sort=Sort0}) -> + {Sort, ProcInfo}=sort(Col, Sort0, Data), Parent ! {holder_updated, length(Data)}, S0#holder{info=EI#etop_info{procinfo=ProcInfo}, sort=Sort}. change_accum(true, S0) -> S0#holder{accum=true}; -change_accum(false, S0 = #holder{info=#etop_info{procinfo=Info}}) -> +change_accum(false, S0=#holder{info=#etop_info{procinfo=Info}}) -> self() ! refresh, S0#holder{accum=lists:sort(Info)}. handle_update(EI=#etop_info{procinfo=ProcInfo0}, - S0 = #holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> + S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> {ProcInfo1, S1} = accum(ProcInfo0, S0), {_SO, ProcInfo} = sort(KeyField, Sort#sort{sort_key=undefined}, ProcInfo1), Parent ! {holder_updated, length(ProcInfo)}, S1#holder{info=EI#etop_info{procinfo=ProcInfo}}. -accum(ProcInfo, State = #holder{accum=true}) -> +accum(ProcInfo, State=#holder{accum=true}) -> {ProcInfo, State}; -accum(ProcInfo0, State = #holder{accum=Previous}) -> +accum(ProcInfo0, State=#holder{accum=Previous}) -> ProcInfo = lists:sort(ProcInfo0), {accum2(ProcInfo,Previous,[]), State#holder{accum=ProcInfo}}. -accum2([PI = #etop_proc_info{pid=Pid, reds=Reds, runtime=RT}|PIs], +accum2([PI=#etop_proc_info{pid=Pid, reds=Reds, runtime=RT}|PIs], [#etop_proc_info{pid=Pid, reds=OldReds, runtime=OldRT}|Old], Acc) -> accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds, runtime=RT-OldRT}|Acc]); -accum2(PIs = [#etop_proc_info{pid=Pid}|_], [#etop_proc_info{pid=OldPid}|Old], Acc) +accum2(PIs=[#etop_proc_info{pid=Pid}|_], [#etop_proc_info{pid=OldPid}|Old], Acc) when Pid > OldPid -> accum2(PIs, Old, Acc); accum2([PI|PIs], Old, Acc) -> accum2(PIs, Old, [PI|Acc]); accum2([], _, Acc) -> Acc. -sort(Col, Opt = #sort{sort_key=Col, sort_incr=Bool}, Table) -> +sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) -> {Opt#sort{sort_incr=not Bool}, lists:reverse(Table)}; sort(Col, S=#sort{sort_incr=true}, Table) -> {S#sort{sort_key=Col}, lists:keysort(col_to_element(Col), Table)}; sort(Col, S=#sort{sort_incr=false}, Table) -> {S#sort{sort_key=Col}, lists:reverse(lists:keysort(col_to_element(Col), Table))}. - - - +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% get_procinfo_data(Col, Info) -> element(col_to_element(Col), Info). @@ -654,8 +645,8 @@ col_to_element(?COL_FUN) -> #etop_proc_info.cf; col_to_element(?COL_MSG) -> #etop_proc_info.mq. get_pids(From, Indices, ProcInfo) -> - Processes = [lists:nth(I, ProcInfo) || I <- Indices], - From ! {self(), [X#etop_proc_info.pid || X <- Processes]}. + Processes = [(lists:nth(I+1, ProcInfo))#etop_proc_info.pid || I <- Indices], + From ! {self(), Processes}. get_row(From, Row, pid, Info) -> Pid = case Row =:= -1 of @@ -666,12 +657,21 @@ get_row(From, Row, pid, Info) -> 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])}. + From ! {self(), observer_lib:to_str(Data)}. + +get_rows_from_pids(From, Pids0, Info) -> + Res = lists:foldl(fun(Pid, Data = {Ids, Pids}) -> + case index(Pid, Info, 0) of + false -> Data; + Index -> {[Index|Ids], [Pid|Pids]} + end + end, {[],[]}, Pids0), + From ! {self(), Res}. get_attr(From, Row, Attrs) -> Attribute = case Row rem 2 =:= 0 of @@ -679,3 +679,7 @@ get_attr(From, Row, Attrs) -> false -> Attrs#attrs.odd end, From ! {self(), Attribute}. + +index(Pid, [#etop_proc_info{pid=Pid}|_], Index) -> Index; +index(Pid, [_|PI], Index) -> index(Pid, PI, Index+1); +index(_, _, _) -> false. diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index b01b91c0b2..6414a26ec9 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -192,12 +192,9 @@ create_page(Notebook, Node, Module, What) -> {Panel, Stc}. create_menus(MenuBar) -> - observer_wx:create_menu( - [ - {"File", [#create_menu{id = ?CLOSE, text = "Close"}]}, - {"View", [#create_menu{id = ?REFRESH, text = "Refresh"}]} - ], - MenuBar). + Menus = [{"File", [#create_menu{id = ?CLOSE, text = "Close"}]}, + {"View", [#create_menu{id = ?REFRESH, text = "Refresh"}]}], + observer_lib:create_menus(Menus, MenuBar, new_window). check_boxes(CheckListBox, Bool, all) -> lists:foreach(fun(Index) -> diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 8e08b57b92..b79358193e 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -110,24 +110,17 @@ create_window(ParentFrame, TraceOpts) -> 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). + Menus = [{"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"}]} + ], + observer_lib:create_menus(Menus, MenuBar, new_window). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -370,7 +363,7 @@ textformat(Other) -> io_lib:format("~p~n",[Other]). -tuple_space(X) when is_tuple(X) -> print(size(X), X, "}"); +tuple_space(X) when is_tuple(X) -> print(tuple_size(X), X, "}"); tuple_space(X) -> io_lib:format("~p",[X]). diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index a2797700d8..d4990ec0ff 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -26,6 +26,7 @@ -export([get_table/3]). +-include("observer_defs.hrl"). -import(observer_lib, [to_str/1]). -behaviour(wx_object). @@ -68,8 +69,6 @@ sort_incr=true }). --record(attrs, {even, odd, deleted, changed, searched}). - -record(search, {enable=true, % Subwindow is enabled win, % Sash Sub window obj @@ -116,7 +115,7 @@ init([Parent, Opts]) -> ColumnNames = column_names(Node, Source, TabId), KeyPos = key_pos(Node, Source, TabId), - Attrs = create_attrs(), + Attrs = observer_lib:create_attrs(), Self = self(), Holder = spawn_link(fun() -> @@ -587,7 +586,7 @@ parse_ets_data([], Cols, Tab) -> sort(Col, S=#holder{n=N, parent=Parent, sort=Opt0, table=Table0}) -> {Opt, Table} = sort(Col, Opt0, Table0), - Parent ! {refresh, 0, N}, + Parent ! {refresh, 0, N-1}, S#holder{sort=Opt, table=Table}. sort(Col, Opt = #opt{sort_key=Col, sort_incr=Bool}, Table) -> @@ -595,7 +594,7 @@ sort(Col, Opt = #opt{sort_key=Col, sort_incr=Bool}, Table) -> sort(Col, S=#opt{sort_incr=true}, Table) -> {S#opt{sort_key=Col}, keysort(Col, Table)}; sort(Col, S=#opt{sort_incr=false}, Table) -> - {S=#opt{sort_key=Col}, lists:reverse(keysort(Col, Table))}. + {S#opt{sort_key=Col}, lists:reverse(keysort(Col, Table))}. keysort(Col, Table) -> Sort = fun([A0|_], [B0|_]) -> @@ -800,13 +799,3 @@ key_pos(Node, ets, TabId) -> KeyPos = rpc:call(Node, ets, info, [TabId, keypos]), is_integer(KeyPos) orelse throw(node_or_table_down), KeyPos. - -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), - deleted = wxListItemAttr:new({240,30,30}, {100,100,100}, Font), - changed = wxListItemAttr:new(Text, {255,215,0}, Font), - searched = wxListItemAttr:new(Text, {235,215,90}, Font) - }. diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 230195c9de..dbf573151f 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -339,7 +339,7 @@ display_table_info(Parent0, Node, Source, Table) -> Parent = observer_lib:get_wx_parent(Parent0), Title = "Table Info: " ++ atom_to_list(Table#tab.name), Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, - [{style, ?wxCAPTION bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), + [{style, ?wxCAPTION bor ?wxCLOSE_BOX}]), IdInfo = {"Identification and Owner", [{"Name", Table#tab.name}, diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index ef3c6bb304..4688e271ee 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -20,7 +20,7 @@ -behaviour(wx_object). -export([start/0]). --export([create_menus/2, create_menu/2, create_txt_dialog/4, try_rpc/4, +-export([create_menus/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, @@ -44,6 +44,7 @@ -record(state, {frame, menubar, + menus = [], status_bar, notebook, main_panel, @@ -86,7 +87,7 @@ setup(#state{frame = Frame} = State) -> {Nodes, NodeMenus} = get_nodes(), DefMenus = default_menus(NodeMenus), - create_menu(DefMenus, MenuBar), + observer_lib:create_menus(DefMenus, MenuBar, default), wxFrame:setMenuBar(Frame, MenuBar), StatusBar = wxFrame:createStatusBar(Frame, []), @@ -115,9 +116,9 @@ setup(#state{frame = Frame} = State) -> wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), wxPanel:setSizer(Panel, MainSizer), - wxNotebook:connect(Notebook, command_notebook_page_changed), + wxNotebook:connect(Notebook, command_notebook_page_changing), wxFrame:connect(Frame, close_window, [{skip, true}]), - wxMenu:connect(Frame, command_menu_selected, [{skip, true}]), + wxMenu:connect(Frame, command_menu_selected), SysPid = wx_object:get_pid(SysPanel), SysPid ! {active, node()}, @@ -139,9 +140,9 @@ setup(#state{frame = Frame} = State) -> %%Callbacks handle_event(#wx{obj = Notebook, id = ?ID_NOTEBOOK, - event = #wxNotebook{type = command_notebook_page_changed}}, + event = Ev = #wxNotebook{type = command_notebook_page_changing}}, #state{active_tab=Previous, node=Node, notebook = Notebook} = State) -> - io:format("Command notebook changed ~n"), + io:format("Command notebook changed ~p ~n", [Ev]), Pid = get_active_pid(State), Previous ! not_active, Pid ! {active, Node}, @@ -151,13 +152,22 @@ 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 = ?wxID_ABOUT, event = #wxCommand{type = command_menu_selected}}, + State = #state{frame=Frame}) -> + AboutString = "Observe an erlang system\n" + "Authors: Olle Mattson & Magnus Eriksson & Dan Gudmundsson", + Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP}, + {caption, "About"}], + wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)), + {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 @@ -192,11 +202,9 @@ handle_event(#wx{id = ?ID_CONNECT, event = #wxCommand{type = command_menu_select 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; + cancel -> State; {value, Value} when is_list(Value) -> try - Node = list_to_atom(Value), case net_adm:ping(Node) of pang -> @@ -205,7 +213,6 @@ handle_event(#wx{id = ?ID_PING, event = #wxCommand{type = command_menu_selected} pong -> change_node_view(Node, State) end - catch _:_ -> create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION), State @@ -229,15 +236,13 @@ 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}) -> +handle_call({create_menus, TabMenus}, _From, + State = #state{menubar=MenuBar, menus=PrevTabMenus}) -> wx:batch(fun() -> - {_Nodes, NodeMenus} = get_nodes(), - DefMenus = default_menus(NodeMenus), - Menus = merge_menus(DefMenus, TabMenus), - clean_menus(MenuBar), - create_menu(Menus, MenuBar) + clean_menus(PrevTabMenus, MenuBar), + observer_lib:create_menus(TabMenus, MenuBar, plugin) end), - {reply, ok, State}; + {reply, ok, State#state{menus=TabMenus}}; handle_call(Msg, _From, State) -> io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), @@ -308,6 +313,13 @@ connect(NodeName, 1, Cookie) -> connect2(NodeName, longnames, Cookie). connect2(NodeName, Opts, Cookie) -> + case net_adm:names() of + {ok, _} -> %% Epmd is running + ok; + {error, address} -> + Epmd = os:find_executable("epmd"), + os:cmd(Epmd) + end, case net_kernel:start([NodeName, Opts]) of {ok, _} -> case is_alive() of @@ -367,10 +379,11 @@ create_connect_dialog(connect, #state{frame = Frame}) -> {style, ?wxHORIZONTAL}]), NameText = wxStaticText:new(Dialog, ?wxID_ANY, "Node name: "), - NameCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {200, 25}}, {style, ?wxDEFAULT}]), + NameCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {200, 25}}]), wxTextCtrl:setValue(NameCtrl, "observer"), CookieText = wxStaticText:new(Dialog, ?wxID_ANY, "Secret cookie: "), - CookieCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {200, 25}}, {style, ?wxDEFAULT}]), + CookieCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, + [{size, {200, 25}}, {style, ?wxTE_PASSWORD}]), BtnSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxID_DEFAULT), Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}], @@ -390,13 +403,8 @@ create_connect_dialog(connect, #state{frame = Frame}) -> 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; + {ok, Bin} = file:read_file(CookiePath), + binary_to_list(Bin); false -> "" end, @@ -414,66 +422,57 @@ create_connect_dialog(connect, #state{frame = Frame}) -> end. default_menus(NodesMenuItems) -> - FileMenu = {"File", [#create_menu{id = ?wxID_EXIT, text = "Quit"}]}, - HelpMenu = {"Help", [#create_menu{id = ?wxID_HELP, text = "Help"}]}, + Quit = #create_menu{id = ?wxID_EXIT, text = "Quit"}, + About = #create_menu{id = ?wxID_ABOUT, text = "About"}, + Help = #create_menu{id = ?wxID_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"}]} + 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]. + case os:type() =:= {unix, darwin} of + false -> + FileMenu = {"File", [Quit]}, + HelpMenu = {"Help", [About,Help]}, + [FileMenu, NodeMenu, HelpMenu]; + true -> + %% On Mac quit and about will be moved to the "default' place + %% automagicly, so just add them to a menu that always exist. + %% But not to the help menu for some reason + {Tag, Menus} = NodeMenu, + [{Tag, Menus ++ [Quit,About]}, {"&Help", [Help]}] + end. -clean_menus(MenuBar) -> - Count = wxMenuBar:getMenuCount(MenuBar), - remove_menu_item(MenuBar, Count). +clean_menus(Menus, MenuBar) -> + remove_menu_items(Menus, MenuBar). -remove_menu_item(MenuBar, Item) when Item > -1 -> - Menu = wxMenuBar:getMenu(MenuBar, Item), - wxMenuBar:remove(MenuBar, Item), +remove_menu_items([{MenuStr = "File", Menus}|Rest], MenuBar) -> + MenuId = wxMenuBar:findMenu(MenuBar, MenuStr), + Menu = wxMenuBar:getMenu(MenuBar, MenuId), + Items = [wxMenu:findItem(Menu, Tag) || #create_menu{text=Tag} <- Menus], + [wxMenu:delete(Menu, MItem) || MItem <- Items], + case os:type() =:= {unix, darwin} of + true -> + wxMenuBar:remove(MenuBar, MenuId), + wxMenu:destroy(Menu); + false -> + ignore + end, + remove_menu_items(Rest, MenuBar); +remove_menu_items([{"Nodes", _}|_], _MB) -> + ok; +remove_menu_items([{Tag, _Menus}|Rest], MenuBar) -> + MenuId = wxMenuBar:findMenu(MenuBar, Tag), + Menu = wxMenuBar:getMenu(MenuBar, MenuId), + wxMenuBar:remove(MenuBar, MenuId), + Items = wxMenu:getMenuItems(Menu), + [wxMenu:'Destroy'(Menu, Item) || Item <- Items], 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), + remove_menu_items(Rest, MenuBar); +remove_menu_items([], _MB) -> 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} = @@ -489,19 +488,16 @@ 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, + wx:foreach(fun(Item) -> wxMenu:'Destroy'(NodeMenu, Item) end, wxMenu:getMenuItems(NodeMenu)), - wx:foreach(fun(Record) -> - create_menu_item(Record, NodeMenu) - end, NodesMenuItems), + Index = wx:foldl(fun(Record, Index) -> + observer_lib:create_menu_item(Record, NodeMenu, Index) + end, 0, 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, + Dist = case erlang:is_alive() of + true -> #create_menu{id = ?ID_PING, text = "Connect node"}; + false -> #create_menu{id = ?ID_CONNECT, text = "Enable distribution"} + end, + observer_lib:create_menu_item(Dist, NodeMenu, Index), State#state{nodes = Nodes}. -- cgit v1.2.3 From b7b88933672591d7f7c2a71a4c1643b6ca486f23 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Wed, 9 Nov 2011 15:48:02 +0100 Subject: [observer] Improve process_info window Also refactor some (re)used code --- lib/observer/src/observer_lib.erl | 26 +- lib/observer/src/observer_pro_wx.erl | 22 +- lib/observer/src/observer_procinfo.erl | 644 +++++++++++---------------------- lib/observer/src/observer_sys_wx.erl | 17 +- lib/observer/src/observer_wx.erl | 34 +- 5 files changed, 261 insertions(+), 482 deletions(-) (limited to 'lib/observer') diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index b36aaa7541..8bc79ffddd 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -21,7 +21,7 @@ -export([get_wx_parent/1, display_info_dialog/1, interval_dialog/4, start_timer/1, stop_timer/1, - display_info/2, update_info/2, to_str/1, + display_info/2, fill_info/2, update_info/2, to_str/1, create_menus/3, create_menu_item/3, create_attrs/0 ]). @@ -117,6 +117,22 @@ display_info(Frame, Info) -> wxWindow:setSizerAndFit(Panel, Sizer), {Panel, Sizer, InfoFs}. +fill_info([{Str, Key}|Rest], Data) when is_atom(Key); is_function(Key) -> + [{Str, get_value(Key, Data)} | fill_info(Rest, Data)]; +fill_info([{Str, {Format, Key}}|Rest], Data) + when is_atom(Key); is_function(Key), is_atom(Format) -> + [{Str, {Format, get_value(Key,Data)}} | fill_info(Rest, Data)]; +fill_info([{Str,SubStructure}|Rest], Data) when is_list(SubStructure) -> + [{Str, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; +fill_info([{Str,Attrib,SubStructure}|Rest], Data) -> + [{Str, Attrib, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; +fill_info([], _) -> []. + +get_value(Key, Data) when is_atom(Key) -> + proplists:get_value(Key,Data); +get_value(Fun, Data) when is_function(Fun) -> + Fun(Data). + update_info([Fields|Fs], [{_Header, SubStructure}| Rest]) -> update_info2(Fields, SubStructure), update_info(Fs, Rest); @@ -154,9 +170,9 @@ to_str({time_ms, MS}) -> true -> integer_to_list(S) ++ " Secs" end; -to_str({A, B}) -> +to_str({A, B}) when is_atom(A), is_atom(B) -> lists:concat([A, ":", B]); -to_str({M,F,A}) -> +to_str({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) -> lists:concat([M, ":", F, "/", A]); to_str(Value) when is_list(Value) -> case lists:all(fun(X) -> is_integer(X) end, Value) of @@ -172,8 +188,8 @@ to_str(Pid) when is_pid(Pid) -> pid_to_list(Pid); to_str(No) when is_integer(No) -> integer_to_list(No); -to_str(ShouldNotGetHere) -> - erlang:error({?MODULE, to_str, ShouldNotGetHere}). +to_str(Term) -> + io_lib:format("~w", [Term]). create_menus(Menus, MenuBar, Type) -> Add = fun({Tag, Ms}, Index) -> diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 8e3e28caec..4bc6da6476 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -233,14 +233,14 @@ dump_to_file(Parent, FileName, Holder) -> wxDialog:destroy(MD) end. -start_procinfo(_Node, undefined, _Frame, Opened) -> +start_procinfo(undefined, _Frame, Opened) -> Opened; -start_procinfo(Node, Pid, Frame, Opened) -> +start_procinfo(Pid, Frame, Opened) -> case lists:member(Pid, Opened) of true -> Opened; false -> - observer_procinfo:start(Node, Pid, Frame, self()), + observer_procinfo:start(Pid, Frame, self()), [Pid | Opened] end. @@ -356,23 +356,21 @@ handle_event(#wx{id=?ID_KILL}, {noreply, State#state{sel={Ids,Pids}}}; -handle_event(#wx{id=?ID_PROC}, +handle_event(#wx{id = ?ID_PROC}, #state{panel=Panel, popup_menu=Pop, - holder=Holder, sel={_, [Pid|_]}, procinfo_menu_pids=Opened}=State) -> wxWindow:show(Pop, [{show, false}]), - Node = call(Holder, {get_node, self()}), - Opened2 = start_procinfo(Node, Pid, Panel, Opened), + Opened2 = start_procinfo(Pid, Panel, Opened), {noreply, State#state{procinfo_menu_pids=Opened2}}; -handle_event(#wx{id=?ID_TRACEMENU}, +handle_event(#wx{id = ?ID_TRACEMENU}, #state{holder=Holder, popup_menu=Pop, trace_options=Options, match_specs=MatchSpecs, - sel=Pids, + sel={_, Pids}, tracemenu_opened=false, panel=Panel}=State) -> wxWindow:show(Pop, [{show, false}]), @@ -471,15 +469,13 @@ handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, handle_event(#wx{event=#wxList{type=command_list_item_activated}}, #state{panel=Panel, procinfo_menu_pids=Opened, - holder=Holder, sel={_, [Pid|_]}}=State) when Pid =/= undefined -> - Node = call(Holder, {get_node, self()}), - Opened2=start_procinfo(Node, Pid, Panel, Opened), + Opened2 = start_procinfo(Pid, Panel, Opened), {noreply, State#state{procinfo_menu_pids=Opened2}}; handle_event(Event, State) -> - io:format("~p~p, handle event ~p\n", [?MODULE, ?LINE, Event]), + io:format("~p:~p: handle event ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 6414a26ec9..2600109161 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -20,7 +20,7 @@ -behaviour(wx_object). --export([start/4]). +-export([start/3]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, handle_call/3, handle_info/2]). @@ -28,452 +28,78 @@ -include_lib("wx/include/wx.hrl"). -include("observer_defs.hrl"). --define(CLOSE, 601). --define(REFRESH, 602). +-define(REFRESH, 601). -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], []). +-record(state, {parent, + frame, + pid, + pages=[] + }). + +-record(worker, {panel, callback}). + +start(Process, ParentFrame, Parent) -> + wx_object:start(?MODULE, [Process, ParentFrame, Parent], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Node, Process, ParentFrame, Parent]) -> +init([Pid, 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}} + Title=case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, registered_name]) of + [] -> io_lib:format("~p",[Pid]); + {registered_name, Registered} -> atom_to_list(Registered) + end, + Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title], + [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {800,700}}]), + MenuBar = wxMenuBar:new(), + create_menus(MenuBar), + wxFrame:setMenuBar(Frame, MenuBar), + + Notebook = wxNotebook:new(Frame, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), + + ProcessPage = init_panel(Notebook, "Process Information", Pid, fun init_process_page/2), + MessagePage = init_panel(Notebook, "Messages", Pid, fun init_message_page/2), + DictPage = init_panel(Notebook, "Dictionary", Pid, fun init_dict_page/2), + StackPage = init_panel(Notebook, "Stack Trace", Pid, fun init_stack_page/2), + + wxFrame:connect(Frame, close_window), + wxMenu:connect(Frame, command_menu_selected), + %% wxNotebook:connect(Notebook, command_notebook_page_changed, [{skip,true}]), + wxFrame:show(Frame), + {Frame, #state{parent=Parent, + pid=Pid, + frame=Frame, + pages=[ProcessPage,MessagePage,DictPage,StackPage] + }} catch error:{badrpc, _} -> - observer_wx:return_to_localnode(ParentFrame, Node), - {stop, badrpc, #procinfo_state{parent = Parent, - pid = Process}} + observer_wx:return_to_localnode(ParentFrame, node(Pid)), + {stop, badrpc, #state{parent=Parent, pid=Pid}}; + Error:Reason -> + io:format("~p:~p: ~p ~p~n ~p~n", + [?MODULE, ?LINE, Error, Reason, erlang:get_stacktrace()]) 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) -> - Menus = [{"File", [#create_menu{id = ?CLOSE, text = "Close"}]}, - {"View", [#create_menu{id = ?REFRESH, text = "Refresh"}]}], - observer_lib:create_menus(Menus, MenuBar, new_window). - -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. +init_panel(Notebook, Str, Pid, Fun) -> + Panel = wxPanel:new(Notebook), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + {Window,Callback} = Fun(Panel, Pid), + wxSizer:add(Sizer, Window, [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), + wxPanel:setSizer(Panel, Sizer), + true = wxNotebook:addPage(Notebook, Panel, Str), + #worker{panel=Panel, callback=Callback}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_event(#wx{event = #wxClose{type = close_window}}, - State) -> +handle_event(#wx{event=#wxClose{type=close_window}}, State) -> {stop, shutdown, State}; -handle_event(#wx{id = ?CLOSE, - event = #wxCommand{type = command_menu_selected}}, - State) -> +handle_event(#wx{id=?wxID_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(#wx{id=?REFRESH}, #state{pages=Pages}=State) -> + [(W#worker.callback)() || W <- Pages], + {noreply, State}; handle_event(Event, State) -> io:format("~p: ~p, Handle event: ~p~n", [?MODULE, ?LINE, Event]), @@ -491,18 +117,158 @@ 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]), +terminate(_Reason, #state{parent=Parent,pid=Pid,frame=Frame}) -> Parent ! {procinfo_menu_closed, Pid}, case Frame of - undefined -> - ok; - _ -> - wxFrame:destroy(Frame) + undefined -> ok; + _ -> wxFrame:destroy(Frame) end, ok. code_change(_, _, State) -> {stop, not_yet_implemented, State}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +init_process_page(Panel, Pid) -> + Fields = process_info_fields(Pid), + {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields), + {FPanel, fun() -> observer_lib:update_info(UpFields, process_info_fields(Pid)) end}. + +init_text_page(Parent) -> + Style = ?wxTE_MULTILINE bor ?wxTE_RICH2 bor ?wxTE_READONLY, + Text = wxTextCtrl:new(Parent, ?wxID_ANY, [{style, Style}]), + Font = observer_wx:get_attrib({font, modern}), + Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), + true = wxTextCtrl:setDefaultStyle(Text, Attr), + wxTextAttr:destroy(Attr), + Text. + +init_message_page(Parent, Pid) -> + Text = init_text_page(Parent), + Format = fun(Message, Number) -> + {io_lib:format("~-4.w ~p~n", [Number, Message]), + Number+1} + end, + Update = fun() -> + {messages,RawMessages} = + observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]), + {Messages,_} = lists:mapfoldl(Format, 1, RawMessages), + Last = wxTextCtrl:getLastPosition(Text), + wxTextCtrl:remove(Text, 0, Last), + case Messages =:= [] of + true -> + wxTextCtrl:writeText(Text, "No messages"); + false -> + wxTextCtrl:writeText(Text, Messages) + end + end, + Update(), + {Text, Update}. + +init_dict_page(Parent, Pid) -> + Text = init_text_page(Parent), + Update = fun() -> + {dictionary,RawDict} = + observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]), + Dict = [io_lib:format("~-20.w ~p~n", [K, V]) || {K, V} <- RawDict], + Last = wxTextCtrl:getLastPosition(Text), + wxTextCtrl:remove(Text, 0, Last), + wxTextCtrl:writeText(Text, Dict) + end, + Update(), + {Text, Update}. + +init_stack_page(Parent, Pid) -> + Text = init_text_page(Parent), + Format = fun({Mod, Fun, Arg, Info}) -> + Str = io_lib:format("~w:~w/~w", [Mod,Fun,Arg]), + case Info of + [{file,File},{line,Line}] -> + io_lib:format("~-45.s ~s:~w~n", [Str,File,Line]); + _ -> + [Str,$\n] + end + end, + Update = fun() -> + {current_stacktrace,RawBt} = + observer_wx:try_rpc(node(Pid), erlang, process_info, + [Pid, current_stacktrace]), + Last = wxTextCtrl:getLastPosition(Text), + wxTextCtrl:remove(Text, 0, Last), + [wxTextCtrl:writeText(Text, Format(Entry)) || Entry <- RawBt] + end, + Update(), + {Text, Update}. + +create_menus(MenuBar) -> + Menus = [{"File", [#create_menu{id=?wxID_CLOSE, text="Close"}]}, + {"View", [#create_menu{id=?REFRESH, text="Refresh\tCtrl-R"}]}], + observer_lib:create_menus(Menus, MenuBar, new_window). + +process_info_fields(Pid) -> + RawInfo = observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]), + Struct = [{"Overview", + [{"Initial Call", initial_call}, + {"Current Function", current_function}, + {"Registered Name", registered_name}, + {"Status", status}, + {"Message Queue Len",message_queue_len}, + {"Priority", priority}, + {"Trap Exit", trap_exit}, + {"Reductions", reductions}, + {"Binary", binary}, + {"Last Calls", last_calls}, + {"Catch Level", catchlevel}, + {"Trace", trace}, + {"Suspending", suspending}, + {"Sequential Trace Token", sequential_trace_token}, + {"Error Handler", error_handler}]}, + {"Connections", + [{"Group Leader", group_leader}, + {"Links", links}, + {"Monitors", monitors}, + {"Monitored by", monitored_by}]}, + {"Memory and Garbage Collection", right, + [{"Memory", {bytes, memory}}, + {"Stack and Heaps", {bytes, total_heap_size}}, + {"Heap Size", {bytes, heap_size}}, + {"Stack Size", {bytes, stack_size}}, + {"GC Min Heap Size", {bytes, get_gc_info(min_heap_size)}}, + {"GC FullSweep After", get_gc_info(fullsweep_after)} + ]}], + observer_lib:fill_info(Struct, RawInfo). + +item_list() -> + [ %% 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]. + +get_gc_info(Arg) -> + fun(Data) -> + GC = proplists:get_value(garbage_collection, Data), + proplists:get_value(Arg, GC) + end. diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index 5bc5fd49f6..36aa226bd1 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -59,8 +59,10 @@ init2([Notebook, Parent]) -> {Info, Stat} = info_fields(), Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxHORIZONTAL), - {FPanel0, _FSizer0, Fields0} = observer_lib:display_info(Panel, fill_info(Info, SysInfo)), - {FPanel1, _FSizer1, Fields1} = observer_lib:display_info(Panel, fill_info(Stat, SysInfo)), + {FPanel0, _FSizer0, Fields0} = + observer_lib:display_info(Panel, observer_lib:fill_info(Info, SysInfo)), + {FPanel1, _FSizer1, Fields1} = + observer_lib:display_info(Panel, observer_lib:fill_info(Stat, SysInfo)), wxSizer:add(Sizer, FPanel0, [{flag, ?wxEXPAND bor ?wxTOP bor ?wxBOTTOM bor ?wxLEFT}, {proportion, 1}, {border, 5}]), wxSizer:add(Sizer, FPanel1, [{flag, ?wxEXPAND bor ?wxTOP bor ?wxBOTTOM bor ?wxRIGHT}, @@ -81,7 +83,8 @@ update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) -> try SysInfo = observer_wx:try_rpc(Node, ?MODULE, sys_info, []), {Info, Stat} = info_fields(), - observer_lib:update_info(Fields, fill_info(Info, SysInfo) ++ fill_info(Stat, SysInfo)), + observer_lib:update_info(Fields, observer_lib:fill_info(Info, SysInfo) ++ + observer_lib:fill_info(Stat, SysInfo)), wxSizer:layout(Sizer) catch E:R -> io:format("~p:~p ~p~n",[E,R, erlang:get_stacktrace()]) @@ -124,14 +127,6 @@ info_fields() -> ], {Info, Stat}. -fill_info([{Str, Key}|Rest], Data) when is_atom(Key) -> - [{Str, proplists:get_value(Key,Data)} | fill_info(Rest, Data)]; -fill_info([{Str,SubStructure}|Rest], Data) -> - [{Str, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; -fill_info([{Str,Attrib,SubStructure}|Rest], Data) -> - [{Str, Attrib, fill_info(SubStructure, Data)}|fill_info(Rest,Data)]; -fill_info([], _) -> []. - %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_info(refresh_interval, #sys_wx_state{panel = Panel, diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 4688e271ee..b39b34c240 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -20,8 +20,8 @@ -behaviour(wx_object). -export([start/0]). --export([create_menus/2, create_txt_dialog/4, try_rpc/4, - return_to_localnode/2]). +-export([create_menus/2, get_attrib/1, + 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]). @@ -62,6 +62,9 @@ start() -> create_menus(Object, Menus) when is_list(Menus) -> wx_object:call(Object, {create_menus, Menus}). +get_attrib(What) -> + wx_object:call(observer, {get_attrib, What}). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -79,6 +82,7 @@ init(_Args) -> wxFrame:show(Frame), net_kernel:monitor_nodes(true), process_flag(trap_exit, true), + register(observer, self()), {Frame, UpdState}. setup(#state{frame = Frame} = State) -> @@ -133,16 +137,19 @@ setup(#state{frame = Frame} = State) -> node = node(), nodes = Nodes }, + %% Create resources which we don't want to duplicate + SysFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + SysFontSize = wxFont:getPointSize(SysFont), + Modern = wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), + put({font, modern}, Modern), UpdState. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%Callbacks -handle_event(#wx{obj = Notebook, id = ?ID_NOTEBOOK, - event = Ev = #wxNotebook{type = command_notebook_page_changing}}, - #state{active_tab=Previous, node=Node, notebook = Notebook} = State) -> - io:format("Command notebook changed ~p ~n", [Ev]), +handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}}, + #state{active_tab=Previous, node=Node} = State) -> Pid = get_active_pid(State), Previous ! not_active, Pid ! {active, Node}, @@ -232,8 +239,7 @@ handle_event(Event, State) -> Pid ! Event, {noreply, State}. -handle_cast(Cast, State) -> - io:format("~p:~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]), +handle_cast(_Cast, State) -> {noreply, State}. handle_call({create_menus, TabMenus}, _From, @@ -244,8 +250,10 @@ handle_call({create_menus, TabMenus}, _From, end), {reply, ok, State#state{menus=TabMenus}}; -handle_call(Msg, _From, State) -> - io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), +handle_call({get_attrib, Attrib}, _From, State) -> + {reply, get(Attrib), State}; + +handle_call(_Msg, _From, State) -> {reply, ok, State}. handle_info({nodeup, _Node}, State) -> @@ -265,13 +273,11 @@ handle_info({nodedown, 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]), +handle_info(_Info, State) -> {noreply, State}. -terminate(Reason, #state{frame = Frame}) -> +terminate(_Reason, #state{frame = Frame}) -> wxFrame:destroy(Frame), - io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), ok. code_change(_, _, State) -> -- cgit v1.2.3 From 3caa688c5522b8f0f039b9375cf6377b1bd88f0e Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Thu, 10 Nov 2011 11:12:48 +0100 Subject: [observer] Fix portability bugs Turn on generic listctrl implementation on Mac, the native doesn't work in wx-2.8. Fix listctrl colours on mac Turn on wxSystemMenu for miniFrame to get a close box on Windows. Direct observer help to a browser. Cleanups --- lib/observer/src/observer_defs.hrl | 6 ++++++ lib/observer/src/observer_lib.erl | 15 +++++++++------ lib/observer/src/observer_pro_wx.erl | 11 +++++------ lib/observer/src/observer_sys_wx.erl | 25 ++++++------------------- lib/observer/src/observer_tv_table.erl | 2 +- lib/observer/src/observer_tv_wx.erl | 21 ++++++++------------- lib/observer/src/observer_wx.erl | 23 +++++++++++++++-------- 7 files changed, 50 insertions(+), 53 deletions(-) (limited to 'lib/observer') diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index 567eb26e3b..9e68bd7f07 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -49,3 +49,9 @@ }). -record(attrs, {even, odd, deleted, changed, searched}). +-define(BG_EVEN, {230,230,250}). +-define(BG_ODD, {255,255,255}). +-define(BG_DELETED, {100,100,100}). +-define(FG_DELETED, {240,30,30}). +-define(BG_SEARCHED,{235,215,90}). +-define(BG_CHANGED, {230,230,250}). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 8bc79ffddd..b3a2dcf5ec 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -255,12 +255,15 @@ create_menu_item(separator, Menu, Index) -> create_attrs() -> Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), - Text = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT), - #attrs{even = wxListItemAttr:new(Text, {255,255,255}, Font), - odd = wxListItemAttr:new(Text, {240,240,255}, Font), - deleted = wxListItemAttr:new({240,30,30}, {100,100,100}, Font), - changed = wxListItemAttr:new(Text, {255,215,0}, Font), - searched = wxListItemAttr:new(Text, {235,215,90}, Font) + Text = case wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT) of + {255,255,255,_} -> {10,10,10}; %% Is white on Mac for some reason + Color -> Color + end, + #attrs{even = wxListItemAttr:new(Text, ?BG_EVEN, Font), + odd = wxListItemAttr:new(Text, ?BG_ODD, Font), + deleted = wxListItemAttr:new(?FG_DELETED, ?BG_DELETED, Font), + changed = wxListItemAttr:new(Text, ?BG_CHANGED, Font), + searched = wxListItemAttr:new(Text, ?BG_SEARCHED, Font) }. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 4bc6da6476..1c0b3ed5ec 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -182,7 +182,7 @@ create_popup_menu(ParentFrame) -> MiniFrame. create_list_box(Panel, Holder) -> - Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL, + Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_HRULES, ListCtrl = wxListCtrl:new(Panel, [{style, Style}, {onGetItemText, fun(_, Row, Col) -> @@ -296,11 +296,10 @@ handle_info({node, Node}, #state{holder=Holder}=State) -> {noreply, State}; handle_info(Info, State) -> - io:format("~p, ~p, Handled unexpected info: ~p~n", [?MODULE, ?LINE, Info]), + io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. -terminate(Reason, #state{holder=Holder}) -> - io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), +terminate(_Reason, #state{holder=Holder}) -> Holder ! stop, etop:stop(), ok. @@ -310,12 +309,12 @@ code_change(_, _, State) -> handle_call(Msg, _From, State) -> - io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), {reply, ok, State}. handle_cast(Msg, State) -> - io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), + io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), {noreply, State}. %%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index 36aa226bd1..ddedcf3829 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -47,14 +47,7 @@ start_link(Notebook, Parent) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init(Args) -> - try - init2(Args) - catch E:R -> - io:format("~p:~p ~p~n",[E,R, erlang:get_stacktrace()]) - end. - -init2([Notebook, Parent]) -> +init([Notebook, Parent]) -> SysInfo = sys_info(), {Info, Stat} = info_fields(), Panel = wxPanel:new(Notebook), @@ -80,16 +73,11 @@ create_sys_menu(Parent) -> observer_wx:create_menus(Parent, [View]). update_syspage(#sys_wx_state{node = Node, fields=Fields, sizer=Sizer}) -> - try SysInfo = observer_wx:try_rpc(Node, ?MODULE, sys_info, []), {Info, Stat} = info_fields(), observer_lib:update_info(Fields, observer_lib:fill_info(Info, SysInfo) ++ observer_lib:fill_info(Stat, SysInfo)), - wxSizer:layout(Sizer) - catch E:R -> - io:format("~p:~p ~p~n",[E,R, erlang:get_stacktrace()]) - end, - ok. + wxSizer:layout(Sizer). info_fields() -> Info = [{"System and Architecture", @@ -165,18 +153,17 @@ handle_info(not_active, #sys_wx_state{timer = Timer} = State) -> {noreply, State#sys_wx_state{timer = observer_lib:stop_timer(Timer)}}; handle_info(Info, State) -> - io:format("~p, ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]), + io:format("~p:~p: Unhandled info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. -terminate(Reason, _State) -> - io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]), +terminate(_Reason, _State) -> 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]), + io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), {reply, ok, State}. handle_cast(Msg, State) -> @@ -199,7 +186,7 @@ handle_event(#wx{id = ?ID_REFRESH_INTERVAL, {noreply, State#sys_wx_state{timer=Timer}}; handle_event(Event, State) -> - io:format("handle event ~p\n", [Event]), + io:format("~p:~p: Unhandled event ~p\n", [?MODULE,?LINE,Event]), {noreply, State}. diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index d4990ec0ff..f744623960 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -43,7 +43,7 @@ -define(SEARCH_ENTRY, 420). -define(GOTO_ENTRY, 421). --define(DEFAULT_COL_WIDTH, 100). +-define(DEFAULT_COL_WIDTH, 150). -record(state, { diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index dbf573151f..191faf73ff 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -173,16 +173,13 @@ handle_event(Event, State) -> io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. -handle_sync_event(Event, _Obj, _State) -> - io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), +handle_sync_event(_Event, _Obj, _State) -> ok. -handle_call(Event, From, State) -> - io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), +handle_call(_Event, _From, State) -> {noreply, State}. -handle_cast(Event, State) -> - io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), +handle_cast(_Event, State) -> {noreply, State}. handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) -> @@ -213,12 +210,10 @@ handle_info({error, Error}, State) -> handle_error(Error), {noreply, State}; -handle_info(Event, State) -> - io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), +handle_info(_Event, State) -> {noreply, State}. -terminate(Event, _State) -> - io:format("~p:~p, terminate ~p\n", [?MODULE, ?LINE, Event]), +terminate(_Event, _State) -> ok. code_change(_, _, State) -> @@ -339,7 +334,8 @@ display_table_info(Parent0, Node, Source, Table) -> Parent = observer_lib:get_wx_parent(Parent0), Title = "Table Info: " ++ atom_to_list(Table#tab.name), Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, - [{style, ?wxCAPTION bor ?wxCLOSE_BOX}]), + [{style, ?wxSYSTEM_MENU bor ?wxCAPTION + bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), IdInfo = {"Identification and Owner", [{"Name", Table#tab.name}, @@ -452,7 +448,7 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> protection = Protection, reg_name = RegName}, Row) -> _Item = wxListCtrl:insertItem(Grid, Row, ""), if (Row rem 2) =:= 0 -> - wxListCtrl:setItemBackgroundColour(Grid, Row, {240,240,255}); + wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN); true -> ignore end, if Protection == private -> @@ -466,7 +462,6 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> end, [{0,Name}, {1,Id}, {2,Size}, {3, Memory div 1024}, {4,Owner}, {5,RegName}]), - %% wxListCtrl:setItemData(Grid, Item, Item), Row + 1 end, ProcInfo = case Dir of diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index b39b34c240..45725f03b5 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -70,8 +70,9 @@ get_attrib(What) -> init(_Args) -> wx:new(), - Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer", [{size, {1000, 500}}, - {style, ?wxDEFAULT_FRAME_STYLE}]), + catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1), + 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), @@ -162,7 +163,15 @@ handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selecte {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]), + External = "http://www.erlang.org/doc/apps/observer/index.html", + Internal = filename:join([code:lib_dir(observer),"doc", "html", "index.html"]), + Help = case filelib:is_file(Internal) of + true -> Internal; + false -> External + end, + wx_misc:launchDefaultBrowser(Help) orelse + create_txt_dialog(State#state.frame, "Could not launch browser: ~n " ++ Help, + "Error", ?wxICON_ERROR), {noreply, State}; handle_event(#wx{id = ?wxID_ABOUT, event = #wxCommand{type = command_menu_selected}}, @@ -483,11 +492,9 @@ 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), + {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}) -> -- cgit v1.2.3 From b65b0c4bf4896021cbd327a4bc1acd426645a5f3 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Fri, 11 Nov 2011 15:00:11 +0100 Subject: [observer] Rework tracing part of the gui No tracing is implemented yet. --- lib/observer/src/observer_defs.hrl | 13 +- lib/observer/src/observer_lib.erl | 4 + lib/observer/src/observer_pro_wx.erl | 110 +- lib/observer/src/observer_trace_wx.erl | 668 +++++---- lib/observer/src/observer_traceoptions_wx.erl | 1971 +++++++++++++------------ lib/observer/src/observer_tv_table.erl | 2 +- lib/observer/src/observer_tv_wx.erl | 2 +- lib/observer/src/observer_wx.erl | 46 +- 8 files changed, 1506 insertions(+), 1310 deletions(-) (limited to 'lib/observer') diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index 9e68bd7f07..d83a1e2fa5 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -26,10 +26,12 @@ on_all_link = false, main_window = true}). --record(match_spec, {alias, - term_ms = [], - str_ms = [], - fun2ms}). +-record(match_spec, {name = "", + term = [], + str = [], + func = ""}). + +-record(tpattern, {m, fa, ms}). -record(traced_func, {func_name, %atom arity, %integer @@ -49,9 +51,12 @@ }). -record(attrs, {even, odd, deleted, changed, searched}). +-define(EVEN(Row), ((Row rem 2) =:= 0)). -define(BG_EVEN, {230,230,250}). -define(BG_ODD, {255,255,255}). -define(BG_DELETED, {100,100,100}). -define(FG_DELETED, {240,30,30}). -define(BG_SEARCHED,{235,215,90}). -define(BG_CHANGED, {230,230,250}). + +-define(LCTRL_WDECR, 4). %% Remove some pixels in column width to avoid creating unnecessary scrollbar diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index b3a2dcf5ec..90c270e977 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -170,6 +170,10 @@ to_str({time_ms, MS}) -> true -> integer_to_list(S) ++ " Secs" end; +to_str({func, {F,A}}) when is_atom(F), is_integer(A) -> + lists:concat([F, "/", A]); +to_str({func, {F,'_'}}) when is_atom(F) -> + atom_to_list(F); to_str({A, B}) when is_atom(A), is_atom(B) -> lists:concat([A, ":", B]); to_str({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) -> diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 1c0b3ed5ec..cfc1c0665f 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -71,10 +71,7 @@ panel, popup_menu, parent_notebook, - trace_options=#trace_options{}, - match_specs=[], timer, - tracemenu_opened, procinfo_menu_pids=[], sel={[], []}, holder}). @@ -88,9 +85,7 @@ init([Notebook, Parent]) -> Self = self(), Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end), {ProPanel, State} = setup(Notebook, Parent, Holder), - MatchSpecs = generate_matchspecs(), - - {ProPanel, State#state{holder=Holder, match_specs=MatchSpecs}}. + {ProPanel, State#state{holder=Holder}}. setup(Notebook, Parent, Holder) -> ProPanel = wxPanel:new(Notebook, []), @@ -105,41 +100,16 @@ setup(Notebook, Parent, Holder) -> Popup = create_popup_menu(ProPanel), - State = #state{parent=Parent, - grid=Grid, - panel=ProPanel, - popup_menu=Popup, - parent_notebook=Notebook, - tracemenu_opened=false, - holder=Holder, - timer={false, 10} + State = #state{parent=Parent, + grid=Grid, + panel=ProPanel, + popup_menu=Popup, + parent_notebook=Notebook, + holder=Holder, + timer={false, 10} }, {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 @@ -155,8 +125,9 @@ create_pro_menu(Parent, Holder) -> #create_menu{id=?ID_REFRESH_INTERVAL, text="Refresh Interval"}]}, {"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"}]} + #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). @@ -271,11 +242,6 @@ handle_info(refresh_interval, #state{holder=Holder}=State) -> Holder ! refresh, {noreply, State}; -handle_info({tracemenu_closed, TraceOpts, MatchSpecs}, State) -> - {noreply, State#state{tracemenu_opened=false, - trace_options=TraceOpts, - match_specs=MatchSpecs}}; - handle_info({procinfo_menu_closed, Pid}, #state{procinfo_menu_pids=Opened}=State) -> NewPids = lists:delete(Pid, Opened), @@ -365,59 +331,20 @@ handle_event(#wx{id = ?ID_PROC}, {noreply, State#state{procinfo_menu_pids=Opened2}}; handle_event(#wx{id = ?ID_TRACEMENU}, - #state{holder=Holder, - popup_menu=Pop, - trace_options=Options, - match_specs=MatchSpecs, - sel={_, Pids}, - tracemenu_opened=false, - panel=Panel}=State) -> + #state{popup_menu=Pop, sel={_, Pids}, 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 = call(Holder, {get_node, self()}), - observer_trace_wx:start(Node, - Pids, - Options, - MatchSpecs, - Panel, - self()), - {noreply, State#state{tracemenu_opened=true}} + observer_trace_wx:add_processes(observer_wx:get_tracer(), Pids), + {noreply, State} end; -handle_event(#wx{id=?ID_TRACE_ALL_MENU, event=#wxCommand{type=command_menu_selected}}, - #state{holder=Holder, - trace_options=Options, - match_specs=MatchSpecs, - tracemenu_opened=false, - panel=Panel}=State) -> - Node = call(Holder, {get_node, self()}), - observer_trace_wx:start(Node, - all, - Options, - MatchSpecs, - Panel, - self()), - {noreply, State#state{tracemenu_opened=true}}; - - -handle_event(#wx{id=?ID_TRACE_NEW_MENU, event=#wxCommand{type=command_menu_selected}}, - #state{holder=Holder, - trace_options=Options, - match_specs=MatchSpecs, - tracemenu_opened=false, - panel=Panel}=State) -> - Node = call(Holder, {get_node, self()}), - observer_trace_wx:start(Node, - new, - Options, - MatchSpecs, - Panel, - self()), - {noreply, State#state{tracemenu_opened=true}}; +handle_event(#wx{id=?ID_TRACE_NEW_MENU, event=#wxCommand{type=command_menu_selected}}, State) -> + observer_trace_wx:add_processes(observer_wx:get_tracer(), [new]), + {noreply, State}; handle_event(#wx{event=#wxSize{size={W,_}}}, #state{grid=Grid}=State) -> @@ -425,8 +352,9 @@ handle_event(#wx{event=#wxSize{size={W,_}}}, Cols = wxListCtrl:getColumnCount(Grid), Last = lists:foldl(fun(I, Last) -> Last - wxListCtrl:getColumnWidth(Grid, I) - end, W-2, lists:seq(0, Cols - 2)), + end, W-Cols*3-?LCTRL_WDECR, lists:seq(0, Cols - 2)), Size = max(200, Last), + %% io:format("Width ~p ~p => ~p~n",[W, Last, Size]), wxListCtrl:setColumnWidth(Grid, Cols-1, Size) end), {noreply, State}; diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index b79358193e..23b9b1fe6b 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -18,7 +18,7 @@ -module(observer_trace_wx). --export([start/6]). +-export([start_link/2, add_processes/2]). -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). @@ -33,154 +33,214 @@ -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) -> +-define(TOGGLE_TRACE, 307). +-define(ADD_NEW, 308). +-define(ADD_TP, 309). +-define(PROCESSES, 350). +-define(MODULES, 351). +-define(FUNCTIONS, 352). + +-record(state, + {parent, + panel, + p_view, + m_view, + f_view, + nodes = [], + toggle_button, + tpids = [], %% #tpid + def_trace_opts = [], + tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec} + match_specs = []}). % [ #match_spec{} ] + +-record(tpid, {pid, opts}). + +start_link(Notebook, ParentPid) -> + wx_object:start_link(?MODULE, [Notebook, ParentPid], []). + +add_processes(Tracer, Pids) when is_list(Pids) -> + wx_object:cast(Tracer, {add_processes, Pids}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([Notebook, ParentPid]) -> + wx:batch(fun() -> create_window(Notebook, ParentPid) end). + +create_window(Notebook, ParentPid) -> %% 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, []), + Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), Sizer = wxBoxSizer:new(?wxVERTICAL), - - %% Menues - MenuBar = wxMenuBar:new(), - create_menues(MenuBar), - wxFrame:setMenuBar(Frame, MenuBar), - wxMenu:connect(Frame, command_menu_selected, []), - + Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}]), + ProcessView = create_process_view(Splitter), + {MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter), + wxSplitterWindow:setSashGravity(Splitter, 0.5), + wxSplitterWindow:setMinimumPaneSize(Splitter,50), + wxSplitterWindow:splitHorizontally(Splitter, ProcessView, MatchSpecView), + wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]), %% 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 + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + ToggleButton = wxToggleButton:new(Panel, ?TOGGLE_TRACE, "Start Trace", []), + wxSizer:add(Buttons, ToggleButton), + New = wxButton:new(Panel, ?ADD_NEW, [{label, "Trace New Processes"}]), + wxSizer:add(Buttons, New), + ATP = wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}]), + wxSizer:add(Buttons, ATP), + wxMenu:connect(Panel, command_togglebutton_clicked, []), + wxMenu:connect(Panel, command_button_clicked, []), + wxSizer:add(Sizer, Buttons, [{flag, ?wxALL},{border, 2}, {proportion,0}]), 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) -> + {Panel, #state{parent=ParentPid, panel=Panel, + p_view=ProcessView, m_view=ModView, f_view=FuncView, + match_specs=default_matchspecs()}}. + +default_matchspecs() -> + Ms = [{"Return Trace", [{'_', [], [{return_trace}]}], "fun(_) -> return_trace() end"}, + {"Exception Trace", [{'_', [], [{exception_trace}]}], "fun(_) -> exception_trace() end"}, + {"Message Caller", [{'_', [], [{message,{caller}}]}], "fun(_) -> message(caller()) end"}, + {"Message Dump", [{'_', [], [{message,{process_dump}}]}], "fun(_) -> message(process_dump()) end"}], + [make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms]. + +create_process_view(Parent) -> + Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Grid = wxListCtrl:new(Parent, [{winid, ?PROCESSES}, {style, Style}]), + Li = wxListItem:new(), + AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(Grid, Col, Li), + wxListCtrl:setColumnWidth(Grid, Col, DefSize), + Col + 1 + end, + ListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120}, + {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}], + lists:foldl(AddListEntry, 0, ListItems), + wxListItem:destroy(Li), + + %% wxListCtrl:connect(Grid, command_list_item_activated), + %% wxListCtrl:connect(Grid, command_list_item_selected), + wxListCtrl:connect(Grid, size, [{skip, true}]), + + wxWindow:setFocus(Grid), + Grid. + +create_matchspec_view(Parent) -> + Panel = wxPanel:new(Parent), + MainSz = wxBoxSizer:new(?wxHORIZONTAL), + Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Splitter = wxSplitterWindow:new(Panel, []), + Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES}, {style, Style}]), + Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCTIONS}, {style, Style}]), + Li = wxListItem:new(), + wxListItem:setText(Li, "Modules"), + wxListCtrl:insertColumn(Modules, 0, Li), + wxListItem:setText(Li, "Functions"), + wxListCtrl:insertColumn(Funcs, 0, Li), + wxListCtrl:setColumnWidth(Funcs, 0, 150), + wxListItem:setText(Li, "Match Spec"), + wxListCtrl:insertColumn(Funcs, 1, Li), + wxListCtrl:setColumnWidth(Funcs, 1, 300), + wxListItem:destroy(Li), + wxSplitterWindow:setSashGravity(Splitter, 0.0), + wxSplitterWindow:setMinimumPaneSize(Splitter,50), + wxSplitterWindow:splitVertically(Splitter, Modules, Funcs, [{sashPosition, 150}]), + wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), + + wxListCtrl:connect(Modules, size, [{skip, true}]), + wxListCtrl:connect(Funcs, size, [{skip, true}]), + wxListCtrl:connect(Modules, command_list_item_selected), + %% wxListCtrl:connect(Funcs, command_list_item_selected), + wxPanel:setSizer(Panel, MainSz), + {Panel, Modules, Funcs}. + +create_menues(Parent) -> Menus = [{"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"}]} - ], - observer_lib:create_menus(Menus, MenuBar, new_window). - + #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}] + }], + observer_wx:create_menus(Parent, Menus). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %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), +%%Main window +handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) -> + case wx:getObjectType(Obj) =:= wxListCtrl of + true -> + wx:batch(fun() -> + Cols = wxListCtrl:getColumnCount(Obj), + Last = lists:foldl(fun(I, Last) -> + Last - wxListCtrl:getColumnWidth(Obj, I) + end, W-?LCTRL_WDECR, lists:seq(0, Cols - 2)), + Size = max(150, Last), + wxListCtrl:setColumnWidth(Obj, Cols-1, Size) + end); + false -> ok end, {noreply, State}; +handle_event(#wx{id=?ADD_NEW}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> + case observer_traceoptions_wx:process_trace(Parent, TraceOpts) of + {ok, Opts} -> + Process = #tpid{pid=new, opts=Opts}, + {noreply, do_add_processes([Process], State#state{def_trace_opts=Opts})}; + cancel -> + {noreply, State} + end; + +handle_event(#wx{id=?ADD_TP}, + State = #state{panel=Parent, nodes=Nodes, match_specs=Ms}) -> + Node = case Nodes of + [N|_] -> N; + [] -> node() + end, + case observer_traceoptions_wx:trace_pattern(self(), Parent, Node, Ms) of + cancel -> + {noreply, State}; + Patterns -> + {noreply, do_add_patterns(Patterns, State)} + end; + +handle_event(#wx{id=?MODULES, event=#wxList{type=command_list_item_selected, itemIndex=Row}}, + State = #state{tpatterns=TPs, m_view=Mview, f_view=Fview}) -> + Module = list_to_atom(wxListCtrl:getItemText(Mview, Row)), + update_functions_view(dict:fetch(Module, TPs), Fview), + {noreply, State}; + +%% 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}]), + #state{panel = Panel, + def_trace_opts = TraceOpts, + match_specs = MatchSpecs, + tpatterns = TracePatterns + } = State) -> + Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), case wxFileDialog:showModal(Dialog) of ?wxID_OK -> Path = wxFileDialog:getPath(Dialog), - write_file(Frame, Path, TraceOpts, MatchSpecs); + write_file(Panel, Path, TraceOpts, MatchSpecs, dict:to_list(TracePatterns)); _ -> ok end, @@ -189,8 +249,8 @@ handle_event(#wx{id = ?SAVE_TRACEOPTS, 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}]), + #state{panel = Panel} = State) -> + Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_FILE_MUST_EXIST}]), State2 = case wxFileDialog:showModal(Dialog) of ?wxID_OK -> Path = wxFileDialog:getPath(Dialog), @@ -202,92 +262,180 @@ handle_event(#wx{id = ?LOAD_TRACEOPTS, {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 = #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{id=ID, event = What}, State) -> + io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, self(), ID, What]), + {noreply, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handle_call(Msg, _From, State) -> + io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. -handle_event(#wx{event = What}, State) -> - io:format("~p~p: Unhandled event: ~p ~n", [?MODULE, self(), What]), +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_trace_opts=TraceOpts}) -> + case observer_traceoptions_wx:process_trace(Parent, TraceOpts) of + {ok, Opts} -> + POpts = [#tpid{pid=Pid, opts=Opts} || Pid <- Pids], + {noreply, do_add_processes(POpts, State#state{def_trace_opts=Opts})}; + cancel -> + {noreply, State} + end; +handle_cast(Msg, State) -> + io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), {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)), +handle_info({active, _Node}, State=#state{parent=Parent}) -> + create_menues(Parent), + {noreply, State}; + +handle_info(not_active, State) -> {noreply, State}; +handle_info({update_ms, NewMs}, State) -> + {noreply, State#state{match_specs=NewMs}}; + handle_info(Any, State) -> io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]), {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. +terminate(Reason, #state{nodes=Nodes}) -> + %% case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of + %% undefined -> fine; + %% Pid -> exit(Pid, kill) + %% end, + 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}. +do_add_processes(POpts, S0=#state{p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> + case merge_pids(POpts, OldPids) of + {OldPids, [], []} -> + S0; + {Pids, New, Changed} -> + update_process_view(Pids, LCtrl), + Ns1 = lists:usort([node(Pid) || #tpid{pid=Pid} <- New, is_pid(Pid)]), + Nodes = case ordsets:subtract(Ns1, Ns0) of + [] -> Ns0; %% No new Nodes + NewNs -> + %% Handle new nodes + %% BUGBUG add trace patterns for new nodes + ordsets:union(NewNs, Ns0) + end, + S0#state{tpids=Pids, nodes=Nodes} + end. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +update_process_view(Pids, LCtrl) -> + wxListCtrl:deleteAllItems(LCtrl), + wx:foldl(fun(#tpid{pid=Pid, opts=Opts}, Row) -> + _Item = wxListCtrl:insertItem(LCtrl, Row, ""), + ?EVEN(Row) andalso + wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Pid)), + wxListCtrl:setItem(LCtrl, Row, 1, observer_lib:to_str(Opts)), + Row+1 + end, 0, Pids). + +do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) -> + Old = case dict:find(Module, TPs0) of + {ok, Prev} -> Prev; + error -> [] + end, + case merge_patterns(NewPs, Old) of + {Old, [], []} -> + State; + {MPatterns, New, Changed} -> + TPs = dict:store(Module, MPatterns, TPs0), + update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), + update_functions_view(dict:fetch(Module, TPs), Fview), + State#state{tpatterns=TPs} + end. + +update_modules_view(Mods, Module, LCtrl) -> + wxListCtrl:deleteAllItems(LCtrl), + wx:foldl(fun(Mod, Row) -> + _Item = wxListCtrl:insertItem(LCtrl, Row, ""), + ?EVEN(Row) andalso + wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Mod)), + (Mod =:= Module) andalso + wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, ?wxLIST_STATE_SELECTED), + Row+1 + end, 0, Mods). + +update_functions_view(Funcs, LCtrl) -> + wxListCtrl:deleteAllItems(LCtrl), + wx:foldl(fun(#tpattern{fa=FA, ms=#match_spec{str=Ms}}, Row) -> + _Item = wxListCtrl:insertItem(LCtrl, Row, ""), + ?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({func,FA})), + wxListCtrl:setItem(LCtrl, Row, 1, Ms), + Row+1 + end, 0, Funcs). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +merge_pids([N1=#tpid{pid=new}|Ns], [N2=#tpid{pid=new}|Old]) -> + {Pids, New, Changed} = merge_pids_1(Ns,Old), + {[N1|Pids], New, [{N2,N2}|Changed]}; +merge_pids([N1=#tpid{pid=new}|Ns], Old) -> + {Pids, New, Changed} = merge_pids_1(Ns,Old), + {[N1|Pids], [N1|New], Changed}; +merge_pids(Ns, [N2=#tpid{pid=new}|Old]) -> + {Pids, New, Changed} = merge_pids_1(Ns,Old), + {[N2|Pids], New, Changed}; +merge_pids(New, Old) -> + merge_pids_1(New, Old). + +merge_pids_1(New, Old) -> + merge(lists:sort(New), Old, #tpid.pid, [], [], []). + +merge_patterns(New, Old) -> + merge(lists:sort(New), Old, #tpattern.fa, [], [], []). + + +merge([N|Ns], [N|Os], El, New, Ch, All) -> + merge(Ns, Os, El, New, Ch, [N|All]); +merge([N|Ns], [O|Os], El, New, Ch, All) + when element(El, N) == element(El, O) -> + merge(Ns, Os, El, New, [{O,N}|Ch], [N|All]); +merge([N|Ns], Os=[O|_], El, New, Ch, All) + when element(El, N) < element(El, O) -> + merge(Ns, Os, El, [N|New], Ch, [N|All]); +merge(Ns=[N|_], [O|Os], El, New, Ch, All) + when element(El, N) > element(El, O) -> + merge(Ns, Os, El, New, Ch, [O|All]); +merge([], Os, _El, New, Ch, All) -> + {lists:reverse(All, Os), New, Ch}; +merge(Ns, [], _El, New, Ch, All) -> + {lists:reverse(All, Ns), Ns++New, Ch}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start_trace(Node, TracedProcs, TracedDict, #trace_options{send = Send, treceive = Receive, functions = Functions, @@ -385,10 +533,9 @@ print(Num, X, 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}}) -> + match_spec = #match_spec{term = MS}}) -> dbg:tpl({KeyAtom, Function, Arity}, MS) end, RecordList), @@ -396,37 +543,23 @@ trace_functions(TracedDict) -> 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 +write_file(Frame, Filename, TraceOps, MatchSpecs, TPs) -> + FormatMS = fun(#match_spec{name=Id, term=T, func=F}) -> + io_lib:format("[{name,\"~s\"}, {term, ~w}, {func, \"~s\"}]", + [Id, T, F]) + end, + FormatTP = fun({Module, FTPs}) -> + List = format_ftp(FTPs, FormatMS), + io_lib:format("{tp, ~w, [~s]}.~n",[Module, List]) + end, + Str = + ["%%%\n%%% This file is generated by Observer\n", + "%%%\n%%% DO NOT EDIT!\n%%%\n", + [["{ms, ", FormatMS(Ms), "}.\n"] || Ms <- MatchSpecs], + "{traceopts, ", io_lib:format("~w",[TraceOps]) ,"}.\n", + [FormatTP(TP) || TP <- TPs] + ], + case file:write_file(Filename, list_to_binary(Str)) of ok -> success; {error, Reason} -> @@ -434,47 +567,38 @@ write_file(Frame, Filename, #trace_options{send = Send, observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR) end. +format_ftp([#tpattern{fa={F,A}, ms=Ms}], FormatMS) -> + io_lib:format("{~w, ~w, ~s}", [F,A,FormatMS(Ms)]); +format_ftp([#tpattern{fa={F,A}, ms=Ms}|Rest], FormatMS) -> + [io_lib:format("{~w, ~w, ~s},~n ", [F,A,FormatMS(Ms)]), + format_ftp(Rest, FormatMS)]. -read_settings(Filename, #state{frame = Frame} = State) -> +read_settings(Filename, #state{match_specs=Ms0, def_trace_opts=TO0} = State) -> case file:consult(Filename) of {ok, Terms} -> - {TraceOpts, MatchSpecs} = parse_settings(Terms, {#trace_options{}, []}), - State#state{trace_options = TraceOpts, match_specs = MatchSpecs}; + Ms = lists:usort(Ms0 ++ [parse_ms(MsList) || {ms, MsList} <- Terms]), + TOs = lists:usort(TO0 ++ proplists:get_value(traceopts, Terms, [])), + lists:foldl(fun parse_tp/2, + State#state{match_specs=Ms, def_trace_opts=TOs}, + Terms); {error, _} -> - observer_wx:create_txt_dialog(Frame, "Could not load settings", "Error", ?wxICON_ERROR), + observer_wx:create_txt_dialog(State#state.panel, "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]}). +parse_ms(Opts) -> + Name = proplists:get_value(name, Opts, "TracePattern"), + Term = proplists:get_value(term, Opts, [{'_',[],[ok]}]), + FunStr = proplists:get_value(term, Opts, "fun(_) -> ok end"), + make_ms(Name, Term, FunStr). + +make_ms(Name, Term, FunStr) -> + #match_spec{name=Name, term=Term, str=io_lib:format("~w", Term), func = FunStr}. + +parse_tp({tp, Mod, FAs}, State) -> + Patterns = [#tpattern{m=Mod,fa={F,A}, ms=parse_ms(List)} || + {F,A,List} <- FAs], + do_add_patterns({Mod, Patterns}, State); +parse_tp(_, State) -> + State. diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index 7244efdc50..7570610b97 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -21,210 +21,420 @@ -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]) -> +-export([process_trace/2, trace_pattern/4]). + +-compile(export_all). + +process_trace(Parent, Default) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), + Panel = wxPanel:new(Dialog), + MainSz = wxBoxSizer:new(?wxVERTICAL), + PanelSz = wxBoxSizer:new(?wxHORIZONTAL), + LeftSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Tracing options"}]), + RightSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Inheritance options:"}]), + + FuncBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace function call", []), + check_box(FuncBox, lists:member(functions, Default)), + SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []), + check_box(SendBox, lists:member(send, Default)), + RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []), + check_box(RecBox, lists:member('receive', Default)), + EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace process events", []), + check_box(EventBox, lists:member(events, Default)), + + {SpawnBox, SpwnAllRadio, SpwnFirstRadio} = + optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "spawn"), + {LinkBox, LinkAllRadio, LinkFirstRadio} = + optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "link"), + SpawnBool = lists:member(on_spawn, Default) orelse lists:member(on_first_spawn, Default), + LinkBool = lists:member(on_link, Default) orelse lists:member(on_first_link, Default), + check_box(SpawnBox, SpawnBool), + check_box(LinkBox, LinkBool), + enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio]), + enable(LinkBox, [LinkAllRadio, LinkFirstRadio]), + wxRadioButton:setValue(SpwnAllRadio, lists:member(on_spawn, Default)), + wxRadioButton:setValue(SpwnFirstRadio, lists:member(on_first_spawn, Default)), + wxRadioButton:setValue(LinkAllRadio, lists:member(on_link, Default)), + wxRadioButton:setValue(LinkFirstRadio, lists:member(on_first_link, Default)), + + wxSizer:add(LeftSz, FuncBox, []), + wxSizer:add(LeftSz, SendBox, []), + wxSizer:add(LeftSz, RecBox, []), + wxSizer:add(LeftSz, EventBox, []), + wxSizer:add(LeftSz, 150, -1), + + wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}]), + wxSizer:add(PanelSz, RightSz,[{flag, ?wxEXPAND}]), + wxPanel:setSizer(Panel, PanelSz), + wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + wxWindow:setSizerAndFit(Dialog, MainSz), + wxSizer:setSizeHints(MainSz, Dialog), + wxCheckBox:connect(SpawnBox, command_checkbox_clicked, + [{callback, fun(#wx{event=#wxCommand{}},_) -> + enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio]) + end}]), + wxCheckBox:connect(LinkBox, command_checkbox_clicked, + [{callback, fun(#wx{event=#wxCommand{}},_) -> + enable(LinkBox, [LinkAllRadio, LinkFirstRadio]) + end}]), + + Res = case wxDialog:showModal(Dialog) of + ?wxID_OK -> + All = [{SendBox, send}, {RecBox, 'receive'}, + {FuncBox, functions}, {EventBox, events}, + {{SpawnBox, SpwnAllRadio}, on_spawn}, + {{SpawnBox,SpwnFirstRadio}, on_first_spawn}, + {{LinkBox, LinkAllRadio}, on_link}, + {{LinkBox, LinkFirstRadio}, on_first_link}], + Check = fun({Box, Radio}) -> + wxCheckBox:getValue(Box) andalso wxRadioButton:getValue(Radio); + (Box) -> + wxCheckBox:getValue(Box) + end, + Opts = [Id || {Tick, Id} <- All, Check(Tick)], + {ok, lists:reverse(Opts)}; + ?wxID_CANCEL -> + cancel + end, + wxDialog:destroy(Dialog), + Res. + +trace_pattern(ParentPid, Parent, Node, 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{}} + Module = module_selector(Parent, Node), + MFAs = function_selector(Parent, Node, Module), + MatchSpec = select_matchspec(ParentPid, Parent, MatchSpecs), + {Module, [#tpattern{m=M,fa={F,A},ms=MatchSpec} || {M,F,A} <- MFAs]} + catch cancel -> cancel end. +module_selector(Parent, Node) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, + {size, {400, 400}}]), + Panel = wxPanel:new(Dialog), + PanelSz = wxBoxSizer:new(?wxVERTICAL), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY), + ListBox = wxListBox:new(Panel, ?wxID_ANY, [{style, ?wxLB_SINGLE}]), + wxSizer:add(PanelSz, TxtCtrl, [{flag, ?wxEXPAND}]), + wxSizer:add(PanelSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(Panel, PanelSz), + wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}, {proportion, 1}]), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}, {proportion, 0}]), + wxWindow:setSizer(Dialog, MainSz), + OkId = wxDialog:getAffirmativeId(Dialog), + OkButt = wxWindow:findWindowById(OkId), + wxWindow:disable(OkButt), + wxWindow:setFocus(TxtCtrl), + %% init data + Modules = get_modules(Node), + AllModules = [{atom_to_list(X), X} || X <- Modules], + filter_listbox_data("", AllModules, ListBox), + wxTextCtrl:connect(TxtCtrl, command_text_updated, + [{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> + filter_listbox_data(Input, AllModules, ListBox) + end}]), + wxListBox:connect(ListBox, command_listbox_doubleclicked, + [{callback, fun(_, _) -> wxDialog:endModal(Dialog, ?wxID_OK) end}]), + wxListBox:connect(ListBox, command_listbox_selected, + [{callback, fun(_, _) -> wxWindow:enable(OkButt) end}]), + + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + SelId = wxListBox:getSelection(ListBox), + Module = wxListBox:getClientData(ListBox, SelId), + wxDialog:destroy(Dialog), + Module; + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. -setup(ParentFrame, Node, TraceOpts, TracedFuncs, MatchSpecs) -> +function_selector(Parent, Node, Module) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Functions", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, + {size, {400, 400}}]), -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Setup main window + Panel = wxPanel:new(Dialog), + PanelSz = wxBoxSizer:new(?wxVERTICAL), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY), + ListBox = wxCheckListBox:new(Panel, ?wxID_ANY, [{style, ?wxLB_EXTENDED}]), + wxSizer:add(PanelSz, TxtCtrl, [{flag, ?wxEXPAND}]), + wxSizer:add(PanelSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), + SelAllBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Check Visible"}]), + DeSelAllBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Uncheck Visible"}]), + ButtonSz = wxBoxSizer:new(?wxHORIZONTAL), + [wxSizer:add(ButtonSz, Button, []) || Button <- [SelAllBtn, DeSelAllBtn]], + wxSizer:add(PanelSz, ButtonSz, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}, {proportion, 0}]), + wxPanel:setSizer(Panel, PanelSz), + wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}, {proportion, 1}]), + + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}, {proportion, 0}]), + wxWindow:setSizer(Dialog, MainSz), + wxWindow:setFocus(TxtCtrl), + %% Init + Functions = observer_wx:try_rpc(Node, Module, module_info, [functions]), + Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, + not(erl_internal:guard_bif(Name, Arity))]), + ParsedChoices = parse_function_names(Choices), + filter_listbox_data("", ParsedChoices, ListBox), + %% Setup Event handling + wxTextCtrl:connect(TxtCtrl, command_text_updated, + [{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> + filter_listbox_data(Input, ParsedChoices, ListBox) + end}]), + Self = self(), + wxCheckListBox:connect(ListBox, command_checklistbox_toggled, + [{callback, fun(#wx{event=#wxCommand{commandInt=N}}, _) -> + Self ! {ListBox, wxCheckListBox:isChecked(ListBox, N), + wxListBox:getClientData(ListBox, N)} + end}]), + Check = fun(Id, Bool) -> + wxCheckListBox:check(ListBox, Id, [{check, Bool}]), + Self ! {ListBox, Bool, wxListBox:getClientData(ListBox, Id)} + end, + wxButton:connect(SelAllBtn, command_button_clicked, + [{callback, fun(#wx{}, _) -> + Count = wxListBox:getCount(ListBox), + [Check(SelId, true) || + SelId <- lists:seq(0, Count-1), + not wxCheckListBox:isChecked(ListBox, SelId)] + end}]), + wxButton:connect(DeSelAllBtn, command_button_clicked, + [{callback, fun(#wx{}, _) -> + Count = wxListBox:getCount(ListBox), + [Check(SelId, false) || + SelId <- lists:seq(0, Count-1), + wxCheckListBox:isChecked(ListBox, SelId)] + end}]), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + wxDialog:destroy(Dialog), + case get_checked_funcs(ListBox, []) of + [] -> [{Module, '_', '_'}]; + FAs -> + [{Module, F, A} || {F,A} <- FAs] + end; + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. + +get_checked_funcs(ListBox, Acc) -> + receive + {ListBox, true, FA} -> + get_checked_funcs(ListBox, [FA|lists:delete(FA,Acc)]); + {ListBox, false, FA} -> + get_checked_funcs(ListBox, lists:delete(FA,Acc)) + after 0 -> + lists:reverse(Acc) + end. + +select_matchspec(Pid, Parent, MatchSpecs) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Match Specifications", + [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, + {size, {400, 400}}]), - Frame = wxFrame:new(ParentFrame, ?TRACEOPTS_FRAME, "Trace options", - [{style, ?wxRESIZE_BORDER bor ?wxCLOSE_BOX}, - {size, {400, 500}}]), - Panel = wxPanel:new(Frame, []), + Panel = wxPanel:new(Dialog), + PanelSz = wxBoxSizer:new(?wxVERTICAL), MainSz = wxBoxSizer:new(?wxVERTICAL), - Notebook = wxNotebook:new(Panel, ?wxID_ANY), - Modules = get_modules(Node), + TxtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Match specification:"}]), + BtnSz = wxBoxSizer:new(?wxHORIZONTAL), + SavedSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Saved match specifications:"}]), -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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), + TextCtrl = create_styled_txtctrl(Panel), + wxSizer:add(TxtSz, TextCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), - {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}. + AddMsBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "New"}]), + EditMsBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Edit"}]), + DelMsBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Delete"}]), + wxSizer:add(BtnSz, AddMsBtn), + wxSizer:add(BtnSz, EditMsBtn), + wxSizer:add(BtnSz, DelMsBtn), + + ListBox = wxListBox:new(Panel, ?wxID_ANY, []), + wxSizer:add(SavedSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(PanelSz, TxtSz, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(PanelSz, BtnSz), + wxSizer:add(PanelSz, SavedSz, [{flag, ?wxEXPAND}, {proportion, 1}]), + + wxWindow:setSizer(Panel, PanelSz), + wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}, {proportion, 1}]), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}, {proportion, 0}]), + wxWindow:setSizer(Dialog, MainSz), + OkId = wxDialog:getAffirmativeId(Dialog), + OkButt = wxWindow:findWindowById(OkId), + wxWindow:disable(OkButt), + wxWindow:disable(EditMsBtn), + wxWindow:disable(DelMsBtn), + + Choices = ms_names(MatchSpecs), + filter_listbox_data("", Choices, ListBox), + + Add = fun(_,_) -> + case edit_ms(TextCtrl, new, Parent) of + Ms = #match_spec{} -> add_and_select(-1, Ms, ListBox); + Else -> Else + end + end, + Edit = fun(_,_) -> + SelId = wxListBox:getSelection(ListBox), + case SelId >= 0 of + true -> + #match_spec{name=Name} = wxListBox:getClientData(ListBox,SelId), + case edit_ms(TextCtrl, Name, Parent) of + Ms = #match_spec{} -> add_and_select(SelId, Ms, ListBox); + Else -> Else + end; + false -> + ok + end + end, + Del = fun(_,_) -> + SelId = wxListBox:getSelection(ListBox), + case SelId >= 0 of + true -> + wxListBox:delete(ListBox, SelId); + false -> + ok + end + end, + Sel = fun(#wx{event=#wxCommand{commandInt=Id}}, _) -> + case Id >= 0 of + true -> + wxWindow:enable(OkButt), + wxWindow:enable(EditMsBtn), + wxWindow:enable(DelMsBtn), + #match_spec{func=Str} = wxListBox:getClientData(ListBox,Id), + wxStyledTextCtrl:setText(TextCtrl, Str); + false -> + try + wxWindow:disable(OkButt), + wxWindow:disable(EditMsBtn), + wxWindow:disable(DelMsBtn) + catch _:_ -> ok + end + end + end, + wxButton:connect(AddMsBtn, command_button_clicked, [{callback,Add}]), + wxButton:connect(EditMsBtn, command_button_clicked, [{callback,Edit}]), + wxButton:connect(DelMsBtn, command_button_clicked, [{callback,Del}]), + wxListBox:connect(ListBox, command_listbox_selected, [{callback, Sel}]), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + SelId = wxListBox:getSelection(ListBox), + Count = wxListBox:getCount(ListBox), + MSs = [wxListBox:getClientData(ListBox, Id) || + Id <- lists:seq(0, Count-1)], + Pid ! {update_ms, MSs}, + MS = lists:nth(SelId+1, MSs), + wxDialog:destroy(Dialog), + MS; + ?wxID_CANCEL -> + wxDialog:destroy(Dialog), + throw(cancel) + end. + +edit_ms(TextCtrl, Label0, Parent) -> + Str = ensure_last_is_dot(wxStyledTextCtrl:getText(TextCtrl)), + try + MatchSpec = ms_from_string(Str), + Label = case Label0 == new of + true -> get_label(Parent); + _ -> Label0 + end, + #match_spec{name=Label, term=MatchSpec, + str=io_lib:format("~w",[MatchSpec]), + func=Str} + catch + throw:cancel -> + ok; + throw:Error -> + observer_wx:create_txt_dialog(Parent, Error, "Error", ?wxICON_ERROR), + ok + end. + +get_label(Frame) -> + Dialog = wxTextEntryDialog:new(Frame, "Enter alias: "), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + wxTextEntryDialog:getValue(Dialog); + ?wxID_CANCEL -> + throw(cancel) + end. + +ms_from_string(Str) -> + try + Tokens = case erl_scan:string(Str) of + {ok, Ts, _} -> Ts; + {error, {SLine, SMod, SError}, _} -> + throw(io_lib:format("~w: ~s", [SLine,SMod:format_error(SError)])) + end, + Exprs = case erl_parse:parse_exprs(Tokens) of + {ok, T} -> T; + {error, {PLine, PMod, PError}} -> + throw(io_lib:format("~w: ~s", [PLine,PMod:format_error(PError)])) + end, + Term = case Exprs of + [{'fun', _, {clauses, Clauses}}|_] -> + case ms_transform:transform_from_shell(dbg,Clauses,orddict:new()) of + {error, [{_,[{MSLine,Mod,MSInfo}]}],_} -> + throw(io_lib:format("~w: ~p", [MSLine,Mod:format_error(MSInfo)])); + {error, _} -> + throw("Could not convert fun() to match spec"); + Ms -> + Ms + end; + [Expr|_] -> + erl_parse:normalise(Expr) + end, + case erlang:match_spec_test([], Term, trace) of + {ok, _, _, _} -> Term; + {error, List} -> throw([[Error, $\n] || {_, Error} <- List]) + end + catch error:_Reason -> + %% io:format("Bad term: ~s~n ~p in ~p~n", [Str, _Reason, erlang:get_stacktrace()]), + throw("Invalid term") + end. + +add_and_select(Id, MS0, ListBox) -> + [{Str,User}] = ms_names([MS0]), + Sel = case Id >= 0 of + true -> + wxListBox:setString(ListBox, Id, Str), + wxListBox:setClientData(ListBox, Id, User), + Id; + false -> + wxListBox:append(ListBox, Str, User) + end, + wxListBox:setSelection(ListBox, Sel). filter_listbox_data(Input, Data, ListBox) -> - FilteredData = [X || X <- Data, re:run(X, Input) =/= nomatch], + FilteredData = [X || X = {Str, _} <- Data, re:run(Str, Input) =/= nomatch], wxListBox:clear(ListBox), - wxListBox:appendStrings(ListBox, FilteredData), + wxListBox:appendStrings(ListBox, [Str || {Str,_} <- FilteredData]), + wx:foldl(fun({_, Term}, N) -> + wxListBox:setClientData(ListBox, N, Term), + N+1 + end, 0, FilteredData), FilteredData. get_modules(Node) -> @@ -241,41 +451,11 @@ optionpage_top_right(Panel, TopRightSz, Options, Text) -> 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,[]), + FixedFont = observer_wx:get_attrib({font, modern}), Ed = wxStyledTextCtrl:new(Parent), wxStyledTextCtrl:styleClearAll(Ed), wxStyledTextCtrl:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont), @@ -314,14 +494,12 @@ keyWords() -> lists:flatten([K ++ " " || K <- L] ++ [0]). -enable({CheckBox, AllRadio, FirstRadio}) -> +enable(CheckBox, Radio) -> case wxCheckBox:isChecked(CheckBox) of false -> - wxWindow:disable(AllRadio), - wxWindow:disable(FirstRadio); + [wxWindow:disable(R) || R <- Radio]; true -> - wxWindow:enable(AllRadio), - wxWindow:enable(FirstRadio) + [wxWindow:enable(R) || R <- Radio] end. @@ -333,18 +511,14 @@ check_box(ChkBox, Bool) -> 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], + StrList = [{atom_to_list(Name) ++ "/" ++ integer_to_list(Arity), Term} + || Term = {Name, Arity} <- Choices], parse_function_names(StrList, []). parse_function_names([], Acc) -> lists:reverse(Acc); -parse_function_names([H|T], Acc) -> +parse_function_names([{H, Term}|T], Acc) -> IsFun = re:run(H, ".*-fun-\\d*?-"), IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-"), IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-"), @@ -355,209 +529,204 @@ parse_function_names([H|T], Acc) -> true -> H end, - parse_function_names(T, [Parsed | Acc]). + parse_function_names(T, [{Parsed, Term} | Acc]). -show_ms_in_savedlistbox(MatchSpecList) -> - MsOrAlias = fun(#match_spec{alias = A, str_ms = M, fun2ms = F}) -> +ms_names(MatchSpecList) -> + MsOrAlias = fun(#match_spec{name = A, str = M}) -> case A of - undefined -> - if - F =:= undefined -> M; - true -> F - end; - _ -> - A + "" -> M; + _ -> A ++ " " ++ M 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]. + [{MsOrAlias(X), 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([]) -> "."; @@ -569,498 +738,446 @@ ensure_last_is_dot(String) -> 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}. +%% 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_tv_table.erl b/lib/observer/src/observer_tv_table.erl index f744623960..7b5cdb44b9 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -264,7 +264,7 @@ handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> Cols = wxListCtrl:getColumnCount(Grid), Last = lists:foldl(fun(I, Last) -> Last - wxListCtrl:getColumnWidth(Grid, I) - end, W-2, lists:seq(0, Cols - 2)), + end, W-?LCTRL_WDECR, lists:seq(0, Cols - 2)), Size = max(?DEFAULT_COL_WIDTH, Last), wxListCtrl:setColumnWidth(Grid, Cols-1, Size) end), diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 191faf73ff..ded03fadb1 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -131,7 +131,7 @@ handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> Cols = wxListCtrl:getColumnCount(Grid), Last = lists:foldl(fun(I, Last) -> Last - wxListCtrl:getColumnWidth(Grid, I) - end, W-2, lists:seq(0, Cols - 2)), + end, W-?LCTRL_WDECR, lists:seq(0, Cols - 2)), Size = max(200, Last), wxListCtrl:setColumnWidth(Grid, Cols-1, Size) end), diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 45725f03b5..d737faed32 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -20,7 +20,7 @@ -behaviour(wx_object). -export([start/0]). --export([create_menus/2, get_attrib/1, +-export([create_menus/2, get_attrib/1, get_tracer/0, 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, @@ -40,6 +40,8 @@ -define(FIRST_NODES_MENU_ID, 1000). -define(LAST_NODES_MENU_ID, 2000). +-define(TRACE_STR, "Trace Overview"). + %% Records -record(state, {frame, @@ -51,6 +53,7 @@ pro_panel, tv_panel, sys_panel, + trace_panel, active_tab, node, nodes @@ -65,10 +68,13 @@ create_menus(Object, Menus) when is_list(Menus) -> get_attrib(What) -> wx_object:call(observer, {get_attrib, What}). +get_tracer() -> + wx_object:call(observer, get_tracer). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init(_Args) -> + register(observer, self()), wx:new(), catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1), Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer", @@ -80,10 +86,8 @@ init(_Args) -> State = #state{frame = Frame}, UpdState = setup(State), - wxFrame:show(Frame), net_kernel:monitor_nodes(true), process_flag(trap_exit, true), - register(observer, self()), {Frame, UpdState}. setup(#state{frame = Frame} = State) -> @@ -103,13 +107,24 @@ setup(#state{frame = Frame} = State) -> 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", []), + %% Setup sizer create early to get it when window shows + MainSizer = wxBoxSizer:new(?wxVERTICAL), + + wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]), + wxPanel:setSizer(Panel, MainSizer), + + wxNotebook:connect(Notebook, command_notebook_page_changing), + wxFrame:connect(Frame, close_window, [{skip, true}]), + wxMenu:connect(Frame, command_menu_selected), + wxFrame:show(Frame), + + %% I postpone the creation of the other tabs so they can query/use + %% the window size + %% Process Panel ProPanel = observer_pro_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, ProPanel, "Processes", []), @@ -118,12 +133,9 @@ setup(#state{frame = Frame} = State) -> 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_changing), - wxFrame:connect(Frame, close_window, [{skip, true}]), - wxMenu:connect(Frame, command_menu_selected), + %% Trace Viewer Panel + TracePanel = observer_trace_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []), SysPid = wx_object:get_pid(SysPanel), SysPid ! {active, node()}, @@ -134,6 +146,7 @@ setup(#state{frame = Frame} = State) -> sys_panel = SysPanel, pro_panel = ProPanel, tv_panel = TVPanel, + trace_panel = TracePanel, active_tab = SysPid, node = node(), nodes = Nodes @@ -143,6 +156,7 @@ setup(#state{frame = Frame} = State) -> SysFontSize = wxFont:getPointSize(SysFont), Modern = wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), put({font, modern}, Modern), + put({font, fixed}, Modern), UpdState. @@ -262,6 +276,9 @@ handle_call({create_menus, TabMenus}, _From, handle_call({get_attrib, Attrib}, _From, State) -> {reply, get(Attrib), State}; +handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) -> + {reply, TraceP, State}; + handle_call(_Msg, _From, State) -> {reply, ok, State}. @@ -360,11 +377,12 @@ 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}) -> +get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, tv_panel=Tv, trace_panel=Trace}) -> Panel = case check_page_title(Notebook) of "Processes" -> Pro; "System" -> Sys; - "Table Viewer" -> Tv + "Table Viewer" -> Tv; + ?TRACE_STR -> Trace end, wx_object:get_pid(Panel). -- cgit v1.2.3 From 45fb452f5f067c5658a3dfe16495032728bf8a9d Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Fri, 18 Nov 2011 11:06:52 +0100 Subject: [observer] Work around wxWidgets windows bug wxCheckListBox:setClientData crashes on windows --- lib/observer/src/observer_traceoptions_wx.erl | 69 ++++++++++++++++----------- lib/observer/src/observer_wx.erl | 3 ++ 2 files changed, 44 insertions(+), 28 deletions(-) (limited to 'lib/observer') diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index 7570610b97..043126d85f 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -191,36 +191,46 @@ function_selector(Parent, Node, Module) -> Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, not(erl_internal:guard_bif(Name, Arity))]), ParsedChoices = parse_function_names(Choices), - filter_listbox_data("", ParsedChoices, ListBox), + filter_listbox_data("", ParsedChoices, ListBox, false), %% Setup Event handling wxTextCtrl:connect(TxtCtrl, command_text_updated, - [{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> - filter_listbox_data(Input, ParsedChoices, ListBox) - end}]), + [{callback, + fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> + filter_listbox_data(Input, ParsedChoices, ListBox, false) + end}]), Self = self(), + + %% Sigh clientdata in checklistbox crashes on windows, wx-bug I presume. + %% Don't have time to investigate now, workaround file bug report later + GetClientData = fun(LB, N) -> + String = wxListBox:getString(LB, N), + {_, Data} = lists:keyfind(String, 1, ParsedChoices), + Data + end, wxCheckListBox:connect(ListBox, command_checklistbox_toggled, - [{callback, fun(#wx{event=#wxCommand{commandInt=N}}, _) -> - Self ! {ListBox, wxCheckListBox:isChecked(ListBox, N), - wxListBox:getClientData(ListBox, N)} - end}]), + [{callback, + fun(#wx{event=#wxCommand{commandInt=N}}, _) -> + Self ! {ListBox, wxCheckListBox:isChecked(ListBox, N), + GetClientData(ListBox, N)} + end}]), Check = fun(Id, Bool) -> - wxCheckListBox:check(ListBox, Id, [{check, Bool}]), - Self ! {ListBox, Bool, wxListBox:getClientData(ListBox, Id)} - end, + wxCheckListBox:check(ListBox, Id, [{check, Bool}]), + Self ! {ListBox, Bool, GetClientData(ListBox, Id)} + end, wxButton:connect(SelAllBtn, command_button_clicked, - [{callback, fun(#wx{}, _) -> - Count = wxListBox:getCount(ListBox), - [Check(SelId, true) || - SelId <- lists:seq(0, Count-1), - not wxCheckListBox:isChecked(ListBox, SelId)] - end}]), + [{callback, fun(#wx{}, _) -> + Count = wxListBox:getCount(ListBox), + [Check(SelId, true) || + SelId <- lists:seq(0, Count-1), + not wxCheckListBox:isChecked(ListBox, SelId)] + end}]), wxButton:connect(DeSelAllBtn, command_button_clicked, - [{callback, fun(#wx{}, _) -> - Count = wxListBox:getCount(ListBox), - [Check(SelId, false) || - SelId <- lists:seq(0, Count-1), - wxCheckListBox:isChecked(ListBox, SelId)] - end}]), + [{callback, fun(#wx{}, _) -> + Count = wxListBox:getCount(ListBox), + [Check(SelId, false) || + SelId <- lists:seq(0, Count-1), + wxCheckListBox:isChecked(ListBox, SelId)] + end}]), case wxDialog:showModal(Dialog) of ?wxID_OK -> wxDialog:destroy(Dialog), @@ -426,15 +436,18 @@ add_and_select(Id, MS0, ListBox) -> end, wxListBox:setSelection(ListBox, Sel). - filter_listbox_data(Input, Data, ListBox) -> + filter_listbox_data(Input, Data, ListBox, true). + +filter_listbox_data(Input, Data, ListBox, AddClientData) -> FilteredData = [X || X = {Str, _} <- Data, re:run(Str, Input) =/= nomatch], wxListBox:clear(ListBox), wxListBox:appendStrings(ListBox, [Str || {Str,_} <- FilteredData]), - wx:foldl(fun({_, Term}, N) -> - wxListBox:setClientData(ListBox, N, Term), - N+1 - end, 0, FilteredData), + AddClientData andalso + wx:foldl(fun({_, Term}, N) -> + wxListBox:setClientData(ListBox, N, Term), + N+1 + end, 0, FilteredData), FilteredData. get_modules(Node) -> diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index d737faed32..f9dec1ab1b 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -137,6 +137,9 @@ setup(#state{frame = Frame} = State) -> TracePanel = observer_trace_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []), + %% Force redraw (window needs it) + wxWindow:refresh(Panel), + SysPid = wx_object:get_pid(SysPanel), SysPid ! {active, node()}, UpdState = State#state{main_panel = Panel, -- cgit v1.2.3 From 39730743916b300eb3e229c4e6e8a2987487d797 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Fri, 18 Nov 2011 16:14:34 +0100 Subject: [observer] Implemented basic tracing functionality Use ttb which does most of the work already. --- lib/observer/src/observer_procinfo.erl | 2 +- lib/observer/src/observer_trace_wx.erl | 274 ++++++----- lib/observer/src/observer_traceoptions_wx.erl | 658 +------------------------- lib/observer/src/ttb.erl | 44 +- 4 files changed, 195 insertions(+), 783 deletions(-) (limited to 'lib/observer') diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 2600109161..127599a39e 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -137,7 +137,7 @@ init_process_page(Panel, Pid) -> init_text_page(Parent) -> Style = ?wxTE_MULTILINE bor ?wxTE_RICH2 bor ?wxTE_READONLY, Text = wxTextCtrl:new(Parent, ?wxID_ANY, [{style, Style}]), - Font = observer_wx:get_attrib({font, modern}), + Font = observer_wx:get_attrib({font, fixed}), Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), true = wxTextCtrl:setDefaultStyle(Text, Attr), wxTextAttr:destroy(Attr), diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index 23b9b1fe6b..0ab7db121b 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -39,6 +39,7 @@ -define(PROCESSES, 350). -define(MODULES, 351). -define(FUNCTIONS, 352). +-define(TRACERWIN, 353). -record(state, {parent, @@ -91,6 +92,7 @@ create_window(Notebook, ParentPid) -> wxWindow:setSizer(Panel, Sizer), {Panel, #state{parent=ParentPid, panel=Panel, p_view=ProcessView, m_view=ModView, f_view=FuncView, + toggle_button = ToggleButton, match_specs=default_matchspecs()}}. default_matchspecs() -> @@ -204,6 +206,46 @@ handle_event(#wx{id=?MODULES, event=#wxList{type=command_list_item_selected, ite update_functions_view(dict:fetch(Module, TPs), Fview), {noreply, State}; + +handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, + #state{panel = Panel, + nodes = Nodes, + tpids = TProcs, + tpatterns = TPs, + toggle_button = ToggleBtn} = State) -> + LogWin = wxFrame:new(Panel, ?TRACERWIN, "Trace Log", [{size, {750, 800}}]), + Text = wxTextCtrl:new(LogWin, ?wxID_ANY, + [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor + ?wxTE_DONTWRAP bor ?wxTE_READONLY}]), + Font = observer_wx:get_attrib({font, fixed}), + Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), + true = wxTextCtrl:setDefaultStyle(Text, Attr), + Env = wx:get_env(), + Write = fun(Trace) -> + wx:set_env(Env), + wxTextCtrl:appendText(Text, textformat(Trace)) + end, + {ok, _} = ttb:tracer(Nodes, [{file, {local,"/tmp/foo"}}, {shell, {only, Write}}]), + setup_ttb(dict:to_list(TPs), TProcs), + wxFrame:connect(LogWin, close_window, [{skip, true}]), + wxFrame:show(LogWin), + wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), + {noreply, State}; + +handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, + #state{toggle_button = ToggleBtn} = State) -> + %%Stop tracing + ttb:stop(nofetch), + wxToggleButton:setLabel(ToggleBtn, "Start Trace"), + {noreply, State}; + +handle_event(#wx{id=?TRACERWIN, event=#wxClose{}}, + #state{toggle_button = ToggleBtn} = State) -> + %%Stop tracing + ttb:stop(nofetch), + wxToggleButton:setLabel(ToggleBtn, "Start Trace"), + {noreply, State}; + %% handle_event(#wx{id = ?CLEAR, event = #wxCommand{type = command_menu_selected}}, %% #state{text_ctrl = TxtCtrl} = State) -> %% wxTextCtrl:clear(TxtCtrl), @@ -261,28 +303,6 @@ handle_event(#wx{id = ?LOAD_TRACEOPTS, wxDialog:destroy(Dialog), {noreply, State2}; - -%% 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{id=ID, event = What}, State) -> io:format("~p:~p: Unhandled event: ~p, ~p ~n", [?MODULE, self(), ID, What]), {noreply, State}. @@ -321,7 +341,7 @@ handle_info(Any, State) -> io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]), {noreply, State}. -terminate(Reason, #state{nodes=Nodes}) -> +terminate(_Reason, #state{nodes=_Nodes}) -> %% case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of %% undefined -> fine; %% Pid -> exit(Pid, kill) @@ -337,7 +357,7 @@ do_add_processes(POpts, S0=#state{p_view=LCtrl, tpids=OldPids, nodes=Ns0}) -> case merge_pids(POpts, OldPids) of {OldPids, [], []} -> S0; - {Pids, New, Changed} -> + {Pids, New, _Changed} -> update_process_view(Pids, LCtrl), Ns1 = lists:usort([node(Pid) || #tpid{pid=Pid} <- New, is_pid(Pid)]), Nodes = case ordsets:subtract(Ns1, Ns0) of @@ -369,7 +389,7 @@ do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_vi case merge_patterns(NewPs, Old) of {Old, [], []} -> State; - {MPatterns, New, Changed} -> + {MPatterns, _New, _Changed} -> TPs = dict:store(Module, MPatterns, TPs0), update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), update_functions_view(dict:fetch(Module, TPs), Fview), @@ -437,111 +457,115 @@ merge(Ns, [], _El, New, Ch, All) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -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, +setup_ttb(TPs, TPids) -> + _R1 = [setup_tps(FTP, []) || {_, FTP} <- TPs], + _R2 = [ttb:p(Pid, dbg_flags(Flags)) || #tpid{pid=Pid, opts=Flags} <- TPids], + [#tpid{pid=_Pid, opts=_Flags}|_] = TPids, + %% io:format("ttb:p(pid(\"~w\", ~w).", [Pid, Flags]), + %% io:format("TTB ~w ~w~n",[R2, R1]), + ok. - case Functions of - true -> - trace_functions(TracedDict); - false -> - ok +%% Sigh order is important +setup_tps([First=#tpattern{fa={_,'_'}}|Rest], Prev) -> + setup_tp(First), + [setup_tp(TP) || TP <- lists:reverse(Prev)], + setup_tps(Rest, []); +setup_tps([First=#tpattern{fa={F,_}}|Rest], Prev = [#tpattern{fa={F,_}}|_]) -> + setup_tps(Rest, [First|Prev]); +setup_tps([First|Rest], Prev) -> + [setup_tp(TP) || TP <- lists:reverse(Prev)], + setup_tps(Rest, [First]); +setup_tps([], Prev) -> + [setup_tp(TP) || TP <- lists:reverse(Prev)]. + +setup_tp(#tpattern{m=M,fa={F,A}, ms=#match_spec{term=Ms}}) -> + ttb:tpl(M,F,A,Ms). + +dbg_flags(Flags) -> + [dbg_flag(Flag) || Flag <- Flags]. + +dbg_flag(send) -> s; +dbg_flag('receive') -> r; +dbg_flag(functions) -> c; +dbg_flag(on_spawn) -> sos; +dbg_flag(on_link) -> sol; +dbg_flag(on_first_spawn) -> sofs; +dbg_flag(on_first_link) -> sofl; +dbg_flag(events) -> p. + +textformat(Trace) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 -> + format_trace(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace)); +textformat(Trace) when element(1, Trace) == drop, tuple_size(Trace) =:= 2 -> + io_lib:format("*** Dropped ~p messages.~n", [element(2,Trace)]); +textformat(Trace) when element(1, Trace) == seq_trace, tuple_size(Trace) >= 3 -> + io_lib:format("*** Seq trace not implmented.~n", []); +textformat(_) -> + "". + +format_trace(Trace, Size, TS0={_,_,MS}) -> + {_,{H,M,S}} = calendar:now_to_local_time(TS0), + TS = io_lib:format("~.2.0w:~.2.0w:~.2.0w:~.6.0w", [H,M,S,MS]), + From = element(2, Trace), + case element(3, Trace) of + 'receive' -> + case element(4, Trace) of + {dbg,ok} -> ""; + Message -> + io_lib:format("~s (~100p) << ~100p ~n", [TS,From,Message]) + end; + 'send' -> + Message = element(4, Trace), + To = element(5, Trace), + io_lib:format("~s (~100p) ~100p ! ~100p ~n", [TS,From,To,Message]); + call -> + case element(4, Trace) of + MFA when Size == 5 -> + Message = element(5, Trace), + io_lib:format("~s (~100p) call ~s (~100p) ~n", [TS,From,ffunc(MFA),Message]); + MFA -> + io_lib:format("~s (~100p) call ~s ~n", [TS,From,ffunc(MFA)]) + end; + return_from -> + MFA = element(4, Trace), + Ret = element(5, Trace), + io_lib:format("~s (~100p) returned from ~s -> ~100p ~n", [TS,From,ffunc(MFA),Ret]); + return_to -> + MFA = element(4, Trace), + io_lib:format("~s (~100p) returning to ~s ~n", [TS,From,ffunc(MFA)]); + spawn when Size == 5 -> + Pid = element(4, Trace), + MFA = element(5, Trace), + io_lib:format("~s (~100p) spawn ~100p as ~s ~n", [TS,From,Pid,ffunc(MFA)]); + Op -> + io_lib:format("~s (~100p) ~100p ~s ~n", [TS,From,Op,ftup(Trace,4,Size)]) 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(tuple_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). +%%% These f* functions returns non-flat strings + +%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)" +%% {M,F,A} -> "M:F/A" +ffunc({M,F,Argl}) when is_list(Argl) -> + io_lib:format("~100p:~100p(~s)", [M, F, fargs(Argl)]); +ffunc({M,F,Arity}) -> + io_lib:format("~100p:~100p/~100p", [M,F,Arity]); +ffunc(X) -> io_lib:format("~100p", [X]). +%% Integer -> "Integer" +%% [A1, A2, ..., AN] -> "A1, A2, ..., AN" +fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity); 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}}) -> - dbg:tpl({KeyAtom, Function, Arity}, MS) - end, - RecordList), - acc_in - end, - dict:fold(Trace, acc_in, TracedDict). +fargs([A]) -> io_lib:format("~100p", [A]); %% last arg +fargs([A|Args]) -> [io_lib:format("~100p,", [A]) | fargs(Args)]; +fargs(A) -> io_lib:format("~100p", [A]). % last or only arg + +%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size" +ftup(Trace, Index, Index) -> + io_lib:format("~100p", [element(Index, Trace)]); +ftup(Trace, Index, Size) -> + [io_lib:format("~100p ", [element(Index, Trace)]) + | ftup(Trace, Index+1, Size)]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% write_file(Frame, Filename, TraceOps, MatchSpecs, TPs) -> FormatMS = fun(#match_spec{name=Id, term=T, func=F}) -> diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl index 043126d85f..bad05ec016 100644 --- a/lib/observer/src/observer_traceoptions_wx.erl +++ b/lib/observer/src/observer_traceoptions_wx.erl @@ -145,14 +145,22 @@ module_selector(Parent, Node) -> wxListBox:connect(ListBox, command_listbox_doubleclicked, [{callback, fun(_, _) -> wxDialog:endModal(Dialog, ?wxID_OK) end}]), wxListBox:connect(ListBox, command_listbox_selected, - [{callback, fun(_, _) -> wxWindow:enable(OkButt) end}]), + [{callback, fun(#wx{event=#wxCommand{commandInt=Id}}, _) -> + Id >= 0 andalso wxWindow:enable(OkButt) + end}]), case wxDialog:showModal(Dialog) of ?wxID_OK -> SelId = wxListBox:getSelection(ListBox), - Module = wxListBox:getClientData(ListBox, SelId), - wxDialog:destroy(Dialog), - Module; + case SelId >= 0 of + true -> + Module = wxListBox:getClientData(ListBox, SelId), + wxDialog:destroy(Dialog), + Module; + false -> + wxDialog:destroy(Dialog), + throw(cancel) + end; ?wxID_CANCEL -> wxDialog:destroy(Dialog), throw(cancel) @@ -194,7 +202,7 @@ function_selector(Parent, Node, Module) -> filter_listbox_data("", ParsedChoices, ListBox, false), %% Setup Event handling wxTextCtrl:connect(TxtCtrl, command_text_updated, - [{callback, + [{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> filter_listbox_data(Input, ParsedChoices, ListBox, false) end}]), @@ -208,7 +216,7 @@ function_selector(Parent, Node, Module) -> Data end, wxCheckListBox:connect(ListBox, command_checklistbox_toggled, - [{callback, + [{callback, fun(#wx{event=#wxCommand{commandInt=N}}, _) -> Self ! {ListBox, wxCheckListBox:isChecked(ListBox, N), GetClientData(ListBox, N)} @@ -443,11 +451,11 @@ filter_listbox_data(Input, Data, ListBox, AddClientData) -> FilteredData = [X || X = {Str, _} <- Data, re:run(Str, Input) =/= nomatch], wxListBox:clear(ListBox), wxListBox:appendStrings(ListBox, [Str || {Str,_} <- FilteredData]), - AddClientData andalso + AddClientData andalso wx:foldl(fun({_, Term}, N) -> wxListBox:setClientData(ListBox, N, Term), N+1 - end, 0, FilteredData), + end, 0, FilteredData), FilteredData. get_modules(Node) -> @@ -468,7 +476,7 @@ optionpage_top_right(Panel, TopRightSz, Options, Text) -> create_styled_txtctrl(Parent) -> - FixedFont = observer_wx:get_attrib({font, modern}), + FixedFont = observer_wx:get_attrib({font, fixed}), Ed = wxStyledTextCtrl:new(Parent), wxStyledTextCtrl:styleClearAll(Ed), wxStyledTextCtrl:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont), @@ -553,194 +561,6 @@ ms_names(MatchSpecList) -> end, [{MsOrAlias(X), 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) -> @@ -750,447 +570,3 @@ ensure_last_is_dot(String) -> false -> String ++ "." 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/ttb.erl b/lib/observer/src/ttb.erl index 1471be92e5..61fd6d1787 100644 --- a/lib/observer/src/ttb.erl +++ b/lib/observer/src/ttb.erl @@ -75,29 +75,41 @@ do_tracer(Nodes0,PI,Client,Traci) -> do_tracer(Clients,PI,Traci). do_tracer(Clients,PI,Traci) -> - ShellOutput = proplists:get_value(shell, Traci, false), - {ClientSucc,Succ} = + Shell = proplists:get_value(shell, Traci, false), + DefShell = fun(Trace) -> dbg:dhandler(Trace, standard_io) end, + {ClientSucc,Succ} = lists:foldl( - fun({N,{local,File},TF},{CS,S}) -> - TF2 = case ShellOutput of - only -> none; - _ -> TF - end, - [_Sname,Host] = string:tokens(atom_to_list(N),"@"), + fun({N,{local,File},TF},{CS,S}) -> + {TF2, FileInfo, ShellOutput} = + case Shell of + only -> {none, shell_only, DefShell}; + true -> {TF, {file,File}, DefShell}; + {only,Fun} -> {none, shell_only, Fun}; + Fun when is_function(Fun) -> {TF, {file,File}, Fun}; + _ -> {TF, {file,File}, false} + end, + Host = case N of + nonode@nohost -> + {ok, H} = inet:gethostname(), + H; + _ -> + [_,H] = string:tokens(atom_to_list(N),"@"), + H + end, case catch dbg:tracer(N,port,dbg:trace_port(ip,0)) of {ok,N} -> {ok,Port} = dbg:trace_port_control(N,get_listen_port), {ok,T} = dbg:get_tracer(N), rpc:call(N,seq_trace,set_system_tracer,[T]), dbg:trace_client(ip,{Host,Port}, - {fun ip_to_file/2,{{file,File}, ShellOutput}}), + {fun ip_to_file/2,{FileInfo, ShellOutput}}), {[{N,{local,File,Port},TF2}|CS], [N|S]}; Other -> display_warning(N,{cannot_open_ip_trace_port, Host, Other}), {CS, S} - end; + end; ({N,C,_}=Client,{CS,S}) -> case catch dbg:tracer(N,port,dbg:trace_port(file,C)) of {ok,N} -> @@ -620,7 +632,7 @@ stop_opts(Opts) -> case {FormatData, lists:member(return_fetch_dir, Opts)} of {false, true} -> {fetch, FetchDir}; % if we specify return_fetch_dir, the data should be fetched - {false, false} -> + {false, false} -> case lists:member(nofetch,Opts) of false -> {fetch, FetchDir}; true -> nofetch @@ -1275,10 +1287,10 @@ display_warning(Item,Warning) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Trace client which reads an IP port and puts data directly to a file. %%% This is used when tracing remote nodes with no file system. -ip_to_file({metadata,_,_},{_, only} = State) -> +ip_to_file({metadata,_,_},{shell_only, _} = State) -> State; -ip_to_file(Trace, {_, only} = State) -> - dbg:dhandler(Trace, standard_io), +ip_to_file(Trace, {shell_only, Fun} = State) -> + Fun(Trace), State; ip_to_file(Trace,{{file,File}, ShellOutput}) -> Fun = dbg:trace_port(file,File), %File can be a filename or a wrap spec @@ -1302,8 +1314,8 @@ ip_to_file(Trace,{Port, ShellOutput}) -> erlang:port_command(Port,B), {Port, ShellOutput}. -show_trace(Trace, true) -> - dbg:dhandler(Trace, standard_io); +show_trace(Trace, Fun) when is_function(Fun) -> + Fun(Trace); show_trace(_, _) -> ok. -- cgit v1.2.3