%% %% %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) -> 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). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %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(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). 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]}).