aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer/src/observer_trace_wx.erl
diff options
context:
space:
mode:
authorMagnus Eriksson <[email protected]>2011-09-29 13:20:42 +0200
committerDan Gudmundsson <[email protected]>2011-11-08 08:45:36 +0100
commit5e1fafb077740c9d919bc532b2c392f4f20bbf1b (patch)
tree0697735d4c1669b5d7b1bf3e348a650c0a5a2b00 /lib/observer/src/observer_trace_wx.erl
parent766a0a84f0b9e4b9341fb06364bf5430574588a6 (diff)
downloadotp-5e1fafb077740c9d919bc532b2c392f4f20bbf1b.tar.gz
otp-5e1fafb077740c9d919bc532b2c392f4f20bbf1b.tar.bz2
otp-5e1fafb077740c9d919bc532b2c392f4f20bbf1b.zip
[observer] Started on a wx gui
Diffstat (limited to 'lib/observer/src/observer_trace_wx.erl')
-rw-r--r--lib/observer/src/observer_trace_wx.erl487
1 files changed, 487 insertions, 0 deletions
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]}).