aboutsummaryrefslogblamecommitdiffstats
path: root/lib/observer/src/observer_trace_wx.erl
blob: 23b9b1fe6bf9e2dd76ade98c09dd7c6bdbaace0f (plain) (tree)



















                                                                         
                                         













                                                                          

































                                                                                       
                        
                                                                              
                                        






                                                                                               
              









                                                                               
                                    































































                                                                                                              
                                                                                  


                                                                                  

                                                                                












                                                                                                



                     




















































                                                                                                 

                                                                   





                                                                                       


                                                
                                                                                        







                                                                   

                                                                       










                                                         























                                                                                                            

                                                                                


                                                           
 










                                                                                             



                                                                                




                                                            

                     


                                              



                                                                                 





                                                                



                                       
                                                                                
 















                                                                                 
 





















































































                                                                                                 










































































                                                                                  
                                                                




















                                                           

                                                                        
                                                                                           






                                                                                 
















                                                                                   






                                                                                 




                                                               
 
                                                                               

                                  




                                                                                  
                     

                                                                                       


                 














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

-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).
-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
    Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]),
    Sizer = wxBoxSizer:new(?wxVERTICAL),
    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
    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),
    {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"}]
	     }],
    observer_wx:create_menus(Parent, Menus).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%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{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(Panel, Path, TraceOpts, MatchSpecs, dict:to_list(TracePatterns));
	_ ->
	    ok
    end,
    wxDialog:destroy(Dialog),
    {noreply, State};

handle_event(#wx{id = ?LOAD_TRACEOPTS,
		 event = #wxCommand{type = command_menu_selected}},
	     #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),
		     read_settings(Path, State);
		 _ ->
		     State
	     end,
    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}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_call(Msg, _From, State) ->
    io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]),
    {reply, ok, State}.

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

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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,
			   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}}) ->
					  dbg:tpl({KeyAtom, Function, Arity}, MS)
				  end,
				  RecordList),
		    acc_in
	    end,
    dict:fold(Trace, acc_in, TracedDict).

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} ->
	    FailMsg = file:format_error(Reason),
	    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{match_specs=Ms0, def_trace_opts=TO0} = State) ->
    case file:consult(Filename) of
	{ok, Terms} ->
	    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(State#state.panel, "Could not load settings",
					  "Error", ?wxICON_ERROR),
	    State
    end.

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.